'use client';

import { useCallback, useEffect, useRef, useState } from 'react';
import {
  Mic,
  Square,
  Sparkles,
  FileText,
  CheckCircle2,
  AlertCircle,
  Loader2,
  Trash2,
} from 'lucide-react';
import { Button, GlassCard } from '@sehat/ui';
import { api, scribe, ApiClientError } from '@/lib/api-client';
import type {
  ScribeTranscript,
  ScribeTranscriptStatus,
} from '@/lib/scribe-types';
import type { SoapDraftResponse } from '@/lib/copilot-types';
import {
  formatTokenUsage,
  normalizeSoapDraftResponse,
} from '@/lib/copilot-normalize';

interface ScribeRecorderProps {
  appointmentId: string;
}

// Phase mirrors the backend's `ScribeTranscriptStatus` (queued/transcribing/
// ready/failed) plus two UI-only states (idle, recording, uploading) that
// have no backend equivalent. Keeping both 'queued' and 'transcribing'
// distinct lets us show "Waiting for Whisper..." vs "Transcribing audio…"
// — same end state, slightly different copy.
type Phase =
  | 'idle'
  | 'recording'
  | 'uploading'
  | 'queued'
  | 'transcribing'
  | 'ready'
  | 'failed';

const POLL_MS = 2000;

export function ScribeRecorder({ appointmentId }: ScribeRecorderProps) {
  const [phase, setPhase] = useState<Phase>('idle');
  const [elapsed, setElapsed] = useState(0); // seconds while recording
  const [transcriptId, setTranscriptId] = useState<string | null>(null);
  const [transcript, setTranscript] = useState<string>('');
  const [meta, setMeta] = useState<{ language?: string; duration?: number }>({});
  const [error, setError] = useState<string | null>(null);

  // SOAP-generation state.
  const [soapDraft, setSoapDraft] = useState<SoapDraftResponse | null>(null);
  const [subjective, setSubjective] = useState('');
  const [objective, setObjective] = useState('');
  const [assessment, setAssessment] = useState('');
  const [plan, setPlan] = useState('');
  const [followUpInWeeks, setFollowUpInWeeks] = useState<number | undefined>();
  const [generatingSoap, setGeneratingSoap] = useState(false);
  const [savingSoap, setSavingSoap] = useState(false);
  const [savedSoap, setSavedSoap] = useState(false);

  // Refs that survive across renders without retriggering effects.
  const recorderRef = useRef<MediaRecorder | null>(null);
  const chunksRef = useRef<BlobPart[]>([]);
  const streamRef = useRef<MediaStream | null>(null);
  const timerRef = useRef<ReturnType<typeof setInterval> | null>(null);
  const pollRef = useRef<ReturnType<typeof setInterval> | null>(null);

  const stopTimer = useCallback(() => {
    if (timerRef.current) {
      clearInterval(timerRef.current);
      timerRef.current = null;
    }
  }, []);

  const stopPolling = useCallback(() => {
    if (pollRef.current) {
      clearInterval(pollRef.current);
      pollRef.current = null;
    }
  }, []);

  // Cleanup on unmount.
  useEffect(() => {
    return () => {
      stopTimer();
      stopPolling();
      if (recorderRef.current && recorderRef.current.state !== 'inactive') {
        try {
          recorderRef.current.stop();
        } catch {
          // No-op.
        }
      }
      streamRef.current?.getTracks().forEach((t) => t.stop());
    };
  }, [stopPolling, stopTimer]);

  const beginPolling = useCallback(
    (id: string) => {
      stopPolling();
      pollRef.current = setInterval(async () => {
        try {
          const r: ScribeTranscript = await scribe.getTranscript(id);
          if (r.status === 'ready') {
            stopPolling();
            setTranscript(r.transcript ?? '');
            setMeta({
              language: r.languageDetected,
              duration: r.durationSeconds,
            });
            setPhase('ready');
          } else if (r.status === 'failed') {
            stopPolling();
            setError(r.errorMessage ?? 'Transcription failed');
            setPhase('failed');
          } else {
            setPhase(r.status === 'queued' ? 'transcribing' : (r.status as Phase));
          }
        } catch (e) {
          stopPolling();
          setError(
            e instanceof ApiClientError ? e.message : 'Failed to fetch transcript',
          );
          setPhase('failed');
        }
      }, POLL_MS);
    },
    [stopPolling],
  );

  async function startRecording() {
    setError(null);
    setSoapDraft(null);
    setSavedSoap(false);
    setTranscript('');
    setTranscriptId(null);
    setElapsed(0);

    if (typeof window === 'undefined' || !navigator.mediaDevices?.getUserMedia) {
      setError('Audio recording is not supported in this browser');
      return;
    }

    try {
      const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
      streamRef.current = stream;
      const mime = pickMime();
      const recorder = new MediaRecorder(
        stream,
        mime ? { mimeType: mime } : undefined,
      );
      chunksRef.current = [];
      recorder.ondataavailable = (e) => {
        if (e.data && e.data.size > 0) chunksRef.current.push(e.data);
      };
      recorder.onstop = () => {
        const blobMime = recorder.mimeType || 'audio/webm';
        const blob = new Blob(chunksRef.current, { type: blobMime });
        streamRef.current?.getTracks().forEach((t) => t.stop());
        streamRef.current = null;
        void uploadRecording(blob);
      };
      recorder.start();
      recorderRef.current = recorder;
      setPhase('recording');
      timerRef.current = setInterval(() => setElapsed((s) => s + 1), 1000);
    } catch (e) {
      setError(
        e instanceof Error
          ? `Microphone permission denied: ${e.message}`
          : 'Microphone permission denied',
      );
      setPhase('idle');
    }
  }

  function stopRecording() {
    stopTimer();
    const rec = recorderRef.current;
    if (rec && rec.state !== 'inactive') {
      rec.stop();
    }
    recorderRef.current = null;
    setPhase('uploading');
  }

  async function uploadRecording(blob: Blob) {
    setPhase('uploading');
    try {
      const ext = blob.type.includes('mp4')
        ? 'mp4'
        : blob.type.includes('ogg')
          ? 'ogg'
          : 'webm';
      const file = new File([blob], `scribe-${Date.now()}.${ext}`, {
        type: blob.type,
      });
      const r = await scribe.startTranscript(file, appointmentId);
      setTranscriptId(r.transcriptId);
      setPhase(
        r.status === 'ready' ? 'ready' : (r.status as ScribeTranscriptStatus),
      );
      if (r.status !== 'ready') beginPolling(r.transcriptId);
      else {
        // Tiny optimization for the unusual instant-ready path.
        const tr = await scribe.getTranscript(r.transcriptId);
        setTranscript(tr.transcript ?? '');
        setMeta({ language: tr.languageDetected, duration: tr.durationSeconds });
      }
    } catch (e) {
      setError(
        e instanceof ApiClientError ? e.message : 'Failed to upload recording',
      );
      setPhase('failed');
    }
  }

  function discard() {
    stopTimer();
    stopPolling();
    setTranscript('');
    setTranscriptId(null);
    setSoapDraft(null);
    setSavedSoap(false);
    setError(null);
    setElapsed(0);
    setPhase('idle');
  }

  async function convertToSoap() {
    if (!transcriptId) return;
    setGeneratingSoap(true);
    setError(null);
    setSavedSoap(false);
    try {
      const r = await scribe.toSoap(transcriptId);
      const normalized = normalizeSoapDraftResponse(r);
      setSoapDraft(normalized);
      setSubjective(normalized.subjective);
      setObjective(normalized.objective);
      setAssessment(normalized.assessment);
      setPlan(normalized.plan);
      setFollowUpInWeeks(normalized.followUpInWeeks ?? undefined);
    } catch (e) {
      setError(
        e instanceof ApiClientError ? e.message : 'Failed to generate SOAP draft',
      );
    } finally {
      setGeneratingSoap(false);
    }
  }

  async function approveAndSave() {
    setSavingSoap(true);
    setError(null);
    try {
      await api.saveAppointmentNotes(appointmentId, {
        subjective: subjective.trim() || undefined,
        objective: objective.trim() || undefined,
        assessment: assessment.trim() || undefined,
        plan: plan.trim() || undefined,
        followUpInWeeks,
      });
      setSavedSoap(true);
    } catch (e) {
      setError(e instanceof ApiClientError ? e.message : 'Failed to save notes');
    } finally {
      setSavingSoap(false);
    }
  }

  const recording = phase === 'recording';
  const busy =
    phase === 'uploading' || phase === 'transcribing' || phase === 'queued';

  return (
    <div className="space-y-4">
      <div className="rounded-xl border border-surface-border bg-white p-5">
        <div className="flex flex-wrap items-center gap-4">
          <RecordButton
            recording={recording}
            disabled={busy}
            onClick={() => (recording ? stopRecording() : void startRecording())}
          />
          <div className="min-w-0 flex-1">
            <p className="text-sm font-semibold text-ink">
              {recording
                ? 'Recording…'
                : phase === 'uploading'
                  ? 'Uploading audio…'
                  : phase === 'transcribing'
                    ? 'Transcribing…'
                    : phase === 'ready'
                      ? 'Transcript ready'
                      : phase === 'failed'
                        ? 'Something went wrong'
                        : 'Tap to record the consult'}
            </p>
            <p className="text-xs text-ink-muted">
              {recording
                ? formatTime(elapsed)
                : busy
                  ? 'This usually takes a few seconds.'
                  : 'Audio stays on Sehat servers. The patient should be informed.'}
            </p>
          </div>
          {busy ? (
            <Loader2 className="h-5 w-5 animate-spin text-brand" />
          ) : null}
          {phase === 'ready' || phase === 'failed' ? (
            <button
              type="button"
              onClick={discard}
              className="inline-flex items-center gap-1 rounded-lg bg-surface-alt px-3 py-1.5 text-xs font-semibold text-ink-muted hover:bg-error/10 hover:text-error"
            >
              <Trash2 className="h-3.5 w-3.5" /> Discard
            </button>
          ) : null}
        </div>
      </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}

      {phase === 'ready' ? (
        <div className="space-y-3">
          <div className="space-y-1.5">
            <label className="text-sm font-medium text-ink-muted">
              Transcript
              {meta.language ? (
                <span className="ml-2 rounded-pill bg-brand/10 px-2 py-0.5 text-[11px] font-semibold text-brand">
                  {meta.language}
                </span>
              ) : null}
              {typeof meta.duration === 'number' ? (
                <span className="ml-2 text-[11px] text-ink-subtle">
                  {formatTime(meta.duration)}
                </span>
              ) : null}
            </label>
            <textarea
              value={transcript}
              onChange={(e) => setTranscript(e.target.value)}
              rows={8}
              className="w-full rounded-lg border border-surface-border bg-white px-3 py-2 text-sm text-ink focus:border-brand focus:outline-none focus:ring-2 focus:ring-brand/30"
            />
          </div>

          <Button
            type="button"
            variant="primary"
            size="md"
            fullWidth={false}
            loading={generatingSoap}
            disabled={!transcript.trim()}
            onClick={() => void convertToSoap()}
          >
            <Sparkles className="h-4 w-4" /> Convert to SOAP
          </Button>
        </div>
      ) : null}

      {soapDraft ? (
        <SoapDraftView
          subjective={subjective}
          objective={objective}
          assessment={assessment}
          plan={plan}
          followUpInWeeks={followUpInWeeks}
          modelVersion={soapDraft.modelVersion}
          tokensUsed={formatTokenUsage(soapDraft.tokensUsed)}
          saving={savingSoap}
          saved={savedSoap}
          onChangeSubjective={setSubjective}
          onChangeObjective={setObjective}
          onChangeAssessment={setAssessment}
          onChangePlan={setPlan}
          onChangeFollowUp={setFollowUpInWeeks}
          onApprove={() => void approveAndSave()}
        />
      ) : null}
    </div>
  );
}

function RecordButton({
  recording,
  disabled,
  onClick,
}: {
  recording: boolean;
  disabled: boolean;
  onClick: () => void;
}) {
  return (
    <button
      type="button"
      onClick={onClick}
      disabled={disabled}
      aria-label={recording ? 'Stop recording' : 'Start recording'}
      className={[
        'flex h-16 w-16 flex-shrink-0 items-center justify-center rounded-full shadow-brand-glow transition-transform disabled:opacity-50',
        recording
          ? 'animate-pulse bg-error text-white hover:scale-105'
          : 'bg-gradient-hero text-white hover:scale-105',
      ].join(' ')}
    >
      {recording ? (
        <Square className="h-6 w-6" fill="currentColor" />
      ) : (
        <Mic className="h-7 w-7" />
      )}
    </button>
  );
}

/**
 * Reusable SOAP draft editor — same visual treatment used by the SOAP tab
 * in copilot-panel.tsx. Kept local to scribe-recorder for now; can be hoisted
 * to its own file if the doctor-portal grows a third consumer.
 */
function SoapDraftView({
  subjective,
  objective,
  assessment,
  plan,
  followUpInWeeks,
  modelVersion,
  tokensUsed,
  saving,
  saved,
  onChangeSubjective,
  onChangeObjective,
  onChangeAssessment,
  onChangePlan,
  onChangeFollowUp,
  onApprove,
}: {
  subjective: string;
  objective: string;
  assessment: string;
  plan: string;
  followUpInWeeks: number | undefined;
  modelVersion: string;
  tokensUsed: string;
  saving: boolean;
  saved: boolean;
  onChangeSubjective: (v: string) => void;
  onChangeObjective: (v: string) => void;
  onChangeAssessment: (v: string) => void;
  onChangePlan: (v: string) => void;
  onChangeFollowUp: (v: number | undefined) => void;
  onApprove: () => void;
}) {
  return (
    <div className="space-y-4 rounded-xl border border-brand/30 bg-brand/5 p-5">
      <div className="flex items-center gap-2">
        <FileText className="h-4 w-4 text-brand" />
        <p className="text-sm font-bold text-ink">SOAP draft</p>
      </div>
      <Field label="Subjective" value={subjective} onChange={onChangeSubjective} />
      <Field label="Objective" value={objective} onChange={onChangeObjective} />
      <Field label="Assessment" value={assessment} onChange={onChangeAssessment} />
      <Field label="Plan" value={plan} onChange={onChangePlan} />

      <div className="space-y-1.5">
        <label className="text-sm font-medium text-ink-muted">
          Follow-up (weeks)
        </label>
        <input
          type="number"
          min={0}
          value={followUpInWeeks ?? ''}
          onChange={(e) =>
            onChangeFollowUp(
              e.target.value === '' ? undefined : Number(e.target.value),
            )
          }
          className="h-11 w-32 rounded-lg border border-surface-border bg-white px-3 text-sm text-ink focus:border-brand focus:outline-none focus:ring-2 focus:ring-brand/30"
        />
      </div>

      <div className="flex flex-wrap items-center gap-3">
        <Button
          type="button"
          variant="primary"
          size="md"
          fullWidth={false}
          loading={saving}
          onClick={onApprove}
        >
          <CheckCircle2 className="h-4 w-4" /> Approve &amp; save
        </Button>
        {saved ? (
          <span className="inline-flex items-center gap-1 text-sm font-semibold text-green-700">
            <CheckCircle2 className="h-4 w-4" /> Saved to consultation notes
          </span>
        ) : null}
      </div>

      <p className="text-[11px] text-ink-subtle">
        Model: <span className="font-mono">{modelVersion}</span> · Tokens:{' '}
        <span className="font-mono">{tokensUsed}</span>
      </p>
    </div>
  );
}

function Field({
  label,
  value,
  onChange,
}: {
  label: string;
  value: string;
  onChange: (v: string) => void;
}) {
  return (
    <div className="space-y-1.5">
      <label className="text-sm font-semibold text-ink">{label}</label>
      <textarea
        value={value}
        onChange={(e) => onChange(e.target.value)}
        rows={3}
        className="w-full rounded-lg border border-surface-border bg-white px-3 py-2 text-sm text-ink focus:border-brand focus:outline-none focus:ring-2 focus:ring-brand/30"
      />
    </div>
  );
}

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 c of candidates) {
    if (MediaRecorder.isTypeSupported(c)) return c;
  }
  return undefined;
}

function formatTime(s: number): string {
  const mm = Math.floor(s / 60)
    .toString()
    .padStart(2, '0');
  const ss = Math.floor(s % 60)
    .toString()
    .padStart(2, '0');
  return `${mm}:${ss}`;
}
