/**
 * LangGraph 书籍生成 - API 路由
 * 支持书籍创建时的预估显示
 */

import Router from '@koa/router';
import { Context } from 'koa';
import { langGraphGenerator, getScaleConfig, resolveGenLevel, mapBookTypeToGenLevel } from './index';
import { bookStore, cancelAudioGeneration } from './book-generator.store';
import { prisma } from '../../models';
import { estimateBookWords, estimateAudioMinutesFromWords, checkBookGenerationQuota, checkAudioQuota, atomicReserveQuota, releaseQuota, AUDIO_BILLING_CONFIG } from '../subscription/subscription.service';
import { optionalAuth } from '../../middleware/auth';
import { getAllBookTypes, getDetectableTypes, getBookTypeConfig, BOOK_TYPE_CONFIG, DETECTABLE_TYPES } from './book-type-config';
import { callLLMWithMessages, callLLM, ChatMessage } from '../../services/llm';
import { cleanLlmShortText, extractJsonFromResponse } from '../../services/llm/response-cleaner';
import { runWithContext } from '../../services/llm-context';
import { createVideoProjectFromBook, generateVideoForProject } from '../video-generator/video-generator.service';
import { mergeChapterAudios } from '../player/player.service';
import { exec } from 'child_process';
import { promisify } from 'util';
import fs from 'fs/promises';
import path from 'path';
import { advanceChapter, regenerateChapter } from './stage-manager';
import { deepPlanBookNode } from './nodes/deep-plan.node';
import { richOutlineNode } from './nodes/rich-outline.node';
import { writeChaptersParallelNode } from './nodes/content.node';
import { continuityEditNode } from './nodes/continuity-edit.node';
import { GraphState } from './graph';

const execPromise = promisify(exec);

// 开发环境测试用户ID
const TEST_USER_ID = '1';

const router = new Router();

/**
 * GET /api/book-generator/langgraph/estimate
 * 获取书籍规模预估信息
 */
router.get('/estimate', async (ctx: Context) => {
  const { scale, userId, bookType } = ctx.query as { scale?: string; userId?: string; bookType?: string };

  if (!scale) {
    ctx.status = 400;
    ctx.body = { code: 1, message: '请提供书籍规模' };
    return;
  }

  const scaleConfig = getScaleConfig(scale);
  const totalWords = scaleConfig.totalWords;
  const audioMinutes = estimateAudioMinutesFromWords(totalWords);

  // 章节数
  const estimatedChapters = scaleConfig.isShortArticle ? 1 : scaleConfig.chapters;

  // 大纲层级（优先使用书籍类型映射）
  const genLevel = bookType && bookType !== 'auto'
    ? mapBookTypeToGenLevel(bookType)
    : resolveGenLevel(scale);
  const genLevelDescriptions: Record<number, { label: string; desc: string }> = {
    1: { label: '仅章', desc: '无节和小节，适合短文、小说' },
    2: { label: '章→节', desc: '有节无小节，适合科普、商业书' },
    3: { label: '章→节→小节', desc: '完整三级结构，适合教材、技术书' },
  };

  const result: any = {
    scale,
    scaleConfig,
    audioMinutes: {
      min: estimateAudioMinutesFromWords(Math.round(totalWords * 0.8)),
      max: estimateAudioMinutesFromWords(Math.round(totalWords * 1.2)),
      avg: audioMinutes
    },
    estimatedChapters,
    defaultGenLevel: genLevel,
    genLevelInfo: genLevelDescriptions[genLevel] || { label: '章→节', desc: '' },
  };
  
  // 如果提供了 userId，同时检查用户配额
  if (userId) {
    const quotaCheck = await checkBookGenerationQuota(parseInt(userId), scale);
    result.quotaCheck = quotaCheck;
  }
  
  ctx.body = {
    code: 0,
    message: 'success',
    data: result
  };
});

/**
 * POST /api/book-generator/langgraph/detect-scale
 * AI 预览检测书籍规模（创建前调用，让用户看到检测结果）
 */
router.post('/detect-scale', async (ctx) => {
  const { description } = ctx.request.body as { description?: string };
  if (!description) {
    ctx.body = { code: 1, message: '缺少 description' };
    return;
  }
  const scale = await autoDetectBookType('', description);
  const config = getScaleConfig(scale);
  ctx.body = {
    code: 0,
    data: {
      bookScale: scale,
      totalWords: config.totalWords,
      chapters: config.chapters,
      isShortArticle: config.isShortArticle,
    },
  };
});

/**
 * GET /api/book-generator/langgraph/book-types
 * 获取所有书籍类型配置（供前端显示）
 */
router.get('/book-types', async (ctx: Context) => {
  const types = getAllBookTypes().map(t => ({
    key: t.key,
    label: t.label,
    description: t.description,
    chapters: t.chapters,
    totalWords: t.totalWords,
    audioMinutes: t.audioMinutes,
    chapterWords: t.chapterWords,
    structureFormat: t.structureFormat,
    readingDifficulty: t.readingDifficulty,
    isShortArticle: t.isShortArticle,
  }));

  ctx.body = {
    code: 0,
    message: 'success',
    data: types,
  };
});

/**
 * POST /api/book-generator/langgraph/detect-book-type
 * AI 自动检测书籍类型
 */
router.post('/detect-book-type', async (ctx: Context) => {
  const { title, description } = ctx.request.body as {
    title?: string;
    description?: string;
  };

  if (!title) {
    ctx.status = 400;
    ctx.body = { code: 1, message: '请提供书籍标题' };
    return;
  }

  const detectableTypes = DETECTABLE_TYPES;

  // 构建类型特征描述，帮助 AI 分类
  const typeDescriptions = detectableTypes.map(key => {
    const t = BOOK_TYPE_CONFIG[key];
    return `- ${t.key}: ${t.description}，${t.chapters}章，总字数约${t.totalWords}字，每章约${t.chapterWords}字，结构形式：${t.structureFormat}，阅读难度：${t.readingDifficulty}`;
  }).join('\n');

  const messages: ChatMessage[] = [
    {
      role: 'system',
      content: `你是一位专业的图书分类编辑。根据用户提供的标题和描述，判断这本书最可能属于以下哪种类型：

可选类型：
${typeDescriptions}

## 分类规则
1. 分析标题中的关键词（如"科普""青少年""专业""小说"等）
2. 分析描述中的目标读者、写作风格、内容深度
3. 匹配最符合的类型

## 输出格式
必须返回 JSON，不要包含 markdown 代码块标记：
{
  "detectedType": "类型 key",
  "confidence": 0.85,
  "reasoning": "分类理由，1-2句话"
}

confidence 是 0-1 的数值，表示置信程度。`,
    },
    {
      role: 'user',
      content: `标题：《${title}》\n描述：${description || '无'}`,
    },
  ];

  try {
    const response = await callLLMWithMessages(messages);
    const parsed = parseDetectResult(response);

    if (!parsed) {
      // 降级到关键词匹配
      const fallback = keywordFallback(title, description || '');
      ctx.body = {
        code: 0,
        message: 'success',
        data: {
          detectedType: fallback.detectedType,
          confidence: 0.5,
          reasoning: '基于关键词匹配（AI 解析失败，使用降级策略）',
          config: getBookTypeConfig(fallback.detectedType),
        },
      };
      return;
    }

    ctx.body = {
      code: 0,
      message: 'success',
      data: {
        detectedType: parsed.detectedType,
        confidence: parsed.confidence,
        reasoning: parsed.reasoning,
        config: getBookTypeConfig(parsed.detectedType),
      },
    };
  } catch (error: any) {
    // 最终降级：关键词匹配
    const fallback = keywordFallback(title, description || '');
    ctx.body = {
      code: 0,
      message: 'success',
      data: {
        detectedType: fallback.detectedType,
        confidence: 0.3,
        reasoning: 'LLM 调用失败，使用关键词匹配降级',
        config: getBookTypeConfig(fallback.detectedType),
      },
    };
  }
});

function parseDetectResult(text: string): { detectedType: string; confidence: number; reasoning: string } | null {
  try {
    const match = text.match(/\{[\s\S]*\}/);
    if (!match) return null;
    const data = JSON.parse(match[0]);
    if (!data.detectedType) return null;
    // 验证类型是否有效
    if (!DETECTABLE_TYPES.includes(data.detectedType)) {
      // 尝试模糊匹配
      const found = DETECTABLE_TYPES.find(t => data.detectedType.includes(t) || t.includes(data.detectedType));
      if (!found) return null;
      data.detectedType = found;
    }
    return {
      detectedType: data.detectedType,
      confidence: Math.min(1, Math.max(0, data.confidence || 0.5)),
      reasoning: data.reasoning || '',
    };
  } catch {
    return null;
  }
}

function keywordFallback(_title: string, _description: string): { detectedType: string } {
  // 完全由 AI 判断，fallback 只在 LLM 完全失败时作为最后保险
  return { detectedType: '1000' }; // 最小规模
}

/**
 * AI 根据描述自动生成书名
 */
async function autoGenerateTitle(description: string, userId?: number): Promise<string> {
  return runWithContext({ userId }, async () => {
    const prompt = `根据以下描述起一个书名。要求：中文不超过20字，英文不超过20个单词。只返回书名，不要任何解释、思考或前缀。

描述：${description.substring(0, 200)}

书名：`;

    try {
      const response = await callLLM(prompt);
      const title = cleanLlmShortText(response, {
        maxLength: 50,
        maxChineseChars: 20,
        maxEnglishWords: 20,
      });
      console.log(`[LangGraph] AI 生成书名: "${title}"`);
      return title || '未命名书籍';
    } catch (e) {
      console.warn('[LangGraph] AI 自动生成书名失败，使用默认:', e);
      return '未命名书籍';
    }
  });
}

/**
 * AI 自动检测书籍规模（字数等级），后端静默调用
 * AI 分析描述中的字数要求，以 JSON 格式返回，供程序提取
 */
async function autoDetectBookType(title: string, description: string, userId?: number): Promise<string> {
  return runWithContext({ userId }, async () => {
    console.log(`[autoDetectBookType] 被调用: title="${title}", desc前50="${description?.substring(0, 50)}"`);
    const messages: ChatMessage[] = [
      {
        role: 'system',
        content: `你是一位专业的图书分类编辑。根据标题和描述，判断书籍适合的字数规模。

## 判断规则

**规则1 - 字数直接返回**：如果描述里有"X字"（如"200字""500字""几千字"），直接返回X作为bookScale值，不要映射到预定义规模。
  - 描述含"200字"→返回200
  - 描述含"500字"→返回500
  - 描述含"几千字"→估计约3000，返回3000
  - 描述含"上万字"→估计约10000，返回10000

**规则2 - 无法判断**：描述中没有字数信息 → 返回1000

## 输出格式（必须严格JSON）
{"bookScale": 数字}
只返回JSON，不要任何其他文字。`,
      },
      {
        role: 'user',
        content: `标题：《${title}》\n描述：${description}`,
      },
    ];

    try {
      const response = await callLLMWithMessages(messages);
      const trimmed = response.trim();
      console.log(`[autoDetectBookType] AI原始响应: ${trimmed.substring(0, 200)}`);

      const jsonMatch = trimmed.match(/\{[\s\S]*\}/);
      if (jsonMatch) {
        try {
          const data = JSON.parse(jsonMatch[0]);
          if (typeof data.bookScale === 'number') {
            const wordCount = data.bookScale;
            console.log(`[autoDetectBookType] AI返回: bookScale=${wordCount}`);
            return String(wordCount);
          }
        } catch {
          // JSON解析失败，忽略
        }
      }

      console.log(`[autoDetectBookType] AI响应无法解析，使用默认值1000`);
      return '1000';
    } catch (error: any) {
      console.error(`[autoDetectBookType] AI调用异常:`, error?.message || error);
      return '1000';
    }
  });
}

/**
 * POST /api/book-generator/langgraph/smart-recommend
 * 根据书籍描述智能推荐难度、人群、风格、领域
 */
router.post('/smart-recommend', async (ctx: Context) => {
  try {
    const { description, title } = ctx.request.body as { description: string; title?: string };

    if (!description || !description.trim()) {
      ctx.status = 400;
      ctx.body = { code: 1, message: '描述不能为空' };
      return;
    }

    // 默认推荐值
    const defaultResult = {
      knowledgeLevel: '入门',
      targetAudience: '通用',
      style: '轻松幽默',
      industry: '教育',
      reason: '默认推荐',
    };

    const prompt = `根据以下书籍描述，推荐合适的知识难度、目标人群、写作风格和行业领域。

书名：${title || '未命名'}
描述：${description}

请以 JSON 格式返回推荐结果，不要包含任何其他文字：
{
  "knowledgeLevel": "入门|进阶|专业",
  "targetAudience": "推荐的目标人群",
  "style": "推荐风格",
  "industry": "推荐行业领域",
  "reason": "简要说明推荐理由"
}`;

    const messages: ChatMessage[] = [
      { role: 'system', content: '你是一位专业的图书策划编辑，擅长根据书籍内容推荐合适的定位。' },
      { role: 'user', content: prompt },
    ];

    const response = await callLLMWithMessages(messages);
    const result = extractJsonFromResponse(response) || defaultResult;

    ctx.body = { code: 0, message: 'success', data: result };
  } catch (error) {
    console.error('智能推荐失败:', error);
    // 返回默认值而不是错误
    ctx.body = {
      code: 0,
      message: 'success',
      data: {
        knowledgeLevel: '入门',
        targetAudience: '通用',
        style: '轻松幽默',
        industry: '教育',
        reason: 'AI服务暂时不可用，使用默认推荐',
      },
    };
  }
});

/**
 * POST /api/book-generator/langgraph/books
 * 使用 LangGraph 创建并生成书籍（异步，自动生成大纲和内容）
 * AI 根据标题+描述自动判断书籍类型
 *
 * 优化策略：
 * 1. 并行执行 autoGenerateTitle + autoDetectBookType（串行→并行，耗时减半）
 * 2. 先创建书籍返回响应，AI 结果异步回填（用户无需等待 AI 完成即可看到书籍）
 */
router.post('/books', optionalAuth, async (ctx: Context) => {
  try {
    const body = ctx.request.body as {
      title: string;
      description: string;
      bookScale?: string;
      bookType?: string;
      genLevel?: number;
      generateForeword?: boolean;
      generateAfterword?: boolean;
      immediateGenerate?: boolean;
    };

    if (!body.description) {
      ctx.status = 400;
      ctx.body = { code: 1, message: '描述不能为空' };
      return;
    }

    // 获取用户 ID（未登录时使用测试用户）
    const userId = ctx.state.user?.userId ? parseInt(ctx.state.user.userId as string) : parseInt(TEST_USER_ID);

    const providedTitle = body.title?.trim();
    // 只接受数字格式的 bookScale（如 '200', '1000', '130000'），非数字当作未提供
    const providedBookScale = body.bookScale && /^\d+$/.test(body.bookScale) ? body.bookScale : undefined;

    // ========== 快速路径：用户已提供 title 和 bookScale，无需 AI ==========
    if (providedTitle && providedBookScale) {
      const bookScale = providedBookScale;
      const genLevel = body.genLevel !== undefined
        ? resolveGenLevel(bookScale, body.genLevel)
        : body.bookType && body.bookType !== 'auto'
          ? mapBookTypeToGenLevel(body.bookType)
          : resolveGenLevel(bookScale);

      const scaleConfig = getScaleConfig(bookScale);
      const totalWords = scaleConfig.totalWords;
      
      // 风控：预估费用 + 原子预留
      const estimatedCost = AUDIO_BILLING_CONFIG.pricing.monthly * estimateAudioMinutesFromWords(totalWords) + 0.05; // +LLM预估
      const quotaOk = await atomicReserveQuota(userId, estimatedCost);
      if (!quotaOk) {
        ctx.status = 403;
        ctx.body = { code: 1, message: '月度额度不足，请升级套餐', data: { estimatedCost, estimatedWords: totalWords } };
        return;
      }
      
      const audioQuota = await checkAudioQuota(userId, totalWords);
      if (!audioQuota.allowed) {
        await releaseQuota(userId, estimatedCost); // 退回预留
        ctx.status = 403;
        ctx.body = { code: 1, message: audioQuota.reason, data: { estimatedWords: totalWords, estimatedAudioMinutes: estimateAudioMinutesFromWords(totalWords), remainingMinutes: audioQuota.remainingMinutes, totalMinutes: audioQuota.totalMinutes } };
        return;
      }

      const book = await bookStore.create({
        userId,
        title: providedTitle,
        description: body.description,
        bookScale,
        totalChapters: scaleConfig.isShortArticle ? 1 : scaleConfig.chapters,
        estimatedWords: totalWords,
      });

      if (body.immediateGenerate === false) {
        ctx.body = { code: 0, message: '书籍创建成功（交互模式）', data: { book, genStage: 'draft', mode: 'interactive' } };
        return;
      }

      await bookStore.update(book.id, { genStage: 'outlining' });
      langGraphGenerator.generate(book.id.toString(), body.description, bookScale, genLevel, userId)
        .then(() => console.log(`[LangGraph] 生成完成: bookId=${book.id}`))
        .catch(err => {
          console.error(`[LangGraph] 生成失败: bookId=${book.id}`, err);
          bookStore.update(book.id.toString(), { genStage: 'failed', errorMsg: err instanceof Error ? err.message : '生成失败', failedStage: 'outlining' });
        });

      ctx.body = { code: 0, message: '书籍创建成功，生成已开始', data: { book, genStage: 'outlining', mode: 'async' } };
      return;
    }

    // ========== 需要 AI 辅助的路径：先创建书籍，再异步回填 ==========
    // 使用临时标题/默认规模先创建书籍，立即返回给前端
    // 规模会由 AI 异步回填，配额也会相应调整
    const fallbackScale = providedBookScale || '1000';
    const fallbackTitle = providedTitle || '正在生成标题...';
    const fallbackConfig = getScaleConfig(fallbackScale);
    const fallbackWords = fallbackConfig.totalWords;
    const fallbackCost = AUDIO_BILLING_CONFIG.pricing.monthly * estimateAudioMinutesFromWords(fallbackWords) + 0.05;

    // 配额预检查 + 原子预留（使用 fallback 规模估算）
    const audioQuota = await checkAudioQuota(userId, fallbackWords);
    if (!audioQuota.allowed) {
      ctx.status = 403;
      ctx.body = { code: 1, message: audioQuota.reason, data: { estimatedWords: fallbackWords, estimatedAudioMinutes: estimateAudioMinutesFromWords(fallbackWords), remainingMinutes: audioQuota.remainingMinutes, totalMinutes: audioQuota.totalMinutes } };
      return;
    }

    // 原子预留 fallback 配额，防止并发请求超发
    const reserveOk = await atomicReserveQuota(userId, fallbackCost);
    if (!reserveOk) {
      ctx.status = 403;
      ctx.body = { code: 1, message: '月度额度不足，请升级套餐', data: { estimatedCost: fallbackCost, estimatedWords: fallbackWords } };
      return;
    }

    // 先创建书籍，立即返回
    const book = await bookStore.create({
      userId,
      title: fallbackTitle,
      description: body.description,
      bookScale: fallbackScale,
      totalChapters: fallbackConfig.isShortArticle ? 1 : fallbackConfig.chapters,
      estimatedWords: fallbackWords,
    });

    // 标记为 AI 正在准备中
    const needTitle = !providedTitle;
    const needScale = !providedBookScale;
    const aiPreparing = needTitle || needScale;

    if (body.immediateGenerate === false) {
      // 交互模式：异步填充 AI 结果，但不需要启动生成
      if (aiPreparing) {
        fillAiResultsAsync(book.id.toString(), body.description, providedTitle, providedBookScale, userId);
      }
      // 交互模式暂不调整配额，等用户真正开始生成时再做
      ctx.body = { code: 0, message: '书籍创建成功（交互模式）', data: { book, genStage: 'draft', mode: 'interactive', aiPreparing } };
      return;
    }

    // 非交互模式：需要等 AI 完成后才能确定 bookScale/genLevel 启动生成
    await bookStore.update(book.id, { genStage: 'outlining' });

    // 立即返回响应
    ctx.body = {
      code: 0,
      message: '书籍创建成功，AI 正在准备标题和规模...',
      data: {
        book,
        genStage: 'outlining',
        mode: 'async',
        aiPreparing,
      },
    };

    // 异步：并行执行 AI 生成标题 + 检测规模，然后启动内容生成
    (async () => {
      try {
        let title = providedTitle;
        let bookScale = providedBookScale;

        // 并行执行 AI 调用
        const aiPromises: Promise<any>[] = [];
        const titleIdx = needTitle ? aiPromises.push(autoGenerateTitle(body.description, userId)) - 1 : -1;
        const scaleIdx = needScale ? aiPromises.push(autoDetectBookType(providedTitle || '', body.description, userId)) - 1 : -1;

        if (aiPromises.length > 0) {
          const results = await Promise.all(aiPromises);
          if (titleIdx >= 0) {
            title = results[titleIdx] as string;
            console.log(`[LangGraph] AI 自动生成书名: "${title}"`);
          }
          if (scaleIdx >= 0) {
            bookScale = results[scaleIdx] as string;
            console.log(`[LangGraph] AI 自动检测规模: ${title} -> ${bookScale}`);
          }
        }

        // 回填 AI 结果到数据库
        const scaleConfig = getScaleConfig(bookScale!);
        const actualWords = scaleConfig.totalWords;
        const actualCost = AUDIO_BILLING_CONFIG.pricing.monthly * estimateAudioMinutesFromWords(actualWords) + 0.05;

        // 调整配额预留：从 fallback 切换到实际规模
        if (bookScale !== fallbackScale) {
          await releaseQuota(userId, fallbackCost);
          const adjustOk = await atomicReserveQuota(userId, actualCost);
          if (!adjustOk) {
            console.error(`[LangGraph] 实际规模配额不足，回退到 fallback: bookId=${book.id}, actual=${actualCost}, fallback=${fallbackCost}`);
            // fallback 规模已在之前预留，继续使用
          } else {
            console.log(`[LangGraph] 配额已调整: fallback=${fallbackCost} -> actual=${actualCost}, bookId=${book.id}`);
          }
        }

        await bookStore.update(book.id.toString(), {
          title: title!,
          bookScale: bookScale!,
          totalChapters: scaleConfig.isShortArticle ? 1 : scaleConfig.chapters,
          estimatedWords: actualWords,
        });

        // 确定生成层级并启动内容生成
        const genLevel = body.genLevel !== undefined
          ? resolveGenLevel(bookScale!, body.genLevel)
          : body.bookType && body.bookType !== 'auto'
            ? mapBookTypeToGenLevel(body.bookType)
            : resolveGenLevel(bookScale!);

        await langGraphGenerator.generate(book.id.toString(), body.description, bookScale!, genLevel, userId);
        console.log(`[LangGraph] 生成完成: bookId=${book.id}`);
      } catch (err) {
        console.error(`[LangGraph] AI 准备或生成失败: bookId=${book.id}`, err);
        // 释放预留配额
        await releaseQuota(userId, fallbackCost);
        bookStore.update(book.id.toString(), {
          genStage: 'failed',
          errorMsg: err instanceof Error ? err.message : '生成失败',
          failedStage: 'outlining',
        });
      }
    })();
  } catch (error) {
    console.error('启动失败:', error);
    ctx.status = 500;
    ctx.body = {
      code: 1,
      message: error instanceof Error ? error.message : '启动失败',
    };
  }
});

/**
 * 异步填充 AI 结果（交互模式使用，不启动生成）
 */
async function fillAiResultsAsync(
  bookId: string,
  description: string,
  providedTitle?: string,
  providedBookScale?: string,
  userId?: number,
): Promise<void> {
  console.log(`[fillAiResultsAsync] 开始: bookId=${bookId}, providedTitle="${providedTitle}", providedBookScale="${providedBookScale}", desc前50字="${description?.substring(0, 50)}"`);
  try {
    let title = providedTitle;
    let bookScale = providedBookScale;

    const needTitle = !title;
    const needScale = !bookScale;

    const aiPromises: Promise<any>[] = [];
    const titleIdx = needTitle ? aiPromises.push(autoGenerateTitle(description, userId)) - 1 : -1;
    const scaleIdx = needScale ? aiPromises.push(autoDetectBookType(title || '', description, userId)) - 1 : -1;

    if (aiPromises.length > 0) {
      const results = await Promise.all(aiPromises);
      if (titleIdx >= 0) {
        title = results[titleIdx] as string;
        console.log(`[LangGraph] AI 自动生成书名 (交互): "${title}"`);
      }
      if (scaleIdx >= 0) {
        bookScale = results[scaleIdx] as string;
        console.log(`[LangGraph] AI 自动检测规模 (交互): ${title} -> ${bookScale}`);
      }
    }

    const scaleConfig = getScaleConfig(bookScale!);
    await bookStore.update(bookId.toString(), {
      title: title!,
      bookScale: bookScale!,
      totalChapters: scaleConfig.isShortArticle ? 1 : scaleConfig.chapters,
      estimatedWords: scaleConfig.totalWords,
    });
  } catch (err) {
    console.warn(`[LangGraph] AI 异步填充失败: bookId=${bookId}`, err);
  }
}

/**
 * GET /api/book-generator/langgraph/books
 * 获取书籍列表
 * 返回：公开的书籍（有公开音频）+ 当前用户自己的书籍
 * 注意：此接口已废弃，请使用 /public-books 或 /my-books
 */
router.get('/books', optionalAuth, async (ctx: Context) => {
  try {
    const userId = ctx.state.user?.userId;
    const userIdNum = userId ? parseInt(userId as string) : undefined;
    
    // 获取公开书籍（有公开音频的书籍）
    const publicBooks = await bookStore.getPublicBooks();
    
    // 如果用户已登录，获取用户自己的书籍
    let userBooks: any[] = [];
    if (userIdNum) {
      userBooks = await bookStore.getAllByUser(userIdNum, false);
    }
    
    // 合并并去重（按 id）
    const bookMap = new Map<string, any>();
    publicBooks.forEach(b => bookMap.set(b.id, b));
    userBooks.forEach(b => {
      if (!bookMap.has(b.id)) {
        bookMap.set(b.id, b);
      }
    });
    
    const books = Array.from(bookMap.values());
    
    ctx.body = { code: 0, message: 'success', data: { books } };
  } catch (error) {
    console.error('查询失败:', error);
    ctx.status = 500;
    ctx.body = { code: 1, message: error instanceof Error ? error.message : '查询失败' };
  }
});

/**
 * GET /api/book-generator/langgraph/public-books
 * 获取公开书籍列表（首页专用）
 * 只返回有公开音频的书籍
 */
router.get('/public-books', async (ctx: Context) => {
  try {
    const publicBooks = await bookStore.getPublicBooks();
    ctx.body = { code: 0, message: 'success', data: { books: publicBooks } };
  } catch (error) {
    console.error('查询公开书籍失败:', error);
    ctx.status = 500;
    ctx.body = { code: 1, message: error instanceof Error ? error.message : '查询失败' };
  }
});

/**
 * GET /api/book-generator/langgraph/my-books
 * 获取当前用户自己的书籍列表（管理页专用）
 * 只返回当前用户创建的书籍
 */
router.get('/my-books', optionalAuth, async (ctx: Context) => {
  try {
    const userId = ctx.state.user?.userId;
    if (!userId) {
      ctx.status = 401;
      ctx.body = { code: 1, message: '请先登录' };
      return;
    }
    
    const userBooks = await bookStore.getAllByUser(parseInt(userId as string), false);
    ctx.body = { code: 0, message: 'success', data: { books: userBooks } };
  } catch (error) {
    console.error('查询用户书籍失败:', error);
    ctx.status = 500;
    ctx.body = { code: 1, message: error instanceof Error ? error.message : '查询失败' };
  }
});

/**
 * PUT /api/book-generator/langgraph/books/:id/publish
 * 切换书籍公开状态
 */
router.put('/books/:id/publish', optionalAuth, async (ctx: Context) => {
  try {
    const bookId = ctx.params.id;
    const newStatus = await bookStore.togglePublish(bookId);
    ctx.body = { 
      code: 0, 
      message: 'success', 
      data: { isPublished: newStatus } 
    };
  } catch (error) {
    console.error('切换发布状态失败:', error);
    ctx.status = 500;
    ctx.body = { code: 1, message: error instanceof Error ? error.message : '操作失败' };
  }
});

/**
 * GET /api/book-generator/langgraph/books/:id
 * 获取书籍详情
 * 支持公开过滤：?filterPublic=true&userId=1
 */
router.get('/books/:id', optionalAuth, async (ctx: Context) => {
  try {
    const bookId = ctx.params.id as string;
    const userId = ctx.state.user?.userId || TEST_USER_ID;
    const filterPublic = ctx.query.filterPublic === 'true';
    
    const book = await bookStore.getById(bookId, filterPublic, parseInt(userId as string));

    if (!book) {
      ctx.status = 404;
      ctx.body = { code: 1, message: '书籍不存在' };
      return;
    }

    ctx.body = { code: 0, message: 'success', data: { book } };
  } catch (error) {
    console.error('查询失败:', error);
    ctx.status = 500;
    ctx.body = { code: 1, message: error instanceof Error ? error.message : '查询失败' };
  }
});

/**
 * GET /api/book-generator/langgraph/books/:id/progress
 * 获取书籍生成进度
 */
router.get('/books/:id/progress', async (ctx: Context) => {
  try {
    const bookId = ctx.params.id as string;
    const book = await bookStore.getById(bookId);

    if (!book) {
      ctx.status = 404;
      ctx.body = { code: 1, message: '书籍不存在' };
      return;
    }

    // 计算实际进度（基于章节完成情况）
    const completedChapters = book.chapters.filter((c) => c.genStage === 'video_completed').length;
    const totalChapters = book.outline?.chapters?.length || book.totalChapters || 0;

    // 查找当前正在生成的章节
    const currentGenerating = book.chapters.find(
      (c) => c.genStage === 'content_generating' || c.genStage === 'audio_generating' || c.genStage === 'video_generating'
    );
    const currentChapter = currentGenerating
      ? { number: currentGenerating.number, title: currentGenerating.title, level: currentGenerating.level }
      : null;

    // 如果有大纲，使用大纲章节数计算进度
    let progress = book.progress;
    if (totalChapters > 0 && book.genStage !== 'video_completed') {
      progress = Math.round((completedChapters / totalChapters) * 100);
    }

    ctx.body = {
      code: 0,
      message: 'success',
      data: {
        bookId,
        genStage: book.genStage,
        progress,
        completedChapters,
        totalChapters,
        currentChapter,
      },
    };
  } catch (error) {
    console.error('查询进度失败:', error);
    ctx.status = 500;
    ctx.body = { code: 1, message: error instanceof Error ? error.message : '查询失败' };
  }
});

/**
 * DELETE /api/book-generator/langgraph/books/:id
 * 删除书籍
 */
router.delete('/books/:id', async (ctx: Context) => {
  try {
    const bookId = ctx.params.id as string;
    await bookStore.delete(bookId);
    ctx.body = { code: 0, message: '删除成功' };
  } catch (error) {
    console.error('删除失败:', error);
    ctx.status = 500;
    ctx.body = { code: 1, message: error instanceof Error ? error.message : '删除失败' };
  }
});

/**
 * POST /api/book-generator/langgraph/books/:id/generate
 * 对已有书籍使用 LangGraph 生成
 */
router.post('/books/:id/generate', optionalAuth, async (ctx: Context) => {
  try {
    const bookId = ctx.params.id as string;
    console.log(`[LangGraph Generate] 收到请求 bookId=${bookId}`);
    
    const body = (ctx.request.body || {}) as { bookScale?: string; genLevel?: number };
    const book = await bookStore.getById(bookId);
    console.log(`[LangGraph Generate] book对象:`, book ? '存在' : 'null');

    if (!book) {
      ctx.status = 404;
      ctx.body = { code: 1, message: '书籍不存在' };
      return;
    }

    // ========== 确定 bookScale ==========
    // 优先级：
    //   1) 请求显式指定（body.bookScale）→ 用户选的，直接用
    //   2) AI 根据描述自动检测 → LLM 判断
    //   3) 书籍已有规模 → 沿用
    //   4) 兜底 → 1000（最小规模）
    let bookScale: string | undefined;

    if (body.bookScale && BOOK_TYPE_CONFIG[body.bookScale]) {
      // 用户在当前请求中显式选了规模
      bookScale = body.bookScale;
      console.log(`[LangGraph Generate] 使用请求指定的 bookScale=${bookScale}`);
    } else {
      // 未显式选择 → 让 AI 根据描述判断
      const desc = book.description || '';
      try {
        console.log(`[LangGraph Generate] AI 自动检测规模...`);
        bookScale = await autoDetectBookType(book.title || '', desc, book.userId ?? undefined);
        console.log(`[LangGraph Generate] AI 检测结果 → bookScale=${bookScale}`);
      } catch (e) {
        console.warn(`[LangGraph Generate] AI 检测失败，回退:`, e);
      }

      // AI 失败或返回无效值 → 用书籍已保存的
      if (!bookScale || !BOOK_TYPE_CONFIG[bookScale]) {
        bookScale = book.bookScale && BOOK_TYPE_CONFIG[book.bookScale] ? book.bookScale : undefined;
      }
    }

    // 最终兜底：用最小规模（1000字），避免误判生成大量无用内容
    bookScale = bookScale && BOOK_TYPE_CONFIG[bookScale] ? bookScale : '1000';

    // 持久化检测结果
    if (bookScale !== book.bookScale) {
      await bookStore.update(bookId, { bookScale });
    }
    
    const genLevel = resolveGenLevel(bookScale, body.genLevel, book.description);

    // ========== 生成前配额检查 ==========
    const userId = ctx.state.user?.userId ? parseInt(ctx.state.user.userId as string) : (book.userId || parseInt(TEST_USER_ID));
    const scaleConfig = getScaleConfig(bookScale);
    const totalWords = scaleConfig.totalWords;
    
    // 风控预估
    const estimatedCost = AUDIO_BILLING_CONFIG.pricing.monthly * estimateAudioMinutesFromWords(totalWords) + 0.05;
    const quotaOk = await atomicReserveQuota(userId, estimatedCost);
    if (!quotaOk) {
      ctx.status = 403;
      ctx.body = { code: 1, message: '月度额度不足，请升级套餐' };
      return;
    }
    
    const audioQuota = await checkAudioQuota(userId, totalWords);
    if (!audioQuota.allowed) {
      await releaseQuota(userId, estimatedCost);
      ctx.status = 403;
      ctx.body = {
        code: 1,
        message: audioQuota.reason,
        data: {
          estimatedWords: totalWords,
          estimatedAudioMinutes: estimateAudioMinutesFromWords(totalWords),
          remainingMinutes: audioQuota.remainingMinutes,
          totalMinutes: audioQuota.totalMinutes,
        }
      };
      return;
    }

    // 防止重复生成：如果书籍正在生成中，拒绝请求
    if (book.genStage === 'content_generating' || book.genStage === 'audio_generating' || book.genStage === 'video_generating') {
      console.log(`[LangGraph Generate] 书籍正在生成中，拒绝重复请求 bookId=${bookId}`);
      ctx.status = 400;
      ctx.body = {
        code: 1,
        message: '书籍正在生成中，请勿重复提交',
        data: {
          bookId,
          genStage: book.genStage,
          progress: book.progress,
        }
      };
      return;
    }

    // 异步生成（跳过队列，开发环境下队列不可靠）
    await bookStore.update(bookId, { genStage: 'outlining' });

    langGraphGenerator.generate(bookId, book.description, bookScale, genLevel, userId)
      .then(() => {
        console.log(`[LangGraph] 生成完成: bookId=${bookId}`);
      })
      .catch(err => {
        console.error(`[LangGraph] 生成失败: bookId=${bookId}`, err);
        bookStore.update(bookId, {
          genStage: 'failed', failedStage: 'outlining',
          errorMsg: err instanceof Error ? err.message : '生成失败',
        });
      });

    ctx.body = {
      code: 0,
      message: 'LangGraph 生成任务已启动',
      data: { bookId, genStage: 'outlining', mode: 'async' },
    };
  } catch (error) {
    console.error('启动失败:', error);
    ctx.status = 500;
    ctx.body = {
      code: 1,
      message: error instanceof Error ? error.message : '启动失败',
    };
  }
});

/**
 * POST /api/book-generator/langgraph/books/:id/audio
 * 批量生成书籍所有小节的音频
 */
router.post('/books/:id/audio', async (ctx: Context) => {
  try {
    const bookId = ctx.params.id as string;
    const { voiceId = 'longyingling_v3' } = ctx.request.body as { voiceId?: string };

    const book = await bookStore.getById(bookId);
    if (!book) {
      ctx.status = 404;
      ctx.body = { code: 1, message: '书籍不存在' };
      return;
    }

    // 获取章节树
    const chapters = await bookStore.getChapterTree(bookId);

    // 动态获取叶节点层级（最大level值）
    const maxLevel = chapters.length > 0
      ? Math.max(...chapters.map((c: any) => c.level || 0))
      : 0;

    if (maxLevel === 0) {
      ctx.body = { code: 1, message: '没有章节' };
      return;
    }

    // 获取叶节点（最大level的所有节点）
    const leafNodes = chapters.filter((c: any) => c.level === maxLevel);

    // 检查内容状态 - 叶节点必须有content且genStage为content_completed
    const leafNodesWithContent = leafNodes.filter((c: any) => c.content && c.genStage === 'content_completed');

    if (leafNodes.length === 0) {
      ctx.body = { code: 1, message: `没有${maxLevel === 3 ? '小节' : maxLevel === 2 ? '节' : '章节'}` };
      return;
    }

    if (leafNodesWithContent.length === 0) {
      ctx.body = {
        code: 1,
        message: `没有内容生成完成的${maxLevel === 3 ? '小节' : maxLevel === 2 ? '节' : '章节'}，请先生成内容`,
        data: {
          totalSubsections: leafNodes.length,
          completedSubsections: 0,
        }
      };
      return;
    }

    console.log(`[Audio] 书籍 ${bookId} 共有 ${leafNodes.length} 个${maxLevel === 3 ? '小节' : maxLevel === 2 ? '节' : '章节'}，其中 ${leafNodesWithContent.length} 个内容已生成完成`);

    // 异步生成所有叶节点音频
    for (const sub of leafNodesWithContent) {
      bookStore.generateChapterAudioById(sub.id, book.userId || 1).catch(err => {
        console.error(`[Audio] ${maxLevel === 3 ? '小节' : '节'}${sub.number}音频生成失败:`, err);
      });
    }

    ctx.body = {
      code: 0,
      message: '音频生成任务已启动',
      data: {
        totalSubsections: leafNodesWithContent.length,
        taskId: `audio_${bookId}_${Date.now()}`,
      },
    };
  } catch (error) {
    console.error('音频生成失败:', error);
    ctx.status = 500;
    ctx.body = { code: 1, message: error instanceof Error ? error.message : '音频生成失败' };
  }
});

/**
 * POST /api/book-generator/langgraph/books/:id/audio/cancel
 * 取消书籍所有章节的音频生成
 */
router.post('/books/:id/audio/cancel', async (ctx: Context) => {
  try {
    const bookId = ctx.params.id as string;

    const book = await bookStore.getById(bookId);
    if (!book) {
      ctx.status = 404;
      ctx.body = { code: 1, message: '书籍不存在' };
      return;
    }

    const result = await cancelAudioGeneration(bookId);

    if (result.cancelledCount === 0) {
      ctx.body = {
        code: 0,
        message: '没有正在运行的音频生成任务',
        data: result,
      };
      return;
    }

    console.log(`[Audio] 书籍 ${bookId} 取消音频生成，已取消 ${result.cancelledCount} 个任务，回退 ${result.rolledBackChapters.length} 个章节`);

    ctx.body = {
      code: 0,
      message: `已取消 ${result.cancelledCount} 个音频生成任务`,
      data: result,
    };
  } catch (error) {
    console.error('取消音频生成失败:', error);
    ctx.status = 500;
    ctx.body = { code: 1, message: error instanceof Error ? error.message : '取消失败' };
  }
});

/**
 * GET /api/book-generator/langgraph/books/:id/video-status
 * 获取书籍视频生成状态（用于前端轮询）
 */
router.get('/books/:id/video-status', async (ctx: Context) => {
  try {
    const bookId = ctx.params.id as string;
    const book = await bookStore.getById(bookId);

    if (!book) {
      ctx.status = 404;
      ctx.body = { code: 1, message: '书籍不存在' };
      return;
    }

    // 获取章节树
    const chapters = await bookStore.getChapterTree(bookId);

    // 动态获取叶节点层级
    const maxLevel = chapters.length > 0
      ? Math.max(...chapters.map((c: any) => c.level || 0))
      : 0;

    // 获取叶节点
    const leafNodes = chapters.filter((c: any) => c.level === maxLevel);

    // 统计视频状态
    const total = leafNodes.length;
    const completed = leafNodes.filter((c: any) => c.videoUrl && c.videoUrl !== '').length;
    const pending = leafNodes.filter((c: any) => !c.videoUrl || c.videoUrl === '').length;

    ctx.body = {
      code: 0,
      message: 'success',
      data: {
        bookId,
        total,
        completed,
        pending,
        allCompleted: completed >= total && total > 0,
      },
    };
  } catch (error) {
    console.error('获取视频状态失败:', error);
    ctx.status = 500;
    ctx.body = { code: 1, message: error instanceof Error ? error.message : '获取失败' };
  }
});

/**
 * GET /api/book-generator/langgraph/books/:id/audio-status
 * 获取书籍音频生成状态（用于前端轮询）
 */
router.get('/books/:id/audio-status', async (ctx: Context) => {
  try {
    const bookId = ctx.params.id as string;
    const book = await bookStore.getById(bookId);

    if (!book) {
      ctx.status = 404;
      ctx.body = { code: 1, message: '书籍不存在' };
      return;
    }

    // 获取章节树
    const chapters = await bookStore.getChapterTree(bookId);

    // 动态获取叶节点层级
    const maxLevel = chapters.length > 0
      ? Math.max(...chapters.map((c: any) => c.level || 0))
      : 0;

    // 获取叶节点
    const leafNodes = chapters.filter((c: any) => c.level === maxLevel);

    // 统计音频状态
    const total = leafNodes.length;
    const completed = leafNodes.filter((c: any) => c.audioUrl && c.audioUrl !== '').length;
    const pending = leafNodes.filter((c: any) => !c.audioUrl || c.audioUrl === '').length;

    ctx.body = {
      code: 0,
      message: 'success',
      data: {
        bookId,
        total,
        completed,
        pending,
        allCompleted: completed >= total && total > 0,
      },
    };
  } catch (error) {
    console.error('获取音频状态失败:', error);
    ctx.status = 500;
    ctx.body = { code: 1, message: error instanceof Error ? error.message : '获取失败' };
  }
});

/**
 * GET /api/book-generator/langgraph/books/:id/failed-chapters
 * 获取生成失败的小节列表
 */
router.get('/books/:id/failed-chapters', async (ctx: Context) => {
  try {
    const bookId = ctx.params.id as string;
    const book = await bookStore.getById(bookId);
    if (!book) {
      ctx.status = 404;
      ctx.body = { code: 1, message: '书籍不存在' };
      return;
    }

    // 获取所有失败的小节
    const chapters = await bookStore.getChapterTree(bookId);
    const failedSubsections = chapters.filter(c => c.genStage === 'failed');

    ctx.body = {
      code: 0,
      message: 'success',
      data: {
        bookId,
        failedCount: failedSubsections.length,
        failedChapters: failedSubsections.map(c => ({
          id: c.id,
          number: c.number,
          title: c.title,
          level: c.level,
          errorMsg: c.errorMsg,
          parentId: c.parentId,
        })),
      },
    };
  } catch (error) {
    console.error('获取失败小节失败:', error);
    ctx.status = 500;
    ctx.body = { code: 1, message: error instanceof Error ? error.message : '获取失败' };
  }
});

/**
 * POST /api/book-generator/langgraph/books/:id/resume
 * 从断点处继续生成（重试失败的小节）
 */
router.post('/books/:id/resume', async (ctx: Context) => {
  try {
    const bookId = ctx.params.id as string;
    const book = await bookStore.getById(bookId);
    if (!book) {
      ctx.status = 404;
      ctx.body = { code: 1, message: '书籍不存在' };
      return;
    }

    if (book.genStage === 'video_completed') {
      ctx.body = { code: 1, message: '书籍已生成完成，无需继续' };
      return;
    }

    // 获取失败的小节
    const chapters = await bookStore.getChapterTree(bookId);
    const failedSubsections = chapters.filter(c => c.genStage === 'failed');

    if (failedSubsections.length === 0) {
      ctx.body = { code: 1, message: '没有失败的小节需要重试' };
      return;
    }

    // 重置失败小节的状态为 pending
    for (const sub of failedSubsections) {
      await bookStore.updateChapterById(sub.id, {
        genStage: 'idle',
        errorMsg: null,
        content: null,
      });
    }

    // 直接重新生成（跳过队列）
    const { langGraphGenerator } = await import('./index.js');
    langGraphGenerator.generate(
      bookId,
      book.description || book.title,
      book.bookScale || '1000'
    ).then(() => {
      console.log(`[LangGraph] 续生成完成: bookId=${bookId}`);
    }).catch(err => {
      console.error(`[LangGraph] 续生成失败: bookId=${bookId}`, err);
    });

    ctx.body = {
      code: 0,
      message: `已重启生成，将重试 ${failedSubsections.length} 个失败的小节`,
      data: {
        bookId,
        retryCount: failedSubsections.length,
        genStage: 'resuming',
      },
    };
  } catch (error) {
    console.error('继续生成失败:', error);
    ctx.status = 500;
    ctx.body = { code: 1, message: error instanceof Error ? error.message : '继续生成失败' };
  }
});

/**
 * POST /api/book-generator/langgraph/books/:id/retry-chapter
 * 单独重试某个失败的小节
 */
router.post('/books/:id/retry-chapter', async (ctx: Context) => {
  try {
    const bookId = ctx.params.id as string;
    const { chapterId } = ctx.request.body as { chapterId: number };

    if (!chapterId) {
      ctx.status = 400;
      ctx.body = { code: 1, message: '请提供章节 ID' };
      return;
    }

    // 获取所有章节查找指定的章节
    const chapters = await bookStore.getChapterTree(bookId);
    const chapter = chapters.find(c => c.id === chapterId);
    if (!chapter) {
      ctx.status = 404;
      ctx.body = { code: 1, message: '章节不存在' };
      return;
    }

    // 重置章节状态
    await bookStore.updateChapterById(chapterId, {
      genStage: 'idle',
      errorMsg: null,
      content: null,
    });

    ctx.body = {
      code: 0,
      message: '章节已重置为待生成状态',
      data: { chapterId },
    };
  } catch (error) {
    console.error('重试章节失败:', error);
    ctx.status = 500;
    ctx.body = { code: 1, message: error instanceof Error ? error.message : '重试失败' };
  }
});

/**
 * GET /api/book-generator/langgraph/books/:id/full-content
 * 获取完整书籍内容（所有章节内容合并）
 */
router.get('/books/:id/full-content', async (ctx: Context) => {
  try {
    const bookId = ctx.params.id as string;
    const book = await bookStore.getById(bookId);

    if (!book) {
      ctx.status = 404;
      ctx.body = { code: 1, message: '书籍不存在' };
      return;
    }

    // 获取章节树
    const chapters = await bookStore.getChapterTree(bookId);

    // 按层级和顺序构建内容
    let fullContent = `# ${book.title}\n\n`;
    if (book.description) {
      fullContent += `## 简介\n${book.description}\n\n`;
    }

    // 递归添加章节内容
    const addChapterContent = (chapter: any, indent: string = '') => {
      if (chapter.content && chapter.genStage === 'content_completed') {
        fullContent += `${indent}${chapter.number}. ${chapter.title}\n\n`;
        fullContent += chapter.content + '\n\n';
      }
      // 处理子章节
      const children = chapters.filter((c: any) => c.parentId === chapter.id);
      children.forEach(child => {
        addChapterContent(child, indent + '  ');
      });
    };

    // 添加一级章节
    const level1Chapters = chapters.filter((c: any) => c.level === 1);
    level1Chapters.forEach(chapter => addChapterContent(chapter));

    ctx.body = {
      code: 0,
      message: 'success',
      data: {
        bookId,
        title: book.title,
        totalChapters: level1Chapters.length,
        content: fullContent,
        wordCount: book.estimatedWords,
      },
    };
  } catch (error) {
    console.error('获取完整内容失败:', error);
    ctx.status = 500;
    ctx.body = { code: 1, message: error instanceof Error ? error.message : '获取失败' };
  }
});

/**
 * POST /api/book-generator/langgraph/books/:id/outline
 * 生成书籍大纲
 */
router.post('/books/:id/outline', async (ctx: Context) => {
  try {
    const bookId = ctx.params.id as string;
    const book = await bookStore.getById(bookId);

    if (!book) {
      ctx.status = 404;
      ctx.body = { code: 1, message: '书籍不存在' };
      return;
    }

    // 检查是否已经有大纲
    if (book.outline && book.outline.chapters && book.outline.chapters.length > 0) {
      ctx.body = {
        code: 0,
        message: '大纲已存在，无需重复生成',
        data: { outline: book.outline },
      };
      return;
    }

    // 获取书籍类型配置
    const scaleConfig = getScaleConfig(book.bookScale || '1000');

    // 调用AI生成大纲
    const { generateOutline } = await import('./index.js');
    const outline = await generateOutline(bookId, book.description, book.bookScale || '1000');

    // 更新书籍的大纲
    await bookStore.update(bookId, { outline });

    ctx.body = {
      code: 0,
      message: '大纲生成成功',
      data: { outline },
    };
  } catch (error) {
    console.error('生成大纲失败:', error);
    ctx.status = 500;
    ctx.body = { code: 1, message: error instanceof Error ? error.message : '生成大纲失败' };
  }
});

/**
 * POST /api/book-generator/langgraph/books/:id/chapters
 * 生成单个章节（或全部章节，取决于参数）
 * body: { chapterNumber?: number } - 如果不传chapterNumber，则生成全部
 */
router.post('/books/:id/chapters', async (ctx: Context) => {
  try {
    const bookId = ctx.params.id as string;
    const body = ctx.request.body as { chapterNumber?: number };
    const book = await bookStore.getById(bookId);

    if (!book) {
      ctx.status = 404;
      ctx.body = { code: 1, message: '书籍不存在' };
      return;
    }

    // 如果指定了章节号，只生成单个章节
    if (body.chapterNumber) {
      // 检查章节是否存在
      const chapters = await bookStore.getChapterTree(bookId);
      const targetChapter = chapters.find((c: any) => c.number === body.chapterNumber);

      if (!targetChapter) {
        ctx.status = 404;
        ctx.body = { code: 1, message: `章节 ${body.chapterNumber} 不存在` };
        return;
      }

      // 异步生成章节内容
      bookStore.generateSingleChapterContent(bookId, targetChapter.id).catch(err => {
        console.error(`[Generate Chapter] 章节${body.chapterNumber}生成失败:`, err);
      });

      ctx.body = {
        code: 0,
        message: `章节 ${body.chapterNumber} 生成任务已启动`,
        data: { chapterId: targetChapter.id, chapterNumber: body.chapterNumber },
      };
    } else {
      // 没有指定章节号，触发整本书生成
      ctx.body = {
        code: 0,
        message: '请使用 /generate 接口生成整本书',
        data: { hint: '使用 POST /books/:id/generate' },
      };
    }
  } catch (error) {
    console.error('生成章节失败:', error);
    ctx.status = 500;
    ctx.body = { code: 1, message: error instanceof Error ? error.message : '生成章节失败' };
  }
});

/**
 * POST /api/book-generator/langgraph/books/:id/foreword
 * 生成前言
 */
router.post('/books/:id/foreword', async (ctx: Context) => {
  try {
    const bookId = ctx.params.id as string;
    const book = await bookStore.getById(bookId);

    if (!book) {
      ctx.status = 404;
      ctx.body = { code: 1, message: '书籍不存在' };
      return;
    }

    // 调用LLM生成前言
    const messages: ChatMessage[] = [
      {
        role: 'system',
        content: `你是一位专业的图书作者。请根据以下信息为书籍生成前言（前言通常介绍写作背景、目标读者、内容概要）。
要求：
1. 字数控制在300-500字
2. 语言正式但亲切
3. 不要使用 markdown 格式，直接输出纯文本

输出格式：
直接输出前言内容，不要包含任何标记。`,
      },
      {
        role: 'user',
        content: `书籍信息：
- 书名：${book.title}
- 描述：${book.description}
- 目标读者：${book.targetAudience || '普通读者'}
- 风格：${book.style || '专业'}

请生成这本书的前言。`,
      },
    ];

    const foreword = await callLLMWithMessages(messages);

    // 更新书籍前言字段
    await bookStore.update(bookId, { foreword });

    ctx.body = {
      code: 0,
      message: '前言生成成功',
      data: { foreword },
    };
  } catch (error) {
    console.error('生成前言失败:', error);
    ctx.status = 500;
    ctx.body = { code: 1, message: error instanceof Error ? error.message : '生成前言失败' };
  }
});

/**
 * POST /api/book-generator/langgraph/books/:id/afterword
 * 生成后记
 */
router.post('/books/:id/afterword', async (ctx: Context) => {
  try {
    const bookId = ctx.params.id as string;
    const book = await bookStore.getById(bookId);

    if (!book) {
      ctx.status = 404;
      ctx.body = { code: 1, message: '书籍不存在' };
      return;
    }

    // 调用LLM生成后记
    const messages: ChatMessage[] = [
      {
        role: 'system',
        content: `你是一位专业的图书作者。请为书籍生成后记（后记通常总结全书核心观点、分享写作心得、感谢读者）。
要求：
1. 字数控制在300-500字
2. 语言真挚、诚恳
3. 不要使用 markdown 格式，直接输出纯文本

输出格式：
直接输出后记内容，不要包含任何标记。`,
      },
      {
        role: 'user',
        content: `书籍信息：
- 书名：${book.title}
- 描述：${book.description}
- 章节数：${book.totalChapters}
- 总字数：约${book.estimatedWords}字

请生成这本书的后记。`,
      },
    ];

    const afterword = await callLLMWithMessages(messages);

    // 更新书籍后记字段
    await bookStore.update(bookId, { afterword });

    ctx.body = {
      code: 0,
      message: '后记生成成功',
      data: { afterword },
    };
  } catch (error) {
    console.error('生成后记失败:', error);
    ctx.status = 500;
    ctx.body = { code: 1, message: error instanceof Error ? error.message : '生成后记失败' };
  }
});

/**
 * GET /api/book-generator/langgraph/workflow/:bookId
 * 获取书籍生成工作流状态
 */
router.get('/workflow/:bookId', async (ctx: Context) => {
  try {
    const bookId = ctx.params.bookId as string;
    const book = await bookStore.getById(bookId);

    if (!book) {
      ctx.status = 404;
      ctx.body = { code: 1, message: '书籍不存在' };
      return;
    }

    // 获取章节树
    const chapters = await bookStore.getChapterTree(bookId);

    // 分析工作流状态
    const completedChapters = chapters.filter((c: any) => ['content_completed', 'audio_completed', 'video_completed'].includes(c.genStage)).length;
    const failedChapters = chapters.filter((c: any) => c.genStage === 'failed').length;
    const pendingChapters = chapters.filter((c: any) => c.genStage === 'idle' || !c.genStage).length;
    const generatingChapters = chapters.filter((c: any) => ['content_generating', 'audio_generating', 'video_generating'].includes(c.genStage)).length;

    // 确定当前阶段
    let phase: 'planning' | 'writing' | 'supplement' | 'done' = 'planning';
    if (['content_generating', 'audio_generating', 'video_generating'].includes(book.genStage)) {
      phase = 'writing';
    } else if (completedChapters > 0 && failedChapters === 0) {
      phase = 'done';
    } else if (failedChapters > 0) {
      phase = 'supplement';
    }

    ctx.body = {
      code: 0,
      message: 'success',
      data: {
        bookId,
        genStage: book.genStage,
        phase,
        progress: book.progress || 0,
        completedChapters,
        failedChapters,
        pendingChapters,
        generatingChapters,
        totalChapters: chapters.length,
        failedChapterIds: chapters.filter((c: any) => c.genStage === 'failed').map((c: any) => c.id),
      },
    };
  } catch (error) {
    console.error('获取工作流状态失败:', error);
    ctx.status = 500;
    ctx.body = { code: 1, message: error instanceof Error ? error.message : '获取失败' };
  }
});

/**
 * GET /api/book-generator/langgraph/books/:id/chapters/:chapterId/content
 * 获取指定章节的内容
 */
router.get('/books/:id/chapters/:chapterId/content', optionalAuth, async (ctx: Context) => {
  try {
    const { id: bookId, chapterId } = ctx.params as { id: string; chapterId: string };
    const chapterIdNum = parseInt(chapterId);

    // 获取书籍
    const book = await bookStore.getById(bookId);
    if (!book) {
      ctx.status = 404;
      ctx.body = { code: 1, message: '书籍不存在' };
      return;
    }

    // 获取章节
    const chapter = await prisma.bookChapter.findUnique({
      where: { id: chapterIdNum },
    });
    if (!chapter) {
      ctx.status = 404;
      ctx.body = { code: 1, message: '章节不存在' };
      return;
    }

    // 检查是否属于该书籍
    if (chapter.bookId !== parseInt(bookId)) {
      ctx.status = 404;
      ctx.body = { code: 1, message: '章节不属于该书籍' };
      return;
    }

    ctx.body = {
      code: 0,
      message: 'success',
      data: {
        id: chapter.id,
        bookId: chapter.bookId,
        number: chapter.number,
        title: chapter.title,
        content: chapter.content || '',
        wordCount: chapter.wordCount || 0,
        genStage: chapter.genStage || null,
        level: chapter.level,
        parentId: chapter.parentId,
        summary: chapter.summary || '',
        keyPoints: chapter.keyPoints ? JSON.parse(chapter.keyPoints) : [],
        audioUrl: chapter.audioUrl || '',
        videoUrl: chapter.videoUrl || '',
        isPublic: chapter.isPublic || false,
      },
    };
  } catch (error) {
    console.error('获取章节内容失败:', error);
    ctx.status = 500;
    ctx.body = { code: 1, message: error instanceof Error ? error.message : '获取章节内容失败' };
  }
});

/**
 * POST /api/book-generator/langgraph/books/:id/chapters/:chapterId/content
 * 保存章节内容（手工编辑后保存）
 */
router.post('/books/:id/chapters/:chapterId/content', optionalAuth, async (ctx: Context) => {
  try {
    const { id: bookId, chapterId } = ctx.params as { id: string; chapterId: string };
    const chapterIdNum = parseInt(chapterId);
    const { content } = ctx.request.body as { content: string };

    if (content === undefined) {
      ctx.status = 400;
      ctx.body = { code: 1, message: '内容不能为空' };
      return;
    }

    // 获取章节
    const chapter = await prisma.bookChapter.findUnique({
      where: { id: chapterIdNum },
    });
    if (!chapter) {
      ctx.status = 404;
      ctx.body = { code: 1, message: '章节不存在' };
      return;
    }

    // 检查是否属于该书籍
    if (chapter.bookId !== parseInt(bookId)) {
      ctx.status = 404;
      ctx.body = { code: 1, message: '章节不属于该书籍' };
      return;
    }

    // 计算字数
    const wordCount = content.replace(/<[^>]*>/g, '').length;

    // 更新章节内容
    await bookStore.updateChapterById(chapterIdNum, {
      content,
      wordCount,
    });

    ctx.body = {
      code: 0,
      message: '保存成功',
      data: { chapterId: chapterIdNum, wordCount },
    };
  } catch (error) {
    console.error('保存章节内容失败:', error);
    ctx.status = 500;
    ctx.body = { code: 1, message: error instanceof Error ? error.message : '保存章节内容失败' };
  }
});

/**
 * POST /api/book-generator/langgraph/books/:id/chapters/batch-regenerate
 * 批量重新生成章节内容
 */
router.post('/books/:id/chapters/batch-regenerate', optionalAuth, async (ctx: Context) => {
  try {
    const { id: bookId } = ctx.params as { id: string };
    const { chapterIds } = ctx.request.body as { chapterIds: number[] };

    if (!chapterIds || !Array.isArray(chapterIds) || chapterIds.length === 0) {
      ctx.status = 400;
      ctx.body = { code: 1, message: '请选择要重新生成的章节' };
      return;
    }

    // 获取书籍
    const book = await bookStore.getById(bookId);
    if (!book) {
      ctx.status = 404;
      ctx.body = { code: 1, message: '书籍不存在' };
      return;
    }

    // 更新书籍状态为 content_generating，让前端能正确追踪轮询
    await bookStore.update(bookId, { genStage: 'content_generating' });

    // 重置并触发每个选中章节的重新生成
    for (const chapterId of chapterIds) {
      await bookStore.updateChapterById(chapterId, {
        genStage: 'idle',
        content: null,
      });
      // 后台异步执行
      bookStore.generateSingleChapterContent(bookId, chapterId).catch(err => {
        console.error(`[BatchRegenerate] 章节${chapterId}内容生成失败:`, err);
      });
    }

    ctx.body = {
      code: 0,
      message: `已提交 ${chapterIds.length} 个章节的重新生成任务`,
      data: { count: chapterIds.length },
    };
  } catch (error) {
    console.error('批量重新生成章节失败:', error);
    ctx.status = 500;
    ctx.body = { code: 1, message: error instanceof Error ? error.message : '批量重新生成失败' };
  }
});

/**
 * POST /api/book-generator/langgraph/books/:id/chapters/batch-delete
 * 批量删除章节
 */
router.post('/books/:id/chapters/batch-delete', optionalAuth, async (ctx: Context) => {
  try {
    const { id: bookId } = ctx.params as { id: string };
    const { chapterIds } = ctx.request.body as { chapterIds: number[] };

    if (!chapterIds || !Array.isArray(chapterIds) || chapterIds.length === 0) {
      ctx.status = 400;
      ctx.body = { code: 1, message: '请选择要删除的章节' };
      return;
    }

    // 获取书籍
    const book = await bookStore.getById(bookId);
    if (!book) {
      ctx.status = 404;
      ctx.body = { code: 1, message: '书籍不存在' };
      return;
    }

    // 删除章节（同时会删除子章节，因为没有设置级联删除，需要手动处理）
    // 对于每个要删除的章节，先删除其所有子节点，再删除自身
    for (const chapterId of chapterIds) {
      // 删除子章节（level > 当前章节）
      await prisma.bookChapter.deleteMany({
        where: {
          bookId: parseInt(bookId),
          parentId: chapterId,
        },
      });
      // 删除自身
      await prisma.bookChapter.deleteMany({
        where: {
          id: chapterId,
          bookId: parseInt(bookId),
        },
      });
    }

    ctx.body = {
      code: 0,
      message: `已删除 ${chapterIds.length} 个章节`,
      data: { count: chapterIds.length },
    };
  } catch (error) {
    console.error('批量删除章节失败:', error);
    ctx.status = 500;
    ctx.body = { code: 1, message: error instanceof Error ? error.message : '批量删除失败' };
  }
});

/**
 * POST /api/book-generator/langgraph/books/:id/chapters/:chapterId/regenerate
 * 重新生成指定章节的内容（仅叶节点允许）
 */
router.post('/books/:id/chapters/:chapterId/regenerate', async (ctx: Context) => {
  try {
    const { id: bookId, chapterId } = ctx.params as { id: string; chapterId: string };
    const chapterIdNum = parseInt(chapterId);

    // 获取书籍
    const book = await bookStore.getById(bookId);
    if (!book) {
      ctx.status = 404;
      ctx.body = { code: 1, message: '书籍不存在' };
      return;
    }

    // 获取章节
    const chapter = await prisma.bookChapter.findUnique({
      where: { id: chapterIdNum },
    });
    if (!chapter) {
      ctx.status = 404;
      ctx.body = { code: 1, message: '章节不存在' };
      return;
    }

    // 检查是否为叶节点（没有子节点的节点）
    const childChapters = await prisma.bookChapter.findMany({
      where: { parentId: chapterIdNum },
    });
    if (childChapters.length > 0) {
      ctx.status = 400;
      ctx.body = { code: 1, message: '只有叶节点（小节）才允许重新生成内容' };
      return;
    }

    // 重置章节状态和内容
    await bookStore.updateChapterById(chapterIdNum, {
      genStage: 'idle',
      errorMsg: null,
      content: null,
    });

    // 直接调用单个章节内容生成（不触发整个书籍生成流程）
    // 在后台异步执行
    bookStore.generateSingleChapterContent(bookId, chapterIdNum).catch(err => {
      console.error(`[Regenerate] 章节${chapterIdNum}内容生成失败:`, err);
    });

    ctx.body = {
      code: 0,
      message: '章节内容重新生成任务已启动',
      data: { chapterId: chapterIdNum },
    };
  } catch (error) {
    console.error('重新生成章节失败:', error);
    ctx.status = 500;
    ctx.body = { code: 1, message: error instanceof Error ? error.message : '重新生成失败' };
  }
});

/**
 * POST /api/book-generator/langgraph/books/:id/chapters/:chapterId/regenerate-audio
 * 重新生成章节音频（仅叶节点）
 */
router.post('/books/:id/chapters/:chapterId/regenerate-audio', async (ctx: Context) => {
  try {
    const { id: bookId, chapterId } = ctx.params as { id: string; chapterId: string };
    const chapterIdNum = parseInt(chapterId);

    // 获取书籍
    const book = await bookStore.getById(bookId);
    if (!book) {
      ctx.status = 404;
      ctx.body = { code: 1, message: '书籍不存在' };
      return;
    }

    // 获取章节
    const chapter = await prisma.bookChapter.findUnique({
      where: { id: chapterIdNum },
    });
    if (!chapter) {
      ctx.status = 404;
      ctx.body = { code: 1, message: '章节不存在' };
      return;
    }

    // 检查是否为叶节点
    const childChapters = await prisma.bookChapter.findMany({
      where: { parentId: chapterIdNum },
    });
    if (childChapters.length > 0) {
      ctx.status = 400;
      ctx.body = { code: 1, message: '只有叶节点（小节）才允许重新生成音频' };
      return;
    }

    // 检查内容是否已完成（有内容即可重生成音频，content_generating 但 content 已存在也允许）
    if (!chapter.content || chapter.genStage === 'idle') {
      ctx.status = 400;
      ctx.body = { code: 1, message: '章节内容未生成完成，无法生成音频' };
      return;
    }

    // 清空现有音频并回退状态
    await bookStore.updateChapterById(chapterIdNum, {
      audioUrl: '',
      audioDuration: 0,
      lrcLyrics: null,
      genStage: 'content_completed',
    });

    // 同时清空父章节的合并音频（因为子节音频变了，合并音频也需要重新生成）
    if (chapter.parentId) {
      const parentChapter = await prisma.bookChapter.findUnique({
        where: { id: chapter.parentId },
      });
      if (parentChapter?.audioUrl?.includes('_merged')) {
        console.log(`[RegenerateAudio] 清除父章节 ${parentChapter.id} 的合并音频（子节${chapterIdNum}已重新生成）`);
        await bookStore.updateChapterById(parentChapter.id, {
          audioUrl: '',
          audioDuration: 0,
        });
        // 如果父章节之上还有更高级的合并音频，也一并清除
        if (parentChapter.parentId) {
          const grandParent = await prisma.bookChapter.findUnique({
            where: { id: parentChapter.parentId },
          });
          if (grandParent?.audioUrl?.includes('_merged')) {
            console.log(`[RegenerateAudio] 清除祖父章节 ${grandParent.id} 的合并音频`);
            await bookStore.updateChapterById(grandParent.id, {
              audioUrl: '',
              audioDuration: 0,
            });
          }
        }
      }
    }

    // 异步生成新音频
    bookStore.generateChapterAudioById(chapterIdNum, book.userId || 1);

    ctx.body = {
      code: 0,
      message: '音频重新生成任务已启动',
      data: { chapterId: chapterIdNum },
    };
  } catch (error) {
    console.error('重新生成音频失败:', error);
    ctx.status = 500;
    ctx.body = { code: 1, message: error instanceof Error ? error.message : '重新生成音频失败' };
  }
});

/**
 * POST /api/book-generator/langgraph/books/:id/chapters/:chapterId/video
 * 生成单个章节视频
 */
router.post('/books/:id/chapters/:chapterId/video', async (ctx: Context) => {
  try {
    const { id: bookId, chapterId } = ctx.params as { id: string; chapterId: string };
    const chapterIdNum = parseInt(chapterId);

    const chapter = await prisma.bookChapter.findUnique({
      where: { id: chapterIdNum },
    });
    if (!chapter) {
      ctx.status = 404;
      ctx.body = { code: 1, message: '章节不存在' };
      return;
    }

    // 检查章节是否有音频
    if (!chapter.audioUrl) {
      ctx.status = 400;
      ctx.body = { code: 1, message: '该章节没有音频，请先生成音频' };
      return;
    }

    // 设置视频状态为生成中
    await advanceChapter(chapterIdNum, 'video_generating');

    // 调用视频服务创建视频项目
    const project = await createVideoProjectFromBook(parseInt(bookId), chapterIdNum);

    if (!project) {
      await regenerateChapter(chapterIdNum, 'failed');
      ctx.status = 400;
      ctx.body = { code: 1, message: '创建视频项目失败' };
      return;
    }

    // 异步生成视频
    generateVideoForProject(project.id)
      .then(async (result) => {
        if (result.success) {
          await bookStore.updateChapterById(chapterIdNum, {
            videoUrl: result.outputUrl,
            videoDuration: result.duration,
          });
          await advanceChapter(chapterIdNum, 'video_completed');
        } else {
          await regenerateChapter(chapterIdNum, 'failed');
        }
      })
      .catch(async (err) => {
        console.error(`[Video] 章节${chapter.number}视频生成失败:`, err);
        await regenerateChapter(chapterIdNum, 'failed');
      });

    ctx.body = {
      code: 0,
      message: '视频生成任务已启动',
      data: {
        projectId: project.id,
        chapterId: String(chapterId),
      },
    };
  } catch (error) {
    console.error('视频生成失败:', error);
    ctx.status = 500;
    ctx.body = { code: 1, message: error instanceof Error ? error.message : '视频生成失败' };
  }
});

/**
 * POST /api/book-generator/langgraph/books/:id/videos
 * 批量生成书籍所有章节视频
 */
router.post('/books/:id/videos', async (ctx: Context) => {
  try {
    const bookId = ctx.params.id as string;
    const book = await bookStore.getById(bookId);

    if (!book) {
      ctx.status = 404;
      ctx.body = { code: 1, message: '书籍不存在' };
      return;
    }

    // 获取章节树
    const chapters = await bookStore.getChapterTree(bookId);

    // 动态获取叶节点层级（最大level值）
    const maxLevel = chapters.length > 0
      ? Math.max(...chapters.map((c: any) => c.level || 0))
      : 0;

    if (maxLevel === 0) {
      ctx.body = { code: 1, message: '没有章节' };
      return;
    }

    // 获取叶节点（最大level的所有节点）
    const leafNodes = chapters.filter((c: any) => c.level === maxLevel);

    // 过滤出有音频的叶节点
    const leafNodesWithAudio = leafNodes.filter((c: any) => c.audioUrl && c.audioUrl !== '');

    const leafTypeName = maxLevel === 3 ? '小节' : maxLevel === 2 ? '节' : '章节';

    if (leafNodes.length === 0) {
      ctx.body = { code: 1, message: `没有${leafTypeName}` };
      return;
    }

    if (leafNodesWithAudio.length === 0) {
      ctx.body = {
        code: 1,
        message: `没有音频生成完成的${leafTypeName}，请先生成音频`,
        data: {
          totalSubsections: leafNodes.length,
          audioCompletedSubsections: 0,
        }
      };
      return;
    }

    console.log(`[Video] 书籍 ${bookId} 共有 ${leafNodes.length} 个${leafTypeName}，其中 ${leafNodesWithAudio.length} 个音频已生成完成`);

    // 异步生成所有叶节点视频
    const taskId = `video_${bookId}_${Date.now()}`;
    console.log(`[Video] 开始批量生成视频: bookId=${bookId}, taskId=${taskId}, 总数=${leafNodesWithAudio.length}`);

    for (const sub of leafNodesWithAudio) {
      // 设置视频状态为生成中
      await advanceChapter(sub.id, 'video_generating').catch(() => {});

      // 异步处理每个章节的视频生成
      (async () => {
        try {
          console.log(`[Video] 开始生成章节 ${sub.number} 的视频: ${sub.title}`);

          // 从书籍章节创建视频项目
          const project = await createVideoProjectFromBook(
            parseInt(bookId),
            sub.id,
            book.userId || 1
          );

          if (!project) {
            console.error(`[Video] 章节 ${sub.number} 视频项目创建失败`);
            await regenerateChapter(sub.id, 'failed').catch(() => {});
            return;
          }

          console.log(`[Video] 章节 ${sub.number} 视频项目创建成功: projectId=${project.id}`);

          // 生成视频
          const result = await generateVideoForProject(project.id);

          if (result.success && result.outputUrl) {
            await bookStore.updateChapterById(sub.id, {
              videoUrl: result.outputUrl,
              videoDuration: result.duration,
            });
            await advanceChapter(sub.id, 'video_completed');
          } else {
            await regenerateChapter(sub.id, 'failed');
          }
        } catch (error) {
          console.error(`[Video] 章节 ${sub.number} 视频生成异常:`, error);
          await regenerateChapter(sub.id, 'failed');
        }
      })();
    }

    ctx.body = {
      code: 0,
      message: '视频生成任务已启动',
      data: {
        taskId,
        totalChapters: leafNodesWithAudio.length,
        totalSubsections: leafNodes.length,
      },
    };
  } catch (error) {
    console.error('批量生成视频失败:', error);
    ctx.status = 500;
    ctx.body = { code: 1, message: error instanceof Error ? error.message : '批量生成视频失败' };
  }
});

/**
 * POST /api/book-generator/langgraph/books/:id/merge-audio
 * 合并叶节点音频到上级章节
 */
router.post('/books/:id/merge-audio', async (ctx: Context) => {
  try {
    const bookId = ctx.params.id as string;
    const book = await bookStore.getById(bookId);

    if (!book) {
      ctx.status = 404;
      ctx.body = { code: 1, message: '书籍不存在' };
      return;
    }

    // 获取章节树
    const chapters = await bookStore.getChapterTree(bookId);

    // 动态获取叶节点层级（最大level值）
    const maxLevel = chapters.length > 0
      ? Math.max(...chapters.map((c: any) => c.level || 0))
      : 0;

    if (maxLevel <= 1) {
      ctx.body = { code: 1, message: '书籍层级不足，无法合并音频' };
      return;
    }

    // 获取叶节点（最大level的所有节点）
    const leafNodes = chapters.filter((c: any) => c.level === maxLevel);

    // 检查所有叶节点是否都有音频
    const leafNodesWithAudio = leafNodes.filter((c: any) => c.audioUrl && c.audioUrl !== '');

    if (leafNodes.length === 0) {
      ctx.body = { code: 1, message: `没有${maxLevel === 3 ? '小节' : maxLevel === 2 ? '节' : '章节'}` };
      return;
    }

    if (leafNodesWithAudio.length !== leafNodes.length) {
      ctx.body = {
        code: 1,
        message: `并非所有${maxLevel === 3 ? '小节' : maxLevel === 2 ? '节' : '章节'}都已完成音频生成，无法合并`,
        data: {
          totalLeafNodes: leafNodes.length,
          completedAudioNodes: leafNodesWithAudio.length,
        }
      };
      return;
    }

    // 构建 sectionId → chapterId 映射（3层树需要，2层树直接 parentId 就是 chapterId）
    const sectionToChapter = new Map<number, number>();
    if (maxLevel >= 3) {
      const sections = chapters.filter((c: any) => c.level === 2);
      for (const sec of sections) {
        if (sec.parentId) {
          sectionToChapter.set(sec.id, sec.parentId);
        }
      }
    }

    // 按章（level=1）分组叶节点
    const groupedByChapter: { [key: number]: any[] } = {};
    const chapterMeta: { [key: number]: any } = {}; // 章的基本信息
    leafNodesWithAudio.forEach(node => {
      // 找到该叶节点所属的1级章ID
      let chapterId: number | null = null;
      if (maxLevel === 2) {
        chapterId = node.parentId; // level=2叶节点的parentId就是章
      } else if (maxLevel === 3 && node.parentId) {
        chapterId = sectionToChapter.get(node.parentId) || null; // 通过节→章映射
      }

      if (chapterId) {
        if (!groupedByChapter[chapterId]) {
          groupedByChapter[chapterId] = [];
        }
        groupedByChapter[chapterId].push(node);
      }
    });

    // 收集各章的元信息
    for (const ch of chapters) {
      if (ch.level === 1) {
        chapterMeta[ch.id] = ch;
      }
    }

    const chapterIds = Object.keys(groupedByChapter);

    // 对每个章下的叶节点音频进行合并
    for (const chapterId of chapterIds) {
      const chId = parseInt(chapterId);
      const childNodes = groupedByChapter[chId];
      const chInfo = chapterMeta[chId];

      if (childNodes.length > 0) {
        try {
          const mergedAudioUrl = await mergeChapterAudios(chId);

          if (mergedAudioUrl) {
            console.log(`[Merge Audio] 章 ${chInfo?.number || chId}(${chInfo?.title || ''}) 音频合并完成: ${mergedAudioUrl}`);
          }
        } catch (error) {
          console.error(`[Merge Audio] 章 ${chInfo?.number || chId}(${chInfo?.title || ''}) 音频合并失败:`, error);
        }
      }
    }

    ctx.body = {
      code: 0,
      message: `音频合并任务已启动，共处理 ${chapterIds.length} 个章`,
      data: {
        processedParents: chapterIds.length,
        totalLeafNodes: leafNodes.length,
      },
    };
  } catch (error) {
    console.error('合并音频失败:', error);
    ctx.status = 500;
    ctx.body = { code: 1, message: error instanceof Error ? error.message : '合并音频失败' };
  }
});

/**
 * POST /api/book-generator/langgraph/books/:id/merge-video
 * 合并叶节点视频到上级章节
 */
router.post('/books/:id/merge-video', async (ctx: Context) => {
  try {
    const bookId = ctx.params.id as string;
    const book = await bookStore.getById(bookId);

    if (!book) {
      ctx.status = 404;
      ctx.body = { code: 1, message: '书籍不存在' };
      return;
    }

    // 获取章节树
    const chapters = await bookStore.getChapterTree(bookId);

    // 动态获取叶节点层级（最大level值）
    const maxLevel = chapters.length > 0
      ? Math.max(...chapters.map((c: any) => c.level || 0))
      : 0;

    if (maxLevel <= 1) {
      ctx.body = { code: 1, message: '书籍层级不足，无法合并视频' };
      return;
    }

    // 获取叶节点（最大level的所有节点）
    const leafNodes = chapters.filter((c: any) => c.level === maxLevel);

    // 检查所有叶节点是否都有视频
    const leafNodesWithVideo = leafNodes.filter((c: any) => c.videoUrl && c.videoUrl !== '');

    if (leafNodes.length === 0) {
      ctx.body = { code: 1, message: `没有${maxLevel === 3 ? '小节' : maxLevel === 2 ? '节' : '章节'}` };
      return;
    }

    if (leafNodesWithVideo.length !== leafNodes.length) {
      ctx.body = {
        code: 1,
        message: `并非所有${maxLevel === 3 ? '小节' : maxLevel === 2 ? '节' : '章节'}都已完成视频生成，无法合并`,
        data: {
          totalLeafNodes: leafNodes.length,
          completedVideoNodes: leafNodesWithVideo.length,
        }
      };
      return;
    }

    // 按父节点分组叶节点（通常是1级章节）
    const groupedByParent: { [key: number]: any[] } = {};
    leafNodesWithVideo.forEach(node => {
      if (node.parentId) {
        if (!groupedByParent[node.parentId]) {
          groupedByParent[node.parentId] = [];
        }
        groupedByParent[node.parentId].push(node);
      }
    });

    // 对每个父节点下的叶节点视频进行合并
    let totalProcessedParents = 0;
    for (const parentId in groupedByParent) {
      const childNodes = groupedByParent[parentId];
      if (childNodes.length > 0) {
        // 获取父章节信息
        const parentChapter = chapters.find((c: any) => c.id === parseInt(parentId));

        if (parentChapter) {
          try {
            // 调用合并函数
            const mergedVideoUrl = await mergeChapterVideos(
              parentChapter,
              childNodes,
              book.userId || 1
            );

            if (mergedVideoUrl) {
              // 更新父章节的视频URL
              await bookStore.updateChapterById(parentChapter.id, {
                videoUrl: mergedVideoUrl,
              });

              console.log(`[Merge Video] 章节 ${parentChapter.number}(${parentChapter.title}) 视频合并完成: ${mergedVideoUrl}`);
              totalProcessedParents++;
            }
          } catch (error) {
            console.error(`[Merge Video] 章节 ${parentChapter.number}(${parentChapter.title}) 视频合并失败:`, error);
            // 不抛出异常，继续处理其他章节
          }
        }
      }
    }

    ctx.body = {
      code: 0,
      message: `视频合并任务已完成，共处理 ${totalProcessedParents} 个上级章节`,
      data: {
        processedParents: totalProcessedParents,
        totalLeafNodes: leafNodes.length,
      },
    };
  } catch (error) {
    console.error('合并视频失败:', error);
    ctx.status = 500;
    ctx.body = { code: 1, message: error instanceof Error ? error.message : '合并视频失败' };
  }
});

/**
 * 合并章节视频的辅助函数
 */
async function mergeChapterVideos(parentChapter: any, childChapters: any[], userId: number) {
  try {
    // 获取项目根目录（假设视频文件存储在特定目录下）
    const videoDir = path.join(__dirname, '../../../../../../uploads/video');
    const outputDir = path.join(videoDir, 'merged');

    // 确保输出目录存在
    await fs.mkdir(outputDir, { recursive: true });

    // 创建合并文件列表
    const fileListPath = path.join(videoDir, `merge_list_${parentChapter.id}.txt`);
    const outputPath = path.join(outputDir, `merged_chapter_${parentChapter.id}_${Date.now()}.mp4`);

    // 准备要合并的视频文件路径
    const videoPaths = [];
    for (const child of childChapters) {
      if (child.videoUrl) {
        // 从URL提取文件名
        const fileName = path.basename(child.videoUrl);
        const fullPath = path.join(videoDir, fileName);

        // 检查文件是否存在
        try {
          await fs.access(fullPath);
          videoPaths.push(fullPath);
        } catch (err) {
          console.error(`[Merge Video] 文件不存在: ${fullPath}`, err);
          continue; // 跳过不存在的文件
        }
      }
    }

    if (videoPaths.length === 0) {
      throw new Error('没有找到有效的视频文件');
    }

    if (videoPaths.length === 1) {
      // 如果只有一个视频，检查格式是否一致
      const srcExt = videoPaths[0].split('.').pop()?.toLowerCase();
      const dstExt = outputPath.split('.').pop()?.toLowerCase();
      if (srcExt !== dstExt) {
        // 格式不同，用 FFmpeg 转码
        const cmd = `ffmpeg -i "${videoPaths[0]}" -c copy -y "${outputPath}"`;
        console.log(`[Merge Video] 格式不同，转码: ${cmd}`);
        await execPromise(cmd);
      } else {
        await fs.copyFile(videoPaths[0], outputPath);
      }
      const relativePath = outputPath.replace(videoDir, '/uploads/video').replace(/\\/g, '/');
      return relativePath;
    }

    // 创建FFmpeg输入列表 - 使用concat协议来合并视频
    const inputLines = videoPaths.map(videoPath => `file '${videoPath}'`).join('\n');
    await fs.writeFile(fileListPath, inputLines);

    // 使用FFmpeg合并视频 - 更安全的方式，避免编码问题
    // 先尝试简单的copy方式
    const cmd = `ffmpeg -f concat -safe 0 -i "${fileListPath}" -c copy -avoid_negative_ts make_zero "${outputPath}"`;
    console.log(`[Merge Video] 执行命令: ${cmd}`);

    let result;
    try {
      result = await execPromise(cmd);
      console.log(`[Merge Video] FFmpeg stdout:`, result.stdout);
      console.log(`[Merge Video] FFmpeg stderr:`, result.stderr);
    } catch (execErr: any) {
      console.warn(`[Merge Video] copy模式失败，尝试重新编码:`, execErr.message);
      // 如果简单copy失败，尝试重新编码
      const reencodeCmd = `ffmpeg -f concat -safe 0 -i "${fileListPath}" -c:v libx264 -c:a aac -avoid_negative_ts make_zero "${outputPath}"`;
      console.log(`[Merge Video] 执行重新编码命令: ${reencodeCmd}`);
      result = await execPromise(reencodeCmd);
      console.log(`[Merge Video] FFmpeg reencode stdout:`, result.stdout);
      console.log(`[Merge Video] FFmpeg reencode stderr:`, result.stderr);
    }

    // 清理临时文件
    await fs.unlink(fileListPath).catch(() => {});

    // 检查输出文件是否成功创建
    try {
      await fs.access(outputPath);
    } catch (err) {
      throw new Error('视频合并后文件未生成');
    }

    // 返回相对路径（相对于上传目录）
    const relativePath = outputPath.replace(videoDir, '/uploads/video').replace(/\\/g, '/');
    return relativePath;

  } catch (error) {
    console.error('[Merge Video] 合并过程中出错:', error);
    throw error;
  }
}

/**
 * DEBUG: 手动触发章节音频生成（强制从content_completed重新开始）
 * POST /api/book-generator/debug/trigger-audio
 */
router.post('/debug/trigger-audio', async (ctx: Context) => {
  try {
    const { chapterId } = ctx.request.body as { chapterId?: number };
    if (!chapterId) {
      ctx.status = 400;
      ctx.body = { code: 1, message: '缺少 chapterId' };
      return;
    }

    // 先检查chapter状态，如果是audio_generating但没有audioUrl，说明之前的生成卡住了
    const chapter = await prisma.bookChapter.findUnique({ where: { id: chapterId } });
    if (chapter?.genStage === 'audio_generating' && !chapter?.audioUrl) {
      console.log(`[DEBUG] 章节${chapterId}卡在audio_generating无audioUrl，重置到content_completed重新生成`);
      await prisma.bookChapter.update({ where: { id: chapterId }, data: { genStage: 'content_completed' } });
    }

    console.log(`[DEBUG] 手动触发章节${chapterId}音频生成`);
    await bookStore.generateChapterAudioById(chapterId);

    const updated = await prisma.bookChapter.findUnique({ where: { id: chapterId } });
    ctx.body = {
      code: 0,
      message: '音频生成已触发',
      data: { chapterId, genStage: updated?.genStage, audioUrl: updated?.audioUrl },
    };
  } catch (error) {
    console.error('[DEBUG] 触发音频失败:', error);
    ctx.status = 500;
    ctx.body = { code: 1, message: error instanceof Error ? error.message : '触发失败' };
  }
});


// ============ 交互式生成 API ============

/**
 * POST /api/book-generator/langgraph/books/:id/interactive/plan
 * 异步启动深度规划，前端通过 GET 同名接口轮询结果
 */
router.post('/books/:id/interactive/plan', optionalAuth, async (ctx: Context) => {
  try {
    const bookId = ctx.params.id;
    const idNum = parseInt(bookId);
    const book = await prisma.book.findUnique({ where: { id: idNum } });
    if (!book) {
      ctx.status = 404;
      ctx.body = { code: 1, message: '书籍不存在' };
      return;
    }

    // 标记为生成中，立即返回
    await prisma.book.update({
      where: { id: idNum },
      data: { genStage: 'plan_generating' },
    });

    // 构造 minimal state
    const body = (ctx.request as any).body || {};
    const userFeedback = body.feedback || '';
    const initState: any = {
      bookId,
      topic: book.description,
      bookScale: book.bookScale || '1000',
      genLevel: (book as any).genLevel || 2,
      description: book.description,
      title: book.title,
      userId: book.userId,
      completedChapters: [],
      failedChapters: [],
      userFeedback,
    };

    console.log(`[Interactive] 深度规划已启动(异步): bookId=${bookId}${userFeedback ? ', 含用户反馈' : ''}`);

    // 异步执行 AI 调用，不阻塞响应
    setImmediate(async () => {
      try {
        const result = await deepPlanBookNode(initState);
        const bookPlan = result.bookPlan || '';
        await prisma.book.update({
          where: { id: idNum },
          data: { bookAnalysis: bookPlan, genStage: 'plan_ready' },
        });
        console.log(`[Interactive] 深度规划完成: bookId=${bookId}`);
      } catch (err) {
        console.error(`[Interactive] 深度规划失败: bookId=${bookId}`, err);
        await prisma.book.update({
          where: { id: idNum },
          data: { genStage: 'plan_failed', errorMsg: err instanceof Error ? err.message : '规划失败' },
        });
      }
    });

    ctx.body = {
      code: 0,
      message: '规划已启动',
      data: { genStage: 'plan_generating' },
    };
  } catch (error) {
    console.error('[Interactive] plan 启动失败:', error);
    ctx.status = 500;
    ctx.body = { code: 1, message: error instanceof Error ? error.message : '启动失败' };
  }
});

/**
 * GET /api/book-generator/langgraph/books/:id/interactive/plan
 * 轮询获取深度规划结果
 */
router.get('/books/:id/interactive/plan', optionalAuth, async (ctx: Context) => {
  try {
    const bookId = ctx.params.id;
    const book = await prisma.book.findUnique({ where: { id: parseInt(bookId) } });
    if (!book) {
      ctx.status = 404;
      ctx.body = { code: 1, message: '书籍不存在' };
      return;
    }

    const stage = (book as any).genStage || '';

    if (stage === 'plan_failed') {
      ctx.body = { code: 0, data: { genStage: 'plan_failed', error: (book as any).errorMsg || '规划失败' } };
      return;
    }

    if (stage === 'plan_ready' || book.bookAnalysis) {
      const bookPlan = book.bookAnalysis || '';
      let planData: any = null;
      try {
        const fixed_json = bookPlan.replace(/\\u000a/g, '\\n').replace(/\\\\/g, '\\');
        const match = fixed_json.match(/\{[\s\S]*\}/);
        if (match) planData = JSON.parse(match[0]);
      } catch (e) { /* ignore */ }

      ctx.body = {
        code: 0,
        message: '规划完成',
        data: { rawPlan: bookPlan, planData, genStage: 'plan_ready' },
      };
      return;
    }

    // 还在生成中
    ctx.body = {
      code: 0,
      data: { genStage: stage || 'plan_generating' },
    };
  } catch (error) {
    console.error('[Interactive] 查询 plan 失败:', error);
    ctx.status = 500;
    ctx.body = { code: 1, message: '查询失败' };
  }
});

/**
 * PUT /api/book-generator/langgraph/books/:id/interactive/plan
 * 保存用户修改后的 plan
 */
router.put('/books/:id/interactive/plan', optionalAuth, async (ctx: Context) => {
  try {
    const bookId = ctx.params.id;
    const { plan } = ctx.request.body as { plan: string };

    if (!plan) {
      ctx.status = 400;
      ctx.body = { code: 1, message: 'plan 不能为空' };
      return;
    }

    await prisma.book.update({
      where: { id: parseInt(bookId) },
      data: { bookAnalysis: plan },
    });

    ctx.body = { code: 0, message: 'Plan 已保存' };
  } catch (error) {
    console.error('[Interactive] 保存 plan 失败:', error);
    ctx.status = 500;
    ctx.body = { code: 1, message: error instanceof Error ? error.message : '保存失败' };
  }
});

/**
 * POST /api/book-generator/langgraph/books/:id/interactive/outline
 * 异步启动大纲生成，前端通过 GET 同名接口轮询结果
 */
router.post('/books/:id/interactive/outline', optionalAuth, async (ctx: Context) => {
  try {
    const bookId = ctx.params.id;
    const idNum = parseInt(bookId);
    const book = await prisma.book.findUnique({ where: { id: idNum } });
    if (!book) {
      ctx.status = 404;
      ctx.body = { code: 1, message: '书籍不存在' };
      return;
    }

    // 标记为生成中，立即返回
    await prisma.book.update({
      where: { id: idNum },
      data: { genStage: 'outline_generating' },
    });

    // 构造 state，使用已保存的 plan
    const initState: any = {
      bookId,
      topic: book.description,
      bookScale: book.bookScale || '1000',
      genLevel: (book as any).genLevel || 2,
      description: book.description,
      title: book.title,
      userId: book.userId,
      bookPlan: book.bookAnalysis || '',
      completedChapters: [],
      failedChapters: [],
    };

    console.log(`[Interactive] 大纲生成已启动(异步): bookId=${bookId}`);

    // 异步执行 AI 调用
    setImmediate(async () => {
      try {
        await richOutlineNode(initState);
        // richOutlineNode 内部已通过 bookStore.update 写入 outlineJson
        await prisma.book.update({
          where: { id: idNum },
          data: { genStage: 'outline_ready' },
        });
        console.log(`[Interactive] 大纲生成完成: bookId=${bookId}`);
      } catch (err) {
        console.error(`[Interactive] 大纲生成失败: bookId=${bookId}`, err);
        await prisma.book.update({
          where: { id: idNum },
          data: { genStage: 'outline_failed', errorMsg: err instanceof Error ? err.message : '大纲生成失败' },
        });
      }
    });

    ctx.body = {
      code: 0,
      message: '大纲生成已启动',
      data: { genStage: 'outline_generating' },
    };
  } catch (error) {
    console.error('[Interactive] outline 启动失败:', error);
    ctx.status = 500;
    ctx.body = { code: 1, message: error instanceof Error ? error.message : '启动失败' };
  }
});

/**
 * GET /api/book-generator/langgraph/books/:id/interactive/outline
 * 轮询获取大纲生成结果
 */
router.get('/books/:id/interactive/outline', optionalAuth, async (ctx: Context) => {
  try {
    const bookId = ctx.params.id;
    const book = await prisma.book.findUnique({ where: { id: parseInt(bookId) } });
    if (!book) {
      ctx.status = 404;
      ctx.body = { code: 1, message: '书籍不存在' };
      return;
    }

    const stage = (book as any).genStage || '';

    if (stage === 'outline_failed') {
      ctx.body = { code: 0, data: { genStage: 'outline_failed', error: (book as any).errorMsg || '大纲生成失败' } };
      return;
    }

    if (stage === 'outline_ready' || book.outlineJson) {
      const outlineJson = book.outlineJson || '';
      let outlineData: any = null;
      try {
        outlineData = typeof outlineJson === 'string' ? JSON.parse(outlineJson) : outlineJson;
      } catch (e) { /* ignore */ }

      ctx.body = {
        code: 0,
        message: '大纲生成完成',
        data: { rawOutline: outlineJson, outlineData, genStage: 'outline_ready' },
      };
      return;
    }

    // 还在生成中
    ctx.body = {
      code: 0,
      data: { genStage: stage || 'outline_generating' },
    };
  } catch (error) {
    console.error('[Interactive] 查询 outline 失败:', error);
    ctx.status = 500;
    ctx.body = { code: 1, message: '查询失败' };
  }
});

/**
 * PUT /api/book-generator/langgraph/books/:id/interactive/outline
 * 保存用户修改后的大纲，并重建章节树
 */
router.put('/books/:id/interactive/outline', optionalAuth, async (ctx: Context) => {
  try {
    const bookId = ctx.params.id;
    const { outline } = ctx.request.body as { outline: any };

    if (!outline) {
      ctx.status = 400;
      ctx.body = { code: 1, message: 'outline 不能为空' };
      return;
    }

    const outlineStr = typeof outline === 'string' ? outline : JSON.stringify(outline);

    await prisma.book.update({
      where: { id: parseInt(bookId) },
      data: { outlineJson: outlineStr },
    });

    // 根据大纲重建章节树（删除旧章节，创建新章节）
    const chapterData = typeof outline === 'string' ? JSON.parse(outline) : outline;
    const chapters = chapterData.chapters || [];
    
    // 删除旧章节
    await prisma.bookChapter.deleteMany({ where: { bookId: parseInt(bookId) } });

    // 插入新章节（递归处理章→节→小节）
    let chapterNumber = 0;
    async function insertChapters(parentId: number | null, items: any[], level: number) {
      for (const item of items) {
        chapterNumber++;
        const section = await prisma.bookChapter.create({
          data: {
            bookId: parseInt(bookId),
            title: item.title || '未命名章节',
            summary: item.summary || '',
            content: '',
            wordCount: item.estimatedWords || 0,
            number: chapterNumber,
            level,
            parentId,
            genStage: 'draft',
          },
        });
        // 递归处理子节点（节、小节）
        if (item.sections && item.sections.length > 0) {
          await insertChapters(section.id, item.sections, level + 1);
        }
        if (item.subsections && item.subsections.length > 0) {
          await insertChapters(section.id, item.subsections, level + 1);
        }
      }
    }
    await insertChapters(null, chapters, 1);

    // 更新书籍的章节计数
    await prisma.book.update({
      where: { id: parseInt(bookId) },
      data: { totalChapters: chapterNumber },
    });

    ctx.body = {
      code: 0,
      message: '大纲已保存，章节树已重建',
      data: { chapterCount: chapterNumber },
    };
  } catch (error) {
    console.error('[Interactive] 保存 outline 失败:', error);
    ctx.status = 500;
    ctx.body = { code: 1, message: error instanceof Error ? error.message : '保存失败' };
  }
});

/**
 * POST /api/book-generator/langgraph/books/:id/interactive/generate
 * 运行并行内容生成 + 连贯性编辑
 */
router.post('/books/:id/interactive/generate', optionalAuth, async (ctx: Context) => {
  try {
    const bookId = ctx.params.id;
    const book = await prisma.book.findUnique({ where: { id: parseInt(bookId) } });
    if (!book) {
      ctx.status = 404;
      ctx.body = { code: 1, message: '书籍不存在' };
      return;
    }

    const userId = book.userId || parseInt(TEST_USER_ID);

    // 配额检查
    const scaleConfig = getScaleConfig(book.bookScale || '1000');
    const totalWords = scaleConfig.totalWords;
    const audioQuota = await checkAudioQuota(userId, totalWords);
    if (!audioQuota.allowed) {
      ctx.status = 403;
      ctx.body = { code: 1, message: audioQuota.reason };
      return;
    }

    // 构造 minimal state
    const initState: any = {
      bookId,
      topic: book.description,
      bookScale: book.bookScale || '1000',
      genLevel: (book as any).genLevel || 2,
      description: book.description,
      title: book.title,
      userId,
      bookPlan: book.bookAnalysis || '',
      outline: book.outlineJson || '',
      completedChapters: [],
      failedChapters: [],
    };

    console.log(`[Interactive] 开始并行生成内容: bookId=${bookId}`);

    // ===== 直接异步生成（跳过队列） =====
    const { langGraphGenerator } = await import('./index.js');
    langGraphGenerator.generate(
      bookId,
      book.description,
      book.bookScale || '1000',
      (book as any).genLevel || 2
    ).then(() => {
      console.log(`[Interactive] 生成完成: bookId=${bookId}`);
    }).catch(err => {
      console.error(`[Interactive] 生成失败: bookId=${bookId}`, err);
    });

    await prisma.book.update({
      where: { id: parseInt(bookId) },
      data: { genStage: 'generating_content' },
    });

    ctx.body = {
      code: 0,
      message: '内容生成已启动',
      data: { bookId, genStage: 'generating_content', mode: 'direct' },
    };
    return;
    try {
      await prisma.book.update({
        where: { id: parseInt(bookId) },
        data: { genStage: 'generating_content' },
      });

      // Step 1: 并行生成内容
      const contentResult = await writeChaptersParallelNode(initState);
      console.log(`[Interactive] 并行内容生成完成: bookId=${bookId}`);

      // Step 2: 连贯性编辑
      const mergeState = { ...initState, ...contentResult };
      await continuityEditNode(mergeState);
      console.log(`[Interactive] 连贯性编辑完成: bookId=${bookId}`);

      ctx.body = {
        code: 0,
        message: '内容生成完成',
        data: { bookId, genStage: 'completed', mode: 'sync' },
      };
    } catch (generateError) {
      console.error(`[Interactive] 内容生成失败: bookId=${bookId}`, generateError);
      await prisma.book.update({
        where: { id: parseInt(bookId) },
        data: { genStage: 'failed', errorMsg: generateError instanceof Error ? generateError.message : '生成失败' },
      });
      ctx.status = 500;
      ctx.body = { code: 1, message: generateError instanceof Error ? generateError.message : '生成失败' };
    }
  } catch (error) {
    console.error('[Interactive] generate 失败:', error);
    ctx.status = 500;
    ctx.body = { code: 1, message: error instanceof Error ? error.message : '生成失败' };
  }
});

export default router;
