/**
 * Book Content Generator Routes
 *
 * Generate complete book content with AI, including chapters, sections, and images.
 * Similar pattern to Landing Page Generator - async job-based generation with
 * MongoDB persistence for generated content.
 *
 * POST /generate — start async book content generation
 * GET /status/:jobId — poll job status
 * GET /generated-content/:bookId — get persisted generation status & metadata
 * GET /preview/:bookId — preview generated HTML content
 * GET /download/:bookId — download generated book (HTML or DOCX)
 * DELETE /generated-content/:bookId — clear generation data for retry
 */

import express, { Request, Response } from 'express';
import { authenticate } from '../middleware/auth';
import { requirePermission } from '../middleware/permissions';
import { getModels } from '../models';
import { createJob, updateJobProgress, completeJob, failJob, getJob } from '../services/aiContext/aiJobManager';
import { generateWithAI } from '../utils/aiProvider';
import fs from 'fs';
import path from 'path';
import PDFDocument from 'pdfkit';
import { Document, Packer, Paragraph, TextRun, HeadingLevel, AlignmentType, PageBreak, BorderStyle } from 'docx';
import { parseGeneratedBookHtml, renderBookPdf, renderBookDocx } from '../services/books/bookTypesetting';

const router = express.Router();

// ============================================
// TYPES
// ============================================

interface GenerationConfig {
  bookType: string;
  writingStyle: string;
  toneOfVoice: string;
  contentDepth: string;
  imagesEnabled: boolean;
  imageStyle: string;
  imageAspect: string;
  outputFormat: string;
  visualIdentity?: {
    mode: string;
    primaryColor?: string;
    secondaryColor?: string;
    accentColor?: string;
    headingFont?: string;
    bodyFont?: string;
    visualVibe?: string;
  };
  formatting?: {
    chapterLayout: string;
    lessonLayout: string;
    headingsHierarchy: boolean;
    tables: boolean;
    calloutBoxes: boolean;
    codeBlocks: boolean;
    quotes: boolean;
    tipsNotes: boolean;
    exercises: boolean;
    quizzes: boolean;
    summaries: boolean;
  };
  aiInstructions?: {
    systemInstructions: string;
    writingConstraints: string;
    dosDonts: string;
    additionalGuidance: string;
    outputFormat: string;
  };
}

interface ChapterInput {
  id?: string;
  _id?: string;
  title: string;
  description?: string;
  content?: string;
  learningObjectives?: string[];
  keyTakeaways?: string[];
  status?: string;
  order: number;
}

interface SectionInput {
  id?: string;
  _id?: string;
  title: string;
  type?: string;
  content?: string;
  keyPoints?: string[];
  examples?: string[];
  quotes?: string[];
  status?: string;
  order: number;
}

// ============================================
// LAYOUT & FORMATTING DEFAULTS
// ============================================

/**
 * The layout the wizard starts from. Kept in step with the AI Prompt step's
 * DEFAULT_CONFIG so a generation that never saw that step still produces a
 * properly structured book instead of an unformatted one.
 */
const DEFAULT_FORMATTING = {
  chapterLayout: 'traditional',
  lessonLayout: 'standard',
  headingsHierarchy: true,
  tables: true,
  calloutBoxes: true,
  codeBlocks: true,
  quotes: true,
  tipsNotes: true,
  exercises: true,
  quizzes: true,
  summaries: true,
};

/**
 * Resolve the config to generate with: what the request sent, then what the book
 * was last generated with, then the defaults — merged per-section so a request
 * that carries only the basics does not drop the stored layout.
 */
function withLayoutDefaults(requestConfig: any, storedConfig: any): GenerationConfig {
  const req = requestConfig || {};
  const stored = storedConfig || {};

  return {
    ...stored,
    ...req,
    formatting: {
      ...DEFAULT_FORMATTING,
      ...(stored.formatting || {}),
      ...(req.formatting || {}),
    },
    visualIdentity: {
      ...(stored.visualIdentity || {}),
      ...(req.visualIdentity || {}),
    },
    aiInstructions: {
      ...(stored.aiInstructions || {}),
      ...(req.aiInstructions || {}),
    },
  } as GenerationConfig;
}

/**
 * Turn the formatting switches into instructions the model can act on.
 *
 * Without this the wizard's layout choices were collected, sent, typed — and
 * then never reached a prompt, so every book came back in the model's own
 * default shape no matter what was selected.
 */
function buildFormattingInstructions(config: GenerationConfig): string {
  const f = { ...DEFAULT_FORMATTING, ...(config.formatting || {}) };

  const chapterLayouts: Record<string, string> = {
    traditional: 'Traditional — chapter title, introduction, body sections, then a conclusion.',
    modern: 'Modern — short hook, scannable subheadings, generous use of lists and pull-quotes.',
    academic: 'Academic — numbered sections, formal register, citations and a summary.',
    workbook: 'Workbook — instruction followed by exercises the reader completes.',
    narrative: 'Narrative — story-driven flow with minimal subheadings.',
  };
  const lessonLayouts: Record<string, string> = {
    standard: 'Standard — heading, explanation, example, takeaway.',
    'objective-first': 'Objective-first — state the objective, teach it, then check understanding.',
    'problem-solution': 'Problem-solution — pose the problem, work to the solution.',
    'step-by-step': 'Step-by-step — numbered steps the reader follows in order.',
    'case-study': 'Case study — context, actions taken, results, lessons.',
  };

  const enabled: string[] = [];
  const disabled: string[] = [];
  const push = (on: boolean, text: string) => (on ? enabled : disabled).push(text);

  push(f.headingsHierarchy, 'a strict heading hierarchy (<h2> for sections, <h3> for sub-sections, <h4> for minor points)');
  push(f.tables, 'HTML <table> elements for comparisons and structured data');
  push(f.calloutBoxes, '<div class="callout info"> boxes for important notes');
  push(f.codeBlocks, '<pre><code> blocks for code and commands');
  push(f.quotes, '<blockquote> for quotations');
  push(f.tipsNotes, '<div class="callout tip"> for tips and <div class="callout warning"> for warnings');
  push(f.exercises, 'practical exercises for the reader');
  push(f.quizzes, 'short knowledge-check questions');
  push(f.summaries, 'a summary at the end of each chapter');

  return `## LAYOUT & FORMATTING (follow exactly)

**Chapter layout:** ${chapterLayouts[f.chapterLayout] || f.chapterLayout}
**Section layout:** ${lessonLayouts[f.lessonLayout] || f.lessonLayout}

**Use:**
${enabled.length ? enabled.map(e => `- ${e}`).join('\n') : '- Plain paragraphs only'}

${disabled.length ? `**Do NOT include:**\n${disabled.map(d => `- ${d}`).join('\n')}\n` : ''}
**Markup rules:**
- Return content as HTML fragments only — no <html>, <head>, <body> or <style> tags.
- Do not wrap the HTML in markdown code fences.
- Every paragraph must be inside a <p> tag; never emit bare text between tags.`;
}

// ============================================
// FILE PATH HELPERS
// ============================================

function getBookDir(bookId: string): string {
  return path.join(process.cwd(), 'uploads', 'books', bookId);
}

function getMetadataPath(bookId: string): string {
  return path.join(getBookDir(bookId), 'metadata.json');
}

function getHtmlPath(bookId: string): string {
  return path.join(getBookDir(bookId), 'index.html');
}

function getDocxPath(bookId: string): string {
  return path.join(getBookDir(bookId), 'book.docx');
}

function writeMetadata(bookId: string, metadata: Record<string, any>): void {
  const dir = getBookDir(bookId);
  fs.mkdirSync(dir, { recursive: true });
  fs.writeFileSync(getMetadataPath(bookId), JSON.stringify(metadata, null, 2), 'utf-8');
}

function readMetadata(bookId: string): Record<string, any> | null {
  const metaPath = getMetadataPath(bookId);
  if (!fs.existsSync(metaPath)) return null;
  try {
    return JSON.parse(fs.readFileSync(metaPath, 'utf-8'));
  } catch {
    return null;
  }
}

// ============================================
// HTML BOOK GENERATION
// ============================================

export function generateBookHTML(
  book: any,
  chapters: any[],
  sections: Record<string, any[]>,
  config: GenerationConfig
): string {
  const visualIdentity = config.visualIdentity || {} as any;
  const primaryColor = (visualIdentity as any).primaryColor || '#C8FF2E';
  const secondaryColor = (visualIdentity as any).secondaryColor || '#1a1a2e';
  const accentColor = (visualIdentity as any).accentColor || '#7C6BF0';
  const headingFont = (visualIdentity as any).headingFont || "'Playfair Display', Georgia, serif";
  const bodyFont = (visualIdentity as any).bodyFont || "'Source Serif Pro', Georgia, serif";

  // Ensure chapters is always an array and merge with sections
  const safeChapters = Array.isArray(chapters) ? chapters : [];
  const chaptersWithSections = safeChapters.map(ch => ({
    ...ch,
    sections: sections[ch.id || ch._id] || []
  }));

  console.log(`[BookGenerator] Generating HTML for book: ${book.title || 'Untitled'}`);
  console.log(`[BookGenerator] Chapters count: ${chaptersWithSections.length}`);
  console.log(`[BookGenerator] Total sections: ${chaptersWithSections.reduce((sum, ch) => sum + (ch.sections?.length || 0), 0)}`);

  return `<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>${escapeHtml(book.title || 'Untitled Book')}</title>
  <link rel="preconnect" href="https://fonts.googleapis.com">
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
  <link href="https://fonts.googleapis.com/css2?family=${encodeURIComponent(headingFont.split(',')[0].replace(/['"]/g, ''))}:wght@400;600;700&family=${encodeURIComponent(bodyFont.split(',')[0].replace(/['"]/g, ''))}:wght@400;600&display=swap" rel="stylesheet">
  <style>
    /* ============================================================
       BOOK STYLESHEET
       Print-first: the page is a 6in x 9in trade book. On screen the
       same measure is shown as paper sheets on a neutral desk so the
       preview and the exported document read the same.
       ============================================================ */

    :root {
      --primary-color: ${primaryColor};
      --secondary-color: ${secondaryColor};
      --accent-color: ${accentColor};
      --ink: #1a1a1a;
      --ink-soft: #565656;
      --ink-faint: #8a8a8a;
      --paper: #ffffff;
      --paper-alt: #f7f6f3;
      --rule: #d8d5cf;
      --desk: #e8e6e1;
    }

    @page {
      size: 6in 9in;
      margin: 0.8in 0.7in 0.85in;
    }

    * { margin: 0; padding: 0; box-sizing: border-box; }

    html { -webkit-text-size-adjust: 100%; }

    body {
      font-family: ${bodyFont};
      font-size: 11.5pt;
      line-height: 1.62;
      color: var(--ink);
      background: var(--desk);
      text-rendering: optimizeLegibility;
      -webkit-font-smoothing: antialiased;
      padding: 32px 16px;
    }

    /* Every top-level part of the book is a sheet of paper. */
    .page {
      width: 6in;
      min-height: 9in;
      max-width: 100%;
      margin: 0 auto 28px;
      padding: 0.8in 0.7in 0.85in;
      background: var(--paper);
      box-shadow: 0 1px 2px rgba(0,0,0,0.08), 0 12px 28px rgba(0,0,0,0.10);
      display: flow-root;
      position: relative;
    }

    /* --- Front matter ------------------------------------------------ */

    .cover-page {
      background: var(--paper-alt);
      border-top: 10px solid var(--primary-color);
      text-align: center;
      page-break-after: always;
    }

    .cover-content {
      padding-top: 1.4in;
    }

    .cover-author {
      font-family: ${bodyFont};
      font-size: 9.5pt;
      text-transform: uppercase;
      letter-spacing: 0.22em;
      color: var(--ink-soft);
      margin-bottom: 1.6rem;
    }

    .cover-title {
      font-family: ${headingFont};
      font-size: 30pt;
      font-weight: 700;
      line-height: 1.16;
      color: var(--ink);
      margin-bottom: 1.1rem;
    }

    .cover-rule {
      width: 64px;
      height: 2px;
      background: var(--primary-color);
      margin: 0 auto 1.3rem;
    }

    .cover-subtitle {
      font-family: ${headingFont};
      font-size: 13pt;
      font-style: italic;
      color: var(--ink-soft);
      line-height: 1.45;
      margin-bottom: 1.4rem;
    }

    .cover-description {
      font-size: 10.5pt;
      color: var(--ink-soft);
      line-height: 1.7;
      max-width: 3.6in;
      margin: 0 auto;
    }

    .cover-publisher {
      position: absolute;
      left: 0.7in;
      right: 0.7in;
      bottom: 0.9in;
      font-size: 8.5pt;
      letter-spacing: 0.18em;
      text-transform: uppercase;
      color: var(--ink-faint);
    }

    .title-page {
      text-align: center;
      page-break-after: always;
    }

    .title-content { padding-top: 1.7in; }

    .title-main {
      font-family: ${headingFont};
      font-size: 24pt;
      font-weight: 700;
      line-height: 1.2;
      color: var(--ink);
      margin-bottom: 0.7rem;
    }

    .title-subtitle {
      font-family: ${headingFont};
      font-size: 12pt;
      font-style: italic;
      color: var(--ink-soft);
      margin-bottom: 1.6rem;
    }

    .title-divider {
      width: 52px;
      height: 1px;
      background: var(--rule);
      margin: 0 auto 1.6rem;
    }

    .title-author {
      font-size: 11.5pt;
      color: var(--ink);
    }

    .title-imprint {
      position: absolute;
      left: 0.7in;
      right: 0.7in;
      bottom: 1in;
      font-size: 9pt;
      color: var(--ink-faint);
      line-height: 1.7;
    }

    .copyright-page { page-break-after: always; }

    .copyright-content {
      position: absolute;
      left: 0.7in;
      right: 0.7in;
      bottom: 0.85in;
      font-size: 8.5pt;
      line-height: 1.65;
      color: var(--ink-soft);
    }

    .copyright-content p { margin-bottom: 0.55rem; }
    .copyright-content .copyright-notice { text-align: left; }
    .copyright-content .copyright-fine { color: var(--ink-faint); }

    /* --- Table of contents ------------------------------------------- */

    .toc-page { page-break-after: always; }

    .toc-title {
      font-family: ${headingFont};
      font-size: 16pt;
      font-weight: 700;
      letter-spacing: 0.14em;
      text-transform: uppercase;
      text-align: center;
      margin-bottom: 0.6rem;
    }

    .toc-rule {
      width: 48px;
      height: 1px;
      background: var(--rule);
      margin: 0 auto 2.4rem;
    }

    .toc-group-title {
      font-size: 8pt;
      text-transform: uppercase;
      letter-spacing: 0.18em;
      color: var(--ink-faint);
      margin: 1.8rem 0 0.7rem;
    }

    .toc-group:first-of-type .toc-group-title { margin-top: 0; }

    .toc-entry {
      display: flex;
      align-items: baseline;
      gap: 0.4rem;
      padding: 0.34rem 0;
      text-decoration: none;
      color: var(--ink);
      break-inside: avoid;
    }

    .toc-entry .chapter-number {
      flex: 0 0 1.15rem;
      font-size: 9.5pt;
      color: var(--ink-faint);
    }

    .toc-entry .chapter-title {
      font-size: 10.5pt;
    }

    .toc-entry .toc-leader {
      flex: 1;
      border-bottom: 1px dotted var(--rule);
      transform: translateY(-0.22em);
      min-width: 1rem;
    }

    .toc-entry .page-number {
      font-size: 9.5pt;
      color: var(--ink-soft);
      font-variant-numeric: tabular-nums;
    }

    .toc-entry-front {
      display: block;
      padding: 0.3rem 0;
      font-size: 10.5pt;
      text-decoration: none;
      color: var(--ink);
    }

    .toc-empty-notice {
      font-size: 10pt;
      font-style: italic;
      color: var(--ink-faint);
      padding: 0.6rem 0;
    }

    /* --- Chapters ------------------------------------------------------ */

    .chapter { page-break-before: always; }

    .chapter-header {
      text-align: center;
      padding-top: 0.75in;
      margin-bottom: 2.4rem;
    }

    .chapter-number {
      font-family: ${bodyFont};
      font-size: 8.5pt;
      text-transform: uppercase;
      letter-spacing: 0.26em;
      color: var(--primary-color);
      filter: brightness(0.72);
      margin-bottom: 1rem;
    }

    .chapter-title {
      font-family: ${headingFont};
      font-size: 19pt;
      font-weight: 700;
      line-height: 1.24;
      color: var(--ink);
      margin-bottom: 1rem;
    }

    .chapter-header::after {
      content: '';
      display: block;
      width: 44px;
      height: 1px;
      background: var(--rule);
      margin: 0 auto;
    }

    .chapter-description {
      font-family: ${headingFont};
      font-size: 11pt;
      font-style: italic;
      color: var(--ink-soft);
      max-width: 3.6in;
      margin: 0 auto 1.1rem;
      line-height: 1.5;
    }

    /* Body text: a single measure, justified, indented paragraphs. */
    .chapter-content,
    .front-matter-content {
      hyphens: auto;
      -webkit-hyphens: auto;
    }

    .chapter-content p,
    .front-matter-content p {
      text-align: justify;
      text-indent: 1.35em;
      margin: 0;
      orphans: 2;
      widows: 2;
    }

    /* The opening paragraph of a chapter or of any section is flush left —
       the indent marks a continuation, not a beginning. */
    .chapter-content > p:first-child,
    .front-matter-content > p:first-child,
    .chapter-content h2 + p,
    .chapter-content h3 + p,
    .chapter-content h4 + p,
    .chapter-content section > p:first-child,
    .chapter-content blockquote + p,
    .chapter-content ul + p,
    .chapter-content ol + p,
    .chapter-content table + p,
    .chapter-content pre + p,
    .chapter-content .callout + p {
      text-indent: 0;
    }

    .chapter-content h2 {
      font-family: ${headingFont};
      font-size: 13pt;
      font-weight: 700;
      line-height: 1.3;
      margin: 2rem 0 0.7rem;
      page-break-after: avoid;
      break-after: avoid;
    }

    .chapter-content h3 {
      font-family: ${headingFont};
      font-size: 11.5pt;
      font-weight: 700;
      line-height: 1.35;
      margin: 1.6rem 0 0.55rem;
      page-break-after: avoid;
      break-after: avoid;
    }

    .chapter-content h4 {
      font-family: ${headingFont};
      font-size: 10.5pt;
      font-weight: 400;
      font-style: italic;
      color: var(--ink-soft);
      margin: 1.3rem 0 0.4rem;
      page-break-after: avoid;
      break-after: avoid;
    }

    .chapter-content ul,
    .chapter-content ol,
    .front-matter-content ul,
    .front-matter-content ol {
      margin: 0.8rem 0 0.8rem 1.5rem;
    }

    .chapter-content li,
    .front-matter-content li {
      margin-bottom: 0.32rem;
      text-align: left;
    }

    .chapter-content blockquote {
      margin: 1.1rem 0 1.1rem 1.4rem;
      padding-left: 0.9rem;
      border-left: 1px solid var(--rule);
      font-style: italic;
      color: var(--ink-soft);
      break-inside: avoid;
    }

    .chapter-content blockquote p { text-indent: 0; text-align: left; }

    .chapter-content table {
      width: 100%;
      border-collapse: collapse;
      margin: 1.2rem 0;
      font-size: 9.5pt;
      break-inside: avoid;
    }

    .chapter-content th,
    .chapter-content td {
      border-bottom: 1px solid var(--rule);
      padding: 0.45rem 0.55rem;
      text-align: left;
      vertical-align: top;
    }

    .chapter-content th {
      background: var(--paper-alt);
      font-weight: 600;
      border-bottom: 1px solid var(--ink-faint);
    }

    .chapter-content img {
      max-width: 100%;
      height: auto;
      margin: 1.2rem auto;
      display: block;
      break-inside: avoid;
    }

    .chapter-content code,
    .front-matter-content code {
      font-family: 'Consolas', 'Monaco', monospace;
      font-size: 0.88em;
      background: var(--paper-alt);
      padding: 0.1em 0.3em;
    }

    .chapter-content pre {
      background: var(--paper-alt);
      border: 1px solid var(--rule);
      padding: 0.8rem 0.9rem;
      margin: 1.1rem 0;
      overflow-x: auto;
      font-size: 9pt;
      line-height: 1.5;
      break-inside: avoid;
    }

    .chapter-content pre code { background: transparent; padding: 0; }

    /* Set-off notes: a rule in the margin, not a coloured card. */
    .learning-objectives,
    .key-takeaways,
    .callout {
      border-left: 2px solid var(--primary-color);
      background: var(--paper-alt);
      padding: 0.85rem 1rem;
      margin: 1.4rem 0;
      font-size: 10pt;
      break-inside: avoid;
    }

    .callout.warning { border-left-color: #b98900; }
    .callout.danger { border-left-color: #b23c2e; }
    .key-takeaways { border-left-color: var(--accent-color); }

    .learning-objectives h4,
    .key-takeaways h4,
    .callout h5 {
      font-family: ${bodyFont};
      font-size: 8pt;
      font-weight: 600;
      text-transform: uppercase;
      letter-spacing: 0.16em;
      color: var(--ink-soft);
      margin-bottom: 0.55rem;
    }

    .learning-objectives ul,
    .key-takeaways ul,
    .callout ul {
      list-style: disc;
      margin: 0 0 0 1.1rem;
    }

    .learning-objectives li,
    .key-takeaways li,
    .callout li { margin-bottom: 0.28rem; }

    .callout p { text-indent: 0; text-align: left; }

    .chapter-content > section { margin-top: 1.6rem; }

    /* --- Other front/back matter -------------------------------------- */

    .preface-page,
    .about-author-page,
    .glossary-page,
    .references-page,
    .appendix-page {
      page-break-before: always;
    }

    .part-title {
      font-family: ${headingFont};
      font-size: 16pt;
      font-weight: 700;
      letter-spacing: 0.12em;
      text-transform: uppercase;
      text-align: center;
      padding-top: 0.55in;
      margin-bottom: 0.6rem;
    }

    .part-rule {
      width: 44px;
      height: 1px;
      background: var(--rule);
      margin: 0 auto 2.2rem;
    }

    .author-info { display: block; }

    .author-info h3 {
      font-family: ${headingFont};
      font-size: 12pt;
      margin-bottom: 0.6rem;
    }

    .glossary-term { margin-bottom: 0.7rem; break-inside: avoid; }
    .glossary-term dt { font-weight: 600; display: inline; }
    .glossary-term dd { display: inline; color: var(--ink-soft); }
    .glossary-term dd::before { content: ' — '; }

    .references-list { margin-left: 1.4rem; }
    .references-list li { margin-bottom: 0.4rem; }

    /* --- Print --------------------------------------------------------- */

    @media print {
      body {
        background: #fff;
        padding: 0;
      }

      .page {
        width: auto;
        min-height: 0;
        margin: 0;
        padding: 0;
        box-shadow: none;
        background: transparent;
      }

      .cover-page {
        border-top: none;
        background: transparent;
      }

      .cover-publisher,
      .title-imprint,
      .copyright-content {
        position: static;
        margin-top: 2.5rem;
      }

      .cover-content { padding-top: 1.2in; }
      .copyright-page { display: block; }

      h1, h2, h3, h4 { page-break-after: avoid; break-after: avoid; }
      p { orphans: 2; widows: 2; }
      .chapter { page-break-before: always; }
    }

    /* --- Small screens -------------------------------------------------- */

    @media (max-width: 680px) {
      body { padding: 12px 0; }
      .page {
        width: 100%;
        padding: 1.6rem 1.2rem 2rem;
        margin-bottom: 12px;
      }
      .cover-title { font-size: 22pt; }
      .chapter-title { font-size: 16pt; }
      .cover-publisher,
      .title-imprint,
      .copyright-content { position: static; margin-top: 2rem; }
    }
  </style>
</head>
<body>
${generateCoverPage(book, chaptersWithSections)}
${generateTitlePage(book)}
${generateCopyrightPage(book)}
${generateTOCPage(book, chaptersWithSections)}
${generatePrefacePage(book)}
${generateAboutAuthorPage(book)}
${generateChapters(chaptersWithSections)}
${generateBackMatter(book)}
</body>
</html>`;
}

function escapeHtml(str: string): string {
  return str
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#039;');
}

/** Authors are only printed when the book actually carries them. */
function authorLine(book: any): string {
  const names = Array.isArray(book.authors)
    ? book.authors.map((a: any) => (typeof a === 'string' ? a : a?.name)).filter(Boolean)
    : [];
  return names.join(', ');
}

function generateCoverPage(book: any, _chapters: any[]): string {
  const authors = authorLine(book);

  return `
  <section class="page cover-page" id="cover-page">
    <div class="cover-content">
      ${authors ? `<p class="cover-author">${escapeHtml(authors)}</p>` : ''}
      <h1 class="cover-title">${escapeHtml(book.title || 'Untitled')}</h1>
      <div class="cover-rule"></div>
      ${book.subtitle ? `<p class="cover-subtitle">${escapeHtml(book.subtitle)}</p>` : ''}
      ${book.description ? `<p class="cover-description">${escapeHtml(book.description)}</p>` : ''}
    </div>
    ${book.publisher ? `<p class="cover-publisher">${escapeHtml(book.publisher)}</p>` : ''}
  </section>`;
}

function generateTitlePage(book: any): string {
  const authors = authorLine(book);
  return `
  <section class="page title-page" id="title-page">
    <div class="title-content">
      <h1 class="title-main">${escapeHtml(book.title || 'Untitled')}</h1>
      ${book.subtitle ? `<p class="title-subtitle">${escapeHtml(book.subtitle)}</p>` : ''}
      <div class="title-divider"></div>
      ${authors ? `<p class="title-author">${escapeHtml(authors)}</p>` : ''}
    </div>
    <div class="title-imprint">
      ${book.publisher ? `<p class="title-publisher">${escapeHtml(book.publisher)}</p>` : ''}
      <p class="title-year">${new Date().getFullYear()}</p>
    </div>
  </section>`;
}

function generateCopyrightPage(book: any): string {
  const authors = authorLine(book);
  const year = new Date().getFullYear();

  return `
  <section class="page copyright-page">
    <div class="copyright-content">
      <p class="copyright-main">Copyright © ${year}${authors ? ` ${escapeHtml(authors)}` : ''}</p>
      <p class="copyright-rights">All rights reserved.</p>
      <p class="copyright-notice">No part of this publication may be reproduced, distributed, or transmitted in any form or by any means, including photocopying, recording, or other electronic or mechanical methods, without the prior written permission of the publisher, except in the case of brief quotations embodied in critical reviews and certain other non-commercial uses permitted by copyright law.</p>
      ${book.publisher ? `<p class="copyright-publisher">${escapeHtml(book.publisher)}</p>` : ''}
      <p class="copyright-fine">First Edition</p>
      ${book.isbn ? `<p class="copyright-fine copyright-isbn">ISBN: ${escapeHtml(book.isbn)}</p>` : ''}
    </div>
  </section>`;
}

function generateTOCPage(book: any, chapters: any[]): string {
  // Handle empty chapters array
  const chaptersList = chapters && chapters.length > 0 ? chapters : [];

  // No page numbers here: the HTML preview is a continuous document and has no
  // pagination to number. The exported PDF carries a contents page with real
  // folios taken from the typeset pages.
  const chaptersHtml = chaptersList.map((ch, i) => `
    <a href="#chapter-${i + 1}" class="toc-entry">
      <span class="chapter-number">${i + 1}</span>
      <span class="chapter-title">${escapeHtml(ch.title || 'Untitled')}</span>
    </a>
  `).join('');

  const hasBackMatter =
    (book.glossary && book.glossary.length > 0) ||
    (book.references && book.references.length > 0) ||
    (book.appendix && book.appendix.length > 0);

  return `
  <nav class="page toc-page" id="toc-page">
    <h1 class="toc-title">Contents</h1>
    <div class="toc-rule"></div>

    ${book.preface || book.aboutAuthor ? `
      <div class="toc-group">
        <h2 class="toc-group-title">Front Matter</h2>
        ${book.preface ? `<a href="#preface" class="toc-entry-front">Preface</a>` : ''}
        ${book.aboutAuthor ? `<a href="#about-author" class="toc-entry-front">About the Author</a>` : ''}
      </div>
    ` : ''}

    <div class="toc-group">
      <h2 class="toc-group-title">Chapters</h2>
      ${chaptersList.length > 0
        ? chaptersHtml
        : `<p class="toc-empty-notice">No chapters defined. Add chapters to see them here.</p>`}
    </div>

    ${hasBackMatter ? `
      <div class="toc-group">
        <h2 class="toc-group-title">Back Matter</h2>
        ${book.glossary && book.glossary.length > 0 ? `<a href="#glossary" class="toc-entry-front">Glossary</a>` : ''}
        ${book.references && book.references.length > 0 ? `<a href="#references" class="toc-entry-front">References</a>` : ''}
        ${book.appendix && book.appendix.length > 0 ? `<a href="#appendix" class="toc-entry-front">Appendix</a>` : ''}
      </div>
    ` : ''}
  </nav>`;
}

function generatePrefacePage(book: any): string {
  if (!book.preface) return '';

  return `
  <section id="preface" class="page preface-page">
    <h1 class="part-title">Preface</h1>
    <div class="part-rule"></div>
    <div class="preface-content front-matter-content">
      ${processContent(book.preface)}
    </div>
  </section>`;
}

function generateAboutAuthorPage(book: any): string {
  if (!book.aboutAuthor) return '';

  const authors = book.authors?.[0];
  const authorName = authors?.name || authors || 'Author';
  const authorBio = book.aboutAuthor;

  return `
  <section id="about-author" class="page about-author-page">
    <h1 class="part-title">About the Author</h1>
    <div class="part-rule"></div>
    <div class="author-info front-matter-content">
      <h3>${escapeHtml(authorName)}</h3>
      <div class="author-bio">${processContent(authorBio)}</div>
    </div>
  </section>`;
}

function generateChapters(chapters: any[]): string {
  // Handle empty chapters array
  if (!chapters || chapters.length === 0) {
    return `
    <section class="page chapter">
      <header class="chapter-header">
        <h1 class="chapter-title">No Chapters Yet</h1>
      </header>
      <div class="chapter-content">
        <p>Add chapters to your book to see them here. Chapters will appear in the order they are defined in your book structure.</p>
      </div>
    </section>`;
  }

  return chapters.map((ch, i) => {
    const chapterId = ch.id || ch._id;
    const sections = ch.sections || [];
    const learningObjectives = ch.learningObjectives || [];
    const keyTakeaways = ch.keyTakeaways || [];
    const chapterTitle = ch.title || 'Untitled Chapter';
    const chapterDescription = ch.description || '';
    const chapterContent = ch.content || '';

    // No chapter-to-chapter navigation links: a book does not carry
    // "next / previous" controls on the page, and they printed into the export.
    return `
    <section id="chapter-${i + 1}" class="page chapter">
      <header class="chapter-header">
        <p class="chapter-number">Chapter ${i + 1}</p>
        <h1 class="chapter-title">${escapeHtml(chapterTitle)}</h1>
        ${chapterDescription ? `<p class="chapter-description">${escapeHtml(chapterDescription)}</p>` : ''}
      </header>

      ${learningObjectives.length > 0 ? `
        <div class="learning-objectives">
          <h4>In This Chapter</h4>
          <ul>
            ${learningObjectives.map((obj: string) => `<li>${escapeHtml(obj)}</li>`).join('')}
          </ul>
        </div>
      ` : ''}

      <div class="chapter-content">
        ${chapterContent ? processContent(chapterContent) : `
          <p><em>Content will be generated here when you run AI generation.</em></p>
        `}

        ${sections.length > 0 ? sections.map((sec: any, j: number) => {
          const sectionTitle = sec.title || 'Untitled Section';
          const sectionContent = sec.content || '';
          return `
          <section id="section-${i + 1}-${j + 1}">
            <h2>${escapeHtml(sectionTitle)}</h2>
            ${sectionContent ? processContent(sectionContent) : `
              <p><em>Section content will be generated here.</em></p>
            `}
            ${sec.keyPoints && sec.keyPoints.length > 0 ? `
              <div class="callout tip">
                <h5>Key Points</h5>
                <ul>
                  ${sec.keyPoints.map((kp: string) => `<li>${escapeHtml(kp)}</li>`).join('')}
                </ul>
              </div>
            ` : ''}
            ${sec.examples && sec.examples.length > 0 ? `
              <h4>Examples</h4>
              ${sec.examples.map((ex: string) => `<div class="callout info"><p>${escapeHtml(ex)}</p></div>`).join('')}
            ` : ''}
          </section>`;
        }).join('') : ''}
      </div>

      ${keyTakeaways.length > 0 ? `
        <div class="key-takeaways">
          <h4>Key Takeaways</h4>
          <ul>
            ${keyTakeaways.map((kt: string) => `<li>${escapeHtml(kt)}</li>`).join('')}
          </ul>
        </div>
      ` : ''}
    </section>`;
  }).join('\n');
}

function processContent(content: string): string {
  if (!content) return '';

  let cleaned = content.trim();

  // Models routinely wrap the requested HTML in a markdown fence despite being
  // asked not to. Left in place the fence markers render as literal text in the
  // middle of the book, so strip them before anything else.
  cleaned = cleaned.replace(/^```(?:html|markdown|md)?\s*\n?/i, '').replace(/\n?```\s*$/, '').trim();

  // A full document pasted into a chapter breaks the book's own layout: its
  // <style> overrides the page CSS and the nested <html>/<body> is invalid.
  // Keep only what belongs inside the chapter.
  if (/<html[\s>]/i.test(cleaned) || /<!DOCTYPE/i.test(cleaned)) {
    const body = cleaned.match(/<body[^>]*>([\s\S]*?)<\/body>/i);
    cleaned = (body ? body[1] : cleaned.replace(/<!DOCTYPE[^>]*>/gi, ''))
      .replace(/<\/?(?:html|head|body)[^>]*>/gi, '')
      .replace(/<style[\s\S]*?<\/style>/gi, '')
      .replace(/<script[\s\S]*?<\/script>/gi, '')
      .trim();
  }

  if (!cleaned) return '';

  // Is this actually HTML? The old test was `includes('<')`, which any stray
  // "<" in prose ("if x < y", "->") satisfied — so markdown containing one was
  // emitted raw and the whole chapter rendered as a single unformatted block.
  // Require a real tag instead.
  if (/<(p|div|h[1-6]|ul|ol|li|table|blockquote|section|pre|figure|img|strong|em|br)\b[^>]*>/i.test(cleaned)) {
    return cleaned;
  }

  // Process markdown-like content
  return cleaned
    .split(/\n{2,}/)
    .map(block => block.trim())
    .filter(Boolean)
    .map(para => {
      if (para.startsWith('#### ')) {
        return `<h5>${inlineMarkdown(para.slice(5))}</h5>`;
      }
      if (para.startsWith('### ')) {
        return `<h4>${inlineMarkdown(para.slice(4))}</h4>`;
      }
      if (para.startsWith('## ')) {
        return `<h3>${inlineMarkdown(para.slice(3))}</h3>`;
      }
      if (para.startsWith('# ')) {
        return `<h2>${inlineMarkdown(para.slice(2))}</h2>`;
      }
      if (/^[-*]\s/.test(para)) {
        const items = para.split('\n').map(line => line.replace(/^[-*]\s+/, ''));
        return `<ul>${items.map(i => `<li>${inlineMarkdown(i)}</li>`).join('')}</ul>`;
      }
      // Numbered lists were previously rendered as one run-on paragraph.
      if (/^\d+[.)]\s/.test(para)) {
        const items = para.split('\n').map(line => line.replace(/^\d+[.)]\s+/, ''));
        return `<ol>${items.map(i => `<li>${inlineMarkdown(i)}</li>`).join('')}</ol>`;
      }
      if (para.startsWith('> ')) {
        return `<blockquote>${inlineMarkdown(para.replace(/^>\s?/gm, ''))}</blockquote>`;
      }
      // A single newline inside a paragraph is a soft break, not a new block.
      return `<p>${inlineMarkdown(para).replace(/\n/g, '<br>')}</p>`;
    })
    .join('\n');
}

/** Bold, italic and inline code — otherwise the markers show up as literal text. */
function inlineMarkdown(text: string): string {
  return text
    .replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>')
    .replace(/(^|[^*])\*([^*\n]+)\*/g, '$1<em>$2</em>')
    .replace(/`([^`\n]+)`/g, '<code>$1</code>');
}

function generateBackMatter(book: any): string {
  let html = '';

  // Glossary
  if (book.glossary && book.glossary.length > 0) {
    html += `
    <section id="glossary" class="page glossary-page">
      <h1 class="part-title">Glossary</h1>
      <div class="part-rule"></div>
      <dl>
        ${book.glossary.map((item: any) => `
          <div class="glossary-term">
            <dt>${escapeHtml(item.term || item)}</dt>
            <dd>${escapeHtml(item.definition || '')}</dd>
          </div>
        `).join('')}
      </dl>
    </section>`;
  }

  // References
  if (book.references && book.references.length > 0) {
    html += `
    <section id="references" class="page references-page">
      <h1 class="part-title">References</h1>
      <div class="part-rule"></div>
      <ol class="references-list">
        ${book.references.map((ref: any) => `
          <li>${escapeHtml(typeof ref === 'string' ? ref : ref.title || ref)}</li>
        `).join('')}
      </ol>
    </section>`;
  }

  // Appendix
  if (book.appendix && book.appendix.length > 0) {
    html += `
    <section id="appendix" class="page appendix-page">
      <h1 class="part-title">Appendix</h1>
      <div class="part-rule"></div>
      <div class="chapter-content">
        ${book.appendix.map((app: any, i: number) => `
          <section>
            <h2>${escapeHtml(app.title || `Section ${i + 1}`)}</h2>
            ${app.content ? processContent(app.content) : ''}
          </section>
        `).join('')}
      </div>
    </section>`;
  }

  return html;
}

// ============================================
// CONTENT DEPTH TARGETS
// ============================================

/**
 * How much prose a chapter and each of its sections should actually contain.
 *
 * The generator used to hardcode "minimum 500-1000 words" per chapter and
 * "300+ words" per section at the very end of the prompt — right next to the
 * output schema — which overrode the far more generous depth targets stated
 * earlier in the wizard's prompt. Models anchor on the last, most specific
 * number they read, so every book came back at roughly that floor. Targets are
 * now resolved once, from the book's own configuration, and the same numbers
 * are used everywhere in the prompt.
 */
interface DepthTargets {
  /** The depth level these targets were derived from, for the prompt text. */
  label: string;
  /** Words of prose for the chapter body (introduction + connective writing). */
  chapterWords: number;
  /** Words of prose per section. */
  sectionWords: number;
  /** Words for the whole chapter including all of its sections. */
  totalChapterWords: number;
}

/** Per-depth defaults. Keys cover both vocabularies used across the module. */
const DEPTH_WORD_TARGETS: Record<string, { chapter: number; section: number }> = {
  beginner: { chapter: 900, section: 700 },
  brief: { chapter: 900, section: 700 },
  intermediate: { chapter: 1100, section: 1000 },
  standard: { chapter: 1100, section: 1000 },
  advanced: { chapter: 1400, section: 1500 },
  deep: { chapter: 1400, section: 1500 },
  expert: { chapter: 1600, section: 2000 },
  comprehensive: { chapter: 1600, section: 2000 },
};

/**
 * Resolve the word targets for one chapter.
 *
 * A book that carries an explicit `wordCount` wins — the author asked for a
 * specific size, so the budget is split across chapters and their sections.
 * Otherwise the configured content depth decides.
 */
function resolveDepthTargets(
  book: any,
  config: GenerationConfig,
  totalChapters: number,
  sectionCount: number
): DepthTargets {
  const depthKey = String(config.contentDepth || 'intermediate').toLowerCase();
  const defaults = DEPTH_WORD_TARGETS[depthKey] || DEPTH_WORD_TARGETS.intermediate;
  const label = config.contentDepth || 'Intermediate';

  const requestedTotal = Number(book?.wordCount) || 0;
  if (requestedTotal > 0 && totalChapters > 0) {
    // Clamped so an unrealistic figure (either direction) can't produce a
    // chapter target that the model will ignore outright.
    const perChapter = Math.min(15000, Math.max(1200, Math.round(requestedTotal / totalChapters)));
    // The chapter body carries the introduction and the connective prose; the
    // sections carry the bulk. With no sections the chapter body carries it all.
    const bodyShare = sectionCount > 0 ? Math.round(perChapter * 0.3) : perChapter;
    const perSection = sectionCount > 0
      ? Math.max(400, Math.round((perChapter - bodyShare) / sectionCount))
      : 0;
    return {
      label,
      chapterWords: bodyShare,
      sectionWords: perSection,
      totalChapterWords: perChapter,
    };
  }

  const chapterWords = sectionCount > 0
    ? defaults.chapter
    : defaults.chapter + defaults.section * 2;
  return {
    label,
    chapterWords,
    sectionWords: defaults.section,
    totalChapterWords: chapterWords + defaults.section * sectionCount,
  };
}

/**
 * The depth, structure and originality contract shared by every book prompt.
 *
 * This replaces the old bare word floors. It states what a chapter must
 * actually contain (explanation, examples, considerations, transitions) rather
 * than only how long it should be, so the extra length is spent on substance
 * instead of restated definitions.
 */
function buildDepthDirective(
  targets: DepthTargets,
  config: GenerationConfig,
  book: any,
  sectionCount: number
): string {
  const audience = book?.targetAudience || 'the book\'s intended readers';

  return `## CONTENT DEPTH REQUIREMENTS (these override any shorter figure stated elsewhere)

**Depth level:** ${targets.label}
**Audience:** ${audience}

- Chapter body (introduction and the connective prose around the sections): **~${targets.chapterWords.toLocaleString('en-US')} words**.
${sectionCount > 0 ? `- Each section: **~${targets.sectionWords.toLocaleString('en-US')} words** of developed prose — this is a target to write to, not a ceiling to stop at.
- Whole chapter including its ${sectionCount} section${sectionCount === 1 ? '' : 's'}: **~${targets.totalChapterWords.toLocaleString('en-US')} words**.` : `- This chapter has no separate sections, so the chapter body carries the full **~${targets.totalChapterWords.toLocaleString('en-US')} words** and must define its own subheadings.`}

**What that length must be spent on — never on restating the same point:**

- Break every substantial topic into subheadings (\`<h3>\`, with \`<h4>\` beneath where a point needs it). Let the subject decide how many; do not use an identical section skeleton in every chapter.
- For each major concept, cover what it is, why it matters, how it works, and when and where it applies. Treat this as a checklist of angles, not as literal headings to print.
- Develop arguments across multiple paragraphs. A subheading followed by two thin paragraphs is a failure of this brief.
- Include examples — scenarios, walk-throughs, comparisons, short cases — wherever they genuinely clarify the point. Label hypothetical examples as illustrations rather than presenting them as documented fact.
- Where relevant, cover practical application, common mistakes, limitations and trade-offs.
- Invent no statistics, studies, citations, named experts or historical events. Write from established, general knowledge of the subject; where a precise figure would be needed, describe the relationship qualitatively instead.

**Depth calibration for this audience:** ${
    ['beginner', 'brief'].includes(String(config.contentDepth || '').toLowerCase())
      ? 'Define each term on first use, build from the concrete to the abstract, and let examples carry the explanation. Depth here means patient, thorough explanation — not simplified or shortened content.'
      : ['expert', 'comprehensive', 'advanced', 'deep'].includes(String(config.contentDepth || '').toLowerCase())
      ? 'Assume fluency with the fundamentals. Spend the length on nuance, edge cases, trade-offs between approaches, and the reasoning behind recommendations rather than on foundational definitions.'
      : 'Assume the fundamentals are known but reinforce them briefly where a chapter depends on them. Spend the length on practical application, worked reasoning and real decision-making context.'
  }

**Prohibited:**
- Summary-style or outline-style output. Bullet lists must support prose, never replace it.
- Repeating a definition, example, statistic or conclusion that an earlier chapter or section already covered — refer back to it in a clause and move on.
- Padding: rephrasing a point you have already made, generic filler paragraphs, or closing every section with a recap.
- Any meta commentary about being an AI, about the prompt, or about the generation process.`;
}

/**
 * A compact digest of a chapter that was just generated, handed to the next
 * chapter's prompt so the book reads as one continuous argument instead of a
 * set of independent answers. Plain text and capped, so the added context stays
 * small regardless of how long the chapter ran.
 */
function summarizeChapterForContinuity(chapterTitle: string, chapterData: any): string {
  const html = [
    chapterData?.content || '',
    ...(Array.isArray(chapterData?.sections) ? chapterData.sections.map((s: any) => s?.content || '') : []),
  ].join(' ');

  if (!html.trim()) return '';

  // The subheadings the chapter actually used — the clearest signal of what it
  // covered, and what a later chapter must therefore not re-explain.
  const headings = [...html.matchAll(/<h[2-4][^>]*>([\s\S]*?)<\/h[2-4]>/gi)]
    .map(m => m[1].replace(/<[^>]+>/g, '').trim())
    .filter(Boolean)
    .slice(0, 12);

  const plain = html
    .replace(/<[^>]+>/g, ' ')
    .replace(/&[a-z]+;/gi, ' ')
    .replace(/\s+/g, ' ')
    .trim();

  const opening = plain.slice(0, 700);

  return [
    `"${chapterTitle}" covered: ${opening}${plain.length > 700 ? '…' : ''}`,
    headings.length ? `Subheadings used: ${headings.join('; ')}.` : '',
  ].filter(Boolean).join('\n');
}

// ============================================
// BUILD CHAPTER-SPECIFIC PROMPT (Uses AI-generated prompt)
// ============================================

function buildChapterPrompt(
  book: any,
  chapter: any,
  chapterSections: any[],
  chapterIndex: number,
  totalChapters: number,
  config: GenerationConfig,
  generatedPrompt?: string,  // The comprehensive AI prompt from the AI Prompt step
  continuity?: {
    previousTitle?: string;
    previousSummary?: string;
    nextTitle?: string;
  }
): string {
  const bookTitle = book.title || 'Untitled Book';
  const chapterTitle = chapter.title || `Chapter ${chapterIndex + 1}`;
  const chapterDescription = chapter.description || '';
  const learningObjectives = chapter.learningObjectives || [];
  const keyTakeaways = chapter.keyTakeaways || [];

  const targets = resolveDepthTargets(book, config, totalChapters, chapterSections.length);
  const depthDirective = buildDepthDirective(targets, config, book, chapterSections.length);

  // Where this chapter sits in the argument of the book. Without this each
  // chapter was generated in isolation and re-introduced concepts the reader
  // had already met two chapters earlier.
  const continuityBlock = `### Position in the Book

This is chapter ${chapterIndex + 1} of ${totalChapters}${
    chapterIndex === 0
      ? ' — the opening chapter. Establish the premise and the terminology the rest of the book will rely on.'
      : chapterIndex === totalChapters - 1
      ? ' — the closing chapter. Draw the book\'s threads together and leave the reader with what to do next.'
      : '.'
  }
${continuity?.previousTitle ? `
**Previous chapter:** "${continuity.previousTitle}"
${continuity?.previousSummary ? `
What it already covered — build on this, reference it where it is relevant, and do NOT re-explain it:
${continuity.previousSummary}
` : 'Open by connecting briefly to where that chapter left off.'}` : ''}
${continuity?.nextTitle ? `
**Next chapter:** "${continuity.nextTitle}" — close this chapter by setting up what that one will address, without pre-empting its content.
` : ''}
Use the same terminology for a concept every time it appears. If an earlier chapter named something, keep that name.`;

  const outputContract = `### Output Requirements

Return a JSON object for THIS CHAPTER ONLY:

\`\`\`json
{
  "id": "${chapter.id || chapter._id || ''}",
  "title": "${chapterTitle}",
  "content": "The chapter's own prose as an HTML fragment — opening, framing of the topic, the connective writing between sections, and the closing. ~${targets.chapterWords.toLocaleString('en-US')} words.",
  "sections": [
    {
      "id": "section-id-from-input",
      "title": "Section Title",
      "content": "The section's full prose as an HTML fragment, with its own <h3>/<h4> subheadings. ~${targets.sectionWords.toLocaleString('en-US')} words.",
      "keyPoints": ["the section's actual takeaways"],
      "examples": ["examples used in the section"]
    }
  ]
}
\`\`\`

- Return **one entry per input section, in the given order**, reusing each section's exact input id.
- Return ONLY the JSON object — no prose before or after it, no markdown fence around it.`;

  const chapterOpening = `### Chapter Opening and Close

Open by making clear what this chapter covers, why it matters to this reader, and how it follows from the book so far — written differently from how other chapters open. Close by consolidating what the chapter established and pointing forward. Neither should restate the chapter in full.`;

  // If we have a comprehensive generated prompt, use it and extract just the chapter context
  if (generatedPrompt && generatedPrompt.length > 0) {
    // Use the comprehensive prompt but focus on this specific chapter
    const chapterPrompt = `${generatedPrompt}

---

## FOCUS: GENERATE ONLY CHAPTER ${chapterIndex + 1} OF ${totalChapters}

You are now generating content for **Chapter ${chapterIndex + 1}: "${chapterTitle}"** specifically.

### Chapter Details

**Title:** ${chapterTitle}
**Description:** ${chapterDescription || 'Not provided'}
**Position in Book:** Chapter ${chapterIndex + 1} of ${totalChapters}

${learningObjectives.length > 0 ? `**Learning Objectives:**
${learningObjectives.map((obj: string) => `- ${obj}`).join('\n')}
` : ''}

${keyTakeaways.length > 0 ? `**Key Takeaways to Cover:**
${keyTakeaways.map((kt: string) => `- ${kt}`).join('\n')}
` : ''}

### Sections in This Chapter (${chapterSections.length} total)

${chapterSections.map((sec, i) => `
**Section ${i + 1}: ${sec.title || 'Untitled'}**
- Type: ${sec.type || 'content'}
- Key Points: ${sec.keyPoints?.join(', ') || 'Not specified'}
${sec.examples?.length ? `- Examples to work in: ${sec.examples.join(', ')}` : ''}
${sec.content ? `- Existing Content: ${sec.content.substring(0, 200)}...` : ''}
`).join('\n')}

${continuityBlock}

${chapterOpening}

${depthDirective}

${outputContract}

${buildFormattingInstructions(config)}

**CRITICAL:**
- Write the chapter in full. The depth requirements above replace any shorter word figure stated anywhere earlier in this prompt.
- Every section listed above must come back written — none omitted, none reduced to a placeholder or a summary.
- Return ONLY valid JSON, no additional text.`;

    return chapterPrompt;
  }

  // Fallback: Build a basic prompt if no comprehensive prompt available
  const prompt = `You are writing Chapter ${chapterIndex + 1} of ${totalChapters} for the book "${bookTitle}".

## CHAPTER DETAILS

**Title:** ${chapterTitle}
**Description:** ${chapterDescription || 'Not provided'}
**Position:** Chapter ${chapterIndex + 1} of ${totalChapters}

${learningObjectives.length > 0 ? `**Learning Objectives:**
${learningObjectives.map((obj: string) => `- ${obj}`).join('\n')}
` : ''}

${keyTakeaways.length > 0 ? `**Key Takeaways to Cover:**
${keyTakeaways.map((kt: string) => `- ${kt}`).join('\n')}
` : ''}

**Book type:** ${config.bookType || 'Guide'}
**Writing style:** ${config.writingStyle || 'Professional'}
**Tone:** ${config.toneOfVoice || 'Informative'}
**Language:** ${book.contentLanguage || book.language || 'English'}${
    (book.contentLanguage || book.language) && (book.contentLanguage || book.language) !== 'English'
      ? `\nWrite this chapter in ${book.contentLanguage || book.language}.`
      : ''
  }
${book.description ? `\n**What the book is about:** ${book.description}` : ''}

## SECTIONS IN THIS CHAPTER (${chapterSections.length} sections)

${chapterSections.map((sec, i) => `
### Section ${i + 1}: ${sec.title || 'Untitled'}
- Type: ${sec.type || 'content'}
- Key Points: ${sec.keyPoints?.join(', ') || 'Not specified'}
- Examples: ${sec.examples?.join(', ') || 'Not specified'}
`).join('\n')}

${continuityBlock}

${chapterOpening}

${depthDirective}

## WRITING STANDARD

Write as a knowledgeable author addressing a reader directly: clear explanations, natural transitions between ideas, consistent terminology, and a line of reasoning that carries from one paragraph to the next. Vary sentence and paragraph length. Do not open every section the same way, do not close every section with a recap, and do not lean on bullet lists where prose belongs.

${outputContract}

${buildFormattingInstructions(config)}

Write Chapter ${chapterIndex + 1} now.`;

  return prompt;
}

// ============================================
// UPDATE BOOK GENERATED CONTENT STATUS
// ============================================

async function updateGeneratedContentStatus(
  bookId: string,
  update: {
    status: string;
    jobId?: string;
    generatedAt?: string;
    error?: string;
    chapters?: any[];
    sections?: Record<string, any[]>;
  }
): Promise<void> {
  try {
    const { Book } = getModels();
    const book = await Book.findById(bookId);
    if (!book) {
      console.warn('[BookGenerator] Book not found:', bookId);
      return;
    }

    book.generatedContent = {
      ...(book.generatedContent as any || {}),
      ...update,
      updatedAt: new Date().toISOString(),
    };
    await book.save();
    console.log('[BookGenerator] Updated generatedContent status:', update.status, 'for book:', bookId);
  } catch (dbError: any) {
    console.error('[BookGenerator] Failed to update MongoDB status:', dbError.message);
  }
}

// ============================================
// GENERATE BOOK CONTENT CORE
// ============================================

export async function generateBookContentCore(
  book: any,
  chapters: ChapterInput[],
  sections: Record<string, SectionInput[]>,
  config: GenerationConfig,
  companyId: string,
  onProgress: (progress: number, step: string) => void,
  jobId?: string,
  generatedPrompt?: string,
): Promise<{ chapters: any[]; sections: Record<string, any[]>; html?: string }> {
  const bookId = book.id || book._id?.toString();

  try {
    const models = getModels();

    // Mark as generating
    await updateGeneratedContentStatus(bookId, {
      status: 'generating',
      jobId,
      generatedAt: new Date().toISOString(),
    });
    writeMetadata(bookId, {
      status: 'generating',
      jobId,
      generatedAt: new Date().toISOString(),
      bookId,
      companyId,
    });

    onProgress(5, 'Loading book context...');

    // Get business profile for context
    const { BusinessProfile, ModuleData } = models;
    let brandStrategy: any = null;
    let visualIdentity: any = null;

    try {
      brandStrategy = await ModuleData.findOne({ moduleId: 'brand-strategy', companyId });
      visualIdentity = await ModuleData.findOne({ moduleId: 'visual-identity', companyId });
    } catch {}

    onProgress(10, 'Building generation prompt...');

    // Use the provided generated prompt if available, otherwise build a default one
    let systemPrompt: string;
    let userPrompt: string;

    if (generatedPrompt && generatedPrompt.trim().length > 0) {
      // Use the comprehensive generated prompt from the AI Prompt step
      console.log('[BookGenerator] Using generated prompt from AI Prompt step');
      systemPrompt = `You are an elite author, senior editor, and publishing director with expertise in creating award-winning, print-ready manuscripts. Follow ALL instructions in the user prompt precisely. Generate complete, publication-quality content with no placeholders.`;
      userPrompt = generatedPrompt;
    } else {
      // Build a default prompt from the configuration
      console.log('[BookGenerator] Building default prompt from configuration');
      systemPrompt = `You are an expert author and content creator. Generate comprehensive, publication-quality book content.
The content should be:
- Well-structured with clear chapters and sections
- Professionally written with proper grammar and flow
- Engaging and informative
- Formatted appropriately for the book type`;

      userPrompt = `Generate complete content for the following book:

**Book Title:** ${book.title || 'Untitled'}
**Book Type:** ${config.bookType || 'Guide'}
**Description:** ${book.description || 'No description provided'}
**Target Audience:** ${book.targetAudience || 'General readers'}

**Writing Style:** ${config.writingStyle || 'Professional'}
**Tone of Voice:** ${config.toneOfVoice || 'Informative'}
**Content Depth:** ${config.contentDepth || 'Intermediate'}
**Language:** ${book.contentLanguage || book.language || 'English'}${(book.contentLanguage || book.language) && (book.contentLanguage || book.language) !== 'English' ? `\nGenerate this book in ${book.contentLanguage || book.language}.` : ''}

**Chapters (${chapters.length} total):**
${chapters.map((ch, i) => `
${i + 1}. ${ch.title}
   ${ch.description || 'No description'}
   ${ch.learningObjectives?.length ? `Learning Objectives: ${ch.learningObjectives.join(', ')}` : ''}
   ${ch.keyTakeaways?.length ? `Key Takeaways: ${ch.keyTakeaways.join(', ')}` : ''}
`).join('\n')}

**Sections by Chapter:**
${Object.entries(sections).map(([chapterId, secs]) => {
  const chapter = chapters.find(c => (c.id || c._id) === chapterId);
  return `\n### Chapter: ${chapter?.title || 'Unknown'}\n${secs.map((s, i) => `${i + 1}. ${s.title} (${s.type || 'content'})`).join('\n')}`;
}).join('\n')}

${config.imagesEnabled ? `\n**Include images:** Yes (style: ${config.imageStyle}, aspect ratio: ${config.imageAspect})` : '\n**Include images:** No'}

${buildFormattingInstructions(config)}

${buildDepthDirective(
  resolveDepthTargets(
    book,
    config,
    chapters.length,
    Math.round(Object.values(sections).reduce((sum, secs) => sum + secs.length, 0) / Math.max(chapters.length, 1))
  ),
  config,
  book,
  Math.round(Object.values(sections).reduce((sum, secs) => sum + secs.length, 0) / Math.max(chapters.length, 1))
)}

Chapters must build on one another: later chapters reference what earlier ones established and reuse their terminology rather than redefining it.

Return the result as a JSON object with this structure:
{
  "chapters": [
    {
      "id": "chapter-id-from-input",
      "content": "The chapter's own prose as an HTML fragment — opening, framing, connective writing and close.",
      "sections": [
        {
          "id": "section-id-from-input",
          "content": "The section's full prose as an HTML fragment, with its own subheadings.",
          "keyPoints": ["the section's actual takeaways"],
          "examples": ["examples used in the section"]${config.imagesEnabled ? ',\n          "images": ["image-description-1"]' : ''}
        }
      ]
    }
  ]
}

Include every chapter and every section listed above, using their exact input ids. Return ONLY the JSON object.`;
    }

    onProgress(20, 'Generating book content with AI...');

    // Log what we're sending to AI
    console.log('[BookGenerator] Input chapters count:', chapters.length);
    console.log('[BookGenerator] Input sections count:', Object.keys(sections).length);
    console.log('[BookGenerator] Using generated prompt:', generatedPrompt ? 'Yes (from AI Prompt step)' : 'No (using default)');
    console.log('[BookGenerator] Prompt length:', userPrompt.length, 'characters');

    // Store generated data for all chapters
    const generatedChapterMap: Record<string, any> = {};
    const generatedSectionMaps: Record<string, Record<string, any>> = {};
    const generatedChapterByIndex: any[] = [];

    // Generate content chapter by chapter for better quality and to avoid token limits
    // First, try to generate all at once with high token limit
    // If that fails or produces incomplete content, fall back to chapter-by-chapter

    const totalSections = Object.values(sections).reduce((sum, secs) => sum + secs.length, 0);
    const avgSectionsPerChapter = Math.round(totalSections / Math.max(chapters.length, 1));
    const bookTargets = resolveDepthTargets(book, config, chapters.length, avgSectionsPerChapter);
    const estimatedContentWords = bookTargets.totalChapterWords * chapters.length;
    // Use higher token limit for larger books
    const requiredTokens = Math.max(32000, Math.min(128000, Math.ceil(estimatedContentWords * 1.8)));
    console.log('[BookGenerator] Target words/chapter:', bookTargets.totalChapterWords,
      ', estimated book words:', estimatedContentWords, ', max tokens:', requiredTokens);

    let generatedData: any = null;
    let singlePassSuccess = false;

    // Single-pass is the fast path for a genuinely small book. Anything larger
    // goes straight to chapter-by-chapter: one response covering many chapters
    // is where the thin, summary-style output came from, because the model
    // rations its budget across every chapter at once. The threshold is the
    // book's own word target rather than a raw chapter count, so a short book
    // asked for at expert depth also gets the per-chapter treatment.
    const singlePassEligible =
      chapters.length <= 3 && totalSections <= 9 && estimatedContentWords <= 9000;

    if (singlePassEligible) {
      console.log('[BookGenerator] Attempting single-pass generation...');

      try {
        const result = await generateWithAI(userPrompt, systemPrompt, requiredTokens, 0.7, 'json');

        if (result && result.content) {
          console.log('[BookGenerator] Single-pass response length:', result.content.length);

          // Try to parse the JSON
          try {
            generatedData = JSON.parse(result.content);

            // Validate that we got all chapters, at the depth that was asked
            // for. The old bar was 200 characters — about thirty words — so a
            // book of one-paragraph chapters passed as a success and was
            // written to the database as final.
            if (generatedData?.chapters && Array.isArray(generatedData.chapters)) {
              const receivedChapters = generatedData.chapters.length;
              // Characters of prose a chapter should carry to count as written:
              // 60% of its word target, at ~6 characters per word of HTML.
              const minChapterChars = Math.round(bookTargets.totalChapterWords * 0.6 * 6);
              const validChapters = generatedData.chapters.filter((ch: any) => {
                const body = (ch?.content || '').length;
                const inSections = Array.isArray(ch?.sections)
                  ? ch.sections.reduce((sum: number, s: any) => sum + (s?.content || '').length, 0)
                  : 0;
                return body + inSections >= minChapterChars;
              }).length;

              console.log(`[BookGenerator] Single-pass: received ${receivedChapters}/${chapters.length} chapters, ${validChapters} at target depth (>=${minChapterChars} chars)`);

              // Every chapter must be present and written to depth — otherwise
              // fall through to chapter-by-chapter, which produces the full text.
              if (receivedChapters >= chapters.length && validChapters >= chapters.length) {
                singlePassSuccess = true;
                console.log('[BookGenerator] Single-pass generation successful');
              } else {
                console.log('[BookGenerator] Single-pass output too thin — regenerating chapter by chapter');
              }
            }
          } catch (parseError) {
            console.log('[BookGenerator] Single-pass JSON parse failed, will use chapter-by-chapter');
            generatedData = null;
          }
        }
      } catch (error: any) {
        console.log('[BookGenerator] Single-pass generation failed:', error.message);
        generatedData = null;
      }
    }

    // If single-pass didn't work or produced incomplete content, generate chapter by chapter
    if (!singlePassSuccess || !generatedData) {
      console.log('[BookGenerator] Using chapter-by-chapter generation for complete content...');

      onProgress(25, 'Generating chapter content...');

      // Digest of the chapter just written, so the next one continues the
      // argument instead of restarting it.
      let previousChapterSummary = '';
      // One expansion retry per chapter, and only for chapters that come back
      // far below target — bounded so a weak provider cannot double the run.
      let expansionRetriesLeft = Math.max(2, Math.ceil(chapters.length / 2));

      /** Written characters across a chapter's body and its sections. */
      const writtenChars = (data: any): number => {
        if (!data) return 0;
        const body = (data.content || '').length;
        const inSections = Array.isArray(data.sections)
          ? data.sections.reduce((sum: number, s: any) => sum + (s?.content || '').length, 0)
          : 0;
        return body + inSections;
      };

      const parseChapterResponse = (raw: string): any => {
        try {
          return JSON.parse(raw);
        } catch {
          const match = raw.match(/\{[\s\S]*"content"[\s\S]*"sections"[\s\S]*\}/);
          if (match) {
            try {
              return JSON.parse(match[0]);
            } catch {
              return null;
            }
          }
          return null;
        }
      };

      for (let chapterIndex = 0; chapterIndex < chapters.length; chapterIndex++) {
        const inputChapter = chapters[chapterIndex];
        const chapterId = inputChapter.id || inputChapter._id || '';
        const chapterTitle = inputChapter.title || `Chapter ${chapterIndex + 1}`;
        const chapterSections = chapterId ? (sections[chapterId] || []) : [];

        console.log(`[BookGenerator] Generating chapter ${chapterIndex + 1}/${chapters.length}: "${chapterTitle}"`);

        onProgress(25 + Math.round((chapterIndex / chapters.length) * 50),
          `Generating Chapter ${chapterIndex + 1}/${chapters.length}: ${chapterTitle}`);

        const chapterTargets = resolveDepthTargets(book, config, chapters.length, chapterSections.length);
        // Budget the response against what this chapter was actually asked for
        // (~1.6 tokens per word of HTML, plus JSON overhead), instead of a flat
        // 32k that under-serves a deep chapter and over-reserves a brief one.
        const chapterTokens = Math.max(
          16000,
          Math.min(64000, Math.ceil(chapterTargets.totalChapterWords * 2.4) + 4000)
        );

        // Build chapter-specific prompt - USE THE GENERATED PROMPT FROM AI PROMPT STEP
        const chapterPrompt = buildChapterPrompt(
          book,
          inputChapter,
          chapterSections,
          chapterIndex,
          chapters.length,
          config,
          generatedPrompt,  // Pass the comprehensive AI-generated prompt
          {
            previousTitle: chapterIndex > 0 ? (chapters[chapterIndex - 1]?.title || '') : undefined,
            previousSummary: previousChapterSummary || undefined,
            nextTitle: chapterIndex < chapters.length - 1 ? (chapters[chapterIndex + 1]?.title || '') : undefined,
          }
        );

        // Use the same system prompt from the AI Prompt step
        const chapterSystemPrompt = systemPrompt;

        try {
          const chapterResult = await generateWithAI(
            chapterPrompt,
            chapterSystemPrompt,
            chapterTokens,
            0.7,
            'json'
          );

          if (chapterResult && chapterResult.content) {
            let chapterData: any = parseChapterResponse(chapterResult.content);
            if (!chapterData) {
              console.warn(`[BookGenerator] Failed to parse chapter ${chapterIndex + 1} response`);
            }

            // A chapter that lands far below its target is a thin chapter, not
            // a stylistic choice. Ask once for the missing depth rather than
            // saving a summary as the finished text.
            const targetChars = chapterTargets.totalChapterWords * 6;
            if (chapterData && expansionRetriesLeft > 0 && writtenChars(chapterData) < targetChars * 0.5) {
              console.warn(`[BookGenerator] Chapter ${chapterIndex + 1} came back at ${writtenChars(chapterData)} chars vs ~${targetChars} target — requesting expansion`);
              expansionRetriesLeft--;
              try {
                const expandResult = await generateWithAI(
                  `${chapterPrompt}

---

## REVISION REQUIRED

A first draft of this chapter came back at roughly ${Math.round(writtenChars(chapterData) / 6).toLocaleString('en-US')} words, well short of the ~${chapterTargets.totalChapterWords.toLocaleString('en-US')} words this chapter needs.

Rewrite it in full at the required depth. Add the missing development — further subheadings, worked examples, practical application, considerations and trade-offs — rather than restating what the draft already says at greater length. Return the same JSON structure.

Draft to replace:
${JSON.stringify(chapterData).slice(0, 6000)}`,
                  chapterSystemPrompt,
                  chapterTokens,
                  0.7,
                  'json'
                );
                const expanded = expandResult?.content ? parseChapterResponse(expandResult.content) : null;
                // Keep the expansion only if it genuinely added material.
                if (expanded && writtenChars(expanded) > writtenChars(chapterData)) {
                  chapterData = expanded;
                  console.log(`[BookGenerator] Chapter ${chapterIndex + 1} expanded to ${writtenChars(expanded)} chars`);
                }
              } catch (expandError: any) {
                console.warn(`[BookGenerator] Expansion for chapter ${chapterIndex + 1} failed: ${expandError.message}`);
              }
            }

            if (chapterData) {
              generatedChapterByIndex[chapterIndex] = chapterData;

              if (chapterId) {
                generatedChapterMap[chapterId] = chapterData;
                generatedSectionMaps[chapterId] = {};

                // Map sections by ID or index
                if (chapterData.sections && Array.isArray(chapterData.sections)) {
                  for (let secIndex = 0; secIndex < chapterData.sections.length; secIndex++) {
                    const sec = chapterData.sections[secIndex];
                    const secId = sec.id || (chapterSections[secIndex] && (chapterSections[secIndex].id || chapterSections[secIndex]._id));
                    if (secId) {
                      generatedSectionMaps[chapterId][secId] = sec;
                    }
                  }
                }
              }

              previousChapterSummary = summarizeChapterForContinuity(chapterTitle, chapterData);

              console.log(`[BookGenerator] Chapter ${chapterIndex + 1} generated: content length ${chapterData.content?.length || 0}, sections ${chapterData.sections?.length || 0}, total ${writtenChars(chapterData)} chars (~${chapterTargets.totalChapterWords} words target)`);
            }
          }
        } catch (error: any) {
          console.error(`[BookGenerator] Failed to generate chapter ${chapterIndex + 1}:`, error.message);
          // Continue with other chapters
        }
      }
    } else {
      // Process single-pass results
      if (generatedData?.chapters && Array.isArray(generatedData.chapters)) {
        for (let i = 0; i < generatedData.chapters.length; i++) {
          const genChapter = generatedData.chapters[i];
          generatedChapterByIndex[i] = genChapter;

          const inputChapter = chapters[i];
          const chapterId = inputChapter?.id || inputChapter?._id;

          if (chapterId) {
            generatedChapterMap[chapterId] = genChapter;
            generatedSectionMaps[chapterId] = {};

            const chapterSections = sections[chapterId] || [];
            if (genChapter.sections && Array.isArray(genChapter.sections)) {
              for (let j = 0; j < genChapter.sections.length; j++) {
                const sec = genChapter.sections[j];
                const secId = sec.id || (chapterSections[j] && (chapterSections[j].id || chapterSections[j]._id));
                if (secId) {
                  generatedSectionMaps[chapterId][secId] = sec;
                }
              }
            }
          }
        }
      }
    }

    onProgress(80, 'Processing chapters and sections...');

    // Get models for database updates
    const { BookChapter, BookSection } = models;

    // Update chapters and sections in database
    const updatedChapters: any[] = [];
    const updatedSections: Record<string, any[]> = {};

    // Process ALL input chapters, not just the ones returned by AI
    for (let i = 0; i < chapters.length; i++) {
      const inputChapter = chapters[i];
      const chapterId = inputChapter.id || inputChapter._id;

      if (!chapterId) {
        console.warn(`[BookGenerator] No chapter ID for chapter ${i}, skipping`);
        continue;
      }

      // Get AI-generated content - try ID lookup first, then fall back to index
      let genChapter = generatedChapterMap[chapterId];

      // If not found by ID, try to get by index position
      if (!genChapter && generatedChapterByIndex[i]) {
        genChapter = generatedChapterByIndex[i];
        console.log(`[BookGenerator] Chapter ${i + 1}: Using index-based fallback (ID "${chapterId}" not in generated data)`);
      }

      console.log(`[BookGenerator] Chapter ${i + 1} (${chapterId}):`, {
        title: inputChapter.title || 'Untitled',
        hasGeneratedContent: !!genChapter,
        contentLength: genChapter?.content?.length || 0,
        sectionsCount: genChapter?.sections?.length || 0,
        inputSectionsCount: (sections[chapterId] || []).length,
      });

      try {
        // Update chapter in database with AI content if available
        if (genChapter?.content) {
          await BookChapter.findByIdAndUpdate(chapterId, {
            content: genChapter.content,
            status: 'final',
            aiGenerated: true,
          });
        }

        // Build updated chapter with all fields
        const updatedChapter = {
          ...inputChapter,
          id: chapterId,
          _id: chapterId,
          title: inputChapter.title || genChapter?.title || 'Untitled',
          description: inputChapter.description || '',
          content: genChapter?.content || inputChapter.content || '',
          learningObjectives: inputChapter.learningObjectives || [],
          keyTakeaways: inputChapter.keyTakeaways || [],
          status: genChapter ? 'final' : inputChapter.status || 'draft',
        };
        updatedChapters.push(updatedChapter);

        // Process sections for this chapter
        const chapterSections = sections[chapterId] || [];
        updatedSections[chapterId] = [];

        // Create a map of generated sections and index-based array
        const generatedSectionMap: Record<string, any> = generatedSectionMaps[chapterId] || {};
        const generatedSectionByIndex: any[] = [];
        if (genChapter?.sections && Array.isArray(genChapter.sections)) {
          for (let j = 0; j < genChapter.sections.length; j++) {
            const genSection = genChapter.sections[j];
            generatedSectionByIndex.push(genSection);

            const inputSection = chapterSections[j];
            const sectionId = genSection.id || (inputSection && (inputSection.id || inputSection._id));
            if (sectionId && !generatedSectionMap[sectionId]) {
              generatedSectionMap[sectionId] = genSection;
            }
          }
        }

        // Process ALL input sections for this chapter
        for (let j = 0; j < chapterSections.length; j++) {
          const inputSection = chapterSections[j];
          const sectionId = inputSection.id || inputSection._id;

          if (!sectionId) {
            console.warn(`[BookGenerator] No section ID for section ${j} in chapter ${chapterId}, skipping`);
            continue;
          }

          // Get AI-generated content - try ID lookup first, then fall back to index
          let genSection = generatedSectionMap[sectionId];
          if (!genSection && generatedSectionByIndex[j]) {
            genSection = generatedSectionByIndex[j];
            console.log(`[BookGenerator]   Section ${j + 1}: Using index-based fallback`);
          }

          console.log(`[BookGenerator]   Section ${j + 1} (${sectionId}):`, {
            title: inputSection.title || 'Untitled',
            hasGeneratedContent: !!genSection,
            contentLength: genSection?.content?.length || 0,
          });

          // Update section in database with AI content if available
          if (genSection?.content) {
            await BookSection.findByIdAndUpdate(sectionId, {
              content: genSection.content,
              images: genSection.images || [],
              status: 'final',
              aiGenerated: true,
            });
          }

          // Build updated section
          const updatedSection = {
            ...inputSection,
            id: sectionId,
            _id: sectionId,
            title: inputSection.title || genSection?.title || 'Untitled',
            content: genSection?.content || inputSection.content || '',
            keyPoints: genSection?.keyPoints || inputSection.keyPoints || [],
            examples: genSection?.examples || inputSection.examples || [],
            images: genSection?.images || [],
            status: genSection ? 'final' : inputSection.status || 'draft',
          };
          updatedSections[chapterId].push(updatedSection);
        }
      } catch (err: any) {
        console.warn('[BookGenerator] Failed to update chapter:', chapterId, err.message);
      }
    }

    // Log final counts
    console.log('[BookGenerator] Updated chapters:', updatedChapters.length);
    console.log('[BookGenerator] Total sections:', Object.values(updatedSections).reduce((sum, secs) => sum + secs.length, 0));
    console.log('[BookGenerator] Sample chapter content length:', updatedChapters[0]?.content?.length || 0);

    // Validate all chapters have content - generate placeholder if missing
    let missingContentCount = 0;
    for (let i = 0; i < updatedChapters.length; i++) {
      const chapter = updatedChapters[i];
      if (!chapter.content || chapter.content.trim().length < 100) {
        missingContentCount++;
        console.warn(`[BookGenerator] Chapter ${i + 1} "${chapter.title}" has insufficient content (${chapter.content?.length || 0} chars)`);

        // Generate placeholder content for chapters without AI content
        const chapterTitle = chapter.title || `Chapter ${i + 1}`;
        const learningObjectives = chapter.learningObjectives || [];
        const keyTakeaways = chapter.keyTakeaways || [];

        chapter.content = `
          <h2>${chapterTitle}</h2>
          <p><em>Note: AI-generated content for this chapter was not received. This is placeholder content that should be replaced.</em></p>

          ${chapter.description ? `<p><strong>Description:</strong> ${chapter.description}</p>` : ''}

          ${learningObjectives.length > 0 ? `
            <h3>Learning Objectives</h3>
            <ul>
              ${learningObjectives.map((obj: string) => `<li>${obj}</li>`).join('')}
            </ul>
          ` : ''}

          <p>Content for this chapter will be added after regeneration or manual editing.</p>

          ${keyTakeaways.length > 0 ? `
            <h3>Key Takeaways</h3>
            <ul>
              ${keyTakeaways.map((kt: string) => `<li>${kt}</li>`).join('')}
            </ul>
          ` : ''}
        `.trim();
        console.log(`[BookGenerator] Added placeholder content for chapter ${i + 1}`);
      }
    }

    if (missingContentCount > 0) {
      console.warn(`[BookGenerator] WARNING: ${missingContentCount} chapters have missing/incomplete content. Consider regenerating with higher token limit or in smaller batches.`);
    }

    // Validate all sections have content
    let missingSectionCount = 0;
    for (const chapterId of Object.keys(updatedSections)) {
      const chapterSections = updatedSections[chapterId];
      for (let j = 0; j < chapterSections.length; j++) {
        const section = chapterSections[j];
        if (!section.content || section.content.trim().length < 50) {
          missingSectionCount++;
          console.warn(`[BookGenerator] Section "${section.title}" in chapter has insufficient content (${section.content?.length || 0} chars)`);

          // Generate placeholder content for sections without AI content
          section.content = `
            <h3>${section.title || 'Section'}</h3>
            <p><em>Note: AI-generated content for this section was not received. This is placeholder content.</em></p>
            ${section.keyPoints?.length > 0 ? `
              <h4>Key Points</h4>
              <ul>
                ${section.keyPoints.map((kp: string) => `<li>${kp}</li>`).join('')}
              </ul>
            ` : ''}
            <p>Content for this section will be added after regeneration or manual editing.</p>
          `.trim();
        }
      }
    }

    if (missingSectionCount > 0) {
      console.warn(`[BookGenerator] WARNING: ${missingSectionCount} sections have missing/incomplete content.`);
    }

    onProgress(95, 'Generating complete book HTML...');

    // Generate complete HTML book
    const html = generateBookHTML(book, updatedChapters, updatedSections, config);
    const htmlPath = getHtmlPath(bookId);
    fs.writeFileSync(htmlPath, html, 'utf-8');
    console.log(`[BookGenerator] Generated HTML file: ${htmlPath}`);

    onProgress(98, 'Finalizing...');

    // Write metadata
    writeMetadata(bookId, {
      status: 'completed',
      jobId,
      generatedAt: new Date().toISOString(),
      updatedAt: new Date().toISOString(),
      chaptersGenerated: updatedChapters.length,
      sectionsGenerated: Object.values(updatedSections).reduce((sum, secs) => sum + secs.length, 0),
      bookId,
      companyId,
      htmlPath,
    });

    // Update book status
    await updateGeneratedContentStatus(bookId, {
      status: 'completed',
      jobId,
      generatedAt: new Date().toISOString(),
      chapters: updatedChapters,
      sections: updatedSections,
    });

    console.log(`[BookGenerator] Generation completed for book: ${bookId}`);

    return {
      chapters: updatedChapters,
      sections: updatedSections,
      html,
    };

  } catch (error: any) {
    console.error('[BookGenerator] Generation failed for book:', bookId, error);

    // Mark as failed
    writeMetadata(bookId, {
      status: 'failed',
      jobId,
      error: error.message || 'Generation failed',
      updatedAt: new Date().toISOString(),
    });

    await updateGeneratedContentStatus(bookId, {
      status: 'failed',
      jobId,
      error: error.message || 'Generation failed',
    });

    throw error;
  }
}

// ============================================
// GENERATE ENDPOINT
// ============================================

router.post('/generate', authenticate, requirePermission('books', 'ai-generate'), async (req: Request, res: Response) => {
  const { bookId, companyId, config, chapters, sections, generatedPrompt } = req.body;

  // Debug logging
  console.log('[BookGenerator] === GENERATE REQUEST ===');
  console.log('[BookGenerator] bookId:', bookId);
  console.log('[BookGenerator] companyId:', companyId);
  console.log('[BookGenerator] generatedPrompt length:', generatedPrompt?.length || 0);
  console.log('[BookGenerator] chapters count:', chapters?.length || 0);
  console.log('[BookGenerator] sections keys:', sections ? Object.keys(sections) : 'none');

  if (chapters && chapters.length > 0) {
    console.log('[BookGenerator] First chapter:', {
      id: chapters[0].id || chapters[0]._id,
      title: chapters[0].title,
      hasContent: !!chapters[0].content,
    });
  }

  if (sections && Object.keys(sections).length > 0) {
    const firstKey = Object.keys(sections)[0];
    console.log('[BookGenerator] First sections key:', firstKey, 'count:', sections[firstKey]?.length || 0);
  }

  if (!bookId || !companyId) {
    res.status(400).json({ error: 'bookId and companyId are required' });
    return;
  }

  try {
    const { Book } = getModels();
    const book = await Book.findById(bookId);

    if (!book) {
      res.status(404).json({ error: 'Book not found' });
      return;
    }

    // Check for existing generation status
    const existingContent = book.generatedContent as any;
    if (existingContent?.status === 'generating' && existingContent.jobId) {
      const existingJob = getJob(existingContent.jobId);
      if (existingJob && existingJob.status === 'processing') {
        res.json({ jobId: existingContent.jobId, status: 'processing', message: 'Generation already in progress' });
        return;
      }
      // Stale job — mark as failed and allow retry
      await updateGeneratedContentStatus(bookId, {
        status: 'failed',
        jobId: existingContent.jobId,
        error: 'Generation was interrupted (server may have restarted)',
      });
      writeMetadata(bookId, {
        status: 'failed',
        jobId: existingContent.jobId,
        error: 'Generation was interrupted',
        updatedAt: new Date().toISOString(),
      });
    }

    // Layout & formatting come from the wizard's AI Prompt step, but that step
    // only reports them while it is mounted — and in the Edit flow every step is
    // already complete, so the user can jump straight to Generate without ever
    // opening it. Fall back to what was stored on the book last time, then to
    // the defaults, so a book is never generated with an empty layout config.
    const storedConfig = (book.generationConfig as any) || null;
    const effectiveConfig = withLayoutDefaults(config, storedConfig);
    const effectivePrompt =
      (typeof generatedPrompt === 'string' && generatedPrompt.trim())
        ? generatedPrompt
        : (typeof book.aiPrompt === 'string' && book.aiPrompt.trim() ? book.aiPrompt : undefined);

    console.log('[BookGenerator] Layout config source:',
      config?.formatting ? 'request' : storedConfig?.formatting ? 'stored on book' : 'defaults',
      '| prompt source:', generatedPrompt?.trim() ? 'request' : book.aiPrompt ? 'stored on book' : 'none');

    // Remember this run's prompt and layout choices so the next Edit-flow
    // generation reproduces the same book rather than a default-styled one.
    try {
      if (effectivePrompt) book.aiPrompt = effectivePrompt;
      book.generationConfig = effectiveConfig;
      // Mixed paths are not change-tracked by Mongoose without this.
      book.markModified?.('generationConfig');
      await book.save();
    } catch (persistError: any) {
      // Non-fatal: generation still runs with the resolved config in memory.
      console.warn('[BookGenerator] Could not persist generation config:', persistError.message);
    }

    // Create job
    const job = createJob('book-generator', companyId, bookId);
    updateJobProgress(job.jobId, 5, 'Initializing book generation...');

    res.json({ jobId: job.jobId, status: 'processing' });

    // Start generation in background
    setImmediate(async () => {
      try {
        await generateBookContentCore(
          book.toObject(),
          chapters || [],
          sections || {},
          effectiveConfig,
          companyId,
          (progress, step) => updateJobProgress(job.jobId, progress, step),
          job.jobId,
          effectivePrompt, // Pass the generated prompt from the AI Prompt step
        );

        completeJob(job.jobId, {
          bookId,
          generated: true,
        }, 'book-generator');

        console.log(`[BookGenerator] Job ${job.jobId} completed for book: ${bookId}`);
      } catch (err: any) {
        console.error(`[BookGenerator] Job ${job.jobId} failed:`, err.message);
        failJob(job.jobId, err.message || 'Generation failed');
      }
    });

  } catch (error: any) {
    console.error('[BookGenerator] Error:', error);
    res.status(500).json({ error: error.message || 'Failed to start generation' });
  }
});

// ============================================
// STATUS ENDPOINT
// ============================================

router.get('/status/:jobId', authenticate, async (req: Request, res: Response) => {
  const { jobId } = req.params;
  const job = getJob(jobId);

  if (!job) {
    res.status(404).json({ error: 'Job not found' });
    return;
  }

  res.json({
    jobId: job.jobId,
    status: job.status,
    progress: job.progress,
    step: job.step,
    result: job.result,
    error: job.error,
  });
});

// ============================================
// GENERATED CONTENT STATUS
// ============================================

router.get('/generated-content/:bookId', authenticate, async (req: Request, res: Response) => {
  const { bookId } = req.params;
  const companyId = (req.query.companyId as string) || '';

  // Try filesystem metadata first
  const metadata = readMetadata(bookId);
  if (metadata) {
    if (metadata.status === 'completed') {
      const htmlPath = getHtmlPath(bookId);
      if (!fs.existsSync(htmlPath)) {
        res.json({
          status: 'failed',
          error: 'Generated content not found. Please regenerate.',
          jobId: metadata.jobId,
          updatedAt: metadata.updatedAt,
        });
        return;
      }
    }
    res.json({
      ...metadata,
      previewUrl: metadata.status === 'completed' ? `/book-generator/preview/${bookId}` : null,
      downloadUrl: metadata.status === 'completed' ? `/book-generator/download/${bookId}` : null,
    });
    return;
  }

  // Fall back to MongoDB
  try {
    const { Book } = getModels();
    const book = await Book.findById(bookId);
    if (book?.generatedContent) {
      const gc = book.generatedContent as any;
      res.json({
        status: gc.status || 'none',
        jobId: gc.jobId,
        generatedAt: gc.generatedAt,
        updatedAt: gc.updatedAt,
        error: gc.error,
        previewUrl: gc.status === 'completed' ? `/book-generator/preview/${bookId}` : null,
        downloadUrl: gc.status === 'completed' ? `/book-generator/download/${bookId}` : null,
      });
      return;
    }
  } catch (err: any) {
    console.error('[BookGenerator] Error reading from MongoDB:', err.message);
  }

  res.json({ status: 'none' });
});

// ============================================
// PREVIEW ENDPOINT (serves HTML)
// ============================================

router.get('/preview/:bookId', async (req: Request, res: Response) => {
  const { bookId } = req.params;
  const htmlPath = getHtmlPath(bookId);

  if (!fs.existsSync(htmlPath)) {
    res.status(404).send('<h1>Preview not available</h1><p>No generated content found for this book.</p>');
    return;
  }

  // Set permissive CSP for inline styles
  res.removeHeader('Content-Security-Policy');
  res.setHeader('Content-Security-Policy',
    "default-src 'none'; " +
    "style-src 'unsafe-inline' 'self' https://fonts.googleapis.com; " +
    "font-src 'self' https://fonts.gstatic.com; " +
    "img-src * data: blob:; " +
    "script-src 'unsafe-inline' 'self';"
  );

  res.sendFile(htmlPath);
});

// ============================================
// DOWNLOAD ENDPOINT
// ============================================

router.get('/download/:bookId', async (req: Request, res: Response) => {
  const { bookId } = req.params;
  const format = (req.query.format as string) || 'html';
  const bookDir = getBookDir(bookId);

  if (!fs.existsSync(bookDir)) {
    res.status(404).json({ error: 'No generated content found for this book' });
    return;
  }

  const { Book } = getModels();
  const book = await Book.findById(bookId);
  const bookTitle = book?.title || 'book';
  const bookData = book?.toObject() as any;

  if (format === 'pdf') {
    const htmlPath = getHtmlPath(bookId);
    if (!fs.existsSync(htmlPath)) {
      res.status(404).json({ error: 'No generated content found' });
      return;
    }

    const pdfPath = path.join(bookDir, 'book.pdf');

    try {
      // Typeset the same HTML the preview renders, so the download keeps the
      // book's structure — chapter breaks, headings, lists, quotes, folios.
      const model = parseGeneratedBookHtml(fs.readFileSync(htmlPath, 'utf-8'), bookData);
      await renderBookPdf(model, pdfPath);
      res.download(pdfPath, `${bookTitle}.pdf`);
      return;
    } catch (error: any) {
      console.error('[BookGenerator] Typeset PDF failed, falling back to plain layout:', error);
    }

    try {
      await generatePdfFromHtml(htmlPath, pdfPath, bookData);
      res.download(pdfPath, `${bookTitle}.pdf`);
      return;
    } catch (error: any) {
      console.error('[BookGenerator] PDF generation error:', error);
      res.status(500).json({ error: 'Failed to generate PDF. Please try downloading as HTML or DOCX.' });
      return;
    }
  }

  if (format === 'docx') {
    const docxPath = getDocxPath(bookId);
    const htmlPath = getHtmlPath(bookId);

    if (!fs.existsSync(htmlPath)) {
      res.status(404).json({ error: 'No generated content found' });
      return;
    }

    try {
      const model = parseGeneratedBookHtml(fs.readFileSync(htmlPath, 'utf-8'), bookData);
      await renderBookDocx(model, docxPath);
      res.download(docxPath, `${bookTitle}.docx`);
      return;
    } catch (error: any) {
      console.error('[BookGenerator] Typeset DOCX failed, falling back to plain layout:', error);
    }

    try {
      await generateDocxFromHtml(htmlPath, docxPath, bookData);
      res.download(docxPath, `${bookTitle}.docx`);
      return;
    } catch (error: any) {
      console.error('[BookGenerator] DOCX generation error:', error);
      // Fall back to HTML if DOCX generation fails
    }
  }

  // Default: serve HTML file
  const htmlPath = getHtmlPath(bookId);
  if (fs.existsSync(htmlPath)) {
    res.download(htmlPath, `${bookTitle}.html`);
    return;
  }

  res.status(404).json({ error: 'No generated file found' });
});

// ============================================
// PDF GENERATION FUNCTION
// ============================================

async function generatePdfFromHtml(htmlPath: string, pdfPath: string, book: any): Promise<void> {
  return new Promise((resolve, reject) => {
    try {
      const htmlContent = fs.readFileSync(htmlPath, 'utf-8');

      // Extract text content from HTML for PDF
      const textContent = htmlToText(htmlContent);

      // Create PDF document
      const doc = new PDFDocument({
        size: 'A4',
        margins: { top: 72, bottom: 72, left: 72, right: 72 },
        info: {
          Title: book?.title || 'Book',
          Author: book?.authors?.map((a: any) => a.name || a).join(', ') || 'Unknown Author',
        },
      });

      const stream = fs.createWriteStream(pdfPath);
      doc.pipe(stream);

      // Cover page
      doc.fontSize(24).font('Helvetica-Bold').text(book?.title || 'Untitled', { align: 'center' });
      doc.moveDown();
      if (book?.subtitle) {
        doc.fontSize(16).font('Helvetica').text(book.subtitle, { align: 'center' });
        doc.moveDown();
      }
      if (book?.authors && book.authors.length > 0) {
        doc.fontSize(12).text(`by ${book.authors.map((a: any) => a.name || a).join(', ')}`, { align: 'center' });
      }
      doc.addPage();

      // Add text content
      const lines = textContent.split('\n');
      let inChapter = false;
      // The line after "Chapter N" is that chapter's title — set by the header
      // block — and belongs in the heading, not in the body text.
      let expectChapterTitle = false;

      for (const line of lines) {
        const trimmedLine = line.trim();
        if (!trimmedLine) {
          doc.moveDown(0.5);
          continue;
        }

        // Chapter numbers
        if (trimmedLine.startsWith('Chapter ') || trimmedLine.match(/^[IVX]+\.\s/)) {
          if (inChapter) doc.addPage();
          inChapter = true;
          expectChapterTitle = true;
          doc.fontSize(18).font('Helvetica-Bold').text(trimmedLine, { align: 'center' });
          doc.moveDown(0.5);
          continue;
        }

        // Chapter title
        if (expectChapterTitle) {
          expectChapterTitle = false;
          doc.fontSize(16).font('Helvetica-Bold').text(trimmedLine, { align: 'center' });
          doc.moveDown();
          continue;
        }

        // Section titles
        if (trimmedLine.match(/^[A-Z][a-z]+(\s+[A-Z][a-z]+)*:$/)) {
          doc.fontSize(14).font('Helvetica-Bold').text(trimmedLine);
          doc.moveDown(0.5);
          continue;
        }

        // Regular text
        doc.fontSize(11).font('Helvetica').text(trimmedLine, { align: 'justify' });
      }

      doc.end();

      stream.on('finish', () => resolve());
      stream.on('error', (err) => reject(err));
    } catch (error) {
      reject(error);
    }
  });
}

// Simple HTML to text converter
function htmlToText(html: string): string {
  return html
    .replace(/<style[^>]*>[\s\S]*?<\/style>/gi, '')
    .replace(/<script[^>]*>[\s\S]*?<\/script>/gi, '')
    .replace(/<head[^>]*>[\s\S]*?<\/head>/gi, '')
    .replace(/<nav[^>]*>[\s\S]*?<\/nav>/gi, '')
    // NOT <header>: every chapter opens with <header class="chapter-header">
    // carrying "Chapter N" and the title. Dropping those left the PDF with no
    // chapter titles — and, because the page-break rule below looks for a line
    // starting with "Chapter ", no chapter breaks either.
    .replace(/<footer[^>]*>[\s\S]*?<\/footer>/gi, '')
    .replace(/<br\s*\/?>/gi, '\n')
    .replace(/<\/p>/gi, '\n\n')
    .replace(/<\/div>/gi, '\n')
    .replace(/<\/h[1-6]>/gi, '\n\n')
    .replace(/<\/li>/gi, '\n')
    .replace(/<li[^>]*>/gi, '• ')
    .replace(/<[^>]+>/g, '')
    .replace(/&nbsp;/g, ' ')
    .replace(/&amp;/g, '&')
    .replace(/&lt;/g, '<')
    .replace(/&gt;/g, '>')
    .replace(/&quot;/g, '"')
    .replace(/&#39;/g, "'")
    .replace(/\n{3,}/g, '\n\n')
    .replace(/[ \t]+/g, ' ')
    .trim();
}

// ============================================
// DOCX GENERATION FUNCTION
// ============================================

async function generateDocxFromHtml(htmlPath: string, docxPath: string, book: any): Promise<void> {
  const htmlContent = fs.readFileSync(htmlPath, 'utf-8');
  const textContent = htmlToText(htmlContent);

  // Create document
  const doc = new Document({
    sections: [{
      properties: {
        page: {
          size: { width: 12240, height: 15840 }, // US Letter
          margin: { top: 1440, bottom: 1440, left: 1440, right: 1440 },
        },
      },
      children: [
        // Title
        new Paragraph({
          children: [
            new TextRun({
              text: book?.title || 'Untitled',
              bold: true,
              size: 48,
            }),
          ],
          alignment: AlignmentType.CENTER,
          spacing: { after: 400 },
        }),
        // Subtitle
        ...(book?.subtitle ? [
          new Paragraph({
            children: [
              new TextRun({
                text: book.subtitle,
                size: 28,
                italics: true,
              }),
            ],
            alignment: AlignmentType.CENTER,
            spacing: { after: 400 },
          }),
        ] : []),
        // Authors
        ...(book?.authors && book.authors.length > 0 ? [
          new Paragraph({
            children: [
              new TextRun({
                text: `by ${book.authors.map((a: any) => a.name || a).join(', ')}`,
                size: 24,
              }),
            ],
            alignment: AlignmentType.CENTER,
            spacing: { after: 600 },
          }),
        ] : []),
        // Page break before content
        new Paragraph({
          children: [new PageBreak()],
        }),
        // Content paragraphs
        ...textContent.split('\n\n').map(block => {
          const trimmed = block.trim();
          if (!trimmed) return new Paragraph({ children: [] });

          // Check if it's a heading
          if (trimmed.startsWith('Chapter ') || trimmed.match(/^[IVX]+\.\s/)) {
            return new Paragraph({
              children: [
                new TextRun({
                  text: trimmed,
                  bold: true,
                  size: 32,
                }),
              ],
              heading: HeadingLevel.HEADING_1,
              spacing: { before: 400, after: 200 },
            });
          }

          // Regular paragraph
          return new Paragraph({
            children: [
              new TextRun({
                text: trimmed,
                size: 24,
              }),
            ],
            spacing: { after: 200 },
          });
        }),
      ],
    }],
  });

  // Generate buffer and save
  const buffer = await Packer.toBuffer(doc);
  fs.writeFileSync(docxPath, buffer);
}

// ============================================
// DELETE GENERATED CONTENT (for retry)
// ============================================

router.delete('/generated-content/:bookId', authenticate, async (req: Request, res: Response) => {
  const { bookId } = req.params;
  const companyId = (req.query.companyId as string) || '';

  // Delete filesystem directory
  const bookDir = getBookDir(bookId);
  if (fs.existsSync(bookDir)) {
    fs.rmSync(bookDir, { recursive: true, force: true });
  }

  // Clear MongoDB status
  try {
    const { Book } = getModels();
    const book = await Book.findById(bookId);
    if (book) {
      book.generatedContent = undefined;
      await book.save();
    }
  } catch (err: any) {
    console.error('[BookGenerator] Error clearing MongoDB status:', err.message);
  }

  res.json({ success: true, message: 'Generated content cleared' });
});

export default router;