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

import { MicIcon, SpinnerIcon, XIcon } from './icons';

interface Props {
  /** Called with the recorded audio file when the user confirms. The button
   *  ships as a single-attachment message, so the parent can feed it into
   *  the normal send flow. */
  onComplete: (file: File) => Promise<void> | void;
  disabled?: boolean;
}

// Single-button voice recorder. Click to start; shows an inline pill with
// elapsed time + stop + cancel while recording. On stop, hands a File to the
// parent. No in-place preview yet — we lean on the optimistic message bubble
// to appear once the parent sends.
const MAX_RECORD_SEC = 60;

export function VoiceRecorder({ onComplete, disabled = false }: Props) {
  const [state, setState] = useState<'idle' | 'recording' | 'finalizing'>('idle');
  const [elapsedSec, setElapsedSec] = useState(0);
  const [level, setLevel] = useState(0);
  const recorderRef = useRef<MediaRecorder | null>(null);
  const chunksRef = useRef<Blob[]>([]);
  const streamRef = useRef<MediaStream | null>(null);
  const startedAtRef = useRef<number>(0);
  const cancelledRef = useRef(false);
  const audioCtxRef = useRef<AudioContext | null>(null);
  const analyserRef = useRef<AnalyserNode | null>(null);
  const rafRef = useRef<number>(0);

  // Tick elapsed + auto-stop at MAX_RECORD_SEC.
  useEffect(() => {
    if (state !== 'recording') return;
    const id = window.setInterval(() => {
      const sec = Math.floor((Date.now() - startedAtRef.current) / 1000);
      setElapsedSec(sec);
      if (sec >= MAX_RECORD_SEC) {
        const rec = recorderRef.current;
        if (rec && rec.state !== 'inactive') rec.stop();
      }
    }, 200);
    return () => {
      window.clearInterval(id);
    };
  }, [state]);

  // Live mic level meter via Web Audio AnalyserNode. RMS on 0..1, smoothed.
  useEffect(() => {
    if (state !== 'recording') return;
    const analyser = analyserRef.current;
    if (!analyser) return;
    const buf = new Uint8Array(analyser.frequencyBinCount);
    let cancelled = false;
    const tick = () => {
      if (cancelled) return;
      analyser.getByteFrequencyData(buf as Uint8Array<ArrayBuffer>);
      let sum = 0;
      for (let i = 0; i < buf.length; i++) sum += buf[i]!;
      setLevel(sum / (buf.length * 255));
      rafRef.current = window.requestAnimationFrame(tick);
    };
    rafRef.current = window.requestAnimationFrame(tick);
    return () => {
      cancelled = true;
      window.cancelAnimationFrame(rafRef.current);
    };
  }, [state]);

  const stopStream = () => {
    if (streamRef.current) {
      streamRef.current.getTracks().forEach((t) => t.stop());
      streamRef.current = null;
    }
    if (audioCtxRef.current) {
      void audioCtxRef.current.close().catch(() => {});
      audioCtxRef.current = null;
    }
    analyserRef.current = null;
    setLevel(0);
  };

  const start = async () => {
    if (disabled) return;
    try {
      const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
      streamRef.current = stream;

      try {
        const ctx = new (
          window.AudioContext ||
          (window as unknown as { webkitAudioContext: typeof AudioContext }).webkitAudioContext
        )();
        const source = ctx.createMediaStreamSource(stream);
        const analyser = ctx.createAnalyser();
        analyser.fftSize = 256;
        analyser.smoothingTimeConstant = 0.4;
        source.connect(analyser);
        audioCtxRef.current = ctx;
        analyserRef.current = analyser;
      } catch {
        /* Analyser is best-effort; recording still works without level meter. */
      }

      const mime = pickMime();
      const rec = new MediaRecorder(stream, mime ? { mimeType: mime } : undefined);
      recorderRef.current = rec;
      chunksRef.current = [];
      cancelledRef.current = false;

      rec.ondataavailable = (ev: BlobEvent) => {
        if (ev.data && ev.data.size > 0) chunksRef.current.push(ev.data);
      };
      rec.onstop = () => {
        const blob = new Blob(chunksRef.current, { type: rec.mimeType || 'audio/webm' });
        chunksRef.current = [];
        stopStream();
        if (cancelledRef.current || blob.size === 0) {
          setState('idle');
          setElapsedSec(0);
          return;
        }
        setState('finalizing');
        const ext = extFor(blob.type);
        const filename = 'voice-' + new Date().toISOString().replace(/[:.]/g, '-') + '.' + ext;
        const file = new File([blob], filename, { type: blob.type });
        void Promise.resolve(onComplete(file)).finally(() => {
          setState('idle');
          setElapsedSec(0);
        });
      };

      rec.start();
      startedAtRef.current = Date.now();
      setElapsedSec(0);
      setState('recording');
    } catch (err: unknown) {
      stopStream();
      // Surface device-permission denials or capture failures via console;
      // the composer doesn't have space for inline errors here and the
      // browser already shows a system-level permission prompt.
      console.error('VoiceRecorder.start failed', err);
    }
  };

  const confirm = () => {
    const rec = recorderRef.current;
    if (!rec) return;
    if (rec.state !== 'inactive') rec.stop();
  };

  const cancel = () => {
    cancelledRef.current = true;
    const rec = recorderRef.current;
    if (rec && rec.state !== 'inactive') rec.stop();
    else {
      stopStream();
      setState('idle');
      setElapsedSec(0);
    }
  };

  if (state === 'idle') {
    return (
      <button
        type="button"
        onClick={() => void start()}
        disabled={disabled}
        aria-label="Sprachnachricht aufnehmen"
        title="Sprachnachricht aufnehmen"
        className="inline-flex h-10 w-10 shrink-0 cursor-pointer items-center justify-center rounded-lg text-fg-muted transition hover:bg-surface-3 hover:text-fg focus:outline-none focus-visible:ring-2 focus-visible:ring-accent/50 disabled:cursor-not-allowed disabled:opacity-60 dark:hover:bg-[#313338]"
      >
        <MicIcon className="h-4 w-4" />
      </button>
    );
  }

  if (state === 'finalizing') {
    return (
      <div className="inline-flex h-10 items-center gap-2 rounded-lg px-3 text-xs text-fg-muted">
        <SpinnerIcon className="h-4 w-4" />
        <span>Wird gesendet…</span>
      </div>
    );
  }

  // Visual level: scale from 0..1 → 0..100% width. Floor at 4% so the bar
  // stays visible when silent.
  const levelPct = Math.max(4, Math.min(100, Math.round(level * 180)));
  const remaining = Math.max(0, MAX_RECORD_SEC - elapsedSec);

  return (
    <div className="inline-flex h-10 items-center gap-2 rounded-lg border border-rose-500/40 bg-rose-500/10 px-3 text-xs font-semibold text-rose-700 dark:text-rose-200">
      <span className="h-2 w-2 animate-pulse rounded-full bg-rose-500" />
      <span className="tabular-nums">{formatTime(elapsedSec)}</span>
      <div
        aria-hidden="true"
        className="relative h-1.5 w-20 overflow-hidden rounded-full bg-rose-500/20"
      >
        <span
          className="absolute inset-y-0 left-0 rounded-full bg-rose-500 transition-[width] duration-75"
          style={{ width: levelPct + '%' }}
        />
      </div>
      <span className="tabular-nums text-[10px] text-rose-700/70 dark:text-rose-200/70">
        −{remaining}s
      </span>
      <button
        type="button"
        onClick={cancel}
        aria-label="Aufnahme abbrechen"
        title="Abbrechen"
        className="flex h-7 w-7 cursor-pointer items-center justify-center rounded-md text-fg-muted hover:bg-surface-3 hover:text-fg"
      >
        <XIcon className="h-3.5 w-3.5" />
      </button>
      <button
        type="button"
        onClick={confirm}
        aria-label="Aufnahme senden"
        title="Senden"
        className="flex h-7 w-7 cursor-pointer items-center justify-center rounded-md bg-accent text-accent-fg transition hover:brightness-110"
      >
        <MicIcon className="h-3.5 w-3.5" />
      </button>
    </div>
  );
}

function pickMime(): string | null {
  const candidates = ['audio/webm;codecs=opus', 'audio/webm', 'audio/ogg;codecs=opus', 'audio/mp4'];
  for (const c of candidates) {
    if (typeof MediaRecorder !== 'undefined' && MediaRecorder.isTypeSupported(c)) return c;
  }
  return null;
}

function extFor(mime: string): string {
  if (mime.includes('webm')) return 'webm';
  if (mime.includes('ogg')) return 'ogg';
  if (mime.includes('mp4')) return 'm4a';
  return 'bin';
}

function formatTime(sec: number): string {
  const m = Math.floor(sec / 60);
  const s = sec % 60;
  return m + ':' + s.toString().padStart(2, '0');
}
