/**
 * Newsletter AI Context Routes
 *
 * API endpoints for Newsletter Content OS AI generation.
 * POST /auto-fill — generate newsletter data from company context
 * POST /regenerate — regenerate newsletter data using existing context (avoids duplicate titles)
 */

import express, { Request, Response } from 'express';
import { body, validationResult } from 'express-validator';
import { authenticate } from '../middleware/auth';
import { requirePermission } from '../middleware/permissions';
import { NewsletterPipeline } from '../services/aiContext/newsletterPipeline';
import { NewsletterPipelineInputs } from '../services/aiContext/newsletterPrompts';
import { aiContextService, computeNewsletterAutoFillMapping } from '../services/aiContext/aiContextService';
import { getModels } from '../models';
import { createJob, updateJobProgress, completeJob, failJob, getJob } from '../services/aiContext/aiJobManager';
import { resolveModelSlug } from '../utils/aiProvider';

const router = express.Router();
router.use(authenticate);

function resolvePreferredModelSelection(body: any) {
  const preferredModel = body.preferredModel || body.aiModel || body.formData?.preferredModel || body.formData?.aiModel;
  const resolved = preferredModel ? resolveModelSlug(preferredModel) : null;
  return {
    preferredModel: preferredModel || undefined,
    preferredProvider: resolved?.provider || undefined,
  };
}

/**
 * Turn the selected email template's structure — sent by the client, which
 * reads it from the design's own `{{token}}` placeholders — into an instruction
 * the pipeline can act on.
 *
 * This is what stops the auto-fill stage from outlining a generic newsletter:
 * it states the design's sections, their order and how many content slots exist,
 * so the titles and outlines it produces already fit the chosen template.
 *
 * Returns '' for a missing or structure-less template, which leaves the previous
 * behaviour exactly as it was.
 */
function describeTemplateForPrompt(templateStructure: any): string {
  const areas = Array.isArray(templateStructure?.contentAreas) ? templateStructure.contentAreas : [];
  if (areas.length === 0) return '';

  const byGroup = new Map<string, string[]>();
  for (const area of areas) {
    if (!area?.token || !area?.label) continue;
    const group = String(area.group || 'Content');
    const list = byGroup.get(group) || [];
    list.push(`${area.label} ({{${area.token}}})`);
    byGroup.set(group, list);
  }
  if (byGroup.size === 0) return '';

  const lines = Array.from(byGroup.entries()).map(([group, fields]) => `- ${group}: ${fields.join(', ')}`);
  const slots = Number(templateStructure?.contentSlots) || 0;

  return [
    `SELECTED EMAIL TEMPLATE — "${templateStructure?.name || 'Selected template'}".`,
    'Every newsletter must be written for this design\'s sections, in this order:',
    ...lines,
    slots > 0 ? `The design has ${slots} content slot(s) — plan that many content sections, no more.` : '',
    'Do not plan sections, imagery or layout the design does not provide.',
  ].filter(Boolean).join('\n');
}

/** "product-awareness" → "Product Awareness" — dropdown slugs read as labels. */
function humaniseSlug(value: any): string {
  return String(value || '')
    .replace(/[-_]/g, ' ')
    .replace(/\s+/g, ' ')
    .trim()
    .replace(/\b\w/g, (c) => c.toUpperCase());
}

function slugList(value: any): string {
  const list = Array.isArray(value) ? value : value ? [value] : [];
  return list.map(humaniseSlug).filter(Boolean).join(', ');
}

/**
 * The campaign brief, built from what the user actually typed and selected in
 * the "Generate with AI" form.
 *
 * This is the fix for generated newsletters reading as generic company content:
 * the pipeline treats `customInstructions` as its highest-priority input, but the
 * client only ever sent `formData`/`campaignData` — so the user's prompt, campaign
 * name, type, goals and tones never reached the prompt and the model had nothing
 * to work from but the derived company/ICP/brand context.
 *
 * Built server-side from the request that is already being sent, so every caller
 * of this endpoint benefits and no field can be silently dropped by the client.
 */
function buildCampaignBrief(formData: any, campaignData: any): string {
  const fd = formData || {};
  const cd = campaignData || {};

  // The user's own "What should the AI write about?" text is the primary
  // instruction and is stated on its own, ahead of the configuration.
  const userPrompt = String(fd.prompt || cd.description || '').trim();

  const lines: string[] = [];
  const add = (label: string, value: any) => {
    const text = typeof value === 'string' ? value.trim() : value;
    if (text) lines.push(`- ${label}: ${text}`);
  };

  add('Campaign Name', fd.name || cd.name);
  add('Campaign Type', humaniseSlug(fd.campaignType || cd.campaignType));
  add('Business Goals', slugList(fd.goals || cd.goal));
  add('Communication Tone', slugList(fd.tones || cd.tone));
  add('Target Audience', fd.targetAudience || cd.targetAudience);
  add('Primary CTA', fd.primaryCTA || cd.primaryCTA);
  add('Subject Line Style', humaniseSlug(fd.subjectLineStyle || cd.subjectLineStyle));
  add('Newsletter Types', slugList(cd.newsletterTypes));
  add('Frequency', humaniseSlug(fd.frequency || cd.frequency));
  add('Start Date', fd.startDate || cd.startDate);
  add('End Date', fd.endDate || cd.endDate);
  add('Language', fd.language || cd.language);
  add('Linked Data Sources', slugList(fd.dataSources || cd.dataSources));

  const typeRule = (() => {
    const type = String(fd.campaignType || cd.campaignType || '');
    if (type === 'email-nurturing') return 'This is an EMAIL NURTURING sequence: each email must move the reader one step further along the journey, building trust toward the CTA.';
    if (type === 'hybrid') return 'This is a HYBRID campaign: regular newsletter value combined with a nurturing intent that progresses across the emails.';
    return 'This is a NEWSLETTER: each edition must deliver standalone value on its own theme.';
  })();

  const parts = [
    userPrompt
      ? `WHAT THE USER ASKED FOR (their own words — this is the topic of every newsletter you write; follow it exactly and do not substitute generic company content):\n${userPrompt}`
      : '',
    lines.length > 0 ? `CAMPAIGN CONFIGURATION THE USER SELECTED (every item must be honoured):\n${lines.join('\n')}` : '',
    typeRule,
    'Write to the goals and tone listed above: the goals decide what the content argues for, the tone decides how it reads.',
  ].filter(Boolean);

  return parts.join('\n\n');
}

router.get(
  '/status/:jobId',
  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,
    });
  }
);

// ============================================
// POST /auto-fill
// ============================================

router.post(
  '/auto-fill',
  requirePermission('newsletter-content-os', 'ai-generate'),
  [
    body('companyId').notEmpty().withMessage('Company ID is required'),
  ],
  async (req: Request, res: Response) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      res.status(400).json({ error: 'Validation failed', details: errors.array() });
      return;
    }

    const { companyId, customInstructions } = req.body;
    // Structure of the email template chosen in the wizard's first step, as read
    // from the design's own placeholders by the client.
    const templateBrief = describeTemplateForPrompt(req.body.templateStructure);
    const language = req.body.language || req.body.formData?.language || 'English';
    const { preferredProvider } = resolvePreferredModelSelection(req.body);

    // How many emails the campaign actually contains. The pipeline used to be
    // asked for a hardcoded 5 titles while the campaign could hold any number,
    // and the enrichment below pairs titles to emails BY INDEX — so a 7-email
    // campaign got 5 filled and 2 left as empty stubs with no subject or body.
    // The client builds the stubs from the campaign dates + frequency, so their
    // count is the number the AI must produce.
    const requestedEmailCount = Array.isArray(req.body.campaignData?.sequenceEmails)
      ? req.body.campaignData.sequenceEmails.length
      : 0;

    const job = createJob('newsletter', companyId, req.body._moduleId);

    // Return jobId immediately so the frontend can poll
    res.status(202).json({ jobId: job.jobId, status: 'processing' });

    // Run pipeline in background
    setImmediate(async () => {
      try {
        const { Company, BusinessProfile, ICP } = getModels();
        const company = await Company.findById(companyId);
        if (!company) {
          failJob(job.jobId, 'Company not found');
          return;
        }

        let businessProfile: any = null;
        try { businessProfile = await BusinessProfile.findOne({ companyId }); } catch {}

        const companyContexts = await aiContextService.getByCompany(companyId, 'company-creation');
        const latestCompanyContext = companyContexts.find((c: any) => {
          const a = c.analysis?.toObject?.() || c.analysis || {};
          return c.status === 'approved' || a.industryType || a.businessModel || a.businessSummary;
        });

        let icpData: any = null;
        try { icpData = await ICP.findOne({ companyId, isActive: true }).sort({ createdAt: -1 }); } catch {}

        // Load brand strategy context
        let brandStrategyData: any = null;
        try {
          const { ModuleData } = getModels();
          const brandStrategyDoc = await ModuleData.findOne({ moduleId: 'brand-strategy', companyId });
          if (brandStrategyDoc?.data) {
            brandStrategyData = brandStrategyDoc.data;
          }
        } catch {}

        // Load existing newsletter titles to avoid duplicates
        let existingNewsletterTitles: string[] = [];
        try {
          const { NewsletterContentOS } = getModels();
          const newsletterData = await NewsletterContentOS.findOne({ companyId });
          if (newsletterData?.titles && Array.isArray(newsletterData.titles)) {
            existingNewsletterTitles = newsletterData.titles.map((t: any) => t.title || t.subjectLine).filter(Boolean);
          }
        } catch {}

        // Build pipeline inputs
        const pipelineInputs: NewsletterPipelineInputs = {
          // The user's own brief from the "Generate with AI" popup or the AI Chat
          // generation flow. Stated as the highest-priority instruction in the
          // prompt, so it wins over the derived company context.
          // Everything the user typed or selected, then the design's structure.
          // The pipeline states this block first and marks it authoritative, so
          // it wins over the derived company/ICP/brand context below.
          customInstructions:
            [
              typeof customInstructions === 'string' ? customInstructions.trim() : '',
              buildCampaignBrief(req.body.formData, req.body.campaignData),
              templateBrief,
            ]
              .filter(Boolean)
              .join('\n\n') || undefined,
          companyName: company.name,
          companyDescription: company.description || businessProfile?.description || undefined,
          companyIndustry: businessProfile?.primaryIndustry || undefined,
          companyBusinessModel: businessProfile?.businessModel || undefined,
          companyTargetAudience: undefined,
          companyPrimaryOffering: undefined,
          companyUsps: undefined,
          targetNewsletterCount: requestedEmailCount || 5,
          existingNewsletterTitles: existingNewsletterTitles.length > 0 ? existingNewsletterTitles : undefined,
          language,
        };

        if (latestCompanyContext) {
          const analysis = (latestCompanyContext as any).analysis?.toObject?.() || (latestCompanyContext as any).analysis || {};
          if (analysis.industryType && !pipelineInputs.companyIndustry) pipelineInputs.companyIndustry = analysis.industryType;
          if (analysis.businessModel && !pipelineInputs.companyBusinessModel) pipelineInputs.companyBusinessModel = analysis.businessModel;
          if (analysis.targetAudience?.primary) pipelineInputs.companyTargetAudience = analysis.targetAudience.primary;
          if (analysis.primaryOffering) pipelineInputs.companyPrimaryOffering = analysis.primaryOffering;
          if (analysis.uspSuggestions?.length) pipelineInputs.companyUsps = analysis.uspSuggestions;
        }

        if (icpData) {
          pipelineInputs.icpName = icpData.name || undefined;
          pipelineInputs.icpIndustry = icpData.industry || undefined;
          pipelineInputs.icpCompanySize = icpData.companySize || undefined;
          pipelineInputs.icpPainPoints = icpData.painPoints || undefined;
          pipelineInputs.icpBusinessGoals = icpData.businessGoals || undefined;
        }

        if (brandStrategyData) {
          pipelineInputs.brandArchetype = brandStrategyData.brandArchetype || undefined;
          pipelineInputs.brandPersonality = brandStrategyData.brandPersonality || undefined;
          pipelineInputs.brandValues = brandStrategyData.brandValues || undefined;
          pipelineInputs.brandPositioning = brandStrategyData.brandPositioning || undefined;
          pipelineInputs.brandVoice = brandStrategyData.brandVoice || undefined;
        }

        updateJobProgress(job.jobId, 10, 'Preparing context...');
        const pipeline = new NewsletterPipeline(pipelineInputs, (progress, step) => { updateJobProgress(job.jobId, progress, step); });
        const result = await pipeline.run();

        const context = await aiContextService.create({
          companyId,
          moduleSource: 'newsletter',
          analysisType: 'full-analysis',
          inputs: { companyName: company.name, description: pipelineInputs.companyDescription },
          analysis: result.analysis,
          metadata: {
            pipelineVersion: result.pipelineVersion,
            provider: result.provider,
            model: result.aiModel,
            tokensUsed: result.tokensUsed,
            inputTokens: result.inputTokens,
            outputTokens: result.outputTokens,
            processingTimeMs: result.processingTimeMs,
            latencyMs: result.latencyMs,
            overallConfidence: result.overallConfidence,
            fieldConfidences: result.fieldConfidences,
            finishReason: result.finishReason,
            apiKeyMasked: result.apiKeyMasked,
          },
        });

        await aiContextService.updateStatus(context.id, 'approved');

        const autoFillData = computeNewsletterAutoFillMapping(result.analysis, language);

        // Build the generatedCampaign from formData + AI-generated content
        const formData = req.body.formData || {};
        const campaignData = req.body.campaignData || {};

        // Map AI-generated titles/enhancements into sequence email stubs
        // so that subject lines, preview text, content outlines, and CTAs are populated
        const aiTitles: any[] = Array.isArray(result.analysis?.titles) ? result.analysis.titles : [];
        const aiEnhancements: any[] = Array.isArray(result.analysis?.contentEnhancements) ? result.analysis.contentEnhancements : [];
        const sequenceEmailStubs: any[] = Array.isArray(campaignData.sequenceEmails) ? campaignData.sequenceEmails : [];

        // Used only by the fallback below, when the model returns fewer titles
        // than there are emails.
        const campaignName = formData.name || campaignData.name || 'Newsletter';
        const strategyNotes = result.analysis?.newsletterStrategyNotes || formData.prompt || '';

        const enrichedSequenceEmails = sequenceEmailStubs.map((email: any, index: number) => {
          const title = aiTitles[index];
          const enhancement = aiEnhancements[index];

          // The model can still return fewer items than asked for. Rather than
          // leaving that email with an empty subject and body, fall back to the
          // campaign's own context for this slot so every generated email is
          // usable and the gap is obvious to edit.
          if (!title) {
            const slot = index + 1;
            return {
              ...email,
              subjectLine: email.subjectLine || `${campaignName}: Part ${slot}`,
              previewText: email.previewText || (email.objective ? String(email.objective) : ''),
              cta: email.cta || formData.primaryCTA || campaignData.primaryCTA || '',
              contentOutline: email.contentOutline || (email.objective ? String(email.objective) : ''),
              contentBrief: email.contentBrief || strategyNotes || '',
              generatedContent: email.generatedContent || strategyNotes || '',
            };
          }

          // Build content sections from AI enhancement data
          let generatedContent = '';
          if (enhancement?.contentOutline) {
            generatedContent += enhancement.contentOutline + '\n\n';
          }
          if (enhancement?.contentBrief) {
            generatedContent += enhancement.contentBrief + '\n\n';
          }
          if (Array.isArray(enhancement?.sectionSuggestions) && enhancement.sectionSuggestions.length > 0) {
            generatedContent += enhancement.sectionSuggestions.map((s: any, si: number) =>
              `${si + 1}. [${s.type || 'section'}] ${s.content || ''}`
            ).join('\n');
          }

          return {
            ...email,
            subjectLine: title.subjectLine || title.title || email.subjectLine || '',
            previewText: title.previewText || email.previewText || '',
            cta: title.suggestedCTA || (enhancement?.suggestedCTA) || email.cta || '',
            tone: title.style || email.tone || 'professional',
            ...(generatedContent ? { generatedContent } : {}),
            // Also store content outline and brief for richer display
            contentOutline: enhancement?.contentOutline || title.contentOutline || '',
            contentBrief: enhancement?.contentBrief || '',
            recommendedWordCount: enhancement?.recommendedWordCount || title.recommendedWordCount,
          };
        });

        const generatedCampaign = {
          ...campaignData,
          // Override with AI-generated fields
          aiSummary: result.analysis?.newsletterStrategyNotes || formData.prompt,
          // Preserve form data fields
          name: formData.name || campaignData.name || 'AI Generated Newsletter',
          description: formData.prompt || campaignData.description,
          campaignType: formData.campaignType || campaignData.campaignType,
          goal: formData.goals || campaignData.goal || ['education'],
          tone: formData.tones || campaignData.tone || ['professional'],
          targetAudience: formData.targetAudience || campaignData.targetAudience,
          primaryCTA: formData.primaryCTA || campaignData.primaryCTA,
          startDate: formData.startDate || campaignData.startDate,
          endDate: formData.endDate || campaignData.endDate,
          frequency: formData.frequency || campaignData.frequency,
          sendingTime: formData.sendingTime || campaignData.sendingTime,
          timezone: formData.timezone || campaignData.timezone,
          language: formData.language || language || 'English',
          // Generated content
          generatedContent: result.analysis?.newsletterContent || '',
          generatedSections: {},
          contentAiMetadata: {
            model: result.aiModel || 'unknown',
            provider: result.provider || 'unknown',
            inputTokens: result.inputTokens || 0,
            outputTokens: result.outputTokens || 0,
            tokensUsed: result.tokensUsed || 0,
            // Estimate cost: Claude Sonnet ~$3 per million input tokens, ~$15 per million output tokens
            cost: ((result.inputTokens || 0) * 0.000003 + (result.outputTokens || 0) * 0.000015),
            confidence: result.overallConfidence || 0.85,
            generatedAt: new Date().toISOString(),
            finishReason: result.finishReason || 'end_turn',
            pipelineVersion: result.pipelineVersion || '2.0',
            duration: result.processingTimeMs || 0,
            stageResults: result.stageResults?.length > 0 ? result.stageResults : [{
              stage: 'generation',
              success: result.errors?.length === 0,
              duration: result.processingTimeMs || 0,
              tokensUsed: result.tokensUsed || 0,
            }],
            errors: result.errors || [],
          },
          // Enriched sequence emails with AI-generated content
          sequenceEmails: enrichedSequenceEmails,
          newsletterContentBlocks: campaignData.newsletterContentBlocks || [],
          approvalStatus: 'draft',
        };

        completeJob(job.jobId, { ...autoFillData, generatedCampaign }, 'generated');

        console.log(`[Newsletter-AutoFill] Job ${job.jobId} completed. Provider: ${result.provider}, Model: ${result.aiModel}, Tokens: ${result.tokensUsed}, Errors: ${result.errors?.length || 0}`);
      } catch (err: any) {
        console.error(`[Newsletter-AutoFill] Job ${job.jobId} failed:`, err.message);
        console.error(`[Newsletter-AutoFill] Full error:`, err.stack || err);
        failJob(job.jobId, err.message || 'AI generation failed');
      }
    });
  }
);

// ============================================
// POST /regenerate
// ============================================

router.post(
  '/regenerate',
  requirePermission('newsletter-content-os', 'ai-generate'),
  [
    body('companyId').notEmpty().withMessage('Company ID is required'),
  ],
  async (req: Request, res: Response) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      res.status(400).json({ error: 'Validation failed', details: errors.array() });
      return;
    }

    const { companyId, existingNewsletterData } = req.body;
    const language = req.body.language || req.body.formData?.language || 'English';
    const { preferredProvider } = resolvePreferredModelSelection(req.body);

    // Same index-pairing as /auto-fill below, so it needs the same real count —
    // otherwise regenerating a campaign with more than 5 emails leaves the extras
    // untouched.
    const requestedEmailCount = Array.isArray(req.body.campaignData?.sequenceEmails)
      ? req.body.campaignData.sequenceEmails.length
      : 0;

    const job = createJob('newsletter', companyId, req.body._moduleId);
    res.status(202).json({ jobId: job.jobId, status: 'processing' });

    // Run pipeline in background
    setImmediate(async () => {
      try {
        const { Company, BusinessProfile, ICP } = getModels();
        const company = await Company.findById(companyId);
        if (!company) {
          failJob(job.jobId, 'Company not found');
          return;
        }

        let businessProfile: any = null;
        try { businessProfile = await BusinessProfile.findOne({ companyId }); } catch {}

        const companyContexts = await aiContextService.getByCompany(companyId, 'company-creation');
        const latestCompanyContext = companyContexts.find((c: any) => {
          const a = c.analysis?.toObject?.() || c.analysis || {};
          return c.status === 'approved' || a.industryType || a.businessModel || a.businessSummary;
        });

        let icpData: any = null;
        try { icpData = await ICP.findOne({ companyId, isActive: true }).sort({ createdAt: -1 }); } catch {}

        // Load brand strategy context
        let brandStrategyData: any = null;
        try {
          const { ModuleData } = getModels();
          const brandStrategyDoc = await ModuleData.findOne({ moduleId: 'brand-strategy', companyId });
          if (brandStrategyDoc?.data) {
            brandStrategyData = brandStrategyDoc.data;
          }
        } catch {}

        // Load existing newsletter titles to avoid duplicates
        let existingNewsletterTitles: string[] = [];
        try {
          const { NewsletterContentOS } = getModels();
          const newsletterData = await NewsletterContentOS.findOne({ companyId });
          if (newsletterData?.titles && Array.isArray(newsletterData.titles)) {
            existingNewsletterTitles = newsletterData.titles.map((t: any) => t.title || t.subjectLine).filter(Boolean);
          }
        } catch {}

        // Also use titles passed from frontend for regeneration
        if (existingNewsletterData?.titles && Array.isArray(existingNewsletterData.titles)) {
          const frontendTitles = existingNewsletterData.titles.map((t: any) => typeof t === 'string' ? t : (t.title || t.subjectLine)).filter(Boolean);
          existingNewsletterTitles = [...new Set([...existingNewsletterTitles, ...frontendTitles])];
        }

        // Build pipeline inputs with existing data for regeneration
        const pipelineInputs: NewsletterPipelineInputs = {
          companyName: company.name,
          companyDescription: company.description || businessProfile?.description || undefined,
          companyIndustry: businessProfile?.primaryIndustry || undefined,
          companyBusinessModel: businessProfile?.businessModel || undefined,
          companyTargetAudience: undefined,
          companyPrimaryOffering: undefined,
          companyUsps: undefined,
          targetNewsletterCount: requestedEmailCount || 5,
          existingNewsletterTitles: existingNewsletterTitles.length > 0 ? existingNewsletterTitles : undefined,
          language,
        };

        if (latestCompanyContext) {
          const analysis = (latestCompanyContext as any).analysis?.toObject?.() || (latestCompanyContext as any).analysis || {};
          if (analysis.industryType && !pipelineInputs.companyIndustry) pipelineInputs.companyIndustry = analysis.industryType;
          if (analysis.businessModel && !pipelineInputs.companyBusinessModel) pipelineInputs.companyBusinessModel = analysis.businessModel;
          if (analysis.targetAudience?.primary) pipelineInputs.companyTargetAudience = analysis.targetAudience.primary;
          if (analysis.primaryOffering) pipelineInputs.companyPrimaryOffering = analysis.primaryOffering;
          if (analysis.uspSuggestions?.length) pipelineInputs.companyUsps = analysis.uspSuggestions;
        }

        if (icpData) {
          pipelineInputs.icpName = icpData.name || undefined;
          pipelineInputs.icpIndustry = icpData.industry || undefined;
          pipelineInputs.icpCompanySize = icpData.companySize || undefined;
          pipelineInputs.icpPainPoints = icpData.painPoints || undefined;
          pipelineInputs.icpBusinessGoals = icpData.businessGoals || undefined;
        }

        if (brandStrategyData) {
          pipelineInputs.brandArchetype = brandStrategyData.brandArchetype || undefined;
          pipelineInputs.brandPersonality = brandStrategyData.brandPersonality || undefined;
          pipelineInputs.brandValues = brandStrategyData.brandValues || undefined;
          pipelineInputs.brandPositioning = brandStrategyData.brandPositioning || undefined;
          pipelineInputs.brandVoice = brandStrategyData.brandVoice || undefined;
        }

        updateJobProgress(job.jobId, 10, 'Preparing context...');
        const pipeline = new NewsletterPipeline(pipelineInputs, (progress, step) => { updateJobProgress(job.jobId, progress, step); });
        const result = await pipeline.run();

        const context = await aiContextService.create({
          companyId,
          moduleSource: 'newsletter',
          analysisType: 'full-analysis',
          inputs: { companyName: company.name, description: pipelineInputs.companyDescription },
          analysis: result.analysis,
          metadata: {
            pipelineVersion: result.pipelineVersion,
            provider: result.provider,
            model: result.aiModel,
            tokensUsed: result.tokensUsed,
            inputTokens: result.inputTokens,
            outputTokens: result.outputTokens,
            processingTimeMs: result.processingTimeMs,
            latencyMs: result.latencyMs,
            overallConfidence: result.overallConfidence,
            fieldConfidences: result.fieldConfidences,
            finishReason: result.finishReason,
            apiKeyMasked: result.apiKeyMasked,
          },
        });

        await aiContextService.updateStatus(context.id, 'approved');

        const autoFillData = computeNewsletterAutoFillMapping(result.analysis, language);

        // Build the generatedCampaign from formData + AI-generated content
        const formData = req.body.formData || {};
        const campaignData = req.body.campaignData || {};

        // Map AI-generated titles/enhancements into sequence email stubs
        // so that subject lines, preview text, content outlines, and CTAs are populated
        const aiTitles: any[] = Array.isArray(result.analysis?.titles) ? result.analysis.titles : [];
        const aiEnhancements: any[] = Array.isArray(result.analysis?.contentEnhancements) ? result.analysis.contentEnhancements : [];
        const sequenceEmailStubs: any[] = Array.isArray(campaignData.sequenceEmails) ? campaignData.sequenceEmails : [];

        // Used only by the fallback below, when the model returns fewer titles
        // than there are emails.
        const campaignName = formData.name || campaignData.name || 'Newsletter';
        const strategyNotes = result.analysis?.newsletterStrategyNotes || formData.prompt || '';

        const enrichedSequenceEmails = sequenceEmailStubs.map((email: any, index: number) => {
          const title = aiTitles[index];
          const enhancement = aiEnhancements[index];

          // The model can still return fewer items than asked for. Rather than
          // leaving that email with an empty subject and body, fall back to the
          // campaign's own context for this slot so every generated email is
          // usable and the gap is obvious to edit.
          if (!title) {
            const slot = index + 1;
            return {
              ...email,
              subjectLine: email.subjectLine || `${campaignName}: Part ${slot}`,
              previewText: email.previewText || (email.objective ? String(email.objective) : ''),
              cta: email.cta || formData.primaryCTA || campaignData.primaryCTA || '',
              contentOutline: email.contentOutline || (email.objective ? String(email.objective) : ''),
              contentBrief: email.contentBrief || strategyNotes || '',
              generatedContent: email.generatedContent || strategyNotes || '',
            };
          }

          // Build content sections from AI enhancement data
          let generatedContent = '';
          if (enhancement?.contentOutline) {
            generatedContent += enhancement.contentOutline + '\n\n';
          }
          if (enhancement?.contentBrief) {
            generatedContent += enhancement.contentBrief + '\n\n';
          }
          if (Array.isArray(enhancement?.sectionSuggestions) && enhancement.sectionSuggestions.length > 0) {
            generatedContent += enhancement.sectionSuggestions.map((s: any, si: number) =>
              `${si + 1}. [${s.type || 'section'}] ${s.content || ''}`
            ).join('\n');
          }

          return {
            ...email,
            subjectLine: title.subjectLine || title.title || email.subjectLine || '',
            previewText: title.previewText || email.previewText || '',
            cta: title.suggestedCTA || (enhancement?.suggestedCTA) || email.cta || '',
            tone: title.style || email.tone || 'professional',
            ...(generatedContent ? { generatedContent } : {}),
            // Also store content outline and brief for richer display
            contentOutline: enhancement?.contentOutline || title.contentOutline || '',
            contentBrief: enhancement?.contentBrief || '',
            recommendedWordCount: enhancement?.recommendedWordCount || title.recommendedWordCount,
          };
        });

        const generatedCampaign = {
          ...campaignData,
          // Override with AI-generated fields
          aiSummary: result.analysis?.newsletterStrategyNotes || formData.prompt,
          // Preserve form data fields
          name: formData.name || campaignData.name || 'AI Generated Newsletter',
          description: formData.prompt || campaignData.description,
          campaignType: formData.campaignType || campaignData.campaignType,
          goal: formData.goals || campaignData.goal || ['education'],
          tone: formData.tones || campaignData.tone || ['professional'],
          targetAudience: formData.targetAudience || campaignData.targetAudience,
          primaryCTA: formData.primaryCTA || campaignData.primaryCTA,
          startDate: formData.startDate || campaignData.startDate,
          endDate: formData.endDate || campaignData.endDate,
          frequency: formData.frequency || campaignData.frequency,
          sendingTime: formData.sendingTime || campaignData.sendingTime,
          timezone: formData.timezone || campaignData.timezone,
          language: formData.language || language || 'English',
          // Generated content
          generatedContent: result.analysis?.newsletterContent || '',
          generatedSections: {},
          contentAiMetadata: {
            model: result.aiModel || 'unknown',
            provider: result.provider || 'unknown',
            inputTokens: result.inputTokens || 0,
            outputTokens: result.outputTokens || 0,
            tokensUsed: result.tokensUsed || 0,
            // Estimate cost: Claude Sonnet ~$3 per million input tokens, ~$15 per million output tokens
            cost: ((result.inputTokens || 0) * 0.000003 + (result.outputTokens || 0) * 0.000015),
            confidence: result.overallConfidence || 0.85,
            generatedAt: new Date().toISOString(),
            finishReason: result.finishReason || 'end_turn',
            pipelineVersion: result.pipelineVersion || '2.0',
            duration: result.processingTimeMs || 0,
            stageResults: result.stageResults?.length > 0 ? result.stageResults : [{
              stage: 'generation',
              success: result.errors?.length === 0,
              duration: result.processingTimeMs || 0,
              tokensUsed: result.tokensUsed || 0,
            }],
            errors: result.errors || [],
          },
          // Enriched sequence emails with AI-generated content
          sequenceEmails: enrichedSequenceEmails,
          newsletterContentBlocks: campaignData.newsletterContentBlocks || [],
          approvalStatus: 'draft',
        };

        completeJob(job.jobId, { ...autoFillData, generatedCampaign }, 'regenerated');

        console.log(`[Newsletter-Regenerate] Job ${job.jobId} completed. Provider: ${result.provider}, Model: ${result.aiModel}, Tokens: ${result.tokensUsed}, Errors: ${result.errors?.length || 0}`);
      } catch (err: any) {
        console.error(`[Newsletter-Regenerate] Job ${job.jobId} failed:`, err.message);
        console.error(`[Newsletter-Regenerate] Full error:`, err.stack || err);
        failJob(job.jobId, err.message || 'AI regeneration failed');
      }
    });
  }
);

export default router;