/* ============================================================
   VKTR TCO — Function 2: Live Assistant (V1.7)
   ------------------------------------------------------------
   Design (Notion: V1.7 Roadmap, "Function 2 -- core architecture"):
   - Bounded 2-call pattern, NOT an agentic tool-calling loop:
       Call 1 (assistant_plan, cheap, no thinking)  -> which what-if
         scenarios (if any) to simulate, and whether the question
         actually needs a different screen's fields.
       Local compute (free, instant) -> window.computeTCO() runs for
         every requested scenario using the already-Excel-validated
         engine. The LLM NEVER computes a cost number itself.
       Call 2 (assistant_recommend, moderate extended thinking) -> the
         actual recommendation text + a proposed-changes array, grounded
         in the real numbers from local compute.
   - Propose, never silent-write: every field change is shown as
     old -> new and requires an explicit Accept, individually or via
     "Apply all". Rejecting/accepting never ends the conversation.
   - Floating widget: closed by default, drag-to-reposition, minimize,
     full close. Position/open state persist via localStorage (a UI
     preference, not account data -- no Firestore round-trip needed).
   - Conversation auto-scopes per screen -- switching screens shows
     that screen's own history, never silently mixed with another's.
   ============================================================ */

const ASSISTANT_UI_KEY = "vktr_assistant_ui_v1";

const AssistantStore = {
  load() {
    try {
      const raw = localStorage.getItem(ASSISTANT_UI_KEY);
      const parsed = raw ? JSON.parse(raw) : {};
      // Migration (v1.7.5): anyone who already had this widget open before
      // today saved the OLD default position (right:24, bottom:24), which
      // sat inside the .bottom-nav bar's footprint and overlapped "Lanjut"/
      // "Kembali" -- the actual bug being fixed. A saved position exactly
      // matching that old default means "never actually dragged," not a
      // deliberate placement, so it's safe (and necessary) to replace with
      // the new default rather than silently keep serving the bug via a
      // stale localStorage value the position-fix itself can't override.
      if (parsed.right === 24 && parsed.bottom === 24) {
        delete parsed.right;
        delete parsed.bottom;
      }
      return {
        // Default position stacks above the Help guide-toggle (right:20,
        // bottom:84, 50px tall -- see .guide-toggle in styles.css), which
        // itself already clears the 64px-tall .bottom-nav bar.
        open: false, minimized: false, right: 20, bottom: 146, memoryMode: "rolling",
        ...parsed,
      };
    } catch (e) {
      return { open: false, minimized: false, right: 20, bottom: 146, memoryMode: "rolling" };
    }
  },
  save(uiState) {
    try { localStorage.setItem(ASSISTANT_UI_KEY, JSON.stringify(uiState)); } catch (e) {}
  },
};

// Screens 2-5 own an editable field set already defined for the "Reset to
// Default" buttons (data.jsx SCREEN_RESET_KEYS) -- reused here as-is so the
// assistant's per-screen context always matches exactly what that screen's
// own reset button considers "this screen's fields", no separate list to
// drift out of sync. Screen 6 (Results) has no own fields -- core context
// only, until/unless the plan step pulls in another screen.
// v1.8 (2026-07-17): annualKm is often DERIVED now (Ritase-Cycle Engine,
// §10.10 CALCULATION_ENGINE.md) -- ritaseDistanceKm is the actual editable
// input driving it once an EV vehicle is selected. Both are kept in
// context (buildAssistantContext resolves annualKm below rather than
// reading it raw, so the assistant never quotes/edits a stale number).
const ASSISTANT_CORE_FIELDS = ["vehA", "vehB", "priceA", "priceB", "fleetSize", "horizon", "annualKm", "ritaseDistanceKm", "ecosystemId"];

function buildAssistantContext(s, screenKeys) {
  const keys = new Set(ASSISTANT_CORE_FIELDS);
  screenKeys.forEach((sk) => (window.SCREEN_RESET_KEYS[sk] || []).forEach((k) => keys.add(k)));
  const ctx = {};
  keys.forEach((k) => {
    if (k in s && (typeof s[k] === "string" || typeof s[k] === "number" || typeof s[k] === "boolean")) ctx[k] = s[k];
  });
  // annualKm: resolve to the Ritase-Cycle Engine's derived value (when an EV
  // + Ritase Distance are set) instead of the possibly-stale raw s.annualKm,
  // so the assistant always sees/quotes the number actually used in TCO.
  if ("annualKm" in ctx && window.resolveAnnualKm) ctx.annualKm = Math.round(window.resolveAnnualKm(s));
  return ctx;
}

// v1.7.5: compact vehicle-catalog summary given to assistant_recommend so it
// can identify/compare REAL catalog vehicles (e.g. "what EV is equivalent to
// the Canter?") instead of only being able to discuss whichever two vehicles
// are already selected as A/B -- that was the actual cause of the assistant
// answering an unrelated TCO comparison when asked a simple lookup question,
// since it had no other vehicle data to draw on. Plain-text, one line per
// vehicle, to stay far cheaper in tokens than a repeated-keys JSON array;
// only sent on the recommend call (assistant_plan doesn't need it).
function buildVehicleCatalogSummary() {
  const vehicles = window.VEHICLES || [];
  return vehicles
    .map((v) => `${v.id}|${v.name}|${v.powertrain}|${v.segment}|GVW ${v.gvw}kg|Rp${v.price}`)
    .join("\n");
}

// Same Total TCO / savings formula used everywhere else in the app
// (report.jsx) -- reused rather than re-derived, so the assistant's numbers
// can never drift from what Screen 6 itself shows.
function summarizeTcoResult(s, result, label) {
  const rows = result.rows || [];
  const totalA = rows.reduce((sum, r) => sum + ((r.residual && !s.includeResidualInTco) ? 0 : r.a), 0);
  const totalB = rows.reduce((sum, r) => sum + ((r.residual && !s.includeResidualInTco) ? 0 : r.b), 0);
  return {
    label,
    totalTcoA: Math.round(totalA), totalTcoB: Math.round(totalB),
    savings: Math.round(Math.abs(totalA - totalB)),
    winner: totalA < totalB ? "A" : "B",
    npv: result.npv != null ? Math.round(result.npv) : null,
    paybackYears: result.payback != null ? Math.round(result.payback * 10) / 10 : null,
    co2A: Math.round(result.co2A || 0), co2B: Math.round(result.co2B || 0),
  };
}

function toAnthropicMemory(messages, mode) {
  const turns = mode === "full" ? messages : messages.slice(-6);
  return turns.map((m) => ({ role: m.role, content: m.content }));
}

async function callAssistantAction(action, payload) {
  if (typeof firebase === "undefined" || !firebase.auth().currentUser) throw new Error("not-signed-in");
  if (!window.API_PROXY_URL || window.API_PROXY_URL === "REPLACE_ME") throw new Error("API proxy not configured yet");
  const idToken = await firebase.auth().currentUser.getIdToken();
  const res = await fetch(window.API_PROXY_URL, {
    method: "POST",
    headers: { "Authorization": `Bearer ${idToken}`, "Content-Type": "application/json" },
    body: JSON.stringify({ action, ...payload }),
  });
  const data = await res.json();
  if (!res.ok) throw new Error(data.error || `Request failed (${res.status})`);
  return data;
}

// Collapsed to exactly the 3 plain-language failure states from the spec --
// no stack traces or Cloudflare/Anthropic error detail ever shown to the user.
function classifyFailure(err, lang) {
  const msg = (err && err.message) || String(err);
  if (/token limit reached/i.test(msg)) {
    return tr(lang, "No tokens left — ask your admin to increase your limit.", "Token habis — minta admin menaikkan batas Anda.");
  }
  if (/api not supported|no profile record/i.test(msg)) {
    return tr(lang, "API access isn't enabled for this account.", "Akses API tidak diaktifkan untuk akun ini.");
  }
  return tr(lang, "Couldn't reach the assistant — try again.", "Tidak bisa menghubungi asisten — coba lagi.");
}

function ProposedChangesCard({ s, msg, onAccept, onReject, onApplyAll, lang }) {
  const changes = msg.proposedChanges || [];
  if (!changes.length) return null;
  const applied = msg.appliedStatus || {};
  const pendingCount = changes.filter((c, i) => !applied[i]).length;
  return (
    <div className="assistant-proposed">
      <div className="assistant-proposed-title"><Tr en="Proposed changes" id="Perubahan yang diusulkan" /></div>
      {changes.map((c, i) => {
        const status = applied[i];
        const oldValue = s[c.field];
        return (
          <div key={i} className={"assistant-proposed-row" + (status ? " " + status : "")}>
            <div className="apr-label">{c.label}</div>
            <div className="apr-diff">
              <s>{oldValue == null ? "—" : String(oldValue)}</s> → <b>{String(c.newValue)}</b>
            </div>
            {!status ? (
              <div className="apr-actions">
                <button type="button" className="apr-btn apr-accept" title={tr(lang, "Accept", "Terima")} onClick={() => onAccept(i)}>✓</button>
                <button type="button" className="apr-btn apr-reject" title={tr(lang, "Reject", "Tolak")} onClick={() => onReject(i)}>✕</button>
              </div>
            ) : (
              <div className="apr-status">{status === "accepted" ? "✓ " + tr(lang, "applied", "diterapkan") : "✕ " + tr(lang, "rejected", "ditolak")}</div>
            )}
          </div>
        );
      })}
      {pendingCount > 1 && (
        <button type="button" className="btn btn-ghost" style={{ fontSize: 12, marginTop: 6 }} onClick={onApplyAll}>
          <Tr en="Apply all" id="Terapkan semua" />
        </button>
      )}
    </div>
  );
}

function LiveAssistantWidget({ s, set, screenId, visible }) {
  const { lang } = useLang();
  const { user, profile } = useAuth();
  const gated = window.canUseGatedApi && window.canUseGatedApi(profile);

  const [ui, setUi] = React.useState(AssistantStore.load);
  const [messagesByScreen, setMessagesByScreen] = React.useState({});
  const [question, setQuestion] = React.useState("");
  const [sending, setSending] = React.useState(false);
  const [failure, setFailure] = React.useState(null);
  const dragRef = React.useRef(null);
  const logRef = React.useRef(null);
  const panelRef = React.useRef(null);

  React.useEffect(() => { AssistantStore.save(ui); }, [ui]);

  // v1.7.5: the panel grows UPWARD from `bottom` (position:fixed) -- a large
  // enough `bottom` (from a prior drag, or a saved position from before a
  // viewport resize) pushes the header, and sometimes the whole panel,
  // above the top of the browser viewport where it can no longer be
  // clicked or dragged back down -- a real stuck-panel report. Clamp
  // against the panel's actual measured size so the header always stays
  // reachable, both live during a drag and as a one-time self-heal for any
  // already-bad saved position (mirrors the earlier stale-default migration
  // in AssistantStore.load()).
  const clampPosition = (right, bottom) => {
    const panelW = (panelRef.current && panelRef.current.offsetWidth) || 340;
    const panelH = (panelRef.current && panelRef.current.offsetHeight) || 200;
    const maxRight = Math.max(4, window.innerWidth - panelW - 4);
    const maxBottom = Math.max(4, window.innerHeight - panelH - 4);
    return { right: Math.min(Math.max(4, right), maxRight), bottom: Math.min(Math.max(4, bottom), maxBottom) };
  };

  React.useEffect(() => {
    if (!ui.open || !panelRef.current) return;
    const fix = () => setUi((prev) => ({ ...prev, ...clampPosition(prev.right, prev.bottom) }));
    fix();
    window.addEventListener("resize", fix);
    return () => window.removeEventListener("resize", fix);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [ui.open, ui.minimized]);

  const resetPosition = () => setUi((prev) => ({ ...prev, right: 20, bottom: 146 }));

  const messages = messagesByScreen[screenId] || [];

  React.useEffect(() => {
    if (logRef.current) logRef.current.scrollTop = logRef.current.scrollHeight;
  }, [messages.length, sending]);

  if (!visible) return null;

  const updateMsg = (idx, patch) => {
    setMessagesByScreen((prev) => {
      const list = [...(prev[screenId] || [])];
      list[idx] = { ...list[idx], ...patch };
      return { ...prev, [screenId]: list };
    });
  };

  const handleAccept = (msgIdx, changeIdx) => {
    const msg = messages[msgIdx];
    const change = msg.proposedChanges[changeIdx];
    set(change.field, change.newValue);
    updateMsg(msgIdx, { appliedStatus: { ...msg.appliedStatus, [changeIdx]: "accepted" } });
  };
  const handleReject = (msgIdx, changeIdx) => {
    const msg = messages[msgIdx];
    updateMsg(msgIdx, { appliedStatus: { ...msg.appliedStatus, [changeIdx]: "rejected" } });
  };
  const handleApplyAll = (msgIdx) => {
    const msg = messages[msgIdx];
    const applied = { ...msg.appliedStatus };
    msg.proposedChanges.forEach((c, i) => {
      if (!applied[i]) { set(c.field, c.newValue); applied[i] = "accepted"; }
    });
    updateMsg(msgIdx, { appliedStatus: applied });
  };

  const resetConversation = () => setMessagesByScreen((prev) => ({ ...prev, [screenId]: [] }));

  const handleSend = async () => {
    const q = question.trim();
    if (!q || sending) return;
    setQuestion("");
    setFailure(null);
    const priorMessages = messages;
    const userMsg = { role: "user", content: q };
    setMessagesByScreen((prev) => ({ ...prev, [screenId]: [...priorMessages, userMsg] }));
    setSending(true);
    try {
      let context = buildAssistantContext(s, [screenId]);
      const memory = toAnthropicMemory(priorMessages, ui.memoryMode);

      const planData = await callAssistantAction("assistant_plan", { question: q, context, memory, screenId });
      const plan = (planData && planData.plan) || { scenarios: [], needsOtherScreen: null };

      if (plan.needsOtherScreen && plan.needsOtherScreen !== screenId) {
        context = buildAssistantContext(s, [screenId, plan.needsOtherScreen]);
      }

      const baseResult = window.computeTCO(s);
      const scenarioResults = [summarizeTcoResult(s, baseResult, tr(lang, "Current setup", "Kondisi saat ini"))];
      (plan.scenarios || []).slice(0, 3).forEach((sc) => {
        try {
          const result = window.computeTCO({ ...s, ...(sc.patch || {}) });
          scenarioResults.push(summarizeTcoResult(s, result, sc.label || "Scenario"));
        } catch (e) { /* a malformed patch shouldn't sink the whole question */ }
      });

      const recData = await callAssistantAction("assistant_recommend", {
        question: q, context, scenarioResults, memory, screenId,
        vehicleCatalog: buildVehicleCatalogSummary(),
      });
      const rec = (recData && recData.recommendation) || { message: "", proposedChanges: [] };

      const assistantMsg = { role: "assistant", content: rec.message || "…", proposedChanges: rec.proposedChanges || [], appliedStatus: {} };
      setMessagesByScreen((prev) => ({ ...prev, [screenId]: [...(prev[screenId] || []), assistantMsg] }));
    } catch (err) {
      setFailure(classifyFailure(err, lang));
    } finally {
      setSending(false);
    }
  };

  const startDrag = (e) => {
    dragRef.current = { startX: e.clientX, startY: e.clientY, origRight: ui.right, origBottom: ui.bottom };
    const onMove = (ev) => {
      if (!dragRef.current) return;
      const dx = ev.clientX - dragRef.current.startX;
      const dy = ev.clientY - dragRef.current.startY;
      const next = clampPosition(dragRef.current.origRight - dx, dragRef.current.origBottom - dy);
      setUi((prev) => ({ ...prev, ...next }));
    };
    const onUp = () => {
      dragRef.current = null;
      window.removeEventListener("mousemove", onMove);
      window.removeEventListener("mouseup", onUp);
    };
    window.addEventListener("mousemove", onMove);
    window.addEventListener("mouseup", onUp);
  };

  if (!ui.open) {
    return (
      <div className="assistant-handle" style={{ right: ui.right, bottom: ui.bottom }}
        title={tr(lang, "Live Assistant — ask about your current comparison", "Asisten Langsung — tanyakan tentang perbandingan Anda saat ini")}
        data-tooltip={tr(lang, "AI Assistant", "Asisten AI")}
        onClick={() => setUi((prev) => ({ ...prev, open: true, minimized: false }))}>
        💬
      </div>
    );
  }

  return (
    <div className="assistant-panel" ref={panelRef} style={{ right: ui.right, bottom: ui.bottom }}
      onMouseDown={(e) => { if (e.target === e.currentTarget) startDrag(e); }}>
    <div className="assistant-panel-inner">
      <div className="assistant-header" onMouseDown={startDrag}>
        <span className="assistant-title">💬 <Tr en="Live Assistant" id="Asisten Langsung" /></span>
        <div className="assistant-header-actions">
          <button type="button" className="assistant-icon-btn" title={tr(lang, "Reset position", "Reset posisi")}
            onClick={resetPosition}>⌖</button>
          <button type="button" className="assistant-icon-btn" title={tr(lang, "Minimize", "Kecilkan")}
            onClick={() => setUi((prev) => ({ ...prev, minimized: !prev.minimized }))}>{ui.minimized ? "▢" : "—"}</button>
          <button type="button" className="assistant-icon-btn" title={tr(lang, "Close", "Tutup")}
            onClick={() => setUi((prev) => ({ ...prev, open: false }))}>✕</button>
        </div>
      </div>

      {!ui.minimized && (
        <div className="assistant-body">
          {!gated ? (
            <div className="assistant-gated-note">
              <Tr en="API access isn't enabled for this account. Ask your admin to enable it if you'd like to use the Live Assistant."
                  id="Akses API tidak diaktifkan untuk akun ini. Minta admin mengaktifkannya jika Anda ingin menggunakan Asisten Langsung." />
            </div>
          ) : (
            <>
              <div className="assistant-memory-row">
                <label className="assistant-memory-toggle">
                  <input type="checkbox" checked={ui.memoryMode === "full"}
                    onChange={(e) => setUi((prev) => ({ ...prev, memoryMode: e.target.checked ? "full" : "rolling" }))} />
                  <Tr en="Remember full conversation" id="Ingat seluruh percakapan" />
                </label>
                <button type="button" className="btn btn-ghost" style={{ fontSize: 11, padding: "3px 7px" }} onClick={resetConversation}>
                  <Tr en="Reset" id="Reset" />
                </button>
              </div>
              <div className="assistant-memory-hint">
                {ui.memoryMode === "full"
                  ? tr(lang, "Full memory uses more tokens per message.", "Ingatan penuh memakai lebih banyak token per pesan.")
                  : tr(lang, "Rolling memory (last few turns) — cheaper.", "Ingatan bergulir (beberapa giliran terakhir) — lebih hemat.")}
              </div>

              <div className="assistant-log" ref={logRef}>
                {messages.length === 0 && (
                  <div className="assistant-empty">
                    <Tr en="Ask about this screen's numbers — e.g. what if fleet size were 20?" id="Tanyakan tentang angka di layar ini — mis. bagaimana jika ukuran armada 20?" />
                  </div>
                )}
                {messages.map((m, i) => (
                  <div key={i} className={"assistant-msg " + m.role}>
                    <div className="assistant-msg-content">{m.content}</div>
                    {m.role === "assistant" && m.proposedChanges && m.proposedChanges.length > 0 && (
                      <ProposedChangesCard s={s} msg={m} lang={lang}
                        onAccept={(ci) => handleAccept(i, ci)}
                        onReject={(ci) => handleReject(i, ci)}
                        onApplyAll={() => handleApplyAll(i)} />
                    )}
                  </div>
                ))}
                {sending && <div className="assistant-msg assistant"><div className="assistant-msg-content assistant-typing">…</div></div>}
                {failure && <div className="assistant-failure">{failure}</div>}
              </div>

              <div className="assistant-input-row">
                <input className="input" style={{ flex: 1 }} value={question} disabled={sending}
                  placeholder={tr(lang, "Ask a question…", "Ajukan pertanyaan…")}
                  onChange={(e) => setQuestion(e.target.value)}
                  onKeyDown={(e) => { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); handleSend(); } }} />
                <button type="button" className="btn btn-primary" style={{ fontSize: 13 }} disabled={sending || !question.trim()} onClick={handleSend}>
                  <Tr en="Send" id="Kirim" />
                </button>
              </div>
            </>
          )}
        </div>
      )}
    </div>
    </div>
  );
}

Object.assign(window, { LiveAssistantWidget, buildAssistantContext, summarizeTcoResult });
