import { useEffect, useEffectEvent, useMemo, useState } from "react";
import { Link } from "react-router-dom";
import { PageHeader } from "../components/layout/PageHeader";
import { Badge, Button, Card, EmptyState, Input, Pager, Select, Textarea } from "../components/ui";
import { pageSlice } from "../lib/pagination";
import { useData, useRepos } from "../data/DataContext";
import type {
  Brand,
  BrandProduct,
  ScoutHomeworkRecord,
  ScoutPost,
  ScoutPurpose,
  ScoutResearchNote,
  ScoutResearchTier,
  ScoutRunBrief,
} from "../domain/types";
import {
  groupNotesByTier,
  noteLearnPoints,
  noteReplyHooks,
  formatResearchLink,
} from "../lib/mockResearch";
import { newId } from "../lib/id";
import { allowHttpUrl } from "../lib/externalUrl";
import { bumpScoutTodayDone, loadScoutToday, saveScoutToday } from "../lib/scoutToday";
import { nowUnixNano } from "../lib/time";
import { useI18n } from "../i18n/I18nContext";
import { useJobLive } from "../data/JobLiveContext";
import { jobStatusLabel, jobStatusTone } from "../lib/jobLabels";
import { formatLocalDateTime } from "../lib/time";
import type { Job } from "../domain/types";

function isPending(p: ScoutPost): boolean {
  return p.outreach_status === "new" || p.outreach_status === "drafted";
}

function outreachStatusLabel(
  p: ScoutPost,
  t: (k: string, p?: Record<string, string | number>) => string,
): string {
  return t(`scout.status.${p.outreach_status}`);
}

function MatchQueue({
  title,
  posts,
  page,
  onPageChange,
  currentId,
  onSelect,
  t,
}: {
  title: string;
  posts: ScoutPost[];
  page: number;
  onPageChange: (page: number) => void;
  currentId: string | null;
  onSelect: (id: string) => void;
  t: (k: string, p?: Record<string, string | number>) => string;
}) {
  const pageSize = 8;
  return (
    <Card title={title}>
      {posts.length === 0 ? (
        <EmptyState title={t("scout.noMatchesInQueue")} />
      ) : (
        <div className="hb-stack">
          {pageSlice(posts, page, pageSize).map((p) => (
            <button
              key={p.id}
              type="button"
              className={`hb-scout-queue-item${currentId === p.id ? " is-active" : ""}`}
              onClick={() => onSelect(p.id)}
            >
              <span className="hb-scout-queue-item__meta">
                @{p.author} · {p.search_tag} · {Math.round(p.score)}
                <Badge tone={isPending(p) ? "brand" : "neutral"}>{outreachStatusLabel(p, t)}</Badge>
              </span>
              <span className="hb-scout-queue-item__text">
                {p.text.slice(0, 72)}
                {p.text.length > 72 ? "…" : ""}
              </span>
            </button>
          ))}
          <Pager total={posts.length} page={page} pageSize={pageSize} onPageChange={onPageChange} />
        </div>
      )}
    </Card>
  );
}

function stanceOf(p: ScoutPost, t: (k: string, p?: Record<string, string | number>) => string): string {
  if (p.scout_mode === "activity") return t("scout.stanceActivity");
  if (p.scout_mode === "product" || p.matched_product_label) return t("scout.stanceProduct");
  return t("scout.stanceRelation");
}

/** 每次海巡批次的分組 key（與 theme_key 對齊） */
function postRunKey(p: ScoutPost): string {
  if (p.theme_key) return p.theme_key;
  if (p.matched_product_id) return `product|${p.matched_product_id}`;
  if (p.intent_snippet) return `intent|${p.intent_snippet}`;
  if (p.scout_mode === "activity") return `activity|${p.search_tag || "x"}`;
  return `tag|${p.search_tag || "other"}`;
}

function postRunLabel(p: ScoutPost, t: (k: string, p?: Record<string, string | number>) => string): string {
  return (
    p.theme_label ||
    p.matched_product_label ||
    p.intent_snippet ||
    p.search_tag ||
    t("scout.unnamedRun")
  );
}

function runTimeSuffix(): string {
  const d = new Date();
  const hh = String(d.getHours()).padStart(2, "0");
  const mm = String(d.getMinutes()).padStart(2, "0");
  return `${hh}:${mm}`;
}

/** 批次名過長時截斷（chip／標題用） */
function shortRunLabel(label: string, max = 20): string {
  const t = (label || "").trim();
  if (t.length <= max) return t;
  return `${t.slice(0, Math.max(1, max - 1))}…`;
}

/** 知識圖譜節點卡：學習重點 + 回帖鉤子 + 來源 */
function KnowledgeNoteCard({
  note,
  badge,
  compact,
}: {
  note: ScoutResearchNote;
  badge?: string;
  compact?: boolean;
}) {
  const { t } = useI18n();
  const link = formatResearchLink(note.url, note.source_label);
  const points = noteLearnPoints(note);
  const hooks = noteReplyHooks(note);
  const relationLabel = note.relation ? t(`scout.relation.${note.relation}`) : null;
  const showPoints = compact ? points.slice(0, 3) : points;
  const showHooks = compact ? hooks.slice(0, 1) : hooks;
  const safeUrl = allowHttpUrl(note.url);

  return (
    <li className={`hb-scout-research__card${compact ? " is-preview" : ""}`}>
      <div className="hb-scout-research__head">
        <strong>{note.title}</strong>
        {badge ? <Badge tone="success">{badge}</Badge> : null}
      </div>
      {relationLabel ? (
        <span className="hb-scout-research__relation">{relationLabel}</span>
      ) : null}
      {showPoints.length > 0 ? (
        <div className="hb-scout-research__block">
          <p className="hb-scout-research__block-label">{t("scout.learnPoints")}</p>
          <ul className="hb-scout-research__points">
            {showPoints.map((pt) => (
              <li key={pt}>{pt}</li>
            ))}
          </ul>
        </div>
      ) : note.summary ? (
        <p
          className={`hb-scout-research__summary${compact ? " hb-scout-research__summary--clamp" : ""}`}
        >
          {note.summary}
        </p>
      ) : null}
      {showHooks.length > 0 ? (
        <div className="hb-scout-research__block">
          <p className="hb-scout-research__block-label">{t("scout.replyHooks")}</p>
          <ul className="hb-scout-research__hooks">
            {showHooks.map((h) => (
              <li key={h}>「{h}」</li>
            ))}
          </ul>
        </div>
      ) : null}
      {safeUrl ? (
        <a
          className="hb-scout-research__link"
          href={safeUrl}
          target="_blank"
          rel="noreferrer"
          title={safeUrl}
        >
          <span className="hb-scout-research__link-label">{link.label}</span>
          <span className="hb-scout-research__link-host">{link.host}</span>
        </a>
      ) : null}
    </li>
  );
}

/**
 * 海巡：今日目標 + 現在這一則 + 收合功課／佇列
 */
export function ScoutPage() {
  const repos = useRepos();
  const { tick } = useData();
  const { t } = useI18n();
  const { jobs: liveJobs, reload: reloadJobs } = useJobLive();

  const [brands, setBrands] = useState<Brand[]>([]);
  const [allProducts, setAllProducts] = useState<BrandProduct[]>([]);
  const [posts, setPosts] = useState<ScoutPost[]>([]);

  const [purpose, setPurpose] = useState<ScoutPurpose>("value");
  const [intent, setIntent] = useState("");
  const [productId, setProductId] = useState("");

  const [goal, setGoal] = useState(8);
  const [todayDone, setTodayDone] = useState(0);

  const [currentId, setCurrentId] = useState<string | null>(null);
  const [draftText, setDraftText] = useState("");

  const [report, setReport] = useState<ScoutRunBrief | null>(null);
  const [reportOpen, setReportOpen] = useState(false);
  const [valueQueuePage, setValueQueuePage] = useState(1);
  const [activityQueuePage, setActivityQueuePage] = useState(1);
  const [researchTier, setResearchTier] = useState<"all" | ScoutResearchTier>("all");
  const [homeworkList, setHomeworkList] = useState<ScoutHomeworkRecord[]>([]);
  /** 目前檢視的海巡批次（每次按開始 = 一筆） */
  const [activeRunKey, setActiveRunKey] = useState<string | null>(null);

  const [busy, setBusy] = useState("");
  const [message, setMessage] = useState("");
  const [convAmount, setConvAmount] = useState("");
  const [convNote, setConvNote] = useState("");
  const [scanJob, setScanJob] = useState<Job | null>(null);
  const [scanJobThemeKey, setScanJobThemeKey] = useState<string | null>(null);
  const [crawlerSessionRequired, setCrawlerSessionRequired] = useState(false);
  const [loadError, setLoadError] = useState("");

  useEffect(() => {
    const t = loadScoutToday();
    setTodayDone(t.done);
    setGoal(purpose === "activity" ? t.goalActivity : t.goalValue);
  }, [purpose]);

  useEffect(() => {
    void (async () => {
      try {
        const [b, hw, prods, postList] = await Promise.all([
          repos.scout.listBrands(),
          repos.scout.listHomework(),
          repos.scout.listAllProducts(),
          repos.scout.listPosts(),
        ]);
        setBrands(b);
        setHomeworkList(hw);
        setAllProducts(prods);
        setPosts(postList);

        const pending = postList.filter(isPending).sort((a, b) => (b.score || 0) - (a.score || 0));
        if (pending[0]) setActiveRunKey((cur) => cur || postRunKey(pending[0]!));
        setLoadError("");
      } catch (e) {
        setLoadError(e instanceof Error ? e.message : t("scout.loadFail"));
      }
    })();
  }, [repos, t, tick]);

  const productOptions = useMemo(() => {
    return allProducts.map((p) => {
      const brand = brands.find((b) => b.id === p.brand_id);
      return {
        id: p.id,
        label: `${brand?.display_name || t("scout.brandFallback")} · ${p.label}`,
      };
    });
  }, [allProducts, brands, t]);

  const selectedProduct = useMemo(
    () => allProducts.find((p) => p.id === productId) || null,
    [allProducts, productId],
  );

  /** 每次搜尋一筆：依 posts 出現順序（新掃在前） */
  const runGroups = useMemo(() => {
    const order: string[] = [];
    const map = new Map<
      string,
      { key: string; label: string; pending: number; total: number; mode?: string }
    >();
    for (const p of posts) {
      const key = postRunKey(p);
      let g = map.get(key);
      if (!g) {
        g = {
          key,
          label: postRunLabel(p, t),
          pending: 0,
          total: 0,
          mode: p.scout_mode,
        };
        map.set(key, g);
        order.push(key);
      }
      g.total += 1;
      if (isPending(p)) g.pending += 1;
      if (!g.label || g.label === t("scout.unnamedRun")) g.label = postRunLabel(p, t);
    }
    // 有功課但命中已清空的批次也列出來（可刪）
    for (const h of homeworkList) {
      if (map.has(h.theme_key)) continue;
      order.push(h.theme_key);
      map.set(h.theme_key, {
        key: h.theme_key,
        label: h.theme_label,
        pending: 0,
        total: 0,
        mode: h.brief.mode,
      });
    }
    return order.map((k) => map.get(k)!).filter(Boolean);
  }, [posts, homeworkList, t]);

  const activeRunMeta = useMemo(
    () => runGroups.find((g) => g.key === activeRunKey) || null,
    [runGroups, activeRunKey],
  );

  const runQueue = useMemo(() => {
    return posts
      .filter((p) => !activeRunKey || postRunKey(p) === activeRunKey)
      .slice()
      .sort((a, b) => Number(isPending(b)) - Number(isPending(a)) || (b.score || 0) - (a.score || 0));
  }, [posts, activeRunKey]);

  const pendingQueue = useMemo(() => runQueue.filter(isPending), [runQueue]);
  const valueQueue = useMemo(() => runQueue.filter((p) => p.scout_mode !== "activity"), [runQueue]);
  const activityQueue = useMemo(() => runQueue.filter((p) => p.scout_mode === "activity"), [runQueue]);

  const current = useMemo(
    () => posts.find((p) => p.id === currentId) || null,
    [posts, currentId],
  );

  const visibleScanJob = useMemo(
    () => (scanJob ? liveJobs.find((job) => job.id === scanJob.id) || scanJob : null),
    [liveJobs, scanJob],
  );

  const pollScanJob = useEffectEvent(async (jobId: string, cancelled: () => boolean) => {
    try {
      await reloadJobs();
      const job = await repos.jobs.get(jobId);
      if (cancelled() || !job) return;
      setScanJob((previous) =>
        previous?.id === job.id &&
        previous.status === job.status &&
        previous.updated_at === job.updated_at
          ? previous
          : job,
      );
      if (job.status === "succeeded") {
        const list = await repos.scout.listPosts();
        if (cancelled()) return;
        setPosts(list);
        const completedThemeKey = scanJobThemeKey || job.ref_id || activeRunKey;
        const pending = list
          .filter((p) => postRunKey(p) === completedThemeKey && isPending(p))
          .sort((a, b) => (b.score || 0) - (a.score || 0));
        setMessage(t("scout.scanReady", { n: pending.length }));
      } else if (job.status === "failed" || job.status === "cancelled") {
        setMessage(job.error || t("scout.patrolFail"));
        setCrawlerSessionRequired(/crawler.?session|chrome session/i.test(job.error || ""));
      } else if (
        job.status === "queued" &&
        Number(job.created_at || 0) > 0 &&
        Date.now() - Number(job.created_at) / 1_000_000 > 12_000
      ) {
        setMessage(t("scout.workerWaiting"));
      }
    } catch {
      // The shared JobLiveContext keeps refreshing; leave the last known status visible.
    }
  });

  const scanJobId = scanJob?.id;
  const scanJobStatus = scanJob?.status;

  useEffect(() => {
    if (!scanJobId || !scanJobStatus || ["succeeded", "failed", "cancelled"].includes(scanJobStatus)) return;
    let cancelled = false;
    const poll = () => pollScanJob(scanJobId, () => cancelled);
    void poll();
    const id = window.setInterval(() => void poll(), 1500);
    return () => {
      cancelled = true;
      window.clearInterval(id);
    };
  }, [scanJobId, scanJobStatus]);

  // 選取貼文只帶入草稿；批次由使用者明確選取，不能反向切換。
  useEffect(() => {
    if (!current) {
      setDraftText("");
      return;
    }
    setDraftText(current.draft_text || "");
  }, [current?.id]); // eslint-disable-line react-hooks/exhaustive-deps

  function pickNext(afterId?: string | null, list?: ScoutPost[], runKey?: string | null) {
    const key = runKey === undefined ? activeRunKey : runKey;
    let src = (list || posts).filter(isPending);
    if (key) src = src.filter((p) => postRunKey(p) === key);
    src = src.slice().sort((a, b) => (b.score || 0) - (a.score || 0));
    if (!src.length) {
      setCurrentId(null);
      return;
    }
    if (!afterId) {
      setCurrentId(src[0]!.id);
      return;
    }
    const idx = src.findIndex((p) => p.id === afterId);
    const next = src[idx + 1] || src.find((p) => p.id !== afterId) || null;
    setCurrentId(next?.id || null);
  }

  function selectRun(key: string) {
    setActiveRunKey(key);
    setValueQueuePage(1);
    setActivityQueuePage(1);
    const hw = homeworkList.find((h) => h.theme_key === key);
    if (hw) {
      setReport(hw.brief);
      setResearchTier(
        hw.brief.research_notes?.some((n) => (n.tier || "core") === "core") ? "core" : "all",
      );
    } else {
      setReport(null);
    }
    const pending = posts
      .filter((p) => postRunKey(p) === key && isPending(p))
      .sort((a, b) => (b.score || 0) - (a.score || 0));
    setCurrentId(pending[0]?.id || null);
  }

  async function removeRun(key: string) {
    const g = runGroups.find((x) => x.key === key);
    const label = g?.label || t("scout.thisRun");
    if (
      !window.confirm(t("scout.confirmDeleteRun", { label }))
    ) {
      return;
    }
    setBusy("del-run");
    try {
      await repos.scout.removeTheme(key);
      const list = await reloadPosts();
      const hw = await repos.scout.listHomework();
      setHomeworkList(hw);
      if (activeRunKey === key) {
        setActiveRunKey(null);
        setReport(null);
        setCurrentId(null);
      } else if (currentId && !list.some((p) => p.id === currentId)) {
        pickNext(currentId, list, activeRunKey);
      }
      setMessage(t("scout.deletedRun", { label }));
    } catch (e) {
      setMessage(e instanceof Error ? e.message : t("scout.deleteRunFail"));
    } finally {
      setBusy("");
    }
  }

  function setGoalPersist(n: number) {
    const g = Math.max(1, Math.min(99, n));
    setGoal(g);
    const t = loadScoutToday();
    if (purpose === "activity") {
      saveScoutToday({ ...t, goalActivity: g });
    } else {
      saveScoutToday({ ...t, goalValue: g });
    }
  }

  async function startPatrol() {
    const text = intent.trim();
    if (!text) {
      setMessage(purpose === "activity" ? t("scout.needKeyword") : t("scout.needIntent"));
      return;
    }
    setBusy("run");
    setMessage("");
    setCrawlerSessionRequired(false);
    try {
      const freshProducts = await repos.scout.listAllProducts();
      setAllProducts(freshProducts);
      const selected =
        purpose === "activity" ? null : freshProducts.find((p) => p.id === productId) || null;
      if (purpose === "value" && productId && !selected) {
        throw new Error(t("scout.productMissing"));
      }
      // ① 輕量 brief → 立刻海巡；每次按開始 = 獨立批次（unique theme_key）
      const brief = await repos.scout.prepareBrief({
        intent: text,
        brandId: purpose === "activity" ? null : selected?.brand_id || null,
        productId: purpose === "activity" ? null : selected?.id || null,
        purpose,
        deep: false,
      });
      const baseLabel =
        brief.theme_label || brief.product_label || brief.intent.slice(0, 36) || t("scout.defaultLabel");
      const theme_key = newId("run");
      const theme_label = `${baseLabel} · ${runTimeSuffix()}`;
      const briefSaved: ScoutRunBrief = { ...brief, theme_key, theme_label };

      const { job } = await repos.scout.runScanFromBrief({
        ...briefSaved,
        scan_terms: briefSaved.scan_terms,
      });
      await repos.scout.saveHomework({
        theme_key,
        theme_label,
        purpose,
        brief: briefSaved,
        created_at: nowUnixNano(),
      });
      setHomeworkList(await repos.scout.listHomework());
      setScanJob(job);
      setScanJobThemeKey(theme_key);
      void reloadJobs();
      setMessage(t("scout.scanQueued", { label: theme_label }));

    } catch (e) {
      setMessage(e instanceof Error ? e.message : t("scout.patrolFail"));
      setCrawlerSessionRequired(
        typeof e === "object" && e !== null && "code" in e && (e as { code?: number }).code === 400061,
      );
      setBusy("");
    } finally {
      setBusy("");
    }
  }

  async function reloadPosts(): Promise<ScoutPost[]> {
    const list = await repos.scout.listPosts();
    setPosts(list);
    return list;
  }

  async function regenDraft() {
    if (!current) return;
    setBusy("draft");
    try {
      const next = await repos.scout.draftOutreach(current.id);
      await reloadPosts();
      setDraftText(next.draft_text || "");
    } catch (e) {
      setMessage(e instanceof Error ? e.message : t("scout.draftFail"));
    } finally {
      setBusy("");
    }
  }

  async function skipCurrent() {
    if (!current) return;
    const id = current.id;
    setBusy("skip");
    try {
      await repos.scout.skipOutreach(id);
      const list = await reloadPosts();
      pickNext(id, list);
      setTodayDone(bumpScoutTodayDone().done);
      setMessage(t("scout.skipped"));
    } catch (e) {
      setMessage(e instanceof Error ? e.message : t("scout.patrolFail"));
    } finally {
      setBusy("");
    }
  }

  function openThreadsReply() {
    if (!current) return;
    const url = allowHttpUrl(current.permalink);
    if (!url) {
      setMessage(t("scout.noPermalink"));
      return;
    }
    window.open(url, "_blank", "noopener,noreferrer");
    const text = draftText.trim();
    if (text && navigator.clipboard?.writeText) {
      void navigator.clipboard.writeText(text).then(
        () => setMessage(t("scout.openedAndCopied")),
        () => setMessage(t("scout.openedManual")),
      );
    } else {
      setMessage(t("scout.openedManual"));
    }
  }

  async function markManualDone() {
    if (!current) return;
    const id = current.id;
    setBusy("manual-done");
    setMessage("");
    try {
      await repos.scout.markPublished(id);
      const list = await reloadPosts();
      pickNext(id, list);
      const today = bumpScoutTodayDone();
      setTodayDone(today.done);
      setMessage(t("scout.manualDone", { done: today.done, goal }));
    } catch (e) {
      setMessage(e instanceof Error ? e.message : t("scout.manualDoneFail"));
    } finally {
      setBusy("");
    }
  }

  async function reportConversionForCurrent() {
    if (!current || current.outreach_status !== "published") return;
    const amountRaw = window.prompt("成交金額（可留空）", convAmount);
    if (amountRaw === null) return;
    const note = window.prompt("備註（可留空）", convNote) ?? "";
    const amount = Number(amountRaw) || 0;
    setBusy("conversion");
    setMessage("");
    try {
      // find outcome by listing recent and matching source
      const outcomes = await repos.growth.listOutcomes(1, 50);
      const hit =
        outcomes.find(
          (o) => o.source_type === "scout_outreach" && o.source_id === current.id,
        ) ?? null;
      if (!hit) {
        setMessage("尚無歸因紀錄（請稍後再試，或先完成標記）");
        return;
      }
      await repos.growth.reportConversion(hit.id, amount, note, "TWD");
      setConvAmount(String(amount || ""));
      setConvNote(note);
      setMessage(
        amount > 0
          ? `已回報成交 $${Math.round(amount)}`
          : "已回報成交（無金額）",
      );
    } catch (e) {
      setMessage(e instanceof Error ? e.message : "回報成交失敗");
    } finally {
      setBusy("");
    }
  }

  async function removeCurrent() {
    if (!current) return;
    if (!window.confirm(t("scout.confirmDeletePost"))) return;
    const id = current.id;
    setBusy("del");
    try {
      await repos.scout.removePost(id);
      const list = await reloadPosts();
      pickNext(id, list);
      setMessage(t("scout.deletedPost"));
    } catch (e) {
      setMessage(e instanceof Error ? e.message : t("common.error"));
    } finally {
      setBusy("");
    }
  }

  const progressPct = Math.min(100, Math.round((todayDone / Math.max(1, goal)) * 100));

  return (
    <>
      <PageHeader title={t("scout.title")} />

      {loadError ? <EmptyState title={loadError} /> : null}

      {message ? (
        <p className="hb-banner-ok" role="status">
          {message}
        </p>
      ) : null}

      {crawlerSessionRequired ? (
        <p className="hb-banner-ok" role="alert">
          {t("scout.crawlerSessionRequired")} <Link to="/app/settings">{t("scout.openSettings")}</Link>
        </p>
      ) : null}

      {/* ① 今日設定 */}
      <Card title={t("scout.today")}>
        <div className="hb-stack">
          <div className="hb-tabs hb-tabs--sm" role="tablist">
            <button
              type="button"
              className={`hb-tab ${purpose === "value" ? "is-active" : ""}`}
              onClick={() => {
                setPurpose("value");
                const t = loadScoutToday();
                setGoal(t.goalValue);
              }}
            >
              {t("scout.purposeValue")}
            </button>
            <button
              type="button"
              className={`hb-tab ${purpose === "activity" ? "is-active" : ""}`}
              onClick={() => {
                setPurpose("activity");
                setProductId("");
                const t = loadScoutToday();
                setGoal(t.goalActivity);
              }}
            >
              {t("scout.purposeActivity")}
            </button>
          </div>

          <div className="hb-scout-today-row">
            <Input
              label={t("scout.goal")}
              type="number"
              min={1}
              max={99}
              value={String(goal)}
              onChange={(e) => setGoalPersist(Number(e.target.value) || 1)}
            />
            <div className="hb-scout-progress">
              <p className="hb-field__label" style={{ margin: 0 }}>
                {t("scout.progress", { done: todayDone, goal })}
              </p>
              <div className="hb-progress" aria-hidden>
                <div className="hb-progress__bar" style={{ width: `${progressPct}%` }} />
              </div>
            </div>
          </div>

          <Textarea
            label={purpose === "activity" ? t("scout.keyword") : t("scout.intent")}
            value={intent}
            onChange={(e) => setIntent(e.target.value)}
            rows={purpose === "activity" ? 2 : 3}
            placeholder={
              purpose === "activity"
                ? t("scout.keywordPh")
                : t("scout.intentPh")
            }
          />

          {purpose === "value" ? (
            <>
              <Select
                label={t("scout.productOptional")}
                value={
                  productId && productOptions.some((o) => o.id === productId) ? productId : ""
                }
                onChange={(e) => setProductId(e.target.value)}
              >
                <option value="">{t("scout.noProduct")}</option>
                {productOptions.map((o) => (
                  <option key={o.id} value={o.id}>
                    {o.label}
                  </option>
                ))}
              </Select>
              {selectedProduct ? (
                <p className="text-muted" style={{ fontSize: "0.8rem", margin: 0 }}>
                  {t("scout.placement", { label: selectedProduct.label })}
                  {selectedProduct.pain_points?.[0]
                    ? t("scout.painPart", { pain: selectedProduct.pain_points[0] })
                    : ""}
                </p>
              ) : null}
              {allProducts.length === 0 ? (
                <p className="text-muted" style={{ fontSize: "0.8rem", margin: 0 }}>
                  {t("scout.noProductsBefore")}{" "}
                  <Link to="/app/brands">{t("nav.brands")}</Link>{" "}
                  {t("scout.noProductsAfter")}
                </p>
              ) : null}
            </>
          ) : null}

          <div className="hb-wizard-actions">
            <Button
              type="button"
              onClick={() => void startPatrol()}
              disabled={busy === "run" || !intent.trim()}
            >
              {busy === "run" ? t("scout.fetching") : pendingQueue.length ? t("scout.startMore") : t("scout.start")}
            </Button>
          </div>
        </div>
      </Card>

      {visibleScanJob ? (
        <Card title={t("scout.scanJob")}>
          <div className="hb-inline-badges" role="status" aria-live="polite">
            <Badge tone={jobStatusTone(visibleScanJob.status)}>
              {jobStatusLabel(visibleScanJob.status, t)}
            </Badge>
            <span className="text-muted">
              {visibleScanJob.progress_summary || t("scout.scanInProgress")}
              {visibleScanJob.progress_percent ? ` · ${visibleScanJob.progress_percent}%` : ""}
            </span>
          </div>
        </Card>
      ) : null}

      {/* ② 海巡批次：只由使用者選取，不會因 Job 或貼文更新跳動。 */}
      {runGroups.length > 0 ? (
        <Card title={t("scout.runs")}>
          <div className="hb-stack">
            <p className="text-muted" style={{ margin: 0, fontSize: "0.85rem" }}>
              {t("scout.runCount", { n: runGroups.length })}
            </p>
            <div className="hb-scout-run-list">
              {runGroups.map((g) => (
                <button
                  key={g.key}
                  type="button"
                  className={`hb-scout-run-item${activeRunKey === g.key ? " is-active" : ""}`}
                  onClick={() => selectRun(g.key)}
                >
                  <span>{g.label}</span>
                  <span className="hb-scout-run-item__meta">
                    {g.pending > 0 ? t("scout.runPending", { n: g.pending }) : t("scout.runDone")}
                    {g.total ? t("scout.runTotal", { n: g.total }) : ""}
                  </span>
                </button>
              ))}
            </div>
            <div className="hb-scout-run-row">
              {activeRunKey ? (
                <div className="hb-scout-run-row__action">
                  <Button
                    type="button"
                    variant="danger"
                    disabled={Boolean(busy)}
                    onClick={() => void removeRun(activeRunKey)}
                  >
                    {busy === "del-run" ? t("scout.deleting") : t("scout.deleteRun")}
                  </Button>
                </div>
              ) : null}
            </div>
          </div>
        </Card>
      ) : null}

      {/* ③ 現在這一則（僅目前批次） */}
      <Card title={t("scout.now")}>
        {!current || (activeRunKey && postRunKey(current) !== activeRunKey) ? (
          <EmptyState title={t("scout.emptyBatch")} />
        ) : (
          <div className="hb-stack hb-scout-now">
            <div className="hb-scout-now__meta">
              <div className="hb-inline-badges">
                <Badge tone="neutral">@{current.author}</Badge>
                <Badge tone="brand">{current.search_tag}</Badge>
                <Badge tone="neutral">{Math.round(current.score)}</Badge>
                {current.scan_path ? <Badge tone="neutral">{t("scout.source", { source: current.scan_path })}</Badge> : null}
                {current.classification ? (
                  <Badge tone="neutral">{t("scout.classification", { classification: current.classification })}</Badge>
                ) : null}
              </div>
              <p className="hb-scout-now__stance text-muted">
                {stanceOf(current, t)}
                {current.matched_product_label ? ` · ${current.matched_product_label}` : ""}
                {activeRunMeta ? ` · ${shortRunLabel(activeRunMeta.label, 18)}` : ""}
              </p>
            </div>
            <p className="hb-scout-now__text">{current.text}</p>
            {current.created_at || allowHttpUrl(current.permalink) ? (
              <p className="text-muted" style={{ fontSize: "0.8rem", margin: 0 }}>
                {current.created_at ? t("scout.createdAt", { time: formatLocalDateTime(current.created_at) }) : ""}
                {current.created_at && allowHttpUrl(current.permalink) ? " · " : ""}
                {allowHttpUrl(current.permalink) ? (
                  <a href={allowHttpUrl(current.permalink) ?? undefined} target="_blank" rel="noreferrer">
                    {t("scout.openPermalink")}
                  </a>
                ) : null}
              </p>
            ) : null}
            {current.match_reason ? (
              <p className="text-muted" style={{ fontSize: "0.8rem", margin: 0 }}>
                {current.match_reason}
              </p>
            ) : null}
            {current.opportunity ? (
              <p className="text-muted" style={{ fontSize: "0.8rem", margin: 0 }}>
                {current.opportunity}
              </p>
            ) : null}

            <Textarea
              label={t("scout.draft")}
              value={draftText}
              onChange={(e) => setDraftText(e.target.value)}
              rows={current.scout_mode === "activity" ? 3 : 5}
              placeholder={current.scout_mode === "activity" ? t("scout.draftPhActivity") : t("scout.draftPhValue")}
            />

            <p className="text-muted" style={{ fontSize: "0.85rem", margin: 0 }}>
              {t("scout.manualReplyHint")}
            </p>

            <div className="hb-wizard-actions">
              <Button
                type="button"
                variant="ghost"
                disabled={Boolean(busy) || !isPending(current)}
                onClick={() => void skipCurrent()}
              >
                {busy === "skip" ? "…" : t("scout.skip")}
              </Button>
              <Button
                type="button"
                variant="ghost"
                disabled={Boolean(busy) || !isPending(current)}
                onClick={() => void regenDraft()}
              >
                {busy === "draft" ? "…" : t("scout.regen")}
              </Button>
              <Button
                type="button"
                disabled={Boolean(busy) || !allowHttpUrl(current.permalink)}
                onClick={openThreadsReply}
              >
                {t("scout.openThreadsReply")}
              </Button>
              <Button
                type="button"
                variant="ghost"
                disabled={Boolean(busy) || !isPending(current)}
                onClick={() => void markManualDone()}
              >
                {busy === "manual-done" ? "…" : t("scout.markManualDone")}
              </Button>
              {current.outreach_status === "published" ? (
                <Button
                  type="button"
                  variant="secondary"
                  disabled={Boolean(busy)}
                  onClick={() => void reportConversionForCurrent()}
                >
                  {busy === "conversion" ? "…" : "回報成交"}
                </Button>
              ) : null}
              <Button
                type="button"
                variant="ghost"
                disabled={Boolean(busy)}
                onClick={() => void removeCurrent()}
              >
                {t("common.delete")}
              </Button>
            </div>
          </div>
        )}
      </Card>

      {/* ④ 周邊知識 · 綁目前海巡批次 */}
      {report?.mode !== "activity" &&
      report?.theme_key === activeRunKey &&
      report.research_notes?.length ? (
        <Card
          title={
            report?.theme_label
              ? t("scout.knowledgeWithLabel", { label: report.theme_label })
              : t("scout.knowledgeLearn")
          }
        >
          <div className="hb-scout-learn">
            <div className="hb-scout-learn__toolbar">
              <Button
                type="button"
                variant={report?.research_notes?.length && !reportOpen ? "primary" : "ghost"}
                onClick={() => setReportOpen((v) => !v)}
                disabled={!report}
              >
                {reportOpen
                  ? t("scout.collapseKnowledge")
                  : report?.research_notes?.length
                    ? t("scout.expandLearn", { n: report.research_notes.length })
                    : t("scout.expandKnowledge")}
              </Button>
              {report?.theme_key ? (
                <Button
                  type="button"
                  variant="ghost"
                  disabled={Boolean(busy)}
                  onClick={() => void removeRun(report.theme_key!)}
                >
                  {t("scout.deleteKnowledgeRun")}
                </Button>
              ) : null}
            </div>

            {!reportOpen || !report ? (
              report ? (
                report.research_notes && report.research_notes.length > 0 ? (
                  <>
                    <div className="hb-scout-learn-banner is-ready">
                      <p style={{ fontSize: "0.9rem", fontWeight: 600 }}>
                        {t("scout.notesCount", { n: report.research_notes.length })}
                        {report.product_label ? ` · ${report.product_label}` : ""}
                      </p>
                    </div>
                    <ul className="hb-scout-research hb-scout-research--preview">
                      {report.research_notes
                        .filter((n) => (n.tier || "core") === "core")
                        .slice(0, 2)
                        .map((n) => (
                          <KnowledgeNoteCard key={n.id} note={n} badge={t("scout.badgeCore")} compact />
                        ))}
                    </ul>
                  </>
                ) : (
                  <p className="hb-scout-learn__intro">{t("scout.noKnowledge")}</p>
                )
              ) : null
            ) : (
              <>
                {report.product_label ? (
                  <div className="hb-scout-context">
                    <p className="hb-field__label">{t("scout.product")}</p>
                    <p style={{ fontWeight: 600, margin: 0 }}>{report.product_label}</p>
                    {report.product_context ? (
                      <p style={{ margin: 0, fontSize: "0.9rem", lineHeight: 1.55 }}>
                        {report.product_context}
                      </p>
                    ) : null}
                  </div>
                ) : null}
                {report.pains.length > 0 ? (
                  <div className="hb-scout-context">
                    <p className="hb-field__label">
                      {report.product_label ? t("scout.painsSolved") : t("scout.focus")}
                    </p>
                    <div className="hb-chip-row">
                      {report.pains.map((p) => (
                        <span key={p} className="hb-chip is-static">
                          {p}
                        </span>
                      ))}
                    </div>
                  </div>
                ) : null}
                {report.research_notes && report.research_notes.length > 0 ? (
                  <div className="hb-scout-context hb-scout-learn">
                    <div className="hb-scout-research-tier__head">
                      <p className="hb-field__label" style={{ margin: 0 }}>
                        {t("scout.knowledge")}
                      </p>
                      <Badge tone="brand">{report.research_notes.length}</Badge>
                    </div>
                    <div className="hb-tabs hb-tabs--sm hb-scout-learn__tabs" role="tablist">
                      <button
                        type="button"
                        className={`hb-tab ${researchTier === "all" ? "is-active" : ""}`}
                        onClick={() => setResearchTier("all")}
                      >
                        {t("scout.all")}
                      </button>
                      {(["core", "adjacent", "broad"] as ScoutResearchTier[]).map((tier) => {
                        const n = report.research_notes!.filter(
                          (x) => (x.tier || "adjacent") === tier,
                        ).length;
                        if (!n) return null;
                        return (
                          <button
                            key={tier}
                            type="button"
                            className={`hb-tab ${researchTier === tier ? "is-active" : ""}`}
                            onClick={() => setResearchTier(tier)}
                            title={t(`scout.tier.${tier}Hint`)}
                          >
                            {t(`scout.tier.${tier}`)}
                            <span className="hb-tab__count"> {n}</span>
                          </button>
                        );
                      })}
                    </div>
                    <div className="hb-scout-learn__tiers">
                      {(researchTier === "all"
                        ? groupNotesByTier(report.research_notes)
                        : [
                            {
                              tier: researchTier,
                              notes: report.research_notes.filter(
                                (x) => (x.tier || "adjacent") === researchTier,
                              ),
                            },
                          ]
                      ).map((group) =>
                        group.notes.length === 0 ? null : (
                          <div key={group.tier} className="hb-scout-research-tier">
                            {researchTier === "all" ? (
                              <div className="hb-scout-research-tier__head">
                                <Badge
                                  tone={
                                    group.tier === "core"
                                      ? "success"
                                      : group.tier === "adjacent"
                                        ? "brand"
                                        : "neutral"
                                  }
                                >
                                  {t(`scout.tier.${group.tier}`)}
                                </Badge>
                              </div>
                            ) : null}
                            <ul className="hb-scout-research">
                              {group.notes.map((n) => (
                                <KnowledgeNoteCard key={n.id} note={n} />
                              ))}
                            </ul>
                          </div>
                        ),
                      )}
                    </div>
                  </div>
                ) : (
                  <p className="hb-scout-learn__intro">{t("scout.noWebSummary")}</p>
                )}
              </>
            )}
          </div>
        </Card>
      ) : null}

      {/* ⑤ 不同回覆策略不能混排，避免將短回誤當成痛點接話。 */}
      <MatchQueue
        title={t("scout.valueQueue", { n: valueQueue.length })}
        posts={valueQueue}
        page={valueQueuePage}
        onPageChange={setValueQueuePage}
        currentId={currentId}
        onSelect={setCurrentId}
        t={t}
      />
      <MatchQueue
        title={t("scout.activityQueue", { n: activityQueue.length })}
        posts={activityQueue}
        page={activityQueuePage}
        onPageChange={setActivityQueuePage}
        currentId={currentId}
        onSelect={setCurrentId}
        t={t}
      />
    </>
  );
}
