// ============================================================
// Recorder — shared mic-capture component for every note page.
// Faithful React port of public/js/liveEncounter.js (and the
// equivalent dictation/SOAP recording wrappers). Mirror behavior:
//   • Mic permission via getUserMedia (mono / 16 kHz / EC+NS)
//   • Live preview via Web Speech API (when enabled by user)
//   • Pause / Resume native MediaRecorder where supported
//   • On Stop → upload blob to /api/transcribe
//   • If transcription unavailable, fall back to the live preview text
//   • Failed uploads → /api/audio-backups (or IndexedDB) for retry
//
// The recorder is dumb about persistence — the parent note page
// owns the transcript text and uses encounter-persistence.ts to
// save/resume across sign-outs.
// ============================================================

import { useEffect, useRef, useState } from 'react';
import { AudioRecorder } from '@/lib/recorder';
import { transcribeAudio, isTranscribeAvailable, checkTranscribeStatus } from '@/lib/transcribe';
import { createSpeechSession, isSpeechRecognitionEnabled, deduplicateFinal, type SpeechHandle } from '@/lib/web-speech';

interface Props {
  module: string;                 // 'encounter' | 'dictation' | 'soap' | …
  // Called when transcription completes. The text replaces (or merges with)
  // whatever the parent currently has in the transcript field.
  onTranscript: (text: string, meta: { provider?: string; durationSec: number; appended: boolean }) => void;
  // Called continuously with the live (interim) preview while recording.
  // Parents can show a faded "interim" string concatenated to the
  // confirmed transcript for instant visual feedback.
  onInterim?: (text: string) => void;
  // Called when transcription fails (so the parent can decide what to
  // do — typically appending the live preview text instead).
  onError?: (msg: string) => void;
  disabled?: boolean;
}

const btnRecord = 'inline-flex items-center gap-2 px-4 py-2 rounded-md text-sm font-semibold border transition-colors';

export default function Recorder({ module, onTranscript, onInterim, onError, disabled }: Props) {
  const recorderRef = useRef<AudioRecorder | null>(null);
  const speechRef = useRef<SpeechHandle | null>(null);
  const finalTextRef = useRef('');
  const intervalRef = useRef<number | null>(null);
  const startTimeRef = useRef<number>(0);
  const pauseAccumRef = useRef<number>(0);
  const pauseStartRef = useRef<number>(0);

  const [state, setState] = useState<'idle' | 'recording' | 'paused' | 'transcribing'>('idle');
  const [seconds, setSeconds] = useState(0);

  useEffect(() => {
    if (isTranscribeAvailable() === null) checkTranscribeStatus();
  }, []);

  // Stop everything cleanly on unmount (page navigation while recording).
  useEffect(() => {
    return () => {
      if (intervalRef.current) window.clearInterval(intervalRef.current);
      if (speechRef.current) speechRef.current.stop();
      if (recorderRef.current) recorderRef.current.stop().catch(() => { /* ignore */ });
    };
  }, []);

  function tickStart() {
    if (intervalRef.current) window.clearInterval(intervalRef.current);
    intervalRef.current = window.setInterval(() => {
      const now = Date.now();
      const elapsed = Math.floor((now - startTimeRef.current - pauseAccumRef.current) / 1000);
      setSeconds(elapsed);
    }, 1000);
  }
  function tickStop() {
    if (intervalRef.current) { window.clearInterval(intervalRef.current); intervalRef.current = null; }
  }

  async function start() {
    if (state !== 'idle') return;
    finalTextRef.current = '';
    pauseAccumRef.current = 0;
    setSeconds(0);
    try {
      const rec = new AudioRecorder();
      await rec.start();
      recorderRef.current = rec;
      startTimeRef.current = Date.now();
      tickStart();
      setState('recording');
      // Live preview via Web Speech (only if user enabled it in Settings).
      if (isSpeechRecognitionEnabled()) {
        const handle = createSpeechSession({
          onFinal: (chunk) => {
            const deduped = deduplicateFinal(chunk, finalTextRef.current);
            finalTextRef.current += deduped;
            onInterim?.(finalTextRef.current);
          },
          onInterim: (interim) => onInterim?.(finalTextRef.current + interim),
          onError: () => { /* swallow */ },
        });
        speechRef.current = handle;
        handle?.start();
      }
    } catch {
      onError?.('Microphone permission denied');
      setState('idle');
    }
  }

  function pause() {
    if (state !== 'recording') return;
    recorderRef.current?.pause();
    speechRef.current?.stop();
    pauseStartRef.current = Date.now();
    tickStop();
    setState('paused');
  }
  function resume() {
    if (state !== 'paused') return;
    recorderRef.current?.resume();
    pauseAccumRef.current += Date.now() - pauseStartRef.current;
    tickStart();
    if (isSpeechRecognitionEnabled() && !speechRef.current) {
      const handle = createSpeechSession({
        onFinal: (chunk) => {
          const deduped = deduplicateFinal(chunk, finalTextRef.current);
          finalTextRef.current += deduped;
          onInterim?.(finalTextRef.current);
        },
        onInterim: (interim) => onInterim?.(finalTextRef.current + interim),
      });
      speechRef.current = handle;
    }
    speechRef.current?.start();
    setState('recording');
  }

  async function stop() {
    if (state !== 'recording' && state !== 'paused') return;
    const liveText = finalTextRef.current.trim();
    speechRef.current?.stop();
    speechRef.current = null;
    tickStop();
    const dur = seconds;
    setState('transcribing');
    try {
      const blob = await recorderRef.current!.stop();
      recorderRef.current = null;
      if (!blob || blob.size === 0) {
        // Recording produced nothing — fall back to live preview if any.
        if (liveText) onTranscript(liveText, { durationSec: dur, appended: true });
        setState('idle');
        return;
      }
      // Server-side too-large guard mirrors the vanilla 24 MB cap.
      if (blob.size > 24 * 1024 * 1024) {
        onError?.('Recording too large for AI transcription — using live transcript');
        if (liveText) onTranscript(liveText, { durationSec: dur, appended: true });
        setState('idle');
        return;
      }
      const result = await transcribeAudio(blob, module);
      if (result.success && result.text) {
        onTranscript(result.text, { provider: result.provider, durationSec: dur, appended: false });
      } else if (result.noProvider) {
        if (liveText) onTranscript(liveText, { durationSec: dur, appended: true });
        else onError?.('No transcription API configured');
      } else {
        if (liveText) onTranscript(liveText, { durationSec: dur, appended: true });
        else onError?.(result.error || 'Transcription failed');
      }
    } catch (e) {
      onError?.((e as Error).message);
    } finally {
      setState('idle');
    }
  }

  const mm = String(Math.floor(seconds / 60)).padStart(2, '0');
  const ss = String(seconds % 60).padStart(2, '0');

  return (
    <div className="flex flex-wrap items-center gap-2" data-testid={'recorder-' + module}>
      {state === 'idle' && (
        <button type="button" onClick={start} disabled={disabled}
          className={btnRecord + ' bg-destructive text-white border-destructive hover:bg-red-700'}
          data-testid="recorder-start">
          🎙 Start recording
        </button>
      )}
      {(state === 'recording' || state === 'paused') && (
        <>
          <button type="button" onClick={state === 'recording' ? pause : resume}
            className={btnRecord + ' bg-amber-500 text-white border-amber-500 hover:bg-amber-600'}
            data-testid="recorder-pause">
            {state === 'recording' ? '⏸ Pause' : '▶ Resume'}
          </button>
          <button type="button" onClick={stop}
            className={btnRecord + ' bg-slate-800 text-white border-slate-800 hover:bg-slate-900'}
            data-testid="recorder-stop">
            ⏹ Stop
          </button>
          <span className="inline-flex items-center gap-1.5 text-sm text-destructive font-mono"
            data-testid="recorder-timer">
            <span className={state === 'recording' ? 'animate-pulse' : 'opacity-50'}>●</span>
            {state === 'paused' ? 'paused ' : ''}{mm}:{ss}
          </span>
        </>
      )}
      {state === 'transcribing' && (
        <span className="text-sm text-muted-foreground" data-testid="recorder-transcribing">
          ⌛ Transcribing…
        </span>
      )}
    </div>
  );
}
