import { useEffect, useMemo, useRef, useState } from "react";
import { createPortal } from "react-dom";
import { useNavigate, useSearchParams } from "react-router-dom";
import { Button, EmptyState, Input, MarkdownText, Select, Textarea } from "../../components/ui";
import { AppIcon } from "../../components/ui/AppIcons";
import { useData, useRepos } from "../../data/DataContext";
import type {
  Brand,
  InspireElement,
  InspireElementKind,
  InspireSession,
  InspireSessionSummary,
  TrendItem,
} from "../../domain/types";
import { isAbortError } from "../../lib/abort";
import { newId } from "../../lib/id";
import { saveComposeDraftBody } from "../../lib/composeBridge";
import { nowUnixNano } from "../../lib/time";
import { clearDraft, emptyDraft, saveDraft } from "./wizardState";
import { useI18n } from "../../i18n/I18nContext";

type Props = {
  accountId: string;
  personaId: string;
  personaReady: boolean;
};

const KIND_ORDER: Array<Exclude<InspireElementKind, "persona">> = [
  "role",
  "snippet",
  "brand",
  "trend",
];

function kindLabel(
  kind: Exclude<InspireElementKind, "persona">,
  t: (k: string, p?: Record<string, string | number>) => string,
): string {
  if (kind === "role") return t("inspire.kindRole");
  if (kind === "snippet") return t("inspire.kindSnippet");
  if (kind === "brand") return t("inspire.kindBrand");
  return t("inspire.kindTrend");
}

function isPersonaElement(el: InspireElement): boolean {
  return el.kind === "persona" || el.id.startsWith("el_persona_");
}

/**
 * 靈感主路徑：聊天 + 套用可重用元素 → 產文
 * 元素庫：角色／片段／品牌／熱點（不含人設）。
 */
export function InspirePanel({ accountId, personaId, personaReady }: Props) {
  const repos = useRepos();
  const { refresh, tick } = useData();
  const { t } = useI18n();
  const navigate = useNavigate();
  const [searchParams, setSearchParams] = useSearchParams();
  const bottomRef = useRef<HTMLDivElement>(null);
  /** 今日／外連帶入的 topic 只套一次 */
  const topicSeeded = useRef(false);

  const [session, setSession] = useState<InspireSession | null>(null);
  const [sessionList, setSessionList] = useState<InspireSessionSummary[]>([]);
  const [elements, setElements] = useState<InspireElement[]>([]);
  const [brands, setBrands] = useState<Brand[]>([]);
  const [trends, setTrends] = useState<TrendItem[]>([]);

  const [input, setInput] = useState("");
  const [pinnedIds, setPinnedIds] = useState<string[]>([]);
  const [busy, setBusy] = useState("");
  const [message, setMessage] = useState("");
  const [error, setError] = useState("");
  /** stream 中的助手暫存字（尚未 done） */
  const [streamingText, setStreamingText] = useState("");
  /** 進行中的串流；按停止或離開頁面時用它中止，避免背景繼續燒 AI 額度 */
  const streamAbort = useRef<AbortController | null>(null);
  const [useWeb, setUseWeb] = useState(false);
  const [showAdd, setShowAdd] = useState(false);
  const [newKind, setNewKind] = useState<InspireElementKind>("role");
  const [newTitle, setNewTitle] = useState("");
  const [newBody, setNewBody] = useState("");
  type PromptView = {
    prompt: string;
    mode: string;
    sections: string[];
    blocks: Array<{ title: string; body: string }>;
    fingerprint: string;
    charCount: number;
    runeCount: number;
    note?: string;
    /** 組裝時用的訊息（對照用） */
    message?: string;
  };
  /** 真實 prompt 預覽（後端同 buildInspirePrompt） */
  const [promptPreview, setPromptPreview] = useState<PromptView | null>(null);
  const [showPromptPreview, setShowPromptPreview] = useState(false);
  /** 送出前最後一次 ? 預覽的指紋（用同一 message/mode/pin 才可比） */
  const [lastPreviewFp, setLastPreviewFp] = useState<{
    fingerprint: string;
    mode: string;
    message: string;
    pinnedKey: string;
    personaId: string;
  } | null>(null);
  /** 剛送出後後端回傳的實際 prompt */
  const [lastSent, setLastSent] = useState<{
    fingerprint: string;
    charCount: number;
    runeCount: number;
    prompt: string;
    mode: string;
    message: string;
  } | null>(null);
  /** 路線 1：次要區預設收合，主線只留對話 + 輸入 + 產文 */
  const [showTrends, setShowTrends] = useState(false);
  const [showLibrary, setShowLibrary] = useState(false);
  const [showAdvanced, setShowAdvanced] = useState(false);
  const inputRef = useRef<HTMLTextAreaElement | null>(null);

  function applySessionLocal(sess: InspireSession, els: InspireElement[]) {
    setSession(sess);
    const personaIds = new Set(els.filter(isPersonaElement).map((e) => e.id));
    const pins = (sess.pinned_element_ids || []).filter((id) => !personaIds.has(id));
    setPinnedIds(pins);
    setStreamingText("");
    setLastSent(null);
    setLastPreviewFp(null);
    setPromptPreview(null);
    setShowPromptPreview(false);
    setUseWeb(false);
    setInput("");
  }

  async function reloadSessionList() {
    try {
      setSessionList(await repos.inspiration.listSessions());
    } catch {
      /* list optional on older backend */
    }
  }

  useEffect(() => {
    void (async () => {
      try {
        const [sess, els, bList, tList, summaries] = await Promise.all([
          repos.inspiration.getSession(),
          repos.inspiration.listElements(),
          repos.scout.listBrands(),
          repos.inspiration.listTrends("threads_tag"),
          repos.inspiration.listSessions().catch(() => [] as InspireSessionSummary[]),
        ]);
        const cleanEls = els.filter((e) => !isPersonaElement(e));
        applySessionLocal(sess, els);
        setSessionList(summaries);
        setElements(cleanEls);
        setBrands(bList);
        setTrends([...tList].sort((a, b) => a.heat - b.heat || 0).slice(0, 12));
        setError("");
      } catch (e) {
        setError(e instanceof Error ? e.message : t("inspire.fail"));
      }
    })();
  }, [repos, t, tick]);

  useEffect(() => {
    bottomRef.current?.scrollIntoView({ behavior: "smooth" });
  }, [session?.messages.length, busy, streamingText]);

  // 離開頁面時關掉還開著的串流，否則 SSE 連線與後端的 AI 呼叫都會繼續跑到結束。
  useEffect(() => {
    return () => streamAbort.current?.abort();
  }, []);

  // 從今日等入口：?topic=xxx → 預填輸入框（只一次）
  useEffect(() => {
    if (topicSeeded.current) return;
    const topic = (searchParams.get("topic") || "").trim();
    if (!topic) return;
    topicSeeded.current = true;
    setInput((cur) => (cur.trim() ? cur : t("inspire.topicSeed", { topic })));
    // 清掉 query，避免重整重複塞
    const next = new URLSearchParams(searchParams);
    next.delete("topic");
    setSearchParams(next, { replace: true });
  }, [searchParams, setSearchParams, t]);

  const pinnedElements = useMemo(
    () => elements.filter((e) => pinnedIds.includes(e.id)),
    [elements, pinnedIds],
  );

  /** 發想輪次（不含產文短紀錄） */
  const ideationTurns = useMemo(() => {
    if (!session) return 0;
    return session.messages.filter(
      (m) => m.role === "user" && !(m.text || "").startsWith("【產文】"),
    ).length;
  }, [session]);

  const hasDraftInSession = useMemo(() => {
    if (!session) return false;
    return session.messages.some((m) => Boolean(m.draft?.body));
  }, [session]);

  /** 2～3 輪後提示定稿（路線 1） */
  const showReadyToWrite =
    ideationTurns >= 2 && !hasDraftInSession && !busy;

  const isChatEmpty = !session || session.messages.length === 0;

  function focusInput() {
    // 下一幀再 focus（空狀態按鈕後）
    window.setTimeout(() => {
      const el =
        inputRef.current ||
        (document.querySelector(
          ".hb-inspire-composer .hb-textarea",
        ) as HTMLTextAreaElement | null);
      el?.focus();
    }, 50);
  }

  function startFromTopic() {
    setMessage(t("inspire.startTopicHint"));
    focusInput();
  }

  /** 已有草稿：直接貼到聊天，先說想改哪裡。 */
  function startFromPasteDraft() {
    setMessage(t("inspire.pasteDraftHint"));
    focusInput();
  }

  function insertPinToInput(el: InspireElement) {
    const body = (el.body || "").trim();
    let snippet: string;
    if (el.kind === "brand") {
      snippet = t("inspire.insertBrand", { name: el.title });
    } else if (body) {
      const short = body.length > 40 ? `${body.slice(0, 40)}…` : body;
      snippet = `${el.title}：${short}`;
    } else {
      snippet = el.title;
    }
    setInput((prev) => {
      const p = prev.trim();
      return p ? `${p}\n${snippet}` : snippet;
    });
    setMessage(t("inspire.insertedPin", { name: el.title }));
  }

  const grouped = useMemo(() => {
    const map = new Map<Exclude<InspireElementKind, "persona">, InspireElement[]>();
    for (const k of KIND_ORDER) map.set(k, []);
    for (const el of elements) {
      if (isPersonaElement(el)) continue;
      const kind = el.kind === "persona" ? null : el.kind;
      if (!kind || !map.has(kind)) continue;
      map.get(kind)!.push(el);
    }
    return map;
  }, [elements]);

  function setSessionPins(next: string[]) {
    setPinnedIds(next);
    if (!session) return;
    setSession({ ...session, pinned_element_ids: next });
    setError("");
    setMessage(t("inspire.pinsSessionLocal"));
  }

  function togglePin(id: string) {
    const next = pinnedIds.includes(id)
      ? pinnedIds.filter((x) => x !== id)
      : [...pinnedIds, id];
    setSessionPins(next);
  }

  async function ensureBrandElement(b: Brand): Promise<InspireElement> {
    const id = `el_brand_${b.id}`;
    const existing = elements.find((e) => e.id === id);
    if (existing) return existing;
    const now = nowUnixNano();
    const el: InspireElement = {
      id,
      kind: "brand",
      title: b.display_name,
      body: b.brief || b.display_name,
      ref_id: b.id,
      reusable: true,
      created_at: now,
      updated_at: now,
    };
    const saved = await repos.inspiration.saveElement(el);
    setElements((await repos.inspiration.listElements()).filter((e) => !isPersonaElement(e)));
    return saved;
  }

  async function pinBrand(b: Brand) {
    const el = await ensureBrandElement(b);
    if (!pinnedIds.includes(el.id)) setSessionPins([...pinnedIds, el.id]);
  }

  function pinnedKeyOf(ids: string[]) {
    return [...ids].sort().join(",");
  }

  /** 換上新的 controller，順手中止前一輪沒收乾淨的串流。 */
  function beginStream(): AbortSignal {
    streamAbort.current?.abort();
    const controller = new AbortController();
    streamAbort.current = controller;
    return controller.signal;
  }

  function stopStream() {
    streamAbort.current?.abort();
  }

  /** 聊天：必須有輸入。 */
  function resolveSendPayload(mode: "chat" | "generate"): { message: string; mode: "chat" | "generate" } | null {
    const text = input.trim();
    if (mode === "chat" && !text) return null;
    if (mode === "generate") return null;
    if (!text) return null;
    return { message: text, mode };
  }

  async function generatePost() {
    if (!session || ideationTurns === 0) {
      setMessage(t("inspire.needConversation"));
      return;
    }
    if (!personaReady || !personaId) {
      setMessage(t("inspire.needReadyPersona"));
      return;
    }
    setBusy("generate");
    setMessage("");
    setStreamingText("");
    if (session) {
      setSession({
        ...session,
        messages: [
          ...session.messages,
          {
            id: `local_u_${Date.now()}`,
            role: "user",
            text: `【產文】${t("inspire.generate")}`,
            created_at: nowUnixNano(),
          },
        ],
      });
    }
    const signal = beginStream();
    try {
      const result = await repos.inspiration.chatStream(
        {
          message: "",
          pinnedIds,
          mode: "generate",
          personaId,
          sessionId: session?.id,
          signal,
        },
        (chunk) => {
          setStreamingText((prev) => prev + chunk);
        },
      );
      setStreamingText("");
      setSession(result.session);
      void reloadSessionList();
      if (result.fingerprint) {
        setLastSent({
          fingerprint: result.fingerprint,
          charCount: result.charCount ?? 0,
          runeCount: result.runeCount ?? 0,
          prompt: result.prompt ?? "",
          mode: "generate",
          message: t("inspire.generate"),
        });
        if (result.prompt) {
          setPromptPreview({
            prompt: result.prompt,
            mode: "generate",
            sections: result.sections ?? [],
            blocks: [{ title: t("inspire.sentPrompt"), body: result.prompt }],
            fingerprint: result.fingerprint,
            charCount: result.charCount ?? 0,
            runeCount: result.runeCount ?? 0,
            note: t("inspire.sentPromptNote"),
            message: t("inspire.generate"),
          });
        }
        setMessage(t("inspire.generateOk"));
      }
      refresh();
    } catch (e) {
      setStreamingText("");
      setMessage(isAbortError(e) ? t("inspire.stopped") : e instanceof Error ? e.message : t("inspire.fail"));
      try {
        setSession(await repos.inspiration.getSession());
      } catch {
        /* ignore */
      }
    } finally {
      setBusy("");
    }
  }

  async function send(mode: "chat" | "generate") {
    if (mode === "generate") {
      await generatePost();
      return;
    }
    const payload = resolveSendPayload(mode);
    if (!payload) {
      setMessage(t("inspire.needInput"));
      return;
    }
    const { message: userMsg } = payload;
    setBusy(mode);
    setMessage("");
    setStreamingText("");
    // 樂觀顯示使用者訊息
    if (session) {
      setSession({
        ...session,
        messages: [
          ...session.messages,
          {
            id: `local_u_${Date.now()}`,
            role: "user",
            text: userMsg,
            created_at: nowUnixNano(),
          },
        ],
      });
    }
    setInput("");
    const signal = beginStream();
    try {
      const result = await repos.inspiration.chatStream(
        {
          message: userMsg,
          pinnedIds,
          mode,
          personaId,
          sessionId: session?.id,
          useWeb,
          signal,
        },
        (chunk) => {
          setStreamingText((prev) => prev + chunk);
        },
      );
      setStreamingText("");
      setUseWeb(false);
      setSession(result.session);
      void reloadSessionList();
      if (result.fingerprint) {
        // 僅在「送出參數」與「上次 ? 預覽參數」完全相同時才宣稱一致
        const sameAsPreview =
          lastPreviewFp != null &&
          lastPreviewFp.fingerprint === result.fingerprint &&
          lastPreviewFp.mode === mode &&
          lastPreviewFp.message === userMsg &&
          lastPreviewFp.pinnedKey === pinnedKeyOf(pinnedIds) &&
          lastPreviewFp.personaId === (personaId || "");
        setLastSent({
          fingerprint: result.fingerprint,
          charCount: result.charCount ?? 0,
          runeCount: result.runeCount ?? 0,
          prompt: result.prompt ?? "",
          mode,
          message: userMsg,
        });
        // 送出後把預覽快照換成「實際送出」那份，避免清空輸入後再按 ? 對到空字串
        if (result.prompt) {
          setPromptPreview({
            prompt: result.prompt,
            mode,
            sections: result.sections ?? [],
            blocks: [{ title: t("inspire.sentPrompt"), body: result.prompt }],
            fingerprint: result.fingerprint,
            charCount: result.charCount ?? 0,
            runeCount: result.runeCount ?? 0,
            note: t("inspire.sentPromptNote"),
            message: userMsg,
          });
          setLastPreviewFp({
            fingerprint: result.fingerprint,
            mode,
            message: userMsg,
            pinnedKey: pinnedKeyOf(pinnedIds),
            personaId: personaId || "",
          });
        }
        if (sameAsPreview) {
          setMessage(t("inspire.fpMatch", { fp: result.fingerprint }));
        } else if (lastPreviewFp && lastPreviewFp.fingerprint !== result.fingerprint) {
          setMessage(
            t("inspire.fpMismatch", {
              preview: lastPreviewFp.fingerprint,
              sent: result.fingerprint,
            }),
          );
        } else {
          setMessage(t("inspire.fpSent", { fp: result.fingerprint }));
        }
      }
      refresh();
    } catch (e) {
      setStreamingText("");
      setMessage(isAbortError(e) ? t("inspire.stopped") : e instanceof Error ? e.message : t("inspire.fail"));
      // 失敗時重拉 session 去掉樂觀 user bubble 若後端沒寫入
      try {
        setSession(await repos.inspiration.getSession());
      } catch {
        /* ignore */
      }
    } finally {
      setBusy("");
    }
  }

  async function applyTrendChip(item: TrendItem) {
    setInput((prev) =>
      prev.trim()
        ? prev
        : t("inspire.wantWrite", { label: item.label, summary: item.summary.slice(0, 48) }),
    );
  }

  async function pinTrendAsElement(item: TrendItem) {
    const id = `el_trend_${item.id}`;
    let el = elements.find((e) => e.id === id);
    if (!el) {
      const now = nowUnixNano();
      el = await repos.inspiration.saveElement({
        id,
        kind: "trend",
        title: item.label,
        body: t("inspire.trendBody", { label: item.label, summary: item.summary }),
        ref_id: item.id,
        reusable: true,
        created_at: now,
        updated_at: now,
      });
      setElements((await repos.inspiration.listElements()).filter((e) => !isPersonaElement(e)));
    }
    if (!pinnedIds.includes(el.id)) setSessionPins([...pinnedIds, el.id]);
    setMessage(t("inspire.pinnedTrend", { label: item.label }));
  }

  async function addElement() {
    if (!newTitle.trim() || !newBody.trim()) {
      setMessage(t("inspire.needTitleBody"));
      return;
    }
    setBusy("add");
    try {
      const kind: InspireElementKind =
        newKind === "role" || newKind === "snippet" || newKind === "trend" ? newKind : "snippet";
      await repos.inspiration.saveElement({
        id: newId("el"),
        kind,
        title: newTitle.trim(),
        body: newBody.trim(),
        reusable: true,
        created_at: nowUnixNano(),
        updated_at: nowUnixNano(),
      });
      setElements((await repos.inspiration.listElements()).filter((e) => !isPersonaElement(e)));
      setNewTitle("");
      setNewBody("");
      setShowAdd(false);
      setMessage(t("inspire.added"));
      refresh();
    } catch (e) {
      setMessage(e instanceof Error ? e.message : t("inspire.addFail"));
    } finally {
      setBusy("");
    }
  }

  async function removeElement(id: string) {
    if (!window.confirm(t("inspire.confirmRemove"))) return;
    await repos.inspiration.removeElement(id);
    setElements((await repos.inspiration.listElements()).filter((e) => !isPersonaElement(e)));
    setPinnedIds((ids) => ids.filter((x) => x !== id));
    refresh();
  }

  async function newChat() {
    setBusy("new");
    setMessage("");
    try {
      const next = await repos.inspiration.createSession();
      applySessionLocal(next, elements);
      setMessage(t("inspire.newSessionOk"));
      await reloadSessionList();
      refresh();
    } catch (e) {
      setMessage(e instanceof Error ? e.message : t("inspire.fail"));
    } finally {
      setBusy("");
    }
  }

  async function switchSession(id: string) {
    if (!id || id === session?.id) return;
    setBusy("switch");
    setMessage("");
    try {
      const next = await repos.inspiration.activateSession(id);
      applySessionLocal(next, elements);
      await reloadSessionList();
    } catch (e) {
      setMessage(e instanceof Error ? e.message : t("inspire.fail"));
    } finally {
      setBusy("");
    }
  }

  async function deleteCurrentSession() {
    if (!session) return;
    if (!window.confirm(t("inspire.confirmDeleteSession"))) return;
    setBusy("delete");
    setMessage("");
    try {
      const next = await repos.inspiration.deleteSession(session.id);
      applySessionLocal(next, elements);
      setMessage(t("inspire.deletedSession"));
      await reloadSessionList();
      refresh();
    } catch (e) {
      setMessage(e instanceof Error ? e.message : t("inspire.fail"));
    } finally {
      setBusy("");
    }
  }

  const sessionLabel = useMemo(() => {
    if (!session) return t("inspire.sessionNew");
    if (session.title?.trim()) return session.title.trim();
    const firstUser = session.messages.find((m) => m.role === "user" && m.text.trim());
    if (firstUser) return firstUser.text.trim().slice(0, 24);
    return t("inspire.sessionNew");
  }, [session, t]);

  function goWrite(body: string) {
    saveComposeDraftBody(body);
    navigate("/app/studio?tab=compose");
  }

  function openPlay(body: string) {
    const draft = emptyDraft();
    draft.title = t("inspire.playTitle");
    draft.topic = body.slice(0, 80);
    if (accountId) {
      draft.lead_account_id = accountId;
      draft.steps = draft.steps.map((s, i) =>
        i === 0 ? { ...s, account_id: accountId, text: body } : s,
      );
    }
    clearDraft("new");
    saveDraft(draft, "new");
    navigate("/app/studio/new");
  }

  async function refreshTrends() {
    if (!window.confirm(t("inspire.refreshConfirm"))) return;
    setBusy("trends");
    setMessage("");
    try {
      const list = await repos.inspiration.refreshTrends("threads_tag");
      // heat 現在是序位 1..n
      setTrends([...list].sort((a, b) => a.heat - b.heat || 0).slice(0, 12));
      setMessage(t("inspire.refreshOk", { n: list.length }));
      refresh();
    } catch (e) {
      setMessage(e instanceof Error ? e.message : t("inspire.refreshFail"));
    } finally {
      setBusy("");
    }
  }

  async function togglePromptPreview(mode: "chat" | "generate" = "chat") {
    // 再開一次問號 → 關閉
    if (showPromptPreview) {
      setShowPromptPreview(false);
      return;
    }
    // 預覽允許空訊息：看人設／pin／歷史組裝；產文空輸入時用預設句
    // （送出仍要有字，但 ? 不該因送完清空而打不開）
    const text = input.trim();
    const previewMode = mode === "generate" ? "generate" : "chat";
    const message =
      text || (previewMode === "generate" ? t("inspire.genMessage") : "");
    setBusy("preview");
    setMessage("");
    try {
      const data = await repos.inspiration.previewPrompt({
        message,
        pinnedIds,
        mode: previewMode,
        personaId,
        sessionId: session?.id,
      });
      const view: PromptView = {
        ...data,
        message,
        note:
          data.note ||
          t("inspire.previewModeNote", { mode: previewMode }),
      };
      setPromptPreview(view);
      setLastPreviewFp({
        fingerprint: data.fingerprint,
        mode: previewMode,
        message,
        pinnedKey: pinnedKeyOf(pinnedIds),
        personaId: personaId || "",
      });
      setShowPromptPreview(true);
    } catch (e) {
      setMessage(e instanceof Error ? e.message : t("inspire.fail"));
    } finally {
      setBusy("");
    }
  }

  function copyPromptText(text: string) {
    void navigator.clipboard?.writeText(text).then(
      () => setMessage(t("inspire.copied")),
      () => setMessage(t("inspire.copyFail")),
    );
  }

  if (!session) {
    return <EmptyState title={t("inspire.loading")} description="" />;
  }

  const promptOverlay =
    showPromptPreview && promptPreview
      ? createPortal(
          <div
            className="hb-inspire-prompt-overlay"
            role="dialog"
            aria-modal="true"
            aria-label={t("inspire.previewTitle")}
            onClick={(e) => {
              if (e.target === e.currentTarget) setShowPromptPreview(false);
            }}
            onKeyDown={(e) => {
              if (e.key === "Escape") setShowPromptPreview(false);
            }}
          >
            <div className="hb-inspire-prompt-modal">
              <div className="hb-inspire-prompt-modal__head">
                <div className="hb-inspire-prompt-modal__head-text">
                  <strong>{t("inspire.previewTitle")}</strong>
                  <p className="hb-inspire-prompt-modal__fp">
                    mode=<code>{promptPreview.mode}</code>
                    {" · "}
                    fingerprint=<code>{promptPreview.fingerprint || "—"}</code>
                    {" · "}
                    {promptPreview.runeCount} {t("inspire.runes")}
                    {" / "}
                    {promptPreview.charCount} bytes
                    {lastSent?.fingerprint ? (
                      <>
                        {" · "}
                        {lastSent.fingerprint === promptPreview.fingerprint ? (
                          <span className="hb-inspire-fp-bar__ok">{t("inspire.matchOk")}</span>
                        ) : (
                          <span className="hb-inspire-fp-bar__bad">
                            {t("inspire.matchBadShort", { sent: lastSent.fingerprint })}
                          </span>
                        )}
                      </>
                    ) : null}
                  </p>
                </div>
                <div className="hb-inspire-prompt-modal__actions">
                  <button
                    type="button"
                    className="hb-inspire-tool-btn"
                    onClick={() => copyPromptText(promptPreview.prompt)}
                  >
                    {t("inspire.copyAll")}
                  </button>
                  <button
                    type="button"
                    className="hb-inspire-tool-btn"
                    onClick={() => setShowPromptPreview(false)}
                  >
                    {t("common.cancel")}
                  </button>
                </div>
              </div>
              <p className="hb-inspire-prompt-modal__how">{t("inspire.verifyHowShort")}</p>
              <div className="hb-inspire-prompt-modal__scroll">
                {promptPreview.blocks.length > 0
                  ? promptPreview.blocks.map((blk, i) => (
                      <section key={`${blk.title}-${i}`} className="hb-inspire-prompt-block">
                        <header className="hb-inspire-prompt-block__title">
                          <span>【{blk.title}】</span>
                          <span className="text-muted">
                            {Array.from(blk.body).length} {t("inspire.runes")}
                          </span>
                        </header>
                        <pre className="hb-inspire-prompt-block__body">{blk.body}</pre>
                      </section>
                    ))
                  : (
                    <pre className="hb-inspire-prompt-block__body">{promptPreview.prompt}</pre>
                  )}
              </div>
            </div>
          </div>,
          document.body,
        )
      : null;

  return (
    <>
    <div className={`hb-inspire-chat ${showLibrary ? "" : "is-library-collapsed"}`}>
      {/* 左：主線＝對話 + 輸入 + 產文 */}
      <div className="hb-inspire-chat__main">
        <div className="hb-inspire-chat__top">
          <div className="hb-inspire-session-bar" aria-label={t("inspire.sessionsAria")}>
            <select
              id="hb-inspire-session-select"
              className="hb-inspire-session-select"
              value={session?.id || ""}
              disabled={Boolean(busy)}
              onChange={(e) => void switchSession(e.target.value)}
              title={sessionLabel}
              aria-label={t("inspire.session")}
            >
              {sessionList.length === 0 && session ? (
                <option value={session.id}>{sessionLabel}</option>
              ) : null}
              {sessionList.map((s) => (
                <option key={s.id} value={s.id}>
                  {s.active ? "● " : ""}
                  {s.title || t("inspire.sessionNew")}
                  {s.message_count ? ` · ${s.message_count}` : ""}
                </option>
              ))}
            </select>
            <div className="hb-inspire-session-bar__tools">
              <button
                type="button"
                className="hb-inspire-tool-btn"
                disabled={Boolean(busy)}
                onClick={() => void newChat()}
              >
                {busy === "new" ? "…" : t("inspire.newSession")}
              </button>
            </div>
          </div>
          {showTrends ? (
            <div className="hb-inspire-trends" aria-label={t("inspire.trendsAria")}>
              <div className="hb-inspire-trends__head">
                <div className="hb-inspire-trends__title-block">
                  <span className="hb-inspire-trends__label">{t("inspire.trendsLabel")}</span>
                  <span className="hb-inspire-trends__hint">{t("inspire.trendsHint")}</span>
                </div>
                <div className="hb-inspire-trends__tools">
                  <button
                    type="button"
                    className="hb-inspire-tool-btn"
                    onClick={() => void refreshTrends()}
                    disabled={busy === "trends"}
                  >
                    {busy === "trends" ? "…" : t("inspire.refresh")}
                  </button>
                </div>
              </div>
              <div className="hb-inspire-trends__chips">
                {trends.length === 0 ? (
                  <span className="hb-inspire-trends__empty">{t("inspire.trendsEmpty")}</span>
                ) : null}
                {trends.map((trend, idx) => {
                  const isSeed = trend.source_label === "seed";
                  const label =
                    trend.label.length > 14 ? `${trend.label.slice(0, 14)}…` : trend.label;
                  const tip = [trend.summary, trend.source_label ? t("inspire.source", { label: trend.source_label }) : ""]
                    .filter(Boolean)
                    .join(" · ");
                  return (
                    <div key={trend.id} className="hb-inspire-trend-chip-wrap">
                      <button
                        type="button"
                        className={`hb-inspire-trend-chip ${isSeed ? "is-seed" : ""}`}
                        title={tip}
                        onClick={() => void applyTrendChip(trend)}
                      >
                        <span className="hb-inspire-trend-chip__rank">{idx + 1}</span>
                        <span>{label}</span>
                        {isSeed ? (
                          <span className="hb-inspire-trend-chip__seed">{t("inspire.trendsSeed")}</span>
                        ) : null}
                      </button>
                      <button
                        type="button"
                        className="hb-inspire-trend-chip__pin"
                        title={t("inspire.pinAsElement")}
                        onClick={() => void pinTrendAsElement(trend)}
                      >
                        +
                      </button>
                    </div>
                  );
                })}
              </div>
            </div>
          ) : null}
          {message ? (
            <p className="hb-banner-ok hb-inspire-status" role="status">
              {message}
            </p>
          ) : null}
          {error ? (
            <p className="hb-form-error hb-inspire-status" role="alert">
              {error}
            </p>
          ) : null}
        </div>

        <div className="hb-inspire-messages" role="log" aria-live="polite">
          <p className="hb-inspire-flow-one-liner" role="note">
            {t("inspire.flowOneLiner")}
          </p>
          {isChatEmpty ? (
            <div className="hb-inspire-empty-guide">
              <p className="hb-inspire-empty-guide__title">{t("inspire.emptyTitle")}</p>
              <p className="hb-inspire-empty-guide__desc">{t("inspire.emptyDesc")}</p>
              <div className="hb-inspire-empty-guide__actions">
                <Button type="button" onClick={() => startFromTopic()}>
                  {t("inspire.entryTopic")}
                </Button>
                <Button type="button" variant="ghost" onClick={() => startFromPasteDraft()}>
                  {t("inspire.entryPaste")}
                </Button>
              </div>
            </div>
          ) : null}
          {showReadyToWrite ? (
            <div className="hb-inspire-ready-hint" role="status">
              <span>{t("inspire.readyToWrite", { n: ideationTurns })}</span>
              <button
                type="button"
                className="hb-inspire-tool-btn hb-inspire-tool-btn--primary"
                disabled={Boolean(busy)}
                onClick={() => void generatePost()}
              >
                {t("inspire.generate")}
              </button>
            </div>
          ) : null}
          {session.messages.map((m) => (
            <div
              key={m.id}
              className={`hb-inspire-msg hb-inspire-msg--${m.role}`}
            >
              <div className="hb-inspire-msg__role">
                {m.role === "user"
                  ? t("inspire.you")
                  : m.role === "assistant"
                    ? t("inspire.ai")
                    : t("inspire.system")}
              </div>
              {m.role === "assistant" || m.role === "system" ? (
                <MarkdownText text={m.text} className="hb-inspire-msg__md" />
              ) : (
                <div className="hb-inspire-msg__text">{m.text}</div>
              )}
              {m.draft?.body ? (
                <div className="hb-inspire-draft">
                  <pre className="hb-inspire-draft__body">{m.draft.body}</pre>
                  <div className="hb-wizard-actions">
                    <Button type="button" onClick={() => goWrite(m.draft!.body)}>
                      {t("inspire.useDraft")}
                    </Button>
                    <Button type="button" variant="ghost" onClick={() => openPlay(m.draft!.body)}>
                      {t("inspire.openPlay")}
                    </Button>
                  </div>
                </div>
              ) : null}
            </div>
          ))}
          {streamingText ? (
            <div className="hb-inspire-msg hb-inspire-msg--assistant">
              <div className="hb-inspire-msg__role">{t("inspire.ai")}</div>
              <div className="hb-inspire-msg__stream">
                <MarkdownText text={streamingText} className="hb-inspire-msg__md" />
                <span className="hb-inspire-cursor" aria-hidden>
                  ▍
                </span>
              </div>
            </div>
          ) : null}
          {(busy === "chat" || busy === "generate") && !streamingText ? (
            <div className="hb-inspire-msg hb-inspire-msg--assistant">
              <div className="hb-inspire-msg__role">{t("inspire.ai")}</div>
              <div className="hb-inspire-msg__text text-muted">
                {busy === "generate" ? t("inspire.generating") : t("inspire.thinking")}
              </div>
            </div>
          ) : null}
          {busy === "chat" || busy === "generate" ? (
            <div className="hb-inspire-stop">
              <Button type="button" variant="ghost" onClick={stopStream}>
                {t("inspire.stop")}
              </Button>
            </div>
          ) : null}
          <div ref={bottomRef} />
        </div>

        <div className="hb-inspire-chat__bottom">
          {/* 有 pin 才顯示，避免空狀態噪音 */}
          {pinnedElements.length > 0 ? (
          <div className="hb-inspire-pinned" aria-label={t("inspire.pinnedAria")}>
            <span className="hb-inspire-pinned__label">
              {t("inspire.pinned")}
              <span className="hb-inspire-pinned__count">
                {t("inspire.pinnedCount", { n: pinnedElements.length })}
              </span>
              <span className="text-muted"> · {t("inspire.pinsLocalShort")}</span>
            </span>
            {pinnedElements.map((el) => (
              <div key={el.id} className="hb-inspire-pin-chip is-on">
                <button
                  type="button"
                  className="hb-inspire-pin-chip__main"
                  onClick={() => insertPinToInput(el)}
                  title={t("inspire.insertPinTitle")}
                >
                  {el.title}
                </button>
                <button
                  type="button"
                  className="hb-inspire-pin-chip__x"
                  onClick={() => togglePin(el.id)}
                  title={t("inspire.unpinTitle")}
                  aria-label={t("inspire.unpinTitle")}
                >
                  <AppIcon name="close" size={14} reverse />
                </button>
              </div>
            ))}
          </div>
          ) : null}

          <div className="hb-inspire-composer">
            <div className="hb-inspire-composer__tools">
              <button
                type="button"
                className="hb-inspire-tool-btn hb-inspire-tool-btn--primary"
                title={t("inspire.generateHint")}
                onClick={() => void generatePost()}
                disabled={Boolean(busy) || ideationTurns === 0 || !personaReady}
              >
                {busy === "generate" ? t("inspire.generating") : t("inspire.generate")}
              </button>
              <button
                type="button"
                className={`hb-inspire-tool-btn ${useWeb ? "is-on" : ""}`}
                disabled={Boolean(busy)}
                title={t("inspire.webSearchHint")}
                onClick={() => setUseWeb((v) => !v)}
              >
                {useWeb ? t("inspire.webSearchOn") : t("inspire.webSearch")}
              </button>
              <button
                type="button"
                className="hb-inspire-tool-btn"
                disabled={Boolean(busy)}
                onClick={() => setShowAdvanced((v) => !v)}
              >
                {showAdvanced ? t("inspire.hideAdvanced") : t("inspire.showAdvanced")}
              </button>
              {showAdvanced ? (
                <>
                  <button
                    type="button"
                    className={`hb-inspire-tool-btn ${showTrends ? "is-on" : ""}`}
                    onClick={() => setShowTrends((v) => !v)}
                  >
                    {t("inspire.showTopics")}
                  </button>
                  <button
                    type="button"
                    className={`hb-inspire-tool-btn ${showLibrary ? "is-on" : ""}`}
                    onClick={() => setShowLibrary((v) => !v)}
                  >
                    {t("inspire.showLibrary")}
                  </button>
                  <button
                    type="button"
                    className="hb-inspire-tool-btn"
                    disabled={Boolean(busy) || !session}
                    onClick={() => void deleteCurrentSession()}
                  >
                    {t("inspire.deleteSessionShort")}
                  </button>
                  <button
                    type="button"
                    className="hb-inspire-tool-btn"
                    disabled={Boolean(busy)}
                    title={t("inspire.previewTitle")}
                    aria-label={t("inspire.previewTitle")}
                    onClick={() => void togglePromptPreview("chat")}
                  >
                    {busy === "preview" ? "…" : showPromptPreview ? "×" : "?"}
                  </button>
                  {lastSent?.prompt ? (
                    <button
                      type="button"
                      className="hb-inspire-tool-btn"
                      disabled={Boolean(busy)}
                      title={t("inspire.viewSent")}
                      onClick={() => {
                        setPromptPreview({
                          prompt: lastSent.prompt,
                          mode: lastSent.mode,
                          sections: [],
                          blocks: [{ title: t("inspire.sentPrompt"), body: lastSent.prompt }],
                          fingerprint: lastSent.fingerprint,
                          charCount: lastSent.charCount,
                          runeCount: lastSent.runeCount,
                          note: t("inspire.sentPromptNote"),
                          message: lastSent.message,
                        });
                        setShowPromptPreview(true);
                      }}
                    >
                      {t("inspire.viewSentShort")}
                    </button>
                  ) : null}
                </>
              ) : null}
            </div>
            <div className="hb-inspire-composer__row">
              <Textarea
                label=""
                aria-label={t("inspire.inputAria")}
                value={input}
                onChange={(e) => setInput(e.target.value)}
                rows={1}
                placeholder={t("inspire.inputPh")}
                onKeyDown={(e) => {
                  if (e.key === "Enter" && !e.shiftKey && !e.nativeEvent.isComposing) {
                    e.preventDefault();
                    if (!busy) void send("chat");
                  }
                }}
              />
              <button
                type="button"
                className="hb-inspire-send-btn"
                disabled={Boolean(busy) || !input.trim()}
                title={t("inspire.send")}
                aria-label={t("inspire.send")}
                onClick={() => void send("chat")}
              >
                {busy === "chat" ? (
                  <span className="hb-inspire-send-btn__dots" aria-hidden>
                    …
                  </span>
                ) : (
                  <AppIcon name="send" size={18} />
                )}
              </button>
            </div>
          </div>
        </div>
      </div>

      {/* 右：元素庫，預設收合 */}
      {showLibrary ? (
      <aside className="hb-inspire-library">
        <div className="hb-inspire-library__head">
          <strong>{t("inspire.library")}</strong>
          <div className="hb-inspire-library__head-actions">
            <Button type="button" variant="ghost" onClick={() => setShowAdd((v) => !v)}>
              {showAdd ? t("common.cancel") : t("inspire.addNew")}
            </Button>
            <Button type="button" variant="ghost" onClick={() => setShowLibrary(false)}>
              {t("inspire.hideLibrary")}
            </Button>
          </div>
        </div>

        <div className="hb-inspire-library__scroll">
          {showAdd ? (
            <div className="hb-inspire-add">
              <Select
                label={t("inspire.kind")}
                value={newKind === "role" || newKind === "snippet" || newKind === "trend" ? newKind : "snippet"}
                onChange={(e) => setNewKind(e.target.value as InspireElementKind)}
              >
                <option value="role">{t("inspire.kindRole")}</option>
                <option value="snippet">{t("inspire.kindSnippet")}</option>
                <option value="trend">{t("inspire.kindTrendNote")}</option>
              </Select>
              <Input
                label={t("inspire.name")}
                value={newTitle}
                onChange={(e) => setNewTitle(e.target.value)}
                placeholder={t("inspire.namePh")}
              />
              <Textarea
                label={t("inspire.body")}
                value={newBody}
                onChange={(e) => setNewBody(e.target.value)}
                rows={3}
                placeholder={t("inspire.bodyPh")}
              />
              <Button type="button" onClick={() => void addElement()} disabled={busy === "add"}>
                {t("inspire.saveElement")}
              </Button>
            </div>
          ) : null}

          <div className="hb-inspire-lib-section">
            <p className="hb-inspire-lib-section__title">{t("inspire.citeBrand")}</p>
            <div className="hb-inspire-lib-list">
              {brands.map((b) => {
                const id = `el_brand_${b.id}`;
                const on = pinnedIds.includes(id);
                return (
                  <button
                    key={b.id}
                    type="button"
                    className={`hb-inspire-el ${on ? "is-pinned" : ""}`}
                    onClick={() => void pinBrand(b)}
                  >
                    <span className="hb-inspire-el__title">{b.display_name}</span>
                    <span className="hb-inspire-el__meta">{on ? t("inspire.applied") : t("inspire.clickApply")}</span>
                  </button>
                );
              })}
              {brands.length === 0 ? (
                <p className="text-muted" style={{ fontSize: "var(--hb-text-xs)", margin: 0 }}>
                  {t("inspire.noBrands")}
                </p>
              ) : null}
            </div>
          </div>

          {KIND_ORDER.map((kind) => {
            if (kind === "brand") {
              // 品牌已用上方「引用品牌」
              return null;
            }
            const list = grouped.get(kind) || [];
            if (!list.length) return null;
            return (
              <div key={kind} className="hb-inspire-lib-section">
                <p className="hb-inspire-lib-section__title">{kindLabel(kind, t)}</p>
                <div className="hb-inspire-lib-list">
                  {list.map((el) => (
                    <ElementRow
                      key={el.id}
                      el={el}
                      pinned={pinnedIds.includes(el.id)}
                      onToggle={() => togglePin(el.id)}
                      onRemove={() => void removeElement(el.id)}
                    />
                  ))}
                </div>
              </div>
            );
          })}
        </div>
      </aside>
      ) : null}

    </div>
    {promptOverlay}
    </>
  );
}

function ElementRow({
  el,
  pinned,
  onToggle,
  onRemove,
}: {
  el: InspireElement;
  pinned: boolean;
  onToggle: () => void;
  onRemove: () => void;
}) {
  const { t } = useI18n();
  return (
    <div className={`hb-inspire-el-row ${pinned ? "is-pinned" : ""}`}>
      <button type="button" className="hb-inspire-el" onClick={onToggle}>
        <span className="hb-inspire-el__title">{el.title}</span>
        <span className="hb-inspire-el__meta">
          {pinned ? t("inspire.appliedToggle") : el.body.slice(0, 48)}
          {el.body.length > 48 ? "…" : ""}
        </span>
      </button>
      <button
        type="button"
        className="hb-inspire-el__del"
        aria-label={t("inspire.deleteAria")}
        onClick={onRemove}
      >
        <AppIcon name="close" size={16} reverse />
      </button>
    </div>
  );
}
