// Copilot UI — left rail. Conversational with chip-replies.
// v1c Epic 4: non-destructive apply — fills go through, overwrites require confirm; undo restores originals.

const { useState: useStateCop, useEffect: useEffectCop, useRef: useRefCop, useMemo: useMemoCop, useCallback: useCallbackCop } = React;

const EXAMPLE_PROMPTS = [
  "52F with T2DM and HTN, 89.5 kg, 163 cm, lightly active",
  "74M, CKD stage 4, recent unintentional weight loss",
  "Stage 3 sacral pressure injury, otherwise healthy 68F",
];

// ---- Field label + value formatting for confirm cards ---------------------
const FIELD_LABELS = {
  age: "Age", sex: "Sex", weight: "Weight", weightUnit: "Weight unit",
  height: "Height", heightUnit: "Height unit",
  diagnosis: "Diagnosis", ckdStage: "CKD stage",
  comorbidities: "Comorbidities", activity: "Activity",
  labs: "Labs", foodSecurity: "Food security",
  weightHistory: "Weight history", diagnosisOther: "Diagnosis (other)",
};
function fieldLabel(k) { return FIELD_LABELS[k] || k; }
function displayVal(k, v, ctx) {
  if (v == null || v === "" || (Array.isArray(v) && v.length === 0)) return "—";
  if (k === "sex") return v === "F" ? "Female" : v === "M" ? "Male" : String(v);
  if (k === "diagnosis") return v?.label || String(v);
  if (k === "activity") {
    const a = window.RD.ACTIVITY.find(x => x.id === v);
    return a ? a.label : String(v);
  }
  if (k === "weight") {
    const u = (ctx && ctx.weightUnit) || "";
    return `${v}${u ? " " + u : ""}`;
  }
  if (k === "height") {
    const u = (ctx && ctx.heightUnit) || "";
    return `${v}${u ? " " + u : ""}`;
  }
  if (Array.isArray(v)) return v.join(", ");
  return String(v);
}
function isEmptyValue(v) {
  return v == null || v === "" || (Array.isArray(v) && v.length === 0);
}
function pick(obj, keys) {
  const out = {};
  for (const k of keys) out[k] = obj[k];
  return out;
}
function shallowEq(a, b) {
  if (a === b) return true;
  if (Array.isArray(a) && Array.isArray(b)) {
    if (a.length !== b.length) return false;
    return a.every((x, i) => x === b[i]);
  }
  if (a && b && typeof a === "object" && typeof b === "object") {
    return a.id && b.id ? a.id === b.id : false;
  }
  return false;
}

function CopilotRail({ state, set, calc, setCalc, pes, setPES, note, setNote, openAbout }) {
  const [messages, setMessages] = useStateCop([]);
  const [draft, setDraft] = useStateCop("");
  const [sessionMem, setSessionMem] = useStateCop({ weightUnit: null, heightUnit: null });
  const [latestUndoMsgId, setLatestUndoMsgId] = useStateCop(null);
  const bodyRef = useRefCop(null);
  const taRef = useRefCop(null);

  const hasMessages = messages.length > 0;

  useEffectCop(() => {
    if (bodyRef.current) bodyRef.current.scrollTop = bodyRef.current.scrollHeight;
  }, [messages.length]);

  // ----- Dispatcher -----
  const dispatchMessage = useCallbackCop((text, opts = {}) => {
    if (!text || !text.trim()) return;
    const trimmed = text.trim();

    const userMsg = { id: cryptoId(), role: "user", kind: "text", text: trimmed };
    setMessages(prev => [...prev, userMsg]);

    const intents = window.RD_NLU.classify(trimmed);
    const replies = [];

    const setIntents = intents.filter(i => i.kind === "set");
    const isBulk = setIntents.length >= 2;

    let updatedState = state;
    let fillUndoBefore = null;
    let fillUndoFields = null;

    if (setIntents.length) {
      const { patch, ambigs, summary } = window.RD_Actions.applySetIntents(intents, state, sessionMem);
      const fillPatch = {};
      const overwritePatch = {};
      const overwriteChanges = [];

      for (const k of Object.keys(patch)) {
        const cur = state[k];
        const nextVal = patch[k];
        if (shallowEq(cur, nextVal)) continue;
        // Treat unit fields as "fill" when paired with a fresh value —
        // otherwise a bare unit change would trigger a confirm on its own.
        const isUnitField = (k === "weightUnit" || k === "heightUnit");
        const pairedFieldEmpty = isUnitField
          ? isEmptyValue(state[k === "weightUnit" ? "weight" : "height"])
          : false;
        if (isEmptyValue(cur) || pairedFieldEmpty) {
          fillPatch[k] = nextVal;
        } else {
          overwritePatch[k] = nextVal;
          overwriteChanges.push({
            field: k,
            label: fieldLabel(k),
            from: displayVal(k, cur, state),
            to: displayVal(k, nextVal, { ...state, ...patch }),
          });
        }
      }

      if (Object.keys(fillPatch).length) {
        fillUndoBefore = pick(state, Object.keys(fillPatch));
        fillUndoFields = Object.keys(fillPatch);
        set(fillPatch);
        updatedState = { ...state, ...fillPatch };
        if (fillPatch.weightUnit) setSessionMem(s => ({ ...s, weightUnit: fillPatch.weightUnit }));
        if (fillPatch.heightUnit) setSessionMem(s => ({ ...s, heightUnit: fillPatch.heightUnit }));
      }

      if (overwriteChanges.length) {
        replies.push({
          id: cryptoId(),
          role: "assistant",
          kind: "confirm",
          text: overwriteChanges.length === 1
            ? "This would overwrite an existing value. Apply?"
            : `This would overwrite ${overwriteChanges.length} existing values. Apply?`,
          changes: overwriteChanges,
          patch: overwritePatch,
          before: pick(state, Object.keys(overwritePatch)),
          fillBefore: fillUndoBefore,
          fillFields: fillUndoFields,
          status: "pending",
        });
      } else {
        if (isBulk && summary.length) {
          const r = window.RD_Actions.summaryReply(summary);
          if (r) { r.id = cryptoId(); replies.push(r); }
        } else if (!isBulk && summary.length) {
          replies.push({ id: cryptoId(), role: "assistant", kind: "text", text: `Updated ${summary[0]}.` });
        }
        if (fillUndoFields && fillUndoFields.length && replies.length) {
          const lastAssistant = [...replies].reverse().find(r => r.role === "assistant");
          if (lastAssistant) {
            lastAssistant.undo = { before: fillUndoBefore, fields: fillUndoFields };
            setLatestUndoMsgId(lastAssistant.id);
          }
        }
      }

      if (ambigs.length) {
        for (const r of window.RD_Actions.ambigReplies(ambigs)) replies.push({ ...r, id: cryptoId() });
      }
    }

    for (const it of intents) {
      if (it.kind === "method") {
        setCalc({ energyMethod: it.method });
        const r = window.RD_Actions.methodReply(it.method);
        replies.push({ ...r, id: cryptoId() });
      }
    }
    for (const it of intents) {
      if (it.kind === "calc") {
        const missing = window.RD_Rules.missingForCalc(it.what, updatedState);
        if (missing.length) {
          const r = window.RD_Rules.nudgeReply(missing, it.what);
          replies.push({ ...r, id: cryptoId() });
        } else {
          const r = window.RD_Actions.calcReply(it.what, updatedState);
          replies.push({ ...r, id: cryptoId() });
        }
      }
    }
    for (const it of intents) {
      if (it.kind === "doc") { const r = window.RD_Actions.docReply(it.what); replies.push({ ...r, id: cryptoId() }); }
    }
    for (const it of intents) {
      if (it.kind === "explain") { const r = window.RD_Actions.explainReply(it.topic); replies.push({ ...r, id: cryptoId() }); }
    }
    for (const it of intents) {
      if (it.kind === "meta") { const r = window.RD_Actions.metaReply(it.topic); replies.push({ ...r, id: cryptoId() }); }
    }
    for (const it of intents) {
      if (it.kind === "oos") { replies.push({ ...window.RD_Actions.oosReply(), id: cryptoId() }); }
    }
    if (intents.length === 1 && intents[0].kind === "noop" && replies.length === 0) {
      replies.push({ ...window.RD_Actions.noopReply(intents[0].text), id: cryptoId() });
    }

    if (replies.length) setMessages(prev => [...prev, ...replies]);
  }, [state, sessionMem, set, setCalc]);

  // ----- Confirm actions -----
  const onConfirmApply = useCallbackCop((msg) => {
    set(msg.patch);
    if (msg.patch.weightUnit) setSessionMem(s => ({ ...s, weightUnit: msg.patch.weightUnit }));
    if (msg.patch.heightUnit) setSessionMem(s => ({ ...s, heightUnit: msg.patch.heightUnit }));

    // Undo target = ORIGINALS from before this turn (fills captured pre-set + overwrites pre-apply).
    const combinedBefore = { ...(msg.fillBefore || {}), ...msg.before };

    setMessages(prev => prev.map(m =>
      m.id === msg.id
        ? { ...m, status: "applied", undo: { before: combinedBefore, fields: Object.keys(combinedBefore) } }
        : m
    ));
    setLatestUndoMsgId(msg.id);
  }, [set]);

  const onConfirmCancel = useCallbackCop((msg) => {
    setMessages(prev => prev.map(m => m.id === msg.id ? { ...m, status: "cancelled" } : m));
  }, []);

  const onUndo = useCallbackCop((msg) => {
    if (!msg.undo) return;
    set(msg.undo.before);
    setMessages(prev => prev.map(m => m.id === msg.id ? { ...m, undo: null, undone: true } : m));
    setLatestUndoMsgId(null);
  }, [set]);

  // Resolve chip click — applies a specific resolution.
  const onChipAction = useCallbackCop((action) => {
    if (!action) return;
    if (action.kind === "resolve") {
      if (action.field === "weight") {
        set({ weight: String(action.value), weightUnit: action.unit });
        setSessionMem(s => ({ ...s, weightUnit: action.unit }));
        setMessages(prev => [...prev, { id: cryptoId(), role: "assistant", kind: "text", text: `Got it — ${action.value} ${action.unit}.` }]);
      } else if (action.field === "diagnosis") {
        const dx = window.RD.DIAGNOSES.find(d => d.id === action.value);
        if (dx) {
          set({ diagnosis: dx, ckdStage: action.stage });
          setMessages(prev => [...prev, { id: cryptoId(), role: "assistant", kind: "text", text: `Set diagnosis to ${dx.label}, stage ${action.stage}.` }]);
        }
      } else if (action.field === "activity") {
        const a = window.RD.ACTIVITY.find(x => x.id === action.value);
        set({ activity: action.value });
        setMessages(prev => [...prev, { id: cryptoId(), role: "assistant", kind: "text", text: `Activity set to ${a ? a.label : action.value}.` }]);
      } else if (action.field === "lab") {
        const labStr = `${action.labName} ${action.value}${action.suffix || ""}`;
        const existing = (state.labs || "").trim();
        const list = existing ? existing.split(/,\s*|\n+/).map(s => s.trim()).filter(Boolean) : [];
        const idx = list.findIndex(x => x.toLowerCase().startsWith(action.labName.toLowerCase() + " "));
        if (idx >= 0) list[idx] = labStr; else list.push(labStr);
        set({ labs: list.join(", ") });
        setMessages(prev => [...prev, { id: cryptoId(), role: "assistant", kind: "text", text: `Added ${labStr} to labs.` }]);
      }
    }
    if (action.kind === "flag-pes") {
      const PROBLEMS = window.RD.PROBLEMS || [];
      const match = PROBLEMS.find(p => p.label.toLowerCase() === action.problem.toLowerCase());
      if (match) {
        setPES({ problem: match.id });
        setMessages(prev => [...prev, { id: cryptoId(), role: "assistant", kind: "text", text: `Set PES Problem to "${match.label}". Edit etiology and signs in the Documentation zone.` }]);
      } else {
        setMessages(prev => [...prev, { id: cryptoId(), role: "assistant", kind: "text", text: `Noted "${action.problem}" — review in the PES builder.` }]);
      }
    }
    if (action.kind === "set-basis") {
      setCalc({ proteinWeightBasis: action.value });
      setMessages(prev => [...prev, { id: cryptoId(), role: "assistant", kind: "text", text: `Switched to ${action.value} body weight for protein.` }]);
    }
    if (action.kind === "dismiss-flag") { /* flags panel handles */ }
    if (action.kind === "focus") {
      const el = document.querySelector(`[data-field="${action.target}"]`);
      if (el) { el.focus(); el.scrollIntoView({ block: "center", behavior: "smooth" }); }
    }
  }, [set, setCalc, setPES, state]);

  function onSubmit(e) {
    e.preventDefault();
    const text = draft.trim();
    if (!text) return;
    setDraft("");
    if (taRef.current) taRef.current.style.height = "auto";
    dispatchMessage(text);
  }
  function onExample(text) { dispatchMessage(text); }
  function onTextareaInput(e) {
    setDraft(e.target.value);
    e.target.style.height = "auto";
    e.target.style.height = Math.min(160, e.target.scrollHeight) + "px";
  }
  function onKeyDown(e) {
    if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); onSubmit(e); }
  }

  return (
    <>
      <div className="rail-head rail-head--slim">
        <span className="rail-label">Copilot</span>
      </div>

      {/* Session strip moved to the chart header (cases.jsx / CaseBar) in v1f */}

      <div className="rail-body" ref={bodyRef}>
        {!hasMessages ? (
          <EmptyState onExample={onExample} />
        ) : (
          <MessagesView
            messages={messages}
            onChipAction={onChipAction}
            onConfirmApply={onConfirmApply}
            onConfirmCancel={onConfirmCancel}
            onUndo={onUndo}
            latestUndoMsgId={latestUndoMsgId}
          />
        )}
      </div>

      <div className="composer-wrap">
        <form className="composer" onSubmit={onSubmit}>
          <textarea
            ref={taRef}
            rows={1}
            value={draft}
            placeholder={hasMessages ? "Reply, or update the chart…" : "Describe your patient or ask me to update the chart…"}
            aria-label="Message the copilot"
            onChange={onTextareaInput}
            onKeyDown={onKeyDown}
          />
          <button type="submit" className="send-btn" aria-label="Send" disabled={!draft.trim()}>
            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M12 19V5"/><path d="m6 11 6-6 6 6"/></svg>
          </button>
        </form>
      </div>
    </>
  );
}

// ----- Subcomponents -----

function EmptyState({ onExample }) {
  return (
    <>
      <section className="greeting" aria-label="Greeting">
        <h1>Hi, <em>Joyce</em>. Tell me about your <em>patient</em>, and I'll start the chart.</h1>
        <p>Or jump in anywhere on the right — or click 'Sample patient' in the top-right to see a fully-loaded example.</p>
      </section>
      <section aria-label="Example prompts" style={{ display: "flex", flexDirection: "column", gap: 10 }}>
        <div className="examples-label">Try one of these</div>
        <div className="examples">
          {EXAMPLE_PROMPTS.map((p, i) => (
            <button key={i} className="example" type="button" onClick={() => onExample(p)}>
              <span>{p}</span>
              <span className="arrow" aria-hidden="true">
                <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M5 12h14"/><path d="m13 6 6 6-6 6"/></svg>
              </span>
            </button>
          ))}
        </div>
      </section>
      <div className="helper">
        <span>Press <kbd>Enter</kbd> to send · <kbd>Shift+Enter</kbd> for newline.</span>
      </div>
    </>
  );
}

function MessagesView({ messages, onChipAction, onConfirmApply, onConfirmCancel, onUndo, latestUndoMsgId }) {
  return (
    <div className="messages">
      {messages.map(m => (
        <Message
          key={m.id}
          m={m}
          onChipAction={onChipAction}
          onConfirmApply={onConfirmApply}
          onConfirmCancel={onConfirmCancel}
          onUndo={onUndo}
          isLatestUndo={m.id === latestUndoMsgId && !!m.undo}
        />
      ))}
    </div>
  );
}

function Message({ m, onChipAction, onConfirmApply, onConfirmCancel, onUndo, isLatestUndo }) {
  return (
    <div className={`msg msg-${m.role}`}>
      {m.role === "assistant" && <div className="msg-glyph" aria-hidden="true">·</div>}
      <div className="msg-body">
        <div className="msg-text">{m.text}</div>
        {m.cite && <div className="msg-cite">{m.cite}</div>}
        {m.kind === "chips" && m.chips && (
          <div className="msg-chips">
            {m.chips.map((c, i) => (
              <button key={i} type="button" className="msg-chip" onClick={() => onChipAction(c.action)}>
                {c.label}
              </button>
            ))}
          </div>
        )}
        {m.kind === "confirm" && (
          <div className="copilot-confirm">
            <ul className="confirm-changes">
              {m.changes.map((c, i) => (
                <li key={i}>
                  <span className="cc-field">{c.label}</span>
                  <span className="cc-diff">
                    <span className="cc-from">{c.from}</span>
                    <span className="cc-sep" aria-hidden="true">→</span>
                    <span className="cc-to">{c.to}</span>
                  </span>
                </li>
              ))}
            </ul>
            {m.status === "pending" && (
              <div className="confirm-actions">
                <button type="button" className="msg-chip primary" onClick={() => onConfirmApply(m)}>Apply all</button>
                <button type="button" className="msg-chip" onClick={() => onConfirmCancel(m)}>Cancel</button>
              </div>
            )}
            {m.status === "applied" && <div className="confirm-status applied">Applied.</div>}
            {m.status === "cancelled" && <div className="confirm-status cancelled">No changes made.</div>}
          </div>
        )}
        {isLatestUndo && !m.undone && (
          <div className="msg-undo">
            <button type="button" className="undo-chip" onClick={() => onUndo(m)} title="Restore the values from before this change">
              <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M3 12a9 9 0 1 0 3-6.7"/><path d="M3 4v5h5"/></svg>
              <span>Undo</span>
            </button>
          </div>
        )}
        {m.undone && <div className="confirm-status cancelled">Undone.</div>}
      </div>
    </div>
  );
}

function SessionStrip() {
  const [tick, setTick] = useStateCop(0);
  const startRef = useRefCop(Date.now());
  useEffectCop(() => {
    const t = setInterval(() => setTick(x => x + 1), 30000);
    return () => clearInterval(t);
  }, []);
  function fmt() {
    const s = Math.floor((Date.now() - startRef.current) / 1000);
    if (s < 60) return "just now";
    const m = Math.floor(s / 60);
    if (m < 60) return m + " min ago";
    return Math.floor(m / 60) + "h ago";
  }
  return (
    <div className="session-strip" role="status">
      <span className="pulse" aria-hidden="true"></span>
      <span>Session started <span>{fmt()}</span></span>
      <span className="sep">·</span>
      <span>Nothing saved</span>
      <button className="new-session" type="button" onClick={() => window.location.reload()} data-tip="End current session and start a new one" data-tip-placement="top" data-tip-onboard="1">New session</button>
    </div>
  );
}

function cryptoId() {
  return (window.crypto?.randomUUID?.() || (Math.random().toString(36).slice(2) + Date.now().toString(36)));
}

window.CopilotRail = CopilotRail;
