/**
 * 富信息大纲生成节点（richOutlineNode）
 *
 * 在 full-outline.node.ts 基础上增强：
 * - 每个大纲节点附加 writingInstructions（开篇方式、结构、必覆盖内容、避重复内容、核心收获）
 * - 使用 deepPlan 的分析结果指导大纲生成
 * - 复用 bookStore.createChapterItem（已返回 ID，无竞态），解决 issue #7
 */

import { GraphState } from '../graph';
import { bookStore } from '../book-generator.store';
import { callLLMWithMessages, ChatMessage } from '../../../services/llm';
import { callLLMWithRetry, executeNodeWithTimeout, FAULT_TOLERANCE_CONFIG } from '../fault-tolerance';
import { RICH_OUTLINE_SYSTEM_PROMPT, RICH_OUTLINE_MARKDOWN_PROMPT } from '../prompts/templates';
import { getScaleConfig } from '../book-type-config';
import { PROGRESS } from '../utils';
import { evaluateOutlineQuality } from '../utils/outline-quality';
import { validateInstructionConsistency, InstructionNode } from '../utils/instruction-consistency';
import { prisma } from '../../../models';
import { jsonrepair } from 'jsonrepair';
import fs from 'fs';
import path from 'path';

/** 大纲质量自动重试的最低分数阈值 */
const OUTLINE_RETRY_THRESHOLD = 75;

/** 富信息大纲最大输出 token 数（65 章全量输出约需 24000 tokens，留足余量） */
const RICH_OUTLINE_MAX_TOKENS = 48000;

/**
 * 保存 AI 原始响应到临时文件（用于调试解析失败）
 */
function saveFailedResponse(bookId: string, response: string, errorCtx: string): string {
  try {
    const tempDir = path.join(process.cwd(), 'temp');
    if (!fs.existsSync(tempDir)) {
      fs.mkdirSync(tempDir, { recursive: true });
    }
    const timestamp = Date.now();
    const filename = `rich-outline-fail_${bookId}_${timestamp}.txt`;
    const filepath = path.join(tempDir, filename);
    const content = `[${new Date().toISOString()}] ${errorCtx}\n\n========== AI 原始响应 (${response.length} chars) ==========\n${response}\n\n========== 响应结尾500字符 ==========\n${response.slice(-500)}`;
    fs.writeFileSync(filepath, content, 'utf-8');
    console.log(`[RichOutline] 失败响应已保存: ${filepath}`);
    return filepath;
  } catch (e) {
    console.warn('[RichOutline] 保存失败响应文件失败:', e);
    return '';
  }
}

/**
 * 构建富信息大纲提示词
 */
function buildRichOutlineMessages(
  title: string,
  description: string,
  bookScale: string,
  genLevel: number,
  bookPlan?: any
): ChatMessage[] {
  const config = getScaleConfig(bookScale);
  const totalWords = config?.totalWords || 130000;
  const targetChapters = config?.chapters || 17;

  const levelDesc: Record<number, string> = {
    1: '仅章：只生成章节列表，不要节和小节。',
    2: '章→节：每章下生成若干节（2-4节），节下不要拆小节。',
    3: '章→节→小节：每章下生成节（2-4节），每节下生成小节（2-4小节），完整三层结构。',
  };

  // 规划参考
  let planSection = '';
  if (bookPlan) {
    const parts: string[] = [];
    if (bookPlan.goldenThread) parts.push(`黄金主线：${bookPlan.goldenThread}`);
    if (bookPlan.writingStyle) parts.push(`写作风格：${bookPlan.writingStyle}`);
    if (bookPlan.structureLogic) parts.push(`结构逻辑：${bookPlan.structureLogic}`);
    if (bookPlan.contentDepth) parts.push(`内容深度：${bookPlan.contentDepth}`);
    if (bookPlan.targetAudienceAnalysis) parts.push(`目标读者：${bookPlan.targetAudienceAnalysis}`);
    if (parts.length > 0) planSection = `\n\n## 前序深度规划参考\n${parts.join('\n')}`;
  }

  // 环境变量 OUTLINE_FORMAT=markdown 时使用 Markdown 输出格式
  const isMarkdown = process.env.OUTLINE_FORMAT === 'markdown';
  const basePrompt = isMarkdown ? RICH_OUTLINE_MARKDOWN_PROMPT : RICH_OUTLINE_SYSTEM_PROMPT;

  const chapterReq = isMarkdown
    ? `- **你必须生成整整 ${targetChapters} 个章节，从第1章连续输出到第${targetChapters}章**`
    : `- 目标约${targetChapters}章`;

  const systemPrompt = `${basePrompt}
${planSection}

## 本书约束
- 总字数约${totalWords}字
${chapterReq}
- 大纲层级：${levelDesc[genLevel] || levelDesc[2]}
- genLevel 当前值：${genLevel}`;

  return [
    { role: 'system', content: systemPrompt },
    {
      role: 'user',
      content: `书名：《${title}》\n${description ? `需求描述：${description}\n` : ''}\n请生成完整的富信息大纲。`,
    },
  ];
}

/**
 * 构建重试用的大纲提示词（带上第一次评估的问题）
 */
function buildRichOutlineRetryMessages(
  title: string,
  description: string,
  bookScale: string,
  genLevel: number,
  bookPlan?: any,
  previousWarnings?: string[]
): ChatMessage[] {
  const config = getScaleConfig(bookScale);
  const totalWords = config?.totalWords || 130000;
  const targetChapters = config?.chapters || 17;

  const levelDesc: Record<number, string> = {
    1: '仅章：只生成章节列表，不要节和小节。',
    2: '章→节：每章下生成若干节（2-4节），节下不要拆小节。',
    3: '章→节→小节：每章下生成节（2-4节），每节下生成小节（2-4小节），完整三层结构。',
  };

  let planSection = '';
  if (bookPlan) {
    const parts: string[] = [];
    if (bookPlan.goldenThread) parts.push(`黄金主线：${bookPlan.goldenThread}`);
    if (bookPlan.writingStyle) parts.push(`写作风格：${bookPlan.writingStyle}`);
    if (bookPlan.structureLogic) parts.push(`结构逻辑：${bookPlan.structureLogic}`);
    if (bookPlan.contentDepth) parts.push(`内容深度：${bookPlan.contentDepth}`);
    if (parts.length > 0) planSection = `\n\n## 前序深度规划参考\n${parts.join('\n')}`;
  }

  const warningsSection = previousWarnings && previousWarnings.length > 0
    ? `\n\n## ⚠️ 上次生成的问题（请务必修复）\n${previousWarnings.map(w => `- ${w}`).join('\n')}`
    : '';

  const isMarkdown = process.env.OUTLINE_FORMAT === 'markdown';
  const basePrompt = isMarkdown ? RICH_OUTLINE_MARKDOWN_PROMPT : RICH_OUTLINE_SYSTEM_PROMPT;

  const chapterReq = isMarkdown
    ? `- **你必须生成整整 ${targetChapters} 个章节，从第1章到第${targetChapters}章**`
    : `- 目标约${targetChapters}章`;

  const systemPrompt = `${basePrompt}
${planSection}
${warningsSection}

## 本书约束
- 总字数约${totalWords}字
${chapterReq}
- 大纲层级：${levelDesc[genLevel] || levelDesc[2]}
- genLevel 当前值：${genLevel}

## 重要：请根据"上次生成的问题"改进本次大纲质量`;

  return [
    { role: 'system', content: systemPrompt },
    {
      role: 'user',
      content: `书名：《${title}》\n${description ? `需求描述：${description}\n` : ''}\n请生成完整的高质量富信息大纲，确保修复上次的问题。`,
    },
  ];
}

/**
 * 使用 jsonrepair 修复 AI 返回的有瑕疵 JSON
 * jsonrepair 自动处理：缺失引号、缺失括号、trailing commas、重复逗号、未转义字符等
 */
function repairJson(jsonStr: string): string {
  try {
    return jsonrepair(jsonStr);
  } catch {
    // jsonrepair 也修不了时返回原文，让下游 parse 失败时走 fallback
    return jsonStr;
  }
}

/**
 * 从截断的 JSON 中提取完整的章节数据
 * 当 AI 响应超出 maxTokens 时，JSON 会被截断，本函数尝试提取完整的章节
 */
function extractChaptersFromTruncatedJson(jsonStr: string): {
  chapters: any[];
  mainTheme: string | null;
  structureLogic: string | null;
  missing: number;
} {
  const result = { chapters: [] as any[], mainTheme: null as string | null, structureLogic: null as string | null, missing: 0 };

  try {
    // 提取 mainTheme
    const mainThemeMatch = jsonStr.match(/"mainTheme"\s*:\s*"([^"]+)"/);
    if (mainThemeMatch) result.mainTheme = mainThemeMatch[1];

    // 提取 structureLogic
    const structureLogicMatch = jsonStr.match(/"structureLogic"\s*:\s*"([^"]+)"/);
    if (structureLogicMatch) result.structureLogic = structureLogicMatch[1];

    // 提取章节数组 - 从 "chapters": [ 开始
    const chaptersArrayStart = jsonStr.indexOf('"chapters":[');
    if (chaptersArrayStart === -1) return result;

    // 找到 chapters 数组的结束位置
    // 我们通过计算嵌套的 [ 和 ] 来找到匹配的结束括号
    // 注意：需要从 '[' 之后开始计数（offset 12 = 11 个字符的 "chapters":[ 加上第一个 [）
    let bracketDepth = 0;
    let inString = false;
    let escapeNext = false;
    let arrayEnd = -1;
    const arrayOpenPos = chaptersArrayStart + 11; // position of '[' after "chapters":

    for (let i = arrayOpenPos; i < jsonStr.length; i++) {
      const ch = jsonStr[i];
      if (escapeNext) { escapeNext = false; continue; }
      if (ch === '\\') { escapeNext = true; continue; }
      if (ch === '"') { inString = !inString; continue; }
      if (inString) continue;

      if (ch === '[') bracketDepth++;
      else if (ch === ']') {
        bracketDepth--;
        if (bracketDepth === 0) { arrayEnd = i; break; }
      }
    }

    if (arrayEnd === -1) return result;

    // 提取数组内容
    const arrayContent = jsonStr.substring(arrayOpenPos + 1, arrayEnd);

    // 使用 jsonrepair 修复 AI JSON 瑕疵
    const fixedContent = repairJson('[' + arrayContent + ']');

    // 尝试解析修复后的数组
    try {
      const chapters = JSON.parse(fixedContent);
      if (Array.isArray(chapters) && chapters.length > 0) {
        // 验证每个章节是否有必要的字段
        for (const ch of chapters) {
          if (ch && ch.title && typeof ch.number === 'number') {
            result.chapters.push({
              number: ch.number,
              title: ch.title,
              summary: ch.summary || '',
              keyPoints: Array.isArray(ch.keyPoints) ? ch.keyPoints : [],
              estimatedWords: ch.estimatedWords || 0,
              writingInstructions: ch.writingInstructions || '',
            });
          }
        }
      }
    } catch {
      // JSON.parse 失败，尝试逐个提取章节（处理嵌套对象）
      // 用简单的 bracket 计数来分割各章
      const chapterObjs: string[] = [];
      let depth = 0, start = 0;
      for (let i = 0; i < arrayContent.length; i++) {
        if (arrayContent[i] === '{') { if (depth === 0) start = i; depth++; }
        else if (arrayContent[i] === '}') { depth--; if (depth === 0) chapterObjs.push(arrayContent.substring(start, i + 1)); }
      }
      for (const chStr of chapterObjs) {
        try {
          const repairedCh = repairJson(chStr);
          const ch = JSON.parse(repairedCh);
          if (ch && ch.title && ch.number) {
            result.chapters.push({
              number: ch.number, title: ch.title,
              summary: ch.summary || '',
              keyPoints: Array.isArray(ch.keyPoints) ? ch.keyPoints : [],
              estimatedWords: ch.estimatedWords || 0,
              writingInstructions: ch.writingInstructions || '',
            });
          }
        } catch { /* skip invalid chapter objects */ }
      }
    }

    // 检查最后一个章节是否被截断（通过检查是否有完整的 estimatedWords）
    if (result.chapters.length > 0) {
      const lastChapter = result.chapters[result.chapters.length - 1];
      if (!lastChapter.estimatedWords || lastChapter.estimatedWords === 0) {
        result.missing = 1; // 最后一个章节可能不完整
      }
    }
  } catch (e) {
    console.warn('[RichOutline] extractChaptersFromTruncatedJson 失败:', e);
  }

  return result;
}

/**
 * 策略5：从 Markdown 格式中解析大纲
 *
 * 模型用「母语」markdown 输出时，格式更稳定、单点故障影响更小。
 * 支持格式：
 *   ## 第N章 标题
 *   > 摘要（可选）
 *   **知识点**: kp1 | kp2
 *   **字数**: 2000
 *   ### 写作指令
 *   - **开篇**: ...  /  - **必须覆盖**: 换行 - item
 */
function parseMarkdownOutline(text: string): any | null {
  // 按 ## 标题分割章节
  const chapterBlocks = text.split(/^##\s+(?!写作指令|必须覆盖|不要重复)/m);
  if (chapterBlocks.length < 2) return null; // 至少要有1章+前言

  // 提取主题（# 开头的一行，或开头第一段直到 ##）
  const h1Match = text.match(/^#\s+(.+)$/m);
  const mainTheme = h1Match ? h1Match[1].trim() : '';

  // 提取结构逻辑（# 和第一个 ## 之间的描述文本）
  let structureLogic = '';
  const firstChIdx = text.search(/^##\s+/m);
  if (firstChIdx > 0) {
    const intro = text.substring(0, firstChIdx);
    const introLines = intro.replace(/^#\s+.+\n?/m, '').trim().split(/\n/).filter(l => l.trim() && !l.startsWith('>'));
    structureLogic = introLines.slice(0, 3).join(' ').trim(); // 取前3行
  }

  const chapters: any[] = [];
  for (const block of chapterBlocks) {
    const lines = block.split('\n');
    let lineIdx = 0;

    // 跳过空行
    while (lineIdx < lines.length && !lines[lineIdx].trim()) lineIdx++;
    if (lineIdx >= lines.length) continue;

    // 解析标题行: "第N章" 或 "第N章 标题" 或 "N. 标题"
    const titleLine = lines[lineIdx].trim();
    const titleMatch = titleLine.match(/第\s*(\d+)\s*章\s*(.*)/) || titleLine.match(/^(\d+)[\.\、\s]+(.+)/);
    if (!titleMatch) continue;

    const number = parseInt(titleMatch[1]);
    const title = (titleMatch[2] || titleMatch[1]).trim();
    lineIdx++;

    // 解析摘要（以 > 开头的引用行）
    let summary = '';
    while (lineIdx < lines.length && lines[lineIdx].trim().startsWith('>')) {
      summary += lines[lineIdx].trim().replace(/^>\s*/, '') + ' ';
      lineIdx++;
    }
    summary = summary.trim();

    // 解析 keyPoints 和 estimatedWords
    const keyPoints: string[] = [];
    let estimatedWords = 0;
    while (lineIdx < lines.length && !lines[lineIdx].trim().startsWith('##') && !lines[lineIdx].trim().startsWith('#')) {
      const line = lines[lineIdx].trim();
      if (!line) { lineIdx++; continue; }

      // **知识点**: kp1 | kp2
      const kpMatch = line.match(/\*\*知识点\*\*[：:]\s*(.+)/);
      if (kpMatch) {
        kpMatch[1].split(/\s*[|｜,，、\/]\s*/).forEach(k => {
          const cleaned = k.trim().replace(/^[-*]\s*/, '');
          if (cleaned) keyPoints.push(cleaned);
        });
        lineIdx++; continue;
      }

      // **字数**: 2000
      const wMatch = line.match(/\*\*字数\*\*[：:]\s*(\d+)/);
      if (wMatch) {
        estimatedWords = parseInt(wMatch[1]);
        lineIdx++; continue;
      }

      // 知识点作为列表项: - item 或 1. item
      if (/^[-*]\s/.test(line) && !line.includes('**') && keyPoints.length < 8) {
        keyPoints.push(line.replace(/^[-*]\s*/, '').trim());
        lineIdx++; continue;
      }

      // 如果遇到 ### 或下一个 ##，停止
      if (line.startsWith('###') || line.startsWith('## ')) break;
      lineIdx++;
    }

    // 解析 writingInstructions
    let writingInstructions: any = null;
    const instrIdx = block.indexOf('### 写作指令');
    if (instrIdx !== -1) {
      const instrBlock = block.substring(instrIdx);
      writingInstructions = parseMarkdownInstructions(instrBlock);
    } else {
      // 没有显式"写作指令"小节，尝试解析分散的字段
      writingInstructions = parseMarkdownInstructions(block);
    }

    chapters.push({
      number,
      title,
      summary,
      keyPoints: keyPoints.length > 0 ? keyPoints : ['核心概念', '关键知识', '实践要点'],
      estimatedWords: estimatedWords || 2000,
      writingInstructions: writingInstructions || {
        opening: '以案例或问题引入',
        structure: '概念→原理→应用',
        mustCover: ['核心概念', '关键应用'],
        mustNotRepeat: ['与前文重复的内容'],
        toneAdjustment: '专业但不晦涩',
        keyTakeaway: '掌握核心知识点',
      },
    });
  }

  if (chapters.length >= 2) return { mainTheme, structureLogic, chapters };
  return null;
}

/**
 * 解析 writingInstructions 块（markdown 格式）
 */
function parseMarkdownInstructions(text: string): any | null {
  const result: any = {
    opening: '',
    structure: '',
    mustCover: [] as string[],
    mustNotRepeat: [] as string[],
    toneAdjustment: '',
    keyTakeaway: '',
  };

  const lines = text.split('\n');

  for (let i = 0; i < lines.length; i++) {
    const line = lines[i].trim();
    if (!line) continue;

    // - **开篇**: xxx  或  **开篇**: xxx
    const openingMatch = line.match(/(?:^[-*]\s*)?\*\*开篇\*\*[：:]\s*(.+)/);
    if (openingMatch) { result.opening = openingMatch[1].trim(); continue; }

    const structureMatch = line.match(/(?:^[-*]\s*)?\*\*结构\*\*[：:]\s*(.+)/);
    if (structureMatch) { result.structure = structureMatch[1].trim(); continue; }

    const toneMatch = line.match(/(?:^[-*]\s*)?\*\*语调\*\*[：:]\s*(.+)/);
    if (toneMatch) { result.toneAdjustment = toneMatch[1].trim(); continue; }

    const takeawayMatch = line.match(/(?:^[-*]\s*)?\*\*核心收获\*\*[：:]\s*(.+)/);
    if (takeawayMatch) { result.keyTakeaway = takeawayMatch[1].trim(); continue; }

    // 多行列表: - **必须覆盖**: 后跟 - item1  \n  - item2
    if (line.includes('**必须覆盖**') || line.includes('**Must Cover**')) {
      i = collectListItems(lines, i, result.mustCover);
      continue;
    }
    if (line.includes('**不要重复**') || line.includes('**Must Not Repeat**')) {
      i = collectListItems(lines, i, result.mustNotRepeat);
      continue;
    }
  }

  return result;
}

/** 收集后续的 - item 列表项 */
function collectListItems(lines: string[], startIdx: number, target: string[]): number {
  let i = startIdx;
  // 当前行可能包含标题后的第一个项: "**必须覆盖**: - item1"
  const inlineMatch = lines[i].match(/：\s*[-*]\s*(.+)/);
  if (inlineMatch) target.push(inlineMatch[1].trim());

  while (i + 1 < lines.length) {
    const next = lines[i + 1].trim();
    if (/^[-*]\s/.test(next) && !next.includes('**')) {
      target.push(next.replace(/^[-*]\s*/, '').trim());
      i++;
    } else if (!next || next.startsWith('#')) {
      break;
    } else {
      break;
    }
  }
  return i;
}

/**
 * 解析富信息大纲 JSON
 * 尝试多种解析策略，确保能从 AI 响应中提取有效 JSON
 * 返回 { outline, error }，error 为空表示解析成功
 *
 * 修复记录：
 * - 2026-05-15: 修复 indexOf('') 的严重 Bug
 * - 2026-05-17: 增强解析策略 + 保存失败响应到文件 + 返回详细错误信息
 */
function parseRichOutline(jsonStr: string, bookId?: string): { outline: any | null; error: string } {
  // 记录原始响应用于调试
  console.log('[RichOutline] 原始响应长度:', jsonStr.length);
  if (jsonStr.length > 2000) {
    console.log('[RichOutline] 响应开头:', jsonStr.substring(0, 500));
    console.log('[RichOutline] 响应结尾:', jsonStr.substring(jsonStr.length - 500));
  } else {
    console.log('[RichOutline] 完整响应:', jsonStr);
  }

  try {
    let cleaned = jsonStr.trim();

    // 移除思考标签内容（如 <think>...</think>）
    const thinkTagMatch = cleaned.match(/<think>[\s\S]*?<\/think>/i);
    if (thinkTagMatch) {
      cleaned = cleaned.replace(thinkTagMatch[0], '').trim();
      console.log('[RichOutline] 已移除 <think> 标签，截取后长度:', cleaned.length);
    }

    // 移除 markdown 代码块标记
    cleaned = cleaned.replace(/```json\s*/gi, '').replace(/```\s*/g, '');

    // 策略1：jsonrepair 修复后直接解析
    {
      const repaired = repairJson(cleaned);
      try {
        const data = JSON.parse(repaired);
        if (data.chapters && Array.isArray(data.chapters) && data.chapters.length > 0) {
          console.log('[RichOutline] 策略1成功：jsonrepair + 直接解析');
          return { outline: data, error: '' };
        }
      } catch (e: any) {
        console.log('[RichOutline] 策略1 jsonrepair 后解析失败:', e.message.substring(0, 100));
      }
    }

    // 策略2：正则提取 JSON 对象 + jsonrepair
    const match = cleaned.match(/\{[\s\S]*\}/);
    if (match) {
      try {
        const repaired = repairJson(match[0]);
        const data = JSON.parse(repaired);
        if (data.chapters && Array.isArray(data.chapters) && data.chapters.length > 0) {
          console.log('[RichOutline] 策略2成功：正则提取 + jsonrepair');
          return { outline: data, error: '' };
        }
      } catch (e: any) {
        console.log('[RichOutline] 策略2 失败:', e.message.substring(0, 100));
      }
    }

    // 策略3：从截断 JSON 中提取章节
    const truncationResult = extractChaptersFromTruncatedJson(cleaned);
    if (truncationResult.chapters.length > 0) {
      console.log(`[RichOutline] 策略3：从截断JSON提取到 ${truncationResult.chapters.length} 个完整章节`);
      if (truncationResult.missing > 0) {
        console.warn(`[RichOutline]   警告: ${truncationResult.missing} 个章节不完整或被截断`);
      }
      return {
        outline: {
          mainTheme: truncationResult.mainTheme || '',
          structureLogic: truncationResult.structureLogic || '',
          chapters: truncationResult.chapters,
        },
        error: ''
      };
    }

    // 策略4（fallback）：从自由文本中提取章节标题
    {
      const chapterPatterns: { title: string; summary: string; sections: any[] }[] = [];
      const chapterRegex = /(?:第\s*(\d+|[一二三四五六七八九十百千]+)\s*章|Chapter\s*(\d+))\s*[：:\s]*([^\n]{2,50})(?:\n|$)/gi;
      let chMatch;
      while ((chMatch = chapterRegex.exec(cleaned)) !== null) {
        chapterPatterns.push({ title: chMatch[3].trim(), summary: '', sections: [] });
      }
      if (chapterPatterns.length >= 2) {
        console.log(`[RichOutline] 策略4：从文本提取到 ${chapterPatterns.length} 个章节标题`);
        return {
          outline: {
            chapters: chapterPatterns.map((ch, i) => ({
              number: i + 1, title: ch.title, summary: '',
              keyPoints: [], estimatedWords: 0,
              writingInstructions: '使用简洁明了的语言', sections: [],
            })),
          },
          error: ''
        };
      }
    }

    // 策略5：尝试 Markdown 格式解析（模型用"母语"输出时格式更稳定）
    {
      const mdResult = parseMarkdownOutline(cleaned);
      if (mdResult) {
        console.log(`[RichOutline] 策略5成功：Markdown解析 → ${mdResult.chapters.length} 个章节`);
        return { outline: mdResult, error: '' };
      }
    }

    // 所有策略都失败 → 保存响应到文件并返回详细错误
    const savedPath = bookId ? saveFailedResponse(bookId, jsonStr, 'ALL_PARSE_STRATEGIES_FAILED') : '';
    const errorDetail = savedPath
      ? `富信息大纲解析失败：AI返回格式无法识别（调试文件: ${path.basename(savedPath)}）。响应长度${jsonStr.length}字符。`
      : `富信息大纲解析失败：AI返回格式无法识别。响应长度${jsonStr.length}字符。`;

    console.error('[RichOutline] ❌ 所有解析策略失败:', errorDetail);
    return { outline: null, error: errorDetail };
  } catch (e: any) {
    const errorMsg = `解析异常: ${e.message}`;
    console.error('[RichOutline] 解析异常:', errorMsg);
    if (bookId) saveFailedResponse(bookId, jsonStr, `PARSE_EXCEPTION: ${e.message}`);
    return { outline: null, error: errorMsg };
  }
}

/**
 * 富信息大纲生成节点
 * 一次 AI 调用生成完整树形大纲 + 写作指令，并全部入库
 */
export async function richOutlineNode(state: typeof GraphState.State): Promise<Partial<typeof GraphState.State>> {
  console.log('[RichOutline] 生成富信息大纲, bookId:', state.bookId, 'genLevel:', state.genLevel);

  // 读取规划结果
  let bookPlan: any = null;
  if (state.bookPlan) {
    try { bookPlan = JSON.parse(state.bookPlan); } catch { /* ignore */ }
  }

  try {
    const book = await bookStore.getById(state.bookId);
    const title = book?.title || state.topic;
    const description = book?.description || '';

    const response = await executeNodeWithTimeout(
      state.bookId,
      'rich_outline',
      async () => {
        return callLLMWithRetry(
          buildRichOutlineMessages(title, description, state.bookScale, state.genLevel, bookPlan),
          undefined,
          { bookId: state.bookId, nodeId: 'rich_outline', attempt: 0, maxAttempts: FAULT_TOLERANCE_CONFIG.aiRetry.maxRetries },
          RICH_OUTLINE_MAX_TOKENS
        );
      },
      FAULT_TOLERANCE_CONFIG.nodeTimeout.generate_outline
    );

    const parseResult = parseRichOutline(response, state.bookId);
    if (!parseResult.outline) {
      // 解析失败，把详细错误信息写入数据库
      throw new Error(parseResult.error || '富信息大纲解析失败');
    }
    let outline = parseResult.outline;

    // ===== 质量评估 + 低分自动重试（Issue 12）=====
    let quality = evaluateOutlineQuality(
      outline.chapters,
      getScaleConfig(state.bookScale),
      state.genLevel
    );
    console.log(`[RichOutline] 质量评估: 分数=${quality.score}, 通过=${quality.passed}, 警告=${quality.warnings.length}`);

    // 分数低于阈值，自动用增强提示词重试一次
    let retryCount = 0;
    const MAX_QUALITY_RETRIES = 1; // 只额外重试1次
    while (quality.score < OUTLINE_RETRY_THRESHOLD && retryCount < MAX_QUALITY_RETRIES) {
      retryCount++;
      console.warn(`[RichOutline] ⚠️ 大纲质量=${quality.score} < 阈值${OUTLINE_RETRY_THRESHOLD}，第${retryCount}次重试...`);
      if (quality.warnings.length > 0) {
        quality.warnings.forEach(w => console.warn(`[RichOutline]   - [${w.severity}] ${w.message}`));
      }

      // 用第一次评估发现的问题增强提示词
      const retryResponse = await executeNodeWithTimeout(
        state.bookId,
        'rich_outline',
        async () => {
          return callLLMWithRetry(
            buildRichOutlineRetryMessages(
              title, description, state.bookScale, state.genLevel, bookPlan,
              quality.warnings.map(w => w.message)
            ),
            undefined,
            { bookId: state.bookId, nodeId: 'rich_outline_retry', attempt: 0, maxAttempts: FAULT_TOLERANCE_CONFIG.aiRetry.maxRetries },
            RICH_OUTLINE_MAX_TOKENS
          );
        },
        FAULT_TOLERANCE_CONFIG.nodeTimeout.generate_outline
      );

      const retryResult = parseRichOutline(retryResponse, state.bookId);
      if (!retryResult.outline) {
        console.warn('[RichOutline] 重试解析失败，使用原始大纲');
        break;
      }

      const retryQuality = evaluateOutlineQuality(
        retryResult.outline.chapters,
        getScaleConfig(state.bookScale),
        state.genLevel
      );
      console.log(`[RichOutline] 重试后质量: ${retryQuality.score} (原: ${quality.score})`);

      // 用更好的那份
      if (retryQuality.score > quality.score) {
        outline = retryResult.outline;
        quality = retryQuality;
      } else {
        console.log('[RichOutline] 重试未改善，保留原始大纲');
        break;
      }
    }

    // ===== 写作指令一致性校验（Issue 6）=====
    const instructionNodes: InstructionNode[] = (outline.chapters || []).map((ch: any) => ({
      number: ch.number,
      title: ch.title,
      writingInstructions: ch.writingInstructions,
    }));
    const instructionWarnings = validateInstructionConsistency(instructionNodes);
    if (instructionWarnings.length > 0) {
      console.warn(`[RichOutline] ⚠️ 写作指令一致性检查发现 ${instructionWarnings.length} 个问题：`);
      instructionWarnings.forEach(w => {
        console.warn(`  [${w.severity}] ${w.type}: ${w.description}`);
      });
    } else {
      console.log('[RichOutline] ✅ 写作指令一致性检查通过');
    }

    // 存入数据库 — 使用 createChapterItem（返回 ID，无竞态）
    const totalChapters = outline.chapters.length;
    await bookStore.update(state.bookId, {
      totalChapters,
      outlineJson: JSON.stringify(outline),
      progress: PROGRESS.OUTLINE_DONE,
      // 清理旧的失败状态（重试成功时）
      failedStage: null,
      errorMsg: null,
    });

    const bookIdNum = parseInt(state.bookId);

    for (let i = 0; i < outline.chapters.length; i++) {
      const chapter = outline.chapters[i];
      // level=1 章
      const chapterId = await bookStore.createChapterItem(bookIdNum, {
        number: chapter.number ?? (i + 1),
        title: chapter.title,
        summary: chapter.summary,
        keyPoints: chapter.keyPoints,
        estimatedWords: chapter.estimatedWords,
      }, 0, 1);

      if (chapter.sections && state.genLevel >= 2) {
        for (let j = 0; j < chapter.sections.length; j++) {
          const section = chapter.sections[j];
          const sectionId = await bookStore.createChapterItem(bookIdNum, {
            number: section.number ?? (j + 1),
            title: section.title,
            summary: section.summary || '',
            keyPoints: section.keyPoints || [],
            estimatedWords: section.estimatedWords || 0,
          }, chapterId, 2);

          if (section.subsections && state.genLevel >= 3) {
            for (let k = 0; k < section.subsections.length; k++) {
              const subsection = section.subsections[k];
              await bookStore.createChapterItem(bookIdNum, {
                number: subsection.number ?? (k + 1),
                title: subsection.title,
                summary: subsection.summary || '',
                keyPoints: subsection.keyPoints || [],
                estimatedWords: subsection.estimatedWords || 0,
              }, sectionId, 3);
            }
          }
        }
      }
    }

    console.log('[RichOutline] 富信息大纲生成完成，章节数:', totalChapters);
    return { progress: PROGRESS.OUTLINE_DONE };
  } catch (error) {
    console.error('[RichOutline] 生成失败:', error);
    await bookStore.update(state.bookId, {
      genStage: 'failed', failedStage: 'outlining',
      errorMsg: error instanceof Error ? error.message : '富信息大纲生成失败',
    });
    return { error: error instanceof Error ? error.message : '失败', finished: true };
  }
}