/**
 * Presentation Content Generator Routes
 *
 * Generate complete presentation content with AI, including slides, speaker notes, and visual design.
 * Similar pattern to Book Generator - async job-based generation with
 * MongoDB persistence for generated content.
 *
 * POST /generate — start async presentation content generation
 * GET /status/:jobId — poll job status
 * GET /generated-content/:presentationId — get persisted generation status & metadata
 * GET /preview/:presentationId — preview generated HTML content
 * GET /download/:presentationId — download generated presentation (HTML, PDF, or PPTX)
 * DELETE /generated-content/:presentationId — 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 { buildLanguageInstruction } from '../services/aiContext/prPrompts';
import fs from 'fs';
import path from 'path';
import PDFDocument from 'pdfkit';
import { Document, Packer, Paragraph, TextRun, HeadingLevel, AlignmentType, PageBreak, BorderStyle } from 'docx';

const router = express.Router();

// ============================================
// TYPES
// ============================================

interface PresentationGenerationConfig {
  visualIdentity: {
    mode: string;
    primaryColor?: string;
    secondaryColor?: string;
    accentColor?: string;
    headingFont?: string;
    bodyFont?: string;
    visualVibe?: string;
  };
  presentationType: string;
  templateStyle: string;
  toneOfVoice: string;
  language?: string;
  contentDensity: string;
  visualDesign: {
    animationStyle: string;
    transitionStyle: string;
    includeIcons: boolean;
    includeCharts: boolean;
    includeDiagrams: boolean;
    customDesignInstructions: string;
  };
  slideFormatting: {
    maxBulletsPerSlide: number;
    textDensity: string;
    includeSpeakerNotes: boolean;
    includeCtaSlides: boolean;
    includeTransitionSlides: boolean;
    bulletStyle: string;
  };
  aiInstructions: {
    systemInstructions: string;
    writingConstraints: string;
    dosDonts: string;
    additionalGuidance: string;
  };
}

// ============================================
// FILE PATH HELPERS
// ============================================

function getPresentationDir(id: string): string {
  return path.join(process.cwd(), 'uploads', 'presentations', id);
}

function getHtmlPath(id: string): string {
  return path.join(getPresentationDir(id), 'index.html');
}

function writeMetadata(id: string, metadata: Record<string, any>): void {
  const dir = getPresentationDir(id);
  fs.mkdirSync(dir, { recursive: true });
  fs.writeFileSync(path.join(dir, 'metadata.json'), JSON.stringify(metadata, null, 2), 'utf-8');
}

function readMetadata(id: string): Record<string, any> | null {
  const metaPath = path.join(getPresentationDir(id), 'metadata.json');
  if (!fs.existsSync(metaPath)) return null;
  try {
    return JSON.parse(fs.readFileSync(metaPath, 'utf-8'));
  } catch {
    return null;
  }
}

// ============================================
// HTML PRESENTATION GENERATION
// ============================================

function generatePresentationHTML(
  presentation: any,
  slides: any[],
  briefContent: string,
  config: PresentationGenerationConfig
): string {
  const visualIdentity = config.visualIdentity || {} as any;
  const primaryColor = visualIdentity.primaryColor || '#C8FF2E';
  const secondaryColor = visualIdentity.secondaryColor || '#1a1a2e';
  const accentColor = visualIdentity.accentColor || '#7C6BF0';
  const headingFont = visualIdentity.headingFont || "'Inter', 'Segoe UI', sans-serif";
  const bodyFont = visualIdentity.bodyFont || "'Inter', 'Segoe UI', sans-serif";

  const safeSlides = Array.isArray(slides) ? slides : [];

  console.log(`[PresentationGenerator] Generating HTML for presentation: ${presentation.title || 'Untitled'}`);
  console.log(`[PresentationGenerator] Slides count: ${safeSlides.length}`);

  return `<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>${escapeHtml(presentation.title || 'Untitled Presentation')}</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;500;600;700&family=${encodeURIComponent(bodyFont.split(',')[0].replace(/['"]/g, ''))}:wght@400;500;600&display=swap" rel="stylesheet">
  <style>
    :root {
      --primary-color: ${primaryColor};
      --secondary-color: ${secondaryColor};
      --accent-color: ${accentColor};
      --text-color: #1a1a1a;
      --text-light: #4a4a4a;
      --text-white: #ffffff;
      --bg-color: #ffffff;
      --bg-alt: #f8f8f8;
      --bg-dark: ${secondaryColor};
      --border-color: #e0e0e0;
      --heading-font: ${headingFont};
      --body-font: ${bodyFont};
    }

    * { margin: 0; padding: 0; box-sizing: border-box; }

    body {
      font-family: var(--body-font);
      background: #0a0a1a;
      color: var(--text-color);
      overflow: hidden;
      height: 100vh;
    }

    /* ============================================
       LAYOUT: Sidebar + Main
       ============================================ */

    .presentation-container {
      display: flex;
      height: 100vh;
      width: 100vw;
    }

    /* Left Sidebar Navigation */
    .slide-nav {
      width: 220px;
      background: #111122;
      border-right: 1px solid rgba(255,255,255,0.08);
      display: flex;
      flex-direction: column;
      overflow-y: auto;
      flex-shrink: 0;
    }

    .slide-nav-header {
      padding: 1rem 1rem 0.75rem;
      border-bottom: 1px solid rgba(255,255,255,0.06);
    }

    .slide-nav-header h2 {
      font-family: var(--heading-font);
      font-size: 0.75rem;
      text-transform: uppercase;
      letter-spacing: 0.15em;
      color: var(--primary-color);
      margin-bottom: 0.25rem;
    }

    .slide-nav-header p {
      font-size: 0.7rem;
      color: #666;
    }

    .slide-nav-list {
      flex: 1;
      overflow-y: auto;
      padding: 0.5rem 0;
    }

    .slide-nav-item {
      display: flex;
      align-items: center;
      gap: 0.5rem;
      padding: 0.5rem 1rem;
      cursor: pointer;
      transition: all 0.2s ease;
      border-left: 3px solid transparent;
      font-size: 0.8rem;
      color: #999;
    }

    .slide-nav-item:hover {
      background: rgba(255,255,255,0.04);
      color: #ccc;
    }

    .slide-nav-item.active {
      background: rgba(200,255,46,0.08);
      border-left-color: var(--primary-color);
      color: #fff;
    }

    .slide-nav-item .slide-number {
      display: inline-flex;
      align-items: center;
      justify-content: center;
      width: 1.5rem;
      height: 1.5rem;
      background: rgba(255,255,255,0.06);
      border-radius: 4px;
      font-size: 0.7rem;
      font-weight: 600;
      flex-shrink: 0;
    }

    .slide-nav-item.active .slide-number {
      background: var(--primary-color);
      color: #0a0a1a;
    }

    .slide-nav-item .slide-label {
      white-space: nowrap;
      overflow: hidden;
      text-overflow: ellipsis;
    }

    /* Zoom Controls */
    .zoom-controls {
      display: flex;
      align-items: center;
      justify-content: center;
      gap: 0.5rem;
      padding: 0.75rem 1rem;
      border-top: 1px solid rgba(255,255,255,0.06);
    }

    .zoom-controls button {
      background: rgba(255,255,255,0.08);
      border: 1px solid rgba(255,255,255,0.1);
      color: #ccc;
      border-radius: 4px;
      width: 28px;
      height: 28px;
      font-size: 1rem;
      cursor: pointer;
      display: flex;
      align-items: center;
      justify-content: center;
      transition: all 0.15s ease;
    }

    .zoom-controls button:hover {
      background: rgba(255,255,255,0.14);
      color: #fff;
    }

    .zoom-controls .zoom-level {
      font-size: 0.75rem;
      color: #888;
      min-width: 3rem;
      text-align: center;
    }

    /* Main Content Area */
    .slide-viewport {
      flex: 1;
      display: flex;
      align-items: center;
      justify-content: center;
      background: #0d0d1a;
      overflow: hidden;
      position: relative;
    }

    .slide-wrapper {
      transition: transform 0.2s ease;
    }

    .slide {
      width: 960px;
      height: 540px;
      aspect-ratio: 16/9;
      position: relative;
      overflow: hidden;
      border-radius: 6px;
      box-shadow: 0 20px 80px rgba(0,0,0,0.5), 0 0 0 1px rgba(255,255,255,0.06);
      display: none;
    }

    .slide.active {
      display: flex;
    }

    /* ============================================
       SLIDE TYPE STYLES
       ============================================ */

    /* --- Title Slide (Cover) --- */
    .slide-cover {
      background: linear-gradient(135deg, var(--bg-dark) 0%, #0d0d2a 50%, #151530 100%);
      flex-direction: column;
      align-items: center;
      justify-content: center;
      text-align: center;
      padding: 4rem 3rem;
    }

    .slide-cover::before {
      content: '';
      position: absolute;
      top: 0;
      left: 0;
      right: 0;
      bottom: 0;
      background: radial-gradient(ellipse at top right, rgba(200,255,46,0.12) 0%, transparent 50%),
                  radial-gradient(ellipse at bottom left, rgba(124,107,240,0.12) 0%, transparent 50%);
      pointer-events: none;
    }

    .slide-cover .slide-content-inner {
      position: relative;
      z-index: 1;
    }

    .slide-cover .slide-title {
      font-family: var(--heading-font);
      font-size: 2.75rem;
      font-weight: 700;
      color: #ffffff;
      line-height: 1.15;
      margin-bottom: 0.75rem;
      text-shadow: 0 2px 20px rgba(0,0,0,0.3);
    }

    .slide-cover .slide-subtitle {
      font-size: 1.25rem;
      color: #b0b0c0;
      margin-bottom: 1.5rem;
      line-height: 1.5;
    }

    .slide-cover .slide-meta {
      font-size: 0.85rem;
      color: var(--primary-color);
      letter-spacing: 0.08em;
      margin-top: 1rem;
    }

    /* --- Content Slide --- */
    .slide-content {
      background: #ffffff;
      flex-direction: column;
      padding: 2.5rem 3rem;
    }

    .slide-content .slide-header {
      margin-bottom: 1.5rem;
      padding-bottom: 0.75rem;
      border-bottom: 3px solid var(--primary-color);
    }

    .slide-content .slide-title {
      font-family: var(--heading-font);
      font-size: 1.75rem;
      font-weight: 700;
      color: var(--text-color);
      line-height: 1.2;
    }

    .slide-content .slide-subtitle {
      font-size: 0.95rem;
      color: var(--text-light);
      margin-top: 0.25rem;
    }

    .slide-content .slide-body {
      flex: 1;
      font-size: 1rem;
      line-height: 1.7;
      color: var(--text-color);
    }

    .slide-content .slide-body ul,
    .slide-content .slide-body ol {
      margin: 0.75rem 0;
      padding-left: 1.5rem;
    }

    .slide-content .slide-body li {
      margin-bottom: 0.4rem;
    }

    .slide-content .slide-body p {
      margin-bottom: 0.75rem;
    }

    .slide-content .slide-body h3 {
      font-family: var(--heading-font);
      font-size: 1.2rem;
      font-weight: 600;
      margin: 1rem 0 0.5rem;
      color: var(--text-color);
    }

    .slide-content .slide-footer {
      margin-top: auto;
      padding-top: 0.75rem;
      border-top: 1px solid var(--border-color);
      display: flex;
      justify-content: space-between;
      align-items: center;
      font-size: 0.75rem;
      color: #999;
    }

    /* --- Section Divider --- */
    .slide-section {
      background: linear-gradient(135deg, var(--bg-dark) 0%, #0d0d2a 100%);
      flex-direction: column;
      align-items: center;
      justify-content: center;
      text-align: center;
      padding: 3rem;
    }

    .slide-section::before {
      content: '';
      position: absolute;
      top: 0;
      left: 0;
      right: 0;
      bottom: 0;
      background: radial-gradient(circle at center, rgba(200,255,46,0.06) 0%, transparent 70%);
      pointer-events: none;
    }

    .slide-section .slide-section-number {
      font-family: var(--heading-font);
      font-size: 0.85rem;
      text-transform: uppercase;
      letter-spacing: 0.25em;
      color: var(--primary-color);
      margin-bottom: 0.75rem;
      position: relative;
      z-index: 1;
    }

    .slide-section .slide-title {
      font-family: var(--heading-font);
      font-size: 2.5rem;
      font-weight: 700;
      color: #ffffff;
      line-height: 1.15;
      position: relative;
      z-index: 1;
    }

    .slide-section .slide-subtitle {
      font-size: 1.1rem;
      color: #8888aa;
      margin-top: 0.5rem;
      position: relative;
      z-index: 1;
    }

    /* --- CTA Slide --- */
    .slide-cta {
      background: linear-gradient(135deg, var(--bg-dark) 0%, #0d0d2a 100%);
      flex-direction: column;
      align-items: center;
      justify-content: center;
      text-align: center;
      padding: 3rem;
    }

    .slide-cta::before {
      content: '';
      position: absolute;
      top: 0;
      left: 0;
      right: 0;
      bottom: 0;
      background: radial-gradient(ellipse at bottom center, rgba(200,255,46,0.15) 0%, transparent 60%);
      pointer-events: none;
    }

    .slide-cta .slide-title {
      font-family: var(--heading-font);
      font-size: 2.25rem;
      font-weight: 700;
      color: #ffffff;
      line-height: 1.2;
      margin-bottom: 1rem;
      position: relative;
      z-index: 1;
    }

    .slide-cta .slide-subtitle {
      font-size: 1.1rem;
      color: #b0b0c0;
      margin-bottom: 2rem;
      line-height: 1.5;
      position: relative;
      z-index: 1;
    }

    .slide-cta .cta-button {
      display: inline-block;
      padding: 0.85rem 2.5rem;
      background: var(--primary-color);
      color: #0a0a1a;
      font-family: var(--heading-font);
      font-size: 1rem;
      font-weight: 700;
      text-decoration: none;
      border-radius: 6px;
      position: relative;
      z-index: 1;
      letter-spacing: 0.02em;
    }

    .slide-cta .cta-secondary {
      display: block;
      margin-top: 0.75rem;
      font-size: 0.85rem;
      color: #888;
      position: relative;
      z-index: 1;
    }

    /* --- Closing / Thank You Slide --- */
    .slide-closing {
      background: linear-gradient(135deg, var(--bg-dark) 0%, #0d0d2a 50%, #151530 100%);
      flex-direction: column;
      align-items: center;
      justify-content: center;
      text-align: center;
      padding: 3rem;
    }

    .slide-closing::before {
      content: '';
      position: absolute;
      top: 0;
      left: 0;
      right: 0;
      bottom: 0;
      background: radial-gradient(ellipse at center, rgba(124,107,240,0.1) 0%, transparent 50%);
      pointer-events: none;
    }

    .slide-closing .slide-title {
      font-family: var(--heading-font);
      font-size: 2.5rem;
      font-weight: 700;
      color: #ffffff;
      margin-bottom: 0.5rem;
      position: relative;
      z-index: 1;
    }

    .slide-closing .slide-subtitle {
      font-size: 1.1rem;
      color: #8888aa;
      margin-bottom: 2rem;
      position: relative;
      z-index: 1;
    }

    .slide-closing .contact-info {
      font-size: 0.9rem;
      color: var(--primary-color);
      position: relative;
      z-index: 1;
    }

    .slide-closing .contact-info a {
      color: var(--primary-color);
      text-decoration: none;
    }

    /* --- Speaker Notes (hidden in presentation view) --- */
    .speaker-notes {
      display: none;
    }

    /* --- Slide Number Badge --- */
    .slide-number-badge {
      position: absolute;
      bottom: 0.75rem;
      right: 1rem;
      font-size: 0.7rem;
      color: rgba(0,0,0,0.3);
      font-family: var(--body-font);
    }

    .slide-cover .slide-number-badge,
    .slide-section .slide-number-badge,
    .slide-cta .slide-number-badge,
    .slide-closing .slide-number-badge {
      color: rgba(255,255,255,0.2);
    }

    /* ============================================
       PRINT STYLES (for PDF export)
       ============================================ */

    @media print {
      body {
        background: #fff;
        height: auto;
        overflow: visible;
      }

      .presentation-container {
        display: block;
        height: auto;
      }

      .slide-nav {
        display: none;
      }

      .slide-viewport {
        display: block;
        background: #fff;
      }

      .slide-wrapper {
        transform: none !important;
      }

      .slide {
        display: block !important;
        width: 100% !important;
        height: auto !important;
        aspect-ratio: 16/9 !important;
        page-break-after: always;
        margin-bottom: 0;
        border-radius: 0;
        box-shadow: none;
      }

      .slide.active {
        display: block !important;
      }

      .slide-number-badge {
        display: block;
      }
    }

    /* Responsive */
    @media (max-width: 1200px) {
      .slide-nav {
        width: 180px;
      }
    }

    @media (max-width: 900px) {
      .slide-nav {
        display: none;
      }

      .slide {
        width: 100vw;
        height: 56.25vw;
      }
    }
  </style>
</head>
<body>
  <div class="presentation-container">
    <!-- Slide Navigation Sidebar -->
    <nav class="slide-nav">
      <div class="slide-nav-header">
        <h2>${escapeHtml(presentation.title || 'Untitled Presentation')}</h2>
        <p>${safeSlides.length} slides</p>
      </div>
      <div class="slide-nav-list" id="slide-nav-list">
        ${safeSlides.map((slide: any, i: number) => {
          const slideType = slide.slideType || slide.type || 'content';
          const label = slide.title || `${slideType.charAt(0).toUpperCase() + slideType.slice(1)} ${i + 1}`;
          return `<div class="slide-nav-item${i === 0 ? ' active' : ''}" data-slide="${i}" onclick="goToSlide(${i})">
            <span class="slide-number">${i + 1}</span>
            <span class="slide-label">${escapeHtml(label)}</span>
          </div>`;
        }).join('')}
      </div>
      <div class="zoom-controls">
        <button onclick="zoomOut()" title="Zoom out">&#8722;</button>
        <span class="zoom-level" id="zoom-level">100%</span>
        <button onclick="zoomIn()" title="Zoom in">&#43;</button>
      </div>
    </nav>

    <!-- Slide Viewport -->
    <main class="slide-viewport">
      <div class="slide-wrapper" id="slide-wrapper">
        ${safeSlides.map((slide: any, i: number) => {
          const slideType = slide.slideType || slide.type || 'content';
          const title = slide.title || '';
          const subtitle = slide.subtitle || '';
          const content = slide.content || '';
          const speakerNotes = slide.speakerNotes || slide.notes || '';
          const slideNumber = i + 1;

          let slideClass = 'slide';
          let innerHtml = '';

          if (slideType === 'title' || slideType === 'cover') {
            slideClass += ' slide-cover';
            innerHtml = `
              <div class="slide-content-inner">
                <div class="slide-title">${escapeHtml(title || presentation.title || 'Untitled Presentation')}</div>
                ${subtitle ? `<div class="slide-subtitle">${escapeHtml(subtitle)}</div>` : ''}
                <div class="slide-meta">${escapeHtml(presentation.companyName || '')}</div>
              </div>`;
          } else if (slideType === 'section' || slideType === 'section-divider') {
            slideClass += ' slide-section';
            innerHtml = `
              <div class="slide-section-number">Section ${slideNumber}</div>
              <div class="slide-title">${escapeHtml(title || 'Section')}</div>
              ${subtitle ? `<div class="slide-subtitle">${escapeHtml(subtitle)}</div>` : ''}`;
          } else if (slideType === 'cta') {
            slideClass += ' slide-cta';
            innerHtml = `
              <div class="slide-title">${escapeHtml(title || 'Take the Next Step')}</div>
              ${subtitle ? `<div class="slide-subtitle">${escapeHtml(subtitle)}</div>` : ''}
              <a href="#" class="cta-button">${escapeHtml(slide.ctaLabel || 'Get Started')}</a>
              ${slide.ctaSecondary ? `<span class="cta-secondary">${escapeHtml(slide.ctaSecondary)}</span>` : ''}`;
          } else if (slideType === 'closing' || slideType === 'thank-you') {
            slideClass += ' slide-closing';
            innerHtml = `
              <div class="slide-title">${escapeHtml(title || 'Thank You')}</div>
              ${subtitle ? `<div class="slide-subtitle">${escapeHtml(subtitle)}</div>` : ''}
              ${slide.contactInfo ? `<div class="contact-info">${processContent(slide.contactInfo)}</div>` : ''}`;
          } else {
            slideClass += ' slide-content';
            innerHtml = `
              <div class="slide-header">
                <div class="slide-title">${escapeHtml(title || 'Slide')}</div>
                ${subtitle ? `<div class="slide-subtitle">${escapeHtml(subtitle)}</div>` : ''}
              </div>
              <div class="slide-body">${content ? processContent(content) : '<p>Content will appear here.</p>'}</div>
              <div class="slide-footer">
                <span>${escapeHtml(presentation.title || '')}</span>
                <span>${slideNumber} / ${safeSlides.length}</span>
              </div>`;
          }

          return `<div class="${slideClass}" id="slide-${i}" data-slide="${i}">
            <a id="preview-slide-${i}" style="display:none;"></a>
            ${innerHtml}
            ${speakerNotes ? `<div class="speaker-notes">${escapeHtml(speakerNotes)}</div>` : ''}
            <span class="slide-number-badge">${slideNumber}</span>
          </div>`;
        }).join('')}
      </div>
    </main>
  </div>

  <script>
    let currentSlide = 0;
    const totalSlides = ${safeSlides.length};
    let currentZoom = 100;

    function goToSlide(index) {
      if (index < 0 || index >= totalSlides) return;
      // Hide all slides
      document.querySelectorAll('.slide').forEach(s => s.classList.remove('active'));
      // Show target slide
      const target = document.getElementById('slide-' + index);
      if (target) target.classList.add('active');
      // Update nav
      document.querySelectorAll('.slide-nav-item').forEach(item => item.classList.remove('active'));
      const navItem = document.querySelector('.slide-nav-item[data-slide="' + index + '"]');
      if (navItem) navItem.classList.add('active');
      currentSlide = index;
    }

    function zoomIn() {
      currentZoom = Math.min(200, currentZoom + 10);
      applyZoom();
    }

    function zoomOut() {
      currentZoom = Math.max(30, currentZoom - 10);
      applyZoom();
    }

    function applyZoom() {
      const wrapper = document.getElementById('slide-wrapper');
      if (wrapper) {
        wrapper.style.transform = 'scale(' + (currentZoom / 100) + ')';
      }
      const label = document.getElementById('zoom-level');
      if (label) label.textContent = currentZoom + '%';
    }

    function handleKeyboard(e) {
      if (e.key === 'ArrowRight' || e.key === 'ArrowDown' || e.key === ' ') {
        e.preventDefault();
        goToSlide(currentSlide + 1);
      } else if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') {
        e.preventDefault();
        goToSlide(currentSlide - 1);
      } else if (e.key === 'Home') {
        e.preventDefault();
        goToSlide(0);
      } else if (e.key === 'End') {
        e.preventDefault();
        goToSlide(totalSlides - 1);
      }
    }

    document.addEventListener('keydown', handleKeyboard);
    // Show first slide on load
    goToSlide(0);
  </script>
</body>
</html>`;
}

function escapeHtml(str: string): string {
  return str
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#039;');
}

function processContent(content: string): string {
  if (!content) return '';

  // Already HTML - return as-is
  if (content.includes('<')) return content;

  // Process markdown-like content
  return content
    .split('\n\n')
    .map(para => {
      if (para.startsWith('# ')) {
        return `<h3>${para.slice(2)}</h3>`;
      }
      if (para.startsWith('## ')) {
        return `<h4>${para.slice(3)}</h4>`;
      }
      if (para.startsWith('- ') || para.startsWith('* ')) {
        const items = para.split('\n').map(line => line.replace(/^[-*] /, ''));
        return `<ul>${items.map(i => `<li>${i}</li>`).join('')}</ul>`;
      }
      if (para.startsWith('> ')) {
        return `<blockquote>${para.slice(2)}</blockquote>`;
      }
      return `<p>${para}</p>`;
    })
    .join('\n');
}

// ============================================
// UPDATE PRESENTATION GENERATED CONTENT STATUS
// ============================================

async function updateGeneratedContentStatus(
  presentationId: string,
  update: {
    status: string;
    jobId?: string;
    generatedAt?: string;
    error?: string;
    slides?: any[];
  }
): Promise<void> {
  try {
    const { Presentation } = getModels();
    const presentation = await Presentation.findById(presentationId);
    if (!presentation) {
      console.warn('[PresentationGenerator] Presentation not found:', presentationId);
      return;
    }

    presentation.generatedContent = {
      ...(presentation.generatedContent as any || {}),
      ...update,
      updatedAt: new Date().toISOString(),
    };
    await presentation.save();
    console.log('[PresentationGenerator] Updated generatedContent status:', update.status, 'for presentation:', presentationId);
  } catch (dbError: any) {
    console.error('[PresentationGenerator] Failed to update MongoDB status:', dbError.message);
  }
}

// ============================================
// SLIDE TYPE NORMALIZATION
// ============================================

const VALID_SLIDE_TYPES = new Set([
  'title', 'problem', 'solution', 'market', 'product', 'business-model',
  'traction', 'team', 'financials', 'competition', 'ask', 'timeline',
  'custom', 'about', 'demo', 'case-study', 'pricing', 'next-steps',
  'vision-mission', 'services-products', 'achievements', 'client-portfolio',
  'contact', 'benefits', 'use-cases', 'testimonials', 'call-to-action',
  'objective', 'brand-message', 'sponsorship', 'exhibition', 'franchise',
  'partnership', 'marketing-campaign', 'content',
]);

/** Map AI-generated slide types to valid Mongoose enum values */
function normalizeSlideType(rawType: string | undefined): string {
  if (!rawType) return 'content';
  const lower = rawType.toLowerCase().trim();
  if (VALID_SLIDE_TYPES.has(lower)) return lower;
  // Common aliases — map AI output names to valid schema types
  const aliases: Record<string, string> = {
    'cover': 'title',
    'section-divider': 'content',
    'section': 'content',
    'intro': 'about',
    'opening': 'title',
    'summary': 'content',
    'conclusion': 'content',
    'overview': 'content',
    'agenda': 'content',
    'quote': 'content',
    'default': 'content',
    'main': 'content',
    'body': 'content',
    'standard': 'content',
    'regular': 'content',
    'text': 'content',
    'info': 'content',
    'details': 'content',
    'key-points': 'content',
    'highlights': 'content',
    'insights': 'content',
    'approach': 'content',
    'methodology': 'content',
    'process': 'content',
    'strategy': 'content',
    'results': 'content',
    'impact': 'content',
    'value-proposition': 'content',
    'features': 'content',
    'challenges': 'content',
    'opportunity': 'content',
    'thank-you': 'custom',
    'closing': 'custom',
  };
  return aliases[lower] || 'content';
}

// ============================================
// GENERATE PRESENTATION CONTENT CORE
// ============================================

async function generatePresentationContentCore(
  presentationId: string,
  companyId: string,
  generatedPrompt: string | undefined,
  config: PresentationGenerationConfig,
  slides: any[],
  briefContent: string,
  jobId: string,
  language?: string
): Promise<{ slides: any[]; html?: string }> {
  try {
    const models = getModels();

    // Mark as generating
    await updateGeneratedContentStatus(presentationId, {
      status: 'generating',
      generatedAt: new Date().toISOString(),
    });
    writeMetadata(presentationId, {
      status: 'generating',
      generatedAt: new Date().toISOString(),
      presentationId,
      companyId,
    });

    console.log('[PresentationGenerator] Starting content generation for presentation:', presentationId);
    console.log('[PresentationGenerator] Input slides count:', slides.length);
    console.log('[PresentationGenerator] Using generated prompt:', generatedPrompt ? 'Yes' : 'No');

    // Get the presentation from DB
    const { Presentation } = models;
    const presentation = await Presentation.findById(presentationId);
    if (!presentation) {
      throw new Error('Presentation not found: ' + presentationId);
    }
    const presentationData = presentation.toObject();

    updateJobProgress(jobId, 10, 'Loading presentation context...');

    // Get business profile for context
    let brandStrategy: any = null;
    let visualIdentityData: any = null;
    try {
      const { ModuleData } = models;
      brandStrategy = await ModuleData.findOne({ moduleId: 'brand-strategy', companyId });
      visualIdentityData = await ModuleData.findOne({ moduleId: 'visual-identity', companyId });
    } catch {}

    // Build system and user prompts
    updateJobProgress(jobId, 15, 'Building generation prompt...');
    let systemPrompt: string;
    let userPrompt: string;

    // Append language instruction if a non-English language is selected
    const languageInstruction = buildLanguageInstruction(language);
    console.log('[PresentationGenerator] Language:', language, '→ instruction length:', languageInstruction.length);

    if (generatedPrompt && generatedPrompt.trim().length > 0) {
      console.log('[PresentationGenerator] Using generated prompt from AI Prompt step');
      systemPrompt = `You are an elite presentation designer and content strategist. Follow ALL instructions in the user prompt precisely. Generate complete, presentation-ready slide content with no placeholders. Return valid JSON.` + languageInstruction;
      userPrompt = generatedPrompt;
    } else {
      console.log('[PresentationGenerator] Building default prompt from configuration');
      systemPrompt = `You are an elite presentation designer and content strategist. Generate compelling, professional, and complete presentation slide content.

Your output must be:
- Well-structured with clear visual hierarchy
- Professionally written with concise, impactful language
- Tailored to the presentation type and audience
- Consistently formatted across all slides
- Rich in substance with no placeholder text

SLIDE TYPE GUIDELINES:
- title/cover: Impactful headline, minimal text, strong visual presence
- content: Clear section header, 3-5 key points with supporting detail
- section: Bold section number, concise title, brief subtitle
- cta: Compelling call-to-action with clear value proposition
- closing: Thank you message, contact information, next steps
- market: Include market data with chart-friendly format (labels + values)
- financials: Include financial projections with numeric data points
- team: Include team member details (name, role, brief bio)
- competition: Include competitive comparison data (features, positioning)
- client-portfolio: Include client/project names with brief descriptions`;

      const slideOutline = slides.map((s, i) => {
        const slideType = s.slideType || s.type || 'content';
        return `\n${i + 1}. [${slideType}] ${s.title || 'Untitled'}${s.description ? ': ' + s.description : ''}`;
      }).join('');

      userPrompt = `Generate complete content for the following presentation:

**Presentation Title:** ${presentationData.title || 'Untitled'}
**Presentation Type:** ${config.presentationType || 'Pitch Deck'}
**Template Style:** ${config.templateStyle || 'Professional'}
**Tone of Voice:** ${config.toneOfVoice || 'Professional'}
**Content Density:** ${config.contentDensity || 'Balanced'}

**Slides (${slides.length} total):**${slideOutline}

${briefContent ? `**Brief/Context:** ${briefContent}` : ''}

${config.slideFormatting?.maxBulletsPerSlide ? `**Max bullets per slide:** ${config.slideFormatting.maxBulletsPerSlide}` : ''}
${config.slideFormatting?.includeSpeakerNotes ? '**Include detailed speaker notes for each slide**' : ''}

For each slide, generate:
1. A clear, impactful title
2. A supporting subtitle (where appropriate)
3. Rich HTML content with proper formatting
4. Key points as a structured array
5. A brief description of the visual/imagery that should accompany the slide
6. Speaker notes with talking points
7. Chart data for applicable slide types (market, financials, competition)

Return the result as a JSON object with this structure:
{
  "slides": [
    {
      "id": "slide-id-from-input",
      "title": "Clear, Impactful Title",
      "subtitle": "Supporting subtitle (optional)",
      "content": "<h3>Section Header</h3><p>Opening statement.</p><ul><li>Key point 1 with supporting detail</li><li>Key point 2 with supporting detail</li><li>Key point 3 with supporting detail</li></ul>",
      "keyPoints": ["Point 1", "Point 2", "Point 3"],
      "visualDescription": "Description of what visual should accompany this slide",
      "chartData": null,
      "speakerNotes": "What the presenter should say when showing this slide. Include transition phrases and emphasis points.",
      "slideType": "content|title|section|cta|closing",
      "layout": "single-column|two-column|image-left|image-right|full-image",
      "ctaLabel": "Button text (for CTA slides only)",
      "contactInfo": "Contact details (for closing slides only)"
    }
  ]
}

**SLIDE-TYPE-SPECIFIC REQUIREMENTS:**
- For "market" type slides: Include chartData with TAM/SAM/SOM market sizing data. Format: {"type": "bar", "title": "Market Size", "labels": ["TAM", "SAM", "SOM"], "datasets": [{"label": "Market Size ($B)", "values": [50, 20, 5]}]}
- For "financials" type slides: Include chartData with revenue/growth projections. Format: {"type": "bar", "title": "Revenue Growth", "labels": ["Year 1", "Year 2", "Year 3"], "datasets": [{"label": "Revenue ($M)", "values": [2, 8, 25]}]}
- For "team" type slides: Format content as a grid of team members. Use HTML table or grid layout with: Name, Role, and brief bio for each member
- For "competition" type slides: Include a comparison matrix. Use HTML table format with features/competitors
- For "client-portfolio" type slides: Format as a card grid with client names and brief project descriptions

**CRITICAL RULES:**
- Generate COMPLETE, detailed content for EVERY slide - absolutely NO placeholder text
- Each content slide MUST have substantive, specific content relevant to the slide's topic
- Use proper HTML formatting: h3 for section headers, p for paragraphs, ul/li for bullet lists, strong for emphasis, blockquote for quotes
- Keep bullet points concise (max ${config.slideFormatting?.maxBulletsPerSlide || 6} per slide) but informative
- Ensure narrative flow: each slide should logically follow from the previous one
- Vary content structure: not every slide should be a bullet list - use paragraphs, quotes, stats, and visual descriptions
- Write as if delivering a real, high-stakes presentation` + languageInstruction;
    }

    console.log('[PresentationGenerator] Prompt length:', userPrompt.length, 'characters');

    // Generate content for all slides
    const totalSlides = slides.length;
    const estimatedContentWords = totalSlides * 200;
    const requiredTokens = Math.max(16000, Math.min(64000, Math.ceil(estimatedContentWords * 2)));

    let generatedData: any = null;

    // Try single-pass generation
    updateJobProgress(jobId, 20, 'Generating slide content with AI...');
    try {
      const result = await generateWithAI(userPrompt, systemPrompt, requiredTokens, 0.7, 'json', 'ollama', undefined, undefined, undefined, true);

      if (result && result.content) {
        console.log('[PresentationGenerator] AI response length:', result.content.length);

        try {
          generatedData = JSON.parse(result.content);

          if (generatedData?.slides && Array.isArray(generatedData.slides)) {
            const receivedSlides = generatedData.slides.length;
            console.log(`[PresentationGenerator] Received ${receivedSlides}/${totalSlides} slides`);
            updateJobProgress(jobId, 60, 'Processing AI response...');
          }
        } catch (parseError) {
          console.log('[PresentationGenerator] JSON parse failed, trying extraction...');
          const match = result.content.match(/\{[\s\S]*"slides"[\s\S]*\}/);
          if (match) {
            try {
              generatedData = JSON.parse(match[0]);
            } catch {
              console.warn('[PresentationGenerator] Failed to parse extracted JSON');
              generatedData = null;
            }
          }
        }
      }
    } catch (error: any) {
      console.error('[PresentationGenerator] Single-pass generation failed:', error.message);
      updateJobProgress(jobId, 25, 'Single-pass generation failed, trying slide-by-slide...');
      generatedData = null;
    }

    // If single-pass didn't work or produced incomplete content, generate slide by slide
    if (!generatedData || !generatedData.slides) {
      updateJobProgress(jobId, 25, 'Generating slide-by-slide content...');
      console.log('[PresentationGenerator] Falling back to slide-by-slide generation...');
      generatedData = { slides: [] };

      for (let slideIndex = 0; slideIndex < totalSlides; slideIndex++) {
        const inputSlide = slides[slideIndex];
        const slideType = inputSlide.slideType || inputSlide.type || 'content';
        const slideTitle = inputSlide.title || `Slide ${slideIndex + 1}`;

        const slideProgress = 25 + Math.round(((slideIndex + 1) / totalSlides) * 55);
        updateJobProgress(jobId, slideProgress, `Generating slide ${slideIndex + 1}/${totalSlides}: ${slideTitle}...`);
        console.log(`[PresentationGenerator] Generating slide ${slideIndex + 1}/${totalSlides}: "${slideTitle}" (${slideType})`);

        const slidePrompt = `${userPrompt}

---

## FOCUS: GENERATE ONLY SLIDE ${slideIndex + 1} OF ${totalSlides}

You are now generating content for **Slide ${slideIndex + 1}: "${slideTitle}"** specifically.

### Slide Details
**Type:** ${slideType}
**Title:** ${slideTitle}
${inputSlide.description ? `**Description:** ${inputSlide.description}` : ''}

### Output Requirements

Generate a JSON object for THIS SLIDE ONLY with this structure:

\`\`\`json
{
  "id": "${inputSlide.id || inputSlide._id || ''}",
  "title": "Clear, Impactful Title",
  "subtitle": "Supporting subtitle (optional)",
  "content": "<h3>Section Header</h3><p>Opening statement.</p><ul><li>Key point 1</li><li>Key point 2</li></ul>",
  "keyPoints": ["Point 1", "Point 2", "Point 3"],
  "visualDescription": "Description of visual/imagery for this slide",
  "chartData": null,
  "speakerNotes": "Detailed speaker notes with talking points...",
  "slideType": "${slideType}",
  "layout": "single-column",
  "ctaLabel": "Button text (for CTA slides only)",
  "contactInfo": "Contact info (for closing slides only)"
}
\`\`\`

**SLIDE-TYPE REQUIREMENTS for ${slideType}:**
${slideType === 'market' ? '- Include chartData with TAM/SAM/SOM market sizing: {"type": "bar", "title": "Market Size", "labels": ["TAM", "SAM", "SOM"], "datasets": [{"label": "Size ($B)", "values": [50, 20, 5]}]}' : ''}
${slideType === 'financials' ? '- Include chartData with revenue/growth projections: {"type": "bar", "title": "Revenue Growth", "labels": ["Year 1", "Year 2", "Year 3"], "datasets": [{"label": "Revenue ($M)", "values": [2, 8, 25]}]}' : ''}
${slideType === 'team' ? '- Format as grid of team members with Name, Role, and brief bio' : ''}
${slideType === 'competition' ? '- Include competitive comparison matrix in HTML table format' : ''}
${slideType === 'client-portfolio' ? '- Format as card grid with client names and project descriptions' : ''}

**CRITICAL:**
- Generate COMPLETE, DETAILED content — absolutely NO placeholder text
- This is slide ${slideIndex + 1} of ${totalSlides} — ensure narrative continuity
- Return ONLY valid JSON, no additional text outside the JSON object
- Content must be substantive, specific, and professionally written
- Vary content structure appropriately for this slide type`;

        try {
          const slideResult = await generateWithAI(slidePrompt, systemPrompt, 8000, 0.7, 'json', 'ollama', undefined, undefined, undefined, true);

          if (slideResult && slideResult.content) {
            let slideData: any = null;
            try {
              slideData = JSON.parse(slideResult.content);
            } catch {
              const match = slideResult.content.match(/\{[\s\S]*"content"[\s\S]*\}/);
              if (match) {
                try {
                  slideData = JSON.parse(match[0]);
                } catch {
                  console.warn(`[PresentationGenerator] Failed to parse slide ${slideIndex + 1}`);
                }
              }
            }

            if (slideData) {
              slideData.id = inputSlide.id || inputSlide._id || slideData.id;
              generatedData.slides[slideIndex] = slideData;
              console.log(`[PresentationGenerator] Slide ${slideIndex + 1} generated: content length ${slideData.content?.length || 0}`);
            }
          }
        } catch (error: any) {
          console.error(`[PresentationGenerator] Failed to generate slide ${slideIndex + 1}:`, error.message);
          const errorProgress = 25 + Math.round(((slideIndex + 1) / totalSlides) * 55);
          updateJobProgress(jobId, errorProgress, `Slide ${slideIndex + 1}/${totalSlides} failed, continuing...`);
        }
      }
    }

    // Process and merge generated slides
    updateJobProgress(jobId, 85, 'Processing slides and generating HTML...');
    const updatedSlides: any[] = [];

    for (let i = 0; i < totalSlides; i++) {
      const inputSlide = slides[i];
      const generatedSlide = generatedData?.slides?.[i] || generatedData?.slides?.find((s: any) => s.id === (inputSlide.id || inputSlide._id));

      const updatedSlide = {
        ...inputSlide,
        id: inputSlide.id || inputSlide._id || `slide-${i + 1}`,
        _id: inputSlide._id || inputSlide.id || '',
        order: inputSlide.order ?? i,
        type: normalizeSlideType(generatedSlide?.slideType || inputSlide.slideType || inputSlide.type),
        title: generatedSlide?.title || inputSlide.title || `Slide ${i + 1}`,
        subtitle: generatedSlide?.subtitle || inputSlide.subtitle || '',
        content: generatedSlide?.content || inputSlide.content || '',
        notes: generatedSlide?.speakerNotes || inputSlide.notes || '',
        slideType: normalizeSlideType(generatedSlide?.slideType || inputSlide.slideType || inputSlide.type),
        layout: generatedSlide?.layout || inputSlide.layout || 'single-column',
        chartData: generatedSlide?.chartData || inputSlide.chartData || undefined,
        status: generatedSlide ? 'final' : (inputSlide.status || 'draft'),
      };

      // Generate placeholder content for slides without AI content
      if (!updatedSlide.content || updatedSlide.content.trim().length < 20) {
        const slideType = updatedSlide.slideType || 'content';
        const slideTitle = updatedSlide.title || `Slide ${i + 1}`;

        console.warn(`[PresentationGenerator] Slide ${i + 1} "${slideTitle}" has insufficient content`);

        if (slideType === 'title' || slideType === 'cover') {
          updatedSlide.content = `<p><em>AI-generated content for this slide was not received. Please regenerate or edit manually.</em></p>`;
        } else if (slideType === 'section' || slideType === 'section-divider') {
          updatedSlide.content = '';
        } else if (slideType === 'cta') {
          updatedSlide.content = `<p>Take action today.</p>`;
        } else if (slideType === 'closing' || slideType === 'thank-you') {
          updatedSlide.content = '';
        } else {
          updatedSlide.content = `
            <h3>${slideTitle}</h3>
            <p><em>Content for this slide will be added after regeneration or manual editing.</em></p>
            <ul>
              <li>Key point one</li>
              <li>Key point two</li>
              <li>Key point three</li>
            </ul>`;
        }
      }

      updatedSlides.push(updatedSlide);
    }

    console.log('[PresentationGenerator] Updated slides:', updatedSlides.length,
      updatedSlides.map((s: any) => `#${s.order} type=${s.type} title="${(s.title || '').slice(0, 30)}" content=${(s.content || '').length}ch`).join('\n  '));

    // Generate complete HTML presentation
    updateJobProgress(jobId, 95, 'Generating presentation HTML...');
    const html = generatePresentationHTML(presentationData, updatedSlides, briefContent, config);
    const htmlPath = getHtmlPath(presentationId);
    fs.writeFileSync(htmlPath, html, 'utf-8');
    console.log(`[PresentationGenerator] Generated HTML file: ${htmlPath}`);

    // Write metadata
    updateJobProgress(jobId, 98, 'Saving results...');
    writeMetadata(presentationId, {
      status: 'completed',
      generatedAt: new Date().toISOString(),
      updatedAt: new Date().toISOString(),
      slidesGenerated: updatedSlides.length,
      presentationId,
      companyId,
      htmlPath,
    });

    // Update presentation status
    await updateGeneratedContentStatus(presentationId, {
      status: 'completed',
      generatedAt: new Date().toISOString(),
      slides: updatedSlides,
    });

    // Also update the main presentation.slides with generated content
    // so the frontend can display the content without needing to dig into generatedContent
    try {
      const { Presentation } = getModels();
      const pres = await Presentation.findById(presentationId);
      if (pres) {
        const mappedSlides = updatedSlides.map((s: any) => {
          const slide = {
            id: s.id || s._id || `slide-${s.order}`,
            order: s.order ?? 0,
            type: s.type || s.slideType || 'content',
            title: s.title || '',
            subtitle: s.subtitle || '',
            content: s.content || '',
            notes: s.notes || '',
            chartData: s.chartData || undefined,
            layout: s.layout || 'single-column',
            aiGenerated: true,
            generatedAt: new Date(),
          };
          console.log(`[PresentationGenerator] Mapping slide #${slide.order}: type=${slide.type}, title="${(slide.title || '').slice(0, 30)}", content=${slide.content.length}ch`);
          return slide;
        });
        pres.slides = mappedSlides;
        // Also persist the language so regenerate-slide can pick it up
        if (language) {
          pres.language = language;
        }
        pres.markModified('slides');
        await pres.save();
        console.log(`[PresentationGenerator] ✅ Saved presentation.slides for ${presentationId}`);

        // Verify the save actually worked by re-reading
        const verify = await Presentation.findById(presentationId);
        if (verify) {
          console.log(`[PresentationGenerator] Verification: presentation has ${verify.slides.length} slides, first slide content length: ${(verify.slides[0]?.content || '').length}`);
        }
      } else {
        console.warn(`[PresentationGenerator] ⚠️ Presentation ${presentationId} not found for slide update`);
      }
    } catch (slideUpdateErr: any) {
      console.error(`[PresentationGenerator] ❌ Could not update presentation.slides: ${slideUpdateErr.message}`, slideUpdateErr.stack);
    }

    console.log(`[PresentationGenerator] Generation completed for presentation: ${presentationId}`);

    return {
      slides: updatedSlides,
      html,
    };

  } catch (error: any) {
    console.error('[PresentationGenerator] Generation failed for presentation:', presentationId, error);

    // Mark as failed
    writeMetadata(presentationId, {
      status: 'failed',
      error: error.message || 'Generation failed',
      updatedAt: new Date().toISOString(),
    });

    await updateGeneratedContentStatus(presentationId, {
      status: 'failed',
      error: error.message || 'Generation failed',
    });

    throw error;
  }
}

// ============================================
// GENERATE ENDPOINT
// ============================================

router.post('/generate', authenticate, requirePermission('presentations', 'ai-generate'), async (req: Request, res: Response) => {
  const { presentationId, companyId, config, slides, generatedPrompt, briefContent, language: bodyLanguage } = req.body;
  const effectiveLanguage = bodyLanguage || config?.language || 'en';

  // Debug logging
  console.log('[PresentationGenerator] === GENERATE REQUEST ===');
  console.log('[PresentationGenerator] presentationId:', presentationId);
  console.log('[PresentationGenerator] companyId:', companyId);
  console.log('[PresentationGenerator] language:', effectiveLanguage);
  console.log('[PresentationGenerator] generatedPrompt length:', generatedPrompt?.length || 0);
  console.log('[PresentationGenerator] slides count:', slides?.length || 0);
  console.log('[PresentationGenerator] briefContent length:', briefContent?.length || 0);

  if (!presentationId || !companyId) {
    res.status(400).json({ error: 'presentationId and companyId are required' });
    return;
  }

  try {
    const { Presentation } = getModels();
    const presentation = await Presentation.findById(presentationId);

    if (!presentation) {
      res.status(404).json({ error: 'Presentation not found' });
      return;
    }

    // Check for existing generation status
    const existingContent = presentation.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(presentationId, {
        status: 'failed',
        jobId: existingContent.jobId,
        error: 'Generation was interrupted (server may have restarted)',
      });
      writeMetadata(presentationId, {
        status: 'failed',
        jobId: existingContent.jobId,
        error: 'Generation was interrupted',
        updatedAt: new Date().toISOString(),
      });
    }

    // Create job
    const job = createJob('presentation-generator', companyId, presentationId);
    updateJobProgress(job.jobId, 5, 'Initializing presentation generation...');

    res.json({ jobId: job.jobId, status: 'processing' });

    // Start generation in background
    setImmediate(async () => {
      // 20-minute timeout guard
      const GENERATION_TIMEOUT_MS = 20 * 60 * 1000;
      const timeoutId = setTimeout(() => {
        console.error(`[PresentationGenerator] Job ${job.jobId} timed out after ${GENERATION_TIMEOUT_MS / 1000}s`);
        updateJobProgress(job.jobId, -1, 'Generation timed out');
        failJob(job.jobId, 'Generation timed out after 20 minutes. Please try again.');
        updateGeneratedContentStatus(presentationId, {
          status: 'failed',
          error: 'Generation timed out after 20 minutes',
        }).catch(() => {});
      }, GENERATION_TIMEOUT_MS);

      try {
        await generatePresentationContentCore(
          presentationId,
          companyId,
          generatedPrompt,
          config || {},
          slides || [],
          briefContent || '',
          job.jobId,
          effectiveLanguage,
        );

        clearTimeout(timeoutId);
        updateJobProgress(job.jobId, 100, 'Generation complete');
        completeJob(job.jobId, {
          presentationId,
          generated: true,
        }, 'presentation-generator');

        console.log(`[PresentationGenerator] Job ${job.jobId} completed for presentation: ${presentationId}`);
      } catch (err: any) {
        clearTimeout(timeoutId);
        console.error(`[PresentationGenerator] Job ${job.jobId} failed:`, err.message);
        failJob(job.jobId, err.message || 'Generation failed');
      }
    });

  } catch (error: any) {
    console.error('[PresentationGenerator] 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/:presentationId', authenticate, async (req: Request, res: Response) => {
  const { presentationId } = req.params;
  const companyId = (req.query.companyId as string) || '';

  // Try filesystem metadata first
  const metadata = readMetadata(presentationId);
  if (metadata) {
    if (metadata.status === 'completed') {
      const htmlPath = getHtmlPath(presentationId);
      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' ? `/presentation-generator/preview/${presentationId}` : null,
      downloadUrl: metadata.status === 'completed' ? `/presentation-generator/download/${presentationId}` : null,
    });
    return;
  }

  // Fall back to MongoDB
  try {
    const { Presentation } = getModels();
    const presentation = await Presentation.findById(presentationId);
    if (presentation?.generatedContent) {
      const gc = presentation.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' ? `/presentation-generator/preview/${presentationId}` : null,
        downloadUrl: gc.status === 'completed' ? `/presentation-generator/download/${presentationId}` : null,
      });
      return;
    }
  } catch (err: any) {
    console.error('[PresentationGenerator] Error reading from MongoDB:', err.message);
  }

  res.json({ status: 'none' });
});

// ============================================
// PREVIEW ENDPOINT (serves HTML)
// ============================================

router.get('/preview/:presentationId', async (req: Request, res: Response) => {
  const { presentationId } = req.params;
  const htmlPath = getHtmlPath(presentationId);

  if (!fs.existsSync(htmlPath)) {
    res.status(404).send('<h1>Preview not available</h1><p>No generated content found for this presentation.</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/:presentationId', async (req: Request, res: Response) => {
  const { presentationId } = req.params;
  const format = (req.query.format as string) || 'html';
  const presentationDir = getPresentationDir(presentationId);

  if (!fs.existsSync(presentationDir)) {
    res.status(404).json({ error: 'No generated content found for this presentation' });
    return;
  }

  const { Presentation } = getModels();
  const presentation = await Presentation.findById(presentationId);
  const presentationTitle = presentation?.title || 'presentation';
  const presentationData = presentation?.toObject() as any;

  if (format === 'pdf') {
    try {
      const htmlPath = getHtmlPath(presentationId);
      if (!fs.existsSync(htmlPath)) {
        res.status(404).json({ error: 'No generated content found' });
        return;
      }

      // Create PDF using PDFKit
      const pdfPath = path.join(presentationDir, 'presentation.pdf');
      await generatePdfFromHtml(htmlPath, pdfPath, presentationData);

      res.download(pdfPath, `${presentationTitle}.pdf`);
      return;
    } catch (error: any) {
      console.error('[PresentationGenerator] PDF generation error:', error);
      res.status(500).json({ error: 'Failed to generate PDF. Please try downloading as HTML.' });
      return;
    }
  }

  if (format === 'pptx') {
    try {
      // PPTX export: return JSON data for client-side export
      // Client will use pptxgenjs or similar library to create the actual PPTX
      const htmlPath = getHtmlPath(presentationId);
      if (!fs.existsSync(htmlPath)) {
        res.status(404).json({ error: 'No generated content found' });
        return;
      }

      const metadata = readMetadata(presentationId);
      const gc = presentationData?.generatedContent || {};
      const slidesData = gc.slides || metadata?.slides || [];

      res.json({
        title: presentationTitle,
        slides: slidesData,
        config: presentationData?.config || {},
        format: 'pptx-data',
      });
      return;
    } catch (error: any) {
      console.error('[PresentationGenerator] PPTX data generation error:', error);
      res.status(500).json({ error: 'Failed to generate PPTX data' });
      return;
    }
  }

  // Default: serve HTML file
  const htmlPath = getHtmlPath(presentationId);
  if (fs.existsSync(htmlPath)) {
    res.download(htmlPath, `${presentationTitle}.html`);
    return;
  }

  res.status(404).json({ error: 'No generated file found' });
});

// ============================================
// PDF GENERATION FUNCTION
// ============================================

async function generatePdfFromHtml(htmlPath: string, pdfPath: string, presentation: any): Promise<void> {
  return new Promise((resolve, reject) => {
    try {
      const htmlContent = fs.readFileSync(htmlPath, 'utf-8');
      const textContent = htmlToText(htmlContent);

      // Create PDF document - landscape for presentations
      const doc = new PDFDocument({
        size: [960, 540],
        margins: { top: 40, bottom: 40, left: 60, right: 60 },
        info: {
          Title: presentation?.title || 'Presentation',
          Author: presentation?.companyName || 'Unknown',
        },
      });

      const stream = fs.createWriteStream(pdfPath);
      doc.pipe(stream);

      // Title slide
      doc.fontSize(28).font('Helvetica-Bold').text(presentation?.title || 'Untitled', { align: 'center' });
      doc.moveDown();
      if (presentation?.subtitle) {
        doc.fontSize(16).font('Helvetica').text(presentation.subtitle, { align: 'center' });
      }
      doc.addPage({ size: [960, 540], margins: { top: 40, bottom: 40, left: 60, right: 60 } });

      // Add slide content
      const lines = textContent.split('\n');
      let slideCount = 0;
      const maxLinesPerSlide = 12;

      for (let i = 0; i < lines.length; i++) {
        const line = lines[i].trim();
        if (!line) continue;

        // Rough heuristic: new slide on headings
        if (line.startsWith('Section') || slideCount === 0) {
          if (slideCount > 0) {
            doc.addPage({ size: [960, 540], margins: { top: 40, bottom: 40, left: 60, right: 60 } });
          }
          slideCount++;
        }

        if (line.match(/^[A-Z].*$/) && line.length < 80) {
          doc.fontSize(20).font('Helvetica-Bold').text(line, { align: 'left' });
          doc.moveDown(0.3);
        } else {
          doc.fontSize(12).font('Helvetica').text(line, { align: 'left' });
          doc.moveDown(0.2);
        }
      }

      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, '')
    .replace(/<header[^>]*>[\s\S]*?<\/header>/gi, '')
    .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();
}

// ============================================
// DELETE GENERATED CONTENT (for retry)
// ============================================

router.delete('/generated-content/:presentationId', authenticate, async (req: Request, res: Response) => {
  const { presentationId } = req.params;
  const companyId = (req.query.companyId as string) || '';

  // Delete filesystem directory
  const presentationDir = getPresentationDir(presentationId);
  if (fs.existsSync(presentationDir)) {
    fs.rmSync(presentationDir, { recursive: true, force: true });
  }

  // Clear MongoDB status
  try {
    const { Presentation } = getModels();
    const presentation = await Presentation.findById(presentationId);
    if (presentation) {
      presentation.generatedContent = undefined;
      await presentation.save();
    }
  } catch (err: any) {
    console.error('[PresentationGenerator] Error clearing MongoDB status:', err.message);
  }

  res.json({ success: true, message: 'Generated content cleared' });
});

// ============================================
// REGENERATE SINGLE SLIDE
// ============================================

router.post('/regenerate-slide', authenticate, requirePermission('presentations', 'ai-generate'), async (req: Request, res: Response) => {
  const { presentationId, companyId, slideIndex, slideType, slideContext, customInstructions } = req.body;

  console.log('[PresentationGenerator] === REGENERATE SLIDE REQUEST ===');
  console.log('[PresentationGenerator] presentationId:', presentationId);
  console.log('[PresentationGenerator] slideIndex:', slideIndex);
  console.log('[PresentationGenerator] slideType:', slideType);

  if (!presentationId || !companyId || slideIndex === undefined || slideIndex === null) {
    res.status(400).json({ error: 'presentationId, companyId, and slideIndex are required' });
    return;
  }

  try {
    const { Presentation } = getModels();
    const presentation = await Presentation.findById(presentationId);

    if (!presentation) {
      res.status(404).json({ error: 'Presentation not found' });
      return;
    }

    const presentationData = presentation.toObject();
    const slides = presentationData.slides || [];

    if (slideIndex < 0 || slideIndex >= slides.length) {
      res.status(400).json({ error: `slideIndex ${slideIndex} is out of range (0-${slides.length - 1})` });
      return;
    }

    const currentSlide = slides[slideIndex];
    const currentSlideType = slideType || currentSlide.slideType || currentSlide.type || 'content';
    const currentSlideTitle = currentSlide.title || `Slide ${slideIndex + 1}`;

    // Build context from adjacent slides for continuity
    const prevSlide = slideIndex > 0 ? { title: slides[slideIndex - 1].title, type: slides[slideIndex - 1].slideType || slides[slideIndex - 1].type } : null;
    const nextSlide = slideIndex < slides.length - 1 ? { title: slides[slideIndex + 1].title, type: slides[slideIndex + 1].slideType || slides[slideIndex + 1].type } : null;

    // Get business profile for context
    let brandStrategy: any = null;
    let visualIdentityData: any = null;
    try {
      const { ModuleData } = getModels();
      brandStrategy = await ModuleData.findOne({ moduleId: 'brand-strategy', companyId });
      visualIdentityData = await ModuleData.findOne({ moduleId: 'visual-identity', companyId });
    } catch {}

    const presentationType = slideContext?.presentationType || presentationData.type || 'company-profile';
    const tone = slideContext?.tone || 'professional';
    const templateStyle = slideContext?.templateStyle || 'modern';
    const language = slideContext?.language || presentationData.language || 'en';

    // Build focused prompt for single-slide regeneration
    const languageInstruction = buildLanguageInstruction(language);
    console.log('[PresentationGenerator] Regenerate-slide language:', language, '→ instruction length:', languageInstruction.length);
    const systemPrompt = `You are an elite presentation designer and content strategist. Generate compelling, professional content for a SINGLE slide within a larger presentation.

SLIDE TYPE GUIDELINES:
- title/cover: Impactful headline, minimal text, strong visual presence
- content: Clear section header, 3-5 key points with supporting detail
- section: Bold section number, concise title, brief subtitle
- cta: Compelling call-to-action with clear value proposition
- closing: Thank you message, contact information, next steps
- market: Include market data with chart-friendly format
- financials: Include financial projections with numeric data points
- team: Include team member details (name, role, brief bio)
- competition: Include competitive comparison data
- client-portfolio: Include client/project names with brief descriptions` + languageInstruction;

    const adjacencyContext = [
      prevSlide ? `Previous slide: "${prevSlide.title}" (${prevSlide.type})` : 'This is the first slide',
      nextSlide ? `Next slide: "${nextSlide.title}" (${nextSlide.type})` : 'This is the last slide',
    ].join('\n');

    const slidePrompt = `Regenerate content for Slide ${slideIndex + 1} of ${slides.length} in a presentation titled "${presentationData.title || 'Untitled'}".

**Presentation Context:**
- Type: ${presentationType}
- Tone: ${tone}
- Template Style: ${templateStyle}

**Adjacent Slides (for narrative continuity):**
${adjacencyContext}

**Current Slide:**
- Index: ${slideIndex + 1} of ${slides.length}
- Title: "${currentSlideTitle}"
- Type: ${currentSlideType}

${customInstructions ? `**Custom Instructions:** ${customInstructions}` : ''}

Generate complete, professional content for THIS SLIDE ONLY. Return a JSON object:

\`\`\`json
{
  "title": "Clear, Impactful Title",
  "subtitle": "Supporting subtitle (optional)",
  "content": "<h3>Section Header</h3><p>Opening statement.</p><ul><li>Key point 1</li><li>Key point 2</li></ul>",
  "keyPoints": ["Point 1", "Point 2", "Point 3"],
  "visualDescription": "Description of visual/imagery for this slide",
  "chartData": null,
  "speakerNotes": "Detailed speaker notes with talking points...",
  "slideType": "${currentSlideType}",
  "layout": "single-column",
  "ctaLabel": "Button text (for CTA slides only)",
  "contactInfo": "Contact info (for closing slides only)"
}
\`\`\`

**CRITICAL:**
- Generate COMPLETE, DETAILED content — NO placeholder text
- Ensure narrative continuity with adjacent slides
- Return ONLY valid JSON, no additional text
- Content must be substantive and professionally written`;

    // Generate the slide content — use Ollama locally for reliable generation
    const result = await generateWithAI(slidePrompt, systemPrompt, 8000, 0.7, 'json', 'ollama', undefined, undefined, undefined, true);

    let slideData: any = null;
    if (result && result.content) {
      try {
        slideData = JSON.parse(result.content);
      } catch {
        const match = result.content.match(/\{[\s\S]*"content"[\s\S]*\}/);
        if (match) {
          try {
            slideData = JSON.parse(match[0]);
          } catch {
            console.warn('[PresentationGenerator] Failed to parse regenerated slide JSON');
          }
        }
      }
    }

    if (!slideData) {
      res.status(500).json({ error: 'Failed to generate slide content' });
      return;
    }

    // Merge with existing slide data
    const updatedSlide = {
      ...currentSlide,
      id: currentSlide.id || currentSlide._id || `slide-${slideIndex + 1}`,
      order: currentSlide.order ?? slideIndex,
      type: normalizeSlideType(slideData.slideType || currentSlide.slideType || currentSlide.type),
      title: slideData.title || currentSlide.title,
      subtitle: slideData.subtitle || currentSlide.subtitle || '',
      content: slideData.content || currentSlide.content || '',
      notes: slideData.speakerNotes || currentSlide.notes || '',
      slideType: normalizeSlideType(slideData.slideType || currentSlide.slideType || currentSlide.type),
      layout: slideData.layout || currentSlide.layout || 'single-column',
      status: 'final',
      aiGenerated: true,
      generatedAt: new Date(),
    };

    // Update the slide in the presentation
    slides[slideIndex] = updatedSlide;
    presentation.slides = slides;

    // Update generatedContent status
    if (presentation.generatedContent) {
      const gc = presentation.generatedContent as any;
      if (gc.slides && Array.isArray(gc.slides)) {
        gc.slides[slideIndex] = updatedSlide;
      }
    }

    await presentation.save();

    // Regenerate the HTML preview
    const presentationConfig: PresentationGenerationConfig = slideContext?.config || {
      visualIdentity: { mode: 'datasource', primaryColor: '', secondaryColor: '', accentColor: '', headingFont: '', bodyFont: '', visualVibe: '' },
      presentationType: presentationType,
      templateStyle,
      toneOfVoice: tone,
      contentDensity: 'balanced',
      visualDesign: { animationStyle: 'subtle', transitionStyle: 'fade', includeIcons: true, includeCharts: true, includeDiagrams: false, customDesignInstructions: '' },
      slideFormatting: { maxBulletsPerSlide: 6, textDensity: 'standard', includeSpeakerNotes: true, includeCtaSlides: true, includeTransitionSlides: false, bulletStyle: '•' },
      aiInstructions: { systemInstructions: '', writingConstraints: '', dosDonts: '', additionalGuidance: '' },
    };

    const briefContentStr = slideContext?.briefContent || '';
    const html = generatePresentationHTML(presentationData, slides, briefContentStr, presentationConfig);
    const htmlPath = getHtmlPath(presentationId);
    fs.writeFileSync(htmlPath, html, 'utf-8');

    // Update metadata
    writeMetadata(presentationId, {
      ...readMetadata(presentationId),
      updatedAt: new Date().toISOString(),
    });

    console.log(`[PresentationGenerator] Slide ${slideIndex + 1} regenerated for presentation: ${presentationId}`);

    res.json({
      success: true,
      slide: updatedSlide,
      slideIndex,
    });

  } catch (error: any) {
    console.error('[PresentationGenerator] Regenerate slide error:', error);
    res.status(500).json({ error: error.message || 'Failed to regenerate slide' });
  }
});

// ============================================
// SAVE MANUALLY EDITED HTML
// ============================================

const MAX_EDITED_HTML_BYTES = 5 * 1024 * 1024; // 5 MB

/**
 * PUT /presentation-html/:presentationId — Persist manually edited presentation HTML.
 * Body: { companyId, html }.
 *
 * Mirrors the landing-page /page-html endpoint. Theme override blocks are
 * stripped from the incoming HTML so they are never double-baked — the preview
 * and theme-customisation panel re-inject them at render time.
 */
router.put('/presentation-html/:presentationId', authenticate, async (req: Request, res: Response) => {
  const { presentationId } = req.params;
  const { companyId, html } = req.body;

  if (!companyId) {
    res.status(400).json({ error: 'companyId is required' });
    return;
  }
  if (typeof html !== 'string') {
    res.status(400).json({ error: 'html must be a string' });
    return;
  }
  if (Buffer.byteLength(html, 'utf-8') > MAX_EDITED_HTML_BYTES) {
    res.status(413).json({ error: 'Edited presentation is too large to save' });
    return;
  }
  // Guard against a truncated / non-document payload overwriting a working page.
  if (!/<html[\s>]/i.test(html) || !/<\/html>/i.test(html) || !/<body[\s>]/i.test(html)) {
    res.status(400).json({ error: 'html must be a complete HTML document' });
    return;
  }

  try {
    // Ownership check — the presentation must belong to the caller's company.
    const { Presentation } = getModels();
    const presentation = await Presentation.findById(presentationId);
    if (!presentation) {
      res.status(404).json({ error: 'Presentation not found' });
      return;
    }
    if (!req.user!.companyIds.includes(presentation.companyId) && req.user!.role !== 'admin') {
      res.status(403).json({ error: 'Access denied' });
      return;
    }

    const htmlPath = getHtmlPath(presentationId);
    if (!fs.existsSync(htmlPath)) {
      res.status(404).json({ error: 'No generated presentation found for this presentation' });
      return;
    }

    // Strip theme override blocks from the incoming HTML so we never double-bake
    // the customisation. The preview endpoint and the theme-customisation panel
    // re-inject them at render time.
    let finalHtml = html;
    // Remove <style id="mengo-theme-override">...</style> blocks
    finalHtml = finalHtml.replace(/<style\s+id=["']mengo-theme-override["'][^>]*>[\s\S]*?<\/style>/gi, '');
    // Remove <script id="mengo-cta-behavior">...</script> blocks
    finalHtml = finalHtml.replace(/<script\s+id=["']mengo-cta-behavior["'][^>]*>[\s\S]*?<\/script>/gi, '');

    fs.mkdirSync(getPresentationDir(presentationId), { recursive: true });
    fs.writeFileSync(htmlPath, finalHtml, 'utf-8');

    console.log(`[PresentationGenerator] Saved manual edits for presentation ${presentationId} (${finalHtml.length} chars)`);
    res.json({ success: true, size: finalHtml.length });
  } catch (error: unknown) {
    const err = error as Error;
    console.error('[PresentationGenerator] Error saving edited presentation HTML:', err?.message || err, err?.stack);
    res.status(500).json({ error: 'Failed to save presentation edits', details: err?.message });
  }
});

export default router;