'use client';

import { useRef, useState } from 'react';
import { AlertCircle, CheckCircle2, Loader2, Mic, Square } from 'lucide-react';
import { GlassCard } from '@sehat/ui';
import { copilot, scribe, ApiClientError } from '@/lib/api-client';
import type {
  UniversalFormField,
  UniversalFormFillResult,
} from '@/lib/copilot-types';

type UniversalScribePhase =
  | 'idle'
  | 'recording'
  | 'transcribing'
  | 'filling'
  | 'ready'
  | 'failed';

interface UniversalFormScribeProps {
  context: string;
  fields: UniversalFormField[];
  existingValues?: Record<string, unknown>;
  onApplyValues: (values: UniversalFormFillResult['values']) => void;
  onResult?: (result: UniversalFormFillResult) => void;
}

/**
 * Drop-in voice-to-form helper for any doctor-portal module.
 *
 * Modules provide their field schema and receive extracted values back through
 * onApplyValues. Missing required fields are surfaced as a concise banner so
 * the user can dictate only the missing details on the next pass.
 */
export function UniversalFormScribe({
  context,
  fields,
  existingValues,
  onApplyValues,
  onResult,
}: UniversalFormScribeProps) {
  const [phase, setPhase] = useState<UniversalScribePhase>('idle');
  const [error, setError] = useState<string | null>(null);
  const [transcript, setTranscript] = useState('');
  const [result, setResult] = useState<UniversalFormFillResult | null>(null);
  const recorderRef = useRef<MediaRecorder | null>(null);
  const streamRef = useRef<MediaStream | null>(null);
  const chunksRef = useRef<BlobPart[]>([]);

  async function start() {
    setError(null);
    setTranscript('');
    setResult(null);
    if (typeof window === 'undefined' || !navigator.mediaDevices?.getUserMedia) {
      setError('Audio recording is not supported in this browser.');
      setPhase('failed');
      return;
    }
    try {
      const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
      const mime = pickMime();
      const recorder = new MediaRecorder(
        stream,
        mime ? { mimeType: mime } : undefined,
      );
      streamRef.current = stream;
      chunksRef.current = [];
      recorder.ondataavailable = (event) => {
        if (event.data.size > 0) chunksRef.current.push(event.data);
      };
      recorder.onstop = () => {
        const blob = new Blob(chunksRef.current, {
          type: recorder.mimeType || 'audio/webm',
        });
        stream.getTracks().forEach((track) => track.stop());
        streamRef.current = null;
        void processAudio(blob);
      };
      recorderRef.current = recorder;
      recorder.start();
      setPhase('recording');
    } catch (e) {
      setError(
        e instanceof Error
          ? `Microphone permission denied: ${e.message}`
          : 'Microphone permission denied',
      );
      setPhase('failed');
    }
  }

  function stop() {
    const recorder = recorderRef.current;
    if (recorder && recorder.state !== 'inactive') recorder.stop();
    recorderRef.current = null;
    setPhase('transcribing');
  }

  async function processAudio(blob: Blob) {
    try {
      setPhase('transcribing');
      const text = await scribe.transcribeClip(blob);
      setTranscript(text.transcript);
      setPhase('filling');
      const filled = await copilot.formFill({
        transcript: text.transcript,
        context,
        fields,
        existingValues,
      });
      setResult(filled);
      onApplyValues(filled.values);
      onResult?.(filled);
      setPhase('ready');
    } catch (e) {
      setError(
        e instanceof ApiClientError ? e.message : 'Could not fill the form from audio.',
      );
      setPhase('failed');
    }
  }

  const busy = phase === 'transcribing' || phase === 'filling';
  const recording = phase === 'recording';
  const missingPrompts = result?.missingFields.map((field) => field.prompt) ?? [];

  return (
    <div className="space-y-3">
      {missingPrompts.length > 0 ? (
        <GlassCard tone="light" padding="md" className="border-accent/40 bg-accent/10">
          <div className="flex items-start gap-2 text-accent-dark">
            <AlertCircle className="mt-0.5 h-4 w-4 flex-shrink-0" />
            <div className="space-y-1">
              <p className="text-sm font-bold">More detail needed</p>
              <p className="text-sm">{missingPrompts.join(' ')}</p>
            </div>
          </div>
        </GlassCard>
      ) : null}

      <div className="flex flex-wrap items-center gap-3 rounded-lg border border-surface-border bg-white p-3">
        <button
          type="button"
          onClick={recording ? stop : () => void start()}
          disabled={busy}
          className={[
            'flex h-11 w-11 items-center justify-center rounded-full text-white disabled:opacity-50',
            recording ? 'bg-error' : 'bg-brand',
          ].join(' ')}
          aria-label={recording ? 'Stop voice fill' : 'Start voice fill'}
        >
          {recording ? <Square className="h-5 w-5" /> : <Mic className="h-5 w-5" />}
        </button>
        <div className="min-w-0 flex-1">
          <p className="text-sm font-semibold text-ink">
            {recording
              ? 'Listening...'
              : phase === 'transcribing'
                ? 'Transcribing...'
                : phase === 'filling'
                  ? 'Filling form...'
                  : phase === 'ready'
                    ? 'Form filled from voice'
                    : 'Fill this form by voice'}
          </p>
          {transcript ? (
            <p className="truncate text-xs text-ink-muted">{transcript}</p>
          ) : (
            <p className="text-xs text-ink-muted">
              Dictate the fields naturally. Missing required details appear above.
            </p>
          )}
        </div>
        {busy ? <Loader2 className="h-5 w-5 animate-spin text-brand" /> : null}
        {result && missingPrompts.length === 0 ? (
          <span className="inline-flex items-center gap-1 text-sm font-semibold text-green-700">
            <CheckCircle2 className="h-4 w-4" /> Complete
          </span>
        ) : null}
      </div>

      {error ? (
        <GlassCard tone="light" padding="md" className="border-error/40 bg-error/10">
          <div className="flex items-start gap-2 text-error">
            <AlertCircle className="mt-0.5 h-4 w-4 flex-shrink-0" />
            <p className="text-sm">{error}</p>
          </div>
        </GlassCard>
      ) : null}
    </div>
  );
}

export function applyUniversalScribeValues<T extends Record<string, unknown>>(
  current: T,
  values: UniversalFormFillResult['values'],
): T {
  return { ...current, ...values };
}

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