import React, { useEffect, useRef, useState } from 'react';

import { Button } from '../shared/ui/controls';

import styles from './ControlApp.module.css';

function formatTime(sec: number): string {
  if (!Number.isFinite(sec) || sec < 0) return '0:00';
  const s = Math.floor(sec);
  const m = Math.floor(s / 60);
  const r = s % 60;
  return `${String(m)}:${String(r).padStart(2, '0')}`;
}

function clampAudioGain(v: number): number {
  if (!Number.isFinite(v)) return 1;
  return Math.max(0, Math.min(1, v));
}

function VolumeSpeakerIcon({ gain }: { gain: number }) {
  if (gain <= 0.001) {
    return (
      <svg viewBox="0 0 24 24" aria-hidden focusable="false">
        <path
          fill="currentColor"
          d="M16.5 12c0-1.77-1.02-3.29-2.5-4.03v2.21l2.45 2.45c.03-.2.05-.41.05-.63zm2.5 0c0 .94-.2 1.82-.54 2.64l1.51 1.51C20.63 14.91 21 13.5 21 12c0-4.28-2.99-7.86-7-8.77v2.06c2.89.86 5 3.54 5 6.71zM4.27 3 3 4.27 7.73 9H3v6h4l5 5v-6.73l4.25 4.25c-.67.52-1.42.93-2.25 1.18v2.06c1.38-.31 2.63-.95 3.69-1.81L19.73 21 21 19.73l-9-9L4.27 3zM12 4 9.91 6.09 12 8.18V4z"
        />
      </svg>
    );
  }
  if (gain < 0.5) {
    return (
      <svg viewBox="0 0 24 24" aria-hidden focusable="false">
        <path
          fill="currentColor"
          d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02z"
        />
      </svg>
    );
  }
  return (
    <svg viewBox="0 0 24 24" aria-hidden focusable="false">
      <path
        fill="currentColor"
        d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z"
      />
    </svg>
  );
}

export type ControlAudioCardProps = {
  assetId: string;
  name: string;
  autoplay: boolean;
  loop: boolean;
  statusLabel: string;
  statusDetail?: string;
  extraBadge?: React.ReactNode;
  audioEl: HTMLAudioElement | null;
  initialGain: number;
  gainMap: Map<string, number>;
  playTitle: string;
  playLabel: string;
  pauseLabel: string;
  stopLabel: string;
  volumeLabel: string;
  modeAutoLabel: string;
  modeManualLabel: string;
  loopLabel: string;
  onceLabel: string;
  scrubSeekLabel: string;
  durationUnknownLabel: string;
  /** Редкий bump родителя (play/pause/error) — не для scrub. */
  onStatusChange: () => void;
  onPlay: () => void;
  onPause: () => void;
  onStop: () => void;
};

/**
 * Карточка трека: scrub/time обновляются локально (RAF → DOM), без ре-рендера всего ControlApp.
 */
export function ControlAudioCard({
  assetId,
  name,
  autoplay,
  loop,
  statusLabel,
  statusDetail,
  extraBadge,
  audioEl,
  initialGain,
  gainMap,
  playTitle,
  playLabel,
  pauseLabel,
  stopLabel,
  volumeLabel,
  modeAutoLabel,
  modeManualLabel,
  loopLabel,
  onceLabel,
  scrubSeekLabel,
  durationUnknownLabel,
  onStatusChange,
  onPlay,
  onPause,
  onStop,
}: ControlAudioCardProps) {
  const scrubRef = useRef<HTMLDivElement | null>(null);
  const scrubFillRef = useRef<HTMLDivElement | null>(null);
  const curTimeRef = useRef<HTMLDivElement | null>(null);
  const durTimeRef = useRef<HTMLDivElement | null>(null);
  const [gainUi, setGainUi] = useState(() => clampAudioGain(initialGain));
  const onStatusChangeRef = useRef(onStatusChange);
  onStatusChangeRef.current = onStatusChange;

  useEffect(() => {
    setGainUi(clampAudioGain(gainMap.get(assetId) ?? initialGain));
  }, [assetId, gainMap, initialGain]);

  useEffect(() => {
    if (!audioEl) return;
    let raf = 0;

    const paint = (): void => {
      const dur = audioEl.duration && Number.isFinite(audioEl.duration) ? audioEl.duration : 0;
      const cur = audioEl.currentTime && Number.isFinite(audioEl.currentTime) ? audioEl.currentTime : 0;
      const pct = dur > 0 ? Math.max(0, Math.min(1, cur / dur)) : 0;
      if (scrubFillRef.current) {
        scrubFillRef.current.style.width = `${String(Math.round(pct * 100))}%`;
      }
      if (curTimeRef.current) curTimeRef.current.textContent = formatTime(cur);
      if (durTimeRef.current) durTimeRef.current.textContent = dur ? formatTime(dur) : '—:—';
      if (scrubRef.current) {
        scrubRef.current.setAttribute('aria-valuemin', '0');
        scrubRef.current.setAttribute('aria-valuemax', String(dur > 0 ? Math.round(dur) : 0));
        scrubRef.current.setAttribute('aria-valuenow', String(Math.round(cur)));
        scrubRef.current.title = dur > 0 ? scrubSeekLabel : durationUnknownLabel;
        scrubRef.current.classList.toggle(styles.audioScrubPointer ?? 'audioScrubPointer', dur > 0);
        scrubRef.current.classList.toggle(styles.audioScrubDefault ?? 'audioScrubDefault', dur <= 0);
      }
    };

    const stopLoop = (): void => {
      if (raf !== 0) {
        window.cancelAnimationFrame(raf);
        raf = 0;
      }
    };

    const loopPaint = (): void => {
      paint();
      if (!audioEl.paused) {
        raf = window.requestAnimationFrame(loopPaint);
      } else {
        raf = 0;
      }
    };

    const startLoop = (): void => {
      stopLoop();
      raf = window.requestAnimationFrame(loopPaint);
    };

    const onPlayEv = (): void => {
      startLoop();
      onStatusChangeRef.current();
    };
    const onPauseEv = (): void => {
      stopLoop();
      paint();
      onStatusChangeRef.current();
    };
    const onEndedEv = (): void => {
      stopLoop();
      paint();
      onStatusChangeRef.current();
    };
    const onMetaEv = (): void => {
      paint();
      onStatusChangeRef.current();
    };

    audioEl.addEventListener('play', onPlayEv);
    audioEl.addEventListener('pause', onPauseEv);
    audioEl.addEventListener('ended', onEndedEv);
    audioEl.addEventListener('canplay', onMetaEv);
    audioEl.addEventListener('error', onMetaEv);
    paint();
    if (!audioEl.paused) startLoop();

    return () => {
      stopLoop();
      audioEl.removeEventListener('play', onPlayEv);
      audioEl.removeEventListener('pause', onPauseEv);
      audioEl.removeEventListener('ended', onEndedEv);
      audioEl.removeEventListener('canplay', onMetaEv);
      audioEl.removeEventListener('error', onMetaEv);
    };
  }, [audioEl, durationUnknownLabel, scrubSeekLabel]);

  const seekByClientX = (clientX: number): void => {
    if (!audioEl || !scrubRef.current) return;
    const dur = audioEl.duration && Number.isFinite(audioEl.duration) ? audioEl.duration : 0;
    if (!dur) return;
    const rect = scrubRef.current.getBoundingClientRect();
    const next = (clientX - rect.left) / Math.max(1, rect.width);
    audioEl.currentTime = Math.max(0, Math.min(dur, next * dur));
    const cur = audioEl.currentTime;
    const pct = Math.max(0, Math.min(1, cur / dur));
    if (scrubFillRef.current) scrubFillRef.current.style.width = `${String(Math.round(pct * 100))}%`;
    if (curTimeRef.current) curTimeRef.current.textContent = formatTime(cur);
  };

  const applyGain = (v: number): void => {
    const g = clampAudioGain(v);
    gainMap.set(assetId, g);
    if (audioEl) {
      try {
        audioEl.volume = g;
      } catch {
        // ignore
      }
    }
    setGainUi(g);
  };

  return (
    <div className={styles.audioCard}>
      <div className={styles.audioMeta}>
        <div className={styles.audioName}>{name}</div>
        <div className={styles.audioBadges}>
          <div>{autoplay ? modeAutoLabel : modeManualLabel}</div>
          <div>{loop ? loopLabel : onceLabel}</div>
          <div title={statusDetail}>{statusLabel}</div>
          {extraBadge}
        </div>
        <div className={styles.spacer10} />
        <div
          ref={scrubRef}
          role="slider"
          tabIndex={0}
          className={[styles.audioScrub, styles.audioScrubDefault].join(' ')}
          onKeyDown={(e) => {
            if (!audioEl) return;
            const dur = audioEl.duration && Number.isFinite(audioEl.duration) ? audioEl.duration : 0;
            if (!dur) return;
            if (e.key === 'ArrowLeft') audioEl.currentTime = Math.max(0, audioEl.currentTime - 5);
            if (e.key === 'ArrowRight') audioEl.currentTime = Math.min(dur, audioEl.currentTime + 5);
            if (curTimeRef.current) curTimeRef.current.textContent = formatTime(audioEl.currentTime);
            const pct = Math.max(0, Math.min(1, audioEl.currentTime / dur));
            if (scrubFillRef.current) scrubFillRef.current.style.width = `${String(Math.round(pct * 100))}%`;
          }}
          onClick={(e) => seekByClientX(e.clientX)}
        >
          <div ref={scrubFillRef} className={styles.scrubFill} style={{ width: '0%' }} />
        </div>
        <div className={styles.timeRow}>
          <div ref={curTimeRef}>0:00</div>
          <div ref={durTimeRef}>—:—</div>
        </div>
      </div>
      <div className={styles.audioControls}>
        <div className={styles.audioTransport}>
          <Button variant="primary" title={playTitle} ariaLabel={playLabel} onClick={onPlay}>
            ▶
          </Button>
          <Button title={pauseLabel} ariaLabel={pauseLabel} onClick={onPause}>
            ❚❚
          </Button>
          <Button
            title={stopLabel}
            ariaLabel={stopLabel}
            onClick={() => {
              onStop();
              if (scrubFillRef.current) scrubFillRef.current.style.width = '0%';
              if (curTimeRef.current) curTimeRef.current.textContent = '0:00';
            }}
          >
            ■
          </Button>
        </div>
        <div className={styles.audioVolumeRow}>
          <span className={styles.audioVolumeIcon} aria-hidden>
            <VolumeSpeakerIcon gain={gainUi} />
          </span>
          <input
            type="range"
            min={0}
            max={1}
            step={0.01}
            value={gainUi}
            disabled={!audioEl}
            className={styles.audioVolume}
            aria-label={volumeLabel}
            title={`${volumeLabel}: ${String(Math.round(gainUi * 100))}%`}
            onChange={(e) => applyGain(Number(e.currentTarget.value))}
          />
        </div>
      </div>
    </div>
  );
}
