/* ============================================================
   Beacon CS — TRAIN (QA library · processing flows · read-only sources)
   ============================================================ */
function faqDisplayId(faq) {
  if (!faq) return "";
  return [faq.logicalId || faq.id, faq.version].filter(Boolean).join(" · ");
}

function documentAnswerMode(doc) {
  if (doc.realtimeSource) return doc.realtimeHealth === "正常" ? "实时来源" : "不用于回答";
  if (doc.status === "expired" || doc.status === "retired" || doc.route.includes("不用于回答")) return "不用于回答";
  if (doc.route.includes("已发布问答") || doc.route.includes("维护问答")) return "仅供维护问答 / 流程";
  if (doc.route.includes("实时来源")) return "实时来源";
  if (doc.route === "低风险引用") return "低风险引用";
  return "不用于回答";
}

function documentUseState(doc) {
  if (doc.syncStatus) return doc.syncStatus;
  if (doc.status === "synced") return "calculating";
  return "normal";
}

function documentCanBeStopped(doc) {
  return doc.status !== "retired" && documentAnswerMode(doc) !== "不用于回答";
}

function documentHasImpact(doc) {
  return !doc.impact.startsWith("0 ") && !doc.impact.startsWith("尚未") && !doc.impact.includes("无当前");
}

function TrainView({ intent = {} }) {
  const { SKILLS, DOCUMENTS, FAQ_LIBRARY_TOTALS, FAQ_QUEUE, FAQ_SIMILAR_GROUPS, CONVERSATION_GAPS } = window.DATA;
  const { Badge } = window;
  const Icons = window.Icons;
  const [sel, setSel] = useState(null);
  const [tab, setTab] = useState("review");
  const [doc, setDoc] = useState(null);
  const [documents, setDocuments] = useState(DOCUMENTS);
  const [faqs, setFaqs] = useState(FAQ_QUEUE);
  const [faqTotals, setFaqTotals] = useState(FAQ_LIBRARY_TOTALS);
  const [similarGroups, setSimilarGroups] = useState(() => FAQ_SIMILAR_GROUPS.filter(group => !window.PROTOTYPE_STATE?.resolvedQaConflictIds?.[group.id]));
  const [faqActivity, setFaqActivity] = useState([]);
  const [disabledFlowIds, setDisabledFlowIds] = useState([]);
  const [resolvedGapIds, setResolvedGapIds] = useState([]);
  const actionableGaps = CONVERSATION_GAPS.filter(gap => !["generated", "update", "merged", "source-only"].includes(gap.state) && !resolvedGapIds.includes(gap.id));
  const [search, setSearch] = useState("");
  const [typeFilter, setTypeFilter] = useState("all");
  const [languageFilter, setLanguageFilter] = useState("all");
  const [statusFilter, setStatusFilter] = useState("all");
  const [categoryFilter, setCategoryFilter] = useState("all");
  const [sourceLabelFilter, setSourceLabelFilter] = useState("all");
  const [routeFilter, setRouteFilter] = useState("all");
  const [impactFilter, setImpactFilter] = useState("all");
  const [showSourceFilters, setShowSourceFilters] = useState(false);
  const [reviewPane, setReviewPane] = useState("library");
  const [similarFocusId, setSimilarFocusId] = useState(null);
  const [similarDecisionOpen, setSimilarDecisionOpen] = useState(null);
  const [faqOpen, setFaqOpen] = useState(null);
  const [gapOpen, setGapOpen] = useState(null);
  const [relatedDoc, setRelatedDoc] = useState(null);
  const [stopDocOpen, setStopDocOpen] = useState(null);
  useEffect(() => {
    const t = intent.tab || window.BeaconNav?.intent("train")?.tab;
    if (t) setTab(t);
    if (intent.pane) setReviewPane(intent.pane);
    if (intent.faqId) {
      const target = faqs.find(item => item.id === intent.faqId);
      if (target) setFaqOpen({ ...target, viewOnly: Boolean(intent.viewOnly) });
    }
    if (intent.gapId) {
      const target = CONVERSATION_GAPS.find(item => item.id === intent.gapId);
      if (target) setGapOpen(target);
    }
    if (intent.similarFaqId) {
      setReviewPane("similarity");
      setSimilarFocusId(intent.similarFaqId);
    }
    if (intent.documentId) {
      const target = documents.find(item => item.id === intent.documentId);
      if (target) setDoc(target);
    }
  }, [intent.tab, intent.pane, intent.faqId, intent.gapId, intent.similarFaqId, intent.documentId, intent.viewOnly]);
  const openTarget = (section, nextTab) => window.BeaconNav?.go(section, nextTab ? { tab: nextTab } : {});
  const removeFAQ = (id) => {
    const removed = faqs.find(faq => faq.id === id);
    setFaqs(items => items.filter(faq => faq.id !== id));
    setSimilarGroups(items => items.filter(group => group.leftId !== id && group.rightId !== id));
    FAQ_SIMILAR_GROUPS
      .filter(group => group.state === "conflict" && (group.leftId === id || group.rightId === id))
      .forEach(group => { window.PROTOTYPE_STATE.resolvedQaConflictIds[group.id] = true; });
    Object.keys(window.PROTOTYPE_STATE.dynamicQaConflictIds)
      .filter(groupId => groupId.includes(id))
      .forEach(groupId => { delete window.PROTOTYPE_STATE.dynamicQaConflictIds[groupId]; });
    window.removePrototypeContentRef(id);
    if (removed?.status === "pending") {
      setFaqTotals(current => ({
        ...current,
        total: Math.max(0, current.total - (removed.logicalId ? 0 : 1)),
        pending: Math.max(0, current.pending - 1),
      }));
    }
  };
  const rejectFAQ = (id, reason) => {
    const rejected = faqs.find(item => item.id === id);
    setFaqActivity(items => [{
      action: rejected?.createMode === "系统建议" ? "拒绝系统建议" : "放弃待审稿",
      target: faqDisplayId(rejected) || id,
      reason,
      by: "当前操作人",
      at: "刚刚",
    }, ...items].slice(0, 5));
    removeFAQ(id);
  };
  const mergeFAQ = (sourceId, targetId) => {
    const source = faqs.find(item => item.id === sourceId);
    if (!source) return;
    setFaqs(items => items
      .filter(item => item.id !== sourceId)
      .map(item => item.id === targetId
        ? {
            ...item,
            aliases: [...new Set([...(item.aliases || []), source.q, ...(source.aliases || [])])],
            sourceRefs: [...new Set([...(item.sourceRefs || []), ...(source.sourceRefs || [])])],
            mergedSignals: [...new Set([...(item.mergedSignals || []), source.trigger, source.evidence].filter(Boolean))],
            history: [{
              action: "合并重复候选",
              source: faqDisplayId(source),
              reason: "确认重复，复用目标待审稿",
              by: "当前操作人",
              at: "刚刚",
            }, ...(item.history || [])],
            updatedAt: "刚刚",
          }
        : item));
    setSimilarGroups(items => items.filter(group => group.leftId !== sourceId && group.rightId !== sourceId));
    setFaqActivity(items => [{
      action: "合并重复候选",
      target: faqDisplayId(source),
      reason: `合并到 ${targetId}`,
      by: "当前操作人",
      at: "刚刚",
    }, ...items].slice(0, 5));
    window.markPrototypeChange({ kind: "qa", contentRefs: [targetId] });
  };
  const useExistingFAQ = (draftId, groupId, reason) => {
    const draft = faqs.find(item => item.id === draftId);
    setFaqActivity(items => [{
      action: "复用已有问答",
      target: faqDisplayId(draft) || draftId,
      reason,
      by: "当前操作人",
      at: "刚刚",
    }, ...items].slice(0, 5));
    removeFAQ(draftId);
    setSimilarGroups(items => items.filter(group => group.id !== groupId));
    window.PROTOTYPE_STATE.resolvedQaConflictIds[groupId] = true;
    delete window.PROTOTYPE_STATE.dynamicQaReviewIds[groupId];
    delete window.PROTOTYPE_STATE.dynamicQaConflictIds[groupId];
    setFaqOpen(null);
  };
  const applySimilarityDecision = ({ group, decision, reason }) => {
    const left = faqs.find(item => item.id === group.leftId);
    const right = faqs.find(item => item.id === group.rightId);
    const editable = left?.status === "pending" ? left : right?.status === "pending" ? right : null;
    if (decision === "existing" && editable) {
      useExistingFAQ(editable.id, group.id, reason);
      setSimilarDecisionOpen(null);
      return;
    }
    setSimilarGroups(items => items.map(item => {
      if (item.id !== group.id) return item;
      const audit = { decisionReason: reason, decisionBy: "当前操作人", decisionAt: "刚刚" };
      if (decision === "independent") {
        delete window.PROTOTYPE_STATE.dynamicQaReviewIds[group.id];
        delete window.PROTOTYPE_STATE.dynamicQaConflictIds[group.id];
        window.PROTOTYPE_STATE.resolvedQaConflictIds[group.id] = true;
        return {
          ...item,
          ...audit,
          state: "independent",
          conditionRelation: "用户问题不同",
          resolvedDecision: "已确认保留独立",
          recommendation: "用户问题或会改变答案的条件不同，保留两条问答",
        };
      }
      delete window.PROTOTYPE_STATE.dynamicQaReviewIds[group.id];
      window.PROTOTYPE_STATE.dynamicQaConflictIds[group.id] = true;
      delete window.PROTOTYPE_STATE.resolvedQaConflictIds[group.id];
      return {
        ...item,
        ...audit,
        state: "conflict",
        conditionRelation: "同一用户问题",
        answerRelation: "答案冲突",
        recommendation: "阻止当前草稿发布，修正答案或使用已有问答",
      };
    }));
    window.invalidatePrototypeTests();
    setSimilarDecisionOpen(null);
  };
  const updateFAQBatch = (ids, change) => {
    setFaqs(items => items.map(item => {
      if (!ids.includes(item.id)) return item;
      if (change.type === "disable" && item.status !== "published") return item;
      if (change.type === "tag-add") {
        const tags = [...new Set([...(item.tags || []), change.value])];
        return { ...item, tags, updatedAt: "刚刚" };
      }
      if (change.type === "tag-remove") {
        const tags = (item.tags || []).filter(tag => tag !== change.value);
        return { ...item, tags, updatedAt: "刚刚" };
      }
      if (change.type === "topic") return { ...item, topic: change.value, updatedAt: "刚刚" };
      if (change.type === "disable") return { ...item, status: "disabled", updatedAt: "刚刚" };
      return item;
    }));
    if (change.type === "disable") {
      window.markPrototypeChange({ kind: "qa", contentRefs: ids });
    }
  };
  const saveFAQ = (draft) => {
    const nextId = draft.id || `FAQ-${Math.max(...faqs.map(item => Number((item.logicalId || item.id).replace(/\D/g, "")) || 0)) + 1}`;
    const nextDraft = {
      ...draft,
      id: nextId,
      version: draft.version || "v1 待审核",
      liveVersion: draft.liveVersion || "无",
      liveStatus: draft.liveStatus || "尚未发布",
      draftStep: draft.draftStep || "编辑中",
      status: draft.status || "pending",
      history: draft.suggestionGapId ? [{
        action: "从系统建议人工创建",
        source: draft.suggestionGapId,
        reason: draft.evidence || draft.decision || "人工确认需要维护问答",
        by: "当前操作人",
        at: "刚刚",
      }, ...(draft.history || [])] : draft.history,
    };
    const draftIdentity = nextDraft.logicalId || nextDraft.id;
    const existingGroupsByTarget = new Map(similarGroups
      .filter(group => group.leftId === nextDraft.id || group.rightId === nextDraft.id)
      .map(group => [group.leftId === nextDraft.id ? group.rightId : group.leftId, group]));
    const allRefreshedGroups = faqs
      .filter(candidate => candidate.id !== nextDraft.id)
      .filter(candidate => (candidate.logicalId || candidate.id) !== draftIdentity)
      .map(candidate => {
        const existing = existingGroupsByTarget.get(candidate.id);
        const compared = compareFaqCandidate(nextDraft, candidate) || existing;
        if (!compared) return null;
        const groupId = existing?.id || `DYN-${[nextDraft.id, candidate.id].sort().join("-")}`;
        return {
          ...existing,
          ...compared,
          id: groupId,
          leftId: nextDraft.id,
          rightId: candidate.id,
          difference: compared.state === "conflict"
            ? "同一业务条件下的答案不一致。"
            : compared.state === "duplicate"
              ? "问题、答案和支持范围已经接近。"
              : "内容相似，但业务条件不同或仍待判断。",
          recommendation: compared.state === "conflict"
            ? "阻止当前草稿发布，继续修正答案或范围"
            : compared.state === "duplicate"
              ? "使用已有问答，或确认当前草稿是否仍有独立价值"
              : "确认业务条件不同后可以保持独立",
        };
      })
      .filter(Boolean)
      .sort((left, right) => right.rankScore - left.rankScore);
    const refreshedGroups = allRefreshedGroups.filter((group, index) => index < 5 || group.state === "conflict");
    existingGroupsByTarget.forEach((group, targetId) => {
      const refreshed = refreshedGroups.find(item => item.id === group.id);
      if (group.state !== "conflict") return;
      if (refreshed?.state === "conflict") delete window.PROTOTYPE_STATE.resolvedQaConflictIds[group.id];
      else window.PROTOTYPE_STATE.resolvedQaConflictIds[group.id] = true;
    });
    Object.keys(window.PROTOTYPE_STATE.dynamicQaConflictIds)
      .filter(groupId => groupId.includes(nextDraft.id))
      .forEach(groupId => { delete window.PROTOTYPE_STATE.dynamicQaConflictIds[groupId]; });
    Object.keys(window.PROTOTYPE_STATE.dynamicQaReviewIds)
      .filter(groupId => groupId.includes(nextDraft.id))
      .forEach(groupId => { delete window.PROTOTYPE_STATE.dynamicQaReviewIds[groupId]; });
    refreshedGroups
      .filter(group => group.state === "conflict" && group.id.startsWith("DYN-"))
      .forEach(group => { window.PROTOTYPE_STATE.dynamicQaConflictIds[group.id] = true; });
    refreshedGroups
      .filter(group => ["review", "duplicate"].includes(group.state) && group.id.startsWith("DYN-"))
      .forEach(group => { window.PROTOTYPE_STATE.dynamicQaReviewIds[group.id] = true; });
    setSimilarGroups(items => [
      ...items.filter(group => group.leftId !== nextDraft.id && group.rightId !== nextDraft.id),
      ...refreshedGroups,
    ]);
    setFaqs(items => draft.id
      ? items.map(item => item.id === draft.id ? nextDraft : item)
      : [...items, nextDraft]);
    if (!draft.id) {
      setFaqTotals(current => ({
        ...current,
        total: current.total + (draft.logicalId ? 0 : 1),
        pending: current.pending + 1,
      }));
    }
    window.markPrototypeChange({
      kind: "qa",
      contentRefs: [faqDisplayId(nextDraft) || nextDraft.id],
      sourceRevisions: nextDraft.sourceRefs || [],
    });
    if (draft.suggestionGapId) setResolvedGapIds(items => [...new Set([...items, draft.suggestionGapId])]);
    setFaqOpen(null);
  };
  const createFAQVersion = (faq) => {
    const logicalId = faq.logicalId || faq.id;
    const activeDraft = faqs.find(item => item.status === "pending" && (item.logicalId || item.id) === logicalId);
    if (activeDraft && activeDraft.id !== faq.id) {
      setFaqOpen(activeDraft);
      return;
    }
    setFaqOpen({
      ...faq,
      id: "",
      logicalId,
      version: "新版本待审核",
      liveVersion: faq.status === "published" ? faq.version : faq.liveVersion || faq.version,
      liveStatus: faq.status === "published" ? "已发布" : faq.liveStatus || "已停用",
      draftStep: "编辑中",
      status: "pending",
      createMode: "人工创建",
    });
  };
  const activeDoc = documents.find(item => item.id === doc?.id) || documents[0];
  const visibleDocuments = documents.filter(item => {
    const query = search.trim().toLowerCase();
    const matchesSearch = !query || [item.title, item.id, item.category, ...(item.sourceTags || [])].some(value => value.toLowerCase().includes(query));
    const matchesType = typeFilter === "all" || item.type === typeFilter;
    const matchesLanguage = languageFilter === "all" || item.language === languageFilter;
    const matchesStatus = statusFilter === "all" || documentUseState(item) === statusFilter;
    const matchesCategory = categoryFilter === "all" || item.category === categoryFilter;
    const matchesSourceLabel = sourceLabelFilter === "all" || (item.sourceTags || []).includes(sourceLabelFilter);
    const matchesRoute = routeFilter === "all" || documentAnswerMode(item) === routeFilter;
    const hasImpact = documentHasImpact(item);
    const matchesImpact = impactFilter === "all"
      || (impactFilter === "with" && hasImpact)
      || (impactFilter === "without" && !hasImpact);
    return matchesSearch && matchesType && matchesLanguage && matchesStatus && matchesCategory && matchesSourceLabel && matchesRoute && matchesImpact;
  });
  useEffect(() => {
    if (visibleDocuments.length && !visibleDocuments.some(item => item.id === activeDoc.id)) {
      setDoc(visibleDocuments[0]);
    }
  }, [search, typeFilter, languageFilter, statusFilter, categoryFilter, sourceLabelFilter, routeFilter, impactFilter, documents, activeDoc.id]);
  const selectDocument = (item) => {
    setDoc(item);
  };
  const stopDocumentUse = (id) => {
    const stoppedDocument = documents.find(item => item.id === id);
    const affectedIds = faqs
      .filter(item => item.status === "published" && (item.sourceRefs || []).some(ref => ref.startsWith(id)))
      .map(item => item.id);
    const affectedFlowIds = SKILLS
      .filter(item => ["published", "review"].includes(item.status) && (item.sourceRefs || []).some(ref => ref.startsWith(id)))
      .map(item => item.id);
    setDocuments(items => items.map(item => item.id === id
      ? {
          ...item,
          status: "retired",
          answerReason: "已紧急停止用于回答；原始内容和上游标签仍只读保留。",
          route: "不用于回答",
          impact: affectedIds.length || affectedFlowIds.length ? `${affectedIds.length} 条问答、${affectedFlowIds.length} 个处理流程已停用` : "0 个当前引用内容",
        }
      : item));
    setDoc(current => current?.id === id ? { ...current, status: "retired", route: "不用于回答" } : current);
    if (affectedIds.length) {
      setFaqs(items => items.map(item => affectedIds.includes(item.id)
        ? {
            ...item,
            status: "disabled",
            updatedAt: "刚刚",
            disableReason: `引用的 ${id} 已停止用于回答`,
          }
        : item));
      setFaqTotals(current => ({
        ...current,
        published: Math.max(0, current.published - affectedIds.length),
        disabled: current.disabled + affectedIds.length,
      }));
    }
    if (affectedFlowIds.length) setDisabledFlowIds(items => [...new Set([...items, ...affectedFlowIds])]);
    window.markPrototypeChange({
      kind: "source",
      contentRefs: [...affectedIds, ...affectedFlowIds],
      sourceRevisions: stoppedDocument ? [`${id} 上游修订 ${stoppedDocument.upstreamRevision}`] : [id],
    });
    setStopDocOpen(null);
  };
  const refreshDocument = (id) => {
    setDocuments(items => items.map(item => item.id === id
      ? item.syncStatus === "failed"
        ? { ...item, syncIssue: "刚刚重试失败；仍保留上次成功同步的修订、回答用途和引用影响" }
        : { ...item, syncStatus: "normal", updated: "刚刚", change: "已重新读取上游状态、修订、栏目和标签；回答用途已按规则重新计算" }
      : item));
  };
  const secondarySourceFilterCount = [typeFilter, languageFilter, categoryFilter, sourceLabelFilter].filter(value => value !== "all").length;
  const sourceFilterCount = secondarySourceFilterCount + [statusFilter, routeFilter, impactFilter].filter(value => value !== "all").length;
  const clearSourceFilters = () => {
    setTypeFilter("all");
    setLanguageFilter("all");
    setStatusFilter("all");
    setCategoryFilter("all");
    setSourceLabelFilter("all");
    setRouteFilter("all");
    setImpactFilter("all");
  };
  if (sel) return <SkillDetail skill={sel} onBack={() => setSel(null)} />;

  const statusBadge = (s) => ({
    published: <Badge tone="green" dot="var(--green)">已发布</Badge>,
    review: <Badge tone="amber" dot="var(--amber)">审核中</Badge>,
    shadow: <Badge tone="purple" dot="var(--purple)">对照测试中</Badge>,
    blocked: <Badge tone="red" dot="var(--red)">禁止保存</Badge>,
  })[s.status];
  const primaryAction = tab === "skills"
    ? <button className="btn primary sm" onClick={() => openTarget("test", "regression")}><Icons.flask s={14} /> 运行测试</button>
    : tab === "review"
      ? <button className="btn primary sm" onClick={() => setFaqOpen({ createMode: "人工创建", language: "中文", risk: "低风险" })}><Icons.doc s={14} /> 新建问答</button>
      : <button className="btn primary sm" onClick={() => refreshDocument(activeDoc.id)}><Icons.refresh s={14} /> 重新同步</button>;

  return (
    <div className="content-pad fadein">
      <window.PageHead title="训练" sub="先维护问答，再处理流程和来源影响。"
        right={primaryAction} />

      <window.Tabs active={tab} onChange={setTab} tabs={[
        { id: "review", label: "问答库", count: faqTotals.total.toLocaleString() },
        { id: "skills", label: "处理流程" },
        { id: "documents", label: "来源" },
      ]} />

      {tab === "documents" && (
        <div className="captain-knowledge">
          <div className="captain-board-head">
            <div className="captain-source-title">
              <div className="captain-title">来源</div>
              <div>内容、修订、栏目和标签由 Zendesk 或原始系统维护；这里查看系统计算的回答用途和引用影响。</div>
            </div>
            <div className="row captain-actions">
              <div className="captain-search"><Icons.search s={14} /><input aria-label="搜索来源" value={search} onChange={event => setSearch(event.target.value)} placeholder="搜索标题、编号、栏目或标签" /></div>
              <button className="btn primary sm" onClick={() => refreshDocument(activeDoc.id)}><Icons.refresh s={13} /> 重新同步</button>
            </div>
          </div>

          <div className="captain-filters captain-primary-filters">
            <select className="captain-filter" aria-label="筛选来源同步状态" value={statusFilter} onChange={event => setStatusFilter(event.target.value)}>
              <option value="all">全部同步状态</option>
              <option value="normal">同步正常</option>
              <option value="calculating">用途计算中</option>
              <option value="failed">同步异常</option>
            </select>
            <select className="captain-filter" aria-label="筛选回答方式" value={routeFilter} onChange={event => setRouteFilter(event.target.value)}>
              <option value="all">全部回答用途</option>
              <option>低风险引用</option>
              <option>实时来源</option>
              <option>仅供维护问答 / 流程</option>
              <option>不用于回答</option>
            </select>
            <select className="captain-filter" aria-label="筛选引用影响" value={impactFilter} onChange={event => setImpactFilter(event.target.value)}>
              <option value="all">全部引用影响</option>
              <option value="with">有受影响内容</option>
              <option value="without">无受影响内容</option>
            </select>
            <button className={"btn sm" + (showSourceFilters ? " active" : "")} aria-expanded={showSourceFilters} onClick={() => setShowSourceFilters(value => !value)}>
              <Icons.sliders s={13} /> 更多筛选{secondarySourceFilterCount ? ` ${secondarySourceFilterCount}` : ""}
            </button>
            {sourceFilterCount ? <button className="btn sm ghost" onClick={clearSourceFilters}>清除筛选</button> : null}
          </div>

          {showSourceFilters ? (
            <div className="captain-secondary-filters">
              <select className="captain-filter" aria-label="筛选来源类型" value={typeFilter} onChange={event => setTypeFilter(event.target.value)}>
                <option value="all">全部来源类型</option>
                {[...new Set(documents.map(item => item.type))].map(value => <option key={value}>{value}</option>)}
              </select>
              <select className="captain-filter" aria-label="筛选语言" value={languageFilter} onChange={event => setLanguageFilter(event.target.value)}>
                <option value="all">全部语言</option>
                {[...new Set(documents.map(item => item.language))].map(value => <option key={value}>{value}</option>)}
              </select>
              <select className="captain-filter" aria-label="筛选上游栏目" value={categoryFilter} onChange={event => setCategoryFilter(event.target.value)}>
                <option value="all">全部上游栏目</option>
                {[...new Set(documents.map(item => item.category))].map(value => <option key={value}>{value}</option>)}
              </select>
              <select className="captain-filter" aria-label="筛选上游标签" value={sourceLabelFilter} onChange={event => setSourceLabelFilter(event.target.value)}>
                <option value="all">全部上游标签</option>
                {[...new Set(documents.flatMap(item => item.sourceTags || []))].map(value => <option key={value}>{value}</option>)}
              </select>
            </div>
          ) : null}

          <div className="captain-body">
            <div className="captain-list">
              {visibleDocuments.map(d => (
                <DocumentRow key={d.id} doc={d} active={activeDoc.id === d.id} onSelect={() => selectDocument(d)} onRelated={() => { selectDocument(d); setRelatedDoc(d.id); }} onRefresh={() => refreshDocument(d.id)} onStop={() => setStopDocOpen(d)} />
              ))}
              {!visibleDocuments.length ? <window.EmptyState title="没有符合条件的来源" sub="调整搜索词或筛选条件后重试。" /> : null}

              <div className="captain-pagination">
                <span>显示第 1 - {visibleDocuments.length} 条，共 {visibleDocuments.length} 条</span>
                <div className="row" style={{ gap: 8 }}>
                  <button className="btn sm ghost" disabled><Icons.chevR s={13} style={{ transform: "rotate(180deg)" }} /></button>
                  <span className="mono">1 / 1</span>
                  <button className="btn sm ghost" disabled><Icons.chevR s={13} /></button>
                </div>
              </div>
            </div>

            <div className="captain-side">
              <DocumentDetail
                doc={activeDoc}
                faqs={faqs}
                flows={SKILLS}
                focusRelated={relatedDoc === activeDoc.id}
                onOpenFAQ={(faq) => setFaqOpen(faq)}
                onOpenFlow={(flow) => setSel(flow)}
                onRefresh={() => refreshDocument(activeDoc.id)}
                onStop={() => setStopDocOpen(activeDoc)}
              />
            </div>
          </div>
        </div>
      )}

      {tab === "review" && (
        <div className="faq-workspace">
          <div className="faq-workspace-tabs" aria-label="问答库视图">
            <button className={reviewPane === "library" ? "active" : ""} onClick={() => setReviewPane("library")}>问答库 <span className="mono">{faqTotals.total.toLocaleString()}</span></button>
            <button className={reviewPane === "suggestions" ? "active" : ""} onClick={() => setReviewPane("suggestions")}>系统建议 <span className="mono">{actionableGaps.length}</span></button>
            <button className={reviewPane === "similarity" ? "active" : ""} onClick={() => { setSimilarFocusId(null); setReviewPane("similarity"); }}>相似项 <span className="mono">{FAQ_LIBRARY_TOTALS.similarGroups - (FAQ_SIMILAR_GROUPS.length - similarGroups.length)}</span></button>
          </div>
          {reviewPane === "library" ? (
            <FAQListPanel
              faqs={faqs}
              totals={faqTotals}
              similarGroups={similarGroups}
              onCreateFAQ={(f) => setFaqOpen(f)}
              onCreateVersion={createFAQVersion}
              onRunTests={() => openTarget("test", "regression")}
              onRejectRequest={(f) => setFaqOpen({ ...f, action: "reject" })}
              onMergeRequest={(f) => setFaqOpen({ ...f, action: "merge" })}
              onShowSimilar={(faqId) => { setSimilarFocusId(faqId); setReviewPane("similarity"); }}
              onBatchUpdate={updateFAQBatch}
              activity={faqActivity}
            />
          ) : null}
          {reviewPane === "suggestions" ? <ConversationGapPanel gaps={actionableGaps} onOpenGap={setGapOpen} /> : null}
          {reviewPane === "similarity" ? (
            <SimilarityPanel
              groups={similarGroups}
              faqs={faqs}
              focusId={similarFocusId}
              onClearFocus={() => setSimilarFocusId(null)}
              onOpenFAQ={setFaqOpen}
              onRequestDecision={(group, decision) => setSimilarDecisionOpen({ group, decision })}
            />
          ) : null}
        </div>
      )}

      {tab === "skills" && (
        <div className="card">
          <div className="card-head"><Icons.layers s={15} /><span className="card-title">处理流程</span><span className="card-sub">{SKILLS.length} 个处理流程</span></div>
          <table className="tbl">
            <thead><tr><th>处理流程</th><th>场景</th><th>重新测试通过率</th><th>安全规则</th><th>状态</th><th></th></tr></thead>
            <tbody>
              {SKILLS.map(s => {
                return (
                <tr key={s.id} style={{ cursor: "pointer" }} onClick={() => setSel(s)}>
                  <td><div style={{ fontWeight: 600 }}>{s.name}</div><div className="muted" style={{ fontSize: 11 }}>研发标识：<span className="mono">{s.key}</span> · {s.id} · {s.ver}</div></td>
                  <td><div>{s.uc}</div><div className="muted" style={{ fontSize: 11.5, marginTop: 2 }}>按适用条件和工具结果分支处理</div></td>
                  <td><div className="row" style={{ gap: 7 }}><div style={{ width: 56 }}><window.Meter pct={s.pass} color={s.pass >= 90 ? "var(--green)" : s.pass >= 80 ? "var(--blue)" : "var(--orange)"} /></div><span style={{ fontSize: 12, fontWeight: 600 }}>{s.pass}%</span></div></td>
                  <td>{s.redlines.length ? <span className="status" title={s.redlines.join(" · ")}><Icons.lock s={12} className="status-ico" />{s.redlines.length} 条安全规则</span> : <span className="muted">—</span>}</td>
                  <td>{disabledFlowIds.includes(s.id) ? <Badge tone="red" dot="var(--red)">已停用</Badge> : statusBadge(s)}</td>
                  <td><Icons.chevR s={15} className="muted" /></td>
                </tr>
                );
              })}
            </tbody>
          </table>
        </div>
      )}
      {faqOpen ? <FAQModal key={`${faqOpen.id || faqOpen.logicalId || "new"}-${faqOpen.action || "view"}`} seed={faqOpen} targets={faqs} similarGroups={similarGroups} onClose={() => setFaqOpen(null)} onSave={saveFAQ} onCreateVersion={createFAQVersion} onOpenFAQ={setFaqOpen} onReject={(id, reason) => { rejectFAQ(id, reason); setFaqOpen(null); }} onMerge={(sourceId, targetId) => { mergeFAQ(sourceId, targetId); setFaqOpen(null); }} /> : null}
      {gapOpen ? (
        <SuggestionDetailModal
          gap={gapOpen}
          faqs={faqs}
          onClose={() => setGapOpen(null)}
          onOpenFAQ={(faq) => { setGapOpen(null); setFaqOpen(faq); }}
          onOpenFlow={() => {
            setGapOpen(null);
            setTab("skills");
          }}
          onManualCreate={(g) => {
            setGapOpen(null);
            setFaqOpen({
              createMode: "人工创建",
              q: g.intent,
              scope: g.scope,
              trigger: g.signal,
              evidence: g.basis,
              decision: g.decision,
              suggestionGapId: g.id,
              risk: "高风险",
            });
          }}
        />
      ) : null}
      {stopDocOpen ? (
        <SourceStopModal
          doc={stopDocOpen}
          affectedFaqs={faqs.filter(item => item.status === "published" && (item.sourceRefs || []).some(ref => ref.startsWith(stopDocOpen.id)))}
          affectedFlows={SKILLS.filter(item => ["published", "review"].includes(item.status) && (item.sourceRefs || []).some(ref => ref.startsWith(stopDocOpen.id)))}
          onClose={() => setStopDocOpen(null)}
          onConfirm={() => stopDocumentUse(stopDocOpen.id)}
        />
      ) : null}
      {similarDecisionOpen ? (
        <SimilarityDecisionModal
          group={similarDecisionOpen.group}
          decision={similarDecisionOpen.decision}
          faqs={faqs}
          onClose={() => setSimilarDecisionOpen(null)}
          onConfirm={(reason) => applySimilarityDecision({ ...similarDecisionOpen, reason })}
        />
      ) : null}
    </div>
  );
}

function SourceStopModal({ doc, affectedFaqs, affectedFlows, onClose, onConfirm }) {
  const Icons = window.Icons;
  return (
    <div className="modal-scrim" onClick={onClose}>
      <div className="modal" onClick={event => event.stopPropagation()}>
        <div className="modal-head">
          <div>
            <div style={{ fontWeight: 700, fontSize: 15 }}>停止使用这个来源？</div>
            <div className="muted" style={{ fontSize: 12.5, marginTop: 4 }}>{doc.id} · {doc.title}</div>
          </div>
          <button className="btn ghost sm" aria-label="关闭停止来源弹窗" onClick={onClose}><Icons.x s={16} /></button>
        </div>
        <div className="modal-body">
          <p style={{ marginTop: 0 }}>停止后，这个来源立即不再参与回答。明确引用当前修订的已发布问答也会立即停用。</p>
          <div className="source-stop-list">
            {affectedFaqs.length ? affectedFaqs.map(item => (
              <div key={item.id} className="spread rc-row">
                <span>{faqDisplayId(item)} · {item.q}</span>
                <window.Badge tone="red">将停用</window.Badge>
              </div>
            )) : null}
            {affectedFlows.length ? affectedFlows.map(item => (
              <div key={item.id} className="spread rc-row">
                <span>{item.name} · {item.ver}</span>
                <window.Badge tone="red">将停用</window.Badge>
              </div>
            )) : null}
            {!affectedFaqs.length && !affectedFlows.length ? <div className="note">当前没有问答或处理流程明确引用这个修订。</div> : null}
          </div>
          <div className="note" style={{ marginTop: 12 }}><Icons.refresh s={13} /> 恢复回答不能直接撤销本次操作；需要重新同步来源，并让受影响问答创建新版本、重新测试和发布。</div>
        </div>
        <div className="modal-foot">
          <span className="muted" style={{ fontSize: 12 }}>本次操作和受影响问答会进入历史记录。</span>
          <div className="row" style={{ gap: 8 }}>
            <button className="btn sm" onClick={onClose}>取消</button>
            <button className="btn danger sm" onClick={onConfirm}>停止使用来源</button>
          </div>
        </div>
      </div>
    </div>
  );
}

function DocumentDetail({ doc, faqs, flows, focusRelated, onOpenFAQ, onOpenFlow, onRefresh, onStop }) {
  const Icons = window.Icons;
  const [detailTab, setDetailTab] = useState(focusRelated ? "related" : "overview");
  const related = faqs.filter(f => (f.sourceRefs || []).some(ref => ref.startsWith(doc.id)));
  const relatedFlows = (flows || []).filter(flow => (flow.sourceRefs || []).some(ref => ref.startsWith(doc.id)));
  useEffect(() => {
    if (focusRelated) setDetailTab("related");
  }, [focusRelated, doc.id]);
  return (
    <div className="card">
      <div className="card-head"><Icons.doc s={15} /><span className="card-title">来源详情（只读）</span><span className="card-sub">{doc.id}</span></div>
      <div className="card-pad">
        <div className="source-detail-title">{doc.title}</div>
        <div className="source-detail-tabs">
          <button className={detailTab === "overview" ? "active" : ""} onClick={() => setDetailTab("overview")}>概览</button>
          <button className={detailTab === "related" ? "active" : ""} onClick={() => setDetailTab("related")}>引用内容 {related.length + relatedFlows.length}</button>
          <button className={detailTab === "history" ? "active" : ""} onClick={() => setDetailTab("history")}>同步记录</button>
        </div>
        {detailTab === "overview" ? (
          <>
            <div className="source-use-summary">
              <SourceSyncBadge doc={doc} />
              <SourceAnswerBadge status={doc.status} />
              <span>{documentAnswerMode(doc)}</span>
            </div>
            <div className="note"><Icons.lock s={13} style={{ flexShrink: 0 }} /> 内容、修订号、栏目和标签都由原始系统维护；回答用途由规则计算，本页不审核或编辑文章。</div>
            <dl className="kv source-kv">
              <dt>原始来源</dt><dd>{doc.type}</dd>
              <dt>语言</dt><dd>{doc.language}</dd>
              <dt>上游栏目</dt><dd>{doc.category}</dd>
              <dt>上游标签</dt><dd>{(doc.sourceTags || []).join(" / ") || "无"}</dd>
              <dt>上游修订</dt><dd className="mono">{doc.upstreamRevision}</dd>
              <dt>最后同步</dt><dd>{doc.updated}</dd>
              {doc.syncIssue ? <><dt>同步异常</dt><dd>{doc.syncIssue}</dd></> : null}
              <dt>回答用途</dt><dd>{documentAnswerMode(doc)} · {doc.answerReason}</dd>
              <dt>引用影响</dt><dd>{doc.impact}</dd>
              {doc.realtimeSource ? <><dt>实时信息</dt><dd>{doc.realtimeSource}</dd><dt>实时状态</dt><dd>{doc.realtimeHealth}</dd></> : null}
            </dl>
            <div className="source-excerpt">
              <b>原文摘录</b>
              <p>{doc.excerpt}</p>
            </div>
          </>
        ) : null}
        {detailTab === "related" ? (
          <div className="related-faq source-related">
            {related.length ? related.map(f => (
              <button key={f.id} className="related-faq-row" onClick={() => onOpenFAQ(f)}>
                <span>{faqDisplayId(f)} · {f.q}</span>
                <FAQStatusBadge faq={f} />
              </button>
            )) : null}
            {relatedFlows.length ? relatedFlows.map(flow => (
              <button key={flow.id} className="related-faq-row" onClick={() => onOpenFlow(flow)}>
                <span>{flow.name} · {flow.ver}</span>
                <window.Badge tone={flow.status === "published" ? "green" : "amber"}>{flow.status === "published" ? "已发布" : "待审核"}</window.Badge>
              </button>
            )) : null}
            {!related.length && !relatedFlows.length ? <span className="muted" style={{ fontSize: 12.5 }}>没有问答或处理流程引用这个来源。问答可以独立存在。</span> : null}
          </div>
        ) : null}
        {detailTab === "history" ? (
          <div className="source-history">
            <div><span className="mono">上游修订 {doc.upstreamRevision}</span><b>{doc.change}</b><small>同步于 {doc.updated}</small></div>
            <div><span>当前用途</span><b>{doc.route}</b><small>{doc.answerReason}</small></div>
          </div>
        ) : null}
        <div className="doc-actions">
          <a className="btn sm" href={`https://support.bitmart.com/hc/zh-cn/search?query=${encodeURIComponent(doc.title)}`} target="_blank" rel="noreferrer"><Icons.globe s={13} /> 在原始系统查看全文</a>
          <button className="btn sm" onClick={() => setDetailTab("related")}><Icons.link s={13} /> 查看引用内容</button>
          <button className="btn sm" onClick={onRefresh}><Icons.refresh s={13} /> 重新同步</button>
          {documentCanBeStopped(doc) ? <button className="btn sm danger" onClick={onStop}><Icons.x s={13} /> 停止用于回答</button> : null}
        </div>
      </div>
    </div>
  );
}

function DocumentRow({ doc, active, onSelect, onRelated, onRefresh, onStop }) {
  const Icons = window.Icons;
  const [menu, setMenu] = useState(false);
  return (
    <button className={"captain-doc-row" + (active ? " active" : "")} onClick={onSelect}>
      <div className="captain-doc-main">
        <div className="captain-doc-title">{doc.title}</div>
        <div className="captain-doc-meta">
          <span><Icons.globe s={12} /> {doc.language}</span>
          <span><Icons.link s={12} /> {doc.type}</span>
          <span><Icons.shield s={12} /> {documentAnswerMode(doc)}</span>
          <span className="mono">{doc.id}</span>
        </div>
      </div>
      <div className="captain-doc-stats">
        <SourceSyncBadge doc={doc} />
        <SourceAnswerBadge status={doc.status} />
        <span className="mono muted">上游修订 {doc.upstreamRevision}</span>
        <span className="muted">{doc.category} · {(doc.sourceTags || []).join(" / ")}</span>
        <span className="muted">{doc.impact}</span>
        <span className="muted"><Icons.refresh s={12} /> 同步于 {doc.updated}</span>
        <span className="captain-menu-wrap" onClick={(e) => { e.stopPropagation(); setMenu(!menu); }}>
          <span className="captain-more"><Icons.sliders s={13} /></span>
          {menu ? (
            <span className="captain-popover">
              <span onClick={(e) => { e.stopPropagation(); setMenu(false); onRelated(); }}><Icons.link s={13} /> 查看引用内容</span>
              <span onClick={(e) => { e.stopPropagation(); setMenu(false); onRefresh(); }}><Icons.refresh s={13} /> 重新同步</span>
              {documentCanBeStopped(doc) ? <span className="danger" onClick={(e) => { e.stopPropagation(); setMenu(false); onStop(); }}><Icons.x s={13} /> 停止用于回答</span> : null}
            </span>
          ) : null}
        </span>
      </div>
    </button>
  );
}

function SourceSyncBadge({ doc }) {
  const { Badge } = window;
  const state = documentUseState(doc);
  if (state === "failed") return <Badge tone="amber" dot="var(--amber)">同步异常</Badge>;
  if (state === "calculating") return <Badge tone="gray">用途计算中</Badge>;
  return <Badge tone="green" dot="var(--green)">同步正常</Badge>;
}

function SourceAnswerBadge({ status }) {
  const { Badge } = window;
  const map = {
    synced: <Badge tone="gray">暂不回答</Badge>,
    current: <Badge tone="green" dot="var(--green)">用途已计算</Badge>,
    "needs-review": <Badge tone="red" dot="var(--red)">引用已阻断</Badge>,
    expired: <Badge tone="red" dot="var(--red)">上游不可用</Badge>,
    retired: <Badge tone="gray">已停止用于回答</Badge>,
  };
  return map[status] || <Badge tone="gray">{status}</Badge>;
}

function FAQStatusBadge({ faq }) {
  const { Badge } = window;
  if (faq.status === "published") return <Badge tone="green">已发布</Badge>;
  if (faq.status === "disabled") return <Badge tone="red">已停用</Badge>;
  return <Badge tone="amber">待审核</Badge>;
}

function FAQListPanel({ faqs, totals, similarGroups, onCreateFAQ, onShowSimilar, onBatchUpdate, activity }) {
  const Icons = window.Icons;
  const [search, setSearch] = useState("");
  const [statusFilter, setStatusFilter] = useState("all");
  const [languageFilter, setLanguageFilter] = useState("all");
  const [topicFilter, setTopicFilter] = useState("all");
  const [scopeFilters, setScopeFilters] = useState([]);
  const [riskFilter, setRiskFilter] = useState("all");
  const [sourceFilter, setSourceFilter] = useState("all");
  const [similarFilter, setSimilarFilter] = useState("all");
  const [usageMin, setUsageMin] = useState("");
  const [usageMax, setUsageMax] = useState("");
  const [updatedFilter, setUpdatedFilter] = useState("all");
  const [sort, setSort] = useState("priority");
  const [selected, setSelected] = useState([]);
  const [batchMessage, setBatchMessage] = useState("");
  const [batchMode, setBatchMode] = useState("");
  const [batchValue, setBatchValue] = useState("");
  const [batchOperation, setBatchOperation] = useState("add");
  const [page, setPage] = useState(1);
  const prototypePageSize = 5;
  const conflictIds = new Set(similarGroups.filter(group => group.state === "conflict").flatMap(group => [group.leftId, group.rightId]));
  const similarCountFor = (faqId) => similarGroups.filter(group => group.leftId === faqId || group.rightId === faqId).length;
  const filteredFaqs = faqs
    .filter(faq => {
      const query = search.trim().toLowerCase();
      const searchText = [faq.id, faq.q, faq.answer, ...(faq.aliases || [])].join(" ").toLowerCase();
      const hasSource = (faq.sourceRefs || []).length > 0;
      const currentSimilarCount = similarCountFor(faq.id);
      const matchesSimilarity = similarFilter === "all"
        || (similarFilter === "has" && currentSimilarCount > 0)
        || (similarFilter === "conflict" && conflictIds.has(faq.id))
        || (similarFilter === "none" && currentSimilarCount === 0);
      const usage = faq.usage30d || 0;
      const matchesUsage = (!usageMin || usage >= Number(usageMin))
        && (!usageMax || usage <= Number(usageMax));
      const updatedDay = faq.updatedAt === "刚刚" ? 23 : Number((faq.updatedAt || "").slice(3, 5)) || 0;
      const matchesUpdated = updatedFilter === "all"
        || (updatedFilter === "7d" && updatedDay >= 16)
        || (updatedFilter === "older" && updatedDay > 0 && updatedDay < 16);
      return (!query || searchText.includes(query))
        && (statusFilter === "all" || faq.status === statusFilter)
        && (languageFilter === "all" || faq.language === languageFilter)
        && (topicFilter === "all" || faq.topic === topicFilter)
        && scopeFilters.every(scope => (faq.tags || []).includes(scope))
        && (riskFilter === "all" || faq.risk === riskFilter)
        && (sourceFilter === "all" || (sourceFilter === "with" ? hasSource : !hasSource))
        && matchesSimilarity
        && matchesUsage
        && matchesUpdated;
    })
    .sort((left, right) => {
      if (sort === "priority") {
        const priority = item => {
          if (conflictIds.has(item.id)) return 0;
          if (item.status === "pending") return 1;
          if (item.status === "disabled") return 2;
          return 3;
        };
        const rank = priority(left) - priority(right);
        if (rank !== 0) return rank;
        const usageRank = (right.usage30d || 0) - (left.usage30d || 0);
        if (usageRank !== 0) return usageRank;
        return right.updatedAt.localeCompare(left.updatedAt);
      }
      if (sort === "updated") return right.updatedAt.localeCompare(left.updatedAt);
      if (sort === "similar") return similarCountFor(right.id) - similarCountFor(left.id);
      return (right.usage30d || 0) - (left.usage30d || 0);
    });
  const hasNarrowFilter = search.trim() || usageMin || usageMax || scopeFilters.length > 0 || [languageFilter, topicFilter, riskFilter, sourceFilter, similarFilter, updatedFilter].some(value => value !== "all");
  const resultCount = hasNarrowFilter
    ? filteredFaqs.length
    : statusFilter === "all"
      ? totals.total
      : totals[statusFilter];
  const samplePageCount = Math.max(1, Math.ceil(filteredFaqs.length / prototypePageSize));
  const visibleFaqs = filteredFaqs.slice((page - 1) * prototypePageSize, page * prototypePageSize);
  useEffect(() => {
    setSelected([]);
    setBatchMode("");
    setPage(1);
  }, [search, statusFilter, languageFilter, topicFilter, scopeFilters.join("|"), riskFilter, sourceFilter, similarFilter, usageMin, usageMax, updatedFilter, sort]);
  const toggleSelection = (id) => setSelected(items => items.includes(id) ? items.filter(item => item !== id) : [...items, id]);
  const selectAll = () => setSelected(items => items.length === visibleFaqs.length ? [] : visibleFaqs.map(faq => faq.id));
  const addScopeFilter = (value) => {
    if (!value || scopeFilters.includes(value)) return;
    setScopeFilters(items => [...items, value]);
  };
  const applyBatch = () => {
    if (batchMode === "disable") {
      const eligible = selected.filter(id => faqs.find(item => item.id === id)?.status === "published");
      onBatchUpdate(eligible, { type: "disable" });
      setBatchMessage(eligible.length
        ? `已停用 ${eligible.length} 条已发布问答；待审核和已停用项未重复处理。发布前需要重新测试覆盖变化。`
        : "当前选择中没有可停用的已发布问答。");
    } else if (batchMode === "topic" && batchValue) {
      onBatchUpdate(selected, { type: "topic", value: batchValue });
      setBatchMessage(`已更新 ${selected.length} 条问答的主题为“${batchValue}”；答案版本不变，操作已记录。`);
    } else if (batchMode === "tag" && batchValue.trim()) {
      onBatchUpdate(selected, { type: `tag-${batchOperation}`, value: batchValue.trim() });
      setBatchMessage(`已为 ${selected.length} 条问答${batchOperation === "add" ? "增加" : "移除"}标签“${batchValue.trim()}”；答案版本不变，操作已记录。`);
    } else {
      return;
    }
    setBatchMode("");
    setBatchValue("");
  };
  return (
    <div className="card qa-library-card">
      <div className="card-head">
        <Icons.flag s={15} />
        <span className="card-title">问答库</span>
        <span className="card-sub">{totals.total.toLocaleString()} 条问答 · {totals.pending} 个待审核版本</span>
        <button className="btn primary sm" style={{ marginLeft: "auto" }} onClick={() => onCreateFAQ({ createMode: "人工创建", language: "中文", risk: "低风险" })}><Icons.doc s={13} /> 新建问答</button>
      </div>
      <div className="qa-library-tools">
        <div className="qa-search"><Icons.search s={14} /><input value={search} onChange={event => setSearch(event.target.value)} placeholder="搜索问题、答案、常见问法或编号" /></div>
        <select aria-label="筛选问答状态" value={statusFilter} onChange={event => setStatusFilter(event.target.value)}>
          <option value="all">全部状态</option>
          <option value="pending">待审核</option>
          <option value="published">已发布</option>
          <option value="disabled">已停用</option>
        </select>
        <select aria-label="筛选问答语言" value={languageFilter} onChange={event => setLanguageFilter(event.target.value)}>
          <option value="all">全部语言</option>
          {[...new Set(faqs.map(faq => faq.language))].map(value => <option key={value}>{value}</option>)}
        </select>
        <select aria-label="筛选问答主题" value={topicFilter} onChange={event => setTopicFilter(event.target.value)}>
          <option value="all">全部主题</option>
          {[...new Set(faqs.map(faq => faq.topic))].map(value => <option key={value}>{value}</option>)}
        </select>
        <div className="qa-scope-filter">
          <select aria-label="添加标签筛选" value="" onChange={event => addScopeFilter(event.target.value)}>
            <option value="">添加标签筛选</option>
            {[...new Set(faqs.flatMap(faq => faq.tags || []))]
              .filter(value => !scopeFilters.includes(value))
              .map(value => <option key={value}>{value}</option>)}
          </select>
          {scopeFilters.map(value => (
            <button key={value} className="qa-filter-chip" aria-label={`移除标签筛选 ${value}`} onClick={() => setScopeFilters(items => items.filter(item => item !== value))}>
              {value}<Icons.x s={11} />
            </button>
          ))}
        </div>
        <select aria-label="筛选问答风险" value={riskFilter} onChange={event => setRiskFilter(event.target.value)}>
          <option value="all">全部风险</option>
          <option>低风险</option>
          <option>高风险</option>
        </select>
        <select aria-label="筛选问答来源" value={sourceFilter} onChange={event => setSourceFilter(event.target.value)}>
          <option value="all">全部来源</option>
          <option value="with">有来源引用</option>
          <option value="without">无来源引用</option>
        </select>
        <select aria-label="筛选相似问答" value={similarFilter} onChange={event => setSimilarFilter(event.target.value)}>
          <option value="all">全部相似状态</option>
          <option value="has">有相似项</option>
          <option value="conflict">答案冲突</option>
          <option value="none">无相似项</option>
        </select>
          <label className="qa-number-filter">近30天命中
            <input aria-label="近三十天最低命中量" type="number" min="0" value={usageMin} onChange={event => setUsageMin(event.target.value)} placeholder="最低" />
            <span>至</span>
            <input aria-label="近三十天最高命中量" type="number" min="0" value={usageMax} onChange={event => setUsageMax(event.target.value)} placeholder="最高" />
          </label>
        <select aria-label="筛选更新时间" value={updatedFilter} onChange={event => setUpdatedFilter(event.target.value)}>
          <option value="all">全部更新时间</option>
          <option value="7d">最近 7 天</option>
          <option value="older">7 天前</option>
        </select>
      </div>
      <div className="qa-list-summary">
        <span>{hasNarrowFilter ? <>当前筛选 <b>{filteredFaqs.length}</b> 条</> : <>全库 <b>{resultCount.toLocaleString()}</b> 条</>}</span>
        <label>排序
          <select value={sort} onChange={event => setSort(event.target.value)}>
            <option value="priority">待处理优先</option>
            <option value="usage">近 30 天命中</option>
            <option value="updated">最近更新</option>
            <option value="similar">相似项数量</option>
          </select>
        </label>
      </div>
      {selected.length ? (
        <div className="qa-batch-bar">
          <span>当前页已选 {selected.length} 条</span>
          <button className="btn sm" onClick={() => { setBatchMode("tag"); setBatchValue(""); }}>批量改标签</button>
          <button className="btn sm" onClick={() => { setBatchMode("topic"); setBatchValue(""); }}>调整主题</button>
          <button className="btn sm danger" onClick={() => { setBatchMode("disable"); setBatchValue(""); }}>批量停用</button>
          <button className="btn sm ghost" onClick={() => setSelected([])}>取消选择</button>
        </div>
      ) : null}
      {batchMode ? (
        <div className="qa-batch-editor">
          <b>{batchMode === "tag" ? "修改标签" : batchMode === "topic" ? "调整主题" : "确认批量停用"}</b>
          {batchMode === "tag" ? (
            <select aria-label="选择批量操作" value={batchOperation} onChange={event => setBatchOperation(event.target.value)}>
              <option value="add">增加</option>
              <option value="remove">移除</option>
            </select>
          ) : null}
          {batchMode === "tag" ? (
            <select aria-label="批量标签" value={batchValue} onChange={event => setBatchValue(event.target.value)}>
              <option value="">选择已有标签</option>
              {[...new Set(faqs.flatMap(faq => faq.tags || []))].map(value => <option key={value}>{value}</option>)}
            </select>
          ) : null}
          {batchMode === "topic" ? (
            <select aria-label="批量主题" value={batchValue} onChange={event => setBatchValue(event.target.value)}>
              <option value="">选择主题</option>
              {[...new Set(faqs.map(faq => faq.topic).filter(Boolean))].map(value => <option key={value}>{value}</option>)}
            </select>
          ) : null}
          {batchMode === "disable" ? <span>只停用选中的已发布问答；待审核版本应放弃，已停用版本不会重复处理。</span> : null}
          <button className="btn primary sm" onClick={applyBatch} disabled={batchMode !== "disable" && !batchValue.trim()}>应用到 {selected.length} 条</button>
          <button className="btn sm ghost" onClick={() => setBatchMode("")}>取消</button>
        </div>
      ) : null}
      {batchMessage ? <div className="note qa-batch-note"><Icons.checkCircle s={13} /> {batchMessage}</div> : null}
      {activity?.length ? (
        <div className="note qa-batch-note">
          <Icons.checkCircle s={13} />
          最近处理：{activity[0].action} · {activity[0].target} · {activity[0].reason} · {activity[0].by} · {activity[0].at}
        </div>
      ) : null}
      <div className="qa-table-wrap">
        <table className="tbl qa-table">
          <thead>
            <tr>
              <th><input type="checkbox" aria-label="选择当前页问答" checked={visibleFaqs.length > 0 && selected.length === visibleFaqs.length} onChange={selectAll} /></th>
              <th>问答</th>
              <th>状态</th>
              <th>主题与标签</th>
              <th>风险</th>
              <th>近 30 天命中</th>
              <th>相似项</th>
              <th>版本与更新</th>
              <th></th>
            </tr>
          </thead>
          <tbody>
            {visibleFaqs.map(faq => (
              <tr key={faq.id} onClick={() => onCreateFAQ(faq)}>
                <td onClick={event => event.stopPropagation()}><input type="checkbox" aria-label={`选择 ${faq.id}`} checked={selected.includes(faq.id)} onChange={() => toggleSelection(faq.id)} /></td>
                <td>
                  <div className="qa-question">{faq.q}</div>
                  <div className="qa-row-sub"><span className="mono">{faq.logicalId || faq.id}</span><span>{(faq.aliases || []).length} 个常见问法</span></div>
                </td>
                <td><FAQStatusBadge faq={faq} /></td>
                <td>
                  <div className="qa-classification">{faq.language} · {faq.topic}</div>
                  <div className="qa-row-sub">{(faq.tags || []).length ? faq.tags.join(" / ") : "无标签"}</div>
                </td>
                <td><span className={faq.risk === "高风险" ? "qa-risk high" : "qa-risk"}>{faq.risk}</span></td>
                <td><span className="mono qa-usage">{(faq.usage30d || 0).toLocaleString()}</span></td>
                <td onClick={event => event.stopPropagation()}>
                  {similarCountFor(faq.id) ? <button className={"qa-similar-link" + (conflictIds.has(faq.id) ? " conflict" : "")} onClick={() => onShowSimilar(faq.id)}>{conflictIds.has(faq.id) ? "答案冲突" : `${similarCountFor(faq.id)} 条相似`}</button> : <span className="muted">无</span>}
                </td>
                <td><div className="mono">{faq.version}</div><div className="qa-row-sub">{faq.status === "pending" && faq.draftStep ? `${faq.draftStep} · ` : ""}{faq.updatedAt}</div></td>
                <td><button className="btn sm ghost" onClick={event => { event.stopPropagation(); onCreateFAQ(faq); }}>查看</button></td>
              </tr>
            ))}
          </tbody>
        </table>
        {!visibleFaqs.length ? <window.EmptyState title="没有符合条件的问答" sub="调整搜索词或筛选条件后重试。" /> : null}
      </div>
      <div className="captain-pagination">
        <span>当前展示第 {(page - 1) * prototypePageSize + (visibleFaqs.length ? 1 : 0)} - {(page - 1) * prototypePageSize + visibleFaqs.length} 条 · 全库 {resultCount.toLocaleString()} 条</span>
        <div className="row" style={{ gap: 8 }}>
          <button className="btn sm ghost" aria-label="上一页" disabled={page === 1} onClick={() => setPage(current => Math.max(1, current - 1))}><Icons.chevR s={13} style={{ transform: "rotate(180deg)" }} /></button>
          <span className="mono">{page} / {samplePageCount}</span>
          <button className="btn sm ghost" aria-label="下一页" disabled={page === samplePageCount} onClick={() => setPage(current => Math.min(samplePageCount, current + 1))}><Icons.chevR s={13} /></button>
        </div>
      </div>
    </div>
  );
}

function ConversationGapPanel({ gaps, onOpenGap }) {
  const { Badge } = window;
  const Icons = window.Icons;
  const actionLabel = (gap) => gap.state === "merged"
    ? "合并待审稿"
    : gap.state === "generated" || gap.state === "update"
      ? "创建或更新草稿"
      : gap.state === "failed"
        ? "判断失败"
      : "不创建草稿";
  const actionTone = (gap) => gap.state === "merged"
    ? "blue"
    : gap.state === "generated" || gap.state === "update"
      ? "green"
      : gap.state === "failed"
        ? "red"
      : gap.state === "manual"
        ? "amber"
        : "gray";
  return (
    <div className="card">
      <div className="card-head"><Icons.search s={15} /><span className="card-title">系统建议</span><span className="card-sub">只处理经过筛选的问题组，不逐会话生成</span></div>
      <div className="suggestion-summary">
        系统先检查答案依据和相似问答，再决定合并待审稿、创建或更新草稿，或者不创建草稿。高风险和答案冲突只指出缺口，不自动写答案。
      </div>
      <div className="qa-table-wrap">
        <table className="tbl suggestion-table">
          <thead><tr><th>问题组</th><th>触发信号</th><th>答案依据</th><th>匹配现有问答</th><th>建议动作</th><th></th></tr></thead>
          <tbody>
            {gaps.map(gap => (
              <tr key={gap.id} onClick={() => onOpenGap(gap)}>
                <td><div className="qa-question">{gap.intent}</div><div className="qa-row-sub"><span className="mono">{gap.id}</span><span>{gap.language} · {gap.scope}</span></div></td>
                <td>{gap.signal}</td>
                <td>{gap.basis}</td>
                <td>{gap.match}</td>
                <td><Badge tone={actionTone(gap)}>{actionLabel(gap)}</Badge><div className="qa-row-sub">{gap.action}</div></td>
                <td><button className="btn sm ghost" onClick={event => { event.stopPropagation(); onOpenGap(gap); }}>查看</button></td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>
    </div>
  );
}

function SimilarityPanel({ groups, faqs, focusId, onClearFocus, onOpenFAQ, onRequestDecision }) {
  const { Badge } = window;
  const Icons = window.Icons;
  const [stateFilter, setStateFilter] = useState("all");
  const focusedGroups = focusId ? groups.filter(group => group.leftId === focusId || group.rightId === focusId) : groups;
  const visibleGroups = stateFilter === "all" ? focusedGroups : focusedGroups.filter(group => group.state === stateFilter);
  const label = {
    duplicate: ["近似重复", "blue"],
    conflict: ["答案冲突", "red"],
    independent: ["相似但独立", "gray"],
    review: ["待人工判断", "amber"],
  };
  return (
    <div className="card">
      <div className="card-head"><Icons.link s={15} /><span className="card-title">相似项</span><span className="card-sub">系统只查找候选和排序；人决定复用已有、保持独立或标记答案冲突</span></div>
      <div className="similarity-toolbar">
        {[["all", "全部"], ["review", "待人工判断"], ["conflict", "答案冲突"], ["duplicate", "近似重复"], ["independent", "相似但独立"]].map(([id, text]) => (
          <button key={id} className={"pill" + (stateFilter === id ? " active" : "")} onClick={() => setStateFilter(id)}>{text}</button>
        ))}
        {focusId ? <button className="pill active" onClick={onClearFocus}>只看 {focusId} ×</button> : null}
        <span>问题和答案排序分只决定展示顺序，不会自动合并或覆盖问答。</span>
      </div>
      <div className="qa-table-wrap">
        <table className="tbl similarity-table">
          <thead><tr><th>相似问答</th><th>元数据</th><th>差异</th><th>判断</th><th>处理</th></tr></thead>
          <tbody>
            {visibleGroups.map(group => {
              const left = faqs.find(faq => faq.id === group.leftId);
              const right = faqs.find(faq => faq.id === group.rightId);
              const [defaultLabel, defaultTone] = label[group.state];
              const needsReview = group.state === "review";
              const stateLabel = defaultLabel;
              const tone = defaultTone;
              const editable = left?.status === "pending" ? left : right?.status === "pending" ? right : left;
              return (
                <tr key={group.id}>
                  <td>
                    <button className="similar-question" onClick={() => onOpenFAQ(left)}>{left?.q}</button>
                    <button className="similar-question" onClick={() => onOpenFAQ(right)}>{right?.q}</button>
                    <div className="qa-row-sub"><span className="mono">问题排序 {group.questionScore ?? group.similarity} · 答案排序 {group.answerScore ?? "—"}</span><span>{group.leftId} / {group.rightId}</span></div>
                  </td>
                  <td>{group.metadata}</td>
                  <td>
                    <div className="similarity-signals">
                      <span>{group.questionRelation}</span>
                      <span className={group.answerRelation === "答案冲突" ? "danger" : ""}>{group.answerRelation}</span>
                      <span>{group.conditionRelation}</span>
                    </div>
                    <div>{group.difference}</div>
                  </td>
                  <td><Badge tone={tone}>{stateLabel}</Badge><div className="qa-row-sub">{group.recommendation}</div></td>
                  <td>
                    {group.resolvedDecision ? (
                      <span className="qa-resolved">
                        <span><Icons.checkCircle s={13} /> {group.resolvedDecision}</span>
                        <small>{group.decisionReason || "已记录处理理由"} · {group.decisionBy || "当前操作人"} · {group.decisionAt || "刚刚"}</small>
                      </span>
                    ) : (
                      <div className="similar-actions">
                        <button className="btn sm" onClick={() => onOpenFAQ(editable)}>打开对比项</button>
                        {["duplicate", "conflict"].includes(group.state) && editable?.status === "pending" ? <button className="btn sm ghost" onClick={() => onRequestDecision(group, "existing")}>使用已有</button> : null}
                        {needsReview && editable?.status === "pending" ? <button className="btn sm ghost" onClick={() => onRequestDecision(group, "existing")}>复用已有</button> : null}
                        {needsReview ? <button className="btn sm ghost" onClick={() => onRequestDecision(group, "independent")}>保持独立</button> : null}
                        {needsReview ? <button className="btn sm danger" onClick={() => onRequestDecision(group, "conflict")}>标记答案冲突</button> : null}
                        {group.state === "conflict" ? <button className="btn sm danger" onClick={() => onOpenFAQ(editable)}>修正草稿</button> : null}
                      </div>
                    )}
                  </td>
                </tr>
              );
            })}
          </tbody>
        </table>
      </div>
    </div>
  );
}

function SimilarityDecisionModal({ group, decision, faqs, onClose, onConfirm }) {
  const Icons = window.Icons;
  const [reason, setReason] = useState("");
  const [differenceConfirmed, setDifferenceConfirmed] = useState(false);
  const left = faqs.find(item => item.id === group.leftId);
  const right = faqs.find(item => item.id === group.rightId);
  const title = decision === "existing" ? "复用已有问答" : decision === "independent" ? "保持两条问答独立" : "标记答案冲突";
  const action = decision === "existing" ? "确认复用" : decision === "independent" ? "确认保持独立" : "确认标记冲突";
  const canConfirm = reason.trim() && (decision !== "independent" || differenceConfirmed);
  return (
    <div className="modal-scrim" onClick={onClose}>
      <div className="modal faq-modal" onClick={event => event.stopPropagation()}>
        <div className="modal-head">
          <div><div style={{ fontWeight: 700, fontSize: 15 }}>{title}</div><div className="muted" style={{ fontSize: 12.5, marginTop: 4 }}>{group.leftId} / {group.rightId}</div></div>
          <button className="btn ghost sm" aria-label="关闭相似项处理弹窗" onClick={onClose}><Icons.x s={16} /></button>
        </div>
        <div className="modal-body">
          <div className="similarity-decision-pair">
            <div><b>{left?.q}</b><span>{left?.answer}</span></div>
            <div><b>{right?.q}</b><span>{right?.answer}</span></div>
          </div>
          {decision === "independent" ? (
            <label className="qa-tool-toggle"><input type="checkbox" checked={differenceConfirmed} onChange={event => setDifferenceConfirmed(event.target.checked)} /> 会改变答案的差异已经写进问题或答案</label>
          ) : null}
          <label className="form-field"><span>处理理由</span><textarea value={reason} onChange={event => setReason(event.target.value)} placeholder="说明为什么复用、保持独立或判定为答案冲突。" /></label>
          <div className="note"><Icons.shield s={13} /> 系统保存对比对象、决定、理由、实际操作人和时间；改变问题、答案、常见问法、语言或风险后会重新提示。</div>
        </div>
        <div className="modal-foot">
          <span className="muted" style={{ fontSize: 12 }}>没有理由时不能完成判断。</span>
          <div className="row" style={{ gap: 8 }}><button className="btn sm" onClick={onClose}>取消</button><button className={"btn sm " + (decision === "conflict" ? "danger" : "primary")} disabled={!canConfirm} onClick={() => onConfirm(reason.trim())}>{action}</button></div>
        </div>
      </div>
    </div>
  );
}

function SuggestionDetailModal({ gap, faqs, onClose, onOpenFAQ, onManualCreate, onOpenFlow }) {
  const Icons = window.Icons;
  const { Badge } = window;
  const candidate = faqs.find(f => f.id === gap.faqId);
  const resultTone = gap.state === "merged" ? "blue" : gap.state === "generated" || gap.state === "update" ? "green" : gap.state === "failed" ? "red" : gap.state === "manual" ? "amber" : "gray";
  const resultLabel = gap.state === "merged" ? "合并待审稿" : gap.state === "generated" || gap.state === "update" ? "创建或更新草稿" : gap.state === "failed" ? "判断失败" : "不创建草稿";

  return (
    <div className="modal-scrim" onClick={onClose}>
      <div className="modal faq-modal" onClick={e => e.stopPropagation()}>
        <div className="modal-head">
          <div><div style={{ fontWeight: 700, fontSize: 15 }}>系统建议</div><div className="muted" style={{ fontSize: 12.5, marginTop: 4 }}>{gap.id} · {gap.intent}</div></div>
          <button className="btn ghost sm" aria-label="关闭系统建议详情" onClick={onClose}><Icons.x s={16} /></button>
        </div>
        <div className="modal-body">
          <dl className="kv suggestion-kv">
            <dt>问题组</dt><dd>{gap.language} · {gap.scope}</dd>
            <dt>触发信号</dt><dd>{gap.signal}</dd>
            <dt>答案依据</dt><dd>{gap.basis}</dd>
            <dt>安全过滤</dt><dd>{gap.exclude}</dd>
            <dt>相似问答</dt><dd>{gap.match}</dd>
            <dt>建议动作</dt><dd><div className="row" style={{ gap: 8, flexWrap: "wrap" }}><Badge tone={resultTone}>{resultLabel}</Badge><span>{gap.action}</span></div></dd>
            <dt>下一步</dt><dd>{gap.nextAction}</dd>
            <dt>重新判断</dt><dd>{gap.recheck}</dd>
          </dl>
          <div className="note" style={{ marginTop: 14 }}><Icons.shield s={13} style={{ flexShrink: 0 }} /> 问题量和负向反馈只决定优先处理什么；没有稳定低风险答案依据时不创建草稿。</div>
          {candidate ? (
            <div className="note" style={{ marginTop: 14 }}><Icons.checkCircle s={13} style={{ flexShrink: 0 }} /> 已形成 {faqDisplayId(candidate)} 待审稿；后续修改、放弃或合并都在这条问答中处理。</div>
          ) : null}
        </div>
        <div className="modal-foot">
          <span className="muted" style={{ fontSize: 12 }}>{candidate ? "待审稿已经进入问答库。" : "本次结论不会创建待审稿。"}</span>
          <div className="row" style={{ gap: 8 }}>
            <button className="btn sm" onClick={onClose}>关闭</button>
            {candidate ? <button className="btn primary sm" onClick={() => onOpenFAQ(candidate)}><Icons.chevR s={13} /> {gap.state === "merged" ? "打开已合并问答" : "打开待审核问答"}</button> : null}
            {gap.manual && gap.target !== "处理流程" ? <button className="btn primary sm" onClick={() => onManualCreate(gap)}><Icons.doc s={13} /> 人工创建问答</button> : null}
            {gap.manual && gap.target?.includes("处理流程") ? <button className="btn primary sm" onClick={onOpenFlow}><Icons.layers s={13} /> 前往处理流程</button> : null}
          </div>
        </div>
      </div>
    </div>
  );
}

function similarityTokens(value) {
  const normalized = (value || "").toLowerCase().replace(/[^\u4e00-\u9fffa-z0-9]+/g, "");
  if (!normalized) return new Set();
  if (normalized.length === 1) return new Set([normalized]);
  return new Set(Array.from({ length: normalized.length - 1 }, (_, index) => normalized.slice(index, index + 2)));
}

function textSimilarity(left, right) {
  const leftTokens = similarityTokens(left);
  const rightTokens = similarityTokens(right);
  if (!leftTokens.size || !rightTokens.size) return 0;
  const overlap = [...leftTokens].filter(token => rightTokens.has(token)).length;
  return Math.round((overlap / new Set([...leftTokens, ...rightTokens]).size) * 100);
}

function maxTextSimilarity(leftValues, rightValues) {
  return Math.max(0, ...leftValues.flatMap(left => rightValues.map(right => textSimilarity(left, right))));
}

function faqMeaningKey(faq) {
  const value = [faq.q, ...(faq.aliases || []), faq.answer].filter(Boolean).join(" ").toLowerCase();
  const rules = [
    ["refund-progress", [/退款/, /(处理中|进度|状态|批准|通过)/]],
    ["kyc-resubmit", [/(身份认证|认证)/, /(照片|模糊|重提|重新提交)/]],
    ["fiat-card-failure", [/(法币卡|银行卡)/, /(失败|验证|支付)/]],
    ["deposit-confirmation", [/(充值|到账)/, /(确认数|网络|未到账)/]],
    ["login-device", [/(登录|设备)/, /(移除|删除|管理)/]],
  ];
  return rules.find(([, patterns]) => patterns.every(pattern => pattern.test(value)))?.[0] || null;
}

function compareFaqCandidate(draft, candidate) {
  if (draft.language && candidate.language && draft.language !== candidate.language) return null;
  const draftMeaning = faqMeaningKey(draft);
  if (!draftMeaning || draftMeaning !== faqMeaningKey(candidate)) return null;
  const draftTags = draft.tags || [];
  const candidateTags = candidate.tags || [];
  const commonTags = draftTags.filter(tag => candidateTags.includes(tag));
  const topicMatch = Boolean(draft.topic && candidate.topic && draft.topic === candidate.topic);
  const questionScore = maxTextSimilarity([draft.q, ...(draft.aliases || [])].filter(Boolean), [candidate.q, ...(candidate.aliases || [])].filter(Boolean));
  const answerScore = textSimilarity(draft.answer, candidate.answer);
  const similarity = Math.max(questionScore, answerScore);
  const rankScore = similarity + (topicMatch ? 6 : 0) + (commonTags.length ? 4 : 0);
  return {
    similarity,
    rankScore,
    questionScore,
    answerScore,
    state: "review",
    metadata: [candidate.language, candidate.topic, ...(candidate.tags || [])].filter(Boolean).join(" · "),
    questionRelation: "问题需要比较",
    answerRelation: "答案需要比较",
    conditionRelation: "用户问题需要判断",
  };
}

function FAQModal({ seed, targets, similarGroups, onClose, onSave, onCreateVersion, onOpenFAQ, onReject, onMerge }) {
  const Icons = window.Icons;
  const { Badge } = window;
  const readOnly = seed?.status === "published" || seed?.status === "disabled" || seed?.viewOnly;
  const [rejectReason, setRejectReason] = useState("");
  const mergeTargets = (targets || []).filter(f => f.id !== seed?.id && f.status === "pending");
  const [mergeTarget, setMergeTarget] = useState(mergeTargets[0]?.id || "");
  const [risk, setRisk] = useState(seed?.risk === "高风险" || seed?.risk === "资金" ? "高风险" : "低风险");
  const [question, setQuestion] = useState(seed?.q || "");
  const [answer, setAnswer] = useState(seed?.answer || "");
  const [language, setLanguage] = useState(seed?.language || "中文");
  const [topic, setTopic] = useState(seed?.topic || "");
  const [tags, setTags] = useState((seed?.tags || []).join("、"));
  const [aliases, setAliases] = useState((seed?.aliases || []).join("\n"));
  const [sourceRefs, setSourceRefs] = useState((seed?.sourceRefs || []).join("\n"));
  const [allowedScope, setAllowedScope] = useState(seed?.scope || "");
  const [needsTool, setNeedsTool] = useState(Boolean(seed?.toolBranches && !seed.toolBranches.includes("不需要")));
  const [toolBranches, setToolBranches] = useState(seed?.toolBranches || "");
  const [confirmedNewTags, setConfirmedNewTags] = useState([]);
  const canonicalTopics = [...new Set((targets || []).map(item => item.topic).filter(Boolean))];
  const canonicalTags = [...new Set((targets || []).flatMap(item => item.tags || []))];
  const normalizeMetaValue = (value) => (value || "").toLowerCase().replace(/[^a-z0-9\u4e00-\u9fff]/g, "");
  const typedTags = tags.split(/[、,\n]/).map(value => value.trim()).filter(Boolean);
  const nearTagMatches = typedTags
    .filter(value => !canonicalTags.includes(value) && !confirmedNewTags.includes(value))
    .map(value => ({
      value,
      match: canonicalTags.find(existing => normalizeMetaValue(existing) === normalizeMetaValue(value)),
    }))
    .filter(item => item.match);
  const nearTopicMatch = topic
    && !canonicalTopics.includes(topic)
    && canonicalTopics.find(existing => normalizeMetaValue(existing) === normalizeMetaValue(topic));
  const contentRequiresHighRisk = /退款|赔偿|补偿|责任|批准|人工入账|恢复资产|找回资产/.test(`${question} ${answer}`);
  const effectiveHighRisk = risk === "高风险" || contentRequiresHighRisk;
  const canSave = question.trim()
    && answer.trim()
    && topic.trim()
    && (!effectiveHighRisk || allowedScope.trim());

  if (seed?.action === "reject") {
    return (
      <div className="modal-scrim" onClick={onClose}>
        <div className="modal faq-modal" onClick={e => e.stopPropagation()}>
          <div className="modal-head">
            <div><div style={{ fontWeight: 700, fontSize: 15 }}>{seed.createMode === "系统建议" ? "拒绝系统建议" : "放弃待审稿"}</div><div className="muted" style={{ fontSize: 12.5, marginTop: 4 }}>{faqDisplayId(seed)} · 处理结果只进入历史记录，不增加问答状态。</div></div>
            <button className="btn ghost sm" aria-label="关闭拒绝候选弹窗" onClick={onClose}><Icons.x s={16} /></button>
          </div>
          <div className="modal-body">
            <label className="form-field"><span>{seed.createMode === "系统建议" ? "拒绝原因" : "放弃原因"}</span><textarea value={rejectReason} onChange={e => setRejectReason(e.target.value)} placeholder="说明答案错误、条件不清、与现有问答重复或当前不值得维护的原因..." /></label>
            <div className="note"><Icons.refresh s={13} style={{ flexShrink: 0 }} /> 相同信号不会再次生成；只有来源、会话模式或人工指定问题发生实质变化时才重新评估。</div>
          </div>
          <div className="modal-foot">
            <span className="muted" style={{ fontSize: 12 }}>这条待审稿将从问答库移除，原因保留在处理历史。</span>
            <div className="row" style={{ gap: 8 }}><button className="btn sm" onClick={onClose}>取消</button><button className="btn danger sm" disabled={!rejectReason.trim()} onClick={() => onReject(seed.id, rejectReason.trim())}>{seed.createMode === "系统建议" ? "记录拒绝" : "确认放弃"}</button></div>
          </div>
        </div>
      </div>
    );
  }

  if (seed?.action === "merge") {
    return (
      <div className="modal-scrim" onClick={onClose}>
        <div className="modal faq-modal" onClick={e => e.stopPropagation()}>
          <div className="modal-head">
            <div><div style={{ fontWeight: 700, fontSize: 15 }}>合并重复候选</div><div className="muted" style={{ fontSize: 12.5, marginTop: 4 }}>{faqDisplayId(seed)} · 只能合并到已有待审稿，不修改已发布版本。</div></div>
            <button className="btn ghost sm" aria-label="关闭合并候选弹窗" onClick={onClose}><Icons.x s={16} /></button>
          </div>
          <div className="modal-body">
            <label className="form-field"><span>合并到待审稿</span><select value={mergeTarget} onChange={e => setMergeTarget(e.target.value)}>{mergeTargets.map(f => <option key={f.id} value={f.id}>{faqDisplayId(f)} · {f.q}</option>)}</select></label>
            <div className="note"><Icons.link s={13} style={{ flexShrink: 0 }} /> 当前候选的触发信号和参考材料会加入目标问答，当前重复候选从待审核列表移除，处理历史保留本次合并。</div>
          </div>
          <div className="modal-foot">
            <span className="muted" style={{ fontSize: 12 }}>合并不会自动发布或改变目标问答状态。</span>
            <div className="row" style={{ gap: 8 }}><button className="btn sm" onClick={onClose}>取消</button><button className="btn primary sm" disabled={!mergeTarget} onClick={() => onMerge(seed.id, mergeTarget)}><Icons.link s={13} /> 确认合并</button></div>
          </div>
        </div>
      </div>
    );
  }

  const origin = seed?.createMode || "人工创建";
  const explicitGroups = new Map((similarGroups || [])
    .filter(group => group.leftId === seed?.id || group.rightId === seed?.id)
    .map(group => [group.leftId === seed?.id ? group.rightId : group.leftId, group]));
  const computedCandidates = (targets || [])
    .filter(item => item.id !== seed?.id)
    .filter(item => (item.logicalId || item.id) !== (seed?.logicalId || seed?.id))
    .map(item => {
      const rawGroup = compareFaqCandidate({
        q: question,
        answer,
        language,
        topic,
        tags: tags.split(/[、,\n]/).map(value => value.trim()).filter(Boolean),
        aliases: aliases.split("\n").map(value => value.trim()).filter(Boolean),
      }, item);
      const explicit = explicitGroups.get(item.id);
      const group = rawGroup && explicit ? { ...rawGroup, ...explicit } : rawGroup;
      if (!group) return null;
      return {
        faq: item,
        group: {
          ...group,
          difference: explicit?.difference,
          recommendation: explicit?.recommendation,
          id: explicit?.id || `DYN-${seed?.id || "NEW"}-${item.id}`,
        },
      };
    })
    .filter(Boolean)
    .sort((left, right) => right.group.rankScore - left.group.rankScore)
    .slice(0, 5);
  const similarCandidates = computedCandidates;
  const saveDraft = () => {
    onSave({
      ...seed,
      q: question.trim(),
      answer: answer.trim(),
      language,
      topic: topic.trim(),
      tags: tags.split(/[、,\n]/).map(value => value.trim()).filter(Boolean),
      aliases: aliases.split("\n").map(value => value.trim()).filter(Boolean),
      sourceRefs: sourceRefs.split("\n").map(value => value.trim()).filter(Boolean),
      risk: effectiveHighRisk ? "高风险" : risk,
      scope: effectiveHighRisk ? allowedScope.trim() : "",
      toolBranches: effectiveHighRisk && needsTool ? toolBranches.trim() : "",
      updatedAt: "刚刚",
      status: seed?.status || "pending",
    });
  };
  return (
    <div className="modal-scrim" onClick={onClose}>
      <div className="modal faq-modal qa-editor-modal" onClick={e => e.stopPropagation()}>
        <div className="modal-head">
          <div><div style={{ fontWeight: 700, fontSize: 15 }}>{readOnly ? `查看 ${faqDisplayId(seed)}` : seed?.id ? `编辑 ${faqDisplayId(seed)}` : seed?.logicalId ? `创建 ${seed.logicalId} 的新版本` : "创建待审核问答"}</div><div className="muted" style={{ fontSize: 12.5, marginTop: 4 }}>{readOnly ? "已发布和已停用版本只读；修改时创建待审核的新版本。" : `${origin} · 保存后仍需测试和发布。`}</div></div>
          <button className="btn ghost sm" aria-label="关闭常见问答弹窗" onClick={onClose}><Icons.x s={16} /></button>
        </div>
        <div className="modal-body">
          <div className="qa-editor-grid">
            <div className="qa-editor-fields">
              <label className="form-field"><span>问题</span><input value={question} readOnly={readOnly} onChange={e => setQuestion(e.target.value)} placeholder="客户会怎么问？" /></label>
              <label className="form-field"><span>答案</span><textarea value={answer} readOnly={readOnly} onChange={e => setAnswer(e.target.value)} placeholder="填写客户可以看到的答案" /></label>
              <div className="form-grid">
                <label className="form-field"><span>语言</span><select value={language} disabled={readOnly} onChange={e => setLanguage(e.target.value)}><option>中文</option><option>英文</option><option>俄语</option><option>越南语</option><option>西班牙语</option><option>葡萄牙语</option></select></label>
                <label className="form-field"><span>风险</span><select value={risk} disabled={readOnly} onChange={e => setRisk(e.target.value)}><option>低风险</option><option>高风险</option></select></label>
              </div>
              <label className="form-field"><span>主题</span><input list="faq-topic-options" value={topic} readOnly={readOnly} onChange={e => setTopic(e.target.value)} placeholder="优先选择已有主题，例如：退款状态" /></label>
              <datalist id="faq-topic-options">{canonicalTopics.map(value => <option key={value} value={value} />)}</datalist>
              {!readOnly && nearTopicMatch ? (
                <div className="qa-near-tag">
                  <span>“{topic}”接近已有主题“{nearTopicMatch}”。</span>
                  <button type="button" onClick={() => setTopic(nearTopicMatch)}>使用已有</button>
                </div>
              ) : null}
              <label className="form-field"><span>标签（可选）</span><input value={tags} readOnly={readOnly} onChange={e => setTags(e.target.value)} placeholder="用于筛选和相似排序，例如：法币卡、TRC20、中国大陆" /></label>
              {!readOnly ? (
                <div className="qa-meta-guidance">
                  <span>标签只帮助筛选和相似排序，不授权答案。会改变答案的条件应写进问题或答案，必要时拆成另一条问答。</span>
                  {nearTagMatches.map(item => (
                    <div key={item.value} className="qa-near-tag">
                      <span>“{item.value}”接近已有标签“{item.match}”。</span>
                      <button type="button" onClick={() => setTags(current => current.split(/[、,\n]/).map(value => value.trim()).filter(Boolean).map(value => value === item.value ? item.match : value).join("、"))}>使用已有</button>
                      <button type="button" onClick={() => setConfirmedNewTags(values => [...values, item.value])}>确认新增</button>
                    </div>
                  ))}
                  <div>
                    {canonicalTags.filter(value => !tags.split(/[、,\n]/).map(item => item.trim()).includes(value)).slice(0, 6).map(value => (
                      <button key={value} type="button" onClick={() => setTags(current => [...current.split(/[、,\n]/).map(item => item.trim()).filter(Boolean), value].join("、"))}>+ {value}</button>
                    ))}
                  </div>
                </div>
              ) : null}
              <label className="form-field compact-textarea"><span>常见问法（可选）</span><textarea value={aliases} readOnly={readOnly} onChange={e => setAliases(e.target.value)} placeholder="每行一个常见问法" /></label>
              <label className="form-field compact-textarea"><span>参考依据（可选）</span><textarea value={sourceRefs} readOnly={readOnly} onChange={e => setSourceRefs(e.target.value)} placeholder="每行一个上游来源修订或脱敏会话组" /></label>
              {contentRequiresHighRisk && risk !== "高风险" ? (
                <div className="gate red">
                  <div className="gate-ico"><Icons.lock s={16} /></div>
                  <div>问题或答案涉及资金结果、责任或资产处理，仍按高风险检查；把筛选标签改成低风险不能移除支持范围和红线。</div>
                </div>
              ) : null}
              {effectiveHighRisk ? (
                <div className="four-el">
                  <div className="four-el-h">高风险支持范围</div>
                  <label className="form-field"><span>这条问答支持什么</span><textarea value={allowedScope} readOnly={readOnly} onChange={e => setAllowedScope(e.target.value)} placeholder="写清允许表达的结论；当前问题不在这个范围内时停止回答。" /></label>
                  <label className="qa-tool-toggle"><input type="checkbox" checked={needsTool} disabled={readOnly} onChange={e => setNeedsTool(e.target.checked)} /> 涉及个案时需要查询当前事实</label>
                  {needsTool ? <label className="form-field"><span>工具结果对应的回答</span><textarea value={toolBranches} readOnly={readOnly} onChange={e => setToolBranches(e.target.value)} placeholder="分别写明已批准、处理中、已拒绝、失败或对象不匹配时可以说什么。" /></label> : null}
                </div>
              ) : null}
              <div className="qa-version-summary">
                <b>版本记录</b>
                <div><span>当前查看</span><strong>{faqDisplayId(seed) || "新问答待审稿"}</strong></div>
                {seed?.liveVersion ? <div><span>当前线上</span><strong>{seed.logicalId || seed.id} · {seed.liveVersion} · {seed.liveStatus}</strong></div> : null}
                <small>每条问答最多有一个待审核版本。历史版本只读；需要恢复旧内容时，以旧版本为起点创建新版本并重新测试，不直接回滚。</small>
                {(seed?.history || []).slice(0, 2).map((item, index) => (
                  <div key={index}><span>{item.action}</span><strong>{item.source || item.reason} · {item.by} · {item.at}</strong></div>
                ))}
              </div>
            </div>
            <aside className="qa-similar-panel">
              <div className="qa-similar-head"><div><b>相似问答</b><span>{similarCandidates.length} 条候选</span></div><Icons.search s={15} /></div>
              <p>先找同语言内容；主题和标签只帮助排序，缺少标签不会排除。问题、答案和业务条件分开判断。</p>
              {origin === "系统建议" || seed?.suggestionGapId ? (
                <div className="qa-suggestion-source">
                  <b>{origin === "系统建议" ? "系统建议依据" : "来自系统建议的人工创建入口"}</b>
                  <span>{seed.trigger || "筛选后的问题信号"}</span>
                  <span>{seed.evidence || seed.decision || "待确认答案依据"}</span>
                </div>
              ) : null}
              <div className="qa-similar-list">
                {similarCandidates.map(({ faq, group }) => (
                  <div key={faq.id} className={"qa-similar-card" + (group.state === "conflict" ? " conflict" : "")}>
                    <div className="spread"><span className="mono">{faq.logicalId || faq.id}</span><Badge tone={group.state === "conflict" ? "red" : "gray"}>问题排序 {group.questionScore ?? group.similarity}</Badge></div>
                    <b>{faq.q}</b>
                    <span>{faq.language} · {faq.topic} · {(faq.tags || []).join(" / ")}</span>
                    <div className="similarity-signals">
                      <span>{group.questionRelation || "问题可能接近"}</span>
                      <span className={group.answerRelation === "答案冲突" ? "danger" : ""}>{group.answerRelation || "答案待比较"}</span>
                      <span>{group.conditionRelation || "用户问题待判断"}</span>
                    </div>
                    <span className="muted" style={{ fontSize: 11.5 }}>答案排序 {group.answerScore ?? "—"} · 主题和标签只影响排序</span>
                    <small>{faq.answer}</small>
                    <div className="similar-actions">
                      <button className="btn sm" onClick={() => onOpenFAQ(faq)}>打开已有</button>
                    </div>
                  </div>
                ))}
                {!similarCandidates.length ? <div className="qa-similar-empty">没有找到需要优先处理的相似问答。</div> : null}
              </div>
              {similarCandidates.some(item => item.group.state === "conflict") ? <div className="gate red"><div className="gate-ico"><Icons.alert s={16} /></div><div>同一业务条件下的答案冲突未解决，不能发布。</div></div> : null}
              {similarCandidates.some(item => item.group.state === "review") ? <div className="note"><Icons.search s={13} /> 保存后到“相似项”完成复用已有、保持独立或标记冲突的判断。</div> : null}
            </aside>
          </div>
        </div>
        <div className="modal-foot">
          <span className="muted" style={{ fontSize: 12 }}>{readOnly ? "当前版本不能直接修改。" : "保存后仍是待审核问答；发布前还会再次检查相似项。"}</span>
          {readOnly ? (
            <div className="row" style={{ gap: 8 }}><button className="btn sm" onClick={onClose}>关闭</button><button className="btn primary sm" onClick={() => onCreateVersion(seed)}><Icons.refresh s={13} /> 创建新版本</button></div>
          ) : (
            <div className="row" style={{ gap: 8 }}>
              {seed?.id && seed.status === "pending" ? <button className="btn sm ghost" onClick={() => onOpenFAQ({ ...seed, action: "merge" })}>合并</button> : null}
              {seed?.id && seed.status === "pending" ? <button className="btn sm ghost" onClick={() => onOpenFAQ({ ...seed, action: "reject" })}>{origin === "系统建议" ? "拒绝建议" : "放弃草稿"}</button> : null}
              <button className="btn sm" onClick={onClose}>取消</button>
              <button className="btn primary sm" disabled={!canSave} onClick={saveDraft}>保存待审稿</button>
            </div>
          )}
        </div>
      </div>
    </div>
  );
}

/* ---- Single processing-flow detail ---- */
function SkillDetail({ skill, onBack }) {
  const { SKILL_SEGMENTS, AI_DIFF } = window.DATA;
  const { Badge, Redline } = window;
  const Icons = window.Icons;
  const [seg, setSeg] = useState(1);
  const nextVersion = (value) => {
    const current = Number((value || "").match(/\d+/)?.[0] || 0);
    return `v${current + 1} 草稿`;
  };
  const [suggestions, setSuggestions] = useState(skill.key === "kyc-failed" ? AI_DIFF : []);
  const [segments, setSegments] = useState(skill.segments || SKILL_SEGMENTS);
  const [editing, setEditing] = useState(skill.status !== "published");
  const [draftVersion, setDraftVersion] = useState(skill.status === "published" ? "" : skill.ver);
  const [decisions, setDecisions] = useState({});
  const [rejecting, setRejecting] = useState(null);
  const [rejectReason, setRejectReason] = useState("");
  const [testState, setTestState] = useState(skill.status === "published" ? "passed" : "failed");
  const [generatedAt, setGeneratedAt] = useState("基于当前草稿生成");
  const [saved, setSaved] = useState(false);

  const segCur = segments.find(s => s.n === seg);
  const stepBody = segments.find(item => item.n === 3)?.body || "";
  const exitBody = segments.find(item => item.n === 5)?.body || "";
  const hasUnsafePromise = /24\s*小时内通过|保证.{0,8}通过|承诺.{0,8}通过/.test(stepBody);
  const hasPolicyExit = /(政策例外|绕过)/.test(exitBody) && /(停止回答|转人工|人工复核)/.test(exitBody);
  const blockingUnresolved = (suggestion) => {
    if (suggestion.sev !== "blocking") return false;
    if (suggestion.type === "del") return hasUnsafePromise;
    if (suggestion.seg === 5) return !hasPolicyExit;
    return true;
  };
  const blockingPending = suggestions.filter(blockingUnresolved).length;
  const pending = suggestions.filter((suggestion, index) => !decisions[index] || blockingUnresolved(suggestion)).length;
  const unsafeIssues = [
    hasUnsafePromise ? "仍包含无依据的审核时限承诺" : "",
    !exitBody.trim() ? "不支持时的出口和红线不能为空" : "",
    exitBody.trim() && !hasPolicyExit ? "政策例外或绕过请求缺少停止回答或转人工出口" : "",
  ].filter(Boolean);
  const canSubmit = editing && blockingPending === 0 && unsafeIssues.length === 0 && testState === "passed";
  const draftStepLabel = saved ? "等待发布" : testState === "passed" ? "测试通过" : "需要重测";

  const acceptSuggestion = (i) => {
    const suggestion = suggestions[i];
    setSegments(items => items.map(item => item.n === suggestion.seg ? { ...item, body: suggestion.nextBody } : item));
    setDecisions(current => ({ ...current, [i]: { type: "accept" } }));
    setSeg(suggestion.seg);
    setTestState("stale");
    window.PROTOTYPE_STATE.skillDraftReady = false;
    window.markPrototypeChange({ kind: "flow", contentRefs: [`${skill.name} ${draftVersion || skill.ver}`] });
    setSaved(false);
  };
  const startReject = (i) => {
    setRejecting(i);
    setRejectReason("");
  };
  const confirmReject = (i) => {
    if (!rejectReason.trim()) return;
    setDecisions(current => ({ ...current, [i]: { type: "reject", reason: rejectReason.trim() } }));
    setRejecting(null);
    setRejectReason("");
    setSaved(false);
  };
  const updateSegment = (value) => {
    if (!editing) return;
    setSegments(items => items.map(item => item.n === seg ? { ...item, body: value } : item));
    setTestState("stale");
    window.PROTOTYPE_STATE.skillDraftReady = false;
    window.markPrototypeChange({ kind: "flow", contentRefs: [`${skill.name} ${draftVersion || skill.ver}`] });
    setSaved(false);
  };
  const runTests = () => {
    const ready = unsafeIssues.length === 0 && blockingPending === 0;
    setTestState(ready ? "passed" : "failed");
    window.PROTOTYPE_STATE.skillDraftReady = ready;
    window.PROTOTYPE_STATE.testSuitePassed = false;
    setSaved(false);
  };
  const refreshSuggestions = () => {
    const nextSuggestions = AI_DIFF.filter(suggestion => {
      const body = segments.find(item => item.n === suggestion.seg)?.body || "";
      if (suggestion.type === "del") return body.includes(suggestion.text);
      return !body.includes(suggestion.text);
    });
    setSuggestions(nextSuggestions);
    setDecisions({});
    setGeneratedAt("刚刚按当前草稿和最新依据检查");
  };

  return (
    <div className="content-pad fadein">
      <button className="btn sm ghost" onClick={onBack} style={{ marginBottom: 12 }}><Icons.chevR s={14} style={{ transform: "rotate(180deg)" }} /> 返回处理流程库</button>
      <div className="spread" style={{ marginBottom: 18, alignItems: "flex-start" }}>
        <div>
          <div className="row" style={{ gap: 10 }}>
            <h1 className="page-h1" style={{ fontSize: 19 }}>{skill.name}</h1>
            <span className="muted">{draftVersion || skill.ver}</span>
          </div>
          <div className="page-sub">当前线上 {skill.liveVersion || skill.ver} · {skill.liveStatus || "已发布"} · 本次 {draftVersion || "无待审稿"}{draftVersion ? ` · ${draftStepLabel}` : ""} · 研发标识：<span className="mono">{skill.key}</span></div>
        </div>
        <div className="row" style={{ gap: 8 }}>
          {!editing ? <button className="btn primary sm" onClick={() => { const version = nextVersion(skill.liveVersion || skill.ver); setDraftVersion(version); setEditing(true); setTestState("stale"); setGeneratedAt("创建草稿后尚未检查"); setSuggestions([]); window.markPrototypeChange({ kind: "flow", contentRefs: [`${skill.name} ${version}`] }); }}><Icons.refresh s={14} /> 创建新版本</button> : (
            <>
              <button className="btn sm" onClick={refreshSuggestions}><Icons.zap s={14} /> 重新生成建议</button>
              <button className="btn sm" onClick={runTests}><Icons.flask s={14} /> 重新测试</button>
              <button className="btn primary sm" disabled={!canSubmit} onClick={() => setSaved(true)}><Icons.checkCircle s={14} /> 保存并送审</button>
            </>
          )}
        </div>
      </div>

      <div className={"gate " + (canSubmit ? "" : "red")} style={{ marginBottom: 16 }}>
        <div className="gate-ico">{canSubmit ? <Icons.check s={17} /> : <Icons.lock s={17} sw={2} />}</div>
        <div style={{ flex: 1, fontWeight: 500 }}>
          {!editing
            ? <><b>当前版本只读。</b> 修改时创建新版本，线上版本继续使用。</>
            : saved
            ? <><b>已送审。</b> 草稿、建议处理和测试结果已记录。</>
            : canSubmit
              ? <><b>可以送审。</b> 阻断项已处理，测试通过。</>
              : <><b>暂不能送审。</b> {blockingPending ? `还有 ${blockingPending} 条阻断建议未处理；` : ""}{unsafeIssues.join("；") || (testState === "stale" ? "内容已修改，需要重新测试" : "重新测试尚未通过")}。</>}
        </div>
      </div>

      <div className="sec-2col">
        <div className="card">
          <div className="card-head"><Icons.doc s={15} /><span className="card-title">处理流程编辑器</span><span className="card-sub">五个必要部分</span></div>
          <div className="seg-tabs">
            {segments.map(s => (
              <button key={s.n} className={"seg-tab" + (seg === s.n ? " active" : "")} onClick={() => setSeg(s.n)}>
                <span className="seg-num">{s.n}</span>{s.title}
              </button>
            ))}
          </div>
          <div className="card-pad">
            <textarea className="seg-body seg-editor" aria-label={`编辑${segCur.title}`} value={segCur.body} readOnly={!editing} onChange={event => updateSegment(event.target.value)} />
            {seg === 5 && (
              <div className="four-el">
                <div className="four-el-h">转人工后坐席获得的信息</div>
                <div className="four-el-grid">
                  {["客户", "问题", "已收集信息", "当前结论"].map((x, i) => (
                    <div key={i} className="four-el-chip"><Icons.check s={12} style={{ color: "var(--green)" }} /> {x}</div>
                  ))}
                </div>
              </div>
            )}
          </div>
        </div>

        {/* right: review suggestions + redline checker */}
        <div className="col" style={{ gap: 16 }}>
          {/* Review suggestions */}
          <div className="card">
            <div className="card-head"><Icons.zap s={15} /><span className="card-title">审稿建议</span><span className="card-sub">{generatedAt}</span>{pending > 0 ? <Badge tone="amber">{pending} 项待处理</Badge> : <Badge tone="green">已全部处理</Badge>}</div>
            <div className="card-pad" style={{ display: "flex", flexDirection: "column", gap: 10 }}>
              {!suggestions.length ? <div className="note">当前版本没有待处理建议。创建新版本并修改后，系统会按当前草稿、测试失败、来源变化和红线重新生成建议。</div> : null}
              {suggestions.map((d, i) => {
                const done = decisions[i];
                return (
                  <div key={i} className={"diff-card" + (done ? " resolved" : "")}>
                    <div className="spread" style={{ marginBottom: 6 }}>
                      <span className="row" style={{ gap: 7 }}>
                        <Badge tone={d.sev === "blocking" ? "red" : d.sev === "warning" ? "amber" : "gray"}>{d.sev === "blocking" ? "阻断" : d.sev === "warning" ? "警告" : "提示"}</Badge>
                        <span className="muted" style={{ fontSize: 12 }}>{d.sec}</span>
                      </span>
                      {done ? <Badge tone={done.type === "accept" ? "green" : "gray"}>{done.type === "accept" ? "已接受" : "已拒绝"}</Badge> : null}
                    </div>
                    <div className={"diff-line " + d.type}><span className="diff-sign">{d.type === "add" ? "+" : d.type === "del" ? "−" : "~"}</span>{d.text}</div>
                    <dl className="suggestion-detail-list compact">
                      <dt>触发原因</dt><dd>{d.trigger}</dd>
                      <dt>依据</dt><dd>{d.basis}</dd>
                      <dt>接受后</dt><dd>{d.effect}</dd>
                      <dt>需要重测</dt><dd>{d.tests}</dd>
                    </dl>
                    <div className="muted" style={{ fontSize: 12, margin: "7px 0" }}>{d.reason}</div>
                    {!done && (
                      <>
                        <div className="row" style={{ gap: 7 }}>
                          <button className="btn sm" style={{ padding: "4px 10px" }} onClick={() => acceptSuggestion(i)}><Icons.check s={13} /> 接受修改</button>
                          <button className="btn sm ghost" style={{ padding: "4px 10px" }} onClick={() => startReject(i)}>拒绝建议</button>
                        </div>
                        {rejecting === i ? (
                          <div className="reject-box">
                            <label>拒绝原因<input value={rejectReason} onChange={event => setRejectReason(event.target.value)} placeholder="说明为什么不采用；同一依据不会重复提出" /></label>
                            <div className="row" style={{ gap: 7 }}>
                              <button className="btn sm ghost" onClick={() => setRejecting(null)}>取消</button>
                              <button className="btn sm" disabled={!rejectReason.trim()} onClick={() => confirmReject(i)}>确认拒绝</button>
                            </div>
                          </div>
                        ) : null}
                      </>
                    )}
                    {done?.type === "reject" ? <div className="note"><Icons.alert s={13} /> 已拒绝：{done.reason}。{d.sev === "blocking" ? "草稿仍命中红线，继续阻断。" : "同一依据不再重复提出。"}</div> : null}
                  </div>
                );
              })}
            </div>
          </div>

          {/* redline checker */}
          <div className="card">
            <div className="card-head"><Icons.shield s={15} /><span className="card-title">安全检查</span></div>
            <div className="card-pad" style={{ display: "flex", flexDirection: "column", gap: 8 }}>
              {[
                { r: "不得承诺审核通过或完成时间", ok: !unsafeIssues.some(item => item.includes("时限承诺")) },
                { r: "政策例外和绕过请求有明确出口", ok: !unsafeIssues.some(item => item.includes("政策例外")) },
                { r: "禁止披露字段过滤", ok: testState === "passed" },
                { r: "安全敏感问题阻断", ok: testState === "passed" },
                { r: "不用于回答的来源或待审核问答不得用于回答", ok: testState === "passed" },
                { r: "引用的上游修订仍有效", ok: testState === "passed" },
                { r: "高风险结论由当前问答或处理流程覆盖", ok: testState === "passed" },
                { r: "个案表达与工具真实结果一致", ok: testState === "passed" },
                { r: "失败处理检查（每个无法继续的分支都有明确提示或转人工）", ok: testState === "passed" },
              ].map((c, i) => (
                <div key={i} className="spread rc-row">
                  <span style={{ fontSize: 12.5 }}>{c.r}</span>
                  {c.ok ? <Badge tone="green" dot="var(--green)">通过</Badge> : <Badge tone="red" dot="var(--red)">已阻断</Badge>}
                </div>
              ))}
              <div className="spread rc-row">
                <span style={{ fontSize: 12.5 }}>改动后重新测试</span>
                <Badge tone={testState === "passed" ? "green" : testState === "stale" ? "amber" : "red"}>{testState === "passed" ? "通过" : testState === "stale" ? "需要重测" : "失败"}</Badge>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  );
}

window.TrainView = TrainView;
