// pages.jsx — Transcribe + Settings, wired to the real backend.
//
// Transcribe flow:
//   Setup → Recording → Processing → Review & Save
//   Setup picks a real patient (or adds a new one) and a template; Start
//   creates a backend session and begins MediaRecorder. Stop ends the session
//   (per CLAUDE.md gotcha #8: stop recorder → end session → upload audio),
//   uploads the blob, polls for the generated note, and shows transcript +
//   clinical note for review/save.

const { useState: useStateX, useEffect: useEffectX, useRef: useRefX, useMemo: useMemoX } = React;

const TX_FALLBACK_TEMPLATES = [
  { id: "soap",       nameKey: "tx.templates.soap.name",      descriptionKey: "tx.templates.soap.desc", fallbackName: "SOAP Note",       fallbackDescription: "Subjective · Objective · Assessment · Plan" },
  { id: "progress",   nameKey: "tx.templates.progress.name",  descriptionKey: "tx.templates.progress.desc", fallbackName: "Progress Note",   fallbackDescription: "Chief complaint · HPI · ROS · Plan" },
  { id: "referral",   nameKey: "tx.templates.referral.name",  descriptionKey: "tx.templates.referral.desc", fallbackName: "Referral Letter", fallbackDescription: "Patient summary · reason · request" },
  { id: "procedure",  nameKey: "tx.templates.procedure.name", descriptionKey: "tx.templates.procedure.desc", fallbackName: "Procedure Note",  fallbackDescription: "Indication · procedure · findings" },
];

const TX_LANGUAGES = [
  { id: "auto", labelKey: "tx.lang.auto", fallback: "Auto detect" },
  { id: "en", labelKey: "tx.lang.en", fallback: "English" },
  { id: "es", labelKey: "tx.lang.es", fallback: "Spanish" },
  { id: "es-419", labelKey: "tx.lang.es419", fallback: "Spanish (Latin America)" },
  { id: "es-ES", labelKey: "tx.lang.esES", fallback: "Spanish (Spain)" },
  { id: "ca", labelKey: "tx.lang.ca", fallback: "Catalan" },
  { id: "hi-IN", labelKey: "tx.lang.hi", fallback: "Hindi" },
  { id: "bn-IN", labelKey: "tx.lang.bn", fallback: "Bengali" },
  { id: "ta-IN", labelKey: "tx.lang.ta", fallback: "Tamil" },
  { id: "te-IN", labelKey: "tx.lang.te", fallback: "Telugu" },
  { id: "kn-IN", labelKey: "tx.lang.kn", fallback: "Kannada" },
  { id: "ml-IN", labelKey: "tx.lang.ml", fallback: "Malayalam" },
  { id: "mr-IN", labelKey: "tx.lang.mr", fallback: "Marathi" },
  { id: "gu-IN", labelKey: "tx.lang.gu", fallback: "Gujarati" },
  { id: "pa-IN", labelKey: "tx.lang.pa", fallback: "Punjabi" },
  { id: "od-IN", labelKey: "tx.lang.od", fallback: "Odia" },
];

function txTemplateName(t, tmpl) {
  if (!tmpl) return "";
  return (tmpl.nameKey && t(tmpl.nameKey)) || tmpl.name || tmpl.label || tmpl.fallbackName || tmpl.id;
}

function txTemplateDescription(t, tmpl) {
  if (!tmpl) return "";
  return (tmpl.descriptionKey && t(tmpl.descriptionKey)) || tmpl.description || tmpl.desc || tmpl.fallbackDescription || t("tx.templateHelp");
}

function pickMimeType() {
  if (typeof MediaRecorder === "undefined") return null;
  const candidates = [
    "audio/webm;codecs=opus",
    "audio/webm",
    "audio/mp4;codecs=mp4a.40.2",
    "audio/mp4",
  ];
  for (const m of candidates) {
    try { if (MediaRecorder.isTypeSupported(m)) return m; } catch (_) {}
  }
  return "";
}

function txLanguageLabel(t, lang) {
  return (lang.labelKey && t(lang.labelKey)) || lang.fallback || lang.id;
}

function defaultTxLanguage() {
  if (window.MSStore && window.MSStore.getStoredDefaultEncounterLanguage) {
    return window.MSStore.getStoredDefaultEncounterLanguage();
  }
  return "auto";
}

// ── Step 1: Setup (patient + language + template) ────────────────────────────

function TxSetup({ onStart }) {
  const store = window.MSStore.useStore();
  const t = window.MSStore.useT();
  const [lang] = window.MSStore.useLang();
  const [patient, setPatient] = useStateX(null);
  const [templateId, setTemplateId] = useStateX("soap");
  const [language, setLanguage] = useStateX(defaultTxLanguage);
  const [patSearch, setPatSearch] = useStateX("");
  const [addingNew, setAddingNew] = useStateX(false);
  const [newPat, setNewPat] = useStateX({ first: "", last: "", dob: "", sex: "F", phone: "", email: "" });
  const setNP = (k, v) => setNewPat(p => ({ ...p, [k]: v }));
  const [adding, setAdding] = useStateX(false);
  const [addErr, setAddErr] = useStateX("");

  useEffectX(() => {
    if (window.MSStore && window.MSStore.getStoredDefaultEncounterLanguage) {
      setLanguage(window.MSStore.getStoredDefaultEncounterLanguage());
      return;
    }
    setLanguage(current => current === "en" || current === "es" ? (lang === "es" ? "es" : "en") : current);
  }, [lang]);

  const todayPats = useMemoX(() => {
    const today = new Date(); today.setHours(0, 0, 0, 0);
    const tomorrow = new Date(today.getTime() + 86400000);
    const seen = new Set();
    const out = [];
    for (const a of (store.appointments || [])) {
      const t = new Date(a.scheduledAt);
      if (isNaN(t) || t < today || t >= tomorrow || !a.patientName) continue;
      const key = a.patientName.toLowerCase();
      if (seen.has(key)) continue;
      seen.add(key);
      const p = (store.patients || []).find(p => `${p.first} ${p.last}`.toLowerCase() === key) || {
        id: a.patientName, first: a.patientName.split(" ")[0], last: a.patientName.split(" ").slice(1).join(" "),
        avatar: (a.patientName || "?").split(" ").map(s => s[0]).slice(0, 2).join("").toUpperCase(),
        phone: a.raw && a.raw.patient_phone || "",
        email: a.raw && a.raw.patient_email || "",
      };
      out.push({ ...p, _appt: a });
    }
    return out;
  }, [store.appointments, store.patients]);

  const searchResults = useMemoX(() => {
    if (!patSearch) return [];
    const ql = patSearch.toLowerCase();
    return (store.patients || [])
      .filter(p => `${p.first} ${p.last}`.toLowerCase().includes(ql) || (p.phone || "").includes(patSearch) || (p.email || "").toLowerCase().includes(ql))
      .slice(0, 6);
  }, [patSearch, store.patients]);

  const newPatValid = newPat.first.trim() && newPat.last.trim();

  const tmplList = (store.templates && store.templates.length ? store.templates : TX_FALLBACK_TEMPLATES);

  async function quickAddPatient() {
    if (!newPatValid || adding) return;
    setAdding(true); setAddErr("");
    try {
      const fullName = `${newPat.first.trim()} ${newPat.last.trim()}`;
      let age = "";
      if (newPat.dob) {
        const d = new Date(newPat.dob);
        if (!isNaN(d)) age = String(Math.floor((Date.now() - d.getTime()) / (365.25 * 86400000)));
      }
      await store.createPatient({ name: fullName, age, phone: newPat.phone, email: newPat.email });
      setPatient({
        id: fullName,
        first: newPat.first.trim(),
        last: newPat.last.trim(),
        avatar: ((newPat.first[0] || "") + (newPat.last[0] || "")).toUpperCase(),
        age, phone: newPat.phone, email: newPat.email,
      });
      setAddingNew(false);
      setNewPat({ first: "", last: "", dob: "", sex: "F", phone: "", email: "" });
    } catch (e) {
      setAddErr(e && e.message || t("tx.errors.addPatient"));
    } finally {
      setAdding(false);
    }
  }

  return (
    <div className="ms-tx-setup-grid" style={{display: "grid", gridTemplateColumns: "1fr 1fr", gap: 20, maxWidth: 860, margin: "0 auto"}}>
      {/* Patient column */}
      <div className="card" style={{padding: 0, overflow: "hidden"}}>
        <div style={{padding: "18px 20px 14px", borderBottom: "1px solid var(--line)"}}>
          <div className="eyebrow" style={{marginBottom: 4}}>{t("tx.step1")}</div>
          <div style={{fontWeight: 600, fontSize: 15}}>{t("tx.selectPatient")}</div>
        </div>
        <div style={{padding: "14px 16px"}}>
          {!addingNew ? (
            <>
              {!patSearch && todayPats.length > 0 && (
                <div style={{marginBottom: 14}}>
                  <div className="eyebrow" style={{marginBottom: 8}}>{t("common.today")}</div>
                  <div className="col" style={{gap: 4}}>
                    {todayPats.map(p => {
                      const sel = patient && patient.id === p.id;
                      return (
                        <button key={p.id} onClick={() => setPatient(p)}
                          style={{appearance: "none", background: sel ? "var(--primary-soft)" : "var(--bg-soft)", border: "1px solid " + (sel ? "var(--primary-line)" : "var(--line)"), borderRadius: 10, padding: "10px 12px", cursor: "pointer", textAlign: "left", display: "flex", alignItems: "center", gap: 10}}>
                          <div className="avatar avatar-sm">{p.avatar}</div>
                          <div style={{flex: 1, minWidth: 0}}>
                            <div style={{fontWeight: 600, fontSize: 13, color: sel ? "var(--primary-ink)" : "var(--ink)"}}>{p.first} {p.last}</div>
                            <div style={{fontSize: 11.5, color: "var(--ink-4)"}}>{p._appt && p._appt.time}</div>
                          </div>
                          {sel && <Icon.Check size={14} style={{color: "var(--primary)", flexShrink: 0}}/>}
                        </button>
                      );
                    })}
                  </div>
                </div>
              )}
              <div className="ms-search-input" style={{marginBottom: patSearch ? 10 : 0}}>
                <Icon.Search size={14}/>
                <input value={patSearch} onChange={e => setPatSearch(e.target.value)} placeholder={t("tx.searchPatients")}/>
                {patSearch && <button style={{appearance: "none", border: 0, background: "transparent", cursor: "pointer", color: "var(--ink-4)", padding: 0}} onClick={() => setPatSearch("")}><Icon.X size={13}/></button>}
              </div>
              {patSearch && (
                <div className="col" style={{gap: 4}}>
                  {searchResults.map(p => {
                    const sel = patient && patient.id === p.id;
                    return (
                      <button key={p.id} onClick={() => { setPatient(p); setPatSearch(""); }}
                        style={{appearance: "none", background: sel ? "var(--primary-soft)" : "transparent", border: "1px solid " + (sel ? "var(--primary-line)" : "transparent"), borderRadius: 8, padding: "8px 10px", cursor: "pointer", textAlign: "left", display: "flex", alignItems: "center", gap: 10}}>
                        <div className="avatar avatar-sm">{p.avatar}</div>
                        <div style={{flex: 1}}>
                          <div style={{fontWeight: 500, fontSize: 13}}>{p.first} {p.last}</div>
                          <div style={{fontSize: 11.5, color: "var(--ink-4)"}}>{p.phone || p.email || ""}</div>
                        </div>
                        {sel && <Icon.Check size={14} style={{color: "var(--primary)"}}/>}
                      </button>
                    );
                  })}
                  {searchResults.length === 0 && <div style={{padding: "12px 0", textAlign: "center", fontSize: 13, color: "var(--ink-4)"}}>{t("tx.noPatients")}</div>}
                </div>
              )}
              <button className="btn btn-ghost btn-sm" style={{width: "100%", justifyContent: "center", marginTop: 12}} onClick={() => setAddingNew(true)}>
                <Icon.Plus size={13}/>{t("tx.addNew")}
              </button>
            </>
          ) : (
            <div className="col" style={{gap: 12}}>
              <div className="ms-tx-newpat-grid" style={{display: "grid", gridTemplateColumns: "1fr 1fr", gap: 10}}>
                <div><label className="field-label">{t("auth.firstName")} *</label><input className="input" value={newPat.first} onChange={e => setNP("first", e.target.value)} placeholder={t("auth.firstName")}/></div>
                <div><label className="field-label">{t("auth.lastName")} *</label><input className="input" value={newPat.last} onChange={e => setNP("last", e.target.value)} placeholder={t("auth.lastName")}/></div>
                <div><label className="field-label">{t("tx.dob")}</label><input className="input" type="date" value={newPat.dob} onChange={e => setNP("dob", e.target.value)}/></div>
                <div><label className="field-label">{t("tx.phone")}</label><input className="input" value={newPat.phone} onChange={e => setNP("phone", e.target.value)}/></div>
              </div>
              {addErr && <div style={{padding: "8px 10px", borderRadius: 8, color: "var(--accent-danger)", fontSize: 12}}>{addErr}</div>}
              <div className="row" style={{gap: 8}}>
                <button className="btn btn-ghost btn-sm" onClick={() => setAddingNew(false)}>{t("common.cancel")}</button>
                <button className="btn btn-primary btn-sm" disabled={!newPatValid || adding} style={{opacity: newPatValid && !adding ? 1 : 0.5}} onClick={quickAddPatient}>
                  <Icon.Check size={13}/>{adding ? t("tx.adding") : t("tx.addPatient")}
                </button>
              </div>
            </div>
          )}
        </div>
        {patient && (
          <div style={{padding: "12px 16px", background: "var(--primary-soft)", borderTop: "1px solid var(--primary-line)", display: "flex", alignItems: "center", gap: 10}}>
            <div className="avatar avatar-sm" style={{background: "var(--primary)", color: "white", border: 0}}>{patient.avatar}</div>
            <span style={{fontSize: 13, fontWeight: 600, color: "var(--primary-ink)", flex: 1}}>{patient.first} {patient.last}</span>
            <button style={{appearance: "none", border: 0, background: "transparent", cursor: "pointer", color: "var(--primary-ink)"}} onClick={() => setPatient(null)}><Icon.X size={14}/></button>
          </div>
        )}
      </div>

      {/* Template column */}
      <div className="card" style={{padding: 0, overflow: "hidden"}}>
        <div style={{padding: "18px 20px 14px", borderBottom: "1px solid var(--line)"}}>
          <div className="eyebrow" style={{marginBottom: 4}}>{t("tx.step2")}</div>
          <div style={{fontWeight: 600, fontSize: 15}}>{t("tx.configureEncounter")}</div>
        </div>
        <div style={{padding: "14px 16px"}}>
          <div className="col" style={{gap: 10}}>
            <div>
              <label className="field-label">{t("tx.language")}</label>
              <div className="ms-select-wrap">
                <select className="input select" value={language} onChange={e => setLanguage(e.target.value)} aria-label={t("tx.language")}>
                  {TX_LANGUAGES.map(lang => (
                    <option key={lang.id} value={lang.id}>{txLanguageLabel(t, lang)}</option>
                  ))}
                </select>
                <Icon.ChevronDown size={15}/>
              </div>
            </div>
            <div>
              <label className="field-label">{t("tx.chooseTemplate")}</label>
              <div className="ms-select-wrap">
                <select className="input select" value={templateId} onChange={e => setTemplateId(e.target.value)} aria-label={t("tx.chooseTemplate")}>
                  {tmplList.map(tmpl => (
                    <option key={tmpl.id} value={tmpl.id}>{txTemplateName(t, tmpl)}</option>
                  ))}
                </select>
                <Icon.ChevronDown size={15}/>
              </div>
            </div>
            {(() => {
              const selected = tmplList.find(tmpl => tmpl.id === templateId) || tmplList[0] || {};
              return (
                <div style={{padding: "12px 14px", borderRadius: 10, background: "var(--bg-soft)", border: "1px solid var(--line)", fontSize: 12.5, color: "var(--ink-4)", lineHeight: 1.45}}>
                  {txTemplateDescription(t, selected)}
                </div>
              );
            })()}
          </div>
        </div>
        <div style={{padding: "14px 16px", borderTop: "1px solid var(--line)"}}>
          <button className={"btn btn-primary btn-lg" + (patient ? "" : " disabled")}
            style={{width: "100%", justifyContent: "center", opacity: patient ? 1 : 0.4, cursor: patient ? "pointer" : "not-allowed"}}
            onClick={() => patient && onStart(patient, templateId, language)}>
            <Icon.Mic size={16}/>{t("tx.startRecording")}
          </button>
          {!patient && <div style={{fontSize: 12, color: "var(--ink-4)", textAlign: "center", marginTop: 8}}>{t("tx.selectPatientFirst")}</div>}
        </div>
      </div>
    </div>
  );
}

// ── Step 2: Recording (real MediaRecorder) ───────────────────────────────────

function TxRecording({ patient, onStop, onCancel }) {
  const t = window.MSStore.useT();
  const [elapsed, setElapsed] = useStateX(0);
  const [stopped, setStopped] = useStateX(false);
  const [paused, setPaused] = useStateX(false);
  const [error, setError] = useStateX("");
  const [level, setLevel] = useStateX(0);
  const recRef = useRefX(null);
  const chunksRef = useRefX([]);
  const streamRef = useRefX(null);
  const audioCtxRef = useRefX(null);
  const rafRef = useRefX(null);
  const pausedRef = useRefX(false);

  useEffectX(() => {
    let cancelled = false;
    (async () => {
      try {
        if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
          throw new Error(t("tx.cantRecord"));
        }
        const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
        if (cancelled) { stream.getTracks().forEach(t => t.stop()); return; }
        streamRef.current = stream;
        try {
          const AudioCtx = window.AudioContext || window.webkitAudioContext;
          if (AudioCtx) {
            const audioCtx = new AudioCtx();
            const analyser = audioCtx.createAnalyser();
            analyser.fftSize = 256;
            analyser.smoothingTimeConstant = 0.78;
            const source = audioCtx.createMediaStreamSource(stream);
            source.connect(analyser);
            audioCtxRef.current = audioCtx;
            const data = new Uint8Array(analyser.fftSize);
            const tick = () => {
              if (pausedRef.current) {
                setLevel(0);
                rafRef.current = requestAnimationFrame(tick);
                return;
              }
              analyser.getByteTimeDomainData(data);
              let sum = 0;
              for (let i = 0; i < data.length; i++) {
                const centered = (data[i] - 128) / 128;
                sum += centered * centered;
              }
              const rms = Math.sqrt(sum / data.length);
              const normalized = Math.min(1, Math.max(0, (rms - 0.012) * 7.5));
              setLevel(prev => prev * 0.72 + normalized * 0.28);
              rafRef.current = requestAnimationFrame(tick);
            };
            rafRef.current = requestAnimationFrame(tick);
          }
        } catch (_) {
          // Recording still works without the analyser; CSS keeps a fallback pulse.
        }
        const mime = pickMimeType();
        const rec = mime ? new MediaRecorder(stream, { mimeType: mime }) : new MediaRecorder(stream);
        recRef.current = rec;
        rec.addEventListener("dataavailable", (e) => {
          if (e.data && e.data.size > 0) chunksRef.current.push(e.data);
        });
        rec.start();
      } catch (e) {
        const raw = (e && (e.name || e.message)) || "";
        const micMissing = /notfound|not found|devicesnotfound/i.test(raw);
        const micDenied = /notallowed|permission|denied/i.test(raw);
        setError(micMissing ? t("tx.micNotFound") : micDenied ? t("tx.micDenied") : t("tx.couldNotStart"));
      }
    })();

    const id = setInterval(() => setElapsed(e => pausedRef.current ? e : e + 1), 1000);
    return () => {
      cancelled = true;
      clearInterval(id);
      if (rafRef.current) cancelAnimationFrame(rafRef.current);
      try { audioCtxRef.current && audioCtxRef.current.close(); } catch (_) {}
      try { recRef.current && recRef.current.state !== "inactive" && recRef.current.stop(); } catch (_) {}
      const s = streamRef.current;
      if (s) s.getTracks().forEach(t => { try { t.stop(); } catch (_) {} });
    };
  }, []);

  const fmt = s => `${String(Math.floor(s / 60)).padStart(2, "0")}:${String(s % 60).padStart(2, "0")}`;

  function finalize() {
    if (stopped) return;
    setStopped(true);
    pausedRef.current = false;
    setPaused(false);
    const rec = recRef.current;
    if (!rec) {
      onCancel(t("tx.errors.noRecorder"));
      return;
    }
    const finishWithBlob = () => {
      const type = rec.mimeType || "audio/webm";
      const blob = new Blob(chunksRef.current, { type });
      // Stop the mic.
      const s = streamRef.current;
      if (s) s.getTracks().forEach(t => { try { t.stop(); } catch (_) {} });
      if (rafRef.current) cancelAnimationFrame(rafRef.current);
      try { audioCtxRef.current && audioCtxRef.current.close(); } catch (_) {}
      setLevel(0);
      onStop(blob, elapsed);
    };
    if (rec.state === "inactive") {
      finishWithBlob();
    } else {
      rec.addEventListener("stop", finishWithBlob, { once: true });
      try { rec.stop(); } catch (_) { finishWithBlob(); }
    }
  }

  function togglePause() {
    if (stopped) return;
    const rec = recRef.current;
    if (!rec || rec.state === "inactive") return;
    try {
      if (pausedRef.current || rec.state === "paused") {
        if (rec.state === "paused") rec.resume();
        pausedRef.current = false;
        setPaused(false);
      } else if (rec.state === "recording") {
        rec.pause();
        pausedRef.current = true;
        setPaused(true);
        setLevel(0);
      }
    } catch (_) {
      setError(t("tx.errors.pause"));
    }
  }

  if (error) {
    return (
      <div style={{maxWidth: 520, margin: "0 auto", padding: 20}}>
        <div className="card" style={{padding: 24, textAlign: "center", color: "var(--accent-danger)"}}>
          <Icon.Bell size={28}/>
          <div style={{marginTop: 10, fontWeight: 600}}>{error}</div>
          <button className="btn btn-primary" style={{marginTop: 16}} onClick={onCancel}>{t("tx.backToSetup")}</button>
        </div>
      </div>
    );
  }

  const isVoiceActive = !paused && level > 0.16;
  const orbStyle = {
    width: 200,
    height: 200,
    "--voice-level": level.toFixed(3),
  };
  const bars = Array.from({length: 32}).map((_, i) => {
    const phase = Math.abs(Math.sin(i * 0.62));
    const boosted = Math.min(1, 0.18 + level * (0.7 + phase * 0.55));
    return isVoiceActive
      ? {
          height: `${22 + boosted * 74}%`,
          opacity: 0.48 + Math.min(0.52, level * 1.4),
          transform: `scaleY(${0.45 + boosted * 0.75})`,
        }
      : {
          height: "2px",
          opacity: 0.5,
          transform: "scaleY(1)",
        };
  });

  return (
    <div style={{display: "flex", flexDirection: "column", alignItems: "center", paddingTop: 20, paddingBottom: 40, gap: 0}}>
      <div style={{display: "flex", alignItems: "center", gap: 12, marginBottom: 40, background: "var(--surface)", border: "1px solid var(--line)", borderRadius: 999, padding: "8px 20px 8px 10px"}}>
        <div className="avatar avatar-sm">{patient.avatar}</div>
        <div>
          <span style={{fontWeight: 600, fontSize: 13}}>{patient.first} {patient.last}</span>
          {patient.age && <span style={{fontSize: 12, color: "var(--ink-4)", marginLeft: 8}}>{patient.age}</span>}
        </div>
        {paused && <span className="badge" style={{marginLeft: 8}}>{t("tx.paused")}</span>}
      </div>

      <div className={"ms-tx-orb is-rec" + (isVoiceActive ? " is-voice" : "")} style={orbStyle}>
        <div className={"ms-tx-wave " + (isVoiceActive ? "is-wave" : "is-flat")}>
          {bars.map((style, i) => (
            <span key={i} style={style}/>
          ))}
        </div>
      </div>

      <div className="ms-tx-time mono" style={{marginTop: 28, fontSize: 44}}>{fmt(elapsed)}</div>

      <div className="row" style={{gap: 12, justifyContent: "center", marginTop: 40}}>
        <button className="btn" onClick={togglePause} disabled={stopped}
          style={{height: 44, borderRadius: 999, padding: "0 18px", opacity: stopped ? 0.6 : 1}}>
          {paused ? <Icon.Play size={15}/> : <Icon.Pause size={15}/>}
          {paused ? t("tx.resume") : t("tx.pause")}
        </button>
        <button onClick={finalize} disabled={stopped} aria-label={t("tx.tapToStop")}
          style={{width: 64, height: 64, borderRadius: 999, background: "var(--accent-danger)", border: "none", display: "flex", alignItems: "center", justifyContent: "center", cursor: stopped ? "default" : "pointer", boxShadow: "0 6px 20px color-mix(in oklch, var(--accent-danger) 35%, transparent)", opacity: stopped ? 0.6 : 1}}>
          <span style={{width: 20, height: 20, background: "white", borderRadius: 4, display: "block"}}/>
        </button>
      </div>
      <div className="dim" style={{fontSize: 12.5, marginTop: 12}}>{stopped ? t("tx.stopping") : paused ? t("tx.paused") : t("tx.tapToStop")}</div>
    </div>
  );
}

// ── Step 3: Processing — uploads, ends session, polls for note ───────────────

function TxProcessing({ patient, blob, templateId, language, onReady, onError }) {
  const t = window.MSStore.useT();
  const [detail, setDetail] = useStateX(t("tx.creatingSession"));
  const [progress, setProgress] = useStateX(8);
  const ranRef = useRefX(false);
  const store = window.MSStore.useStore();

  useEffectX(() => {
    if (ranRef.current) return;
    ranRef.current = true;
    let cancelled = false;
    (async () => {
      let sessionId = null;
      try {
        // 1. Start session.
        setDetail(t("tx.creatingSession"));
        setProgress(14);
        const fullName = `${patient.first} ${patient.last}`.trim();
        const session = await store.startSession({
          patient_name: fullName,
          patient_phone: patient.phone || "",
          patient_email: patient.email || "",
          language: language || "auto",
        });
        if (cancelled) return;
        sessionId = session && (session.id || session.session_id);
        if (!sessionId) throw new Error(t("tx.errors.createSession"));

        // 2. End session BEFORE uploading audio (CLAUDE.md gotcha #8: the
        // backend rejects upload if the session is ended after upload).
        setDetail(t("tx.endingSession"));
        setProgress(34);
        try {
          await store.endSession(sessionId);
        } catch (e) {
          throw new Error(t("tx.errors.endSession") + ": " + (e && e.message || e));
        }
        if (cancelled) return;

        // 3. Upload audio — the backend transcribes (sync) and then queues
        // clinical-note generation in the background. We do NOT call
        // /summarize separately because /upload-audio already enqueues it
        // when transcripts exist; calling /summarize again races and errors
        // with "No transcript available" if Deepgram returned 0 segments.
        if (!blob || blob.size === 0) {
          throw new Error(t("tx.errors.noAudio"));
        }
        setDetail(t("tx.uploadingSized", { size: (blob.size / 1024 / 1024).toFixed(1) }));
        setProgress(58);
        try {
          await store.uploadAudio(sessionId, blob, templateId);
        } catch (e) {
          throw new Error(t("tx.errors.upload") + ": " + (e && e.message || e));
        }
        if (cancelled) return;

        // 4. Show the transcript immediately. Note generation continues in the
        // background and the review screen refreshes itself when ready.
        setDetail(t("tx.loadingTranscript"));
        setProgress(86);
        let sessionFull = null;
        try { sessionFull = await store.getSession(sessionId); } catch (_) {}
        if (cancelled) return;
        if (!sessionFull) throw new Error(t("tx.errors.loadSession"));
        setProgress(100);
        setTimeout(() => onReady({ sessionId, session: sessionFull, note: sessionFull.clinical_note || null }), 250);
      } catch (e) {
        if (cancelled) return;
        onError(e && e.message ? e.message : t("tx.errors.process"), sessionId);
      }
    })();
    return () => { cancelled = true; };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  return (
    <div style={{display: "flex", flexDirection: "column", alignItems: "center", paddingTop: 60, gap: 16}}>
      <div style={{width: 56, height: 56, borderRadius: 999, background: "var(--primary-soft)", display: "flex", alignItems: "center", justifyContent: "center"}}>
        <Icon.Sparkle size={24} style={{color: "var(--primary)", animation: "spin 1.2s linear infinite"}}/>
      </div>
      <div style={{fontWeight: 600, fontSize: 16}}>{detail}</div>
      <div className="ms-tx-progress" aria-label="Transcription progress">
        <div className="ms-tx-progress-bar" style={{width: progress + "%"}}/>
      </div>
      <div className="mono" style={{fontSize: 12, color: "var(--ink-4)"}}>{progress}%</div>
      <div className="dim" style={{fontSize: 13}}>{t("tx.processingHint")}</div>
    </div>
  );
}

// ── Step 4: Review & Save ────────────────────────────────────────────────────

function TxReview({ patient, sessionId, session, note, onDone, onSaved }) {
  const store = window.MSStore.useStore();
  const t = window.MSStore.useT();
  const [sessionData, setSessionData] = useStateX(session || null);
  const [noteText, setNoteText] = useStateX((note && note.note_text) || "");
  const [noteReady, setNoteReady] = useStateX(!!(note && note.note_text));
  const [ignoreNoteCreatedAt, setIgnoreNoteCreatedAt] = useStateX("");
  const [addingMore, setAddingMore] = useStateX(false);
  const [appendPending, setAppendPending] = useStateX(false);
  const [saved, setSaved] = useStateX(false);
  const [pending, setPending] = useStateX(false);
  const [error, setError] = useStateX("");
  const [previewOpen, setPreviewOpen] = useStateX(false);
  const [copiedNote, setCopiedNote] = useStateX(false);
  const [followupAppointment, setFollowupAppointment] = useStateX(null);

  useEffectX(() => {
    if (!sessionId || noteReady) return;
    let cancelled = false;
    const started = Date.now();
    async function poll() {
      try {
        const fresh = await store.getSession(sessionId);
        if (cancelled) return;
        if (fresh) setSessionData(fresh);
        const freshNote = fresh && fresh.clinical_note;
        if (freshNote && freshNote.note_text && freshNote.created_at !== ignoreNoteCreatedAt) {
          setNoteText(freshNote.note_text);
          setNoteReady(true);
          setAppendPending(false);
          return;
        }
      } catch (_) {}
      if (!cancelled && Date.now() - started < 120_000) setTimeout(poll, 3000);
      if (!cancelled && Date.now() - started >= 120_000) setAppendPending(false);
    }
    const id = setTimeout(poll, 3000);
    return () => { cancelled = true; clearTimeout(id); };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [sessionId, noteReady, ignoreNoteCreatedAt]);

  const transcripts = (sessionData && sessionData.transcripts) || [];
  const followupText = window.MSFollowupTextFromSession ? window.MSFollowupTextFromSession(sessionData, noteText) : "";

  async function handleSave() {
    if (!noteText.trim() || pending) return;
    setError("");
    setPending(true);
    try {
      await store.saveClinicalNote(sessionId, noteText);
      const followupRes = await store.ensureFollowupDraft(sessionId);
      const followupRaw = followupRes && followupRes.appointment;
      if (followupRaw) {
        setFollowupAppointment({
          scheduledAt: followupRaw.scheduled_at,
          reason: followupRaw.reason || "",
        });
      }
      if (onSaved) onSaved();
      setSaved(true);
    } catch (e) {
      setError(e && e.message || t("tx.errors.saveNote"));
    } finally {
      setPending(false);
    }
  }

  function handleCopyNote() {
    if (!noteText.trim()) return;
    window.copyClinicalNoteText(noteText, () => {
      setCopiedNote(true);
      setTimeout(() => setCopiedNote(false), 1600);
    });
  }

  async function handleAddMore(blob) {
    if (!blob || blob.size === 0 || appendPending) {
      setAddingMore(false);
      setError(t("tx.errors.noAudio"));
      return;
    }
    const previousNote = sessionData && sessionData.clinical_note;
    setAddingMore(false);
    setAppendPending(true);
    setError("");
    setIgnoreNoteCreatedAt((previousNote && previousNote.created_at) || "");
    setNoteReady(false);
    setNoteText("");
    try {
      const template = (previousNote && previousNote.template_used) || "soap";
      await store.uploadAudio(sessionId, blob, template);
      const fresh = await store.getSession(sessionId);
      if (fresh) setSessionData(fresh);
    } catch (e) {
      setAppendPending(false);
      setError(e && e.message || t("tx.errors.upload"));
    }
  }

  const NotePreviewModal = window.ClinicalNotePreviewModal;

  if (saved) {
    return (
      <div style={{display: "flex", flexDirection: "column", alignItems: "center", paddingTop: 60, gap: 16}}>
        <div className="ms-success-icon"><Icon.Check size={28}/></div>
        <div style={{fontWeight: 600, fontSize: 18, marginTop: 8}}>{t("tx.saved")}</div>
        <div className="dim" style={{fontSize: 13}}>{patient.first} {patient.last}</div>
        {followupText && (
          <div className="ms-tx-followup-card">
            <div className="row" style={{gap: 10}}>
              <div className="ms-assist-icon"><Icon.CalendarPlus size={15}/></div>
              <div>
                <div style={{fontWeight: 600, fontSize: 13.5}}>Follow-up captured</div>
                <div className="dim" style={{fontSize: 12, marginTop: 2}}>{followupText}</div>
              </div>
            </div>
          </div>
        )}
        {followupAppointment ? (
          <div className="ms-tx-followup-card">
            <div className="row" style={{gap: 10}}>
              <div className="ms-assist-icon"><Icon.CalendarPlus size={15}/></div>
              <div>
                <div style={{fontWeight: 600, fontSize: 13.5}}>Follow-up added to calendar</div>
                <div className="dim" style={{fontSize: 12, marginTop: 2}}>
                  {new Date(followupAppointment.scheduledAt).toLocaleString()} · {followupAppointment.reason || "Follow-up visit"}
                </div>
              </div>
            </div>
          </div>
        ) : (
          <div className="dim" style={{fontSize: 12.5}}>No follow-up calendar item was detected for this note.</div>
        )}
        <button className="btn btn-primary" style={{marginTop: 16}} onClick={onDone}>{t("tx.startNew")}</button>
      </div>
    );
  }

  return (
    <div className="ms-tx-review-grid" style={{display: "grid", gridTemplateColumns: "1fr 420px", gap: 20, alignItems: "start"}}>
      <div className="card" style={{padding: 0}}>
        <div className="card-header">
          <div>
            <div className="card-title">{t("tx.transcript")}</div>
            <div className="dim" style={{fontSize: 12, marginTop: 2}}>
              {patient.first} {patient.last} · {t("tx.transcriptCount", { n: transcripts.length })}
            </div>
          </div>
          <button className="btn btn-sm" onClick={() => { setError(""); setAddingMore(true); }} disabled={addingMore || appendPending}>
            <Icon.Mic size={13}/>{appendPending ? t("tx.addMoreProcessing") : t("tx.addMore")}
          </button>
        </div>
        {addingMore && (
          <div className="ms-tx-append">
            <div className="row" style={{justifyContent: "space-between", marginBottom: 10}}>
              <div>
                <div style={{fontWeight: 600, fontSize: 13}}>{t("tx.addMore")}</div>
                <div className="dim" style={{fontSize: 12, marginTop: 2}}>{t("tx.addMoreHint")}</div>
              </div>
              <button className="btn btn-ghost btn-sm" onClick={() => setAddingMore(false)}>{t("common.cancel")}</button>
            </div>
            <TxRecording patient={patient} onStop={handleAddMore} onCancel={() => setAddingMore(false)}/>
          </div>
        )}
        {appendPending && (
          <div style={{padding: "10px 18px", borderTop: "1px solid var(--line)", borderBottom: "1px solid var(--line)", background: "var(--bg-soft)", color: "var(--ink-3)", fontSize: 12.5}}>
            {t("tx.addMoreProcessing")}
          </div>
        )}
        <div className="ms-tx-lines scroll" style={{maxHeight: 540}}>
          {transcripts.length === 0 && (
            <div style={{padding: 32, textAlign: "center", color: "var(--ink-4)", fontSize: 13}}>
              {t("tx.transcriptEmpty")}
            </div>
          )}
          {transcripts.map((seg, i) => {
            const speaker = seg.speaker_label || (i % 2 === 0 ? "Provider" : "Patient");
            const isProvider = String(speaker).toLowerCase().includes("provider") || String(speaker).toLowerCase().startsWith("doctor") || String(speaker) === "0";
            const text = seg.corrected_text || seg.auto_corrected_text || seg.text || "";
            return (
              <div key={seg.id || i} className={"ms-tx-line " + (isProvider ? "is-prov" : "is-pat")}>
                <div className="ms-tx-who">
                  <span className="avatar avatar-sm" style={isProvider ? {background: "var(--primary-soft)", color: "var(--primary-ink)", borderColor: "var(--primary-line)"} : {}}>
                    {isProvider ? "Dr" : (patient.avatar || "Pt")}
                  </span>
                  <div style={{fontWeight: 600, fontSize: 12}}>{isProvider ? t("tx.provider") : t("tx.patient")}</div>
                </div>
                <div className="ms-tx-text">{text}</div>
              </div>
            );
          })}
        </div>
      </div>

      <div className="col" style={{gap: 12}}>
        <div className="row" style={{gap: 8}}>
          <div className="ms-assist-icon"><Icon.Sparkle size={15}/></div>
          <div>
            <div style={{fontWeight: 600, fontSize: 14}}>{t("tx.note.title")}</div>
            <div className="dim" style={{fontSize: 12}}>{noteReady ? t("tx.note.aiSub") : t("tx.note.pending")}</div>
          </div>
          <div className="spacer"/>
        </div>
        <div className="ms-soap-pane">
          {followupText && (
            <div className="ms-tx-followup-inline">
              <Icon.CalendarPlus size={14}/>
              <div>
                <div style={{fontWeight: 600, fontSize: 12.5}}>Follow-up captured</div>
                <div className="dim" style={{fontSize: 12, marginTop: 1}}>{followupText}</div>
              </div>
            </div>
          )}
          <div className="ms-quick-row" style={{justifyContent: "flex-end"}}>
            <button className="btn btn-sm" disabled={!noteText.trim()} onClick={() => setPreviewOpen(true)}>
              <Icon.Eye size={13}/>Preview
            </button>
            <button className="btn btn-sm" disabled={!noteText.trim()} onClick={handleCopyNote}>
              <Icon.Copy size={13}/>{copiedNote ? "Copied" : "Copy"}
            </button>
          </div>
          <textarea className="ms-soap-text" value={noteText} onChange={e => setNoteText(e.target.value)} style={{minHeight: 360}} placeholder={t("tx.note.placeholder")}/>
        </div>
        {error && <div style={{padding: "10px 12px", borderRadius: 10, color: "var(--accent-danger)", fontSize: 12.5}}>{error}</div>}
        <button className="btn btn-primary btn-lg" disabled={!noteText.trim() || pending} style={{width: "100%", justifyContent: "center", opacity: !noteText.trim() || pending ? 0.6 : 1}} onClick={handleSave}>
          <Icon.Document size={15}/>{pending ? t("common.saving") : t("tx.saveTo", { name: patient.first })}
        </button>
        <button className="btn btn-ghost btn-sm" style={{width: "100%", justifyContent: "center"}} onClick={onDone}>{t("tx.discard")}</button>
      </div>
      <NotePreviewModal
        open={previewOpen}
        noteText={noteText}
        patientName={`${patient.first} ${patient.last}`.trim()}
        followupText={followupText}
        onClose={() => setPreviewOpen(false)}
      />
    </div>
  );
}

// ── Main Transcribe component ─────────────────────────────────────────────────

function Transcribe() {
  const t = window.MSStore.useT();
  const [step, setStep] = useStateX("setup");
  const [patient, setPatient] = useStateX(null);
  const [templateId, setTemplateId] = useStateX("soap");
  const [language, setLanguage] = useStateX(defaultTxLanguage);
  const [recordingBlob, setRecordingBlob] = useStateX(null);
  const [resultPayload, setResultPayload] = useStateX(null);
  const [errorMsg, setErrorMsg] = useStateX("");
  const [errorSessionId, setErrorSessionId] = useStateX(null);
  const [reviewSaved, setReviewSaved] = useStateX(false);

  const handleStart = (p, tmpl, lang) => { setPatient(p); setTemplateId(tmpl); setLanguage(lang || "en"); setReviewSaved(false); setStep("recording"); };
  const handleStop = (blob) => { setRecordingBlob(blob); setReviewSaved(false); setStep("processing"); };
  const handleReady = (payload) => { setResultPayload(payload); setReviewSaved(false); setStep("review"); };
  const handleError = (msg, sessionId) => {
    setErrorMsg(msg);
    setErrorSessionId(sessionId || null);
    setStep("error");
  };
  const handleDone = () => {
    setStep("setup"); setPatient(null); setRecordingBlob(null); setResultPayload(null); setErrorMsg(""); setErrorSessionId(null); setReviewSaved(false); setLanguage(defaultTxLanguage());
  };
  const handleCancel = () => {
    setStep("setup"); setRecordingBlob(null);
  };
  const handleRetry = () => {
    setStep("processing");
    setErrorMsg(""); setErrorSessionId(null);
  };

  const stepLabels = [
    { key: "setup", label: t("tx.step.setup") },
    { key: "recording", label: t("tx.step.recording") },
    { key: "review", label: t("tx.step.review") },
    { key: "save", label: t("tx.step.save") },
  ];
  const stepKeys = stepLabels.map(s => s.key);
  const stepKey = reviewSaved ? "save" : (step === "processing" || step === "error" ? "review" : step);
  const stepIdx = stepKeys.indexOf(stepKey);

  return (
    <div className="ms-page fade-in" style={{paddingBottom: 40}}>
      <div className="ms-page-head" style={{marginBottom: 24, paddingBottom: 20, borderBottom: "1px solid var(--line)"}}>
        <div>
          <h1 className="h1">{t("tx.title")}</h1>
          <div style={{color: "var(--ink-3)", marginTop: 4, fontSize: 14}}>{t("tx.sub")}</div>
        </div>
        <div className="ms-tx-steps">
          {stepLabels.map((s, i) => {
            const isDone = i < stepIdx || (reviewSaved && s.key === "save");
            const isActive = i === stepIdx && !isDone;
            return (
            <div key={s.key} className={"ms-tx-step" + (isActive ? " is-active" : isDone ? " is-done" : "")}>
              <div className="ms-tx-step-dot">
                {isDone ? <Icon.Check size={10}/> : <span>{i + 1}</span>}
              </div>
              <span>{s.label}</span>
              {i < stepLabels.length - 1 && <div className="ms-tx-step-line"/>}
            </div>
            );
          })}
        </div>
      </div>

      {step === "setup"      && <TxSetup onStart={handleStart}/>}
      {step === "recording"  && <TxRecording patient={patient} onStop={handleStop} onCancel={handleCancel}/>}
      {step === "processing" && <TxProcessing patient={patient} blob={recordingBlob} templateId={templateId} language={language} onReady={handleReady} onError={handleError}/>}
      {step === "review"     && <TxReview patient={patient} sessionId={resultPayload && resultPayload.sessionId} session={resultPayload && resultPayload.session} note={resultPayload && resultPayload.note} onDone={handleDone} onSaved={() => setReviewSaved(true)}/>}
      {step === "error" && (
        <div style={{maxWidth: 560, margin: "0 auto", paddingTop: 32}}>
          <div className="card" style={{padding: 28, textAlign: "center"}}>
            <div style={{width: 48, height: 48, borderRadius: 999, background: "color-mix(in oklch, var(--accent-danger) 14%, white)", display: "inline-flex", alignItems: "center", justifyContent: "center", color: "var(--accent-danger)"}}>
              <Icon.Bell size={22}/>
            </div>
            <div style={{marginTop: 14, fontWeight: 600, fontSize: 16, color: "var(--ink)"}}>Recording captured, but processing failed</div>
            <div style={{marginTop: 10, fontSize: 13.5, color: "var(--ink-3)", lineHeight: 1.5, padding: "10px 14px", background: "var(--bg-soft)", borderRadius: 10, border: "1px solid var(--line)", textAlign: "left"}}>
              <div style={{fontFamily: "var(--font-mono)", fontSize: 12.5, color: "var(--accent-danger)", whiteSpace: "pre-wrap", wordBreak: "break-word"}}>{errorMsg || t("tx.unknownError")}</div>
            </div>
            <div style={{fontSize: 12, color: "var(--ink-4)", marginTop: 12, textAlign: "left"}}>
              Common causes: microphone permission was denied, the audio was silent, the network dropped during upload, or the transcription service is unreachable. {errorSessionId ? <>The session <span className="mono">{errorSessionId.slice(0, 8)}</span> was created — you can review it in the patient's chart later.</> : null}
            </div>
            <div className="row" style={{gap: 8, justifyContent: "center", marginTop: 20}}>
              <button className="btn btn-ghost" onClick={handleDone}>Start over</button>
              <button className="btn" onClick={handleCancel}>Re-record</button>
              {recordingBlob && <button className="btn btn-primary" onClick={handleRetry}>Retry processing</button>}
            </div>
          </div>
        </div>
      )}
    </div>
  );
}

/* ────────────────────────────  DICTIONARY  ──────────────────────────── */

function Dictionary() {
  const t = window.MSStore.useT();
  const [entries, setEntries] = useStateX([]);
  const [loading, setLoading] = useStateX(true);
  const [saving, setSaving] = useStateX(false);
  const [error, setError] = useStateX("");
  const [query, setQuery] = useStateX("");
  const [form, setForm] = useStateX({ term: "", original_mistake: "", category: "medical" });
  const set = (k, v) => setForm(f => ({ ...f, [k]: v }));

  async function loadDictionary() {
    setLoading(true);
    setError("");
    try {
      const data = await window.MSApi.apiFetch("/api/dictionary");
      setEntries(Array.isArray(data) ? data : []);
    } catch (e) {
      setError(e && e.message || t("dict.errorLoad"));
    } finally {
      setLoading(false);
    }
  }

  useEffectX(() => { loadDictionary(); }, []);

  async function addEntry(e) {
    e.preventDefault();
    const term = form.term.trim();
    const original = form.original_mistake.trim();
    if (!term || !original || saving) return;
    setSaving(true);
    setError("");
    try {
      const row = await window.MSApi.apiFetch("/api/dictionary", {
        method: "POST",
        body: { term, original_mistake: original, category: form.category || "other" },
      });
      setEntries(list => [row, ...list.filter(x => x && x.id !== row.id)]);
      setForm({ term: "", original_mistake: "", category: "medical" });
    } catch (err) {
      setError(err && err.message || t("dict.errorAdd"));
    } finally {
      setSaving(false);
    }
  }

  async function deleteEntry(id) {
    if (!id) return;
    setError("");
    const before = entries;
    setEntries(list => list.filter(entry => entry.id !== id));
    try {
      await window.MSApi.apiFetch(`/api/dictionary/${id}`, { method: "DELETE" });
    } catch (err) {
      setEntries(before);
      setError(err && err.message || t("dict.errorDelete"));
    }
  }

  const filtered = useMemoX(() => {
    const q = query.trim().toLowerCase();
    if (!q) return entries;
    return entries.filter(entry => {
      const haystack = [entry.term, entry.original_mistake, entry.category].filter(Boolean).join(" ").toLowerCase();
      return haystack.includes(q);
    });
  }, [entries, query]);

  const canAdd = form.term.trim() && form.original_mistake.trim() && !saving;
  const categories = ["medical", "medication", "diagnosis", "procedure", "other"];

  return (
    <div className="ms-page fade-in">
      <div className="ms-page-head">
        <div>
          <h1 className="h1">{t("dict.title")}</h1>
          <div style={{color: "var(--ink-3)", marginTop: 4, fontSize: 14}}>{t("dict.sub")}</div>
        </div>
        <button className="btn" onClick={loadDictionary} disabled={loading}>
          <Icon.Refresh size={15}/>{t("common.refresh")}
        </button>
      </div>

      <div className="ms-grid" style={{gridTemplateColumns: "minmax(280px, 360px) minmax(0, 1fr)"}}>
        <form className="card" onSubmit={addEntry}>
          <div className="card-header">
            <div className="card-title">{t("dict.addTitle")}</div>
          </div>
          <div className="card-body" style={{paddingTop: 0}}>
            <div>
              <label className="field-label">{t("dict.correctTerm")} {t("common.required")}</label>
              <input className="input" value={form.term} onChange={e => set("term", e.target.value)} placeholder={t("dict.correctTermPh")}/>
            </div>
            <div style={{marginTop: 12}}>
              <label className="field-label">{t("dict.heardAs")} {t("common.required")}</label>
              <input className="input" value={form.original_mistake} onChange={e => set("original_mistake", e.target.value)} placeholder={t("dict.heardAsPh")}/>
            </div>
            <div style={{marginTop: 12}}>
              <label className="field-label">{t("dict.category")}</label>
              <div className="ms-select-wrap">
                <select className="input select" value={form.category} onChange={e => set("category", e.target.value)}>
                  {categories.map(c => <option key={c} value={c}>{c}</option>)}
                </select>
                <Icon.ChevronDown size={15}/>
              </div>
            </div>
            <button className="btn btn-primary" style={{width: "100%", justifyContent: "center", marginTop: 16}} disabled={!canAdd} type="submit">
              <Icon.Plus size={14}/>{saving ? t("dict.adding") : t("dict.add")}
            </button>
            {error && <div style={{color: "var(--accent-danger)", fontSize: 12.5, lineHeight: 1.4, marginTop: 12}}>{error}</div>}
          </div>
        </form>

        <div className="card" style={{padding: 0, overflow: "hidden"}}>
          <div style={{padding: 16, borderBottom: "1px solid var(--line)"}}>
            <div className="ms-search-input">
              <Icon.Search size={14}/>
              <input value={query} onChange={e => setQuery(e.target.value)} placeholder={t("dict.searchPh")}/>
              {query && <button type="button" style={{appearance: "none", border: 0, background: "transparent", cursor: "pointer", color: "var(--ink-4)", padding: 0}} onClick={() => setQuery("")}><Icon.X size={13}/></button>}
            </div>
          </div>

          <div className="ms-dict-table">
            <div className="ms-dict-row ms-dict-head">
              <div>{t("dict.term")}</div>
              <div>{t("dict.mistake")}</div>
              <div>{t("dict.frequency")}</div>
              <div>{t("dict.lastUsed")}</div>
              <div></div>
            </div>
            {loading ? (
              <div style={{padding: 28, textAlign: "center", color: "var(--ink-4)", fontSize: 13}}>{t("common.loading")}</div>
            ) : filtered.length === 0 ? (
              <div style={{padding: 28, textAlign: "center", color: "var(--ink-4)", fontSize: 13}}>{query ? t("dict.emptySearch") : t("dict.empty")}</div>
            ) : filtered.map(entry => (
              <div className="ms-dict-row" key={entry.id}>
                <div>
                  <div style={{fontWeight: 600, color: "var(--ink)"}}>{entry.term}</div>
                  <div className="badge" style={{marginTop: 6}}>{entry.category || "other"}</div>
                </div>
                <div style={{color: "var(--ink-3)"}}>{entry.original_mistake}</div>
                <div><span className="badge">{entry.frequency || 1}</span></div>
                <div className="dim" style={{fontSize: 12.5}}>{entry.last_used ? String(entry.last_used).slice(0, 10) : "—"}</div>
                <div style={{textAlign: "right"}}>
                  <button className="btn btn-ghost btn-icon btn-sm" onClick={() => deleteEntry(entry.id)} aria-label={t("common.delete")} title={t("common.delete")}>
                    <Icon.Trash size={14}/>
                  </button>
                </div>
              </div>
            ))}
          </div>
        </div>
      </div>
    </div>
  );
}

/* ────────────────────────────  SETTINGS  ──────────────────────────── */

function Settings() {
  const t = window.MSStore.useT();
  const [section, setSection] = useStateX("profile");

  const sections = [
    { id: "profile",   label: t("set.nav.profile"),  icon: Icon.User },
    { id: "clinic",    label: t("set.nav.clinic"),   icon: Icon.Pin },
    { id: "whatsapp",  label: "WhatsApp",             icon: Icon.Phone },
    { id: "team",      label: t("set.nav.team"),     icon: Icon.Patients },
    { id: "security",  label: t("set.nav.security"), icon: Icon.Heart },
  ];

  return (
    <div className="ms-page fade-in">
      <div className="ms-page-head">
        <div>
          <h1 className="h1">{t("set.title")}</h1>
          <div style={{color: "var(--ink-3)", marginTop: 4, fontSize: 14}}>{t("set.sub")}</div>
        </div>
      </div>

      <div className="ms-set-grid">
        <aside className="ms-set-nav">
          {sections.map(s => (
            <button key={s.id} className={"ms-set-nav-item" + (section === s.id ? " is-active" : "")} onClick={() => setSection(s.id)}>
              <s.icon size={16}/>{s.label}
            </button>
          ))}
        </aside>

        <div className="col" style={{gap: 16}}>
          {section === "profile" && <SetProfile/>}
          {section === "clinic" && <SetClinic/>}
          {section === "whatsapp" && <SetWhatsApp/>}
          {section === "team" && <SetTeam/>}
          {section === "security" && <SetSecurity/>}
        </div>
      </div>
    </div>
  );
}

function SetCard({ title, sub, children, action }) {
  return (
    <div className="card">
      <div className="card-header">
        <div>
          <div className="card-title">{title}</div>
          {sub && <div style={{fontSize: 12.5, color: "var(--ink-3)", marginTop: 2}}>{sub}</div>}
        </div>
        {action}
      </div>
      <div className="card-body" style={{paddingTop: 0}}>{children}</div>
    </div>
  );
}

function SetRow({ label, hint, children }) {
  return (
    <div className="ms-set-row">
      <div style={{flex: 1, minWidth: 0}}>
        <div style={{fontSize: 13, fontWeight: 500, color: "var(--ink)"}}>{label}</div>
        {hint && <div style={{fontSize: 12, color: "var(--ink-4)", marginTop: 2}}>{hint}</div>}
      </div>
      <div style={{flexShrink: 0}}>{children}</div>
    </div>
  );
}

function SetProfile() {
  const store = window.MSStore.useStore();
  const t = window.MSStore.useT();
  const [appLang, setAppLang] = window.MSStore.useLang();
  const prefs = window.MSStore.usePreferences();
  const profile = store.profile || {};
  const [form, setForm] = useStateX({
    name: profile.name || "",
    specialty: profile.specialty || "",
    phone: profile.phone || "",
    license_number: profile.license_number || "",
    bio: profile.bio || "",
  });
  const [pending, setPending] = useStateX(false);
  const [saved, setSaved] = useStateX(false);
  const [error, setError] = useStateX("");
  useEffectX(() => {
    setForm({
      name: profile.name || "",
      specialty: profile.specialty || "",
      phone: profile.phone || "",
      license_number: profile.license_number || "",
      bio: profile.bio || "",
    });
  }, [profile.name, profile.specialty, profile.phone, profile.license_number, profile.bio]);
  const set = (k, v) => setForm(f => ({...f, [k]: v}));

  async function save() {
    setPending(true); setError(""); setSaved(false);
    try {
      await store.updateProfile(form);
      setSaved(true);
      setTimeout(() => setSaved(false), 2000);
    } catch (e) {
      setError(e && e.message || t("set.profile.error"));
    } finally {
      setPending(false);
    }
  }

  const initials = (profile.name || "MS").split(" ").map(s => s[0]).slice(0, 2).join("").toUpperCase();

  return (
    <>
      <SetCard title={t("set.profile.title")} sub={t("set.profile.sub")}>
        <div className="row" style={{gap: 16, padding: "8px 0 16px", borderBottom: "1px solid var(--line)", marginBottom: 12}}>
          <div className="avatar avatar-lg" style={{width: 64, height: 64, fontSize: 22, background: profile.avatar_color || "var(--primary-soft)", color: profile.avatar_color ? "white" : "var(--primary-ink)", borderColor: "var(--primary-line)"}}>{initials}</div>
          <div className="col" style={{gap: 6, justifyContent: "center"}}>
            <div style={{fontSize: 13, fontWeight: 600}}>{profile.email || ""}</div>
            <div className="dim" style={{fontSize: 12}}>{t("set.profile.signedInVia", { method: profile.auth_provider === "google" ? t("set.profile.googleSso") : t("set.profile.password") })}</div>
          </div>
        </div>
        <div>
          <label className="field-label">{t("set.profile.displayName")}</label>
          <input className="input" value={form.name} onChange={e => set("name", e.target.value)} placeholder="Dr. Jane Doe"/>
        </div>
        <div style={{marginTop: 12}}>
          <label className="field-label">{t("set.profile.specialty")}</label>
          <input className="input" value={form.specialty} onChange={e => set("specialty", e.target.value)} placeholder="Family Medicine"/>
        </div>
        <div style={{marginTop: 12}}>
          <label className="field-label">{t("set.profile.phone")}</label>
          <input className="input" value={form.phone} onChange={e => set("phone", e.target.value)} placeholder="(555) 555-0100"/>
        </div>
        <div style={{marginTop: 12}}>
          <label className="field-label">{t("set.profile.license")}</label>
          <input className="input" value={form.license_number} onChange={e => set("license_number", e.target.value)}/>
        </div>
      </SetCard>

      <SetCard title={t("set.preferences.title")} sub={t("set.preferences.sub")}>
        <SetRow label={t("set.preferences.appLanguage")} hint={t("set.preferences.appLanguageHint")}>
          <div className="ms-segmented" role="group" aria-label={t("set.preferences.appLanguage")}>
            {window.MSi18n && window.MSi18n.SUPPORTED.map(code => (
              <button key={code} className={appLang === code ? "is-active" : ""} onClick={() => setAppLang(code)} aria-pressed={appLang === code}>
                {code.toUpperCase()}
              </button>
            ))}
          </div>
        </SetRow>
        <SetRow label={t("set.preferences.encounterLanguage")} hint={t("set.preferences.encounterLanguageHint")}>
          <div className="ms-select-wrap" style={{width: 240}}>
            <select className="input select" value={prefs.defaultEncounterLanguage} onChange={e => prefs.setDefaultEncounterLanguage(e.target.value)} aria-label={t("set.preferences.encounterLanguage")}>
              {TX_LANGUAGES.map(lang => (
                <option key={lang.id} value={lang.id}>{txLanguageLabel(t, lang)}</option>
              ))}
            </select>
            <Icon.ChevronDown size={15}/>
          </div>
        </SetRow>
        <SetRow label={t("set.preferences.theme")} hint={t("set.preferences.themeHint")}>
          <div className="ms-segmented" role="group" aria-label={t("set.preferences.theme")}>
            <button className={prefs.theme === "light" ? "is-active" : ""} onClick={() => prefs.setTheme("light")} aria-pressed={prefs.theme === "light"}>
              <Icon.Sun size={14}/>{t("set.preferences.themeLight")}
            </button>
            <button className={prefs.theme === "dark" ? "is-active" : ""} onClick={() => prefs.setTheme("dark")} aria-pressed={prefs.theme === "dark"}>
              <Icon.Moon size={14}/>{t("set.preferences.themeDark")}
            </button>
          </div>
        </SetRow>
      </SetCard>

      <div className="row" style={{gap: 8, justifyContent: "flex-end"}}>
        {error && <span style={{color: "var(--accent-danger)", fontSize: 12.5, alignSelf: "center", flex: 1}}>{error}</span>}
        {saved && <span style={{color: "var(--accent-success)", fontSize: 12.5, alignSelf: "center"}}>{t("common.saved")}</span>}
        <button className="btn btn-primary" disabled={pending} onClick={save}>{pending ? t("common.saving") : t("set.profile.save")}</button>
      </div>
    </>
  );
}

function SetClinic() {
  const store = window.MSStore.useStore();
  const account = store.activeAccount || {};
  const [name, setName] = useStateX(account.name || "");
  const [startHour, setStartHour] = useStateX(account.business_start_hour != null ? account.business_start_hour : 9);
  const [endHour, setEndHour] = useStateX(account.business_end_hour != null ? account.business_end_hour : 18);
  const [pending, setPending] = useStateX(false);
  const [saved, setSaved] = useStateX(false);
  const [error, setError] = useStateX("");
  useEffectX(() => {
    setName(account.name || "");
    setStartHour(account.business_start_hour != null ? account.business_start_hour : 9);
    setEndHour(account.business_end_hour != null ? account.business_end_hour : 18);
  }, [account.id, account.name, account.business_start_hour, account.business_end_hour]);

  function fmtHour(h) {
    if (h === 0) return "12 AM";
    if (h < 12) return h + " AM";
    if (h === 12) return "12 PM";
    return (h - 12) + " PM";
  }

  async function save() {
    setPending(true); setError(""); setSaved(false);
    try {
      await store.updateAccount(name, { business_start_hour: startHour, business_end_hour: endHour });
      setSaved(true);
      setTimeout(() => setSaved(false), 2000);
    } catch (e) {
      setError(e && e.message || "Could not update clinic.");
    } finally {
      setPending(false);
    }
  }

  const hourOptions = Array.from({length: 24}, (_, i) => i);

  return (
    <>
      <SetCard title="Clinic information" sub={account.id ? `Workspace ID: ${account.id}` : ""}>
        <div>
          <label className="field-label">Clinic name</label>
          <input className="input" value={name} onChange={e => setName(e.target.value)}/>
        </div>
        <SetRow label="Workspace type" hint="Solo accounts share data only with you; clinic accounts share with all members.">
          <span className="badge">{account.type === "team" ? "Clinic" : (account.type || "individual")}</span>
        </SetRow>
        <SetRow label="Your role">
          <span className="badge">{store.activeRole || "—"}</span>
        </SetRow>
      </SetCard>
      <SetCard title="Working hours" sub="Appointments can only be booked during these hours. The calendar highlights this range.">
        <div className="row" style={{gap: 16, flexWrap: "wrap"}}>
          <div style={{flex: 1, minWidth: 140}}>
            <label className="field-label">Opens at</label>
            <select className="select" value={startHour} onChange={e => setStartHour(Number(e.target.value))}>
              {hourOptions.map(h => <option key={h} value={h}>{fmtHour(h)}</option>)}
            </select>
          </div>
          <div style={{flex: 1, minWidth: 140}}>
            <label className="field-label">Closes at</label>
            <select className="select" value={endHour} onChange={e => setEndHour(Number(e.target.value))}>
              {hourOptions.filter(h => h > startHour).map(h => <option key={h} value={h}>{fmtHour(h)}</option>)}
            </select>
          </div>
        </div>
        <div style={{fontSize: 12.5, color: "var(--ink-4)", marginTop: 8}}>
          Currently: {fmtHour(startHour)} – {fmtHour(endHour)} ({endHour - startHour} hours)
        </div>
      </SetCard>
      <div className="row" style={{gap: 8, justifyContent: "flex-end"}}>
        {error && <span style={{color: "var(--accent-danger)", fontSize: 12.5, alignSelf: "center", flex: 1}}>{error}</span>}
        {saved && <span style={{color: "var(--accent-success)", fontSize: 12.5, alignSelf: "center"}}>Saved.</span>}
        <button className="btn btn-primary" disabled={pending || !name.trim()} onClick={save}>{pending ? "Saving…" : "Save"}</button>
      </div>
    </>
  );
}

function SetWhatsApp() {
  const store = window.MSStore.useStore();
  const [data, setData] = useStateX(null);
  const [number, setNumber] = useStateX("");
  const [pending, setPending] = useStateX(false);
  const [loading, setLoading] = useStateX(true);
  const [saved, setSaved] = useStateX(false);
  const [error, setError] = useStateX("");
  const [copied, setCopied] = useStateX(false);
  const [provisionOpen, setProvisionOpen] = useStateX(false);
  const [provClinicName, setProvClinicName] = useStateX("");
  const [provCountry, setProvCountry] = useStateX("US");
  const [provAreaCode, setProvAreaCode] = useStateX("");
  const [provisioning, setProvisioning] = useStateX(false);
  const [provResult, setProvResult] = useStateX(null);
  const [linkOpen, setLinkOpen] = useStateX(false);
  const [linkPhone, setLinkPhone] = useStateX("");
  const [linkName, setLinkName] = useStateX("");
  const [linkSid, setLinkSid] = useStateX("");
  const [linkToken, setLinkToken] = useStateX("");
  const [linking, setLinking] = useStateX(false);

  async function load() {
    setLoading(true); setError("");
    try {
      const r = await window.MSApi.apiFetch("/api/account/integrations");
      setData(r || {});
      setNumber((r && r.whatsapp_number) || "");
    } catch (e) {
      setError(e && e.message || "Could not load WhatsApp settings.");
    } finally {
      setLoading(false);
    }
  }

  useEffectX(() => { load(); }, [store.activeAccount && store.activeAccount.id]);

  async function save() {
    setPending(true); setError(""); setSaved(false);
    try {
      const r = await window.MSApi.apiFetch("/api/account/whatsapp-number", {
        method: "PATCH",
        body: { whatsapp_number: number.trim() },
      });
      setData(d => ({...(d || {}), ...(r || {})}));
      setNumber((r && r.whatsapp_number) || "");
      setSaved(true);
      setTimeout(() => setSaved(false), 2000);
    } catch (e) {
      setError(e && e.message || "Could not update WhatsApp number.");
    } finally {
      setPending(false);
    }
  }

  async function copyWebhook() {
    const url = data && data.webhook_url;
    if (!url) return;
    try {
      await navigator.clipboard.writeText(url);
      setCopied(true);
      setTimeout(() => setCopied(false), 1600);
    } catch (_) {}
  }

  async function provisionNumber() {
    if (!provClinicName.trim()) return;
    setProvisioning(true); setError(""); setProvResult(null);
    try {
      const r = await window.MSApi.apiFetch("/api/whatsapp/provision", {
        method: "POST",
        body: {
          clinic_name: provClinicName.trim(),
          country: provCountry,
          area_code: provAreaCode.trim(),
        },
      });
      setProvResult(r);
      setProvisionOpen(false);
      setProvClinicName("");
      setProvAreaCode("");
      await load(); // Refresh senders list
    } catch (e) {
      setError(e && e.message || "Provisioning failed.");
    } finally {
      setProvisioning(false);
    }
  }

  async function deactivateSender(senderId) {
    if (!confirm("Deactivate this WhatsApp sender? It will stop receiving messages.")) return;
    try {
      await window.MSApi.apiFetch(`/api/whatsapp/senders/${senderId}`, { method: "DELETE" });
      await load();
    } catch (e) {
      setError(e && e.message || "Could not deactivate sender.");
    }
  }

  async function linkExisting() {
    if (!linkPhone.trim()) return;
    setLinking(true); setError("");
    try {
      const r = await window.MSApi.apiFetch("/api/whatsapp/link-existing", {
        method: "POST",
        body: {
          phone_number: linkPhone.trim(),
          display_name: linkName.trim(),
          twilio_subaccount_sid: linkSid.trim(),
          twilio_subaccount_token: linkToken.trim(),
        },
      });
      setProvResult(r);
      setLinkOpen(false);
      setLinkPhone(""); setLinkName(""); setLinkSid(""); setLinkToken("");
      await load();
    } catch (e) {
      setError(e && e.message || "Failed to link number.");
    } finally {
      setLinking(false);
    }
  }

  const senders = (data && data.senders) || [];
  const primary = senders.find(s => s.is_primary) || senders[0] || null;
  const isOwner = store.activeRole === "owner" || store.activeRole === "admin_doctor";

  return (
    <>
      <SetCard title="WhatsApp" sub={store.activeAccount && store.activeAccount.name ? store.activeAccount.name : ""}>
        <div className="row" style={{gap: 8, alignItems: "flex-end"}}>
          <div style={{flex: 1, minWidth: 220}}>
            <label className="field-label">Twilio sender number (manual)</label>
            <input className="input" value={number} onChange={e => setNumber(e.target.value)} placeholder="+60123456789"/>
          </div>
          <button className="btn btn-primary" disabled={pending || loading} onClick={save}>
            <Icon.Check size={14}/>{pending ? "Saving..." : "Save"}
          </button>
        </div>
        <div className="row" style={{gap: 8, flexWrap: "wrap", marginTop: 12}}>
          <span className={"badge " + (data && data.twilio_configured ? "badge-success" : "badge-warn")}>
            <span className={"dot " + (data && data.twilio_configured ? "dot-success" : "dot-warn")}/>
            {data && data.twilio_configured ? "Twilio configured" : "Twilio token missing"}
          </span>
          <span className={"badge " + (primary ? "badge-success" : "badge-warn")}>
            <span className={"dot " + (primary ? "dot-success" : "dot-warn")}/>
            {primary ? primary.status : "No sender linked"}
          </span>
        </div>
        {error && <div style={{color: "var(--accent-danger)", fontSize: 12.5, marginTop: 10}}>{error}</div>}
        {saved && <div style={{color: "var(--accent-success)", fontSize: 12.5, marginTop: 10}}>Saved.</div>}
      </SetCard>

      {/* Auto-provision card */}
      {isOwner && (
        <SetCard title="Auto-provision number" sub="Get a dedicated WhatsApp number for your clinic automatically. No Twilio account needed.">
          {provResult && (
            <div style={{padding: 12, background: "var(--green-bg, #e8f5e9)", borderRadius: 8, marginBottom: 12}}>
              <div style={{fontWeight: 600, color: "var(--green, #2e7d32)", fontSize: 14}}>Number provisioned!</div>
              <div style={{fontSize: 13, marginTop: 4}}>{provResult.phone_number} — {provResult.display_name}</div>
            </div>
          )}
          {!provisionOpen ? (
            <button className="btn btn-primary" onClick={() => { setProvisionOpen(true); setProvClinicName(store.activeAccount?.name || ""); }}>
              <Icon.Plus size={14}/> Provision new number
            </button>
          ) : (
            <div style={{display: "flex", flexDirection: "column", gap: 12}}>
              <div>
                <label className="field-label">Clinic display name</label>
                <input className="input" value={provClinicName} onChange={e => setProvClinicName(e.target.value)} placeholder="Dr. Smith's Clinic"/>
              </div>
              <div className="row" style={{gap: 12}}>
                <div style={{flex: 1}}>
                  <label className="field-label">Country</label>
                  <select className="select" value={provCountry} onChange={e => setProvCountry(e.target.value)}>
                    <option value="US">United States</option>
                    <option value="GB">United Kingdom</option>
                    <option value="CA">Canada</option>
                    <option value="AU">Australia</option>
                    <option value="IN">India</option>
                    <option value="MY">Malaysia</option>
                    <option value="ES">Spain</option>
                    <option value="MX">Mexico</option>
                  </select>
                </div>
                <div style={{flex: 1}}>
                  <label className="field-label">Area code (optional)</label>
                  <input className="input" value={provAreaCode} onChange={e => setProvAreaCode(e.target.value)} placeholder="415"/>
                </div>
              </div>
              <div className="row" style={{gap: 8}}>
                <button className="btn btn-primary" disabled={provisioning || !provClinicName.trim()} onClick={provisionNumber}>
                  {provisioning ? "Provisioning..." : "Provision number"}
                </button>
                <button className="btn btn-ghost" onClick={() => setProvisionOpen(false)} disabled={provisioning}>Cancel</button>
              </div>
              <div style={{fontSize: 12, color: "var(--ink-4)"}}>
                This creates a Twilio subaccount, buys a phone number, and configures the webhook automatically.
                The number will be ready for WhatsApp within minutes.
              </div>
            </div>
          )}
        </SetCard>
      )}

      {/* Link existing number card */}
      {isOwner && (
        <SetCard title="Link existing number" sub="Already have a WhatsApp Business number? Connect it here.">
          {!linkOpen ? (
            <button className="btn btn-ghost" onClick={() => setLinkOpen(true)}>
              <Icon.Plus size={14}/> Link existing number
            </button>
          ) : (
            <div style={{display: "flex", flexDirection: "column", gap: 12}}>
              <div className="row" style={{gap: 12}}>
                <div style={{flex: 1}}>
                  <label className="field-label">WhatsApp number</label>
                  <input className="input" value={linkPhone} onChange={e => setLinkPhone(e.target.value)} placeholder="+15558887292"/>
                </div>
                <div style={{flex: 1}}>
                  <label className="field-label">Display name</label>
                  <input className="input" value={linkName} onChange={e => setLinkName(e.target.value)} placeholder="My Clinic"/>
                </div>
              </div>
              <div style={{fontSize: 12.5, color: "var(--ink-4)", fontWeight: 500}}>Twilio subaccount credentials (optional — leave blank to use master account)</div>
              <div className="row" style={{gap: 12}}>
                <div style={{flex: 1}}>
                  <label className="field-label">Subaccount SID</label>
                  <input className="input" value={linkSid} onChange={e => setLinkSid(e.target.value)} placeholder="ACxxxxxxxx"/>
                </div>
                <div style={{flex: 1}}>
                  <label className="field-label">Auth Token</label>
                  <input className="input" type="password" value={linkToken} onChange={e => setLinkToken(e.target.value)} placeholder="••••••••"/>
                </div>
              </div>
              <div className="row" style={{gap: 8}}>
                <button className="btn btn-primary" disabled={linking || !linkPhone.trim()} onClick={linkExisting}>
                  {linking ? "Linking..." : "Link number"}
                </button>
                <button className="btn btn-ghost" onClick={() => setLinkOpen(false)} disabled={linking}>Cancel</button>
              </div>
              <div style={{fontSize: 12, color: "var(--ink-4)"}}>
                If this number is on a Twilio subaccount, provide the credentials so messages route through that account.
                Otherwise, messages will use the platform's master Twilio account.
              </div>
            </div>
          )}
        </SetCard>
      )}

      <SetCard title="Webhook">
        <SetRow label="Inbound URL">
          <div className="row" style={{gap: 8, justifyContent: "flex-end"}}>
            <span className="mono" style={{fontSize: 12.5, color: "var(--ink-3)", maxWidth: 420, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap"}}>{(data && data.webhook_url) || "—"}</span>
            <button className="btn btn-icon btn-sm" onClick={copyWebhook} aria-label="Copy webhook URL"><Icon.Copy size={14}/></button>
          </div>
        </SetRow>
        {copied && <div style={{color: "var(--accent-success)", fontSize: 12.5, marginTop: 8}}>Copied.</div>}
      </SetCard>

      <SetCard title={`Linked senders (${senders.length})`}>
        {loading && <div style={{padding: 18, color: "var(--ink-4)", fontSize: 13}}>Loading...</div>}
        {!loading && senders.length === 0 && <div style={{padding: 18, color: "var(--ink-4)", fontSize: 13}}>No WhatsApp sender linked. Use auto-provision above or manually enter a Twilio number.</div>}
        {!loading && senders.map(sender => (
          <div key={sender.id} className="ms-set-row">
            <div className="row" style={{gap: 10, minWidth: 0}}>
              <div className="avatar"><Icon.Phone size={14}/></div>
              <div style={{minWidth: 0}}>
                <div style={{fontSize: 13.5, fontWeight: 600}}>
                  {sender.phone_number}
                  {sender.display_name && <span style={{fontWeight: 400, color: "var(--ink-3)", marginLeft: 8}}>{sender.display_name}</span>}
                </div>
                <div style={{fontSize: 12, color: "var(--ink-4)", marginTop: 2}}>
                  {sender.provider} · {sender.inbound_count || 0} inbound · {sender.outbound_count || 0} outbound
                  {sender.twilio_subaccount_sid && " · subaccount"}
                </div>
              </div>
            </div>
            <div className="row" style={{gap: 8}}>
              {sender.last_inbound_at && <span className="dim" style={{fontSize: 12}}>{sender.last_inbound_at.slice(0, 16)}</span>}
              <span className={"badge " + (sender.status === "active" ? "badge-success" : "")}>{sender.status}</span>
              {isOwner && sender.status === "active" && (
                <button className="btn btn-sm btn-ghost" style={{color: "var(--red)"}} onClick={() => deactivateSender(sender.id)}>
                  Deactivate
                </button>
              )}
            </div>
          </div>
        ))}
      </SetCard>
    </>
  );
}

function SetTeam() {
  const store = window.MSStore.useStore();
  const [email, setEmail] = useStateX("");
  const [role, setRole] = useStateX("doctor");
  const [pending, setPending] = useStateX(false);
  const [error, setError] = useStateX("");
  const [success, setSuccess] = useStateX("");
  const members = store.members || [];
  const invitations = store.invitations || [];

  async function invite() {
    if (!email.trim() || pending) return;
    setPending(true); setError(""); setSuccess("");
    try {
      const r = await store.inviteTeamMember(email.trim(), role);
      setSuccess(r && r.status === "added" ? "Member added." : "Invitation sent.");
      setEmail("");
    } catch (e) {
      setError(e && e.message || "Could not send invitation.");
    } finally {
      setPending(false);
    }
  }

  const canInvite = store.isAdmin;
  const initials = (n) => (n || "?").split(" ").map(s => s[0]).slice(0, 2).join("").toUpperCase();

  return (
    <>
      {canInvite && (
        <SetCard title="Invite team member">
          <div className="row" style={{gap: 8, alignItems: "flex-end"}}>
            <div style={{flex: 1}}>
              <label className="field-label">Email</label>
              <input className="input" type="email" value={email} onChange={e => setEmail(e.target.value)} placeholder="colleague@clinic.com"/>
            </div>
            <div style={{width: 200}}>
              <label className="field-label">Role</label>
              <select className="input select" value={role} onChange={e => setRole(e.target.value)}>
                <option value="admin_doctor">Admin doctor</option>
                <option value="doctor">Doctor</option>
                <option value="staff">Staff</option>
              </select>
            </div>
            <button className="btn btn-primary" disabled={pending || !email.trim()} onClick={invite}>{pending ? "Inviting…" : "Send invite"}</button>
          </div>
          {error && <div style={{color: "var(--accent-danger)", fontSize: 12.5, marginTop: 8}}>{error}</div>}
          {success && <div style={{color: "var(--accent-success)", fontSize: 12.5, marginTop: 8}}>{success}</div>}
        </SetCard>
      )}
      <SetCard title={`Team members (${members.length})`} sub={`${members.filter(m => m.role === "doctor" || m.role === "admin_doctor" || m.role === "owner").length} providers · ${members.filter(m => m.role === "staff").length} staff`}>
        {members.length === 0 && <div style={{padding: 24, color: "var(--ink-4)", fontSize: 13}}>No active members yet.</div>}
        {members.map(m => (
          <div key={m.id} className="ms-set-row">
            <div className="row" style={{gap: 10, flex: 1, minWidth: 0}}>
              <div className="avatar" style={{background: m.avatar_color || "var(--primary-soft)", color: m.avatar_color ? "white" : "var(--primary-ink)", borderColor: "var(--primary-line)"}}>{initials(m.name)}</div>
              <div style={{minWidth: 0}}>
                <div style={{fontSize: 13.5, fontWeight: 500}}>{m.name}</div>
                <div style={{fontSize: 12, color: "var(--ink-4)"}}>{m.role.replace("_", " ")} · {m.email}</div>
              </div>
            </div>
            <span className="badge badge-success"><span className="dot dot-success"/>Active</span>
          </div>
        ))}
        {invitations.length > 0 && (
          <>
            <div className="eyebrow" style={{marginTop: 16, marginBottom: 8}}>Pending invitations</div>
            {invitations.map(inv => (
              <div key={inv.id} className="ms-set-row">
                <div className="row" style={{gap: 10, flex: 1, minWidth: 0}}>
                  <div className="avatar"><Icon.Mail size={14}/></div>
                  <div style={{minWidth: 0}}>
                    <div style={{fontSize: 13.5, fontWeight: 500}}>{inv.email}</div>
                    <div style={{fontSize: 12, color: "var(--ink-4)"}}>{inv.role.replace("_", " ")}</div>
                  </div>
                </div>
                <span className="badge badge-warn">Invited</span>
              </div>
            ))}
          </>
        )}
      </SetCard>
    </>
  );
}

function SetTemplates() {
  const store = window.MSStore.useStore();
  const list = (store.templates && store.templates.length) ? store.templates : TX_FALLBACK_TEMPLATES;
  return (
    <SetCard title={t("set.tmpl.title")} sub={t("set.tmpl.sub")}>
      <div className="col" style={{gap: 8}}>
        {list.map(t => (
          <div key={t.id} className="ms-set-row">
            <div style={{flex: 1, minWidth: 0}}>
              <div style={{fontSize: 13.5, fontWeight: 600}}>{t.name || t.label}</div>
              <div style={{fontSize: 12, color: "var(--ink-4)", marginTop: 2}}>{t.description || t.desc || ""}</div>
            </div>
            {t.specialty && <span className="badge">{t.specialty}</span>}
          </div>
        ))}
      </div>
    </SetCard>
  );
}

function SetSecurity() {
  const store = window.MSStore.useStore();
  const t = window.MSStore.useT();
  const profile = store.profile || {};
  const [deleting, setDeleting] = useStateX(false);
  const [error, setError] = useStateX("");
  const [confirmOpen, setConfirmOpen] = useStateX(false);
  const [confirmText, setConfirmText] = useStateX("");

  async function deleteAccount() {
    const phrase = t("set.sec.deleteConfirmPhrase");
    if (confirmText !== phrase) {
      setError(t("set.sec.deletePhraseError", { phrase }));
      return;
    }
    setDeleting(true);
    setError("");
    try {
      await store.deleteOwnAccount();
    } catch (e) {
      setError(e && e.message || t("set.sec.deleteError"));
      setDeleting(false);
    }
  }

  return (
    <>
      <SetCard title={t("set.sec.signin")} sub={t("set.sec.signinSub")}>
        <SetRow label={t("set.sec.method")} hint={profile.auth_provider === "google" ? t("set.sec.methodGoogle") : t("set.sec.methodPwd")}>
          <span className="badge badge-success"><Icon.Check size={11}/>{profile.auth_provider === "google" ? t("set.sec.googleSso") : t("set.sec.pwd")}</span>
        </SetRow>
        <SetRow label={t("set.sec.email")}><span className="mono" style={{fontSize: 12.5}}>{profile.email || "—"}</span></SetRow>
        <SetRow label={t("set.sec.created")} hint={profile.created_at || ""}><span className="dim" style={{fontSize: 12.5}}>{(profile.created_at || "").slice(0, 10)}</span></SetRow>
      </SetCard>

      <SetCard title={t("set.sec.hipaa")}>
        <SetRow label={t("set.sec.phi")} hint={t("set.sec.phiHint")}><span className="badge badge-success">{t("set.sec.active")}</span></SetRow>
        <SetRow label={t("set.sec.audit")} hint={t("set.sec.auditHint")}><span className="badge">{t("set.sec.enabled")}</span></SetRow>
      </SetCard>

      <SetCard title={t("set.sec.danger")} sub={t("set.sec.dangerSub")}>
        <SetRow label={t("set.sec.deleteAccount")} hint={t("set.sec.deleteHint")}>
          <button className="btn btn-sm" disabled={deleting} onClick={() => { setConfirmOpen(true); setConfirmText(""); setError(""); }} style={{color: "var(--accent-danger)"}}>
            <Icon.Trash size={13}/>{deleting ? t("common.loading") : t("set.sec.deleteButton")}
          </button>
        </SetRow>
        {error && <div style={{color: "var(--accent-danger)", fontSize: 12.5, marginTop: 8}}>{error}</div>}
      </SetCard>

      {confirmOpen && (
        <div className="ms-edit-overlay" role="dialog" aria-modal="true" aria-label={t("set.sec.deleteAccount")} onClick={e => { if (e.target === e.currentTarget && !deleting) setConfirmOpen(false); }}>
          <div className="ms-book-modal slide-up" style={{maxWidth: 520}}>
            <div className="ms-edit-head">
              <div>
                <div className="card-title" style={{color: "var(--accent-danger)"}}>{t("set.sec.deleteAccount")}</div>
                <div className="dim" style={{fontSize: 12.5, marginTop: 2}}>{profile.email || ""}</div>
              </div>
              <button className="btn btn-icon btn-sm" disabled={deleting} onClick={() => setConfirmOpen(false)} aria-label={t("common.close")}><Icon.X size={16}/></button>
            </div>
            <div className="card-body" style={{padding: 20}}>
              <div style={{fontSize: 13.5, color: "var(--ink-3)", lineHeight: 1.5}}>{t("set.sec.deleteModalBody")}</div>
              <div style={{marginTop: 16}}>
                <label className="field-label">{t("set.sec.deleteTypeLabel", { phrase: t("set.sec.deleteConfirmPhrase") })}</label>
                <input className="input" value={confirmText} onChange={e => { setConfirmText(e.target.value); if (error) setError(""); }} autoFocus/>
              </div>
              {error && <div style={{color: "var(--accent-danger)", fontSize: 12.5, marginTop: 10}}>{error}</div>}
              <div className="row" style={{gap: 8, justifyContent: "flex-end", marginTop: 18}}>
                <button className="btn" disabled={deleting} onClick={() => setConfirmOpen(false)}>{t("common.cancel")}</button>
                <button className="btn btn-primary" disabled={deleting || confirmText !== t("set.sec.deleteConfirmPhrase")} onClick={deleteAccount} style={{background: "var(--accent-danger)", borderColor: "var(--accent-danger)"}}>
                  <Icon.Trash size={14}/>{deleting ? t("common.loading") : t("set.sec.deleteButton")}
                </button>
              </div>
            </div>
          </div>
        </div>
      )}
    </>
  );
}

Object.assign(window, { Transcribe, Dictionary, Settings });
