// SQSEO app, Keyword Research screen (live: POST /api/research -> persisted
// longtails; GET /api/keywords by tab; keyword drilldown; save-to-list; CSV export).
(function init(){
if(!window.LongtailIQDesignSystem_ae8f12 || !window.LTQ || !window.LTQ.api || !window.LTQ.ResultsTable){return setTimeout(init,30);}
const React = window.React;
const DS = window.LongtailIQDesignSystem_ae8f12;
const { Button, Select, Chip, Icon, Badge, MetricCard, Card, OpportunityScore } = DS;
const { ResultsTable, IntentBadge, Difficulty, Demand, Score, PlatformBadge, TrendChart, OpportunityMap, EmptyState, SQ, api, toast, reloadBoot, Modal } = window.LTQ;
const t = (k, v) => (window.LTQ.t ? window.LTQ.t(k, v) : k);

const LOADING_STEPS = () => [1, 2, 3, 4, 5, 6].map((i) => t("research.loading_" + i));
// Example seeds speak the user's language (the engine expands around them).
const PRESETS = () => [1, 2, 3, 4, 5].map((i) => t("research.preset_" + i));
const TABS = [
  { id: "all", key: "total" }, { id: "questions", key: "questions" },
  { id: "comparisons", key: "comparisons" }, { id: "prepositions", key: "prepositions" }, { id: "buyer", key: "buyer" },
  { id: "lowdiff", key: "lowdiff" }, { id: "ai", key: "ai" },
  { id: "local", key: "local" }, { id: "product", key: "product" },
];

// Surface chips are a REAL filter over the generated rows (chip -> the
// platform value the engine emits). Every chip on = everything shows.
const PLATFORM_CHIPS = [
  ["Google", "Google"], ["AI Overviews", "Google AI Overview"],
  ["ChatGPT", "ChatGPT"], ["Perplexity", "Perplexity"], ["Reddit", "Reddit"],
];

// ---- Seed input panel ----
function SeedPanel({ seed, setSeed, onGenerate, generating, recent, siteSeeds, platforms, onTogglePlatform }) {
  const submit = (e) => { e && e.preventDefault(); if (seed.trim()) onGenerate(seed.trim()); };
  return (
    <Card pad={24} tone="raised" style={{ borderRadius: "var(--r-xl)", overflow: "hidden", position: "relative" }}>
      <div style={{ position: "absolute", inset: 0, background: "var(--grad-mesh)", opacity: 0.55, pointerEvents: "none" }} />
      <form onSubmit={submit} style={{ position: "relative" }}>
        <div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 14 }}>
          <span className="lt-eyebrow">{t("research.eyebrow")}</span>
          <span style={{ color: "var(--ink-300)" }}>·</span>
          <span style={{ fontSize: 12.5, color: "var(--text-muted)", fontWeight: 600 }}>{t("research.eyebrow_sub")}</span>
        </div>
        <div style={{ display: "flex", alignItems: "center", gap: 8, padding: "8px 8px 8px 14px", background: "var(--surface-card)", border: "1px solid var(--border-strong)", borderRadius: "var(--r-md)", boxShadow: "var(--shadow-xs)" }}>
          <Icon name="search" size={19} style={{ color: "var(--accent-500)" }} />
          <input data-tour="seed" value={seed} onChange={(e) => setSeed(e.target.value)} placeholder={t("research.seed_ph")}
            style={{ flex: 1, minWidth: 120, height: 38, border: "none", outline: "none", background: "transparent", fontFamily: "var(--font-sans)", fontSize: 15, fontWeight: 500, color: "var(--text-strong)" }} />
          <Button variant="primary" size="lg" leadingIcon={generating ? undefined : "sparkles"} onClick={submit} disabled={generating || !seed.trim()}>
            {generating ? t("research.generating") : t("research.generate")}
          </Button>
        </div>
        <div style={{ display: "flex", gap: 8, flexWrap: "wrap", marginTop: 14 }}>
          {PLATFORM_CHIPS.map(([p]) => <Chip key={p} size="sm" active={platforms.has(p)} onToggle={() => onTogglePlatform(p)}>{p}</Chip>)}
        </div>
        {/* Seeds mined from THEIR site (Site Intelligence) beat generic examples —
            the "crawl the site for seed keywords" fallback, made one-click. */}
        {siteSeeds && siteSeeds.length > 0 ? (
          <div style={{ display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap", marginTop: 16 }}>
            <span style={{ display: "inline-flex", alignItems: "center", gap: 5, fontSize: 12, color: "var(--text-faint)", fontWeight: 600 }}><Icon name="radar" size={13} /> {t("research.from_site")}</span>
            {siteSeeds.map((p) => (
              <button key={p} type="button" onClick={() => setSeed(p)} style={{ height: 26, padding: "0 10px", borderRadius: 999, border: "1px solid var(--border-strong)", background: "var(--surface-card)", color: "var(--text-body)", fontSize: 12, fontWeight: 600, fontFamily: "var(--font-sans)", cursor: "pointer", display: "inline-flex", alignItems: "center", gap: 6, maxWidth: 240, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
                <span style={{ width: 4, height: 4, borderRadius: 999, background: "var(--accent-500)", flex: "none" }} />{p}
              </button>
            ))}
          </div>
        ) : (
          <div style={{ display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap", marginTop: 16 }}>
            <span style={{ fontSize: 12, color: "var(--text-faint)", fontWeight: 600 }}>{t("research.try")}</span>
            {PRESETS().map((p) => (
              <button key={p} type="button" onClick={() => setSeed(p)} style={{ height: 26, padding: "0 10px", borderRadius: 999, border: "1px dashed var(--ink-300)", background: "transparent", color: "var(--text-muted)", fontSize: 12, fontWeight: 600, fontFamily: "var(--font-sans)", cursor: "pointer" }}>{p}</button>
            ))}
          </div>
        )}
        {recent && recent.length > 0 && (
          <div style={{ display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap", marginTop: 10 }}>
            <span style={{ display: "inline-flex", alignItems: "center", gap: 5, fontSize: 12, color: "var(--text-faint)", fontWeight: 600 }}><Icon name="history" size={13} /> {t("research.recent")}</span>
            {recent.slice(0, 6).map((r) => (
              <button key={r.id} type="button" onClick={() => setSeed(r.seed)} title={t("research.n_longtails", { n: r.total })} style={{ height: 26, padding: "0 10px", borderRadius: 999, border: "1px solid var(--border-subtle)", background: "var(--surface-card)", color: "var(--text-body)", fontSize: 12, fontWeight: 600, fontFamily: "var(--font-sans)", cursor: "pointer", maxWidth: 220, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{r.seed}</button>
            ))}
          </div>
        )}
      </form>
    </Card>
  );
}

// Game-feel (phase 7): the level bar. Spring-driven fill that lags then catches
// up (never jumps), milestone ticks per step, shimmer only while active, and a
// completion beat (fill flash + bounce) when `done` flips. Transform-only.
function LevelBar({ progress, steps, step, done }) {
  const fillRef = React.useRef(null);
  React.useEffect(() => {
    const el = fillRef.current;
    if (!el) return;
    const GF = window.GameFeel;
    const target = Math.max(0.04, Math.min(1, progress));
    if (!GF || GF.reduced()) { el.style.transform = `scaleX(${target})`; return; }
    GF.elSpring(el, done ? GF.presets.pop : GF.presets.settle, (n, v) => {
      n.style.transform = `scaleX(${Math.max(0, Math.min(1.0, v))})`;
    }).set(target);
  }, [progress, done]);
  return (
    <div style={{ position: "relative", margin: "2px 0 20px" }}>
      <div style={{ height: 8, borderRadius: 999, background: "var(--ink-100)", overflow: "hidden", position: "relative" }}>
        <div ref={fillRef} className={done ? "lt-bar-done" : "lt-shimmer"}
          style={{ position: "absolute", inset: 0, borderRadius: 999, background: "var(--ink-900)", transform: "scaleX(0.04)", transformOrigin: "left center" }} />
      </div>
      <div style={{ position: "absolute", inset: 0, display: "flex", alignItems: "center", justifyContent: "space-between", padding: "0 2px", pointerEvents: "none" }}>
        {Array.from({ length: steps }, (_, i) => (
          <span key={i} style={{ width: 4, height: 4, borderRadius: 999, transition: "background var(--dur-base) var(--ease-out)",
            background: done || i <= step ? "rgba(255,255,255,0.85)" : "var(--ink-300)" }} />
        ))}
      </div>
    </div>
  );
}

function LoadingPanel({ seed, done }) {
  const [step, setStep] = React.useState(0);
  const stepsList = LOADING_STEPS();
  React.useEffect(() => { if (done) { setStep(stepsList.length - 1); return; } if (step >= stepsList.length - 1) return; const t = setTimeout(() => setStep((s) => s + 1), 480); return () => clearTimeout(t); }, [step, done]);
  return (
    <Card pad={32} tone="raised" style={{ borderRadius: "var(--r-xl)" }}>
      {/* SQ does the digging while the checklist narrates the steps. */}
      {SQ && window.SQRobot
        ? <div style={{ display: "flex", flexDirection: "column", alignItems: "center", textAlign: "center", marginBottom: 20 }}>
            <SQ state="search" width={128} interactive bubble={false} />
            <div style={{ fontSize: 16, fontWeight: 700, color: "var(--text-strong)", marginTop: 2 }}>{t("research.uncovering", { seed })}</div>
            <div style={{ fontSize: 13, color: "var(--text-muted)" }}>{t("research.scanning")}</div>
          </div>
        : <div style={{ display: "flex", alignItems: "center", gap: 12, marginBottom: 22 }}>
            <span style={{ width: 36, height: 36, borderRadius: 11, display: "grid", placeItems: "center", background: "var(--grad-accent)", boxShadow: "var(--shadow-accent)" }}><Icon name="sparkles" size={18} style={{ color: "#fff" }} /></span>
            <div>
              <div style={{ fontSize: 16, fontWeight: 700, color: "var(--text-strong)" }}>{t("research.uncovering", { seed })}</div>
              <div style={{ fontSize: 13, color: "var(--text-muted)" }}>{t("research.scanning")}</div>
            </div>
          </div>}
      <LevelBar progress={done ? 1 : (step + 0.6) / stepsList.length} steps={stepsList.length} step={step} done={done} />
      <div style={{ display: "flex", flexDirection: "column", gap: 12 }}>
        {stepsList.map((s, i) => {
          const done2 = done || i < step, current = !done && i === step;
          return (
            <div key={i} style={{ display: "flex", alignItems: "center", gap: 12, opacity: done || i <= step ? 1 : 0.4, transition: "opacity var(--dur-base)" }}>
              <span style={{ width: 22, height: 22, borderRadius: 999, display: "grid", placeItems: "center", flex: "none", background: done2 ? "var(--green-500)" : current ? "var(--accent-50)" : "var(--ink-100)", border: current ? "2px solid var(--accent-500)" : "none" }}>
                {done2 ? <Icon name="check" size={12} strokeWidth={3} style={{ color: "#fff" }} /> : current ? <span className="lt-spin" style={{ width: 9, height: 9, border: "2px solid var(--accent-300)", borderTopColor: "var(--accent-500)", borderRadius: 999 }} /> : <span style={{ width: 6, height: 6, borderRadius: 999, background: "var(--ink-300)" }} />}
              </span>
              <span style={{ fontSize: 14, fontWeight: current ? 700 : 600, color: done2 ? "var(--text-body)" : current ? "var(--text-strong)" : "var(--text-faint)" }}>{s}</span>
            </div>
          );
        })}
      </div>
      <div style={{ display: "grid", gridTemplateColumns: "repeat(3,1fr)", gap: 10, marginTop: 26 }}>{[0, 1, 2].map((i) => <div key={i} className="lt-shimmer" style={{ height: 64, borderRadius: "var(--r-md)", background: "var(--ink-100)" }} />)}</div>
    </Card>
  );
}

function FilterGroup({ label, children }) {
  return <div style={{ display: "flex", flexDirection: "column", gap: 8, paddingBottom: 16, marginBottom: 16, borderBottom: "1px solid var(--border-subtle)" }}>
    <span style={{ fontSize: 11, fontWeight: 700, textTransform: "uppercase", letterSpacing: "0.07em", color: "var(--text-faint)" }}>{label}</span>{children}</div>;
}
function FiltersBody({ f, setF }) {
  const inp = { width: "100%", height: 38, padding: "0 10px", borderRadius: 8, border: "1px solid var(--border-strong)", background: "var(--surface-card)", fontSize: 13, fontFamily: "var(--font-sans)", color: "var(--text-strong)", outline: "none" };
  return (
    <>
      <FilterGroup label={t("research.include_words")}><input value={f.include} onChange={(e) => setF({ ...f, include: e.target.value })} placeholder={t("research.include_ph")} style={inp} /></FilterGroup>
      <FilterGroup label={t("research.exclude_words")}><input value={f.exclude} onChange={(e) => setF({ ...f, exclude: e.target.value })} placeholder={t("research.exclude_ph")} style={inp} /></FilterGroup>
      <FilterGroup label={t("research.min_opportunity") + " · " + f.min}><input type="range" min="0" max="100" value={f.min} onChange={(e) => setF({ ...f, min: +e.target.value })} style={{ width: "100%", accentColor: "var(--accent-500)" }} /></FilterGroup>
      <label style={{ display: "flex", alignItems: "center", gap: 8, fontSize: 13, color: "var(--text-body)", cursor: "pointer", minHeight: 34 }}>
        <input type="checkbox" checked={f.questionsOnly} onChange={(e) => setF({ ...f, questionsOnly: e.target.checked })} style={{ accentColor: "var(--accent-500)", width: 15, height: 15 }} /> {t("research.questions_only")}
      </label>
    </>
  );
}
function FiltersRail({ f, setF }) {
  return (
    <div style={{ width: 220, flex: "none" }} className="lt-hide-mobile">
      <Card pad={18} style={{ position: "sticky", top: 80 }}>
        <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 16 }}>
          <span style={{ display: "inline-flex", alignItems: "center", gap: 7, fontSize: 14, fontWeight: 700, color: "var(--text-strong)" }}><Icon name="sliders-horizontal" size={15} /> {t("research.filters")}</span>
          <button onClick={() => setF({ min: 0, include: "", exclude: "", questionsOnly: false })} style={{ border: "none", background: "transparent", color: "var(--accent-600)", fontSize: 12, fontWeight: 700, cursor: "pointer" }}>{t("research.reset")}</button>
        </div>
        <FiltersBody f={f} setF={setF} />
      </Card>
    </div>
  );
}

// ---- keyword drilldown drawer ----
function KeywordDrawer({ id, onClose, onOpen, onSave, onBrief }) {
  const [d, setD] = React.useState(null); // null = loading, "error" = failed
  React.useEffect(() => { let alive = true; setD(null); api.get("/api/keyword/" + id).then((r) => { if (alive) setD(r.ok ? r.data : "error"); }); return () => { alive = false; }; }, [id]);
  const k = d && d !== "error" && d.keyword;
  React.useEffect(() => { const onKey = (e) => { if (e.key === "Escape") onClose(); }; window.addEventListener("keydown", onKey); return () => window.removeEventListener("keydown", onKey); }, []);
  return (
    <div onMouseDown={(e) => { if (e.target === e.currentTarget) onClose(); }} style={{ position: "fixed", inset: 0, zIndex: 130, background: "rgba(12,16,14,0.42)", display: "flex", justifyContent: "flex-end" }}>
      <div className="lt-drawer-in" style={{ width: "min(460px, 94vw)", height: "100%", background: "var(--paper)", borderLeft: "1px solid var(--border-subtle)", boxShadow: "var(--shadow-lg)", overflowY: "auto", padding: 24 }}>
        <div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start", gap: 12, marginBottom: 18 }}>
          <span className="lt-eyebrow">{t("research.keyword_detail")}</span>
          <button onClick={onClose} aria-label={t("chrome.close")} style={{ width: 30, height: 30, display: "grid", placeItems: "center", border: "none", background: "transparent", cursor: "pointer", color: "var(--text-faint)" }}><Icon name="x" size={18} /></button>
        </div>
        {d === "error" ? (
          <div style={{ padding: 40, textAlign: "center", fontSize: 13, color: "var(--text-faint)" }}>
            {t("chrome.load_failed")}{" "}
            <button onClick={() => { setD(null); api.get("/api/keyword/" + id).then((r) => setD(r.ok ? r.data : "error")); }} style={{ border: "none", background: "transparent", cursor: "pointer", fontSize: 13, fontWeight: 600, color: "var(--accent-600)" }}>{t("chrome.retry")}</button>
          </div>
        ) : !k ? <div className="lt-loading-delayed" style={{ padding: 40, textAlign: "center", color: "var(--text-faint)" }}><span className="lt-spin" style={{ display: "inline-block", width: 20, height: 20, border: "2px solid var(--accent-200)", borderTopColor: "var(--accent-500)", borderRadius: 999 }} /></div> : (
          <>
            <h2 style={{ fontFamily: "var(--font-display)", fontSize: 21, fontWeight: 600, letterSpacing: "-0.02em", color: "var(--text-strong)", lineHeight: 1.25 }}>{k.q}</h2>
            <div style={{ display: "flex", alignItems: "center", gap: 14, margin: "16px 0" }}>
              <Score score={k.score} width={120} />
              <div><div className="lt-num" style={{ fontFamily: "var(--font-mono)", fontSize: 28, fontWeight: 700, color: k.score >= 80 ? "var(--viz-green)" : "var(--text-strong)" }}>{k.score}</div><div style={{ fontSize: 11.5, color: "var(--text-faint)" }}>{t("research.opportunity_100")}</div></div>
            </div>
            <div style={{ display: "flex", gap: 8, margin: "0 0 14px" }}>
              <button onClick={() => { onBrief && onBrief(k); }} style={{ flex: 1, height: 36, borderRadius: 9, border: "none", background: "var(--ink-900)", color: "#fff", cursor: "pointer", fontFamily: "var(--font-sans)", fontSize: 13, fontWeight: 600, display: "inline-flex", alignItems: "center", justifyContent: "center", gap: 7 }}><Icon name="file-pen-line" size={15} /> {t("research.content_brief")}</button>
              <button onClick={() => { onSave && onSave(k); }} style={{ height: 36, padding: "0 14px", borderRadius: 9, border: "1px solid var(--border-subtle)", background: "var(--paper)", cursor: "pointer", fontFamily: "var(--font-sans)", fontSize: 13, fontWeight: 600, color: "var(--text-body)", display: "inline-flex", alignItems: "center", gap: 7 }}><Icon name="bookmark-plus" size={15} /> {t("research.save")}</button>
              <button onClick={async () => { try { await navigator.clipboard.writeText(k.q); window.LTQ.toast(t("research.copied")); } catch (e) { window.LTQ.toast(t("settings.copy_manual"), { tone: "warning" }); } }} title={t("research.copy_keyword")} style={{ height: 36, padding: "0 12px", borderRadius: 9, border: "1px solid var(--border-subtle)", background: "var(--paper)", cursor: "pointer", fontFamily: "var(--font-sans)", fontSize: 13, fontWeight: 600, color: "var(--text-body)", display: "inline-flex", alignItems: "center", gap: 7 }}><Icon name="copy" size={15} /></button>
            </div>
            <div style={{ display: "flex", alignItems: "flex-start", gap: 8, padding: "12px 14px", background: "var(--ink-50)", borderRadius: 10, fontSize: 13, color: "var(--text-body)", lineHeight: 1.5 }}>
              <Icon name="lightbulb" size={15} style={{ color: "var(--accent-500)", marginTop: 1, flex: "none" }} />{d.reason}
            </div>
            <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 10, margin: "16px 0" }}>
              {[[t("research.est_demand"), (k.demand || 0).toLocaleString()], [t("research.difficulty"), k.diff], [t("research.est_cpc"), "$" + (d.cpc || 0)], [t("research.intent"), window.LTQ.dataLabel("intent", k.intent)], [t("research.funnel"), window.LTQ.dataLabel("funnel", k.funnel)], [t("research.platform"), k.platform], ["SERP / GEO", window.LTQ.dataLabel("serp", k.serp)], [t("research.content_type"), window.LTQ.dataLabel("type", k.type)]].map(([l, v], i) => (
                <div key={i} style={{ border: "1px solid var(--border-subtle)", borderRadius: 10, padding: "10px 12px" }}>
                  <div style={{ fontSize: 11, color: "var(--text-faint)", marginBottom: 3 }}>{l}</div>
                  <div style={{ fontSize: 13.5, fontWeight: 600, color: "var(--text-strong)" }}>{v}</div>
                </div>
              ))}
            </div>
            <div style={{ marginBottom: 18 }}>
              <div style={{ fontSize: 12, fontWeight: 700, color: "var(--text-faint)", textTransform: "uppercase", letterSpacing: "0.06em", marginBottom: 8 }}>{t("research.estimated_trend")}</div>
              <Card pad={12}><TrendChart id={"d" + id} points={d.trend} height={70} color="var(--accent-500)" /></Card>
            </div>
            {d.related && d.related.length > 0 && (
              <div>
                <div style={{ fontSize: 12, fontWeight: 700, color: "var(--text-faint)", textTransform: "uppercase", letterSpacing: "0.06em", marginBottom: 8 }}>{t("research.related_in", { cluster: k.cluster })}</div>
                <div style={{ display: "flex", flexDirection: "column", gap: 2 }}>
                  {d.related.map((r) => (
                    <button key={r.id} onClick={() => onOpen(r.id)} style={{ display: "flex", alignItems: "center", gap: 10, width: "100%", padding: "8px 8px", border: "none", borderRadius: 8, background: "transparent", cursor: "pointer", textAlign: "left" }}
                      onMouseEnter={e => e.currentTarget.style.background = "var(--ink-50)"} onMouseLeave={e => e.currentTarget.style.background = "transparent"}>
                      <span style={{ flex: 1, minWidth: 0, fontSize: 13, fontWeight: 600, color: "var(--text-strong)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{r.q}</span>
                      <span className="lt-num" style={{ fontFamily: "var(--font-mono)", fontSize: 12.5, fontWeight: 700, color: r.score >= 80 ? "var(--viz-green)" : "var(--text-muted)" }}>{r.score}</span>
                    </button>
                  ))}
                </div>
              </div>
            )}
          </>
        )}
      </div>
    </div>
  );
}

// ---- save-to-list modal (create a new list, or append to an existing one) ----
function SaveToListModal({ items, onClose, onSaved }) {
  const [mode, setMode] = React.useState("new"); // new | existing
  const [name, setName] = React.useState("");
  const [tag, setTag] = React.useState("");
  const [lists, setLists] = React.useState([]);
  const [listId, setListId] = React.useState("");
  const [busy, setBusy] = React.useState(false);
  React.useEffect(() => { api.get("/api/lists").then((r) => { if (r.ok) { const ls = r.data.lists || []; setLists(ls); if (ls.length) setListId(ls[0].id); } }); }, []);
  const field = { width: "100%", height: 42, padding: "0 12px", borderRadius: 10, background: "var(--surface-card)", border: "1px solid var(--border-strong)", fontFamily: "var(--font-sans)", fontSize: 14, color: "var(--text-strong)", outline: "none" };
  const seg = (m, label) => (
    <button type="button" onClick={() => setMode(m)} disabled={m === "existing" && lists.length === 0}
      style={{ flex: 1, height: 32, borderRadius: 8, border: "none", cursor: m === "existing" && !lists.length ? "default" : "pointer", opacity: m === "existing" && !lists.length ? 0.5 : 1,
        background: mode === m ? "var(--paper)" : "transparent", boxShadow: mode === m ? "var(--shadow-xs)" : "none", fontFamily: "var(--font-sans)", fontSize: 12.5, fontWeight: 600, color: mode === m ? "var(--text-strong)" : "var(--text-muted)" }}>{label}</button>
  );
  const itemsPayload = items.map((r) => ({ keyword: r.q, intent: r.intent, platform: r.platform, demand: r.demand, difficulty: r.diff, score: r.score }));
  const valid = mode === "new" ? name.trim().length > 0 : !!listId;
  const submit = async (e) => {
    e && e.preventDefault();
    if (!valid) return;
    setBusy(true);
    const res = mode === "existing"
      ? await api.post("/api/lists/" + listId + "/items", { items: itemsPayload })
      : await api.post("/api/lists", { name: name.trim(), tag: tag.trim(), items: itemsPayload });
    setBusy(false);
    if (res.ok) {
      await reloadBoot();
      const where = mode === "existing" ? (lists.find((l) => l.id === listId)?.name || "list") : name.trim();
      const n = mode === "existing" && typeof res.data.added === "number" ? res.data.added : items.length;
      toast(t("research.saved_to", { n, where }), { tone: "success" });
      if (window.LTQEngine) window.LTQEngine.celebrate("save");
      onSaved(); onClose();
    } else toast(res.error || t("wizard.toast.save_failed"), { tone: "error" });
  };
  return (
    <Modal title={t("research.save_to_list")} sub={t("research.n_selected", { n: items.length })} onClose={onClose}>
      <form onSubmit={submit} style={{ display: "flex", flexDirection: "column", gap: 12 }}>
        <div style={{ display: "flex", gap: 2, padding: 3, borderRadius: 10, background: "var(--ink-50)", border: "1px solid var(--border-subtle)" }}>{seg("new", t("research.new_list"))}{seg("existing", t("research.add_existing"))}</div>
        {mode === "new" ? (
          <>
            <label style={{ fontSize: 12.5, fontWeight: 600, color: "var(--text-body)" }}>{t("research.list_name")}<input autoFocus value={name} onChange={(e) => setName(e.target.value)} placeholder={t("research.list_name_ph")} style={{ ...field, marginTop: 6 }} /></label>
            <label style={{ fontSize: 12.5, fontWeight: 600, color: "var(--text-body)" }}>{t("research.tag")} <span style={{ color: "var(--text-faint)", fontWeight: 500 }}>{t("wizard.optional")}</span><input value={tag} onChange={(e) => setTag(e.target.value)} placeholder={t("research.tag_ph")} style={{ ...field, marginTop: 6 }} /></label>
          </>
        ) : (
          <label style={{ fontSize: 12.5, fontWeight: 600, color: "var(--text-body)" }}>{t("research.choose_list")}
            <select value={listId} onChange={(e) => setListId(e.target.value)} style={{ ...field, marginTop: 6, cursor: "pointer" }}>
              {lists.map((l) => <option key={l.id} value={l.id}>{l.name} ({l.count})</option>)}
            </select>
          </label>
        )}
        <div style={{ display: "flex", justifyContent: "flex-end", gap: 8, marginTop: 4 }}>
          <button type="button" onClick={onClose} style={{ height: 38, padding: "0 14px", borderRadius: 9, border: "1px solid var(--border-subtle)", background: "var(--paper)", cursor: "pointer", fontFamily: "var(--font-sans)", fontSize: 13, fontWeight: 600, color: "var(--text-body)" }}>{t("chrome.cancel")}</button>
          <button type="submit" disabled={busy || !valid} style={{ height: 38, padding: "0 16px", borderRadius: 9, border: "none", background: "var(--ink-900)", color: "#fff", cursor: valid ? "pointer" : "default", opacity: valid ? 1 : 0.6, fontFamily: "var(--font-sans)", fontSize: 13, fontWeight: 600, display: "inline-flex", alignItems: "center", gap: 7 }}>
            {busy ? <span className="lt-spin" style={{ width: 14, height: 14, border: "2px solid rgba(255,255,255,0.4)", borderTopColor: "#fff", borderRadius: 999 }} /> : <Icon name="bookmark-plus" size={15} />} {mode === "existing" ? t("research.add_to_list") : t("research.save_list")}
          </button>
        </div>
      </form>
    </Modal>
  );
}

// ---- results ----
function ResultsView({ rows, counts, tab, setTab, loadingRows, platforms }) {
  const [selected, setSelected] = React.useState(new Set());
  const [f, setF] = React.useState({ min: 0, include: "", exclude: "", questionsOnly: false });
  const [drawerId, setDrawerId] = React.useState(null);
  const [briefRow, setBriefRow] = React.useState(null); // row being briefed (BriefDrawer)
  const [saving, setSaving] = React.useState(null); // items[] when the save modal is open
  const [filtersOpen, setFiltersOpen] = React.useState(false); // compact-viewport filter sheet
  const compact = window.LTQ.useCompact ? window.LTQ.useCompact() : false;
  React.useEffect(() => { setSelected(new Set()); }, [tab]);

  const toggle = (q) => setSelected((s) => { const n = new Set(s); n.has(q) ? n.delete(q) : n.add(q); return n; });

  const words = (s) => s.split(",").map((w) => w.trim().toLowerCase()).filter(Boolean);
  const inc = words(f.include), exc = words(f.exclude);
  // Surface chips: hide rows whose platform maps to a toggled-off chip;
  // platforms outside the chip set always show.
  const chipByPlatform = {};
  for (const [chip, plat] of PLATFORM_CHIPS) chipByPlatform[plat] = chip;
  const filtered = rows.filter((r) => {
    const chip = chipByPlatform[r.platform];
    if (chip && platforms && !platforms.has(chip)) return false;
    if (r.score < f.min) return false;
    if (f.questionsOnly && r.kind !== "question") return false;
    const q = r.q.toLowerCase();
    if (inc.length && !inc.some((w) => q.includes(w))) return false;
    if (exc.length && exc.some((w) => q.includes(w))) return false;
    return true;
  });
  const toggleAll = () => setSelected((s) => (filtered.every((r) => s.has(r.q)) ? new Set() : new Set(filtered.map((r) => r.q))));
  const selectedRows = rows.filter((r) => selected.has(r.q));

  const c = counts || {};
  const total = c.total || 0;
  const pct = (v) => (total ? Math.round((v / total) * 100) : 0);
  const metrics = [
    { label: t("dash.total_longtails"), value: String(c.total || 0), icon: "list" },
    { label: t("kind.buyer"), value: String(c.buyer || 0), icon: "shopping-bag", tone: "accent" },
    { label: t("kind.question"), value: String(c.questions || 0), icon: "help-circle" },
    { label: t("dash.low_competition"), value: String(c.lowdiff || 0), icon: "trending-down" },
    { label: t("research.ai_search_ops"), value: String(c.ai || 0), icon: "sparkles", tone: "accent" },
    { label: t("kind.comparison"), value: String(c.comparisons || 0), icon: "git-compare" },
  ];
  const mapItems = [
    { label: t("dash.low_competition"), value: c.lowdiff || 0, pct: pct(c.lowdiff || 0), color: "var(--green-500)" },
    { label: t("kind.question"), value: c.questions || 0, pct: pct(c.questions || 0), color: "var(--blue-500)" },
    { label: t("kind.buyer"), value: c.buyer || 0, pct: pct(c.buyer || 0), color: "var(--accent-500)" },
    { label: t("research.ai_answer_ops"), value: c.ai || 0, pct: pct(c.ai || 0), color: "var(--viz-cyan)" },
    { label: t("kind.comparison"), value: c.comparisons || 0, pct: pct(c.comparisons || 0), color: "var(--amber-500)" },
    { label: t("research.local_product"), value: (c.local || 0) + (c.product || 0), pct: pct((c.local || 0) + (c.product || 0)), color: "var(--ink-500)" },
  ];
  const tabItems = TABS.map((tb) => ({ id: tb.id, label: t("research.tab_" + tb.id), count: c[tb.key] || 0 }));
  const { Tabs } = DS;

  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 20 }}>
      {c.grounded > 0 && (
        <div style={{ display: "flex", alignItems: "center", gap: 10, padding: "10px 14px", borderRadius: "var(--r-lg)", border: "1px solid var(--accent-100)", background: "var(--accent-50, #ecfdf5)" }}>
          <Icon name="radar" size={16} style={{ color: "var(--accent-600)", flex: "none" }} />
          <span style={{ flex: 1, fontSize: 13, color: "var(--text-body)" }}>
            <b style={{ color: "var(--text-strong)" }}>{c.grounded}</b> {t("research.grounded_note")}
          </span>
        </div>
      )}
      <div style={{ display: "grid", gridTemplateColumns: "repeat(6, 1fr)", gap: 12 }} className="lt-kw-metrics lt-keep-grid">
        {metrics.map((m, i) => <MetricCard key={i} {...m} />)}
      </div>

      <div style={{ display: "grid", gridTemplateColumns: "1fr 1.3fr", gap: 16 }} className="lt-ov-grid">
        <Card pad={20}>
          <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 14 }}>
            <span style={{ fontSize: 14, fontWeight: 700, color: "var(--text-strong)" }}>{t("research.opportunity_mix")}</span>
            <Badge tone="neutral" variant="soft">{t("research.n_longtails", { n: total })}</Badge>
          </div>
          <OpportunityMap items={mapItems} />
        </Card>
        <Card pad={20}>
          <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 16 }}>
            <span style={{ fontSize: 14, fontWeight: 700, color: "var(--text-strong)" }}>{t("dash.top_clusters")}</span>
            <span style={{ fontSize: 12, color: "var(--text-faint)" }}>{t("research.n_clustered", { n: total })}</span>
          </div>
          {/* Real cluster rollup from this run (was a sorted duplicate of the mix card) */}
          <OpportunityMap items={(() => {
            const agg = new Map();
            for (const r of rows) { if (!r.cluster) continue; const e = agg.get(r.cluster) || { n: 0 }; e.n++; agg.set(r.cluster, e); }
            const top = [...agg.entries()].sort((a, b) => b[1].n - a[1].n).slice(0, 6);
            const maxN = Math.max(1, ...top.map(([, e]) => e.n));
            const palette = ["var(--viz-blue)", "var(--accent-500)", "var(--viz-cyan)", "var(--viz-amber)", "var(--viz-green)", "var(--ink-500)"];
            return top.map(([name, e], i) => ({ label: name, value: e.n, pct: Math.round((e.n / maxN) * 100), color: palette[i % palette.length] }));
          })()} />
        </Card>
      </div>

      <div>
        <div className="lt-scroll" style={{ overflowX: "auto", marginBottom: 16 }}><Tabs value={tab} onChange={setTab} items={tabItems} /></div>
        {compact && (
          <div style={{ display: "flex", justifyContent: "flex-end", marginBottom: 10 }}>
            <button onClick={() => setFiltersOpen(true)}
              style={{ display: "inline-flex", alignItems: "center", gap: 7, minHeight: 40, padding: "0 14px", border: "1px solid var(--border-subtle)",
                borderRadius: 999, background: "var(--paper)", cursor: "pointer", fontFamily: "var(--font-sans)", fontSize: 13, fontWeight: 600, color: "var(--text-body)" }}>
              <Icon name="sliders-horizontal" size={15} /> {t("research.filters")}
              {(f.min > 0 || f.include || f.exclude || f.questionsOnly) && <span style={{ width: 7, height: 7, borderRadius: 999, background: "var(--accent-500)" }} />}
            </button>
          </div>
        )}
        {compact && window.LTQ.BottomSheet && (
          <window.LTQ.BottomSheet open={filtersOpen} onClose={() => setFiltersOpen(false)} title={t("research.filters")}>
            <FiltersBody f={f} setF={setF} />
            <button onClick={() => { setF({ min: 0, include: "", exclude: "", questionsOnly: false }); }}
              style={{ marginTop: 14, width: "100%", minHeight: 42, border: "1px solid var(--border-subtle)", borderRadius: 10, background: "var(--paper)", cursor: "pointer", fontFamily: "var(--font-sans)", fontSize: 13, fontWeight: 600, color: "var(--accent-600)" }}>{t("research.reset")}</button>
          </window.LTQ.BottomSheet>
        )}
        <div style={{ display: "flex", gap: 16, alignItems: "flex-start" }}>
          <FiltersRail f={f} setF={setF} />
          <div style={{ flex: 1, minWidth: 0 }}>
            {selected.size > 0 && (
              <div style={{ display: "flex", alignItems: "center", gap: 12, padding: "10px 14px", marginBottom: 12, background: "var(--ink-900)", borderRadius: "var(--r-md)", color: "#fff", boxShadow: "var(--shadow-md)" }}>
                <span className="lt-num" style={{ fontFamily: "var(--font-mono)", fontWeight: 700, fontSize: 14 }}>{selected.size}</span>
                <span style={{ fontSize: 13, color: "rgba(245,242,236,0.8)" }}>{t("research.selected")}</span>
                <div style={{ flex: 1 }} />
                <button onClick={() => setSaving(selectedRows)} style={{ height: 30, padding: "0 12px", border: "none", borderRadius: 999, cursor: "pointer", background: "rgba(255,255,255,0.16)", color: "#fff", fontSize: 12.5, fontWeight: 600, fontFamily: "var(--font-sans)" }}>{t("research.save_to_list_btn")}</button>
                <button onClick={() => {
                  // Export exactly what's selected (the topbar Export menu covers "all").
                  const esc = (v) => { const s = String(v == null ? "" : v); return /[",\n]/.test(s) ? '"' + s.replace(/"/g, '""') + '"' : s; };
                  const head = ["keyword", "intent", "funnel", "platform", "demand", "difficulty", "score", "cluster"];
                  const csv = [head.join(",")].concat(selectedRows.map((r) => [r.q, r.intent, r.funnel, r.platform, r.demand, r.diff, r.score, r.cluster].map(esc).join(","))).join("\n");
                  const a = document.createElement("a");
                  a.href = URL.createObjectURL(new Blob(["﻿" + csv], { type: "text/csv;charset=utf-8" }));
                  a.download = "sqseo-selected-keywords.csv";
                  document.body.appendChild(a); a.click(); a.remove();
                  setTimeout(() => URL.revokeObjectURL(a.href), 4000);
                  toast(t("chrome.export_started"), { tone: "success" });
                }} style={{ height: 30, padding: "0 12px", border: "none", borderRadius: 999, cursor: "pointer", background: "rgba(255,255,255,0.12)", color: "#fff", fontSize: 12.5, fontWeight: 600, fontFamily: "var(--font-sans)" }}>{t("research.export_csv")}</button>
                <button onClick={() => setSelected(new Set())} style={{ height: 30, padding: "0 12px", border: "none", borderRadius: 999, cursor: "pointer", background: "transparent", color: "rgba(255,255,255,0.7)", fontSize: 12.5, fontWeight: 600, fontFamily: "var(--font-sans)" }}>{t("research.clear")}</button>
              </div>
            )}
            {loadingRows ? <Card pad={0}><div style={{ padding: 18 }}>{[0, 1, 2, 3, 4, 5].map((i) => <div key={i} className="lt-shimmer" style={{ height: 38, borderRadius: 8, background: "var(--ink-100)", marginBottom: 8 }} />)}</div></Card>
              : filtered.length === 0 ? <Card pad={0}><EmptyState icon="filter" sq="sad" title={t("research.no_matches")} body={t("research.no_matches_body")} /></Card>
              : <ResultsTable rows={filtered} selected={selected} onToggle={toggle} onToggleAll={toggleAll} onRowClick={(r) => setDrawerId(r.id)} onBrief={(r) => setBriefRow(r)} onSave={(r) => setSaving([r])} />}
            <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginTop: 14 }}>
              <span style={{ fontSize: 13, color: "var(--text-muted)" }}>{t("research.showing")} <b style={{ color: "var(--text-strong)" }}>{filtered.length}</b> {t("research.of_n", { n: rows.length })} · <span style={{ color: "var(--text-faint)" }}>{t("research.estimated_note")}</span></span>
            </div>
          </div>
        </div>
      </div>

      {drawerId && <KeywordDrawer id={drawerId} onClose={() => setDrawerId(null)} onOpen={setDrawerId} onSave={(row) => setSaving([row])} onBrief={(k) => { setDrawerId(null); setBriefRow({ id: k.id }); }} />}
      {briefRow && window.LTQ.BriefDrawer && <window.LTQ.BriefDrawer keywordId={briefRow.id} onClose={() => setBriefRow(null)} />}
      {saving && <SaveToListModal items={saving} onClose={() => setSaving(null)} onSaved={() => setSelected(new Set())} />}
    </div>
  );
}

function KeywordResearch() {
  const boot = window.LTQ.boot || {};
  const projectId = boot.project && boot.project.id;
  const [phase, setPhase] = React.useState("init");
  const [runDone, setRunDone] = React.useState(false);
  const [seed, setSeed] = React.useState("");
  const [rows, setRows] = React.useState([]);
  const [counts, setCounts] = React.useState(null);
  const [tab, setTab] = React.useState("all");
  const [loadingRows, setLoadingRows] = React.useState(false);
  const [recent, setRecent] = React.useState([]);
  const [siteSeeds, setSiteSeeds] = React.useState([]);
  const [platforms, setPlatforms] = React.useState(() => new Set(PLATFORM_CHIPS.map(([p]) => p)));
  const togglePlatform = (p) => setPlatforms((s) => { const n = new Set(s); n.has(p) ? n.delete(p) : n.add(p); return n; });
  const loadRecent = React.useCallback(() => { api.get("/api/research/history").then((r) => { if (r.ok) setRecent(r.data.runs || []); }); }, []);

  // Seed topics the Site Intelligence scanner mined from THEIR pages (silent
  // when no profile exists yet — the generic examples remain the fallback).
  React.useEffect(() => {
    let alive = true;
    setSiteSeeds([]);
    api.get("/api/site-intel/profile").then((r) => {
      if (alive && r.ok && r.data.profile && Array.isArray(r.data.profile.seedTopics)) setSiteSeeds(r.data.profile.seedTopics.slice(0, 5));
    });
    return () => { alive = false; };
  }, [projectId]);

  const loadKeywords = React.useCallback(async (t) => {
    setLoadingRows(true);
    const r = await api.get("/api/keywords?tab=" + (t || "all"));
    setLoadingRows(false);
    if (r.ok) { setRows(r.data.keywords || []); setCounts(r.data.counts || counts); }
  }, [counts]);

  // initial load (and on project switch)
  React.useEffect(() => {
    let alive = true;
    setPhase("init"); setTab("all"); loadRecent();
    // Seed handed over by the Deep Dive's CTA: consume once and auto-generate —
    // the user lands here with their first real run already underway.
    const prefill = typeof window.LTQ.seedPrefill === "string" ? window.LTQ.seedPrefill.trim() : "";
    if (prefill) {
      window.LTQ.seedPrefill = null;
      setSeed(prefill);
      generate(prefill);
      return () => { alive = false; };
    }
    (async () => {
      const state = await api.get("/api/research");
      if (!alive) return;
      const c = state.ok ? state.data.counts : null;
      setCounts(c);
      if (state.ok && state.data.run && state.data.run.seed) setSeed(state.data.run.seed);
      if (c && c.total > 0) { const kw = await api.get("/api/keywords?tab=all"); if (alive && kw.ok) { setRows(kw.data.keywords || []); setCounts(kw.data.counts); } setPhase("results"); }
      else setPhase("empty");
    })();
    return () => { alive = false; };
  }, [projectId]);

  const changeTab = (t) => { setTab(t); loadKeywords(t); };

  const generate = async (s) => {
    setPhase("loading");
    const started = Date.now();
    const res = await api.post("/api/research", { seed: s });
    // let the loading steps breathe a moment for a premium feel
    const wait = Math.max(0, 1600 - (Date.now() - started));
    setTimeout(async () => {
      if (res.ok) {
        setTab("all");
        const kw = await api.get("/api/keywords?tab=all");
        if (kw.ok) { setRows(kw.data.keywords || []); setCounts(kw.data.counts); }
        await reloadBoot();
        loadRecent();
        // level-complete beat: the bar fills + all steps tick before the swap
        setRunDone(true);
        await new Promise((r) => setTimeout(r, 560));
        setRunDone(false);
        toast(t("research.found_toast", { n: res.data.run.total, seed: s }), { tone: "success" });
        setPhase("results");
        // First-ever completed research in this workspace gets the big moment;
        // every later run keeps the standard burst (existing behavior).
        if (res.data.run.total > 0 && window.LTQEngine) {
          const big = window.GameFeel && window.GameFeel.first("research");
          window.LTQEngine.celebrate(big ? "grade" : "run");
        }
        if (res.data.run.total > 0 && window.LTQ.sqCheer) window.LTQ.sqCheer();
      } else {
        toast(res.error || t("research.failed"), { tone: "error" });
        if (window.LTQ.sqOops) window.LTQ.sqOops();
        setPhase(counts && counts.total > 0 ? "results" : "empty");
      }
    }, wait);
  };

  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 20 }}>
      <SeedPanel seed={seed} setSeed={setSeed} onGenerate={generate} generating={phase === "loading"} recent={recent} siteSeeds={siteSeeds} platforms={platforms} onTogglePlatform={togglePlatform} />
      {phase === "init" && <Card pad={0}><div style={{ padding: 28 }}><div className="lt-shimmer" style={{ height: 120, borderRadius: 12, background: "var(--ink-100)" }} /></div></Card>}
      {phase === "empty" && (
        <Card pad={0} style={{ overflow: "visible" }}><EmptyState icon="radar" sq="search"
          sqSay={t("research.sq.coach")} sqSayKey="research_coach" title={t("research.empty_title")}
          body={t("research.empty_body")}
          action={<Button variant="primary" leadingIcon="sparkles" onClick={() => seed.trim() ? generate(seed.trim()) : setSeed(PRESETS()[0])}>{t("research.empty_cta")}</Button>} /></Card>
      )}
      {phase === "loading" && <LoadingPanel seed={seed} done={runDone} />}
      {phase === "results" && <ResultsView rows={rows} counts={counts} tab={tab} setTab={changeTab} loadingRows={loadingRows} platforms={platforms} />}
    </div>
  );
}
window.LTQ.KeywordResearch = KeywordResearch;
})();
