"use client";

import { useEffect, useMemo, useRef, useState } from "react";
import Link from "next/link";
import { useRouter } from "next/navigation";
import {
  AlertTriangle,
  CheckCircle2,
  ChevronDown,
  ClipboardCheck,
  LocateFixed,
  RotateCcw,
  Square,
} from "lucide-react";

import { AppShell } from "@/components/AppShell";
import { Badge } from "@/components/ui/Badge";
import { Button } from "@/components/ui/Button";
import { Select } from "@/components/ui/Select";
import {
  reviewChapterHref,
  reviewChapterOptions,
  type ReviewChapterOption,
} from "@/lib/review/chapterNav";
import type { ChapterEntry } from "@/lib/workbench/chapter";
import type { ProjectResponse, ReviewHistoryItem } from "@/lib/api/types";
import { friendlyError } from "@/lib/errors/messages";
import {
  allResolved,
  emptyDecisions,
  setNote,
  setVerdict,
  unresolvedIndices,
  type DecisionDraft,
  type Verdict,
} from "@/lib/review/decisions";
import {
  latestReview,
  normalizeCharacterization,
  normalizeConflicts,
  normalizeForeshadowSug,
  normalizePace,
} from "@/lib/review/history";
import { api } from "@/lib/api/client";
import {
  displayOrder,
  groupConflicts,
  nextUnresolvedInOrder,
} from "@/lib/review/grouping";
import { applyConflictFix } from "@/lib/review/applyFix";
import { locateInDraft } from "@/lib/review/locate";
import { conflictSnippet } from "@/lib/review/snippet";
import { useAccept } from "@/lib/review/useAccept";
import { useAutosave } from "@/lib/autosave/useAutosave";
import { useRefine } from "@/lib/style/useRefine";
import { useReviewStream } from "@/lib/review/useReviewStream";
import type { ReviewConflict, StyleDriftSegment } from "@/lib/review/sse";
import { normalizeStyleDrift } from "@/lib/style/style";
import { locateDriftSegment } from "@/lib/style/locateSegment";
import { useToast } from "@/components/Toast";
import { ThinkingIndicator } from "@/components/ThinkingIndicator";
import { AcceptPanel } from "./AcceptPanel";
import { AnnotatedText } from "./AnnotatedText";
import { CharacterizationPanel } from "./CharacterizationPanel";
import { ConflictCard } from "./ConflictCard";
import { ForeshadowSuggestions } from "./ForeshadowSuggestions";
import { PacePanel } from "./PacePanel";
import { ReviewSectionPanel } from "./ReviewSectionPanel";
import { ReviewSummaryRail } from "./ReviewSummaryRail";
import { StylePanel } from "@/components/style/StylePanel";
import { RefineView } from "@/components/style/RefineView";

// 命中区闪烁高亮持续时长（朱砂脉冲）。
const FLASH_DURATION_MS = 900;

interface ReviewReportProps {
  project: ProjectResponse;
  chapterNo: number;
  initialReview: ReviewHistoryItem | undefined;
  initialDraft: string;
  // 章节导航目录（大纲章节）：无 chapterOptions 时回退用。
  chapters?: ChapterEntry[];
  // 选章选项（以真实章为准，含可审/已审标记）；优先于 chapters 渲染选择器。
  chapterOptions?: ReviewChapterOption[];
}

// 审稿报告页主体（UX §6.4 / §8.3 / §9）。
// 数据源：进页用历史留痕 conflicts 种入；「重新审稿」用当前终稿跑 SSE 覆盖。
// 裁决草稿随 conflicts 长度对齐；未决禁验收（gate）。
export function ReviewReport({
  project,
  chapterNo,
  initialReview,
  initialDraft,
  chapters,
  chapterOptions,
}: ReviewReportProps) {
  const router = useRouter();
  const review = useReviewStream();
  const accept = useAccept();
  const conflictFix = useRefine(); // 采纳无补丁冲突时，临时调 AI 重写定位段
  // 审稿页草稿自动保存：采纳/手改/AI 改写后防抖 PUT /draft，改动随时留存、刷新不丢
  // （与「验收本章」解耦——验收前也能保存，修「采纳后没保存」）。
  const autosave = useAutosave(project.id, chapterNo, initialDraft);
  const toast = useToast();

  const [finalText, setFinalText] = useState(initialDraft);
  const [drafts, setDrafts] = useState<DecisionDraft[]>(() =>
    emptyDecisions(normalizeConflicts(initialReview).length),
  );
  const [focusedIndex, setFocusedIndex] = useState<number | null>(null);
  const [missing, setMissing] = useState<Set<number>>(new Set());
  // 正在 AI 改写的冲突下标（采纳无补丁 → 调 refine 重写定位段；用于卡片 loading/禁用）。
  const [rewriting, setRewriting] = useState<Set<number>>(new Set());
  // 可编辑终稿 textarea 引用：供「跳转」/采纳后在正文里选中定位问题区域。
  const editorRef = useRef<HTMLTextAreaElement>(null);
  // 回炉中漂移段在终稿里定位到的原文（null=未打开 RefineView）。内容锚定位，非位置 idx。
  const [refineText, setRefineText] = useState<string | null>(null);
  // 伏笔「仍待处理」数：由 ForeshadowSuggestions 上抛（已登记/已回收的已扣除）。
  // null=尚未上报，回退到原始建议数，避免首帧闪「无建议」。
  const [foreshadowRemaining, setForeshadowRemaining] = useState<number | null>(
    null,
  );
  const seededRef = useRef(false);
  // flashMark 的收尾定时器句柄：组件卸载或重复触发时清除，避免定时器泄漏。
  const flashTimerRef = useRef<number | null>(null);
  useEffect(
    () => () => {
      if (flashTimerRef.current !== null) {
        window.clearTimeout(flashTimerRef.current);
      }
    },
    [],
  );

  // 进页一次性把历史留痕（冲突 + 伏笔建议 + 节奏）种入流状态（无需重审即可裁决/查看）。
  const seededConflicts = useMemo(
    () => normalizeConflicts(initialReview),
    [initialReview],
  );
  const seededForeshadow = useMemo(
    () => normalizeForeshadowSug(initialReview),
    [initialReview],
  );
  const seededPace = useMemo(
    () => normalizePace(initialReview),
    [initialReview],
  );
  const seededStyle = useMemo(
    () => normalizeStyleDrift(initialReview),
    [initialReview],
  );
  const seededCharacterization = useMemo(
    () => normalizeCharacterization(initialReview),
    [initialReview],
  );
  useEffect(() => {
    if (seededRef.current) return;
    seededRef.current = true;
    const hasSeed =
      seededConflicts.length > 0 ||
      seededForeshadow.length > 0 ||
      seededPace !== null ||
      seededStyle !== null ||
      seededCharacterization !== null;
    if (hasSeed) {
      review.seed({
        conflicts: seededConflicts,
        foreshadow: seededForeshadow,
        pace: seededPace,
        style: seededStyle,
        characterization: seededCharacterization,
      });
    }
  }, [
    review,
    seededConflicts,
    seededForeshadow,
    seededPace,
    seededStyle,
    seededCharacterization,
  ]);

  // 当前生效冲突 = 流状态（重审后即时更新，否则种入的历史）。
  const conflicts: ReviewConflict[] = review.state.conflicts;
  const foreshadow = review.state.foreshadow;
  const pace = review.state.pace;
  const style = review.state.style;
  const characterization = review.state.characterization;
  // 伏笔徽标/验收预览统一用「仍待处理数」（随登记/回收递减）；未上报前回退原始建议数。
  const foreshadowOpen = foreshadowRemaining ?? foreshadow.length;
  const sectionStatus = (name: string): string | undefined =>
    review.state.sections.find((s) => s.name === name)?.status;

  // 重审完成（done 边沿）→ 重置裁决草稿对齐新冲突集（冲突可能整组变化）。
  const conflictCount = conflicts.length;
  const wasReviewingRef = useRef(false);
  useEffect(() => {
    if (review.state.phase === "reviewing") {
      wasReviewingRef.current = true;
      return;
    }
    if (review.state.phase === "done" && wasReviewingRef.current) {
      wasReviewingRef.current = false;
      setDrafts(emptyDecisions(conflictCount));
      setMissing(new Set());
    }
  }, [review.state.phase, conflictCount]);

  // 终稿改动（采纳/手改/AI 改写/直接编辑均经 setFinalText）→ 防抖自动保存草稿。
  // 跳过首帧：初值=已存草稿，useAutosave 基线即它，无需保存。
  const autosaveOnChange = autosave.onChange;
  const skipFirstAutosave = useRef(true);
  useEffect(() => {
    if (skipFirstAutosave.current) {
      skipFirstAutosave.current = false;
      return;
    }
    autosaveOnChange(finalText);
  }, [finalText, autosaveOnChange]);

  const onReReview = (): void => {
    setMissing(new Set());
    void review.start(project.id, chapterNo, finalText);
  };

  const onVerdict = (index: number, verdict: Verdict): void => {
    setDrafts((prev) => setVerdict(prev, index, verdict));
    setMissing((prev) => {
      if (!prev.has(index)) return prev;
      const next = new Set(prev);
      next.delete(index);
      return next;
    });
    // 采纳改法：①带预出补丁（original→replacement）→ 瞬时改入终稿并选中；
    // ②有补丁但原文找不到（已手改/漂移）→ 提示手改；③无补丁但能在正文定位 →
    // 临时调 AI 重写该段并套入；④定位不到（摘要/跨章）→ 提示手改或忽略。
    if (verdict === "accept") {
      const c = conflicts[index];
      if (c) {
        const out = applyConflictFix(finalText, c.original, c.replacement);
        if (out.status === "applied") {
          const at = finalText.indexOf(c.original ?? "");
          setFinalText(out.text);
          toast("已采纳改入终稿，验收时请复核。", "success");
          if (at !== -1 && c.replacement) {
            selectRegionSoon(at, at + c.replacement.length, index);
          }
        } else if (out.status === "not-found") {
          toast("未在终稿中找到原文（可能已手改），请在左侧正文手改。", "error");
        } else {
          void aiFixOnAccept(index, c);
        }
      }
    }
  };

  // 无预出补丁的冲突，采纳时临时调 AI（refiner）重写定位到的那段原文，套入终稿并选中。
  // 定位不到（多为摘要/跨章问题）→ 不调 AI，提示手改/忽略。复用既有 refine 端点（不变量#3）。
  const aiFixOnAccept = async (
    index: number,
    c: ReviewConflict,
  ): Promise<void> => {
    const loc = locateInDraft(finalText, c);
    if (loc === null) {
      toast("这条无法在正文定位（多为摘要/跨章问题），请手改或忽略。", "info");
      return;
    }
    const prevText = finalText;
    const segment = prevText.slice(loc.start, loc.end);
    setRewriting((s) => new Set(s).add(index));
    const res = await conflictFix.refine(
      project.id,
      chapterNo,
      segment,
      c.suggestion,
    );
    setRewriting((s) => {
      const next = new Set(s);
      next.delete(index);
      return next;
    });
    if (!res) return; // useRefine 已就失败 toast
    const at = prevText.indexOf(segment);
    if (at === -1) {
      toast("正文已变动，未能自动套用，请手改。", "error");
      return;
    }
    const next =
      prevText.slice(0, at) + res.refined + prevText.slice(at + segment.length);
    setFinalText(next);
    toast("已用 AI 改写并套入正文，请复核。", "success");
    selectRegionSoon(at, at + res.refined.length, index);
  };
  const onNote = (index: number, note: string): void =>
    setDrafts((prev) => setNote(prev, index, note));

  // R2：按 type 分组 + 严重度排序（仅改显示顺序，每条仍携原始 index）。
  const groups = useMemo(() => groupConflicts(conflicts), [conflicts]);
  const order = useMemo(() => displayOrder(groups), [groups]);
  // 能在当前终稿里定位的冲突下标集合——只对这些显示「跳转」/锚点，避免点了定位不到刷错误提示。
  const locatable = useMemo(() => {
    const s = new Set<number>();
    conflicts.forEach((c, i) => {
      if (locateInDraft(finalText, c) !== null) s.add(i);
    });
    return s;
  }, [conflicts, finalText]);

  const jumpToCard = (index: number): void => {
    setFocusedIndex(index);
    document
      .getElementById(`conflict-card-${index}`)
      ?.scrollIntoView({ behavior: "smooth", block: "center" });
  };

  // R2：跳到显示顺序中的下一条未裁决（环回）；全决则提示完成。
  const jumpToNextUnresolved = (): void => {
    const next = nextUnresolvedInOrder(order, drafts, focusedIndex);
    if (next === null) {
      toast("全部冲突已裁决。", "success");
      return;
    }
    jumpToCard(next);
  };
  // 把命中区滚到视野（页面级 scrollIntoView，无内部滚动条）；有对应 <mark> 用它居中，
  // 否则退回滚动编辑器本体。instant 滚动，天然尊重 prefers-reduced-motion。
  const scrollRegionIntoView = (index?: number): void => {
    const mark =
      index !== undefined
        ? document.getElementById(`draft-mark-${index}`)
        : null;
    if (mark) {
      mark.scrollIntoView({ block: "center", behavior: "auto" });
    } else {
      editorRef.current?.scrollIntoView({ block: "center", behavior: "auto" });
    }
  };

  // 在正文里选中 [start,end) 并滚动到视野；下一帧执行（等 value 落定）。
  const selectRegionSoon = (start: number, end: number, index?: number): void => {
    requestAnimationFrame(() => {
      const el = editorRef.current;
      if (!el) return;
      el.focus();
      el.setSelectionRange(start, end);
      scrollRegionIntoView(index);
    });
  };

  // 闪烁高亮 <mark>（朱砂脉冲 0.9s）；尊重 prefers-reduced-motion（CSS 内降级为描边）。
  const flashMark = (index: number): void => {
    const mark = document.getElementById(`draft-mark-${index}`);
    if (!mark) return;
    mark.classList.add("draft-mark-flash");
    if (flashTimerRef.current !== null) {
      window.clearTimeout(flashTimerRef.current);
    }
    flashTimerRef.current = window.setTimeout(() => {
      mark.classList.remove("draft-mark-flash");
      flashTimerRef.current = null;
    }, FLASH_DURATION_MS);
  };

  // 跳转/锚点：在可编辑正文里定位、选中并滚动到该冲突对应区域 + 闪烁；定位不到则提示手动查找。
  const focusRegion = (index: number): void => {
    setFocusedIndex(index);
    const c = conflicts[index];
    const el = editorRef.current;
    if (!c || !el) return;
    const loc = locateInDraft(finalText, c);
    if (loc === null) {
      toast("未能在正文中定位该处，可能已改动，请手动查找。", "error");
      return;
    }
    el.focus();
    el.setSelectionRange(loc.start, loc.end);
    scrollRegionIntoView(index);
    flashMark(index);
  };

  const onAccept = async (): Promise<void> => {
    const outcome = await accept.accept(
      project.id,
      chapterNo,
      finalText,
      drafts,
    );
    if (!outcome.conflictUnresolved) {
      if (outcome.missingIndices.length > 0) {
        setMissing(new Set(outcome.missingIndices));
      }
      return;
    }
    // 验收闸读的是「持久化的最新审稿」；本页冲突可能为空/过期（显示 0 冲突却被拦）。
    // 回拉最新审稿留痕 → 回灌冲突 + 重置裁决草稿，把「看不见的冲突」显式呈现供裁决。
    const { data } = await api.GET(
      "/projects/{project_id}/chapters/{chapter_no}/reviews",
      { params: { path: { project_id: project.id, chapter_no: chapterNo } } },
    );
    const fresh = normalizeConflicts(latestReview(data?.reviews));
    setMissing(new Set(outcome.missingIndices));
    if (fresh.length > 0) {
      review.seed({ conflicts: fresh, foreshadow, pace, style, characterization });
      setDrafts(emptyDecisions(fresh.length));
      toast("审稿报告已刷新：检测到未裁决冲突，请裁决后再验收", "error");
    } else {
      toast("尚有冲突未裁决，请先「重新审稿」查看", "error");
    }
  };

  // 终稿按空行切段（大文本：仅在 finalText 变化时重算，不在每次 render split）。
  const finalParas = useMemo(() => finalText.split(/\n{2,}/), [finalText]);

  // 一键回炉：用漂移段自带原文在终稿里做内容匹配定位（内容锚），定位不到则就 #9
  // 给出明确提示而非静默 no-op；定位到才打开 RefineView。
  const onRefineSegment = (segment: StyleDriftSegment): void => {
    const located = locateDriftSegment(finalParas, segment);
    if (located === null) {
      toast("无法定位该段（原文可能已改动），请手动选择要回炉的段落。", "error");
      return;
    }
    setRefineText(located);
  };

  // 采纳：把重写段替换终稿中的原段（首个匹配），合入终稿（经既有 draft 路径）。
  const onAdopt = (original: string, refined: string): void => {
    setFinalText((prev) => {
      const idx = prev.indexOf(original);
      if (idx === -1) {
        toast("未在终稿中找到原段，请手动合入。", "error");
        return prev;
      }
      return prev.slice(0, idx) + refined + prev.slice(idx + original.length);
    });
    setRefineText(null);
    toast("已合入终稿，记得验收时复核。", "success");
  };

  const resolved = allResolved(drafts);
  const unresolved = unresolvedIndices(drafts).length;
  const reviewing = review.isReviewing;
  const hasReport =
    initialReview !== undefined ||
    review.state.phase === "done" ||
    review.state.sections.length > 0 ||
    seededConflicts.length > 0 ||
    foreshadow.length > 0 ||
    pace !== null ||
    style !== null ||
    characterization !== null;

  return (
    <AppShell
      title={`〈${project.title}〉`}
      subtitle={`第 ${chapterNo} 章 · 审稿报告`}
      projectId={project.id}
      activeNav="review"
    >
      <div className="flex min-h-[calc(100vh-var(--chrome,4rem))] flex-col lg:grid lg:h-[calc(100vh-var(--chrome,4rem))] lg:min-h-0 lg:grid-cols-[minmax(0,1fr)_24rem]">
        {/* 左：终稿正文 + 就地标注 */}
        <section className="flex min-h-[70vh] min-w-0 flex-col bg-bg lg:min-h-0">
          <div className="flex items-center gap-3 border-b border-line bg-panel px-4 py-3 sm:px-6">
            <h1 className="min-w-0 flex-1 truncate font-serif text-lg text-ink">
              第 {chapterNo} 章 审稿报告
            </h1>
            <Select
              controlSize="sm"
              aria-label="切换审稿章节"
              className="max-w-[12rem] shrink-0"
              value={chapterNo}
              onChange={(e) =>
                router.push(
                  reviewChapterHref(project.id, Number(e.target.value)),
                )
              }
            >
              {(
                chapterOptions ?? reviewChapterOptions(chapters ?? [], chapterNo)
              ).map((opt: ReviewChapterOption) => (
                <option key={opt.no} value={opt.no} disabled={opt.disabled}>
                  {opt.label}
                </option>
              ))}
            </Select>
            <span className="hidden shrink-0 font-mono text-xs text-ink-soft sm:inline">
              {conflictCount} 冲突
            </span>
            <div className="shrink-0">
              {reviewing ? (
                <Button onClick={review.stop} variant="danger" size="sm">
                  <Square className="h-4 w-4" aria-hidden="true" />
                  停
                </Button>
              ) : (
                <Button onClick={onReReview} variant="primary" size="sm">
                  <RotateCcw className="h-4 w-4" aria-hidden="true" />
                  重新审稿
                </Button>
              )}
            </div>
          </div>

          {review.state.error ? (
            <ReviewErrorNote error={review.state.error} />
          ) : null}

          <div className="flex-1 overflow-auto px-4 py-4 sm:px-6 sm:py-6">
            <div className="mb-2 flex items-start justify-between gap-3">
              <p className="text-xs text-ink-soft">
                可直接在下方正文修改（改动自动保存草稿 — 摘要从终稿提炼）；点冲突的「跳转」或顶部锚点定位到对应文字。
              </p>
              <span className="shrink-0 font-mono text-xs text-ink-soft">
                {autosave.status === "saving"
                  ? "保存中…"
                  : autosave.status === "error"
                    ? "保存失败"
                    : (autosave.savedLabel ?? "")}
              </span>
            </div>
            <AnnotatedText
              value={finalText}
              onChange={setFinalText}
              conflicts={conflicts}
              focusedIndex={focusedIndex}
              onAnchorClick={focusRegion}
              projectId={project.id}
              chapterNo={chapterNo}
              editorRef={editorRef}
            />
          </div>
        </section>

        {/* 右：审稿分区 + 冲突裁决 + 验收 gate */}
        <aside
          className="flex flex-col border-t border-line bg-panel px-4 py-4 lg:overflow-auto lg:border-l lg:border-t-0"
          aria-label="审稿报告与验收"
        >
          <ReviewSummaryRail
            conflictCount={conflictCount}
            unresolvedCount={unresolved}
            reviewing={reviewing}
            hasReport={hasReport}
            onNextUnresolved={jumpToNextUnresolved}
          />

          <div className="space-y-3">
            <ReviewSectionPanel
              title="一致性"
              statusLabel={
                reviewing
                  ? "审稿中"
                  : conflictCount > 0
                    ? `${conflictCount} 冲突`
                    : hasReport
                      ? "通过"
                      : "待审"
              }
              statusVariant={
                reviewing
                  ? "info"
                  : conflictCount > 0
                    ? "danger"
                    : hasReport
                      ? "success"
                      : "neutral"
              }
              defaultOpen={conflictCount > 0 || !hasReport}
            >
              <SectionStatusLine
                reviewing={reviewing}
                done={review.state.phase === "done"}
                conflictCount={conflictCount}
                sections={review.state.sections}
              />
              {conflicts.length === 0 ? (
                <p className="mt-2 rounded border border-dashed border-line p-4 text-xs text-ink-soft">
                  {review.state.phase === "done" || seededConflicts.length === 0
                    ? "未发现一致性冲突。"
                    : "进页未带审稿留痕，点「重新审稿」开始。"}
                </p>
              ) : (
                <div className="mt-3 space-y-4">
                  <p className="rounded border border-dashed border-line/70 bg-bg/40 px-3 py-1.5 text-[11px] text-ink-soft">
                    裁决先暂存在本地，点下方「验收本章」时与终稿一并保存落库。
                  </p>

                  {groups.map((group) => {
                    const unresolvedInGroup = group.items.filter(
                      ({ index }) => !drafts[index]?.verdict,
                    ).length;
                    return (
                      <details
                        key={group.type}
                        open={unresolvedInGroup > 0}
                        className="group rounded border border-line/70 bg-bg/35 p-2"
                      >
                        <summary className="flex cursor-pointer list-none items-center justify-between gap-2 text-xs font-semibold text-ink">
                          <span className="flex items-center gap-2">
                            {group.type}
                            <Badge
                              variant={
                                unresolvedInGroup > 0 ? "danger" : "success"
                              }
                            >
                              {group.items.length}
                            </Badge>
                          </span>
                          <span className="flex items-center gap-1.5">
                            <span className="font-mono text-2xs text-ink-soft">
                              {unresolvedInGroup > 0
                                ? `${unresolvedInGroup} 未裁决`
                                : "已处理"}
                            </span>
                            <ChevronDown
                              className="h-4 w-4 text-ink-soft motion-safe:transition-transform group-open:rotate-180"
                              aria-hidden="true"
                            />
                          </span>
                        </summary>
                        <ul className="mt-3 space-y-3">
                          {group.items.map(({ conflict: c, index: i }) => (
                            <ConflictCard
                              key={i}
                              index={i}
                              total={conflictCount}
                              conflict={c}
                              draft={drafts[i] ?? { verdict: null, note: "" }}
                              missing={missing.has(i)}
                              snippet={conflictSnippet(finalText, c.where)}
                              locatable={locatable.has(i)}
                              rewriting={rewriting.has(i)}
                              onVerdict={onVerdict}
                              onNote={onNote}
                              onJump={focusRegion}
                            />
                          ))}
                        </ul>
                      </details>
                    );
                  })}
                </div>
              )}
            </ReviewSectionPanel>

            <ReviewSectionPanel
              title="伏笔"
              statusLabel={
                sectionStatus("foreshadow") === "incomplete"
                  ? "未完成"
                  : foreshadowOpen > 0
                    ? `${foreshadowOpen} 待登记`
                    : "无建议"
              }
              statusVariant={
                sectionStatus("foreshadow") === "incomplete"
                  ? "warning"
                  : foreshadowOpen > 0
                    ? "accent"
                    : "success"
              }
              defaultOpen={foreshadow.length > 0}
            >
            <ForeshadowSuggestions
              suggestions={foreshadow}
              incomplete={sectionStatus("foreshadow") === "incomplete"}
              projectId={project.id}
              chapterNo={chapterNo}
              onRemainingChange={setForeshadowRemaining}
            />
            </ReviewSectionPanel>
            <ReviewSectionPanel
              title="节奏"
              statusLabel={
                sectionStatus("pace") === "incomplete"
                  ? "未完成"
                  : pace
                    ? "已完成"
                    : "无报告"
              }
              statusVariant={
                sectionStatus("pace") === "incomplete"
                  ? "warning"
                  : pace
                    ? "info"
                    : "neutral"
              }
              defaultOpen={pace !== null}
            >
            <PacePanel
              pace={pace}
              incomplete={sectionStatus("pace") === "incomplete"}
            />
            </ReviewSectionPanel>
            <ReviewSectionPanel
              title="文风"
              statusLabel={
                sectionStatus("style") === "incomplete"
                  ? "未完成"
                  : style
                    ? "已完成"
                    : "无报告"
              }
              statusVariant={
                sectionStatus("style") === "incomplete"
                  ? "warning"
                  : style
                    ? "info"
                    : "neutral"
              }
              defaultOpen={style !== null}
            >
            <StylePanel
              style={style}
              incomplete={sectionStatus("style") === "incomplete"}
              onRefine={onRefineSegment}
            />
            </ReviewSectionPanel>
            <ReviewSectionPanel
              title="人物塑造"
              statusLabel={
                sectionStatus("characterization") === "incomplete"
                  ? "未完成"
                  : characterization && characterization.issues.length > 0
                    ? `${characterization.issues.length} 建议`
                    : characterization
                      ? "无明显问题"
                      : "无报告"
              }
              statusVariant={
                sectionStatus("characterization") === "incomplete"
                  ? "warning"
                  : characterization && characterization.issues.length > 0
                    ? "accent"
                    : characterization
                      ? "success"
                      : "neutral"
              }
              defaultOpen={
                characterization !== null && characterization.issues.length > 0
              }
            >
            <CharacterizationPanel
              characterization={characterization}
              incomplete={sectionStatus("characterization") === "incomplete"}
            />
            </ReviewSectionPanel>
            {refineText !== null ? (
              <RefineView
                projectId={project.id}
                chapterNo={chapterNo}
                segment={refineText}
                onAdopt={onAdopt}
                onClose={() => setRefineText(null)}
              />
            ) : null}
          </div>

          <section id="accept-panel" className="mt-4 scroll-mt-4">
            <AcceptPanel
              projectId={project.id}
              chapterNo={chapterNo}
              reviewed={initialReview !== undefined || review.state.phase === "done"}
              conflictCount={conflictCount}
              unresolvedCount={resolved ? 0 : unresolved}
              foreshadowCount={foreshadowOpen}
              accepting={accept.status === "accepting"}
              result={accept.result}
              onAccept={() => void onAccept()}
            />
          </section>
        </aside>

        {/* 移动端（<lg 单列）快捷条：编辑器在上、审稿/验收在下，滚一整屏才够得着——
            固定底栏直接「跳到未裁决」或「去验收」，免去长滚。桌面端隐藏（右栏常驻可见）。 */}
        {hasReport && !accept.result ? (
          <div className="fixed inset-x-0 bottom-0 z-30 flex items-center gap-2 border-t border-line bg-panel/95 px-4 py-2 backdrop-blur supports-[backdrop-filter]:bg-panel/80 lg:hidden">
            <span className="flex-1 truncate text-xs text-ink-soft">
              {unresolved > 0 ? `${unresolved} 项冲突待裁决` : "可验收本章"}
            </span>
            {unresolved > 0 ? (
              <Button
                onClick={jumpToNextUnresolved}
                variant="secondary"
                size="sm"
              >
                <LocateFixed className="h-4 w-4" aria-hidden="true" />
                跳到未裁决
              </Button>
            ) : null}
            <Button
              onClick={() =>
                document
                  .getElementById("accept-panel")
                  ?.scrollIntoView({ behavior: "smooth", block: "center" })
              }
              variant="primary"
              size="sm"
            >
              <ClipboardCheck className="h-4 w-4" aria-hidden="true" />
              去验收
            </Button>
          </div>
        ) : null}
      </div>
    </AppShell>
  );
}

// 审稿失败提示：友好文案（不暴露 raw code）+ 可选「去设置」动作（F4）。
function ReviewErrorNote({
  error,
}: {
  error: { code: string; message: string };
}) {
  const friendly = friendlyError(error.code, error.message);
  return (
    <p className="border-b border-line bg-panel px-6 py-2 text-sm text-conflict">
      {friendly.text}
      {friendly.actionHref ? (
        <>
          {" "}
          <Link
            href={friendly.actionHref}
            className="underline hover:text-cinnabar"
          >
            {friendly.actionLabel}
          </Link>
        </>
      ) : null}
    </p>
  );
}

interface SectionStatusLineProps {
  reviewing: boolean;
  done: boolean;
  conflictCount: number;
  sections: { name: string; status: string }[];
}

// 四审进行中骨架逐项点亮（UX §9）；M2 仅 continuity 一项。
function SectionStatusLine({
  reviewing,
  done,
  conflictCount,
  sections,
}: SectionStatusLineProps) {
  if (reviewing) {
    return (
      <p className="mt-1 text-xs text-info">
        <ThinkingIndicator label="审稿进行中" />
      </p>
    );
  }
  if (done || sections.length > 0) {
    return (
      <p className="mt-1 text-xs">
        {conflictCount > 0 ? (
          <Badge variant="danger">
            <AlertTriangle className="h-3 w-3" aria-hidden="true" />
            {conflictCount} 冲突
          </Badge>
        ) : (
          <Badge variant="success">
            <CheckCircle2 className="h-3 w-3" aria-hidden="true" />
            通过
          </Badge>
        )}
      </p>
    );
  }
  return null;
}
