/**
 * 内容生成节点
 * 为所有叶节点（没有子节点的节点）生成实际内容
 */

import { GraphState } from '../graph';
import { bookStore } from '../book-generator.store';
import { prisma } from '../../../models';
import { createBookTools } from '../../../services/llm/book-tools';
import { checkQuotaForWords, markGenerationInterrupted, getGeneratedWordCount } from '../../subscription/subscription.service';
import { PROGRESS, countWords } from '../utils';
import { advanceChapter, regenerateChapter } from '../stage-manager';
import { getBookWordLimit, getWordUpperLimit } from '../book-type-config';
import { AsyncPool } from '../utils/async-pool';
import { cleanThinkingText, truncateAtBoundary } from '../utils/content-cleaner';
import { safeTransitionChapter } from '../stage-manager';
import { BookConsistencyTracker } from '../utils/content-consistency';
import { withGlobalLLMConcurrency, getBookConcurrency } from '../utils/llm-concurrency';

/** 全局术语一致性跟踪器（单例，跨整本书的章节共享） */
const globalConsistencyTracker = new BookConsistencyTracker();

/**
 * 为被截断的内容补一个自然结尾句，
 * 避免截断后看起来像没写完。
 */
function appendNaturalClosure(content: string): string {
  if (!content || content.length < 50) return content;
  const trimmed = content.trimEnd();
  // 已有完整结尾标点的不补
  const naturalEndings = /[。！？.!?）」"”']\s*$/;
  if (naturalEndings.test(trimmed)) return trimmed;

  // 从内容末尾提取关键词，生成一个简短收尾句
  const lastSentences = trimmed.split(/[。！？.!?]/).filter(Boolean);
  const lastTopic = lastSentences.length > 0
    ? lastSentences[lastSentences.length - 1].substring(0, 30).trim()
    : '';

  if (lastTopic.length > 3) {
    return trimmed + `。以上就是关于${lastTopic}的讨论。`;
  }
  return trimmed + '。以上就是本章的主要内容。';
}

/**
 * 安全更新父章节状态：仅在父节点下所有子节点都已完成时，才推进父节点状态。
 * 替代原先的 updateMany 批量操作，避免失败子节点污染已完成父节点状态。
 */
async function safelyCompleteParentChapters(bookIdNum: number): Promise<void> {
  const parents = await prisma.bookChapter.findMany({
    where: { bookId: bookIdNum, level: { in: [1, 2] } },
    select: { id: true, genStage: true },
  });

  const allChildren = await prisma.bookChapter.findMany({
    where: { bookId: bookIdNum, parentId: { in: parents.map(p => p.id) } },
    select: { id: true, genStage: true, parentId: true },
  });

  // 按 parentId 分组
  const childrenByParent = new Map<number, typeof allChildren>();
  for (const child of allChildren) {
    if (!child.parentId) continue;
    if (!childrenByParent.has(child.parentId)) childrenByParent.set(child.parentId, []);
    childrenByParent.get(child.parentId)!.push(child);
  }

  for (const parent of parents) {
    const children = childrenByParent.get(parent.id) || [];
    if (children.length === 0) continue; // 无子节点（单层大纲），跳过

    const allChildrenDone = children.every(
      c => c.genStage === 'content_completed' || c.genStage === 'audio_generating'
        || c.genStage === 'audio_completed' || c.genStage === 'video_generating'
        || c.genStage === 'video_completed'
    );

    if (allChildrenDone && parent.genStage !== 'content_completed') {
      try {
        await safeTransitionChapter(parent.id, parent.genStage as any, 'content_completed');
      } catch (err: any) {
        console.warn(`[Content] 父节点 #${parent.id} 状态转移失败:`, err.message);
      }
    }
  }
}

/**
 * 内容生成节点
 * 为所有叶节点（没有子节点的节点）生成实际内容
 */
export async function writeChaptersNode(state: typeof GraphState.State): Promise<Partial<typeof GraphState.State>> {
  console.log('[LangGraph] 生成章节内容, bookId:', state.bookId);

  const book = await bookStore.getById(state.bookId);
  if (!book || !book.outline) {
    console.log('[LangGraph] 无大纲或书籍，跳过章节生成');
    return { finished: true, progress: PROGRESS.CONTENT_END };
  }

  const bookIdNum = parseInt(state.bookId);

  // 查找所有章节
  const allChapters = await prisma.bookChapter.findMany({
    where: { bookId: bookIdNum },
    orderBy: { number: 'asc' }
  });

  // 找出所有叶节点（没有子节点的节点）
  const allParentIds = new Set(allChapters.map(c => c.parentId));
  const leafNodes = allChapters.filter(c => !allParentIds.has(c.id));

  // 过滤出未完成内容的叶节点
  const targetsToGenerate = leafNodes.filter((n: any) => n.genStage !== 'content_completed' && !n.content);
  const alreadyCompleted = leafNodes.length - targetsToGenerate.length;

  if (alreadyCompleted > 0) {
    console.log(`[LangGraph] ✅ 跳过 ${alreadyCompleted} 个已生成内容的目标`);
  }

  if (targetsToGenerate.length === 0) {
    console.log('[LangGraph] ✅ 所有内容已完成，跳过内容生成（音频由 AudioScanner 统一负责）');
    return { finished: true, progress: PROGRESS.CONTENT_END };
  }

  // 正常生成内容流程
  console.log(`[LangGraph] 共 ${leafNodes.length} 个叶节点，${targetsToGenerate.length} 个待生成`);

  // 构建父节点映射（用于长篇书籍）
  const chapterMap = new Map<number, any>();
  const sectionMap = new Map<number, any>();

  const chaptersAndSections = await prisma.bookChapter.findMany({
    where: { bookId: bookIdNum, level: { in: [1, 2] } }
  });

  chaptersAndSections.forEach(c => {
    if (c.level === 1) chapterMap.set(c.id, c);
    if (c.level === 2) sectionMap.set(c.id, c);
  });

  const failedChapters: number[] = [];
  const bookTools = createBookTools(state.bookId, bookStore);

  let currentWordCount = await getGeneratedWordCount(state.bookId);
  console.log(`[LangGraph] 当前已生成字数: ${currentWordCount}`);

  const totalTargets = targetsToGenerate.length;
  let completedCount = 0;

  for (const target of targetsToGenerate) {
    // 查找父节点信息
    const parentSection = sectionMap.get(target.parentId || 0);
    const parentChapter = parentSection ? chapterMap.get(parentSection.parentId || 0) : null;

    const chapterTitle = parentChapter?.title || '未知章';
    const sectionTitle = parentSection?.title || '未知节';
    const chapterSummary = parentChapter?.summary || '';
    const sectionSummary = parentSection?.summary || '';

    // 额度监控
    try {
      const quotaCheck = await checkQuotaForWords(book.userId || 1, currentWordCount);
      if (!quotaCheck.sufficient) {
        console.warn(`[LangGraph] ⚠️ 额度不足，中断生成`);
        await markGenerationInterrupted(state.bookId, target.number - 1, currentWordCount);
        return {
          currentChapter: target.number,
          progress: Math.round((completedCount / totalTargets) * 80) + 15,
          error: `额度不足中断`
        };
      }
    } catch (quotaErr) {
      console.warn(`[LangGraph] 额度检查失败，继续生成`);
    }

    // 字数校验：累计字数达到规模值的120%时停止生成（AI自行理解±20%浮动）
    const scaleWords = parseInt(state.bookScale);
    if (scaleWords && currentWordCount >= getWordUpperLimit(scaleWords)) {
      console.warn(`[LangGraph] ⚠️ 累计字数 ${currentWordCount} 已达规模上限 ${getWordUpperLimit(scaleWords)}（基数${scaleWords}×120%），停止生成`);
      return {
        currentChapter: target.number,
        progress: PROGRESS.CONTENT_END,
        error: `已达规模上限，约${Math.round(getWordUpperLimit(scaleWords) / 10000)}万字`
      };
    }

    // 根据是否有父章节判断是短文还是长篇
    const isShortArticle = !parentSection && target.level === 1;
    // 注：messages 由 generateSingleChapterContent 内部构建，此处只记录日志
    if (isShortArticle) {
      console.log(`[LangGraph] 生成短文「${target.title}」`);
    } else {
      console.log(`[LangGraph] 生成${chapterTitle} - ${sectionTitle} - ${target.title}`);
    }

    try {
      // ===== 直接生成内容（不使用队列，由 LangGraph 内部控制） =====
      const existing = await prisma.bookChapter.findUnique({
        where: { id: target.id },
        select: { content: true, wordCount: true, genStage: true },
      });
      if (existing?.content && existing.genStage === 'content_completed') {
        console.log(`[LangGraph] 「${target.title}」已有完成内容，复用`);
        currentWordCount += existing.wordCount || countWords(existing.content);
        completedCount++;
      } else {
        console.log(`[LangGraph] 「${target.title}」开始生成...`);
        const MAX_RETRIES = 3;
        let lastError: Error | null = null;
        for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
          try {
            if (attempt > 0) {
              const delay = Math.min(3000 * Math.pow(2, attempt - 1) + Math.random() * 2000, 30000);
              console.log(`[LangGraph] 「${target.title}」第${attempt + 1}次重试，等待${(delay / 1000).toFixed(1)}s...`);
              await new Promise(resolve => setTimeout(resolve, delay));
            }
            await withGlobalLLMConcurrency(() => bookStore.generateSingleChapterContent(state.bookId, target.id));
            lastError = null;
            break;
          } catch (err: any) {
            lastError = err;
            console.error(`[LangGraph] 「${target.title}」第${attempt + 1}次失败:`, err.message);
          }
        }
        if (lastError) throw lastError;
        completedCount++;
      }

      // 重新读取章节（队列处理器已更新）
      const chapter = await prisma.bookChapter.findUnique({
        where: { id: target.id },
        select: { content: true, wordCount: true },
      });
      if (!chapter?.content) throw new Error('内容生成结果为空');

      let content = chapter.content;
      let wordCount = chapter.wordCount || countWords(content);
      currentWordCount += wordCount;

      // ========== 三层安全防护 ==========

      // Layer 2: 章节预算偏差检测 — 按句截断 + 补结尾句
      const estimated = target.estimatedWords || 500;
      if (estimated > 0 && wordCount > estimated * 3) {
        console.warn(`[Security] ⚠️ 章节「${target.title}」字数异常: ${wordCount} > 预算 ${estimated}×3，截断处理`);
        const budgetChars = Math.round(estimated * 2.5);
        content = truncateAtBoundary(content, budgetChars, 'sentence');
        content = appendNaturalClosure(content);
        wordCount = countWords(content);
        await bookStore.updateChapterById(target.id, { content, wordCount });
      }

      // Layer 3: 全书累计字数上限
      const bookWordLimit = getBookWordLimit();
      if (currentWordCount > bookWordLimit) {
        console.warn(`[Security] ⚠️ 全书字数超限: ${currentWordCount} > ${bookWordLimit}，中断生成`);
        await markGenerationInterrupted(state.bookId, target.number, currentWordCount);
        return {
          currentChapter: target.number,
          progress: Math.round((completedCount / totalTargets) * 80) + 15,
          error: `全书字数超限（${currentWordCount} > ${bookWordLimit}）`
        };
      }

      // 音频生成由 AudioScanner 统一负责，不在此处 fire-and-forget
      // 配额消耗已移至 TTS 完成回调（processTtsTask onComplete），按实际音频时长扣费

      const progress = Math.round((completedCount / totalTargets) * (PROGRESS.CONTENT_END - PROGRESS.CONTENT_START)) + PROGRESS.CONTENT_START;
      await bookStore.update(state.bookId, { progress });
      console.log(`[LangGraph] 「${target.title}」内容完成 (${completedCount}/${totalTargets})，累计${currentWordCount}字，进度${progress}%`);

    } catch (error) {
      const errorMsg = error instanceof Error ? error.message : '失败';
      await bookStore.updateChapterById(target.id, {
        contentError: errorMsg
      });
      await regenerateChapter(target.id, 'failed');
      failedChapters.push(target.number);
      console.error(`[LangGraph] 「${target.title}」内容失败:`, errorMsg);
    }
  }

  // 安全更新父节点状态（逐条验证子节点，不再用 updateMany 盲改）
  await safelyCompleteParentChapters(bookIdNum);

  return {
    currentChapter: leafNodes.length,
    progress: PROGRESS.CONTENT_END,
    error: failedChapters.length > 0 ? `小节${failedChapters.join(',')}失败` : undefined
  };
}

// ============ 并行内容生成节点（新策略专用） ============

/** 默认并发数（可通过环境变量 LLM_BOOK_CONCURRENCY 配置） */
const PARALLEL_CONCURRENCY = getBookConcurrency();

/**
 * 解析 crossReferences，按依赖层级分组
 * 组0 = 无依赖的节点（可最先并行）
 * 组1 = 依赖组0的节点
 * 组2 = 依赖组0/组1的节点
 * 依此类推
 */
function groupByDependencyLayer(
  targets: any[],
  crossReferences: Record<string, { dependsOn: string[]; usedBy: string[] }> | undefined
): { layer: number; nodes: any[] }[] {
  if (!crossReferences || Object.keys(crossReferences).length === 0) {
    // 无 crossReferences，全部放第一层（最大并行）
    return [{ layer: 0, nodes: targets }];
  }

  // 建立 number → 节点映射
  const nodeByNumber = new Map<number, any>();
  targets.forEach(t => nodeByNumber.set(t.number, t));

  // 计算每个节点的入度（有多少依赖）
  interface NodeDegree { node: any; dependsOn: number[]; layer: number; }
  const degrees: NodeDegree[] = targets.map(t => ({
    node: t,
    dependsOn: [],
    layer: 0,
  }));

  // 解析依赖关系
  for (const [refKey, ref] of Object.entries(crossReferences)) {
    // refKey 格式: "ch3" 或 "chapter3"
    const match = refKey.match(/ch(?:apter)?(\d+)/i);
    if (!match) continue;
    const num = parseInt(match[1]);
    const node = degrees.find(d => d.node.number === num);
    if (!node) continue;

    // 解析 dependsOn
    const depNums: number[] = [];
    for (const dep of (ref.dependsOn || [])) {
      const depMatch = dep.match(/ch(?:apter)?(\d+)/i);
      if (depMatch) depNums.push(parseInt(depMatch[1]));
    }
    node.dependsOn = depNums;
  }

  // 拓扑分层：迭代找出每层的无依赖节点
  const layers: any[][] = [];
  const assigned = new Set<number>();
  let changed = true;
  while (changed) {
    changed = false;
    const currentLayer: any[] = [];
    for (const d of degrees) {
      if (assigned.has(d.node.number)) continue;
      // 检查所有依赖是否已分配
      const allDepsAssigned = d.dependsOn.every(dep => assigned.has(dep));
      if (allDepsAssigned || d.dependsOn.length === 0) {
        currentLayer.push(d.node);
        assigned.add(d.node.number);
        changed = true;
      }
    }
    if (currentLayer.length > 0) layers.push(currentLayer);
  }

  // 未分配节点（孤立的循环依赖等）放入最后一层
  const unassigned = targets.filter(t => !assigned.has(t.number));
  if (unassigned.length > 0) layers.push(unassigned);

  return layers.map((nodes, i) => ({ layer: i, nodes }));
}

/**
 * 为单个叶节点生成内容的函数（供并行调用）
 */
async function generateSingleNodeContent(
  target: any,
  context: {
    bookId: string;
    bookIdNum: number;
    topic: string;
    chapterMap: Map<number, any>;
    sectionMap: Map<number, any>;
    bookTools: any;
    book: any;
    bookDescription: string;
    writingStyle: string;
  }
): Promise<{ success: boolean; wordCount: number; error?: string }> {
  const { bookId, bookIdNum, topic, chapterMap, sectionMap, bookTools, book, bookDescription, writingStyle } = context;

  // 查找父节点信息
  const parentSection = sectionMap.get(target.parentId || 0);
  const parentChapter = parentSection ? chapterMap.get(parentSection.parentId || 0) : null;

  const chapterTitle = parentChapter?.title || '未知章';
  const sectionTitle = parentSection?.title || '未知节';
  const chapterSummary = parentChapter?.summary || '';
  const sectionSummary = parentSection?.summary || '';

  // 判断是短文还是长篇
  const isShortArticle = !parentSection && target.level === 1;
  // 注：messages 由 generateSingleChapterContent 内部构建，此处只记录日志
  if (isShortArticle) {
    console.log(`[ParallelContent] 生成短文「${target.title}」`);
  } else {
    console.log(`[ParallelContent] 生成${chapterTitle} - ${sectionTitle} - ${target.title}`);
  }

  try {
    // ===== 直接生成内容（不使用队列，由 AsyncPool 并发生成） =====
    const existing = await prisma.bookChapter.findUnique({
      where: { id: target.id },
      select: { content: true, wordCount: true, genStage: true },
    });
    if (existing?.content && existing.genStage === 'content_completed') {
      return { success: true, wordCount: existing.wordCount || countWords(existing.content) };
    }

    const MAX_RETRIES = 3;
    let lastError: Error | null = null;
    for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
      try {
        if (attempt > 0) {
          const delay = Math.min(3000 * Math.pow(2, attempt - 1) + Math.random() * 2000, 30000);
          console.log(`[ParallelContent] 「${target.title}」第${attempt + 1}次重试，等待${(delay / 1000).toFixed(1)}s...`);
          await new Promise(resolve => setTimeout(resolve, delay));
        }
        // 全局并发控制：LLM 调用走信号量，避免多书同时打爆 API 限流
        await withGlobalLLMConcurrency(() => bookStore.generateSingleChapterContent(bookId, target.id));
        lastError = null;
        break;
      } catch (err: any) {
        lastError = err;
        console.error(`[ParallelContent] 「${target.title}」第${attempt + 1}次失败:`, err.message);
      }
    }
    if (lastError) throw lastError;

    // 重新读取（generateSingleChapterContent 已更新 DB）
    const chapter = await prisma.bookChapter.findUnique({
      where: { id: target.id },
      select: { content: true },
    });
    let content = chapter?.content || '';
    let wordCount = countWords(content);

    // 章节预算偏差检测 — 智能截断（按句截断 + 补结尾句）
    const estimated = target.estimatedWords || 500;
    if (estimated > 0 && wordCount > estimated * 3) {
      console.warn(`[Security] 「${target.title}」字数异常: ${wordCount} > ${estimated}×3，截断`);
      const budgetChars = Math.round(estimated * 2.5);
      content = truncateAtBoundary(content, budgetChars, 'sentence');
      // 补一个自然结尾句，避免截断后看起来像没写完
      content = appendNaturalClosure(content);
      wordCount = countWords(content);
      await bookStore.updateChapterById(target.id, { content, wordCount });
    }

    // ===== 事中一致性跟踪：注册术语到全局跟踪器 =====
    if (content && content.length > 50) {
      try {
        globalConsistencyTracker.registerChapter(
          target.number,
          target.title || '',
          content
        );
      } catch (trackErr) {
        // 跟踪失败不阻塞内容生成
        console.warn(`[ParallelContent] 一致性跟踪失败:`, trackErr);
      }
    }

    // 配额消耗已移至 TTS 完成回调，不在内容生成阶段扣除
    // 音频生成由 AudioScanner 统一负责，不在此处 fire-and-forget

    return { success: true, wordCount };
  } catch (error) {
    const errorMsg = error instanceof Error ? error.message : '失败';
    await bookStore.updateChapterById(target.id, { contentError: errorMsg });
    await regenerateChapter(target.id, 'failed');
    console.error(`[ParallelContent] 「${target.title}」失败:`, errorMsg);
    return { success: false, wordCount: 0, error: errorMsg };
  }
}

/**
 * 并行内容生成节点
 *
 * 使用 AsyncPool 并发生成所有叶节点的内容。
 * 根据 deepPlan 中的 crossReferences 做拓扑排序，
 * 有依赖的章节先生成，被依赖的章节后生成。
 *
 * 解决 issue #2（内容生成纯串行）
 */
export async function writeChaptersParallelNode(state: typeof GraphState.State): Promise<Partial<typeof GraphState.State>> {
  console.log('[ParallelContent] 并行生成章节内容, bookId:', state.bookId);

  const book = await bookStore.getById(state.bookId);
  if (!book || !book.outline) {
    console.log('[ParallelContent] 无大纲或书籍，跳过');
    return { finished: true, progress: PROGRESS.CONTENT_END };
  }

  const bookIdNum = parseInt(state.bookId);

  // 查找所有章节
  const allChapters = await prisma.bookChapter.findMany({
    where: { bookId: bookIdNum },
    orderBy: { number: 'asc' }
  });

  // 找出所有叶节点
  const allParentIds = new Set(allChapters.map(c => c.parentId));
  const leafNodes = allChapters.filter(c => !allParentIds.has(c.id));

  // 过滤出未完成内容的目标
  const targetsToGenerate = leafNodes.filter((n: any) => n.genStage !== 'content_completed' && !n.content);

  if (targetsToGenerate.length === 0) {
    console.log('[ParallelContent] 所有内容已完成（音频由 AudioScanner 统一负责）');
    return { finished: true, progress: PROGRESS.CONTENT_END };
  }

  console.log(`[ParallelContent] ${targetsToGenerate.length} 个叶节点待生成，并发数=${PARALLEL_CONCURRENCY}`);

  // 构建父节点映射
  const chapterMap = new Map<number, any>();
  const sectionMap = new Map<number, any>();
  const chaptersAndSections = await prisma.bookChapter.findMany({
    where: { bookId: bookIdNum, level: { in: [1, 2] } }
  });
  chaptersAndSections.forEach(c => {
    if (c.level === 1) chapterMap.set(c.id, c);
    if (c.level === 2) sectionMap.set(c.id, c);
  });

  const bookTools = createBookTools(state.bookId, bookStore);

  // 解析 crossReferences 做拓扑分组
  let crossReferences: Record<string, { dependsOn: string[]; usedBy: string[] }> = {};
  if (state.bookPlan) {
    try {
      const plan = JSON.parse(state.bookPlan);
      crossReferences = plan.crossReferences || {};
    } catch { /* ignore */ }
  }

  // 按依赖层级分组
  const layers = groupByDependencyLayer(targetsToGenerate, crossReferences);
  console.log(`[ParallelContent] 拓扑分层: ${layers.length} 层`);
  layers.forEach((l, i) => {
    console.log(`  层${i}: [${l.nodes.map(n => n.number).join(', ')}]`);
  });

  // 分层执行：每层内并行，层间串行（依赖满足后下一层才能开始）
  const context = {
    bookId: state.bookId,
    bookIdNum,
    topic: state.topic,
    chapterMap,
    sectionMap,
    bookTools,
    book,
    bookDescription: book.description || '',
    writingStyle: book.style || '',
  };

  let totalSucceeded = 0;
  let totalFailed = 0;
  let totalWords = 0;

  for (const { layer, nodes } of layers) {
    if (nodes.length === 0) continue;
    console.log(`[ParallelContent] 执行层${layer}（${nodes.length}个节点，并发=${PARALLEL_CONCURRENCY}）`);

    // ===== 实时字数校验：在每层开始前检查是否已超限 =====
    const currentTotalWords = await getGeneratedWordCount(state.bookId);
    const scaleWords = parseInt(state.bookScale);
    if (scaleWords && currentTotalWords >= getWordUpperLimit(scaleWords)) {
      console.warn(`[ParallelContent] ⚠️ 累计字数 ${currentTotalWords} 已达规模上限 ${getWordUpperLimit(scaleWords)}，终止后续层`);
      break;
    }
    const bookWordLimit = getBookWordLimit();
    if (currentTotalWords > bookWordLimit) {
      console.warn(`[ParallelContent] ⚠️ 全书字数超限 ${currentTotalWords} > ${bookWordLimit}，终止后续层`);
      break;
    }

    const tasks = nodes.map(target => ({
      fn: () => generateSingleNodeContent(target, context),
    }));

    const pool = new AsyncPool(PARALLEL_CONCURRENCY);
    const results = await pool.runAll(tasks);

    const succeeded = results.filter(r => r.success).length;
    const failed = results.filter(r => !r.success).length;
    totalSucceeded += succeeded;
    totalFailed += failed;
    totalWords += results.reduce((sum, r) => sum + r.wordCount, 0);

    console.log(`[ParallelContent] 层${layer}完成: ${succeeded} 成功, ${failed} 失败`);

    // ===== 失败比例超阈值终止 =====
    const totalProcessed = totalSucceeded + totalFailed;
    const FAILURE_THRESHOLD = 0.3; // 30% 失败率
    if (totalProcessed >= 3 && totalFailed / totalProcessed > FAILURE_THRESHOLD) {
      console.warn(`[ParallelContent] ⚠️ 失败率 ${((totalFailed / totalProcessed) * 100).toFixed(1)}% 超过阈值 ${(FAILURE_THRESHOLD * 100).toFixed(0)}%，终止后续层`);
      break;
    }
  }

  console.log(`[ParallelContent] 总计: ${totalSucceeded} 成功, ${totalFailed} 失败, 总字数=${totalWords}`);

  // 安全更新父节点状态（逐条验证，不再用 updateMany 盲改）
  await safelyCompleteParentChapters(bookIdNum);

  // 检查是否有配额/字数问题
  const currentWordCount = await getGeneratedWordCount(state.bookId);
  const scaleWords = parseInt(state.bookScale);
  const interrupts: string[] = [];

  if (scaleWords && currentWordCount >= getWordUpperLimit(scaleWords)) {
    interrupts.push(`已达规模上限（${currentWordCount}字）`);
  }

  const bookWordLimit = getBookWordLimit();
  if (currentWordCount > bookWordLimit) {
    interrupts.push(`全书字数超限（${currentWordCount} > ${bookWordLimit}）`);
  }

  // ===== 事中一致性报告（Issue 4）=====
  if (totalSucceeded > 0) {
    const report = globalConsistencyTracker.generateReport();
    if (report.hasConflicts) {
      console.warn(`[ParallelContent] ⚠️ 一致性检查发现 ${report.issues.length} 个问题：`);
      report.issues.forEach(issue => {
        console.warn(`  [${issue.severity}] ${issue.type}: ${issue.description}`);
      });
      // 将问题追加到 error 字段，前端可展示
      if (report.issues.filter(i => i.severity === 'high').length > 0) {
        interrupts.push(`术语一致性：${report.issues.filter(i => i.severity === 'high').length}个冲突`);
      }
    } else {
      console.log(`[ParallelContent] ✅ 术语一致性检查通过（${report.terms.length}个术语无冲突）`);
    }
  }

  return {
    currentChapter: leafNodes.length,
    progress: PROGRESS.CONTENT_END,
    error: totalFailed > 0
      ? `${totalFailed}个节点失败` + (interrupts.length > 0 ? `; ${interrupts.join('; ')}` : '')
      : (interrupts.length > 0 ? interrupts.join('; ') : undefined),
  };
}
