import { useEffect, useEffectEvent, useMemo, useRef, useState } from "react";
import { Link } from "react-router-dom";
import { PageHeader } from "../components/layout/PageHeader";
import { Badge, Button, Card, EmptyState, Input, Pager, Textarea } from "../components/ui";
import { useData, useRepos } from "../data/DataContext";
import type {
  ScoutHomeworkRecord,
  ScoutPost,
  ScoutRun,
  ScoutRunBrief,
} from "../domain/types";
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 { useFormatApiError } from "../lib/apiErrors";
import { useJobLive } from "../data/JobLiveContext";
import { jobStatusLabel, jobStatusTone } from "../lib/jobLabels";
import { formatLocalDateTime } from "../lib/time";
import type { Job } from "../domain/types";
import { checkThreadsTerm, isThreadsSearchable, normalizeSearchTerm, selectThreadsSearchTerms, threadsPostIdentity } from "../lib/threadsTerm";

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 runStatusLabel(
  status: ScoutRun["status"],
  t: (k: string, p?: Record<string, string | number>) => string,
): string {
  return t(`scout.runStatus.${status}`);
}

function MatchQueue({
  title,
  posts,
  total,
  page,
  onPageChange,
  currentId,
  onSelect,
  t,
}: {
  title: string;
  posts: ScoutPost[];
  total: number;
  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">
          {posts.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>
                  {p.posted_at != null && p.posted_at > 0
                    ? t("scout.postedAt", { time: formatLocalDateTime(p.posted_at) })
                    : t("scout.postedUnknown")}
                </span>
              </span>
              <span className="hb-scout-queue-item__text">
                {p.text.slice(0, 72)}
                {p.text.length > 72 ? "…" : ""}
              </span>
            </button>
          ))}
          <Pager total={total} page={page} pageSize={pageSize} onPageChange={onPageChange} />
        </div>
      )}
    </Card>
  );
}

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))}…`;
}

/** API 異常重複或 URL 追蹤參數變體都只能在佇列出現一次。 */
function dedupeScoutPosts(list: ScoutPost[]): ScoutPost[] {
  const seen = new Set<string>();
  return list.filter((post) => {
    const raw = post.permalink?.trim();
    const key = (raw && threadsPostIdentity(raw)) || `id:${post.id}`;
    if (seen.has(key)) return false;
    seen.add(key);
    return true;
  });
}

/**
 * 話題靈感：今日目標 → 關鍵字工坊 → 掃描 → 逐則靈感回覆。
 * 找需求／解法媒合已併入商機頁「立即探索」。
 */
export function ScoutPage() {
  const repos = useRepos();
  const { tick } = useData();
  const { t } = useI18n();
  const formatApiError = useFormatApiError();
  const { jobs: liveJobs, reload: reloadJobs, revision: jobsRevision } = useJobLive();
  const seenJobsRevision = useRef<number | null>(null);

  const [posts, setPosts] = useState<ScoutPost[]>([]);
  const [intent, setIntent] = useState("");
  const [goal, setGoal] = useState(20);
  const [todayDone, setTodayDone] = useState(0);

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

  const [activityQueuePage, setActivityQueuePage] = useState(1);
  const [resultPagination, setResultPagination] = useState({ page: 1, pageSize: 8, total: 0, totalPages: 0 });
  const [, setHomeworkList] = useState<ScoutHomeworkRecord[]>([]);
  const [runs, setRuns] = useState<ScoutRun[]>([]);
  const [runPage, setRunPage] = useState(1);
  const [runPagination, setRunPagination] = useState({ page: 1, pageSize: 10, total: 0, totalPages: 0 });
  /** 目前檢視的獨立海巡批次 ID */
  const [activeRunKey, setActiveRunKey] = useState<string | null>(null);

  const [busy, setBusy] = useState("");
  const [message, setMessage] = useState("");
  const [scanJob, setScanJob] = useState<Job | null>(null);
  const [crawlerSessionRequired, setCrawlerSessionRequired] = useState(false);
  const [loadError, setLoadError] = useState("");

  /** 關鍵字工坊：prepareBrief 後停在這裡，確認才 scan */
  const [workshopBrief, setWorkshopBrief] = useState<ScoutRunBrief | null>(null);
  const [workshopTerms, setWorkshopTerms] = useState<string[]>([]);
  /** 勾選要送搜尋的詞（後端依整體意圖排序，預設取前三組高信心查詢） */
  const [selectedTermKeys, setSelectedTermKeys] = useState<Set<string>>(new Set());
  const [newTerm, setNewTerm] = useState("");
  const [termDraftErr, setTermDraftErr] = useState("");

  function formatScoutError(err: unknown, fallbackKey: string): string {
    const code = typeof err === "object" && err !== null && "code" in err
      ? Number((err as { code?: unknown }).code)
      : NaN;
    if (code === 409031) return t("scout.err.runBusy");
    if (code === 404001) return t("scout.err.notFound");
    if (code === 503001 || code === 503002) return t("scout.err.sourceUnavailable");
    return formatApiError(err, fallbackKey);
  }

  const loadRunPosts = async (runId: string, page = 1, selectFirst = false) => {
    const result = await repos.scout.listRunPosts(runId, { page, pageSize: 8 });
    setPosts(dedupeScoutPosts(result.list));
    setResultPagination(result.pagination);
    setActivityQueuePage(result.pagination.page);
    if (selectFirst) {
      const firstPending = result.list.find(isPending);
      setCurrentId(firstPending?.id || result.list[0]?.id || null);
    }
    return result;
  };

  const loadRuns = async (page = runPage, selectFirst = false) => {
    const result = await repos.scout.listRuns({ page, pageSize: 10 });
    setRuns(result.list);
    setRunPagination(result.pagination);
    const selected = result.list.find((run) => run.id === activeRunKey);
    const nextRun = selected || (selectFirst ? result.list[0] : undefined);
    if (nextRun) {
      setActiveRunKey(nextRun.id);
      await loadRunPosts(nextRun.id, 1, true);
    } else if (!result.list.length && result.pagination.total === 0) {
      setActiveRunKey(null);
      setPosts([]);
    }
    return result;
  };

  const notifyNewRunOnJobRevision = useEffectEvent(async () => {
    setMessage(t("scout.newRunReady"));
  });

  useEffect(() => {
    const today = loadScoutToday();
    setTodayDone(today.done);
    setGoal(today.goalActivity || 20);
  }, []);

  useEffect(() => {
    void (async () => {
      try {
        const hw = await repos.scout.listHomework();
        const result = await repos.scout.listRuns({ page: runPage, pageSize: 10 });
        setRuns(result.list);
        setRunPagination(result.pagination);
        setHomeworkList(hw);
        const nextRun = result.list.find((run) => run.id === activeRunKey) || result.list[0];
        if (nextRun) {
          setActiveRunKey(nextRun.id);
          await loadRunPosts(nextRun.id, 1, true);
        } else {
          setPosts([]);
        }
        setLoadError("");
      } catch (e) {
        setLoadError(formatScoutError(e, "scout.loadFail"));
      }
    })();
  // loadRunPosts/loadRuns are render-scoped adapters; the reload trigger is intentionally
  // limited to the selected page and data revision to avoid duplicate requests.
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [repos, t, tick, runPage, formatApiError]);

  // Jobs may finish after navigating away from Scout. Signal that a new batch is
  // available without replacing the user's current run, result page, or selection.
  useEffect(() => {
    if (seenJobsRevision.current === null) {
      seenJobsRevision.current = jobsRevision;
      return;
    }
    if (seenJobsRevision.current === jobsRevision) return;
    seenJobsRevision.current = jobsRevision;
    void notifyNewRunOnJobRevision();
  }, [jobsRevision]);

  const activeRunMeta = useMemo(() => runs.find((run) => run.id === activeRunKey) || null, [runs, activeRunKey]);

  const runQueue = useMemo(() => {
    return posts
      .filter((p) => !activeRunKey || p.run_id === activeRunKey)
      .slice();
  }, [posts, activeRunKey]);

  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 scanInProgress = Boolean(
    visibleScanJob && !["succeeded", "failed", "cancelled"].includes(visibleScanJob.status),
  );

  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") {
        if (cancelled()) return;
        // 完成只提示有新批次；由使用者按「重新整理批次」決定何時載入。
        setMessage(t("scout.newRunReady"));
        setWorkshopBrief(null);
        setWorkshopTerms([]);
      } 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) => p.run_id === key);
    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);
    setActivityQueuePage(1);
    setCurrentId(null);
    void loadRunPosts(key, 1, true);
  }

  async function refreshRuns() {
    setBusy("refresh");
    try {
      await loadRuns(runPage, true);
      setMessage(t("scout.runsRefreshed"));
    } catch (e) {
      setMessage(formatScoutError(e, "scout.loadFail"));
    } finally {
      setBusy("");
    }
  }

  async function removeRun(key: string) {
    const run = runs.find((item) => item.id === key);
    const label = run?.theme_label || t("scout.thisRun");
    if (
      !window.confirm(t("scout.confirmDeleteRun", { label }))
    ) {
      return;
    }
    setBusy("del-run");
    try {
      await repos.scout.removeRun(key);
      const result = await repos.scout.listRuns({ page: runPage, pageSize: 10 });
      const targetPage = result.list.length === 0 && runPage > 1 ? runPage - 1 : runPage;
      const settled = targetPage === runPage
        ? result
        : await repos.scout.listRuns({ page: targetPage, pageSize: 10 });
      if (targetPage !== runPage) setRunPage(targetPage);
      setRuns(settled.list);
      setRunPagination(settled.pagination);
      if (activeRunKey === key) {
        const next = settled.list[0];
        setActiveRunKey(next?.id || null);
        if (next) await loadRunPosts(next.id, 1, true);
        else setPosts([]);
        setCurrentId(null);
      }
      setMessage(t("scout.deletedRun", { label }));
    } catch (e) {
      setMessage(formatScoutError(e, "scout.deleteRunFail"));
    } finally {
      setBusy("");
    }
  }

  function setGoalPersist(n: number) {
    const g = Math.max(1, Math.min(40, n));
    setGoal(g);
    const today = loadScoutToday();
    saveScoutToday({ ...today, goalActivity: g });
  }

  /** ① 產出可審關鍵字（不搜尋） */
  async function planKeywords() {
    const text = intent.trim();
    if (!text) {
      setMessage(t("scout.needKeyword"));
      return;
    }
    setBusy("plan");
    setMessage("");
    setCrawlerSessionRequired(false);
    try {
      const brief = await repos.scout.prepareBrief({
        intent: text,
        brandId: null,
        productId: null,
        purpose: "activity",
        // 後端會產規則變體 +（有 Key 時）AI 擴充短詞
        deep: true,
      });
      const terms = (brief.scan_terms || [])
        .map((s) => normalizeSearchTerm(s))
        .filter(Boolean);
      const short = terms.filter((x) => isThreadsSearchable(x));
      const all = short.length ? short : terms.length ? terms : [];
      if (!all.length) {
        setMessage(t("scout.topic.noTerms"));
        setWorkshopBrief(null);
        setWorkshopTerms([]);
        return;
      }
      // 後端已按整體意圖排序，且持久化前會要求多個主題核同時命中；
      // 取前三組兼顧召回與精準，例如「後端 外包／接案／工程師」。
      const recommended = selectThreadsSearchTerms(text, all);
      const primaryNorm = recommended[0] ?? all[0]!;
      const rest = all.filter((x) => x.toLowerCase() !== primaryNorm.toLowerCase());
      setWorkshopBrief(brief);
      setWorkshopTerms([primaryNorm, ...rest]);
      setSelectedTermKeys(new Set(recommended.map((term) => term.toLowerCase())));
      setNewTerm("");
      setTermDraftErr("");
      setMessage(t("scout.topic.termsReadyPrimary", { n: rest.length }));
    } catch (e) {
      setMessage(e instanceof Error ? e.message : t("scout.patrolFail"));
    } finally {
      setBusy("");
    }
  }

  function removeWorkshopTerm(idx: number) {
    setWorkshopTerms((prev) => {
      const removed = prev[idx];
      const next = prev.filter((_, i) => i !== idx);
      if (removed) {
        const k = removed.toLowerCase();
        setSelectedTermKeys((sel) => {
          const n = new Set(sel);
          n.delete(k);
          return n;
        });
      }
      return next;
    });
  }

  function toggleTermSelected(term: string) {
    const k = normalizeSearchTerm(term).toLowerCase();
    setSelectedTermKeys((prev) => {
      const n = new Set(prev);
      if (n.has(k)) n.delete(k);
      else n.add(k);
      return n;
    });
  }

  function addWorkshopTerm() {
    const term = normalizeSearchTerm(newTerm);
    const check = checkThreadsTerm(term);
    if (!check.ok) {
      setTermDraftErr(t(`radar.explore.termError.${check.reason}`));
      return;
    }
    setWorkshopTerms((prev) => {
      if (prev.some((p) => p.toLowerCase() === term.toLowerCase())) return prev;
      return [...prev, term];
    });
    setSelectedTermKeys((prev) => new Set(prev).add(term.toLowerCase()));
    setNewTerm("");
    setTermDraftErr("");
  }

  function updateWorkshopTerm(idx: number, value: string) {
    setWorkshopTerms((prev) => {
      const old = prev[idx];
      const next = prev.map((x, i) => (i === idx ? value : x));
      if (old) {
        const ok = old.toLowerCase();
        const nk = normalizeSearchTerm(value).toLowerCase();
        setSelectedTermKeys((sel) => {
          const n = new Set(sel);
          if (n.has(ok)) {
            n.delete(ok);
            if (nk) n.add(nk);
          }
          return n;
        });
      }
      return next;
    });
  }

  function clearWorkshop() {
    setWorkshopBrief(null);
    setWorkshopTerms([]);
    setSelectedTermKeys(new Set());
    setNewTerm("");
    setTermDraftErr("");
  }

  /** 建立一個海巡批次；立即開始與人工確認共用同一個後端任務管線。 */
  async function enqueueScan(base: ScoutRunBrief, terms: string[], text: string) {
    const baseLabel = base.theme_label || base.intent.slice(0, 36) || t("scout.defaultLabel");
    const theme_key = newId("run");
    const theme_label = `${baseLabel} · ${runTimeSuffix()}`;
    const remaining = Math.max(1, goal - todayDone);
    const briefSaved: ScoutRunBrief = {
      ...base,
      intent: text,
      scan_terms: terms,
      theme_key,
      theme_label,
      target_count: remaining,
    };

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

  /** 不跳過前處理：先用規則產出建議詞，再立即送出，不要求使用者逐詞確認。 */
  async function startImmediateScan() {
    if (scanInProgress) return;
    const text = intent.trim();
    if (!text) {
      setMessage(t("scout.needKeyword"));
      return;
    }
    setBusy("run");
    setMessage("");
    setCrawlerSessionRequired(false);
    try {
      const base = await repos.scout.prepareBrief({
        intent: text,
        brandId: null,
        productId: null,
        purpose: "activity",
        deep: false,
      });
      const terms = selectThreadsSearchTerms(text, (base.scan_terms || []).map(normalizeSearchTerm).filter(Boolean))
        .filter(isThreadsSearchable)
        .slice(0, 3);
      if (!terms.length) {
        setMessage(t("scout.topic.noTerms"));
        return;
      }
      await enqueueScan(base, terms, text);
    } 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,
      );
    } finally {
      setBusy("");
    }
  }

  /** ② 確認關鍵字後才 enqueue scan（帶今日目標 target_count 補抓） */
  async function confirmScan() {
    if (scanInProgress) return;
    const text = intent.trim();
    if (!text) {
      setMessage(t("scout.needKeyword"));
      return;
    }
    // 只送勾選詞；若都沒勾，退回第一組
    let terms = workshopTerms
      .map((s) => normalizeSearchTerm(s))
      .filter((s) => s && selectedTermKeys.has(s.toLowerCase()));
    if (!terms.length && workshopTerms[0]) {
      terms = [normalizeSearchTerm(workshopTerms[0])].filter(Boolean);
    }
    // 精準：一次最多 3 組 fan-out，對齊「你在 Threads 打少數詞」
    if (terms.length > 3) {
      terms = terms.slice(0, 3);
    }
    if (!terms.length) {
      setMessage(t("scout.workshopEmpty"));
      return;
    }
    const bad = terms.filter((x) => !isThreadsSearchable(x));
    if (bad.length) {
      setMessage(t("scout.topic.termsNeedShort", { n: bad.length }));
      return;
    }
    setBusy("run");
    setMessage("");
    setCrawlerSessionRequired(false);
    try {
      const base =
        workshopBrief ||
        (await repos.scout.prepareBrief({
          intent: text,
          brandId: null,
          productId: null,
          purpose: "activity",
          deep: false,
        }));
      await enqueueScan(base, terms, text);
    } 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,
      );
    } finally {
      setBusy("");
    }
  }

  async function reloadPosts(): Promise<ScoutPost[]> {
    if (!activeRunKey) return [];
    const result = await loadRunPosts(activeRunKey, resultPagination.page);
    if (result.list.length === 0 && resultPagination.page > 1) {
      const previous = await loadRunPosts(activeRunKey, resultPagination.page - 1, true);
      return previous.list;
    }
    return result.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 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(formatScoutError(e, "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}

      {/* ① 今日設定 — 僅話題靈感（activity） */}
      <Card title={t("scout.today")}>
        <div className="hb-stack">
          <p className="hb-radar-section__hint" style={{ margin: 0 }}>
            {t("scout.topic.intro")}
          </p>

          <div className="hb-scout-today-row">
            <Input
              label={t("scout.goal")}
              type="number"
              min={1}
              max={40}
              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={t("scout.keyword")}
					value={intent}
					onChange={(e) => setIntent(e.target.value)}
					rows={2}
					placeholder={t("scout.keywordPh")}
				/>

          <div className="hb-wizard-actions">
            <Button
              type="button"
              onClick={() => void planKeywords()}
              disabled={busy === "plan" || busy === "run" || !intent.trim()}
            >
              {busy === "plan"
                ? t("scout.planning")
                : workshopBrief
                  ? t("scout.replan")
                  : t("scout.planKeywords")}
            </Button>
            {!workshopBrief ? (
              <Button
                type="button"
                variant="secondary"
                onClick={() => void startImmediateScan()}
                disabled={busy === "plan" || busy === "run" || scanInProgress || !intent.trim()}
              >
                {busy === "run" ? t("scout.fetching") : t("scout.startImmediate")}
              </Button>
            ) : null}
            <Link className="hb-btn hb-btn--ghost" to="/app/radar/watches">
              {t("scout.openDailySchedule")}
            </Link>
          </div>
          <p className="hb-scout-schedule-note">
            {t("scout.immediateHint")} {t("scout.scheduleHint")}
          </p>
        </div>
      </Card>

      {workshopBrief ? (
        <Card title={t("scout.workshop")}>
          <div className="hb-stack">
            <p className="text-muted" style={{ margin: 0, fontSize: "var(--hb-text-sm)" }}>
              {t("scout.topic.workshopHintSelect")}
            </p>
            <div className="hb-stack" style={{ gap: "0.5rem" }}>
              {workshopTerms.map((term, idx) => {
                const key = normalizeSearchTerm(term).toLowerCase();
                const checked = selectedTermKeys.has(key);
                const isPrimary = idx === 0;
                return (
                  <div
                    key={`term-${idx}`}
                    style={{ display: "flex", gap: "0.5rem", alignItems: "flex-end" }}
                  >
                    <label
                      style={{
                        display: "flex",
                        alignItems: "center",
                        gap: "0.35rem",
                        paddingBottom: "0.45rem",
                        cursor: "pointer",
                      }}
                    >
                      <input
                        type="checkbox"
                        checked={checked}
                        onChange={() => toggleTermSelected(term)}
                        disabled={busy === "run"}
                        aria-label={t("scout.topic.useTerm")}
                      />
                    </label>
                    <div style={{ flex: 1 }}>
                      <Input
                        label={
                          isPrimary
                            ? t("scout.topic.primaryTerm")
                            : t("scout.topic.variantTerm", { n: idx })
                        }
                        value={term}
                        onChange={(e) => updateWorkshopTerm(idx, e.target.value)}
                        aria-label={`${t("scout.workshop")} ${idx + 1}`}
                      />
                    </div>
                    <Button
                      type="button"
                      variant="ghost"
                      onClick={() => removeWorkshopTerm(idx)}
                      disabled={busy === "run"}
                    >
                      {t("scout.removeTerm")}
                    </Button>
                  </div>
                );
              })}
            </div>
            <div style={{ display: "flex", gap: "0.5rem", alignItems: "flex-end" }}>
              <div style={{ flex: 1 }}>
                <Input
                  label={t("scout.addTermPh")}
                  value={newTerm}
                  onChange={(e) => {
                    setNewTerm(e.target.value);
                    setTermDraftErr("");
                  }}
                  onKeyDown={(e) => {
                    if (e.key === "Enter") {
                      e.preventDefault();
                      addWorkshopTerm();
                    }
                  }}
                />
              </div>
              <Button type="button" variant="ghost" onClick={addWorkshopTerm} disabled={!newTerm.trim()}>
                {t("scout.addTerm")}
              </Button>
            </div>
            {termDraftErr ? (
              <p className="hb-banner-ok" role="status">
                {termDraftErr}
              </p>
            ) : (
              <p className="text-muted" style={{ margin: 0, fontSize: "var(--hb-text-sm)" }}>
                {t("scout.topic.termHint")}
              </p>
            )}
            <div className="hb-wizard-actions">
              <Button
                type="button"
                onClick={() => void confirmScan()}
                disabled={
                  busy === "run" ||
                  busy === "plan" ||
                  scanInProgress ||
                  workshopTerms.map((s) => s.trim()).filter(Boolean).length === 0
                }
              >
                {busy === "run" ? t("scout.fetching") : t("scout.confirmScan")}
              </Button>
              <Button type="button" variant="ghost" onClick={clearWorkshop} disabled={busy === "run"}>
                {t("scout.clearWorkshop")}
              </Button>
            </div>
          </div>
        </Card>
      ) : null}

      {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 或貼文更新跳動。 */}
      {runPagination.total > 0 ? (
        <Card title={t("scout.runs")}>
          <div className="hb-stack">
            <p className="text-muted" style={{ margin: 0, fontSize: "var(--hb-text-sm)" }}>
              {t("scout.runCount", { n: runPagination.total })}
            </p>
            <div className="hb-scout-run-list">
              {runs.map((run) => (
                <button
                  key={run.id}
                  type="button"
                  className={`hb-scout-run-item${activeRunKey === run.id ? " is-active" : ""}`}
                  onClick={() => selectRun(run.id)}
                >
                  <span>{run.theme_label || run.theme_key || t("scout.unnamedRun")}</span>
                  <span className="hb-scout-run-item__meta">
                    <Badge tone={run.status === "failed" ? "danger" : run.status === "succeeded" ? "neutral" : "brand"}>
                      {runStatusLabel(run.status, t)}
                    </Badge>
                    <span>{t("scout.runCreatedAt", { time: formatLocalDateTime(run.created_at) })}</span>
                    {run.pending_count > 0 ? t("scout.runPending", { n: run.pending_count }) : t("scout.runDone")}
                    {run.eligible_count ? t("scout.runTotal", { n: run.eligible_count }) : ""}
                  </span>
                </button>
              ))}
            </div>
            {activeRunMeta && activeRunMeta.shortfall_count > 0 ? (
              <p className="text-muted" style={{ margin: 0, fontSize: "var(--hb-text-xs)" }}>
                {t("scout.shortfall", { n: activeRunMeta.shortfall_count })}{" "}
                {activeRunMeta.shortfall_reasons
                  .map((reason) => t(`scout.shortfallReason.${reason}`))
                  .join("、") || t("scout.shortfallReason.unknown")}
              </p>
            ) : null}
            <Pager
              total={runPagination.total}
              page={runPage}
              pageSize={runPagination.pageSize}
              onPageChange={(page) => {
                setRunPage(page);
                setCurrentId(null);
              }}
            />
            <div className="hb-scout-run-row">
              <Button
                type="button"
                variant="ghost"
                disabled={Boolean(busy)}
                onClick={() => void refreshRuns()}
              >
                {busy === "refresh" ? t("scout.refreshingRuns") : t("scout.refreshRuns")}
              </Button>
              {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 && current.run_id !== 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>
                {current.search_tag ? (
                  <Badge tone="brand">{t("scout.resultKeyword", { tag: current.search_tag })}</Badge>
                ) : null}
                <Badge tone="neutral">{Math.round(current.score)}</Badge>
                <Badge tone="neutral">{t("scout.source")}</Badge>
                {current.classification ? (
                  <Badge tone="neutral">{t("scout.classification", { classification: current.classification })}</Badge>
                ) : null}
              </div>
              <p className="hb-scout-now__stance text-muted">
                {t("scout.stanceActivity")}
                {activeRunMeta ? ` · ${shortRunLabel(activeRunMeta.theme_label || activeRunMeta.theme_key, 18)}` : ""}
              </p>
            </div>
            <p className="hb-scout-now__text">{current.text}</p>
            {current.posted_at || current.created_at || allowHttpUrl(current.permalink) ? (
              <p className="text-muted" style={{ fontSize: "var(--hb-text-xs)", margin: 0 }}>
                {current.posted_at && current.posted_at > 0
                  ? t("scout.postedAt", { time: formatLocalDateTime(current.posted_at) })
                  : t("scout.postedUnknown")}
                {current.created_at ? ` · ${t("scout.scannedAt", { time: formatLocalDateTime(current.created_at) })}` : ""}
                {(current.posted_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: "var(--hb-text-xs)", margin: 0 }}>
                {current.match_reason}
              </p>
            ) : null}

            <Textarea
              label={t("scout.draft")}
              value={draftText}
              onChange={(e) => setDraftText(e.target.value)}
              rows={3}
              placeholder={t("scout.draftPhActivity")}
            />

            <p className="text-muted" style={{ fontSize: "var(--hb-text-sm)", 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>
              <Button
                type="button"
                variant="ghost"
                disabled={Boolean(busy)}
                onClick={() => void removeCurrent()}
              >
                {t("common.delete")}
              </Button>
            </div>
          </div>
        )}
      </Card>


      {/* ⑤ 話題佇列（需求／解法媒合佇列已併入商機） */}
      <MatchQueue
        title={t("scout.activityQueue", { n: activityQueue.length })}
        posts={activityQueue}
        total={resultPagination.total}
        page={activityQueuePage}
        onPageChange={(page) => {
          if (activeRunKey) void loadRunPosts(activeRunKey, page, true);
        }}
        currentId={currentId}
        onSelect={setCurrentId}
        t={t}
      />
    </>
  );
}
