// VoiceScribe.jsx — Practice Suite voice → dental SOAP draft (CA §632 consent, HITL, AB 3030).
const { useState, useRef, useCallback, useEffect } = React;
const CONSENT_KEY = 'td_voice_consent';
const AB3030 =
  'AI-assisted draft for clinician review only — not a diagnosis or chart submission. ' +
  'Confirm with a licensed dentist. California AB 3030 disclosure applies.';

function AB3030Banner() {
  return (
    <div className="banner info" style={{ marginBottom: 14 }}>
      <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="var(--blue)" strokeWidth="1.8">
        <path d="M12 4l8.5 15H3.5z" /><path d="M12 10v4.5M12 17.4v.1" />
      </svg>
      <div>{AB3030}</div>
    </div>
  );
}

function HitlBanner() {
  return (
    <div className="banner warn" style={{ marginBottom: 14 }}>
      <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="var(--seal)" strokeWidth="1.8">
        <path d="M12 4l8.5 15H3.5z" /><path d="M12 10v4.5M12 17.4v.1" />
      </svg>
      <div>
        <b>Draft for clinician review — not submitted to chart.</b>{' '}
        Status: <b>PENDING_HITL_REVIEW</b>. Information only — not diagnosis.
      </div>
    </div>
  );
}

function VoiceScribe({ compact = false }) {
  const [consentOk, setConsentOk] = useState(() => {
    try { return !!localStorage.getItem(CONSENT_KEY); } catch { return false; }
  });
  const [showConsent, setShowConsent] = useState(false);
  const [recording, setRecording] = useState(false);
  const [status, setStatus] = useState(''); // listening | processing | structuring
  const [err, setErr] = useState(null);
  const [transcript, setTranscript] = useState('');
  const [note, setNote] = useState(null);
  const [formatted, setFormatted] = useState('');
  const [source, setSource] = useState('');
  const [copied, setCopied] = useState(false);
  const [savedId, setSavedId] = useState(null);
  const [txLinked, setTxLinked] = useState(false);

  const mediaRef = useRef(null);
  const chunksRef = useRef([]);
  const recognitionRef = useRef(null);

  const grantConsent = useCallback(() => {
    try { localStorage.setItem(CONSENT_KEY, new Date().toISOString()); } catch { /* */ }
    setConsentOk(true);
    setShowConsent(false);
    return true;
  }, []);

  const persistScribeDraft = useCallback((t, soapNote, fmt, src) => {
    if (!window.SuiteState || !soapNote) return;
    const saved = window.SuiteState.saveScribeNote({
      transcript: t,
      note: soapNote,
      formatted: fmt,
      source: src,
    });
    setSavedId(saved.id);
    const tx = window.SuiteState.getTreatmentPlan();
    setTxLinked(!!(tx && tx.scribeRef === saved.id));
  }, []);

  const structureDraft = useCallback(async (text) => {
    const t = (text || '').trim();
    if (!t) return;
    setStatus('structuring');
    setErr(null);
    try {
      const resp = await fetch('/api/scribe', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ transcript: t }),
      });
      const d = await resp.json().catch(() => ({}));
      if (!resp.ok || d.ok === false) {
        if (window.ScribeStructure) {
          const local = window.ScribeStructure.structureSOAP(t);
          setNote(local);
          setFormatted(window.ScribeStructure.formatNote(local, { disclaimer: AB3030 }));
          setSource('rules_local');
          persistScribeDraft(t, local, window.ScribeStructure.formatNote(local, { disclaimer: AB3030 }), 'rules_local');
        } else {
          setErr(d.error || 'Could not structure note. Edit transcript manually.');
        }
        return;
      }
      setTranscript(d.transcript || t);
      setNote(d.note || null);
      setFormatted(d.formatted || '');
      setSource(d.source || 'rules');
      persistScribeDraft(d.transcript || t, d.note || null, d.formatted || '', d.source || 'rules');
    } catch {
      if (window.ScribeStructure) {
        const local = window.ScribeStructure.structureSOAP(t);
        const fmt = window.ScribeStructure.formatNote(local, { disclaimer: AB3030 });
        setNote(local);
        setFormatted(fmt);
        setSource('rules_local');
        persistScribeDraft(t, local, fmt, 'rules_local');
      } else {
        setErr('Network error structuring note.');
      }
    } finally {
      setStatus('');
    }
  }, [persistScribeDraft]);

  const onTranscriptReady = useCallback((text) => {
    setTranscript(text);
    structureDraft(text);
  }, [structureDraft]);

  const startBrowserSTT = useCallback(() => {
    const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
    if (!SpeechRecognition) return false;
    const rec = new SpeechRecognition();
    recognitionRef.current = rec;
    rec.continuous = true;
    rec.interimResults = false;
    rec.lang = 'en-US';
    rec.maxAlternatives = 1;
    const parts = [];
    rec.onstart = () => { setRecording(true); setStatus('listening'); setErr(null); };
    rec.onend = () => {
      setRecording(false);
      setStatus('');
      recognitionRef.current = null;
      const text = parts.join(' ').trim();
      if (text) onTranscriptReady(text);
      else setErr('Nothing heard. Try again or type a transcript below.');
    };
    rec.onerror = (ev) => {
      setRecording(false);
      setStatus('');
      if (ev.error === 'not-allowed') setErr('Microphone access denied.');
      else if (ev.error !== 'aborted') setErr(`Recognition error: ${ev.error}`);
    };
    rec.onresult = (ev) => {
      for (let i = ev.resultIndex; i < ev.results.length; i++) {
        if (ev.results[i].isFinal) parts.push(ev.results[i][0].transcript);
      }
    };
    rec.start();
    return true;
  }, [onTranscriptReady]);

  const startMediaRecorder = useCallback(async () => {
    try {
      const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
      const mr = new MediaRecorder(stream);
      mediaRef.current = mr;
      chunksRef.current = [];
      mr.ondataavailable = (e) => { if (e.data?.size) chunksRef.current.push(e.data); };
      mr.onstop = async () => {
        stream.getTracks().forEach((t) => t.stop());
        setStatus('processing');
        try {
          const blob = new Blob(chunksRef.current, { type: 'audio/webm' });
          const fd = new FormData();
          fd.append('audio', blob, 'scribe.webm');
          const resp = await fetch('/api/transcribe', { method: 'POST', body: fd });
          const d = await resp.json().catch(() => ({}));
          if (!resp.ok || d.ok === false) {
            setErr(d.error || 'Transcription failed. Type the note below instead.');
          } else if (d.text) {
            onTranscriptReady(d.text);
          } else {
            setErr('Empty transcription. Try again.');
          }
        } catch {
          setErr('Network error during transcription.');
        } finally {
          setStatus('');
          setRecording(false);
        }
      };
      mr.start();
      setRecording(true);
      setStatus('listening');
      setErr(null);
      return true;
    } catch {
      setErr('Microphone access denied.');
      return false;
    }
  }, [onTranscriptReady]);

  const startRecording = useCallback(async (forceAfterConsent) => {
    let ok = consentOk;
    try { ok = ok || !!localStorage.getItem(CONSENT_KEY); } catch { /* */ }
    if (!ok && !forceAfterConsent) { setShowConsent(true); return; }
    setNote(null);
    setFormatted('');
    setTranscript('');
    setSource('');
    if (!startBrowserSTT()) await startMediaRecorder();
  }, [consentOk, startBrowserSTT, startMediaRecorder]);

  const stopRecording = useCallback(() => {
    if (recognitionRef.current) {
      recognitionRef.current.stop();
      recognitionRef.current = null;
    }
    if (mediaRef.current?.state === 'recording') mediaRef.current.stop();
    setRecording(false);
  }, []);

  const updateSection = (key, val) => {
    setNote((prev) => {
      const next = { ...(prev || {}), [key]: val };
      if (window.ScribeStructure) {
        setFormatted(window.ScribeStructure.formatNote(next, { disclaimer: AB3030 }));
      }
      return next;
    });
  };

  const copyNote = async () => {
    try {
      await navigator.clipboard.writeText(formatted || '');
      setCopied(true);
      setTimeout(() => setCopied(false), 2000);
    } catch {
      setErr('Copy failed — select text manually.');
    }
  };

  const sendToChartDraft = useCallback(() => {
    if (!note || !window.SuiteState) return;
    const saved = window.SuiteState.saveScribeNote({ transcript, note, formatted, source });
    setSavedId(saved.id);
    setErr(null);
  }, [note, transcript, formatted, source]);

  const openTreatmentPlan = useCallback(() => {
    if (!note || !window.SuiteBridge || !window.SuiteState) {
      location.hash = '#treatment-plan';
      return;
    }
    const saved = window.SuiteState.saveScribeNote({ transcript, note, formatted, source });
    const draft = window.SuiteBridge.planFromSOAP(note, {
      scribeRef: saved.id,
      title: 'From scribe · ' + new Date().toLocaleString(),
    });
    const hints = window.SuiteBridge.parsePerioFromText(
      [transcript, note.objective, note.assessment].filter(Boolean).join(' '),
    );
    if (hints.length) {
      let chart = window.SuiteState.getPerioChart() || window.SuiteState.defaultPerioChart();
      chart = window.SuiteBridge.applyPerioSuggestions(chart, hints);
      window.SuiteState.savePerioChart(chart);
    }
    window.SuiteState.saveTreatmentPlan(draft);
    setSavedId(saved.id);
    setTxLinked(true);
    location.hash = '#treatment-plan';
  }, [note, transcript, formatted, source]);

  useEffect(() => {
    if (!window.SuiteState) return;
    const s = window.SuiteState.getScribeNote();
    if (s && s.note) {
      setTranscript(s.transcript || '');
      setNote(s.note);
      setFormatted(s.formatted || '');
      setSource(s.source || '');
      setSavedId(s.id);
    }
    const tx = window.SuiteState.getTreatmentPlan();
    setTxLinked(!!(tx && s && tx.scribeRef === s.id));
  }, []);

  const statusLabel = {
    listening: 'Listening… speak clearly (tooth numbers, findings, plan)',
    processing: 'Transcribing audio (not retained after transcription)…',
    structuring: 'Structuring SOAP draft…',
  }[status] || '';

  return (
    <div className={compact ? '' : 'wrap'}>
      {!compact && <AB3030Banner />}
      <HitlBanner />

      <div className="card">
        <h3>
          <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="var(--sage)" strokeWidth="1.8">
            <rect x="7" y="3" width="10" height="18" rx="2.4" /><path d="M10.5 18h3" />
          </svg>
          Voice scribe
        </h3>
        <div className="hint">
          Dictate chairside findings. CA Penal Code §632 two-party consent required. Audio is transcribed and
          discarded — zero retention after transcription (xAI Grok primary, faster-whisper fallback when routed).
        </div>

        <div style={{ display: 'flex', gap: 10, flexWrap: 'wrap', marginTop: 12 }}>
          {!recording && status !== 'processing' && status !== 'structuring' ? (
            <button type="button" className="btn btn-primary" onClick={startRecording}>
              <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8">
                <rect x="7" y="3" width="10" height="18" rx="2.4" /><path d="M10.5 18h3" />
              </svg>
              {consentOk ? 'Start recording' : 'Consent + record'}
            </button>
          ) : (
            <button type="button" className="btn" onClick={stopRecording} style={{ borderColor: 'var(--seal)' }}>
              <svg width="15" height="15" viewBox="0 0 24 24" fill="currentColor" stroke="none">
                <rect x="6" y="6" width="12" height="12" rx="2" />
              </svg>
              Stop
            </button>
          )}
          {transcript && !recording && (
            <button type="button" className="btn btn-sm" onClick={() => structureDraft(transcript)}>
              Re-structure draft
            </button>
          )}
          {formatted && (
            <button type="button" className="btn btn-sm" onClick={copyNote}>
              {copied ? 'Copied' : 'Copy draft'}
            </button>
          )}
          {note && (
            <>
              <button type="button" className="btn btn-sm" onClick={sendToChartDraft}>
                Send to chart draft
              </button>
              <button type="button" className="btn btn-sm btn-primary" onClick={openTreatmentPlan}>
                Open in treatment plan
              </button>
            </>
          )}
        </div>
        {savedId && (
          <div className="hint" style={{ marginTop: 8 }}>
            Local chart draft ID: <code>{savedId}</code>
            {txLinked && (
              <> · <a href="#treatment-plan" style={{ color: 'var(--sage)' }}>Treatment plan linked</a></>
            )}
            {' · '}
            <a href="#perio-chart" style={{ color: 'var(--sage)' }}>Perio chart</a>
          </div>
        )}

        {statusLabel && (
          <div style={{ marginTop: 10, fontSize: 12, color: 'var(--sage)', display: 'flex', alignItems: 'center', gap: 8 }}>
            {status === 'listening' && (
              <span style={{
                width: 8, height: 8, borderRadius: '50%', background: 'var(--seal)',
                display: 'inline-block', animation: 'pulse 1s ease-in-out infinite',
              }} />
            )}
            {statusLabel}
          </div>
        )}
        {err && (
          <div className="banner warn" style={{ marginTop: 12, fontSize: 12 }}>
            {err}
          </div>
        )}

        <div className="field" style={{ marginTop: 16 }}>
          <label>Raw transcript (editable)</label>
          <textarea
            value={transcript}
            onChange={(e) => setTranscript(e.target.value)}
            onBlur={() => { if (transcript.trim() && !note) structureDraft(transcript); }}
            rows={compact ? 3 : 4}
            placeholder="Transcript appears here after recording, or paste/type dictation…"
            style={{
              width: '100%', resize: 'vertical', background: 'var(--bg)', border: '1px solid var(--border2)',
              borderRadius: 8, padding: '10px 11px', color: 'var(--text)', fontSize: 13, fontFamily: 'inherit',
            }}
          />
        </div>
        {transcript && !note && !status && (
          <button type="button" className="btn btn-sm" style={{ marginTop: 8 }} onClick={() => structureDraft(transcript)}>
            Generate SOAP draft
          </button>
        )}
      </div>

      {note && (
        <div style={{ marginTop: compact ? 12 : 18 }}>
          <div className="sectiontitle">SOAP draft — clinician review required</div>
          <div className="grid g2">
            {[
              ['subjective', 'Subjective'],
              ['objective', 'Objective'],
              ['assessment', 'Assessment'],
              ['plan', 'Plan'],
            ].map(([key, label]) => (
              <div className="card tight" key={key}>
                <h3>{label}</h3>
                <textarea
                  value={note[key] || ''}
                  onChange={(e) => updateSection(key, e.target.value)}
                  rows={5}
                  style={{
                    width: '100%', marginTop: 8, resize: 'vertical', background: 'var(--bg)',
                    border: '1px solid var(--border2)', borderRadius: 8, padding: '9px 10px',
                    color: 'var(--text)', fontSize: 12.5, fontFamily: 'inherit', lineHeight: 1.45,
                  }}
                />
              </div>
            ))}
          </div>
          <div className="card" style={{ marginTop: 12 }}>
            <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 10 }}>
              <h3 style={{ margin: 0 }}>Formatted draft</h3>
              <span className="chip tone">{source || 'rules'}</span>
            </div>
            <pre style={{
              marginTop: 10, whiteSpace: 'pre-wrap', fontSize: 11.5, lineHeight: 1.5,
              color: 'var(--muted)', fontFamily: 'ui-monospace, SFMono-Regular, Menlo, monospace',
              maxHeight: 220, overflow: 'auto',
            }}>
              {formatted}
            </pre>
          </div>
        </div>
      )}

      {showConsent && (
        <div className="modal-bg" onClick={() => setShowConsent(false)}>
          <div className="modal" onClick={(e) => e.stopPropagation()}>
            <h3 style={{ fontSize: 15, marginBottom: 8, display: 'flex', alignItems: 'center', gap: 8 }}>
              <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="var(--sage)" strokeWidth="1.8">
                <path d="M12 3l8 3v6c0 5-3.5 8-8 9-4.5-1-8-4-8-9V6z" /><path d="M9 12l2 2 4-4" />
              </svg>
              Two-party consent (CA Penal Code §632)
            </h3>
            <p style={{ fontSize: 13, color: 'var(--muted)', lineHeight: 1.55 }}>
              Confirm all parties in the room have been informed and consent to voice capture for clinical
              documentation. Audio is sent for transcription only and is not retained after processing.
              Only the de-identified draft note remains for human review (HITL).
            </p>
            <div style={{ display: 'flex', gap: 10, marginTop: 18, justifyContent: 'flex-end' }}>
              <button type="button" className="btn" onClick={() => setShowConsent(false)}>Cancel</button>
              <button type="button" className="btn btn-primary" onClick={() => { grantConsent(); startRecording(true); }}>
                Consent confirmed — start
              </button>
            </div>
          </div>
        </div>
      )}

      <style>{`
        @keyframes pulse {
          0%, 100% { opacity: 1; }
          50% { opacity: 0.35; }
        }
      `}</style>
    </div>
  );
}

window.VoiceScribe = VoiceScribe;
