/* Robindex v2 — chat desk fork: per-call USD billing with itemized fee line.
   Reuses RXC components; no free quota, no subscriptions, all models unlocked.
   ask() streams from POST /api/chat SSE — no RX.respond / RXS.chargeCall fallback. */
const { useState: c2S, useRef: c2R, useEffect: c2E } = React;
const c2T = (k) => window.RXI.t(k);

const CHAT2_PHASE_MAP = { plan: 0, market: 1, rag: 1, tools: 2, thinking: 3 };
const CHAT2_SSE_EVENTS = ["progress", "tool_call", "delta", "meta", "bill", "final_answer", "error", "done"];

function chat2EmptyResp(lang) {
  const phases = (window.RX && window.RX.phases) ? window.RX.phases(lang) : [
    { key: "plan", label: "…", verb: "…" },
    { key: "rag", label: "…", verb: "…" },
    { key: "rerank", label: "…", verb: "…" },
    { key: "write", label: "…", verb: "…" },
  ];
  return { phases, toolCalls: [], answerMd: "", conviction: 0, citations: [] };
}

function chat2PickSnippet(snippet, lang) {
  if (!snippet) return "";
  if (typeof snippet === "string") return snippet;
  return snippet[lang] || snippet.en || snippet.zh || "";
}

function chat2AdaptCitations(list, lang) {
  return (list || []).map((c) => ({
    ref: c.ref || ("T" + (c.index || "")),
    date: c.date || c.created_at || "",
    likes: c.likes != null ? c.likes : (c.like_count || 0),
    views: c.views != null ? c.views : (c.view_count || "—"),
    url: c.url || c.tweet_url || "",
    snippet: chat2PickSnippet(c.snippet || c.text, lang),
  }));
}

function chat2OwnerFee(bill) {
  if (!bill) return 0;
  return bill.ownerFee != null ? bill.ownerFee : (bill.kolFee || 0);
}

async function streamChat2(opts) {
  const { kolId, message, model, conversationId, signal, onEvent, useFreeAsk } = opts;
  const auth = window.RXAUTH;
  const tok = auth && typeof auth.getToken === "function" ? auth.getToken() : null;
  if (!tok) return { error: "unauthorized", status: 401 };

  const headers = { "Content-Type": "application/json", Authorization: "Bearer " + tok };
  const body = { kol_id: kolId, model, message, conversation_id: conversationId || undefined, useFreeAsk: !!useFreeAsk };
  let res;
  try {
    res = await fetch("/api/chat", { method: "POST", headers, body: JSON.stringify(body), signal });
  } catch (e) {
    if (e && e.name === "AbortError") return { aborted: true };
    return { error: "network_error", detail: String(e && e.message || e) };
  }

  if (!res.ok) {
    let data = null;
    try { data = await res.json(); } catch (_) {}
    return {
      error: (data && data.error) || "request_failed",
      status: res.status,
      need: data && data.need,
      balance: data && data.balance,
      message: (data && data.message) || null,
    };
  }

  const conversationOut = res.headers.get("X-Conversation-Id");
  const reader = res.body.getReader();
  const dec = new TextDecoder();
  let buf = "";
  let answerMd = "";
  let toolCalls = [];
  let phase = 0;
  let bill = null;
  let sources = null;
  const emit = (type, data) => { if (onEvent) onEvent(type, data); };

  while (true) {
    const { done, value } = await reader.read();
    if (done) break;
    buf += dec.decode(value, { stream: true });
    const chunks = buf.split("\n\n");
    buf = chunks.pop() || "";
    for (const ev of chunks) {
      let type = "message", data = "";
      for (const ln of ev.split("\n")) {
        if (ln.startsWith("event:")) type = ln.slice(6).trim();
        else if (ln.startsWith("data:")) data += ln.slice(5).trim();
      }
      if (!data) continue;
      try {
        const parsed = JSON.parse(data);
        if (type === "progress") {
          phase = CHAT2_PHASE_MAP[parsed.phase] != null ? CHAT2_PHASE_MAP[parsed.phase] : phase;
          emit("progress", { phase, text: parsed.text, key: parsed.phase });
        } else if (type === "tool_call") {
          const tc = { name: parsed.name || "tool", args: String(parsed.args || ""), result: {} };
          toolCalls = toolCalls.slice();
          toolCalls[phase] = tc;
          emit("tool_call", { toolCalls: toolCalls.slice(), tc });
        } else if (type === "delta") {
          const chunk = typeof parsed === "string" ? parsed : String(parsed || "");
          answerMd += chunk;
          emit("delta", { answerMd });
        } else if (type === "meta") {
          if (parsed.final && parsed.citations) sources = parsed.citations;
          emit("meta", parsed);
        } else if (type === "bill") {
          bill = parsed;
          emit("bill", parsed);
        } else if (type === "final_answer") {
          answerMd = String(parsed || "");
          emit("final_answer", { answerMd });
        } else if (type === "error") {
          const errMsg = parsed.message || parsed.error || ((window.RXI.lang === "zh" ? "错误" : "Error") + (parsed.status ? " " + parsed.status : ""));
          emit("error", { message: errMsg, raw: parsed });
          return { error: errMsg, bill, answerMd, conversationId: conversationOut };
        } else if (type === "done") {
          emit("done", parsed);
          return { ok: true, answerMd, bill, sources, conversationId: conversationOut, toolCalls, phase };
        }
      } catch (_) {}
    }
  }
  return { ok: true, answerMd, bill, sources, conversationId: conversationOut, toolCalls, phase, incomplete: true };
}

async function refreshChat2Wallet() {
  const api = window.RXAPI;
  if (!api || typeof api.wallet !== "function") return;
  try { await api.wallet(); } catch (_) {}
}

function KMessage2({ msg, model, onCite, onAsk, retryQuestion }) {
  const { Icon, Avatar, ToolGroup, AnswerBlocks, Conviction } = window.RXC;
  const [exp, setExp] = c2S(false);
  const [slug, setSlug] = c2S(msg.slug || null);
  const [showPublish, setShowPublish] = c2S(false);
  const [unpublishing, setUnpublishing] = c2S(false);
  const privy = window.PrivySDK && window.PrivySDK.usePrivy ? window.PrivySDK.usePrivy() : null;
  const loggedIn = privy && privy.authenticated;
  // Copy action feedback: null = idle, "ok" = copied, "fail" = copy failed.
  // Announced via the aria-live=polite span inside the Copy button; the
  // visible label mirrors the state so sighted users also see the result.
  const [copyState, setCopyState] = c2S(null);
  const copyTimer = c2R(null);
  c2E(() => () => { if (copyTimer.current) clearTimeout(copyTimer.current); }, []);
  const r = msg.resp;
  const zh = window.RXI.lang === "zh";
  const copyTxt = copyState === "ok" ? (zh ? "已复制" : "Copied")
                : copyState === "fail" ? (zh ? "复制失败" : "Copy failed")
                : (zh ? "复制" : "Copy");
  const retryTxt = zh ? "重答" : "Retry";
  const onCopy = async () => {
    const text = r.answerMd || "";
    let ok = false;
    try {
      if (navigator.clipboard && typeof navigator.clipboard.writeText === "function") {
        await navigator.clipboard.writeText(text);
        ok = true;
      } else if (typeof document !== "undefined" && typeof document.execCommand === "function") {
        // Safe DOM fallback when the async Clipboard API is unavailable
        // (e.g. insecure context). Uses a detached textarea and execCommand.
        const ta = document.createElement("textarea");
        ta.value = text;
        ta.setAttribute("readonly", "");
        ta.style.position = "absolute";
        ta.style.left = "-9999px";
        document.body.appendChild(ta);
        ta.select();
        try { ok = document.execCommand("copy"); } catch (_) { ok = false; }
        document.body.removeChild(ta);
      }
    } catch (_) { ok = false; }
    setCopyState(ok ? "ok" : "fail");
    if (copyTimer.current) clearTimeout(copyTimer.current);
    copyTimer.current = setTimeout(() => setCopyState(null), ok ? 2000 : 3500);
  };
  const mdl = msg.usedModel || model;
  const bill = msg.bill;
  const E = window.RXE;
  const reg = window.RXS.regFor(msg.kol.id);
  const ownerFee = chat2OwnerFee(bill);
  const partRows = bill ? [
    bill.token > 0 && ["cpu", T2("chTok") + " · " + mdl.name, "↓" + Math.round(bill.tokIn / 100) / 10 + "K ↑" + Math.round(bill.tokOut / 100) / 10 + "K tok", E.usd(bill.token, 3)],
    bill.byok && ["key", T2("chTok") + " · " + mdl.name, T2("chByokNote"), "$0.000"],
    ownerFee > 0 && ["crown", T2("chKol"), T2("chToOwner"), E.usd(ownerFee)],
    bill.distill > 0 && ["sparkles", T2("chDistill"), T2("chToCreator")(reg && reg.creator ? (reg.creator.you ? "you" : reg.creator.by) : ""), E.usd(bill.distill)],
    bill.update > 0 && ["refresh", T2("chUpdate"), T2("chToUpdater")(reg && reg.updater ? (reg.updater.you ? "you" : reg.updater.by) : ""), E.usd(bill.update)],
  ].filter(Boolean) : [];
  return (
    <div className="msg msg-k">
      <Avatar kol={msg.kol} size={30} radius={8} />
      <div className="stream">
        <div className="k-name">
          <b>{msg.kol.display_name}</b>
          <span className="via">
            <span className="mp-badge" style={{ background: mdl.color, width: 15, height: 15, fontSize: 7 }}>{mdl.badge}</span>
            {mdl.name} · {c2T("via")}
          </span>
        </div>
        <ToolGroup phases={r.phases} toolCalls={r.toolCalls} activePhase={msg.phase} done={msg.done} />
        {msg.error ? (
          <div className="thinking" role="alert" aria-live="assertive">
            <span>{msg.error}</span>
          </div>
        ) : msg.cancelled ? (
          <div className="thinking" role="status" aria-live="polite">
            <span>{window.RXI.lang === "zh" ? "已取消" : "Cancelled"}</span>
          </div>
        ) : r.answerMd ? (
          <React.Fragment>
            <AnswerBlocks md={r.answerMd} onCite={onCite} />
            {msg.done ? (
              <React.Fragment>
                <Conviction value={r.conviction} />
                {bill && (
                  <div className="bill-line">
                    <Icon name="gauge" size={12} color="var(--faint)" />
                    <span>{T2("chTotal")}</span>
                    <span className="tot">−{E.usd(bill.total, 3)}</span>
                    <span>· ↓{Math.round(bill.tokIn / 100) / 10}K ↑{Math.round(bill.tokOut / 100) / 10}K tok</span>
                    <button className="exp" aria-expanded={exp} onClick={() => setExp(!exp)}>{exp ? T2("hideBreakdown") : T2("seeBreakdown")}</button>
                    {exp && (
                      <div className="bill-parts">
                        {partRows.map(([ic, t, to, v]) => (
                          <div className="bp-row" key={t}>
                            <Icon name={ic} size={11} color="var(--faint)" />
                            <span>{t}</span><span className="to">{to}</span>
                            <span className="v">{v}</span>
                          </div>
                        ))}
                      </div>
                    )}
                  </div>
                )}
                 <div className="aacts">
                   <button className="aact" aria-label={copyTxt} onClick={onCopy}>
                     <Icon name="copy" size={14} />
                     <span aria-live="polite">{copyTxt}</span>
                   </button>
                   {retryQuestion != null && (
                     <button className="aact" aria-label={retryTxt} onClick={() => onAsk(retryQuestion)}>
                       <Icon name="refresh" size={14} />
                       <span>{retryTxt}</span>
                     </button>
                   )}
                   {loggedIn && (
                     <button className="aact" aria-label={window.RXI2.t("shareBtnLabel")} onClick={() => {
                       if (!slug && window.RXA) {
                         try { window.RXA.track("answer_share_clicked", { kol_id: msg.kol.id, answer_id: msg.id }); } catch (_) {}
                       }
                       setShowPublish(true);
                     }}>
                       <Icon name="share" size={14} />
                       <span>{window.RXI2.t("shareBtnLabel")}</span>
                     </button>
                   )}
                   {loggedIn && slug && (
                     <button className="aact" aria-label={window.RXI2.t("unpublishBtn")} disabled={unpublishing} onClick={async () => {
                       setUnpublishing(true);
                       const res = await window.RXAPI.unpublishAnswer(slug);
                       setUnpublishing(false);
                       if (res && !res.error) {
                         setSlug(null);
                         msg.slug = null;
                       } else {
                         alert(res && res.error ? res.error : "Failed to unpublish");
                       }
                     }}>
                       <Icon name="trash" size={14} />
                       <span>{unpublishing ? window.RXI2.t("unpublishing") : window.RXI2.t("unpublishBtn")}</span>
                     </button>
                   )}
                 </div>
              </React.Fragment>
            ) : (
              <div className="thinking" role="status" aria-live="polite">
                <span>{r.phases[msg.phase] ? r.phases[msg.phase].verb : (window.RXI.lang === "zh" ? "思考中…" : "Thinking…")}</span>
                <span className="tdots"><i></i><i></i><i></i></span>
              </div>
            )}
          </React.Fragment>
        ) : (
          <div className="thinking" role="status" aria-live="polite">
            <span>{msg.progressText || (r.phases[msg.phase] ? r.phases[msg.phase].verb : (window.RXI.lang === "zh" ? "思考中…" : "Thinking…"))}</span>
            <span className="tdots"><i></i><i></i><i></i></span>
           </div>
         )}
       </div>
       {showPublish && (
         <window.PublishModal
           questionText={retryQuestion || ""}
           answerText={r.answerMd || ""}
           citations={r.citations || []}
           kol={msg.kol}
           initialSlug={slug}
           onClose={() => setShowPublish(false)}
           onSuccess={(newSlug) => {
             setSlug(newSlug);
             msg.slug = newSlug;
           }}
         />
       )}
     </div>
   );
 }

function Rail2({ kol, reg, sources, railTab, setRailTab, highlight, mobile }) {
  const { Icon, SourceCard, Avatar } = window.RXC;
  const scrollRef = c2R(null);
  c2E(() => {
    if (highlight && railTab === "sources" && scrollRef.current) {
      const el = scrollRef.current.querySelector("#cite-" + highlight);
      if (el) scrollRef.current.scrollTo({ top: el.offsetTop - 14, behavior: "smooth" });
    }
  }, [highlight, railTab]);
  return (
    <div className={"rail" + (mobile ? " rail-mobile" : "")}>
      <div className="rail-tabs">
        <button className={"rt" + (railTab === "persona" ? " on" : "")} onClick={() => setRailTab("persona")}>
          <Icon name="user" size={13} />{c2T("railPersona")}
        </button>
        <button className={"rt" + (railTab === "sources" ? " on" : "")} onClick={() => setRailTab("sources")}>
          <Icon name="quote" size={13} />{c2T("railSources")}
          {sources.length > 0 && <span className="rt-badge">{sources.length}</span>}
        </button>
      </div>
      <div className="rail-scroll" ref={scrollRef}>
        {railTab === "persona" ? (
          <PersonaCard2R kol={kol} reg={reg} />
        ) : sources.length === 0 ? (
          <div className="empty-rail" role="status">
            <Icon name="quote" size={22} color="var(--ghost)" style={{ margin: "0 auto 10px" }} />
            {c2T("emptyRailA")}<b style={{ color: "var(--dim)" }}>{c2T("emptyRailMark")}</b>{c2T("emptyRailB")}
          </div>
        ) : (
          <React.Fragment>
            <div className="src-note">{c2T("srcNoteA")}<b>{sources.length}</b>{c2T("srcNoteB")(kol.handle)}</div>
            {sources.map((tw) => <SourceCard key={tw.ref} kol={kol} tw={tw} active={highlight === tw.ref} />)}
          </React.Fragment>
        )}
      </div>
    </div>
  );
}
function PersonaCard2R({ kol, reg }) {
  const { Icon, Avatar } = window.RXC;
  const E = window.RXE;
  return (
    <React.Fragment>
      <div className="rcard">
        <div className="persona">
          <div className="persona-top">
            <Avatar kol={kol} size={46} radius={12} />
            <div style={{ minWidth: 0 }}>
              <div className="persona-nm">{kol.display_name} {reg && <ClaimBadge claimed={reg.claimed} />}</div>
              <a className="persona-h" href={"https://x.com/" + kol.handle} target="_blank" rel="noreferrer">
                <Icon name="xLogo" size={11} />@{kol.handle}
              </a>
            </div>
          </div>
          {kol.bio && <div className="persona-bio">{kol.bio}</div>}
          <div className="persona-stats">
            <div className="pstat"><div className="v">{kol.stats.followers}</div><div className="k">{c2T("followers")}</div></div>
            <div className="pstat"><div className="v">{kol.corpus.tweets}</div><div className="k">{c2T("tweetsLabel")}</div></div>
            <div className="pstat"><div className="v win">{c2T("personaOnline")}</div><div className="k">{c2T("personaSlot")}</div></div>
          </div>
          {kol.style && kol.style.length > 0 && <div className="tagrow">{kol.style.map((s) => <span className="ptag" key={s}>{s}</span>)}</div>}
        </div>
      </div>
      {kol.thesis && (
        <div className="rcard">
          <div className="rcard-h"><Icon name="lightbulb" size={13} color="var(--accent)" />{c2T("thesisTitle")}</div>
          <div className="thesis-body">{kol.thesis}</div>
        </div>
      )}
      <div className="rcard">
        <div className="rcard-h"><Icon name="layers" size={13} color="var(--accent)" />{c2T("cloudTitle")}</div>
        <div className="meta-rows">
          <div className="meta-row"><span className="mk">{c2T("mIndexed")}</span><span className="mv mono">{kol.corpus.tweets}</span></div>
          <div className="meta-row"><span className="mk">{c2T("mSince")}</span><span className="mv mono">{kol.corpus.since}</span></div>
          <div className="meta-row"><span className="mk">{c2T("mVersion")}</span><span className="mv mono">{kol.corpus.persona}</span></div>
          {reg && <div className="meta-row"><span className="mk">{T2("cardUpdated")}</span><span className="mv mono">{E.fmtDT(reg.updatedAt)}</span></div>}
        </div>
        {reg && reg.creator && reg.creator.earned < reg.creator.cap && (
          <div style={{ marginTop: 10 }}><RewardBar label={T2("rewardLine")} earned={reg.creator.earned} cap={reg.creator.cap} live={reg.creator.you} /></div>
        )}
      </div>
    </React.Fragment>
  );
}

function EmptyThread2({ kol, onAsk }) {
  const { Icon, Avatar } = window.RXC;
  return (
    <div className="empty-thread">
      <Avatar kol={kol} size={56} radius={16} />
      <h2>{c2T("emptyAsk")(kol.display_name)}</h2>
      <p>{kol.tagline}</p>
      <div className="sugg-grid">
        {(kol.suggested || []).map((s) => (
          <button key={s} className="sugg" onClick={() => onAsk(s)}>
            <Icon name="sparkles" size={14} color="var(--accent)" />
            <span>{s}</span>
            <Icon name="arrowRight" size={14} color="var(--faint)" style={{ marginLeft: "auto" }} />
          </button>
        ))}
      </div>
    </div>
  );
}

function Composer2({ kol, reg, onAsk, models, model, setModel, effort, setEffort, onAddModel }) {
  const { Icon, ModelPicker } = window.RXC;
  const [text, setText] = c2S("");
  const ta = c2R(null);
  const E = window.RXE;
  const privy = window.PrivySDK && window.PrivySDK.usePrivy ? window.PrivySDK.usePrivy() : null;
  const cur = models.find((m) => m.id === model) || models[0];
  const est = E.callBreakdown(reg, cur.byok ? null : cur.id, 9000, 1400, !!cur.byok);

  c2E(() => {
    if (kol && kol.id) {
      const draft = sessionStorage.getItem("rx_composer_draft_" + kol.id);
      if (draft) {
        setText(draft);
        sessionStorage.removeItem("rx_composer_draft_" + kol.id);
      }
    }
  }, [kol && kol.id, privy && privy.authenticated]);

  const submit = () => {
    if (text.trim()) {
      onAsk(text.trim());
      if (privy && privy.authenticated) {
        setText("");
        if (ta.current) ta.current.style.height = "auto";
      }
    }
  };
  return (
    <div className="composer-wrap">
      <div className="composer">
        <div className="chips">
          {(kol.suggested || []).slice(0, 3).map((s) => <button key={s} className="chip" onClick={() => onAsk(s)}>{s}</button>)}
        </div>
        <div className="box">
          <textarea ref={ta} value={text} rows={1} placeholder={c2T("composerPlaceholder")(kol.display_name)}
            onChange={(e) => {
              const val = e.target.value;
              if (!text && val && window.RXA) {
                try { window.RXA.track("question_draft_started"); } catch (_) {}
              }
              setText(val);
              e.target.style.height = "auto";
              e.target.style.height = Math.min(e.target.scrollHeight, 160) + "px";
            }}
            onKeyDown={(e) => { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); submit(); } }} />
          <div className="box-bar">
            <span className="tool-toggle on"><Icon name="search" size={13} />{c2T("toolSearch")}</span>
            <span className="tool-toggle"><Icon name="barChart" size={13} />{c2T("toolMarket")}</span>
            <div className="spacer"></div>
            <ModelPicker models={models} value={model} onChange={setModel} up={true} compact={true} effort={effort} setEffort={setEffort} subscribed={true} onAddModel={onAddModel} />
            <button className="send" aria-label={window.RXI.lang === "zh" ? "发送" : "Send"} disabled={!text.trim()} onClick={submit}><Icon name="send" size={17} /></button>
          </div>
        </div>
        <div className="composer-foot">
          <b>{kol.display_name}</b> {c2T("composerFootB")} · ≈{E.usd(est.total, 3)} / {T2("cardPerCall")}
          {est.kolFee > 0 && <span> · {T2("chKol")} {E.usd(est.kolFee)}</span>} {c2T("composerFoot")}
        </div>
      </div>
    </div>
  );
}

function ChatArea2({ kol, reg, messages, model, onCite, onAsk, threadRef, models, modelId, setModel, effort, setEffort, onAddModel }) {
  return (
    <div className="thread-col">
      <div className="thread" ref={threadRef}>
        <div className="thread-inner">
          {messages.length === 0 ? (
            <EmptyThread2 kol={kol} onAsk={onAsk} />
          ) : (
            messages.map((m, i) => {
              if (m.role === "u") {
                return (
                  <div className="msg msg-u" key={m.id}><div className="bub"><span>{m.text}</span></div></div>
                );
              }
              // The user question that this KOL answer responds to is the
              // nearest preceding user message. Retry re-calls onAsk with
              // that exact text — creating a fresh billed request, never
              // reusing the old bill.
              let retryQuestion = null;
              for (let j = i - 1; j >= 0; j--) {
                if (messages[j].role === "u") { retryQuestion = messages[j].text; break; }
              }
              return <KMessage2 key={m.id} msg={m} model={model} onCite={onCite} onAsk={onAsk} retryQuestion={retryQuestion} />;
            })
          )}
        </div>
      </div>
      <Composer2 kol={kol} reg={reg} onAsk={onAsk} models={models} model={modelId} setModel={setModel} effort={effort} setEffort={setEffort} onAddModel={onAddModel} />
    </div>
  );
}

Object.assign(window, {
  KMessage2, Rail2, PersonaCard2R, EmptyThread2, Composer2, ChatArea2,
  CHAT2_PHASE_MAP, CHAT2_SSE_EVENTS, chat2EmptyResp, chat2AdaptCitations, chat2OwnerFee,
  streamChat2, refreshChat2Wallet,
});
