/**
 * AiContext Service
 *
 * CRUD and query operations for AiContext documents.
 * Includes auto-fill mapping from AI analysis to BusinessProfile fields.
 */

import { getModels } from '../../models';
import { IAiContext, IAiContextInputs, IAiAnalysisResult, AiContextStatus } from '../../models/AiContext';
import { ScriptStatus } from '../../models/SalesScript';
import { trimToLength, trimArrayToCombinedLength } from '../../utils/textTrim';
import {
  normaliseAgeRange,
  normaliseEmployeeCount,
  normaliseYearsOfExperience,
} from '../../utils/numericValidation';

// ============================================
// SHARED TRIMMING HELPERS
// ============================================

/**
 * Coerces a confidence score into the 0–100 range the AiContext schema allows.
 * Non-numeric values become 0 rather than failing validation.
 */
function clampConfidence(value: unknown): number {
  const numeric = typeof value === 'number' ? value : Number(value);
  if (!isFinite(numeric)) return 0;
  return Math.min(100, Math.max(0, Math.round(numeric)));
}

/**
 * Trims a string field to its maxLength if it exceeds the limit.
 * Returns the trimmed string, or empty string if trimming yields meaningless content.
 */
function enforceCharLimit(value: string, maxLength: number): string {
  if (!value || typeof value !== 'string' || value.length <= maxLength) return value;
  const trimmed = trimToLength(value, maxLength);
  return trimmed !== null ? trimmed : '';
}

/**
 * Trims an array of strings so the combined length (joined by newlines)
 * does not exceed maxLength. Returns the original array if it fits.
 */
function enforceArrayCharLimit(items: string[], maxLength: number): string[] {
  if (!items || items.length === 0) return items;
  const combined = items.join('\n');
  if (combined.length <= maxLength) return items;
  const trimmed = trimArrayToCombinedLength(items, maxLength);
  return trimmed !== null ? trimmed : [];
}

// ============================================
// AUTO-FILL MAPPING
// ============================================

/**
 * Maps AI analysis results to BusinessProfile field values.
 * Returns a flat object with BusinessProfile-compatible field names.
 *
 * IMPORTANT: All BusinessProfile fields are always included in the mapping output,
 * even if empty. This ensures the frontend's sanitiseAutoFillData() receives every
 * key and can produce correct defaults, rather than silently omitting fields when
 * pipeline stages fail or return unexpected key names.
 */
export function computeAutoFillMapping(
  inputs: IAiContextInputs,
  analysis: IAiAnalysisResult
): Record<string, any> {
  const mapping: Record<string, any> = {};
  // Also accept common AI variations of field names
  const a = analysis as Record<string, any>;
  const inp = inputs as Record<string, any>;

  // ---- A. Basic Info ----
  if (inp.companyName) mapping.name = inp.companyName;

  // Description (Short): Prefer one-liner, derive from businessSummary if needed
  const shortDesc = a.shortDescription as string | undefined;
  const summary = analysis.businessSummary as string | undefined;
  const longDesc = analysis.description as string | undefined;

  if (shortDesc) {
    mapping.description = shortDesc;
  } else if (summary) {
    // Derive a short description from the first sentence of businessSummary
    const firstSentence = summary.split(/[.!?]\s/)[0];
    mapping.description = firstSentence.length > 100 ? firstSentence.substring(0, 97) + '...' : firstSentence + '.';
  } else {
    mapping.description = a.desc || a.summary || '';
  }

  // Description (Long): Prefer the detailed description, fall back to businessSummary
  if (longDesc && longDesc !== summary) {
    mapping.descriptionLong = longDesc;
  } else if (summary) {
    mapping.descriptionLong = summary;
  } else {
    mapping.descriptionLong = a.detailedDescription || a.longDescription || '';
  }

  if (inp.websiteUrl) mapping.website = inp.websiteUrl;

  // ---- B. Overview ----
  mapping.vision = analysis.vision || a.visionStatement || '';
  mapping.mission = analysis.mission || a.missionStatement || '';
  // coreValues: always produce a string (comma-separated from array)
  const coreValues = analysis.coreValues || a.values;
  if (Array.isArray(coreValues) && coreValues.length) {
    mapping.coreValues = coreValues.join(', ');
  } else {
    mapping.coreValues = typeof coreValues === 'string' ? coreValues : '';
  }
  // usp: always produce a string (take first suggestion or the string value)
  const uspSuggestions = analysis.uspSuggestions || a.usps || a.uniqueSellingPropositions;
  if (Array.isArray(uspSuggestions) && uspSuggestions.length) {
    mapping.usp = typeof uspSuggestions[0] === 'string' ? uspSuggestions[0] : String(uspSuggestions[0]);
  } else {
    mapping.usp = typeof uspSuggestions === 'string' ? uspSuggestions : '';
  }

  // ---- C. Market ----
  if (analysis.industryType) {
    mapping.primaryIndustry = normaliseIndustry(analysis.industryType);
  }
  // secondaryIndustries: categories minus primary industry, comma-separated string
  const allCategories: string[] = analysis.categories || [];
  if (mapping.primaryIndustry && allCategories.length > 1) {
    const primaryLower = mapping.primaryIndustry.toLowerCase();
    const secondary = allCategories
      .filter((c: string) => normaliseIndustry(c).toLowerCase() !== primaryLower)
      .map((c: string) => normaliseIndustry(c))
      .filter(Boolean);
    mapping.secondaryIndustries = [...new Set(secondary)].join(', ');
  } else if (allCategories.length > 0) {
    mapping.secondaryIndustries = allCategories.map((c: string) => normaliseIndustry(c)).join(', ');
  } else {
    mapping.secondaryIndustries = '';
  }
  // Always include targetGeography (even if empty) so it flows through sanitisation
  mapping.targetGeography = analysis.targetGeography || '';
  if (analysis.businessModel) {
    mapping.businessModel = normaliseBusinessModel(analysis.businessModel);
  }

  // ---- D. Offer Layer — always include keys even if empty ----
  mapping.primaryOffering = analysis.primaryOffering || a.primaryOffering || '';
  const secondaryOfferings = analysis.secondaryOfferings || a.secondaryOffering || a.offerings;
  if (Array.isArray(secondaryOfferings) && secondaryOfferings.length) {
    mapping.secondaryOfferings = secondaryOfferings.join(', ');
  } else {
    mapping.secondaryOfferings = typeof secondaryOfferings === 'string' ? secondaryOfferings : '';
  }
  mapping.pricingModel = analysis.pricingModelSuggestion
    ? normalisePricingModel(analysis.pricingModelSuggestion)
    : '';

  // ---- E. Industries array — map categories to BusinessProfile enum values ----
  const validIndustries = ['technology', 'healthcare', 'finance', 'education', 'ecommerce', 'saas', 'consulting', 'manufacturing', 'retail', 'real-estate', 'hospitality', 'media', 'non-profit', 'legal', 'marketing', 'design', 'food-beverage', 'sports', 'other'];
  if (allCategories.length) {
    const mapped = allCategories
      .map(c => normaliseIndustry(c).toLowerCase())
      .filter(c => validIndustries.includes(c));
    // Include primaryIndustry if available
    if (mapping.primaryIndustry) {
      const primary = mapping.primaryIndustry.toLowerCase();
      if (validIndustries.includes(primary) && !mapped.includes(primary)) {
        mapped.push(primary);
      }
    }
    mapping.industries = [...new Set(mapped)];
  } else if (mapping.primaryIndustry) {
    const primary = mapping.primaryIndustry.toLowerCase();
    mapping.industries = validIndustries.includes(primary) ? [primary] : ['other'];
  }

  // ---- Enforce character limits on Business Profile text fields ----
  mapping.description = enforceCharLimit(mapping.description, 300);
  mapping.descriptionLong = enforceCharLimit(mapping.descriptionLong, 4000);
  mapping.vision = enforceCharLimit(mapping.vision, 500);
  mapping.mission = enforceCharLimit(mapping.mission, 500);
  mapping.usp = enforceCharLimit(mapping.usp, 1500);
  mapping.coreValues = enforceCharLimit(mapping.coreValues, 500);
  mapping.primaryOffering = enforceCharLimit(mapping.primaryOffering, 500);
  mapping.secondaryOfferings = enforceCharLimit(mapping.secondaryOfferings, 1000);

  return mapping;
}

/**
 * Normalise AI-generated industry strings to BusinessProfile enum values.
 */
function normaliseIndustry(industry: string): string {
  const normalised = industry.toLowerCase().trim();

  const industryMap: Record<string, string> = {
    'technology': 'Technology',
    'tech': 'Technology',
    'it': 'Technology',
    'software': 'Technology',
    'saas': 'Technology',
    'healthcare': 'Healthcare',
    'health': 'Healthcare',
    'medical': 'Healthcare',
    'finance': 'Finance',
    'financial': 'Finance',
    'fintech': 'Finance',
    'banking': 'Finance',
    'education': 'Education',
    'edtech': 'Education',
    'e-commerce': 'E-commerce',
    'ecommerce': 'E-commerce',
    'retail': 'Retail',
    'consulting': 'Consulting',
    'professional services': 'Consulting',
    'manufacturing': 'Manufacturing',
    'real estate': 'Real Estate',
    'real-estate': 'Real Estate',
    'hospitality': 'Hospitality',
    'travel': 'Hospitality',
    'media': 'Media',
    'entertainment': 'Media',
    'non-profit': 'Non-Profit',
    'nonprofit': 'Non-Profit',
    'legal': 'Legal',
    'law': 'Legal',
    'marketing': 'Marketing',
    'advertising': 'Marketing',
    'design': 'Design',
    'creative': 'Design',
    'food & beverage': 'Food & Beverage',
    'food': 'Food & Beverage',
    'f&b': 'Food & Beverage',
    'sports': 'Sports',
    'fitness': 'Sports',
  };

  return industryMap[normalised] || industry;
}

/**
 * Normalise AI-generated business model strings to BusinessProfile enum values.
 * BusinessProfile enum: b2b, b2c, b2b2c, saas, marketplace, d2c, freemium, subscription, hybrid
 */
function normaliseBusinessModel(model: string): string {
  const normalised = model.toLowerCase().trim();

  const modelMap: Record<string, string> = {
    'b2b': 'b2b',
    'business-to-business': 'b2b',
    'b2c': 'b2c',
    'business-to-consumer': 'b2c',
    'b2b2c': 'b2b2c',
    'saas': 'saas',
    'software as a service': 'saas',
    'marketplace': 'marketplace',
    'd2c': 'd2c',
    'direct-to-consumer': 'd2c',
    'freemium': 'freemium',
    'subscription': 'subscription',
    'hybrid': 'hybrid',
  };

  return modelMap[normalised] || 'hybrid';
}

/**
 * Normalise AI-generated pricing model suggestions to BusinessProfile-compatible values.
 */
function normalisePricingModel(model: string): string {
  const normalised = model.toLowerCase().trim();

  const pricingMap: Record<string, string> = {
    'one-time': 'One-Time',
    'one time': 'One-Time',
    'subscription': 'Subscription',
    'recurring': 'Subscription',
    'freemium': 'Freemium',
    'usage-based': 'Usage-Based',
    'usage based': 'Usage-Based',
    'tiered': 'Tiered',
    'custom-quote': 'Custom Quote',
    'custom quote': 'Custom Quote',
    'commission': 'Commission',
    'hybrid': 'Hybrid',
  };

  return pricingMap[normalised] || model;
}

// ============================================
// ICP AUTO-FILL MAPPING
// ============================================

/**
 * Maps ICP pipeline output to ICP entity fields.
 * Normalises enum values to match the ICP model's valid options.
 *
 * @param currency Resolved pricing currency (user selection → Business Profile
 *   country). Used when the AI omits `primaryCurrency`, so the stored currency
 *   always matches the one its revenue figures were written in.
 */
export function computeICPAutoFillMapping(analysis: Record<string, any>, currency?: string): Record<string, any> {
  const mapping: Record<string, any> = {};

  if (currency) mapping.primaryCurrency = currency;

  // Direct string mappings
  if (analysis.name) mapping.name = analysis.name;
  if (analysis.description) mapping.description = analysis.description;
  if (analysis.industry) mapping.industry = analysis.industry;
  if (analysis.location) mapping.location = analysis.location;
  if (analysis.targetCountry) mapping.targetCountry = analysis.targetCountry;
  if (analysis.targetRegion) mapping.targetRegion = analysis.targetRegion;
  if (analysis.targetCity) mapping.targetCity = analysis.targetCity;
  if (analysis.marketLocation) mapping.marketLocation = analysis.marketLocation;
  if (analysis.primaryCurrency) mapping.primaryCurrency = analysis.primaryCurrency;
  if (analysis.revenueRange) mapping.revenueRange = analysis.revenueRange;
  // Employee count is a numeric field (see the ICP route guard): a generated
  // "50-200" or "1,500 employees" is reduced to a whole number so the value both
  // saves and satisfies the same rule a hand-typed one does.
  if (analysis.employeeCount) {
    const employeeCount = normaliseEmployeeCount(analysis.employeeCount);
    if (employeeCount !== null) mapping.employeeCount = String(employeeCount);
  }

  // Normalised enum fields
  if (analysis.companySize) {
    mapping.companySize = normaliseCompanySize(analysis.companySize);
  }
  if (analysis.fundingStage) {
    mapping.fundingStage = normaliseFundingStage(analysis.fundingStage);
  }
  if (analysis.budgetAuthority) {
    mapping.budgetAuthority = normaliseBudgetAuthority(analysis.budgetAuthority);
  }
  if (analysis.priceSensitivity) {
    mapping.priceSensitivity = normalisePriceSensitivity(analysis.priceSensitivity);
  }
  if (analysis.priority) {
    mapping.priority = normalisePriority(analysis.priority);
  }

  // Number fields
  if (analysis.yearsInBusiness) {
    const years = parseInt(String(analysis.yearsInBusiness), 10);
    if (!isNaN(years) && years >= 0) mapping.yearsInBusiness = years;
  }
  if (analysis.fitScore) {
    const score = parseInt(String(analysis.fitScore), 10);
    if (!isNaN(score) && score >= 0 && score <= 100) mapping.fitScore = score;
  }

  // String fields
  if (analysis.buyingProcess) mapping.buyingProcess = analysis.buyingProcess;
  if (analysis.decisionTimeframe) mapping.decisionTimeframe = analysis.decisionTimeframe;

  // Array fields — convert comma-separated strings to arrays
  if (analysis.techStack) mapping.techStack = ensureArray(analysis.techStack);
  if (analysis.toolsUsed) mapping.toolsUsed = ensureArray(analysis.toolsUsed);
  if (analysis.platforms) mapping.platforms = ensureArray(analysis.platforms);
  if (analysis.businessGoals) mapping.businessGoals = ensureArray(analysis.businessGoals);
  if (analysis.challenges) mapping.challenges = ensureArray(analysis.challenges);
  if (analysis.painPoints) mapping.painPoints = ensureArray(analysis.painPoints);
  if (analysis.priorities) mapping.priorities = ensureArray(analysis.priorities);
  if (analysis.triggerEvents) mapping.triggerEvents = ensureArray(analysis.triggerEvents);

  // Default isActive
  mapping.isActive = true;

  // ---- Enforce character limits on ICP text fields ----
  mapping.description = enforceCharLimit(mapping.description, 2000);
  mapping.buyingProcess = enforceCharLimit(mapping.buyingProcess, 2000);
  mapping.decisionTimeframe = enforceCharLimit(mapping.decisionTimeframe, 2000);
  if (Array.isArray(mapping.businessGoals)) mapping.businessGoals = enforceArrayCharLimit(mapping.businessGoals, 2000);
  if (Array.isArray(mapping.challenges)) mapping.challenges = enforceArrayCharLimit(mapping.challenges, 2000);
  if (Array.isArray(mapping.painPoints)) mapping.painPoints = enforceArrayCharLimit(mapping.painPoints, 2000);
  if (Array.isArray(mapping.priorities)) mapping.priorities = enforceArrayCharLimit(mapping.priorities, 2000);

  return mapping;
}

// ============================================
// PERSONA AUTO-FILL MAPPING
// ============================================

/** Hard cap from the Persona model's `name` maxlength. */
const PERSONA_NAME_MAX_LENGTH = 100;

/**
 * Values that are technically a string but are not a name: the model echoing
 * the schema description back ("string — catchy persona name, e.g. ..."), or a
 * placeholder standing in for one.
 */
const PERSONA_NAME_PLACEHOLDERS = new Set([
  'name', 'persona', 'persona name', 'buyer persona', 'string',
  'null', 'undefined', 'n/a', 'na', 'none', '-', '--', 'tbd', 'unknown',
]);

function isUsablePersonaName(value: string): boolean {
  const normalised = value.toLowerCase();
  if (PERSONA_NAME_PLACEHOLDERS.has(normalised)) return false;
  // Schema echo, e.g. `string — catchy persona name, e.g. 'Marketing Mary'`
  if (/^string\s*[—–\-:]/i.test(value)) return false;
  return true;
}

/**
 * Produce a persona name that will always save and always read sensibly.
 *
 * `name` is required by the Persona model and capped at 100 characters, so an
 * absent, over-long or non-string value made `Persona.create` throw — and every
 * caller swallows that error, so the persona vanished from the results instead
 * of showing up unnamed. The name is therefore coerced, cleaned and clamped
 * here, falling back to the persona's own role when the AI gives us nothing
 * usable.
 */
function sanitisePersonaName(raw: any, fallbackContext: { jobTitle?: string; buyingRole?: string }): string {
  let candidate = '';

  if (typeof raw === 'string') {
    candidate = raw;
  } else if (Array.isArray(raw)) {
    // Some responses wrap the name in an array — take the first usable entry
    // rather than stringifying the whole thing into "a,b,c".
    candidate = raw.find((v) => typeof v === 'string' && v.trim()) || '';
  } else if (raw && typeof raw === 'object') {
    // …or nest it ({ first: 'Mary', label: 'Marketing Mary' }). Never let this
    // become "[object Object]".
    const nested = (raw as Record<string, any>).name ?? (raw as Record<string, any>).label ?? (raw as Record<string, any>).value;
    candidate = typeof nested === 'string' ? nested : '';
  } else if (typeof raw === 'number') {
    candidate = String(raw);
  }

  // Collapse whitespace and strip wrapping quotes/markdown emphasis.
  candidate = candidate
    .replace(/\s+/g, ' ')
    .trim()
    .replace(/^["'`*_]+|["'`*_]+$/g, '')
    .trim();

  if (candidate && !isUsablePersonaName(candidate)) candidate = '';

  if (!candidate) {
    // Derive something meaningful from the rest of the persona so the record is
    // still identifiable in the list.
    const role = fallbackContext.jobTitle?.trim()
      || (fallbackContext.buyingRole ? fallbackContext.buyingRole.replace(/-/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase()) : '')
      || '';
    candidate = role ? `${role} Persona` : 'Buyer Persona';
  }

  if (candidate.length > PERSONA_NAME_MAX_LENGTH) {
    // Cut on a word boundary where possible so the name stays readable.
    const clipped = candidate.slice(0, PERSONA_NAME_MAX_LENGTH);
    const lastSpace = clipped.lastIndexOf(' ');
    candidate = (lastSpace > 40 ? clipped.slice(0, lastSpace) : clipped).trim();
  }

  return candidate;
}

/**
 * Maps Persona pipeline output to Persona entity fields.
 * Normalises enum values to match the Persona model's valid options.
 */
export function computePersonaAutoFillMapping(analysis: Record<string, any>, icpId: string): Record<string, any> {
  const mapping: Record<string, any> = {};

  // Required fields — the name is always populated (see sanitisePersonaName).
  mapping.name = sanitisePersonaName(analysis.name, {
    jobTitle: typeof analysis.jobTitle === 'string' ? analysis.jobTitle : undefined,
    buyingRole: typeof analysis.buyingRole === 'string' ? normaliseBuyingRole(analysis.buyingRole) : undefined,
  });
  mapping.icpId = icpId;
  mapping.isActive = true;

  // Demographics — ageRange and experience are numeric fields stored as strings, so
  // they are reduced to their canonical form ("25 to 34" → "25-34", "8-12 years" →
  // "8-12"). An unusable value is dropped rather than stored, which keeps every
  // generated persona editable under the same rules a hand-typed one follows.
  if (analysis.ageRange) {
    const ageRange = normaliseAgeRange(analysis.ageRange);
    if (ageRange) mapping.ageRange = ageRange;
  }
  if (analysis.gender) mapping.gender = normaliseGender(analysis.gender);
  if (analysis.jobTitle) mapping.jobTitle = analysis.jobTitle;
  if (analysis.seniorityLevel) mapping.seniorityLevel = normaliseSeniorityLevel(analysis.seniorityLevel);
  if (analysis.department) mapping.department = analysis.department;
  if (analysis.industry) mapping.industry = analysis.industry;
  if (analysis.experience) {
    const experience = normaliseYearsOfExperience(analysis.experience);
    if (experience) mapping.experience = experience;
  }

  // Professional
  if (analysis.skills) mapping.skills = ensureArray(analysis.skills);
  if (analysis.toolsUsed) mapping.toolsUsed = ensureArray(analysis.toolsUsed);
  if (analysis.certifications) mapping.certifications = ensureArray(analysis.certifications);

  // Psychographics
  if (analysis.bio) mapping.bio = analysis.bio;
  if (analysis.quote) mapping.quote = analysis.quote;
  if (analysis.goals) mapping.goals = ensureArray(analysis.goals);
  if (analysis.painPoints) mapping.painPoints = ensureArray(analysis.painPoints);
  if (analysis.motivations) mapping.motivations = ensureArray(analysis.motivations);
  if (analysis.values) mapping.values = ensureArray(analysis.values);
  if (analysis.fears) mapping.fears = ensureArray(analysis.fears);

  // Behavioural
  if (analysis.decisionMakingStyle) mapping.decisionMakingStyle = normaliseDecisionMakingStyle(analysis.decisionMakingStyle);
  if (analysis.researchHabits) mapping.researchHabits = analysis.researchHabits;
  if (analysis.contentPreferences) mapping.contentPreferences = ensureArray(analysis.contentPreferences);
  if (analysis.communicationChannel) mapping.communicationChannel = ensureArray(analysis.communicationChannel);

  // Day in the Life
  if (analysis.dailyChallenges) mapping.dailyChallenges = ensureArray(analysis.dailyChallenges);
  if (analysis.successMetrics) mapping.successMetrics = ensureArray(analysis.successMetrics);
  if (analysis.kpi) mapping.kpi = ensureArray(analysis.kpi);

  // Buying Behaviour
  if (typeof analysis.budgetAuthority === 'boolean') {
    mapping.budgetAuthority = analysis.budgetAuthority;
  } else if (analysis.budgetAuthority) {
    mapping.budgetAuthority = normaliseBudgetAuthorityBoolean(analysis.budgetAuthority);
  }
  if (analysis.influenceLevel) mapping.influenceLevel = normaliseInfluenceLevel(analysis.influenceLevel);
  if (analysis.buyingRole) mapping.buyingRole = normaliseBuyingRole(analysis.buyingRole);
  if (analysis.objections) mapping.objections = ensureArray(analysis.objections);

  // Budget Definition
  if (analysis.expectedBudget) mapping.expectedBudget = analysis.expectedBudget;
  if (analysis.spendingAuthority) mapping.spendingAuthority = analysis.spendingAuthority;
  if (analysis.budgetOwnership) mapping.budgetOwnership = analysis.budgetOwnership;
  if (analysis.purchaseApprovalLevel) mapping.purchaseApprovalLevel = analysis.purchaseApprovalLevel;

  // ---- Enforce character limits on Persona text fields ----
  mapping.bio = enforceCharLimit(mapping.bio, 2000);
  mapping.quote = enforceCharLimit(mapping.quote, 500);
  mapping.researchHabits = enforceCharLimit(mapping.researchHabits, 2000);
  if (Array.isArray(mapping.goals)) mapping.goals = enforceArrayCharLimit(mapping.goals, 2000);
  if (Array.isArray(mapping.painPoints)) mapping.painPoints = enforceArrayCharLimit(mapping.painPoints, 2000);
  mapping.expectedBudget = enforceCharLimit(mapping.expectedBudget, 200);
  mapping.spendingAuthority = enforceCharLimit(mapping.spendingAuthority, 200);
  mapping.budgetOwnership = enforceCharLimit(mapping.budgetOwnership, 200);
  mapping.purchaseApprovalLevel = enforceCharLimit(mapping.purchaseApprovalLevel, 200);

  return mapping;
}
// ============================================
// COMPETITOR AUTO-FILL MAPPING
// ============================================

/**
 * Domains a model reaches for when it has no real URL. Storing one of these as
 * a competitor's official website is worse than storing nothing.
 */
const PLACEHOLDER_WEBSITE_PATTERNS = [
  'example.com', 'example.org', 'example.net', 'competitor-example',
  'yourcompany', 'company-name', 'companyname.com', 'domain.com',
  'website.com', 'placeholder', 'lorem', 'test.com', 'acme.com',
  'competitor.com', 'brandname.com', 'yourbrand',
];

/**
 * Normalise a competitor website to a usable site root, or null.
 *
 * Rejects placeholder/example domains and anything that isn't a real hostname,
 * and strips deep paths and tracking parameters so the stored value is the
 * company's site rather than the article the crawler happened to land on.
 */
function sanitiseCompetitorWebsite(value: unknown): string | null {
  if (typeof value !== 'string') return null;
  const trimmed = value.trim().replace(/^["'<]+|["'>]+$/g, '');
  if (!trimmed) return null;

  let parsed: URL;
  try {
    parsed = new URL(trimmed.startsWith('http') ? trimmed : `https://${trimmed}`);
  } catch {
    return null;
  }

  const hostname = parsed.hostname.toLowerCase();
  // A hostname with no dot, or a bare TLD, is not a website.
  if (!hostname.includes('.') || hostname.startsWith('.') || hostname.endsWith('.')) return null;
  if (PLACEHOLDER_WEBSITE_PATTERNS.some((p) => hostname.includes(p))) return null;

  const protocol = parsed.protocol === 'http:' ? 'http:' : 'https:';
  return `${protocol}//${hostname}`;
}

/**
 * Maps Competitor pipeline output to Competitor entity fields.
 * Normalises enum values to match the Competitor model's valid options.
 */
export function computeCompetitorAutoFillMapping(analysis: Record<string, any>): Record<string, any> {
  const mapping: Record<string, any> = {};

  // Direct string mappings
  if (analysis.name) mapping.name = analysis.name;
  // Only a real, non-placeholder URL is stored — a wrong official website is
  // more damaging than an empty field the user can fill in.
  const website = sanitiseCompetitorWebsite(analysis.website);
  if (website) mapping.website = website;
  if (analysis.headquarters) mapping.headquarters = analysis.headquarters;
  if (analysis.fundingRaised) mapping.fundingRaised = analysis.fundingRaised;
  if (analysis.revenueEstimate) mapping.revenueEstimate = analysis.revenueEstimate;
  if (analysis.marketShare) mapping.marketShare = analysis.marketShare;
  if (analysis.targetAudience) mapping.targetAudience = analysis.targetAudience;
  if (analysis.primaryProduct) mapping.primaryProduct = analysis.primaryProduct;
  if (analysis.pricingDetails) mapping.pricingDetails = analysis.pricingDetails;
  if (analysis.valueProposition) mapping.valueProposition = analysis.valueProposition;
  if (analysis.tagline) mapping.tagline = analysis.tagline;
  if (analysis.messaging) mapping.messaging = analysis.messaging;
  if (analysis.contentStrategy) mapping.contentStrategy = analysis.contentStrategy;
  if (analysis.adSpendEstimate) mapping.adSpendEstimate = analysis.adSpendEstimate;
  if (analysis.swotSummary) mapping.swotSummary = analysis.swotSummary;
  if (analysis.recommendedStrategy) mapping.recommendedStrategy = analysis.recommendedStrategy;
  if (analysis.battlecards) mapping.battlecards = analysis.battlecards;

  // Normalised enum fields
  if (analysis.competitorType) {
    mapping.competitorType = normaliseCompetitorType(analysis.competitorType);
  }
  // POST /competitors validates threatLevel with `.isIn([...])` and no
  // `.optional()`, so a generation that omits it is rejected with a 400 and the
  // whole competitor is discarded. Fall back to the Competitor schema's own
  // default rather than losing an otherwise complete profile.
  mapping.threatLevel = analysis.threatLevel
    ? normaliseThreatLevel(analysis.threatLevel)
    : 'medium';
  if (analysis.marketPosition) {
    mapping.marketPosition = normaliseMarketPosition(analysis.marketPosition);
  }
  if (analysis.pricingStrategy) {
    mapping.pricingStrategy = normalisePricingStrategy(analysis.pricingStrategy);
  }
  if (analysis.companySize) {
    mapping.companySize = normaliseCompanySize(analysis.companySize);
  }
  if (analysis.fundingStage) {
    mapping.fundingStage = normaliseFundingStage(analysis.fundingStage);
  }

  // Number fields
  if (analysis.foundedYear) {
    const year = parseInt(String(analysis.foundedYear), 10);
    if (!isNaN(year) && year >= 1900 && year <= new Date().getFullYear() + 1) mapping.foundedYear = year;
  }
  if (analysis.employeeCount) {
    const count = parseInt(String(analysis.employeeCount), 10);
    if (!isNaN(count) && count >= 0) mapping.employeeCount = count;
  }

  // Boolean fields
  if (typeof analysis.freeTrial === 'boolean') mapping.freeTrial = analysis.freeTrial;
  if (typeof analysis.demoAvailable === 'boolean') mapping.demoAvailable = analysis.demoAvailable;

  // Array fields
  if (analysis.geographicReach) mapping.geographicReach = ensureArray(analysis.geographicReach);
  if (analysis.industriesServed) mapping.industriesServed = ensureArray(analysis.industriesServed);
  if (analysis.productCategories) mapping.productCategories = ensureArray(analysis.productCategories);
  if (analysis.keyFeatures) mapping.keyFeatures = ensureArray(analysis.keyFeatures);
  if (analysis.differentiators) mapping.differentiators = ensureArray(analysis.differentiators);
  if (analysis.marketingChannels) mapping.marketingChannels = ensureArray(analysis.marketingChannels);
  if (analysis.seoKeywords) mapping.seoKeywords = ensureArray(analysis.seoKeywords);
  if (analysis.strengths) mapping.strengths = ensureArray(analysis.strengths);
  if (analysis.weaknesses) mapping.weaknesses = ensureArray(analysis.weaknesses);
  if (analysis.opportunities) mapping.opportunities = ensureArray(analysis.opportunities);
  if (analysis.threats) mapping.threats = ensureArray(analysis.threats);
  if (analysis.ourAdvantages) mapping.ourAdvantages = ensureArray(analysis.ourAdvantages);
  if (analysis.ourVulnerabilities) mapping.ourVulnerabilities = ensureArray(analysis.ourVulnerabilities);

  // Default isActive
  mapping.isActive = true;

  // ---- Enforce character limits on Competitor text fields ----
  mapping.tagline = enforceCharLimit(mapping.tagline, 500);
  mapping.valueProposition = enforceCharLimit(mapping.valueProposition, 2000);
  mapping.pricingDetails = enforceCharLimit(mapping.pricingDetails, 2000);
  mapping.messaging = enforceCharLimit(mapping.messaging, 2000);
  mapping.targetAudience = enforceCharLimit(mapping.targetAudience, 2000);
  mapping.contentStrategy = enforceCharLimit(mapping.contentStrategy, 2000);
  mapping.swotSummary = enforceCharLimit(mapping.swotSummary, 2000);
  mapping.recommendedStrategy = enforceCharLimit(mapping.recommendedStrategy, 2000);
  mapping.battlecards = enforceCharLimit(mapping.battlecards, 2000);

  return mapping;
}

// ============================================
// SHARED NORMALISERS (ICP)
// ============================================

function normaliseCompanySize(value: string): string {
  const v = String(value).toLowerCase().trim();
  const map: Record<string, string> = {
    'startup': 'startup', '1-10': 'startup', 'micro': 'startup',
    'small': 'small', '11-50': 'small', 'sme': 'small',
    'medium': 'medium', '51-200': 'medium', 'mid': 'medium', 'mid-size': 'medium', 'mid-sized': 'medium',
    'large': 'large', '201-1000': 'large', 'corporate': 'large',
    'enterprise': 'enterprise', '1000+': 'enterprise', '1000': 'enterprise',
  };
  return map[v] || 'medium';
}

function normaliseFundingStage(value: string): string {
  const v = String(value).toLowerCase().trim();
  const map: Record<string, string> = {
    'bootstrapped': 'bootstrapped', 'self-funded': 'bootstrapped', 'pre-seed': 'bootstrapped',
    'seed': 'seed', 'series a': 'series-a', 'series-a': 'series-a',
    'series b': 'series-b', 'series-b': 'series-b',
    'series c': 'series-c', 'series-c': 'series-c',
    'ipo': 'ipo', 'public': 'ipo', 'enterprise': 'enterprise',
  };
  return map[v] || 'bootstrapped';
}

function normaliseBudgetAuthority(value: string): string {
  const v = String(value).toLowerCase().trim();
  if (v.includes('high')) return 'high';
  if (v.includes('low')) return 'low';
  return 'medium';
}

function normalisePriceSensitivity(value: string): string {
  const v = String(value).toLowerCase().trim();
  if (v.includes('high')) return 'high';
  if (v.includes('low')) return 'low';
  return 'medium';
}

function normalisePriority(value: string): string {
  const v = String(value).toLowerCase().trim();
  if (v.includes('high')) return 'high';
  if (v.includes('low')) return 'low';
  return 'medium';
}

// ============================================
// SHARED NORMALISERS (Persona)
// ============================================

function normaliseGender(value: string): string {
  const v = String(value).toLowerCase().trim();
  if (v.includes('male') && !v.includes('female') && !v.includes('non')) return 'male';
  if (v.includes('female')) return 'female';
  if (v.includes('non') || v.includes('they') || v.includes('neutral')) return 'non-binary';
  return 'prefer-not-say';
}

function normaliseSeniorityLevel(value: string): string {
  const v = String(value).toLowerCase().trim();
  if (v.includes('c-level') || v.includes('c level') || v.includes('c_level') || v.includes('executive') || v.includes('chief')) return 'c-level';
  if (v.includes('senior') || v.includes('director') || v.includes('vp') || v.includes('head')) return 'senior';
  if (v.includes('mid') || v.includes('manager') || v.includes('lead')) return 'mid';
  if (v.includes('entry') || v.includes('junior') || v.includes('associate') || v.includes('intern')) return 'entry';
  if (v.includes('founder') || v.includes('co-founder') || v.includes('owner') || v.includes('ceo')) return 'founder';
  return 'mid';
}

function normaliseDecisionMakingStyle(value: string): string {
  const v = String(value).toLowerCase().trim();
  if (v.includes('analyt')) return 'analytical';
  if (v.includes('intuit')) return 'intuitive';
  if (v.includes('collab') || v.includes('consensus')) return 'collaborative';
  if (v.includes('authoritat') || v.includes('command') || v.includes('decisive')) return 'authoritative';
  return 'analytical';
}

function normaliseInfluenceLevel(value: string): string {
  const v = String(value).toLowerCase().trim();
  if (v.includes('high')) return 'high';
  if (v.includes('low')) return 'low';
  return 'medium';
}

function normaliseBuyingRole(value: string): string {
  const v = String(value).toLowerCase().trim();
  if (v.includes('decision') || v.includes('decider') || v.includes('buyer')) return 'decision-maker';
  if (v.includes('influenc')) return 'influencer';
  if (v.includes('end-user') || v.includes('end user') || v.includes('user')) return 'end-user';
  if (v.includes('technical') || v.includes('evaluator')) return 'technical-evaluator';
  if (v.includes('procurement') || v.includes('purchasing')) return 'procurement';
  if (v.includes('executive') || v.includes('sponsor')) return 'executive-sponsor';
  if (v.includes('budget') || v.includes('approver')) return 'budget-approver';
  if (v.includes('recommend') || v.includes('advocat')) return 'recommender';
  if (v.includes('champion') || v.includes('advocate')) return 'champion';
  if (v.includes('gatekeep') || v.includes('block')) return 'gatekeeper';
  return 'influencer';
}

function normaliseBudgetAuthorityBoolean(value: any): boolean {
  if (typeof value === 'boolean') return value;
  const v = String(value).toLowerCase().trim();
  return v.includes('high') || v.includes('yes') || v.includes('full') || v.includes('direct');
}

function ensureArray(value: any): string[] {
  if (Array.isArray(value)) return value.map(String);
  if (typeof value === 'string') {
    // Try to split comma-separated strings
    if (value.includes(',')) return value.split(',').map(s => s.trim()).filter(Boolean);
    return [value];
  }
  return [];
}

// ============================================
// COMPETITOR NORMALISERS
// ============================================

function normaliseCompetitorType(value: string): string {
  const v = String(value).toLowerCase().trim();
  if (v.includes('direct')) return 'direct';
  if (v.includes('indirect') || v.includes('adjacen')) return 'indirect';
  if (v.includes('potential') || v.includes('emerging') || v.includes('future')) return 'potential';
  if (v.includes('replacement') || v.includes('substitute') || v.includes('altern')) return 'replacement';
  return 'direct';
}

function normaliseThreatLevel(value: string): string {
  const v = String(value).toLowerCase().trim();
  if (v.includes('critical') || v.includes('very high') || v.includes('severe')) return 'critical';
  if (v.includes('high')) return 'high';
  if (v.includes('low') || v.includes('minimal') || v.includes('negligible')) return 'low';
  return 'medium';
}

function normaliseMarketPosition(value: string): string {
  const v = String(value).toLowerCase().trim();
  if (v.includes('leader') || v.includes('dominant') || v.includes('leading')) return 'leader';
  if (v.includes('challenger') || v.includes('challenging') || v.includes('rising')) return 'challenger';
  if (v.includes('niche') || v.includes('specialist') || v.includes('boutique')) return 'niche';
  if (v.includes('follower') || v.includes('follower') || v.includes('fast-follower')) return 'follower';
  return 'challenger';
}

function normalisePricingStrategy(value: string): string {
  const v = String(value).toLowerCase().trim();
  if (v.includes('premium') || v.includes('luxury') || v.includes('high-end')) return 'premium';
  if (v.includes('freemium') || v.includes('free tier') || v.includes('free+paid')) return 'freemium';
  if (v.includes('economy') || v.includes('budget') || v.includes('low-cost') || v.includes('cheap')) return 'economy';
  if (v.includes('competitive') || v.includes('mid') || v.includes('market rate')) return 'competitive';
  return 'unknown';
}

// ============================================
// BRAND STRATEGY AUTO-FILL MAPPING
// ============================================

/**
 * Maps Brand Strategy pipeline output to BrandStrategy entity fields.
 * Normalises constrained values (archetype, personality, toneAttributes).
 */
export function computeBrandStrategyAutoFillMapping(analysis: Record<string, any>): Record<string, any> {
  const mapping: Record<string, any> = {};

  // Direct string mappings
  if (analysis.brandName) mapping.brandName = analysis.brandName;
  if (analysis.tagline) mapping.tagline = analysis.tagline;
  if (analysis.brandPromise) mapping.brandPromise = analysis.brandPromise;
  if (analysis.brandStory) mapping.brandStory = analysis.brandStory;
  if (analysis.marketCategory) mapping.marketCategory = analysis.marketCategory;
  if (analysis.competitiveDifference) mapping.competitiveDifference = analysis.competitiveDifference;
  if (analysis.brandPositioning) mapping.brandPositioning = analysis.brandPositioning;
  if (analysis.uniqueValueProposition) mapping.uniqueValueProposition = analysis.uniqueValueProposition;
  if (analysis.emotionalBenefits) mapping.emotionalBenefits = analysis.emotionalBenefits;
  if (analysis.rationalBenefits) mapping.rationalBenefits = analysis.rationalBenefits;
  if (analysis.brandMessage) mapping.brandMessage = analysis.brandMessage;
  if (analysis.elevatorPitch) mapping.elevatorPitch = analysis.elevatorPitch;
  if (analysis.brandVoice) mapping.brandVoice = analysis.brandVoice;
  if (analysis.toneGuidelines) mapping.toneGuidelines = analysis.toneGuidelines;
  if (analysis.customerExperience) mapping.customerExperience = analysis.customerExperience;

  // Constrained enum: brandArchetype
  if (analysis.brandArchetype) {
    mapping.brandArchetype = normaliseBrandArchetype(analysis.brandArchetype);
  }

  // Constrained array: brandPersonality (max 5 from 12 valid IDs)
  if (analysis.brandPersonality) {
    mapping.brandPersonality = normaliseBrandPersonality(analysis.brandPersonality);
  }

  // Free-form array fields
  if (analysis.brandValues) mapping.brandValues = ensureArray(analysis.brandValues);
  if (analysis.keyMessages) mapping.keyMessages = ensureArray(analysis.keyMessages);
  if (analysis.brandTouchpoints) mapping.brandTouchpoints = ensureArray(analysis.brandTouchpoints);

  // Brand Assets (mascots, jingles, punchlines)
  if (analysis.mascots) mapping.mascots = typeof analysis.mascots === 'string' ? analysis.mascots : (Array.isArray(analysis.mascots) ? analysis.mascots.join('\n') : '');
  if (analysis.jingles) mapping.jingles = typeof analysis.jingles === 'string' ? analysis.jingles : (Array.isArray(analysis.jingles) ? analysis.jingles.join('\n') : '');
  if (analysis.punchlines) mapping.punchlines = typeof analysis.punchlines === 'string' ? analysis.punchlines : (Array.isArray(analysis.punchlines) ? analysis.punchlines.join('\n') : '');

  // Nested object: targetAudience
  if (analysis.targetAudience && typeof analysis.targetAudience === 'object') {
    mapping.targetAudience = {
      demographics: analysis.targetAudience.demographics || '',
      psychographics: analysis.targetAudience.psychographics || '',
      painPoints: ensureArray(analysis.targetAudience.painPoints),
      desires: ensureArray(analysis.targetAudience.desires),
      behaviors: analysis.targetAudience.behaviors || '',
    };
  } else {
    // Handle flat targetAudience fields
    mapping.targetAudience = {
      demographics: analysis.demographics || '',
      psychographics: analysis.psychographics || '',
      painPoints: ensureArray(analysis.painPoints),
      desires: ensureArray(analysis.desires),
      behaviors: analysis.behaviors || '',
    };
  }

  // Nested object: toneAttributes (6 numeric sliders 0-100)
  if (analysis.toneAttributes) {
    mapping.toneAttributes = normaliseToneAttributes(analysis.toneAttributes);
  }

  return mapping;
}

// ============================================
// VISUAL IDENTITY AUTO-FILL MAPPING
// ============================================

/**
 * Maps Visual Identity pipeline output to VisualIdentity entity fields.
 * Normalises hex colors, fonts, and CSS values.
 */
export function computeVisualIdentityAutoFillMapping(analysis: Record<string, any>): Record<string, any> {
  const mapping: Record<string, any> = {};

  // Color fields — normalise to hex
  const colorFields = ['primaryColor', 'secondaryColor', 'accentColor', 'backgroundColor', 'surfaceColor', 'textColor', 'textMutedColor', 'successColor', 'warningColor', 'errorColor', 'infoColor'];
  for (const field of colorFields) {
    if (analysis[field]) {
      const normalised = normaliseHexColor(analysis[field]);
      if (normalised) mapping[field] = normalised;
    }
  }

  // Font fields — normalise font names
  const fontFields = ['headingFont', 'bodyFont', 'accentFont', 'monoFont'];
  for (const field of fontFields) {
    if (analysis[field]) {
      mapping[field] = normaliseFont(analysis[field]);
    }
  }

  // Typography values
  if (analysis.headingLineHeight) mapping.headingLineHeight = String(analysis.headingLineHeight);
  if (analysis.bodyLineHeight) mapping.bodyLineHeight = String(analysis.bodyLineHeight);
  if (analysis.headingLetterSpacing) mapping.headingLetterSpacing = String(analysis.headingLetterSpacing);
  if (analysis.bodyLetterSpacing) mapping.bodyLetterSpacing = String(analysis.bodyLetterSpacing);

  // Border radius values — normalised to a CSS length so the radius preview
  // actually renders the Small → Extra Large progression (see normaliseRadius).
  const radiusDefaults: Record<string, string> = {
    borderRadiusSm: '0.25rem',
    borderRadiusMd: '0.5rem',
    borderRadiusLg: '0.75rem',
    borderRadiusXl: '1rem',
  };
  for (const field of Object.keys(radiusDefaults)) {
    if (analysis[field]) mapping[field] = normaliseRadius(analysis[field], radiusDefaults[field]);
  }

  // Spacing values
  if (analysis.sectionSpacing) mapping.sectionSpacing = String(analysis.sectionSpacing);
  if (analysis.componentSpacing) mapping.componentSpacing = String(analysis.componentSpacing);
  if (analysis.elementSpacing) mapping.elementSpacing = String(analysis.elementSpacing);

  // Nested objects: iconStyle
  if (analysis.iconStyle && typeof analysis.iconStyle === 'object') {
    mapping.iconStyle = {
      name: analysis.iconStyle.name || 'Regular Outline',
      style: ['outline', 'filled', 'duotone'].includes(analysis.iconStyle.style) ? analysis.iconStyle.style : 'outline',
      strokeWidth: typeof analysis.iconStyle.strokeWidth === 'number' ? Math.max(1, Math.min(3, analysis.iconStyle.strokeWidth)) : 2,
      defaultSize: typeof analysis.iconStyle.defaultSize === 'number' ? Math.max(16, Math.min(32, analysis.iconStyle.defaultSize)) : 24,
    };
  }

  // Nested objects: imageStyle
  if (analysis.imageStyle && typeof analysis.imageStyle === 'object') {
    mapping.imageStyle = {
      name: analysis.imageStyle.name || 'Modern Rounded',
      description: analysis.imageStyle.description || 'Clean, modern imagery with rounded corners and warm tones.',
    };
  }

  // Mode is always 'custom' for AI-generated identities
  mapping.mode = 'custom';

  return mapping;
}

// ============================================
// BRAND STRATEGY NORMALISERS
// ============================================

function normaliseBrandArchetype(value: string): string {
  const v = String(value).toLowerCase().trim();
  const archetypeMap: Record<string, string> = {
    'innocent': 'innocent',
    'explorer': 'explorer',
    'sage': 'sage',
    'hero': 'hero',
    'outlaw': 'outlaw',
    'magician': 'magician',
    'caregiver': 'caregiver',
    'jester': 'jester',
    'lover': 'lover',
    'creator': 'creator',
    'ruler': 'ruler',
    'everyman': 'everyman',
    'regular guy': 'everyman',
    'every man': 'everyman',
    'rebel': 'outlaw',
    'revolutionary': 'outlaw',
    'visionary': 'magician',
    'nurturer': 'caregiver',
    'entertainer': 'jester',
    'romantic': 'lover',
    'artist': 'creator',
    'leader': 'ruler',
    'seeker': 'explorer',
    'wise': 'sage',
    'warrior': 'hero',
  };
  return archetypeMap[v] || 'sage';
}

function normaliseBrandPersonality(values: any): string[] {
  const validTraits = ['innovative', 'trustworthy', 'friendly', 'professional', 'bold', 'playful', 'sophisticated', 'authentic', 'empathetic', 'ambitious', 'rebellious', 'nurturing'];
  let arr: string[];
  if (Array.isArray(values)) {
    arr = values.map(String);
  } else if (typeof values === 'string') {
    arr = values.split(',').map(s => s.trim().toLowerCase());
  } else {
    return ['professional'];
  }
  // Normalise common variations
  const normalisationMap: Record<string, string> = {
    'innovative': 'innovative', 'innovating': 'innovative', 'innovation': 'innovative',
    'trustworthy': 'trustworthy', 'trusting': 'trustworthy', 'trust': 'trustworthy', 'reliable': 'trustworthy',
    'friendly': 'friendly', 'approachable': 'friendly', 'warm': 'friendly',
    'professional': 'professional', 'proficient': 'professional',
    'bold': 'bold', 'daring': 'bold', 'courageous': 'bold',
    'playful': 'playful', 'fun': 'playful', 'whimsical': 'playful',
    'sophisticated': 'sophisticated', 'elegant': 'sophisticated', 'refined': 'sophisticated',
    'authentic': 'authentic', 'genuine': 'authentic', 'real': 'authentic',
    'empathetic': 'empathetic', 'caring': 'empathetic', 'compassionate': 'empathetic',
    'ambitious': 'ambitious', 'driven': 'ambitious', 'goal-oriented': 'ambitious',
    'rebellious': 'rebellious', 'disruptive': 'rebellious', 'nonconformist': 'rebellious',
    'nurturing': 'nurturing', 'supportive': 'nurturing', 'helpful': 'nurturing',
  };
  const normalised = arr
    .map(v => normalisationMap[v.toLowerCase()] || v.toLowerCase())
    .filter(v => validTraits.includes(v));
  // Deduplicate and limit to 5
  const unique = [...new Set(normalised)];
  return unique.slice(0, 5).length > 0 ? unique.slice(0, 5) : ['professional'];
}

function normaliseToneAttributes(value: any): { professional: number; friendly: number; authoritative: number; playful: number; empathetic: number; bold: number } {
  const defaultAttrs = { professional: 50, friendly: 50, authoritative: 50, playful: 50, empathetic: 50, bold: 50 };

  if (!value || typeof value !== 'object') return defaultAttrs;

  const clamp = (v: any): number => {
    const num = typeof v === 'string' ? parseInt(v.replace('%', ''), 10) : (typeof v === 'number' ? v : NaN);
    if (isNaN(num)) return 50;
    return Math.max(0, Math.min(100, num));
  };

  return {
    professional: clamp(value.professional),
    friendly: clamp(value.friendly),
    authoritative: clamp(value.authoritative),
    playful: clamp(value.playful),
    empathetic: clamp(value.empathetic),
    bold: clamp(value.bold),
  };
}

// ============================================
// VISUAL IDENTITY NORMALISERS
// ============================================

function normaliseHexColor(value: string): string | null {
  if (!value || typeof value !== 'string') return null;

  let v = value.trim();

  // Already hex
  if (/^#[0-9a-fA-F]{6}$/.test(v)) return v.toLowerCase();
  if (/^#[0-9a-fA-F]{3}$/.test(v)) {
    // Expand 3-digit hex to 6-digit
    return `#${v[1]}${v[1]}${v[2]}${v[2]}${v[3]}${v[3]}`.toLowerCase();
  }

  // Handle rgb() format
  const rgbMatch = v.match(/rgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/);
  if (rgbMatch) {
    const r = parseInt(rgbMatch[1]).toString(16).padStart(2, '0');
    const g = parseInt(rgbMatch[2]).toString(16).padStart(2, '0');
    const b = parseInt(rgbMatch[3]).toString(16).padStart(2, '0');
    return `#${r}${g}${b}`.toLowerCase();
  }

  // Handle named colors
  const namedColors: Record<string, string> = {
    'black': '#000000', 'white': '#ffffff', 'red': '#ef4444', 'green': '#22c55e',
    'blue': '#3b82f6', 'yellow': '#eab308', 'purple': '#a855f7', 'pink': '#ec4899',
    'orange': '#f97316', 'gray': '#6b7280', 'grey': '#6b7280', 'teal': '#14b8a6',
    'cyan': '#06b6d4', 'indigo': '#6366f1', 'lime': '#84cc16', 'amber': '#f59e0b',
    'navy': '#1e3a5f', 'coral': '#ff6b6b', 'salmon': '#fa8072', 'gold': '#d4a017',
    'silver': '#c0c0c0', 'maroon': '#800000', 'olive': '#808000', 'aqua': '#00ffff',
    'darkblue': '#00008b', 'darkgreen': '#006400', 'darkred': '#8b0000',
    'lightblue': '#add8e6', 'lightgreen': '#90ee90', 'lightgray': '#d3d3d3',
  };
  const lower = v.toLowerCase().replace(/[^a-z]/g, '');
  if (namedColors[lower]) return namedColors[lower];

  // Try to extract hex from the value
  const hexMatch = v.match(/#([0-9a-fA-F]{3,6})/);
  if (hexMatch) {
    const hex = hexMatch[1];
    if (hex.length === 3) return `#${hex[0]}${hex[0]}${hex[1]}${hex[1]}${hex[2]}${hex[2]}`.toLowerCase();
    if (hex.length === 6) return `#${hex}`.toLowerCase();
  }

  return null;
}

/**
 * Border-radius values are rendered straight into an inline
 * `style={{ borderRadius: value }}` by the Brand Manual and Visual Identity
 * radius previews. Anything that is not a valid CSS length is silently dropped
 * by the browser, so the preview squares come out with square corners and the
 * Small/Medium/Large/Extra Large progression disappears. Colours and fonts were
 * already normalised on the way in; radius was passed through with a bare
 * String(), so values like "small" or "rounded-lg" reached the database intact.
 *
 * Coerces to a CSS length; anything unrecognised falls back to that tier's
 * documented default (0.25 / 0.5 / 0.75 / 1rem), which keeps the scale ordered.
 */
function normaliseRadius(value: any, fallback: string): string {
  if (typeof value === 'number') {
    return isFinite(value) && value >= 0 ? (value === 0 ? '0' : `${value}px`) : fallback;
  }
  if (!value || typeof value !== 'string') return fallback;

  const v = value.trim().toLowerCase();
  if (v === '0') return '0';

  // A number with a supported unit, e.g. "0.5rem", ".5 rem", "8px", "50%".
  const withUnit = v.match(/^(\d*\.?\d+)\s*(rem|em|px|%)$/);
  if (withUnit) return `${parseFloat(withUnit[1])}${withUnit[2]}`;

  // A bare number is taken as pixels, matching how CSS authors usually mean it.
  const bare = v.match(/^\d*\.?\d+$/);
  if (bare) return `${parseFloat(v)}px`;

  return fallback;
}

function normaliseFont(value: string): string {
  if (!value || typeof value !== 'string') return 'Inter';
  const v = value.trim();

  // Map common AI font suggestions to standard names
  const fontMap: Record<string, string> = {
    'inter': 'Inter',
    'roboto': 'Roboto',
    'open sans': 'Open Sans',
    'opensans': 'Open Sans',
    'lato': 'Lato',
    'poppins': 'Poppins',
    'montserrat': 'Montserrat',
    'playfair display': 'Playfair Display',
    'playfair': 'Playfair Display',
    'raleway': 'Raleway',
    'nunito': 'Nunito',
    'source sans': 'Source Sans Pro',
    'sourcesanspro': 'Source Sans Pro',
    'source code pro': 'Source Code Pro',
    'sourcecodepro': 'Source Code Pro',
    'jetbrains mono': 'JetBrains Mono',
    'jetbrainsmono': 'JetBrains Mono',
    'fira code': 'Fira Code',
    'firacode': 'Fira Code',
    'fira sans': 'Fira Sans',
    'firasans': 'Fira Sans',
    'ubuntu': 'Ubuntu',
    'merriweather': 'Merriweather',
    'pt sans': 'PT Sans',
    'ptsans': 'PT Sans',
    'work sans': 'Work Sans',
    'worksans': 'Work Sans',
    'arial': 'Arial',
    'helvetica': 'Helvetica',
    'georgia': 'Georgia',
    'times new roman': 'Times New Roman',
  };

  const lower = v.toLowerCase();
  if (fontMap[lower]) return fontMap[lower];

  // Return the value as-is if it looks like a proper font name
  if (v.length > 1 && /^[A-Z]/.test(v)) return v;

  // Try to capitalize first letter of each word
  return v.replace(/\b\w/g, c => c.toUpperCase());
}

// ============================================
// WEBSITE PLANNER AUTO-FILL MAPPING
// ============================================

export function computeWebsitePlannerAutoFillMapping(analysis: Record<string, any>): Record<string, any> {
  const mapping: Record<string, any> = {};

  // Direct string fields
  const stringFields = ['name', 'domain', 'websiteGoal', 'primaryCTA', 'secondaryCTA', 'targetAudience', 'country', 'language', 'seoTargetRegion', 'uiStyle', 'animationNotes', 'responsiveNotes', 'accessibilityNotes'];
  for (const field of stringFields) {
    if (analysis[field] && typeof analysis[field] === 'string') {
      mapping[field] = analysis[field];
    }
  }

  // Constrained enum: websiteType
  if (analysis.websiteType) {
    mapping.websiteType = normaliseWebsiteType(analysis.websiteType);
  }

  // Constrained enum: status
  if (analysis.status) {
    mapping.status = normaliseWebsiteStatus(analysis.status);
  }

  // Sections array — normalise structure
  if (Array.isArray(analysis.sections) && analysis.sections.length > 0) {
    mapping.sections = analysis.sections.map((s: any, index: number) => ({
      id: s.id || `sec-${Date.now()}-${index}`,
      name: typeof s.name === 'string' ? s.name : `Section ${index + 1}`,
      enabled: typeof s.enabled === 'boolean' ? s.enabled : true,
      order: typeof s.order === 'number' ? s.order : index,
      purpose: typeof s.purpose === 'string' ? s.purpose : '',
      contentRequirement: typeof s.contentRequirement === 'string' ? s.contentRequirement : '',
      // Section copy. These were being dropped, so even when the pipeline
      // returned finished copy the Content step opened empty and the website
      // generator had nothing but a brief to build from.
      headline: typeof s.headline === 'string' ? s.headline : '',
      subheadline: typeof s.subheadline === 'string' ? s.subheadline : '',
      description: typeof s.description === 'string' ? s.description : '',
      bulletPoints: Array.isArray(s.bulletPoints) ? s.bulletPoints.filter((b: any) => typeof b === 'string') : [],
      trustStatements: Array.isArray(s.trustStatements) ? s.trustStatements.filter((t: any) => typeof t === 'string') : [],
      conversionNotes: typeof s.conversionNotes === 'string' ? s.conversionNotes : '',
      uiNotes: typeof s.uiNotes === 'string' ? s.uiNotes : '',
      cta: typeof s.cta === 'string' ? s.cta : '',
      seoNotes: typeof s.seoNotes === 'string' ? s.seoNotes : '',
      priority: normaliseSectionPriority(s.priority),
      referenceLinks: Array.isArray(s.referenceLinks) ? s.referenceLinks : [],
      mediaRequirement: typeof s.mediaRequirement === 'string' ? s.mediaRequirement : '',
    }));
  }

  // Pages array — normalise structure
  if (Array.isArray(analysis.pages) && analysis.pages.length > 0) {
    mapping.pages = analysis.pages.map((p: any, index: number) => ({
      id: p.id || `page-${Date.now()}-${index}`,
      name: typeof p.name === 'string' ? p.name : `Page ${index + 1}`,
      url: typeof p.url === 'string' ? p.url : `/${typeof p.name === 'string' ? p.name.toLowerCase().replace(/\s+/g, '-') : `page-${index + 1}`}`,
      pageType: normalisePageType(p.pageType),
      goal: typeof p.goal === 'string' ? p.goal : '',
      metaTitle: typeof p.metaTitle === 'string' ? p.metaTitle : '',
      metaDescription: typeof p.metaDescription === 'string' ? p.metaDescription : '',
      keywords: Array.isArray(p.keywords) ? p.keywords.filter((k: any) => typeof k === 'string') : [],
      conversionGoal: typeof p.conversionGoal === 'string' ? p.conversionGoal : '',
      sections: Array.isArray(p.sections) ? p.sections : [],
      isPublished: typeof p.isPublished === 'boolean' ? p.isPublished : false,
    }));
  }

  // Features array — normalise structure
  if (Array.isArray(analysis.features) && analysis.features.length > 0) {
    mapping.features = analysis.features.map((f: any, index: number) => ({
      id: f.id || `feat-${Date.now()}-${index}`,
      name: typeof f.name === 'string' ? f.name : `Feature ${index + 1}`,
      enabled: typeof f.enabled === 'boolean' ? f.enabled : true,
      priority: normaliseSectionPriority(f.priority),
      notes: typeof f.notes === 'string' ? f.notes : '',
      complexity: normaliseFeatureComplexity(f.complexity),
      estimatedTimeline: typeof f.estimatedTimeline === 'string' ? f.estimatedTimeline : '',
      dependencies: Array.isArray(f.dependencies) ? f.dependencies : [],
    }));
  }

  // FAQs — normalise to the WebsiteFAQ shape the Content step renders.
  // The website planner needs its own mapping here: the shared FAQ-module
  // mapping produces a different shape (title/shortAnswer/faqType/tags).
  if (Array.isArray(analysis.faqs) && analysis.faqs.length > 0) {
    mapping.faqs = analysis.faqs
      .filter((f: any) => f && typeof f.question === 'string' && f.question.trim())
      .map((f: any, index: number) => ({
        id: f.id || `faq-${Date.now()}-${index}`,
        question: f.question.trim(),
        answer: typeof f.answer === 'string' ? f.answer : '',
        category: typeof f.category === 'string' ? f.category : '',
        seoImportance: normaliseSectionPriority(f.seoImportance),
        schemaEnabled: typeof f.schemaEnabled === 'boolean' ? f.schemaEnabled : true,
        aiGenerated: true,
      }));
  }

  // Target keywords — ensure string array
  if (analysis.targetKeywords) {
    mapping.targetKeywords = ensureArray(analysis.targetKeywords);
  }

  // SEO clusters — normalise structure
  if (Array.isArray(analysis.seoClusters) && analysis.seoClusters.length > 0) {
    mapping.seoClusters = analysis.seoClusters.map((c: any, index: number) => ({
      id: c.id || `cluster-${Date.now()}-${index}`,
      topic: typeof c.topic === 'string' ? c.topic : `Cluster ${index + 1}`,
      pillarPage: typeof c.pillarPage === 'string' ? c.pillarPage : '',
      clusterPages: Array.isArray(c.clusterPages) ? c.clusterPages.filter((p: any) => typeof p === 'string') : [],
      keywords: Array.isArray(c.keywords) ? c.keywords.filter((k: any) => typeof k === 'string') : [],
      contentGap: typeof c.contentGap === 'string' ? c.contentGap : '',
    }));
  }

  // Design references — ensure string array
  if (analysis.designReferences) {
    mapping.designReferences = ensureArray(analysis.designReferences);
  }

  return mapping;
}

// ============================================
// WEBSITE PLANNER NORMALISERS
// ============================================

function normaliseWebsiteType(value: string): string {
  const v = String(value).toLowerCase().trim().replace(/\s+/g, '-');
  const validTypes = ['corporate', 'saas', 'ecommerce', 'portfolio', 'marketplace', 'landing-page', 'agency', 'personal-brand'];
  const typeMap: Record<string, string> = {
    'corporate': 'corporate', 'business': 'corporate', 'company': 'corporate', 'enterprise': 'corporate',
    'saas': 'saas', 'software-as-a-service': 'saas', 'software': 'saas', 'app': 'saas', 'web-app': 'saas',
    'ecommerce': 'ecommerce', 'e-commerce': 'ecommerce', 'shop': 'ecommerce', 'store': 'ecommerce', 'online-store': 'ecommerce', 'retail': 'ecommerce',
    'portfolio': 'portfolio', 'showcase': 'portfolio', 'creative': 'portfolio',
    'marketplace': 'marketplace', 'platform': 'marketplace', 'multi-vendor': 'marketplace',
    'landing-page': 'landing-page', 'landing': 'landing-page', 'single-page': 'landing-page', 'launch': 'landing-page',
    'agency': 'agency', 'studio': 'agency', 'consulting': 'agency', 'freelance': 'agency',
    'personal-brand': 'personal-brand', 'personal': 'personal-brand', 'personal-website': 'personal-brand', 'personal-site': 'personal-brand',
  };
  return typeMap[v] || 'corporate';
}

function normaliseWebsiteStatus(value: string): string {
  const v = String(value).toLowerCase().trim();
  const statusMap: Record<string, string> = {
    'planning': 'planning', 'plan': 'planning', 'draft': 'planning', 'idea': 'planning',
    'requirements': 'requirements', 'requirement': 'requirements', 'spec': 'requirements', 'specification': 'requirements',
    'design': 'design', 'designing': 'design', 'ui-design': 'design', 'ux': 'design',
    'development': 'development', 'developing': 'development', 'building': 'development', 'coding': 'development',
    'review': 'review', 'reviewing': 'review', 'qa': 'review', 'testing': 'review',
    'live': 'live', 'launched': 'live', 'published': 'live', 'production': 'live',
    'maintenance': 'maintenance', 'maintaining': 'maintenance', 'update': 'maintenance', 'updating': 'maintenance',
  };
  return statusMap[v] || 'planning';
}

function normaliseSectionPriority(value: any): string {
  if (!value) return 'medium';
  const v = String(value).toLowerCase().trim();
  const priorityMap: Record<string, string> = {
    'critical': 'critical', 'essential': 'critical', 'must-have': 'critical', 'required': 'critical',
    'high': 'high', 'important': 'high', 'major': 'high',
    'medium': 'medium', 'moderate': 'medium', 'standard': 'medium', 'normal': 'medium',
    'low': 'low', 'minor': 'low', 'nice-to-have': 'low', 'optional': 'low',
  };
  return priorityMap[v] || 'medium';
}

function normaliseFeatureComplexity(value: any): string {
  if (!value) return 'medium';
  const v = String(value).toLowerCase().trim();
  const complexityMap: Record<string, string> = {
    'simple': 'simple', 'basic': 'simple', 'easy': 'simple', 'minimal': 'simple',
    'medium': 'medium', 'moderate': 'medium', 'standard': 'medium', 'average': 'medium',
    'complex': 'complex', 'advanced': 'complex', 'difficult': 'complex', 'sophisticated': 'complex',
    'enterprise': 'enterprise', 'large-scale': 'enterprise', 'critical-infrastructure': 'enterprise',
  };
  return complexityMap[v] || 'medium';
}

function normalisePageType(value: any): string {
  if (!value) return 'main';
  const v = String(value).toLowerCase().trim();
  const pageTypeMap: Record<string, string> = {
    'main': 'main', 'primary': 'main', 'home': 'main', 'homepage': 'main',
    'landing': 'landing', 'landing-page': 'landing', 'campaign': 'landing',
    'dynamic': 'dynamic', 'blog': 'dynamic', 'blog-post': 'dynamic', 'article': 'dynamic',
    'legal': 'legal', 'legal-page': 'legal', 'privacy': 'legal', 'terms': 'legal',
    'seo': 'seo', 'seo-page': 'seo', 'content': 'seo',
  };
  return pageTypeMap[v] || 'main';
}

// ============================================
// FAQ BANK AUTO-FILL MAPPING
// ============================================

export function computeFaqBankAutoFillMapping(analysis: Record<string, any>): Record<string, any> {
  const mapping: Record<string, any> = {};

  // Suggested categories
  if (Array.isArray(analysis.suggestedCategories) && analysis.suggestedCategories.length > 0) {
    mapping.suggestedCategories = analysis.suggestedCategories.map((c: any, i: number) => ({
      id: c.id || `cat-${Date.now()}-${i}`,
      name: typeof c.name === 'string' ? c.name : `Category ${i + 1}`,
      description: typeof c.description === 'string' ? c.description : '',
      faqType: normaliseFaqType(c.faqType),
    }));
  }

  // Strategy fields
  if (analysis.targetAudienceProfile) mapping.targetAudienceProfile = analysis.targetAudienceProfile;
  if (analysis.faqStrategyNotes) mapping.faqStrategyNotes = analysis.faqStrategyNotes;
  if (Array.isArray(analysis.primaryTopics)) mapping.primaryTopics = ensureArray(analysis.primaryTopics);
  if (analysis.searchIntentProfile) mapping.searchIntentProfile = analysis.searchIntentProfile;
  if (analysis.faqClusterTopic) mapping.faqClusterTopic = analysis.faqClusterTopic;
  if (Array.isArray(analysis.relatedFaqTopics)) mapping.relatedFaqTopics = ensureArray(analysis.relatedFaqTopics);

  // FAQ items
  if (Array.isArray(analysis.faqs) && analysis.faqs.length > 0) {
    mapping.faqs = analysis.faqs.map((faq: any, i: number) => {
      const seo = Array.isArray(analysis.seoEnhancements) ? analysis.seoEnhancements[i] : {};
      const aiNote = Array.isArray(analysis.aiUsageNotes) ? analysis.aiUsageNotes[i] : {};
      const detailedAnswer = Array.isArray(analysis.detailedAnswers) ? analysis.detailedAnswers[i] : undefined;

      return {
        id: faq.id || `faq-${Date.now()}-${i}`,
        title: typeof faq.title === 'string' ? faq.title : `FAQ ${i + 1}`,
        question: typeof faq.question === 'string' ? faq.question : '',
        answer: typeof faq.answer === 'string' ? faq.answer : '',
        shortAnswer: typeof faq.shortAnswer === 'string' ? faq.shortAnswer : '',
        detailedAnswer: typeof detailedAnswer === 'string' ? detailedAnswer : (typeof faq.detailedAnswer === 'string' ? faq.detailedAnswer : ''),
        faqType: normaliseFaqType(faq.faqType),
        tags: ensureArray(faq.tags),
        priority: normaliseFaqPriority(faq.priority),
        audienceType: normaliseFaqAudienceType(faq.audienceType),
        funnelStage: normaliseFaqFunnelStage(faq.funnelStage),
        status: 'draft',
        order: i,
        seoKeywords: seo?.seoKeywords ? ensureArray(seo.seoKeywords) : ensureArray(faq.seoKeywords),
        metaTitle: seo?.metaTitle || faq.metaTitle || '',
        metaDescription: seo?.metaDescription || faq.metaDescription || '',
        searchIntent: normaliseSearchIntent(seo?.searchIntent || faq.searchIntent),
        schemaEnabled: true,
        voiceSearchOptimised: typeof faq.voiceSearchOptimised === 'boolean' ? faq.voiceSearchOptimised : false,
        aiSuggestedUsage: aiNote?.aiSuggestedUsage || faq.aiSuggestedUsage || '',
        aiPriority: normaliseFaqPriority(aiNote?.aiPriority || faq.aiPriority),
        aiContextWeight: clampNumber(aiNote?.aiContextWeight || faq.aiContextWeight, 1, 10),
        searchRelevance: clampNumber(faq.searchRelevance, 0, 100),
        relatedFaqIds: [],
        referenceLinks: [],
        mediaAttachments: [],
        documentUrls: [],
        usedIn: [],
        version: 1,
        viewCount: 0,
        helpfulCount: 0,
        notHelpfulCount: 0,
      };
    });
  }

  return mapping;
}

// ============================================
// CASE STUDY AUTO-FILL MAPPING
// ============================================

export function computeCaseStudyAutoFillMapping(analysis: Record<string, any>): Record<string, any> {
  const mapping: Record<string, any> = {};

  // Direct string fields
  const stringFields = [
    'title', 'shortDescription', 'detailedDescription', 'executiveSummary',
    'clientName', 'clientWebsite', 'challenge', 'goals', 'solution', 'strategy',
    'executionSteps', 'results', 'beforeDescription', 'afterDescription',
  ];
  for (const field of stringFields) {
    if (analysis[field] && typeof analysis[field] === 'string') {
      mapping[field] = analysis[field];
    }
  }

  // SEO fields with length limits matching the schema
  if (analysis.metaTitle && typeof analysis.metaTitle === 'string') {
    mapping.metaTitle = analysis.metaTitle.length > 60 ? analysis.metaTitle.substring(0, 57) + '...' : analysis.metaTitle;
  }
  if (analysis.metaDescription && typeof analysis.metaDescription === 'string') {
    mapping.metaDescription = analysis.metaDescription.length > 160 ? analysis.metaDescription.substring(0, 157) + '...' : analysis.metaDescription;
  }

  // Enum fields
  if (analysis.clientIndustry) mapping.clientIndustry = normaliseCaseStudyIndustry(analysis.clientIndustry);
  if (analysis.department) mapping.department = normaliseCaseStudyDepartment(analysis.department);
  if (analysis.industry) mapping.industry = normaliseCaseStudyIndustry(analysis.industry);
  if (analysis.priority) mapping.priority = normaliseCaseStudyPriority(analysis.priority);
  if (analysis.visibility) mapping.visibility = normaliseCaseStudyVisibility(analysis.visibility);

  // Slug
  if (analysis.slug) mapping.slug = String(analysis.slug).toLowerCase().replace(/\s+/g, '-').replace(/[^a-z0-9-]/g, '');

  // Array fields
  if (Array.isArray(analysis.tags)) mapping.tags = ensureArray(analysis.tags);
  if (Array.isArray(analysis.servicesUsed)) mapping.servicesUsed = ensureArray(analysis.servicesUsed);
  if (Array.isArray(analysis.productsUsed)) mapping.productsUsed = ensureArray(analysis.productsUsed);
  if (Array.isArray(analysis.keyTakeaways)) mapping.keyTakeaways = ensureArray(analysis.keyTakeaways);
  if (Array.isArray(analysis.beforeMetrics)) mapping.beforeMetrics = ensureArray(analysis.beforeMetrics);
  if (Array.isArray(analysis.afterMetrics)) mapping.afterMetrics = ensureArray(analysis.afterMetrics);
  if (Array.isArray(analysis.seoKeywords)) mapping.seoKeywords = ensureArray(analysis.seoKeywords);

  // KPIs array — normalise structure, filter out invalid entries
  if (Array.isArray(analysis.kpis) && analysis.kpis.length > 0) {
    mapping.kpis = analysis.kpis
      .map((kpi: any, i: number) => ({
        label: typeof kpi.label === 'string' && kpi.label.trim() ? kpi.label.trim() : `KPI ${i + 1}`,
        value: typeof kpi.value === 'string' && kpi.value.trim()
          ? kpi.value.trim()
          : (typeof kpi.afterValue === 'string' && kpi.afterValue.trim() ? kpi.afterValue.trim() : `${i + 1}`),
        beforeValue: kpi.beforeValue ? String(kpi.beforeValue) : undefined,
        afterValue: kpi.afterValue ? String(kpi.afterValue) : undefined,
        unit: typeof kpi.unit === 'string' ? kpi.unit : undefined,
        changePercent: typeof kpi.changePercent === 'number' ? kpi.changePercent : undefined,
      }))
      .filter((kpi: any) => kpi.label && kpi.value);
  }

  // Steps array — normalise structure
  if (Array.isArray(analysis.steps) && analysis.steps.length > 0) {
    mapping.steps = analysis.steps.map((step: any, i: number) => ({
      id: step.id || `step-${Date.now()}-${i}`,
      title: typeof step.title === 'string' ? step.title : `Step ${i + 1}`,
      description: typeof step.description === 'string' ? step.description : '',
      order: typeof step.order === 'number' ? step.order : i,
      type: normaliseCaseStudyStepType(step.type),
    }));
  }

  // Testimonials sub-array — filter out entries without required fields
  if (Array.isArray(analysis.testimonials) && analysis.testimonials.length > 0) {
    mapping.testimonials = analysis.testimonials
      .map((t: any) => ({
        quote: typeof t.quote === 'string' ? t.quote.trim() : '',
        author: typeof t.author === 'string' ? t.author.trim() : '',
        role: t.role || undefined,
        company: t.company || undefined,
      }))
      .filter((t: any) => t.quote && t.author);
  }

  // Default values for new case studies
  mapping.status = 'draft';
  mapping.approvalStatus = 'pending';
  mapping.aiGenerated = true;

  return mapping;
}

// ============================================
// TESTIMONIAL AUTO-FILL MAPPING
// ============================================

export function computeTestimonialAutoFillMapping(analysis: Record<string, any>): Record<string, any> {
  const mapping: Record<string, any> = {};

  // Direct string fields
  const stringFields = [
    'customerName', 'customerCompany', 'customerDesignation',
    'customerIndustry', 'customerLocation', 'headline', 'shortQuote',
    'fullTestimonial', 'story', 'emotionalHighlight', 'challenge', 'solution', 'results',
    'beforeState', 'duringState', 'afterState',
  ];
  for (const field of stringFields) {
    if (analysis[field] && typeof analysis[field] === 'string') {
      mapping[field] = analysis[field];
    }
  }

  // Enum fields
  if (analysis.type) mapping.type = normaliseTestimonialType(analysis.type);
  if (analysis.category) mapping.category = normaliseTestimonialCategory(analysis.category);
  if (analysis.authorityLevel) mapping.authorityLevel = normaliseAuthorityLevel(analysis.authorityLevel);
  if (analysis.detailDepth) mapping.detailDepth = normaliseDetailDepth(analysis.detailDepth);
  if (analysis.collectionMethod) mapping.collectionMethod = normaliseCollectionMethod(analysis.collectionMethod);

  // Score fields (0-100)
  const scoreFields = ['authenticityScore', 'emotionalImpactScore', 'conversionPotential', 'specificityScore', 'trustScore'];
  for (const field of scoreFields) {
    if (analysis[field] !== undefined) {
      mapping[field] = clampNumber(analysis[field], 0, 100);
    }
  }

  // Array fields
  if (Array.isArray(analysis.keyResults)) mapping.keyResults = ensureArray(analysis.keyResults);
  if (Array.isArray(analysis.campaignTags)) mapping.campaignTags = ensureArray(analysis.campaignTags);
  if (Array.isArray(analysis.industryTags)) mapping.industryTags = ensureArray(analysis.industryTags);
  if (Array.isArray(analysis.audienceTags)) mapping.audienceTags = ensureArray(analysis.audienceTags);

  // ROI Metrics array
  if (Array.isArray(analysis.roiMetrics) && analysis.roiMetrics.length > 0) {
    mapping.roiMetrics = analysis.roiMetrics.map((m: any) => ({
      metric: typeof m.metric === 'string' ? m.metric : '',
      value: typeof m.value === 'string' ? m.value : String(m.value || ''),
      unit: m.unit || undefined,
    }));
  }

  // Language
  if (analysis.language) mapping.language = analysis.language;

  // Default values for new testimonials
  mapping.status = 'pending';
  mapping.consentVerified = false;
  mapping.isPublic = true;
  mapping.marketingUsagePermission = false;
  mapping.contactPermission = false;

  return mapping;
}

// ============================================
// FAQ BANK NORMALISERS
// ============================================

function normaliseFaqType(value: any): string {
  if (!value) return 'customer';
  const v = String(value).toLowerCase().trim().replace(/\s+/g, '-');
  const typeMap: Record<string, string> = {
    'customer': 'customer', 'general': 'customer', 'public': 'customer',
    'sales': 'sales', 'pricing': 'sales', 'purchase': 'sales',
    'technical': 'technical', 'tech': 'technical', 'troubleshooting': 'technical',
    'internal': 'internal', 'employee': 'internal', 'staff': 'internal',
    'ai-training': 'ai-training', 'ai': 'ai-training', 'training': 'ai-training',
    'website': 'website', 'web': 'website', 'site': 'website',
    'blog': 'blog', 'article': 'blog',
    'newsletter': 'newsletter', 'email': 'newsletter',
    'support': 'support', 'help': 'support',
    'onboarding': 'onboarding', 'getting-started': 'onboarding', 'setup': 'onboarding',
    'legal': 'legal', 'compliance': 'legal', 'terms': 'legal',
    'hr': 'hr', 'human-resources': 'hr', 'people': 'hr',
    'sop': 'sop', 'process': 'sop', 'standard': 'sop',
  };
  return typeMap[v] || 'customer';
}

function normaliseFaqPriority(value: any): string {
  if (!value) return 'medium';
  const v = String(value).toLowerCase().trim();
  const priorityMap: Record<string, string> = {
    'critical': 'critical', 'essential': 'critical', 'must-have': 'critical', 'urgent': 'critical',
    'high': 'high', 'important': 'high', 'major': 'high',
    'medium': 'medium', 'moderate': 'medium', 'normal': 'medium',
    'low': 'low', 'minor': 'low', 'nice-to-have': 'low', 'optional': 'low',
  };
  return priorityMap[v] || 'medium';
}

function normaliseFaqAudienceType(value: any): string {
  if (!value) return 'public';
  const v = String(value).toLowerCase().trim().replace(/\s+/g, '-');
  const audienceMap: Record<string, string> = {
    'public': 'public', 'external': 'public', 'customer': 'public',
    'internal': 'internal', 'employee': 'internal',
    'team-specific': 'team-specific', 'team': 'team-specific',
    'department-specific': 'department-specific', 'department': 'department-specific',
    'admin-only': 'admin-only', 'admin': 'admin-only', 'administrator': 'admin-only',
  };
  return audienceMap[v] || 'public';
}

function normaliseFaqFunnelStage(value: any): string {
  if (!value) return 'general';
  const v = String(value).toLowerCase().trim();
  const stageMap: Record<string, string> = {
    'tofu': 'tofu', 'awareness': 'tofu', 'top-of-funnel': 'tofu', 'discovery': 'tofu',
    'mofu': 'mofu', 'consideration': 'mofu', 'middle-of-funnel': 'mofu', 'evaluation': 'mofu',
    'bofu': 'bofu', 'decision': 'bofu', 'bottom-of-funnel': 'bofu', 'conversion': 'bofu',
    'post-sale': 'post-sale', 'retention': 'post-sale', 'after-sale': 'post-sale',
    'general': 'general', 'all': 'general', 'any': 'general',
  };
  return stageMap[v] || 'general';
}

function normaliseSearchIntent(value: any): string {
  if (!value) return 'informational';
  const v = String(value).toLowerCase().trim();
  const intentMap: Record<string, string> = {
    'informational': 'informational', 'info': 'informational', 'learn': 'informational', 'know': 'informational',
    'navigational': 'navigational', 'navigation': 'navigational', 'find': 'navigational',
    'transactional': 'transactional', 'transaction': 'transactional', 'buy': 'transactional', 'purchase': 'transactional',
    'commercial': 'commercial', 'comparison': 'commercial', 'evaluate': 'commercial', 'research': 'commercial',
  };
  return intentMap[v] || 'informational';
}

// ============================================
// CASE STUDY NORMALISERS
// ============================================

function normaliseCaseStudyIndustry(value: any): string {
  if (!value) return 'technology';
  const v = String(value).toLowerCase().trim().replace(/\s+/g, '-');
  const industryMap: Record<string, string> = {
    'technology': 'technology', 'tech': 'technology', 'it': 'technology', 'software': 'technology', 'saas': 'saas',
    'healthcare': 'healthcare', 'health': 'healthcare', 'medical': 'healthcare', 'pharma': 'healthcare',
    'finance': 'finance', 'financial': 'finance', 'fintech': 'finance', 'banking': 'finance',
    'education': 'education', 'edtech': 'education', 'learning': 'education',
    'retail': 'retail', 'ecommerce': 'ecommerce', 'e-commerce': 'ecommerce', 'consumer': 'retail',
    'manufacturing': 'manufacturing', 'industrial': 'manufacturing', 'production': 'manufacturing',
    'media': 'other', 'entertainment': 'other', 'content': 'other',
    'hospitality': 'other', 'travel': 'other', 'tourism': 'other',
    'real-estate': 'real-estate', 'property': 'real-estate', 'realty': 'real-estate',
    'logistics': 'other', 'supply-chain': 'other', 'shipping': 'other',
    'marketing': 'marketing', 'advertising': 'marketing',
    'consulting': 'consulting', 'advisory': 'consulting',
    'other': 'other',
  };
  return industryMap[v] || 'technology';
}

function normaliseCaseStudyDepartment(value: any): string {
  if (!value) return 'marketing';
  const v = String(value).toLowerCase().trim().replace(/\s+/g, '-');
  const deptMap: Record<string, string> = {
    'marketing': 'marketing', 'growth': 'marketing',
    'sales': 'sales', 'revenue': 'sales',
    'engineering': 'engineering', 'development': 'engineering', 'dev': 'engineering', 'tech': 'engineering',
    'product': 'product', 'product-management': 'product',
    'design': 'design', 'ux': 'design', 'ui': 'design',
    'hr': 'hr', 'human-resources': 'hr', 'people': 'hr',
    'finance': 'finance', 'accounting': 'finance',
    'operations': 'operations', 'ops': 'operations',
    'customer-success': 'customer-success', 'support': 'customer-success', 'cs': 'customer-success',
    'leadership': 'other', 'executive': 'other', 'c-suite': 'other',
    'legal': 'legal', 'compliance': 'legal',
    'other': 'other',
  };
  return deptMap[v] || 'marketing';
}

function normaliseCaseStudyPriority(value: any): string {
  if (!value) return 'medium';
  const v = String(value).toLowerCase().trim();
  const priorityMap: Record<string, string> = {
    'critical': 'critical', 'essential': 'critical', 'must-have': 'critical',
    'high': 'high', 'important': 'high', 'major': 'high',
    'medium': 'medium', 'moderate': 'medium', 'normal': 'medium',
    'low': 'low', 'minor': 'low', 'nice-to-have': 'low',
  };
  return priorityMap[v] || 'medium';
}

function normaliseCaseStudyVisibility(value: any): string {
  if (!value) return 'public';
  const v = String(value).toLowerCase().trim();
  const visibilityMap: Record<string, string> = {
    'public': 'public', 'open': 'public',
    'private': 'private', 'internal': 'internal',
    'gated': 'private', 'gated-content': 'private', 'lead-magnet': 'private',
  };
  return visibilityMap[v] || 'public';
}

function normaliseCaseStudyStepType(value: any): string {
  if (!value) return 'note';
  const v = String(value).toLowerCase().trim();
  const typeMap: Record<string, string> = {
    'challenge': 'challenge', 'problem': 'challenge',
    'strategy': 'strategy', 'approach': 'strategy', 'plan': 'strategy',
    'execution': 'execution', 'implementation': 'execution', 'action': 'execution',
    'result': 'result', 'outcome': 'result', 'impact': 'result',
    'note': 'note', 'comment': 'note', 'observation': 'note',
  };
  return typeMap[v] || 'note';
}

// ============================================
// TESTIMONIAL NORMALISERS
// ============================================

function normaliseTestimonialType(value: any): string {
  if (!value) return 'text';
  const v = String(value).toLowerCase().trim().replace(/\s+/g, '-');
  const typeMap: Record<string, string> = {
    'text': 'text', 'written': 'text', 'quote': 'text',
    'video': 'video', 'video-testimonial': 'video',
    'audio': 'audio', 'voice': 'audio', 'podcast': 'audio',
    'image': 'image', 'photo': 'image', 'picture': 'image',
    'screenshot': 'screenshot', 'screen-capture': 'screenshot',
    'social-media': 'social-media', 'social': 'social-media', 'tweet': 'social-media',
    'email': 'email', 'mail': 'email',
    'whatsapp': 'whatsapp',
    'linkedin-recommendation': 'linkedin-recommendation', 'linkedin': 'linkedin-recommendation',
    'google-review': 'google-review', 'g-review': 'google-review',
    'case-study': 'case-study', 'casestudy': 'case-study',
    'review': 'text', 'rating': 'text',
    'interview': 'text', 'q-a': 'text',
    'before-after': 'text', 'beforeafter': 'text', 'transformation': 'text',
    'story': 'text', 'narrative': 'text',
  };
  return typeMap[v] || 'text';
}

function normaliseTestimonialCategory(value: any): string {
  if (!value) return 'product-quality';
  const v = String(value).toLowerCase().trim().replace(/\s+/g, '-');
  const categoryMap: Record<string, string> = {
    'product-quality': 'product-quality', 'product': 'product-quality', 'quality': 'product-quality',
    'customer-service': 'customer-service', 'service': 'customer-service', 'support': 'customer-service',
    'value-for-money': 'value-for-money', 'value': 'value-for-money', 'pricing': 'value-for-money',
    'ease-of-use': 'ease-of-use', 'usability': 'ease-of-use', 'simple': 'ease-of-use', 'feature-specific': 'ease-of-use', 'features': 'ease-of-use',
    'implementation': 'implementation', 'onboarding': 'implementation', 'setup': 'implementation',
    'trust-security': 'trust-security', 'trust': 'trust-security', 'security': 'trust-security', 'reliability': 'trust-security', 'reliable': 'trust-security', 'stability': 'trust-security',
    'results-roi': 'results-roi', 'roi': 'results-roi', 'return': 'results-roi',
    'partnership': 'partnership', 'collaboration': 'partnership',
    'problem-solved': 'problem-solved', 'solution': 'problem-solved',
    'user-experience': 'user-experience', 'ux': 'user-experience',
    'industry-expertise': 'industry-expertise', 'expertise': 'industry-expertise', 'innovation': 'industry-expertise', 'innovative': 'industry-expertise',
    'integration': 'integration',
  };
  return categoryMap[v] || 'product-quality';
}

function normaliseAuthorityLevel(value: any): string {
  if (!value) return 'manager';
  const v = String(value).toLowerCase().trim();
  const levelMap: Record<string, string> = {
    'executive': 'executive', 'c-suite': 'executive', 'ceo': 'executive', 'cto': 'executive', 'vp': 'executive',
    'manager': 'manager', 'director': 'manager', 'head': 'manager', 'lead': 'manager',
    'specialist': 'specialist', 'engineer': 'specialist', 'analyst': 'specialist', 'developer': 'specialist',
    'individual': 'individual', 'staff': 'individual', 'associate': 'individual',
  };
  return levelMap[v] || 'manager';
}

function normaliseDetailDepth(value: any): string {
  if (!value) return 'moderate';
  const v = String(value).toLowerCase().trim();
  const depthMap: Record<string, string> = {
    'brief': 'brief', 'short': 'brief', 'quick': 'brief', 'summary': 'brief',
    'moderate': 'moderate', 'medium': 'moderate', 'standard': 'moderate', 'average': 'moderate',
    'detailed': 'detailed', 'in-depth': 'detailed', 'thorough': 'detailed',
    'comprehensive': 'comprehensive', 'full': 'comprehensive', 'complete': 'comprehensive', 'extensive': 'comprehensive',
  };
  return depthMap[v] || 'moderate';
}

function normaliseCollectionMethod(value: any): string {
  if (!value) return 'form';
  const v = String(value).toLowerCase().trim();
  const methodMap: Record<string, string> = {
    'form': 'form', 'survey': 'form', 'webform': 'form',
    'email': 'email', 'email-request': 'email',
    'interview': 'interview', 'call': 'interview', 'phone': 'interview',
    'imported': 'imported', 'import': 'imported', 'csv': 'imported',
  };
  return methodMap[v] || 'form';
}

function clampNumber(value: any, min: number, max: number): number {
  const num = Number(value);
  if (isNaN(num)) return min;
  return Math.min(max, Math.max(min, num));
}

/**
 * Normalize AI-generated language values (display names like "English", "Hindi")
 * to ISO 639-1 codes (like "en", "hi"). If the value is already an ISO code, returns it as-is.
 * Only supports English, Hindi, and Marathi (matching Sales Scripts module).
 */
function normalizeLanguageCode(lang: string): string {
  if (!lang) return 'en';
  const LANGUAGE_NAME_TO_CODE: Record<string, string> = {
    'english': 'en',
    'hindi': 'hi',
    'marathi': 'mr',
  };
  const code = LANGUAGE_NAME_TO_CODE[lang.toLowerCase().trim()];
  // If already an ISO code (2-3 chars like 'en', 'hi', 'mr'), return as-is
  if (!code && /^[a-z]{2,3}$/.test(lang.toLowerCase().trim())) {
    return lang.toLowerCase().trim();
  }
  return code || 'en';
}

// ============================================
// PRODUCT AUTO-FILL MAPPING
// ============================================

function normaliseProductStatus(value: any): string {
  if (!value) return 'draft';
  const v = String(value).toLowerCase().trim();
  const statusMap: Record<string, string> = {
    'active': 'active', 'published': 'active', 'live': 'active',
    'draft': 'draft', 'pending': 'draft', 'unpublished': 'draft',
    'discontinued': 'discontinued', 'archived': 'discontinued', 'deprecated': 'discontinued',
  };
  return statusMap[v] || 'draft';
}

function normaliseAudienceType(value: any): string {
  if (!value) return 'both';
  const v = String(value).toLowerCase().trim();
  const audienceMap: Record<string, string> = {
    'b2b': 'b2b', 'business': 'b2b', 'enterprise': 'b2b', 'corporate': 'b2b',
    'b2c': 'b2c', 'consumer': 'b2c', 'retail': 'b2c', 'individual': 'b2c',
    'both': 'both', 'hybrid': 'both', 'mixed': 'both', 'all': 'both',
  };
  return audienceMap[v] || 'both';
}

function extractPriceFromRange(priceRange: any): number {
  if (!priceRange) return 0;
  if (typeof priceRange === 'number') return priceRange;
  const str = String(priceRange);
  const numMatch = str.match(/[\d,.]+/);
  if (!numMatch) return 0;
  const num = parseFloat(numMatch[0].replace(/,/g, ''));
  return isNaN(num) ? 0 : num;
}

/**
 * @param currency Resolved pricing currency (user selection → Business Profile
 *   country). Carried onto the mapped product so the stored `currency` matches
 *   the currency the AI wrote all the money references in.
 */
export function computeProductAutoFillMapping(analysis: Record<string, any>, currency?: string): Record<string, any> {
  const result: Record<string, any> = {};

  if (currency) result.currency = currency;

  // Core fields
  result.name = (analysis.name || '').trim();
  result.status = normaliseProductStatus(analysis.status);
  result.audienceType = normaliseAudienceType(analysis.audienceType);
  result.usp = (analysis.usp || '').trim();
  result.description = (analysis.description || '').trim();
  result.marketingCopy = (analysis.marketingCopy || '').trim();
  result.features = Array.isArray(analysis.features)
    ? analysis.features.filter((f: any) => typeof f === 'string' && f.trim())
    : [];
  result.price = extractPriceFromRange(analysis.priceRange || analysis.price);

  // Strategy fields
  result.primaryKeywords = Array.isArray(analysis.primaryKeywords)
    ? analysis.primaryKeywords.filter((k: any) => typeof k === 'string' && k.trim())
    : [];
  result.competitivePositioning = (analysis.competitivePositioning || '').trim();
  result.priceRange = (analysis.priceRange || '').trim();

  // Content fields
  result.keyBenefits = Array.isArray(analysis.keyBenefits)
    ? analysis.keyBenefits.filter((b: any) => typeof b === 'string' && b.trim())
    : [];
  result.useCases = Array.isArray(analysis.useCases)
    ? analysis.useCases.filter((u: any) => typeof u === 'string' && u.trim())
    : [];
  result.valueProposition = (analysis.valueProposition || '').trim();
  result.elevatorPitch = (analysis.elevatorPitch || '').trim();

  // Enhancement fields
  result.seoTitle = (analysis.seoTitle || '').trim();
  result.seoDescription = (analysis.seoDescription || '').trim();
  result.seoKeywords = Array.isArray(analysis.seoKeywords)
    ? analysis.seoKeywords.filter((k: any) => typeof k === 'string' && k.trim())
    : [];
  result.searchIntent = normaliseSearchIntent(analysis.searchIntent);
  result.aiPriority = normaliseFaqPriority(analysis.aiPriority);
  result.aiContextWeight = clampNumber(analysis.aiContextWeight, 1, 10);
  result.suggestedImprovements = Array.isArray(analysis.suggestedImprovements)
    ? analysis.suggestedImprovements.filter((s: any) => typeof s === 'string' && s.trim())
    : [];

  // ---- Enforce character limits on Product text fields ----
  result.usp = enforceCharLimit(result.usp, 500);
  result.description = enforceCharLimit(result.description, 2000);
  result.marketingCopy = enforceCharLimit(result.marketingCopy, 2000);
  result.competitivePositioning = enforceCharLimit(result.competitivePositioning, 2000);
  result.valueProposition = enforceCharLimit(result.valueProposition, 500);
  result.elevatorPitch = enforceCharLimit(result.elevatorPitch, 500);
  result.seoTitle = enforceCharLimit(result.seoTitle, 60);
  result.seoDescription = enforceCharLimit(result.seoDescription, 160);

  return result;
}

// ============================================
// BLOG AUTO-FILL MAPPING
// ============================================

/**
 * Maps Blog pipeline output to BlogContentOS-compatible structures.
 * Normalises blog strategy, SEO config, content types, titles, and enhancements.
 */
export function computeBlogAutoFillMapping(analysis: Record<string, any>): Record<string, any> {
  const mapping: Record<string, any> = {};

  // Blog Strategy
  if (analysis.blogStrategy && typeof analysis.blogStrategy === 'object') {
    const bs = analysis.blogStrategy;
    mapping.strategy = {
      name: typeof bs.name === 'string' ? bs.name : 'AI-Generated Blog Strategy',
      goals: normaliseBlogGoals(bs.goals),
      targetAudience: typeof bs.targetAudience === 'string' ? bs.targetAudience : '',
      targetRegion: typeof bs.targetRegion === 'string' ? bs.targetRegion : 'Global',
      language: typeof bs.language === 'string' ? normalizeLanguageCode(bs.language) : 'en',
      funnelStage: normaliseFunnelStage(bs.funnelStage),
      competitorBlogs: Array.isArray(bs.competitorBlogs) ? bs.competitorBlogs : [],
      contentDepth: normaliseContentDepth(bs.contentDepth),
      creativityLevel: clampNumber(bs.creativityLevel, 1, 10),
    };
  }

  // SEO Config
  if (analysis.seoConfig && typeof analysis.seoConfig === 'object') {
    const sc = analysis.seoConfig;
    mapping.seoConfig = {
      seoName: typeof sc.seoName === 'string' ? sc.seoName : 'AI-Generated SEO Config',
      searchIntent: normaliseSEOIntent(sc.searchIntent),
      targetAudience: typeof sc.targetAudience === 'string' ? sc.targetAudience : '',
      primaryGoal: normaliseSEOGoalType(sc.primaryGoal),
      primaryKeywords: ensureArray(sc.primaryKeywords),
      secondaryKeywords: ensureArray(sc.secondaryKeywords),
      longTailKeywords: ensureArray(sc.longTailKeywords),
      negativeKeywords: ensureArray(sc.negativeKeywords),
      aiSeoSettings: {
        autoGenerateMetaTitle: typeof sc.autoGenerateMetaTitle === 'boolean' ? sc.autoGenerateMetaTitle : true,
        autoGenerateMetaDescription: typeof sc.autoGenerateMetaDescription === 'boolean' ? sc.autoGenerateMetaDescription : true,
        autoGenerateSlug: typeof sc.autoGenerateSlug === 'boolean' ? sc.autoGenerateSlug : true,
        autoGenerateTOC: typeof sc.autoGenerateTOC === 'boolean' ? sc.autoGenerateTOC : true,
        autoGenerateAltText: typeof sc.autoGenerateAltText === 'boolean' ? sc.autoGenerateAltText : true,
        autoGenerateInternalLinks: typeof sc.autoGenerateInternalLinks === 'boolean' ? sc.autoGenerateInternalLinks : true,
      },
      metaSettings: {
        metaTitleTemplate: typeof sc.metaTitleTemplate === 'string' ? sc.metaTitleTemplate : '{{title}} | {{companyName}}',
        metaDescriptionTemplate: typeof sc.metaDescriptionTemplate === 'string' ? sc.metaDescriptionTemplate : '{{excerpt}}',
        titleMaxLength: clampNumber(sc.titleMaxLength, 30, 100) || 60,
        descriptionMaxLength: clampNumber(sc.descriptionMaxLength, 100, 300) || 160,
      },
      seoRules: {
        minWordCount: clampNumber(sc.minWordCount, 300, 5000) || 800,
        maxWordCount: clampNumber(sc.maxWordCount, 500, 10000) || 2500,
        keywordDensityTarget: clampNumber(sc.keywordDensityTarget, 0.5, 5) || 2,
        includeTOC: typeof sc.includeTOC === 'boolean' ? sc.includeTOC : true,
        includeConclusion: typeof sc.includeConclusion === 'boolean' ? sc.includeConclusion : true,
        includeCTA: typeof sc.includeCTA === 'boolean' ? sc.includeCTA : true,
        maxLinksPerPost: clampNumber(sc.maxLinksPerPost, 0, 20) || 5,
      },
    };
  }

  // Content Types
  if (Array.isArray(analysis.contentTypes) && analysis.contentTypes.length > 0) {
    mapping.contentTypes = analysis.contentTypes.map((ct: any, i: number) => ({
      id: ct.id || `ct-${Date.now()}-${i}`,
      name: typeof ct.name === 'string' ? ct.name : `Content Type ${i + 1}`,
      type: normaliseContentTypeCategory(ct.type),
      enabled: typeof ct.enabled === 'boolean' ? ct.enabled : true,
      percentageAllocation: clampNumber(ct.percentageAllocation, 1, 100) || 20,
      priority: clampNumber(ct.priority, 1, 20) || (i + 1),
      seoIntent: normaliseSEOIntent(ct.seoIntent),
      recommendedLength: clampNumber(ct.recommendedLength, 300, 10000) || 1500,
      funnelPosition: normaliseFunnelStage(ct.funnelPosition || ct.funnelStage),
      ctaStrategy: typeof ct.ctaStrategy === 'string' ? ct.ctaStrategy : '',
      conversionGoal: typeof ct.conversionGoal === 'string' ? ct.conversionGoal : '',
    }));
  }

  // Blog Titles (merged with content enhancements by index)
  const titles = Array.isArray(analysis.titles) ? analysis.titles : [];
  const enhancements = Array.isArray(analysis.contentEnhancements) ? analysis.contentEnhancements : [];

  if (titles.length > 0) {
    mapping.titles = titles.map((t: any, i: number) => {
      const e = enhancements[i] || {};
      return {
        id: t.id || `title-${Date.now()}-${i}`,
        title: typeof t.title === 'string' ? t.title : `Blog Title ${i + 1}`,
        slug: typeof t.slug === 'string' ? t.slug : (typeof t.title === 'string' ? t.title.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '') : `blog-${i + 1}`),
        excerpt: typeof t.excerpt === 'string' ? t.excerpt : '',
        contentType: normaliseContentTypeCategory(t.contentType),
        style: normaliseTitleStyle(t.style),
        seoScore: clampNumber(t.seoScore, 0, 100),
        searchIntent: normaliseSEOIntent(t.searchIntent),
        funnelStage: normaliseFunnelStage(t.funnelStage),
        suggestedKeywords: ensureArray(t.suggestedKeywords),
        suggestedCTA: typeof t.suggestedCTA === 'string' ? t.suggestedCTA : '',
        primaryKeyword: typeof t.primaryKeyword === 'string' ? t.primaryKeyword : '',
        secondaryKeywords: ensureArray(t.secondaryKeywords),
        metaDescription: typeof t.metaDescription === 'string' ? t.metaDescription : '',
        trendingKeywords: ensureArray(t.trendingKeywords),
        // Merged from content enhancement
        contentOutline: typeof e.contentOutline === 'string' ? e.contentOutline : '',
        headingSuggestions: e.headingSuggestions && typeof e.headingSuggestions === 'object' ? {
          h1: typeof e.headingSuggestions.h1 === 'string' ? e.headingSuggestions.h1 : t.title || '',
          h2s: Array.isArray(e.headingSuggestions.h2s) ? e.headingSuggestions.h2s : [],
          h3s: Array.isArray(e.headingSuggestions.h3s) ? e.headingSuggestions.h3s : [],
        } : { h1: t.title || '', h2s: [], h3s: [] },
        contentBrief: typeof e.contentBrief === 'string' ? e.contentBrief : '',
        recommendedWordCount: clampNumber(e.recommendedWordCount, 300, 10000) || 1500,
        imagePrompt: typeof e.imagePrompt === 'string' ? e.imagePrompt : '',
        imageAlt: typeof e.imageAlt === 'string' ? e.imageAlt : '',
        ogImageDescription: typeof e.ogImageDescription === 'string' ? e.ogImageDescription : '',
      };
    });
  }

  // Strategy-level fields
  if (typeof analysis.blogStrategyNotes === 'string') mapping.blogStrategyNotes = analysis.blogStrategyNotes;
  if (Array.isArray(analysis.primaryTopics)) mapping.primaryTopics = ensureArray(analysis.primaryTopics);
  if (typeof analysis.blogClusterTopic === 'string') mapping.blogClusterTopic = analysis.blogClusterTopic;

  // SEO enhancements
  if (analysis.seoEnhancements && typeof analysis.seoEnhancements === 'object') {
    mapping.seoEnhancements = {
      overallKeywordStrategy: typeof analysis.seoEnhancements.overallKeywordStrategy === 'string' ? analysis.seoEnhancements.overallKeywordStrategy : '',
      internalLinkingStrategy: typeof analysis.seoEnhancements.internalLinkingStrategy === 'string' ? analysis.seoEnhancements.internalLinkingStrategy : '',
      pillarPageTopics: Array.isArray(analysis.seoEnhancements.pillarPageTopics) ? analysis.seoEnhancements.pillarPageTopics : [],
    };
  }

  return mapping;
}

// ============================================
// BLOG NORMALISERS
// ============================================

function normaliseBlogGoals(goals: any): string[] {
  const validGoals = ['seo', 'brand-awareness', 'lead-generation', 'product-education', 'authority-building', 'traffic-growth', 'conversion', 'community-building'];
  if (!goals) return ['seo', 'traffic-growth'];
  if (typeof goals === 'string') {
    const mapped = normaliseBlogGoal(goals);
    return validGoals.includes(mapped) ? [mapped] : ['seo'];
  }
  if (Array.isArray(goals)) {
    const mapped = goals.map((g: any) => normaliseBlogGoal(g)).filter((g: string) => validGoals.includes(g));
    return mapped.length > 0 ? [...new Set(mapped)] : ['seo'];
  }
  return ['seo', 'traffic-growth'];
}

function normaliseBlogGoal(value: any): string {
  if (!value) return 'seo';
  const v = String(value).toLowerCase().trim().replace(/\s+/g, '-');
  const goalMap: Record<string, string> = {
    'seo': 'seo', 'search-engine-optimization': 'seo', 'search': 'seo',
    'brand-awareness': 'brand-awareness', 'awareness': 'brand-awareness', 'branding': 'brand-awareness',
    'lead-generation': 'lead-generation', 'leads': 'lead-generation', 'lead-gen': 'lead-generation',
    'product-education': 'product-education', 'education': 'product-education', 'educational': 'product-education',
    'authority-building': 'authority-building', 'authority': 'authority-building', 'thought-leadership': 'authority-building',
    'traffic-growth': 'traffic-growth', 'traffic': 'traffic-growth', 'visitors': 'traffic-growth',
    'conversion': 'conversion', 'conversions': 'conversion', 'convert': 'conversion',
    'community-building': 'community-building', 'community': 'community-building', 'engagement': 'community-building',
  };
  return goalMap[v] || 'seo';
}

function normaliseFunnelStage(value: any): string {
  if (!value) return 'tofu';
  const v = String(value).toLowerCase().trim();
  const stageMap: Record<string, string> = {
    'tofu': 'tofu', 'awareness': 'tofu', 'top-of-funnel': 'tofu', 'discovery': 'tofu',
    'mofu': 'mofu', 'consideration': 'mofu', 'middle-of-funnel': 'mofu', 'evaluation': 'mofu',
    'bofu': 'bofu', 'decision': 'bofu', 'bottom-of-funnel': 'bofu', 'conversion': 'bofu',
  };
  return stageMap[v] || 'tofu';
}

function normaliseContentDepth(value: any): string {
  if (!value) return 'standard';
  const v = String(value).toLowerCase().trim();
  const depthMap: Record<string, string> = {
    'brief': 'brief', 'short': 'brief', 'quick': 'brief', 'summary': 'brief',
    'standard': 'standard', 'medium': 'standard', 'moderate': 'standard', 'average': 'standard',
    'deep': 'deep', 'detailed': 'deep', 'in-depth': 'deep', 'thorough': 'deep',
    'comprehensive': 'comprehensive', 'full': 'comprehensive', 'complete': 'comprehensive', 'extensive': 'comprehensive',
  };
  return depthMap[v] || 'standard';
}

function normaliseContentTypeCategory(value: any): string {
  if (!value) return 'educational';
  const v = String(value).toLowerCase().trim().replace(/\s+/g, '-');
  const typeMap: Record<string, string> = {
    'educational': 'educational', 'education': 'educational', 'informative': 'educational',
    'how-to-guide': 'how-to-guide', 'how-to': 'how-to-guide', 'tutorial': 'how-to-guide', 'guide': 'how-to-guide',
    'industry-trends': 'industry-trends', 'trends': 'industry-trends', 'trending': 'industry-trends',
    'case-study': 'case-study', 'case': 'case-study', 'success-story': 'case-study',
    'comparison': 'comparison', 'vs': 'comparison', 'versus': 'comparison',
    'product-focused': 'product-focused', 'product': 'product-focused', 'feature': 'product-focused',
    'listicle': 'listicle', 'list': 'listicle', 'top': 'listicle',
    'problem-solution': 'problem-solution', 'solution': 'problem-solution',
    'thought-leadership': 'thought-leadership', 'insight': 'thought-leadership',
    'news-analysis': 'news-analysis', 'news': 'news-analysis', 'current-events': 'news-analysis',
    'interview': 'interview', 'q-a': 'interview', 'qa': 'interview',
    'opinion': 'opinion', 'editorial': 'opinion', 'commentary': 'opinion',
    'roundup': 'roundup', 'best-of': 'roundup', 'collection': 'roundup',
    'faq-style': 'faq-style', 'faq': 'faq-style',
  };
  return typeMap[v] || 'educational';
}

function normaliseTitleStyle(value: any): string {
  if (!value) return 'how-to';
  const v = String(value).toLowerCase().trim().replace(/\s+/g, '-');
  const styleMap: Record<string, string> = {
    'how-to': 'how-to', 'how-to-guide': 'how-to', 'guide': 'how-to',
    'listicle': 'listicle', 'list': 'listicle', 'numbered': 'listicle',
    'question': 'question', 'ask': 'question',
    'controversial': 'controversial', 'debate': 'controversial', 'hot-take': 'controversial',
    'data-driven': 'data-driven', 'data': 'data-driven', 'statistical': 'data-driven', 'research': 'data-driven',
    'story': 'story', 'narrative': 'story', 'storytelling': 'story',
    'comparison': 'comparison', 'vs': 'comparison', 'versus': 'comparison',
    'definitive-guide': 'definitive-guide', 'ultimate': 'definitive-guide', 'complete': 'definitive-guide',
    'myth-busting': 'myth-busting', 'debunk': 'myth-busting', 'myth': 'myth-busting',
    'newsjacking': 'newsjacking', 'trending': 'newsjacking', 'current': 'newsjacking',
  };
  return styleMap[v] || 'how-to';
}

function normaliseSEOIntent(value: any): string {
  if (!value) return 'informational';
  const v = String(value).toLowerCase().trim();
  const intentMap: Record<string, string> = {
    'informational': 'informational', 'info': 'informational', 'learn': 'informational',
    'commercial': 'commercial', 'comparison': 'commercial', 'research': 'commercial',
    'transactional': 'transactional', 'buy': 'transactional', 'purchase': 'transactional',
    'navigational': 'navigational', 'find': 'navigational', 'brand': 'navigational',
  };
  return intentMap[v] || 'informational';
}

function normaliseSEOGoalType(value: any): string {
  if (!value) return 'traffic';
  const v = String(value).toLowerCase().trim();
  const goalMap: Record<string, string> = {
    'traffic': 'traffic', 'visitors': 'traffic', 'views': 'traffic',
    'rankings': 'rankings', 'ranking': 'rankings', 'serp': 'rankings', 'position': 'rankings',
    'leads': 'leads', 'lead-generation': 'leads', 'conversions': 'leads',
    'authority': 'authority', 'thought-leadership': 'authority', 'credibility': 'authority',
  };
  return goalMap[v] || 'traffic';
}

// ============================================
// NEWSLETTER AUTO-FILL MAPPING
// ============================================

export function computeNewsletterAutoFillMapping(analysis: Record<string, any>, language?: string): Record<string, any> {
  const mapping: Record<string, any> = {};
  // When generating in a non-English language, the prompt instructs the AI to use English
  // enum values for code fields (objective, funnelStage, etc.), so normalisers work normally.
  // However, if the AI returns non-English values for enum fields despite the instruction,
  // we skip normalisation to avoid overwriting them with English defaults.
  const isNonEnglish = language && language.toLowerCase() !== 'english';

  // Newsletter Strategy
  if (analysis.newsletterStrategy && typeof analysis.newsletterStrategy === 'object') {
    const ns = analysis.newsletterStrategy;
    mapping.strategy = {
      name: typeof ns.name === 'string' ? ns.name : 'AI-Generated Newsletter Strategy',
      objective: isNonEnglish ? (typeof ns.objective === 'string' ? ns.objective : 'education') : normaliseNewsletterGoal(ns.objective),
      audience: typeof ns.audience === 'string' ? ns.audience : '',
      industry: typeof ns.industry === 'string' ? ns.industry : '',
      funnelStage: isNonEnglish ? (typeof ns.funnelStage === 'string' ? ns.funnelStage : 'tofu') : normaliseFunnelStage(ns.funnelStage),
      communicationTone: isNonEnglish ? (typeof ns.communicationTone === 'string' ? ns.communicationTone : 'professional') : normaliseCommunicationTone(ns.communicationTone),
      contentDepth: isNonEnglish ? (typeof ns.contentDepth === 'string' ? ns.contentDepth : 'standard') : normaliseContentDepth(ns.contentDepth),
      ctaGoal: typeof ns.ctaGoal === 'string' ? ns.ctaGoal : '',
    };
  }

  // Content Types
  if (Array.isArray(analysis.contentTypes) && analysis.contentTypes.length > 0) {
    mapping.contentTypes = analysis.contentTypes.map((ct: any, i: number) => ({
      id: ct.id || `nct-${Date.now()}-${i}`,
      name: typeof ct.name === 'string' ? ct.name : `Newsletter Type ${i + 1}`,
      type: isNonEnglish ? (typeof ct.type === 'string' ? ct.type : 'educational') : normaliseNewsletterContentType(ct.type),
      enabled: typeof ct.enabled === 'boolean' ? ct.enabled : true,
      percentageAllocation: clampNumber(ct.percentageAllocation, 1, 100) || 20,
      priority: clampNumber(ct.priority, 1, 20) || (i + 1),
      recommendedLength: clampNumber(ct.recommendedLength, 100, 5000) || 800,
      funnelPosition: isNonEnglish ? (typeof (ct.funnelPosition || ct.funnelStage) === 'string' ? (ct.funnelPosition || ct.funnelStage) : 'tofu') : normaliseFunnelStage(ct.funnelPosition || ct.funnelStage),
      ctaStrategy: typeof ct.ctaStrategy === 'string' ? ct.ctaStrategy : '',
      conversionGoal: typeof ct.conversionGoal === 'string' ? ct.conversionGoal : '',
    }));
  }

  // Newsletter Titles (merged with content enhancements by index)
  const titles = Array.isArray(analysis.titles) ? analysis.titles : [];
  const enhancements = Array.isArray(analysis.contentEnhancements) ? analysis.contentEnhancements : [];

  if (titles.length > 0) {
    mapping.titles = titles.map((t: any, i: number) => {
      const e = enhancements[i] || {};
      return {
        id: t.id || `nl-title-${Date.now()}-${i}`,
        title: typeof t.title === 'string' ? t.title : `Newsletter ${i + 1}`,
        subjectLine: typeof t.subjectLine === 'string' ? t.subjectLine : (typeof t.title === 'string' ? t.title : `Newsletter ${i + 1}`),
        previewText: typeof t.previewText === 'string' ? t.previewText : '',
        contentType: isNonEnglish ? (typeof t.contentType === 'string' ? t.contentType : 'educational') : normaliseNewsletterContentType(t.contentType),
        style: isNonEnglish ? (typeof t.style === 'string' ? t.style : 'educational') : normaliseSubjectLineStyle(t.style),
        engagementScore: clampNumber(t.engagementScore, 0, 100),
        funnelStage: isNonEnglish ? (typeof t.funnelStage === 'string' ? t.funnelStage : 'tofu') : normaliseFunnelStage(t.funnelStage),
        suggestedKeywords: ensureArray(t.suggestedKeywords),
        suggestedCTA: typeof t.suggestedCTA === 'string' ? t.suggestedCTA : (typeof e.suggestedCTA === 'string' ? e.suggestedCTA : ''),
        status: 'generated',
        order: clampNumber(t.order, 1, 20) || (i + 1),
        // Merged from content enhancement
        contentOutline: typeof e.contentOutline === 'string' ? e.contentOutline : '',
        sectionSuggestions: Array.isArray(e.sectionSuggestions) ? e.sectionSuggestions : [],
        contentBrief: typeof e.contentBrief === 'string' ? e.contentBrief : '',
        recommendedWordCount: clampNumber(e.recommendedWordCount, 100, 5000) || 800,
      };
    });
  }

  // Strategy-level fields
  if (typeof analysis.newsletterStrategyNotes === 'string') mapping.newsletterStrategyNotes = analysis.newsletterStrategyNotes;
  if (Array.isArray(analysis.primaryTopics)) mapping.primaryTopics = ensureArray(analysis.primaryTopics);
  if (typeof analysis.newsletterClusterTopic === 'string') mapping.newsletterClusterTopic = analysis.newsletterClusterTopic;

  return mapping;
}

// ============================================
// NEWSLETTER NORMALISERS
// ============================================

function normaliseNewsletterGoal(value: any): string {
  if (!value) return 'education';
  const v = String(value).toLowerCase().trim().replace(/\s+/g, '-');
  const goalMap: Record<string, string> = {
    'education': 'education', 'educational': 'education', 'teaching': 'education',
    'product-awareness': 'product-awareness', 'product': 'product-awareness', 'awareness': 'product-awareness',
    'community-building': 'community-building', 'community': 'community-building', 'engagement': 'community-building',
    'brand-awareness': 'brand-awareness', 'branding': 'brand-awareness', 'visibility': 'brand-awareness',
    'customer-engagement': 'customer-engagement', 'customer': 'customer-engagement',
    'retention': 'retention', 'retain': 'retention', 'loyalty': 'retention',
    'updates': 'updates', 'company-updates': 'updates', 'news': 'updates',
    'founder-communication': 'founder-communication', 'founder': 'founder-communication', 'leadership': 'founder-communication',
    'thought-leadership': 'thought-leadership', 'authority': 'thought-leadership', 'insights': 'thought-leadership',
  };
  return goalMap[v] || 'education';
}

function normaliseCommunicationTone(value: any): string {
  if (!value) return 'professional';
  const v = String(value).toLowerCase().trim();
  const toneMap: Record<string, string> = {
    'professional': 'professional', 'formal': 'professional', 'business': 'professional',
    'conversational': 'conversational', 'friendly': 'conversational', 'casual-conversational': 'conversational',
    'casual': 'casual', 'informal': 'casual', 'relaxed': 'casual',
    'authoritative': 'authoritative', 'expert': 'authoritative', 'commanding': 'authoritative',
    'empathetic': 'empathetic', 'caring': 'empathetic', 'warm': 'empathetic',
    'inspirational': 'inspirational', 'motivational': 'inspirational', 'uplifting': 'inspirational',
    'witty': 'witty', 'humorous': 'witty', 'clever': 'witty',
    'minimal': 'minimal', 'concise': 'minimal', 'brief': 'minimal',
  };
  return toneMap[v] || 'professional';
}

function normaliseSubjectLineStyle(value: any): string {
  if (!value) return 'educational';
  const v = String(value).toLowerCase().trim().replace(/\s+/g, '-');
  const styleMap: Record<string, string> = {
    'educational': 'educational', 'informative': 'educational', 'teaching': 'educational',
    'conversational': 'conversational', 'friendly': 'conversational', 'casual': 'conversational',
    'founder-style': 'founder-style', 'personal': 'founder-style', 'letter': 'founder-style',
    'authority': 'authority', 'expert': 'authority', 'definitive': 'authority',
    'emotional': 'emotional', 'storytelling': 'emotional', 'feeling': 'emotional',
    'insight': 'insight', 'data-driven': 'insight', 'analytical': 'insight',
    'minimal': 'minimal', 'clean': 'minimal', 'simple': 'minimal',
  };
  return styleMap[v] || 'educational';
}

function normaliseNewsletterContentType(value: any): string {
  if (!value) return 'educational';
  const v = String(value).toLowerCase().trim().replace(/\s+/g, '-');
  const typeMap: Record<string, string> = {
    'educational': 'educational', 'education': 'educational', 'informative': 'educational',
    'how-to-guide': 'how-to-guide', 'how-to': 'how-to-guide', 'tutorial': 'how-to-guide', 'guide': 'how-to-guide',
    'industry-trends': 'industry-trends', 'trends': 'industry-trends', 'trending': 'industry-trends',
    'case-study': 'case-study', 'case': 'case-study', 'success-story': 'case-study',
    'comparison': 'comparison', 'vs': 'comparison', 'versus': 'comparison',
    'product-focused': 'product-focused', 'product': 'product-focused', 'feature': 'product-focused',
    'listicle': 'listicle', 'list': 'listicle', 'top': 'listicle',
    'problem-solution': 'problem-solution', 'solution': 'problem-solution',
    'thought-leadership': 'thought-leadership', 'insight': 'thought-leadership', 'opinion': 'thought-leadership',
    'news-analysis': 'news-analysis', 'news': 'news-analysis', 'current-events': 'news-analysis',
    'interview': 'interview', 'q-a': 'interview', 'qa': 'interview',
    'roundup': 'roundup', 'best-of': 'roundup', 'collection': 'roundup',
    'faq-style': 'faq-style', 'faq': 'faq-style',
    'founder-letter': 'founder-letter', 'founder': 'founder-letter', 'personal-note': 'founder-letter',
    'weekly-digest': 'weekly-digest', 'digest': 'weekly-digest', 'weekly': 'weekly-digest',
  };
  return typeMap[v] || 'educational';
}

// ============================================
// LANDING PAGE AUTO-FILL MAPPING
// ============================================

export function computeLandingPageAutoFillMapping(analysis: Record<string, any>): Record<string, any> {
  const mapping: Record<string, any> = {};

  // Direct string fields
  const stringFields = ['name', 'headline', 'subHeadline', 'ctaText', 'ctaGoal', 'conversionGoal', 'metaTitle', 'metaDescription', 'searchIntent'] as const;
  for (const field of stringFields) {
    if (analysis[field] && typeof analysis[field] === 'string') {
      mapping[field] = analysis[field];
    }
  }

  // Constrained enum: pageType
  if (analysis.pageType) {
    mapping.pageType = normaliseLandingPageType(analysis.pageType);
  }

  // Constrained enum: primaryGoal
  if (analysis.primaryGoal) {
    mapping.primaryGoal = normaliseLandingPageGoal(analysis.primaryGoal);
  }

  // Constrained enum: funnelStage
  if (analysis.funnelStage) {
    mapping.funnelStage = normaliseLandingPageFunnelStage(analysis.funnelStage);
  }

  // Constrained enum: framework
  if (analysis.framework) {
    mapping.framework = normaliseLandingPageFramework(analysis.framework);
  }

  // Constrained enum: trafficSource
  if (analysis.trafficSource) {
    mapping.trafficSource = normaliseLandingPageTrafficSource(analysis.trafficSource);
  }

  // Sections array — normalise structure
  if (Array.isArray(analysis.sections) && analysis.sections.length > 0) {
    mapping.sections = analysis.sections.map((s: any, index: number) => ({
      id: s.id || `section-${Date.now()}-${index}`,
      type: normaliseLandingPageSectionType(s.type),
      name: typeof s.name === 'string' ? s.name : `Section ${index + 1}`,
      enabled: typeof s.enabled === 'boolean' ? s.enabled : true,
      order: typeof s.order === 'number' ? s.order : index,
      headline: typeof s.headline === 'string' ? s.headline : '',
      subheadline: typeof s.subheadline === 'string' ? s.subheadline : '',
      description: typeof s.description === 'string' ? s.description : '',
      cta: typeof s.cta === 'string' ? s.cta : '',
      bulletPoints: Array.isArray(s.bulletPoints) ? s.bulletPoints.filter((b: any) => typeof b === 'string') : [],
      trustStatements: Array.isArray(s.trustStatements) ? s.trustStatements.filter((t: any) => typeof t === 'string') : [],
      uiNotes: typeof s.uiNotes === 'string' ? s.uiNotes : '',
      conversionNotes: typeof s.conversionNotes === 'string' ? s.conversionNotes : '',
      seoNotes: typeof s.seoNotes === 'string' ? s.seoNotes : '',
    }));
  }

  // SEO Keywords — ensure string array
  if (analysis.seoKeywords) {
    mapping.seoKeywords = ensureArray(analysis.seoKeywords);
  }

  // New SEO fields from expanded Stage 3
  const seoStringFields = ['focusKeyword', 'h1Heading', 'ogTitle', 'ogDescription'] as const;
  for (const field of seoStringFields) {
    if (analysis[field] && typeof analysis[field] === 'string') {
      mapping[field] = analysis[field];
    }
  }

  // SEO keyword arrays
  const seoArrayFields = ['secondaryKeywords', 'targetKeywords', 'h2Headings'] as const;
  for (const field of seoArrayFields) {
    if (analysis[field]) {
      mapping[field] = ensureArray(analysis[field]);
    }
  }

  // Design references — ensure string array
  if (analysis.designReferences) {
    mapping.designReferences = ensureArray(analysis.designReferences);
  }

  // Competitor references — ensure string array
  if (analysis.competitorReferences) {
    mapping.competitorReferences = ensureArray(analysis.competitorReferences);
  }

  return mapping;
}

// ============================================
// LANDING PAGE NORMALISERS
// ============================================

function normaliseLandingPageType(value: string): string {
  const v = String(value).toLowerCase().trim().replace(/\s+/g, '-');
  const validTypes = ['lead-generation', 'product', 'service', 'saas', 'webinar', 'event', 'app-download', 'sales', 'long-form-sales', 'course', 'book', 'founder-brand', 'employee-portfolio', 'consultation-booking', 'demo-booking', 'offer', 'discount-campaign', 'launch', 'waitlist', 'recruitment', 'affiliate', 'referral', 'case-study', 'industry-specific', 'location-based'];
  const typeMap: Record<string, string> = {
    'lead-generation': 'lead-generation', 'lead-gen': 'lead-generation', 'lead': 'lead-generation', 'lead-capture': 'lead-generation', 'magnet': 'lead-generation',
    'product': 'product', 'product-page': 'product', 'product-landing': 'product',
    'service': 'service', 'service-page': 'service', 'service-landing': 'service',
    'saas': 'saas', 'software': 'saas', 'software-as-a-service': 'saas', 'app': 'saas',
    'webinar': 'webinar', 'webinar-registration': 'webinar', 'webinar-signup': 'webinar',
    'event': 'event', 'event-page': 'event', 'event-registration': 'event',
    'app-download': 'app-download', 'mobile-app': 'app-download', 'download': 'app-download',
    'sales': 'sales', 'sales-page': 'sales', 'direct-sales': 'sales',
    'long-form-sales': 'long-form-sales', 'long-form': 'long-form-sales', 'sales-letter': 'long-form-sales',
    'course': 'course', 'course-page': 'course', 'online-course': 'course',
    'book': 'book', 'book-page': 'book', 'book-landing': 'book',
    'founder-brand': 'founder-brand', 'personal-brand': 'founder-brand', 'founder': 'founder-brand',
    'employee-portfolio': 'employee-portfolio', 'team-portfolio': 'employee-portfolio', 'team-member': 'employee-portfolio',
    'consultation-booking': 'consultation-booking', 'consultation': 'consultation-booking', 'booking': 'consultation-booking',
    'demo-booking': 'demo-booking', 'demo': 'demo-booking', 'book-demo': 'demo-booking',
    'offer': 'offer', 'offer-page': 'offer', 'special-offer': 'offer',
    'discount-campaign': 'discount-campaign', 'discount': 'discount-campaign', 'promo': 'discount-campaign',
    'launch': 'launch', 'product-launch': 'launch', 'new-launch': 'launch',
    'waitlist': 'waitlist', 'wait-list': 'waitlist', 'early-access': 'waitlist',
    'recruitment': 'recruitment', 'hiring': 'recruitment', 'careers': 'recruitment',
    'affiliate': 'affiliate', 'affiliate-page': 'affiliate', 'partner': 'affiliate',
    'referral': 'referral', 'referral-page': 'referral', 'refer-a-friend': 'referral',
    'case-study': 'case-study', 'case-study-page': 'case-study', 'success-story': 'case-study',
    'industry-specific': 'industry-specific', 'industry': 'industry-specific', 'niche': 'industry-specific',
    'location-based': 'location-based', 'local': 'location-based', 'geo': 'location-based',
  };
  return typeMap[v] || 'lead-generation';
}

function normaliseLandingPageGoal(value: string): string {
  const v = String(value).toLowerCase().trim().replace(/\s+/g, '-');
  const goalMap: Record<string, string> = {
    'lead-generation': 'lead-generation', 'lead-gen': 'lead-generation', 'leads': 'lead-generation',
    'demo-booking': 'demo-booking', 'demo': 'demo-booking', 'book-demo': 'demo-booking',
    'call-booking': 'call-booking', 'call': 'call-booking', 'book-call': 'call-booking',
    'product-purchase': 'product-purchase', 'purchase': 'product-purchase', 'buy': 'product-purchase',
    'webinar-registration': 'webinar-registration', 'webinar': 'webinar-registration', 'webinar-signup': 'webinar-registration',
    'whatsapp-lead': 'whatsapp-lead', 'whatsapp': 'whatsapp-lead', 'whatsapp-contact': 'whatsapp-lead',
    'form-submission': 'form-submission', 'form': 'form-submission', 'contact-form': 'form-submission',
    'download': 'download', 'free-download': 'download', 'resource-download': 'download',
    'consultation-booking': 'consultation-booking', 'consultation': 'consultation-booking',
    'email-collection': 'email-collection', 'email': 'email-collection', 'newsletter-signup': 'email-collection',
  };
  return goalMap[v] || 'lead-generation';
}

function normaliseLandingPageFunnelStage(value: string): string {
  const v = String(value).toLowerCase().trim();
  if (v.includes('tofu') || v.includes('top') || v.includes('awareness')) return 'tofu';
  if (v.includes('mofu') || v.includes('middle') || v.includes('consideration')) return 'mofu';
  if (v.includes('bofu') || v.includes('bottom') || v.includes('decision')) return 'bofu';
  return 'tofu';
}

function normaliseLandingPageFramework(value: string): string {
  const v = String(value).toLowerCase().trim();
  if (v.includes('brunson') || v.includes('funnel') || v.includes('clickfunnels')) return 'brunson';
  if (v.includes('hormozi') || v.includes('offer') || v.includes('value')) return 'hormozi';
  if (v.includes('ogilvy') || v.includes('advertising') || v.includes('copy')) return 'ogilvy';
  if (v.includes('storybrand') || v.includes('story') || v.includes('narrative')) return 'storybrand';
  return 'custom';
}

function normaliseLandingPageTrafficSource(value: string): string {
  const v = String(value).toLowerCase().trim().replace(/\s+/g, '-');
  const sourceMap: Record<string, string> = {
    'facebook-ads': 'facebook-ads', 'fb-ads': 'facebook-ads', 'facebook': 'facebook-ads', 'meta-ads': 'facebook-ads',
    'google-ads': 'google-ads', 'adwords': 'google-ads', 'google': 'google-ads', 'sem': 'google-ads',
    'seo': 'seo', 'organic': 'seo', 'organic-search': 'seo',
    'email': 'email', 'email-campaign': 'email', 'email-marketing': 'email',
    'social-organic': 'social-organic', 'social': 'social-organic', 'organic-social': 'social-organic',
    'affiliate': 'affiliate', 'partner': 'affiliate',
    'direct': 'direct', 'direct-traffic': 'direct',
    'referral': 'referral', 'word-of-mouth': 'referral',
  };
  return sourceMap[v] || 'facebook-ads';
}

function normaliseLandingPageSectionType(value: string): string {
  const v = String(value).toLowerCase().trim().replace(/\s+/g, '-');
  const validTypes = ['hero', 'pain-points', 'solution-explanation', 'features', 'benefits', 'how-it-works', 'social-proof', 'testimonials', 'case-studies', 'client-logos', 'pricing', 'offer-breakdown', 'bonuses', 'guarantee', 'faqs', 'cta-section', 'lead-form', 'video-block', 'founder-story', 'comparison-table', 'statistics', 'custom'];
  const typeMap: Record<string, string> = {
    'hero': 'hero', 'hero-section': 'hero', 'hero-banner': 'hero',
    'pain-points': 'pain-points', 'pain': 'pain-points', 'problem': 'pain-points', 'problem-statement': 'pain-points',
    'solution-explanation': 'solution-explanation', 'solution': 'solution-explanation', 'our-solution': 'solution-explanation',
    'features': 'features', 'feature-list': 'features', 'key-features': 'features',
    'benefits': 'benefits', 'benefit': 'benefits', 'why-choose': 'benefits',
    'how-it-works': 'how-it-works', 'process': 'how-it-works', 'steps': 'how-it-works',
    'social-proof': 'social-proof', 'proof': 'social-proof', 'trust': 'social-proof',
    'testimonials': 'testimonials', 'testimonial': 'testimonials', 'reviews': 'testimonials',
    'case-studies': 'case-studies', 'case-study': 'case-studies', 'success-stories': 'case-studies',
    'client-logos': 'client-logos', 'logos': 'client-logos', 'trusted-by': 'client-logos',
    'pricing': 'pricing', 'price': 'pricing', 'plans': 'pricing',
    'offer-breakdown': 'offer-breakdown', 'offer': 'offer-breakdown', 'whats-included': 'offer-breakdown',
    'bonuses': 'bonuses', 'bonus': 'bonuses',
    'guarantee': 'guarantee', 'risk-reversal': 'guarantee', 'warranty': 'guarantee',
    'faqs': 'faqs', 'faq': 'faqs', 'questions': 'faqs',
    'cta-section': 'cta-section', 'cta': 'cta-section', 'call-to-action': 'cta-section',
    'lead-form': 'lead-form', 'form': 'lead-form', 'contact-form': 'lead-form', 'signup-form': 'lead-form',
    'video-block': 'video-block', 'video': 'video-block', 'demo-video': 'video-block',
    'founder-story': 'founder-story', 'about': 'founder-story', 'our-story': 'founder-story',
    'comparison-table': 'comparison-table', 'compare': 'comparison-table', 'vs': 'comparison-table',
    'statistics': 'statistics', 'stats': 'statistics', 'numbers': 'statistics',
  };
  return typeMap[v] || 'custom';
}

// ============================================
// SALES SCRIPT AUTO-FILL MAPPING
// ============================================

export function computeSalesScriptAutoFillMapping(analysis: Record<string, any>): Record<string, any> {
  const mapping: Record<string, any> = {};

  // Direct string fields
  const stringFields = ['title', 'description', 'openingLine', 'hook', 'valueProposition', 'offerPresentation', 'closingCTA', 'followUpCTA', 'exitResponse', 'brandTone', 'communicationStyle', 'messagingGuidelines', 'trainingNotes', 'coachingNotes', 'targetIndustry', 'targetPersona', 'category', 'fullConversationScript'] as const;
  for (const field of stringFields) {
    if (analysis[field] && typeof analysis[field] === 'string') {
      mapping[field] = analysis[field];
    }
  }

  // Constrained enum: scriptType
  if (analysis.scriptType) {
    mapping.scriptType = normaliseScriptType(analysis.scriptType);
  }

  // Constrained enum: status
  mapping.status = normaliseScriptStatus(analysis.status);

  // Constrained enum: funnelStage
  if (analysis.funnelStage) {
    mapping.funnelStage = normaliseSalesFunnelStage(analysis.funnelStage);
  }

  // Constrained enum: audienceType
  if (analysis.audienceType) {
    mapping.audienceType = normaliseSalesAudienceType(analysis.audienceType);
  }

  // Constrained enum: priority
  if (analysis.priority) {
    mapping.priority = normaliseScriptPriority(analysis.priority);
  }

  // Channels array
  if (Array.isArray(analysis.channels)) {
    mapping.channels = analysis.channels
      .map((ch: any) => normaliseCommunicationChannel(ch))
      .filter(Boolean);
  }

  // Tags array
  if (analysis.tags) {
    mapping.tags = ensureArray(analysis.tags);
  }

  // Best practices array
  if (analysis.bestPractices) {
    mapping.bestPractices = ensureArray(analysis.bestPractices);
  }

  // Call examples array
  if (analysis.callExamples) {
    mapping.callExamples = ensureArray(analysis.callExamples);
  }

  // Sections array — normalise structure
  if (Array.isArray(analysis.sections) && analysis.sections.length > 0) {
    mapping.sections = analysis.sections.map((s: any, index: number) => ({
      id: s.id || `sec-${Date.now()}-${index}`,
      type: normaliseScriptSectionType(s.type),
      title: typeof s.title === 'string' ? s.title : `Section ${index + 1}`,
      content: typeof s.content === 'string' ? s.content : '',
      order: typeof s.order === 'number' ? s.order : index,
      isRequired: typeof s.isRequired === 'boolean' ? s.isRequired : true,
      tips: Array.isArray(s.tips) ? s.tips.filter((t: any) => typeof t === 'string') : [],
    }));
  }

  // Qualification questions array
  if (Array.isArray(analysis.qualificationQuestions) && analysis.qualificationQuestions.length > 0) {
    mapping.qualificationQuestions = analysis.qualificationQuestions.map((q: any, index: number) => ({
      id: q.id || `qq-${Date.now()}-${index}`,
      question: typeof q.question === 'string' ? q.question : '',
      purpose: typeof q.purpose === 'string' ? q.purpose : '',
      followUpIfYes: typeof q.followUpIfYes === 'string' ? q.followUpIfYes : '',
      followUpIfNo: typeof q.followUpIfNo === 'string' ? q.followUpIfNo : '',
      order: typeof q.order === 'number' ? q.order : index,
    }));
  }

  // Objection responses array
  if (Array.isArray(analysis.objectionResponses) && analysis.objectionResponses.length > 0) {
    mapping.objectionResponses = analysis.objectionResponses.map((o: any, index: number) => ({
      id: o.id || `obj-${Date.now()}-${index}`,
      objection: typeof o.objection === 'string' ? o.objection : '',
      response: typeof o.response === 'string' ? o.response : '',
      trustBuildingLine: typeof o.trustBuildingLine === 'string' ? o.trustBuildingLine : '',
      ctaSuggestion: typeof o.ctaSuggestion === 'string' ? o.ctaSuggestion : '',
      order: typeof o.order === 'number' ? o.order : index,
    }));
  }

  // Conversation branches array
  if (Array.isArray(analysis.conversationBranches) && analysis.conversationBranches.length > 0) {
    mapping.conversationBranches = analysis.conversationBranches.map((b: any, index: number) => ({
      id: b.id || `br-${Date.now()}-${index}`,
      trigger: typeof b.trigger === 'string' ? b.trigger : '',
      response: typeof b.response === 'string' ? b.response : '',
      nextSection: typeof b.nextSection === 'string' ? b.nextSection : '',
      order: typeof b.order === 'number' ? b.order : index,
    }));
  }

  // Default values
  mapping.aiGenerated = true;
  mapping.isPublic = true;
  mapping.language = 'en';
  mapping.version = 1;

  return mapping;
}

// ============================================
// SALES SCRIPT NORMALISERS
// ============================================

function normaliseScriptType(value: string): string {
  const v = String(value).toLowerCase().trim().replace(/\s+/g, '-');
  const validTypes = ['cold-call', 'warm-call', 'qualification', 'discovery', 'demo', 'sales-pitch', 'follow-up', 'negotiation', 'closing', 'whatsapp', 'email', 'linkedin', 'voice-note', 'appointment', 'reactivation', 'referral', 'upselling', 'cross-selling', 'retention', 'renewal', 'customer-success', 'objection-handling'];
  const typeMap: Record<string, string> = {
    'cold-call': 'cold-call', 'cold': 'cold-call', 'cold-outreach': 'cold-call', 'cold-calling': 'cold-call',
    'warm-call': 'warm-call', 'warm': 'warm-call', 'warm-outreach': 'warm-call',
    'qualification': 'qualification', 'qualifying': 'qualification', 'qualify': 'qualification',
    'discovery': 'discovery', 'discover': 'discovery', 'research': 'discovery',
    'demo': 'demo', 'product-demo': 'demo', 'demonstration': 'demo',
    'sales-pitch': 'sales-pitch', 'pitch': 'sales-pitch', 'presentation': 'sales-pitch',
    'follow-up': 'follow-up', 'followup': 'follow-up', 'follow': 'follow-up',
    'negotiation': 'negotiation', 'negotiate': 'negotiation',
    'closing': 'closing', 'close': 'closing', 'deal-close': 'closing',
    'whatsapp': 'whatsapp', 'wa': 'whatsapp', 'whatsapp-message': 'whatsapp',
    'email': 'email', 'email-outreach': 'email', 'email-script': 'email',
    'linkedin': 'linkedin', 'li': 'linkedin', 'linkedin-message': 'linkedin',
    'voice-note': 'voice-note', 'voicenote': 'voice-note', 'voice-message': 'voice-note',
    'appointment': 'appointment', 'booking': 'appointment', 'scheduling': 'appointment',
    'reactivation': 'reactivation', 'win-back': 'reactivation', 'reactivate': 'reactivation',
    'referral': 'referral', 'refer': 'referral', 'referral-request': 'referral',
    'upselling': 'upselling', 'upsell': 'upselling', 'upgrade': 'upselling',
    'cross-selling': 'cross-selling', 'cross-sell': 'cross-selling',
    'retention': 'retention', 'retain': 'retention', 'keep': 'retention',
    'renewal': 'renewal', 'renew': 'renewal', 'contract-renewal': 'renewal',
    'customer-success': 'customer-success', 'success': 'customer-success', 'onboarding': 'customer-success',
    'objection-handling': 'objection-handling', 'objection': 'objection-handling', 'handle-objections': 'objection-handling',
  };
  return typeMap[v] || 'cold-call';
}

function normaliseScriptStatus(value: any): ScriptStatus {
  if (!value) return 'draft';
  const v = String(value).toLowerCase().trim();
  const validStatuses: ScriptStatus[] = ['draft', 'review', 'approved', 'published', 'archived'];
  // Direct match
  if (validStatuses.includes(v as ScriptStatus)) return v as ScriptStatus;
  // Fuzzy mapping
  const statusMap: Record<string, ScriptStatus> = {
    'draft': 'draft', 'new': 'draft', 'idea': 'draft', 'created': 'draft',
    'review': 'review', 'in-review': 'review', 'pending': 'review', 'pending-review': 'review',
    'approved': 'approved', 'accepted': 'approved', 'ready': 'approved',
    'published': 'published', 'active': 'published', 'live': 'published', 'in-use': 'published',
    'archived': 'archived', 'inactive': 'archived', 'disabled': 'archived', 'deprecated': 'archived',
  };
  return statusMap[v] || 'draft';
}

function normaliseSalesFunnelStage(value: string): string {
  const v = String(value).toLowerCase().trim();
  if (v.includes('awareness') || v.includes('aware') || v.includes('top')) return 'awareness';
  if (v.includes('interest') || v.includes('interested') || v.includes('engaged')) return 'interest';
  if (v.includes('consideration') || v.includes('consider') || v.includes('evaluat')) return 'consideration';
  if (v.includes('decision') || v.includes('decide') || v.includes('choosing')) return 'decision';
  if (v.includes('purchase') || v.includes('buy') || v.includes('buying')) return 'purchase';
  if (v.includes('retention') || v.includes('retain') || v.includes('keep')) return 'retention';
  if (v.includes('advocacy') || v.includes('advocate') || v.includes('referral') || v.includes('promote')) return 'advocacy';
  return 'awareness';
}

function normaliseSalesAudienceType(value: string): string {
  const v = String(value).toLowerCase().trim();
  if (v.includes('prospect') || v.includes('suspect') || v.includes('cold')) return 'prospect';
  if (v.includes('lead') || v.includes('mql') || v.includes('sql')) return 'lead';
  if (v.includes('opportunity') || v.includes('deal') || v.includes('pipeline')) return 'opportunity';
  if (v.includes('customer') || v.includes('client') || v.includes('account')) return 'customer';
  if (v.includes('partner') || v.includes('channel') || v.includes('reseller')) return 'partner';
  if (v.includes('investor') || v.includes('funder') || v.includes('vc')) return 'investor';
  return 'prospect';
}

function normaliseScriptPriority(value: string): string {
  const v = String(value).toLowerCase().trim();
  if (v.includes('critical') || v.includes('essential') || v.includes('must')) return 'critical';
  if (v.includes('high') || v.includes('important') || v.includes('urgent')) return 'high';
  if (v.includes('low') || v.includes('minor') || v.includes('optional')) return 'low';
  return 'medium';
}

function normaliseCommunicationChannel(value: string): string {
  const v = String(value).toLowerCase().trim().replace(/\s+/g, '-');
  const channelMap: Record<string, string> = {
    'phone': 'phone', 'call': 'phone', 'phone-call': 'phone', 'telecom': 'phone',
    'whatsapp': 'whatsapp', 'wa': 'whatsapp',
    'linkedin': 'linkedin', 'li': 'linkedin', 'linkedin-message': 'linkedin',
    'email': 'email', 'e-mail': 'email', 'mail': 'email',
    'zoom': 'zoom', 'zoom-call': 'zoom', 'video-call': 'zoom',
    'google-meet': 'google-meet', 'meet': 'google-meet', 'gmeet': 'google-meet',
    'in-person': 'in-person', 'face-to-face': 'in-person', 'f2f': 'in-person', 'onsite': 'in-person',
    'sms': 'sms', 'text': 'sms', 'text-message': 'sms',
    'voice-note': 'voice-note', 'voicenote': 'voice-note', 'voice-message': 'voice-note',
  };
  return channelMap[v] || '';
}

function normaliseScriptSectionType(value: string): string {
  const v = String(value).toLowerCase().trim().replace(/\s+/g, '-');
  const validTypes = ['opening', 'hook', 'qualification', 'pain-discovery', 'value-position', 'offer', 'objection', 'trust', 'social-proof', 'closing', 'follow-up', 'exit'];
  const typeMap: Record<string, string> = {
    'opening': 'opening', 'open': 'opening', 'intro': 'opening', 'introduction': 'opening', 'greeting': 'opening',
    'hook': 'hook', 'attention': 'hook', 'icebreaker': 'hook',
    'qualification': 'qualification', 'qualify': 'qualification', 'qualifying': 'qualification',
    'pain-discovery': 'pain-discovery', 'pain': 'pain-discovery', 'problem': 'pain-discovery', 'pain-point': 'pain-discovery', 'pain-points': 'pain-discovery',
    'value-position': 'value-position', 'value': 'value-position', 'value-prop': 'value-position', 'value-proposition': 'value-position',
    'offer': 'offer', 'offering': 'offer', 'proposal': 'offer', 'pricing': 'offer',
    'objection': 'objection', 'objections': 'objection', 'objection-handling': 'objection',
    'trust': 'trust', 'credibility': 'trust', 'trust-building': 'trust',
    'social-proof': 'social-proof', 'proof': 'social-proof', 'testimonials': 'social-proof', 'case-study': 'social-proof',
    'closing': 'closing', 'close': 'closing', 'ask': 'closing', 'cta': 'closing',
    'follow-up': 'follow-up', 'followup': 'follow-up', 'next-steps': 'follow-up',
    'exit': 'exit', 'wrap-up': 'exit', 'goodbye': 'exit',
  };
  return typeMap[v] || 'opening';
}

// ============================================
// SALES COLLATERAL AUTO-FILL MAPPING
// ============================================

export function computeSalesCollateralAutoFillMapping(analysis: Record<string, any>): Record<string, any> {
  const mapping: Record<string, any> = {};

  // Direct string fields
  const stringFields = ['name', 'description', 'valueProposition', 'callToAction', 'secondaryCTA', 'targetPersona', 'designBrief', 'usageNotes', 'followUpStrategy', 'idealTiming', 'department'] as const;
  for (const field of stringFields) {
    if (analysis[field] && typeof analysis[field] === 'string') {
      mapping[field] = analysis[field];
    }
  }

  // Constrained enum: type
  if (analysis.type) {
    mapping.type = normaliseCollateralType(analysis.type);
  }

  // Constrained enum: category
  if (analysis.category) {
    mapping.category = normaliseCollateralCategory(analysis.category);
  }

  // Constrained enum: funnelStage (maps to SalesStage)
  if (analysis.funnelStage) {
    mapping.funnelStage = normaliseCollateralFunnelStage(analysis.funnelStage);
  }

  // Constrained enum: accessLevel
  mapping.accessLevel = 'team';

  // Constrained enum: status
  mapping.status = 'draft';

  // Tags array
  if (analysis.tags) {
    mapping.tags = ensureArray(analysis.tags);
  }

  // Industry tags array
  if (analysis.industryTags) {
    mapping.industryTags = ensureArray(analysis.industryTags);
  }

  // Key messages array
  if (analysis.keyMessages) {
    mapping.keyMessages = ensureArray(analysis.keyMessages);
  }

  // Talking points array
  if (analysis.talkingPoints) {
    mapping.talkingPoints = ensureArray(analysis.talkingPoints);
  }

  // Best practices array
  if (analysis.bestPractices) {
    mapping.bestPractices = ensureArray(analysis.bestPractices);
  }

  // Effectiveness tips array
  if (analysis.effectivenessTips) {
    mapping.effectivenessTips = ensureArray(analysis.effectivenessTips);
  }

  // Success metrics array
  if (analysis.successMetrics) {
    mapping.successMetrics = ensureArray(analysis.successMetrics);
  }

  // Suggested distribution channels array
  if (analysis.suggestedDistributionChannels) {
    mapping.suggestedDistributionChannels = ensureArray(analysis.suggestedDistributionChannels);
  }

  // Sections array — normalise structure
  if (Array.isArray(analysis.sections) && analysis.sections.length > 0) {
    mapping.sections = analysis.sections.map((s: any, index: number) => ({
      id: s.id || `sec-${Date.now()}-${index}`,
      title: typeof s.title === 'string' ? s.title : `Section ${index + 1}`,
      content: typeof s.content === 'string' ? s.content : '',
      order: typeof s.order === 'number' ? s.order : index,
    }));
  }

  // Objection responses array
  if (Array.isArray(analysis.objectionResponses) && analysis.objectionResponses.length > 0) {
    mapping.objectionResponses = analysis.objectionResponses.map((o: any, index: number) => ({
      id: o.id || `obj-${Date.now()}-${index}`,
      objection: typeof o.objection === 'string' ? o.objection : '',
      response: typeof o.response === 'string' ? o.response : '',
      order: typeof o.order === 'number' ? o.order : index,
    }));
  }

  // Default values
  mapping.aiGenerated = true;
  mapping.isFavorite = false;
  mapping.isPinned = false;
  mapping.version = '1.0';

  return mapping;
}

// ============================================
// SALES COLLATERAL NORMALISERS
// ============================================

function normaliseCollateralType(value: string): string {
  const v = String(value).toLowerCase().trim().replace(/\s+/g, '-');
  const validTypes = ['one-pager', 'brochure', 'company-profile', 'media-kit', 'case-study', 'whitepaper', 'datasheet', 'proposal', 'product-deck', 'service-deck', 'pricing-sheet', 'pitch-deck', 'demo-video', 'product-demo', 'feature-document', 'technical-specification', 'testimonial-asset', 'roi-document', 'comparison-sheet', 'sales-flyer', 'portfolio', 'client-presentation', 'explainer-video'];
  const typeMap: Record<string, string> = {
    'one-pager': 'one-pager', 'onepager': 'one-pager', 'one-page': 'one-pager', '1-pager': 'one-pager', 'single-page': 'one-pager', 'fact-sheet': 'one-pager',
    'brochure': 'brochure', 'pamphlet': 'brochure', 'booklet': 'brochure',
    'company-profile': 'company-profile', 'company': 'company-profile', 'company-overview': 'company-profile', 'about-us': 'company-profile', 'profile': 'company-profile',
    'media-kit': 'media-kit', 'mediakit': 'media-kit', 'press-kit': 'media-kit', 'presskit': 'media-kit', 'pr-kit': 'media-kit',
    'case-study': 'case-study', 'casestudy': 'case-study', 'case': 'case-study', 'success-story': 'case-study', 'customer-story': 'case-study',
    'whitepaper': 'whitepaper', 'white-paper': 'whitepaper', 'research-paper': 'whitepaper', 'report': 'whitepaper',
    'datasheet': 'datasheet', 'data-sheet': 'datasheet', 'spec-sheet': 'datasheet', 'specification-sheet': 'datasheet', 'product-sheet': 'datasheet',
    'proposal': 'proposal', 'business-proposal': 'proposal', 'deal-proposal': 'proposal', 'rfp': 'proposal',
    'product-deck': 'product-deck', 'productdeck': 'product-deck', 'product-presentation': 'product-deck', 'product-overview': 'product-deck', 'product-slides': 'product-deck',
    'service-deck': 'service-deck', 'servicedeck': 'service-deck', 'service-presentation': 'service-deck', 'service-overview': 'service-deck',
    'pricing-sheet': 'pricing-sheet', 'pricingsheet': 'pricing-sheet', 'pricing': 'pricing-sheet', 'price-list': 'pricing-sheet', 'pricing-guide': 'pricing-sheet',
    'pitch-deck': 'pitch-deck', 'pitchdeck': 'pitch-deck', 'pitch': 'pitch-deck', 'investor-deck': 'pitch-deck', 'startup-deck': 'pitch-deck', 'presentation': 'pitch-deck',
    'demo-video': 'demo-video', 'demovideo': 'demo-video', 'product-demo-video': 'demo-video', 'walkthrough-video': 'demo-video',
    'product-demo': 'product-demo', 'productdemo': 'product-demo', 'demo': 'product-demo', 'product-walkthrough': 'product-demo',
    'feature-document': 'feature-document', 'featuredocument': 'feature-document', 'feature-list': 'feature-document', 'features': 'feature-document',
    'technical-specification': 'technical-specification', 'technicalspec': 'technical-specification', 'tech-spec': 'technical-specification', 'technical-doc': 'technical-specification',
    'testimonial-asset': 'testimonial-asset', 'testimonial': 'testimonial-asset', 'testimonial-sheet': 'testimonial-asset', 'customer-quotes': 'testimonial-asset',
    'roi-document': 'roi-document', 'roidocument': 'roi-document', 'roi-calculator': 'roi-document', 'roi-analysis': 'roi-document', 'business-case': 'roi-document',
    'comparison-sheet': 'comparison-sheet', 'comparisonsheet': 'comparison-sheet', 'competitor-comparison': 'comparison-sheet', 'competitive-analysis': 'comparison-sheet',
    'sales-flyer': 'sales-flyer', 'salesflyer': 'sales-flyer', 'flyer': 'sales-flyer', 'handout': 'sales-flyer', 'leave-behind': 'sales-flyer',
    'portfolio': 'portfolio', 'work-portfolio': 'portfolio', 'project-portfolio': 'portfolio',
    'client-presentation': 'client-presentation', 'clientpresentation': 'client-presentation', 'client-deck': 'client-presentation',
    'explainer-video': 'explainer-video', 'explainervideo': 'explainer-video', 'explainer': 'explainer-video', 'how-it-works-video': 'explainer-video',
  };
  return typeMap[v] || 'one-pager';
}

function normaliseCollateralCategory(value: string): string {
  const v = String(value).toLowerCase().trim().replace(/\s+/g, '-');
  const categoryMap: Record<string, string> = {
    'sales-presentation': 'sales-presentation', 'sales': 'sales-presentation', 'presentation': 'sales-presentation', 'sales-deck': 'sales-presentation',
    'technical-document': 'technical-document', 'technical': 'technical-document', 'tech-doc': 'technical-document', 'documentation': 'technical-document',
    'marketing-material': 'marketing-material', 'marketing': 'marketing-material', 'promo': 'marketing-material', 'promotional': 'marketing-material',
    'client-proposal': 'client-proposal', 'proposal': 'client-proposal', 'business-proposal': 'client-proposal',
    'pricing': 'pricing', 'pricing-material': 'pricing', 'commercial': 'pricing',
    'product-education': 'product-education', 'education': 'product-education', 'training': 'product-education', 'enablement': 'product-education',
    'demo-material': 'demo-material', 'demo': 'demo-material', 'demonstration': 'demo-material',
  };
  return categoryMap[v] || 'marketing-material';
}

function normaliseCollateralFunnelStage(value: string): string {
  const v = String(value).toLowerCase().trim();
  if (v.includes('awareness') || v.includes('aware') || v.includes('top')) return 'awareness';
  if (v.includes('discovery') || v.includes('discover') || v.includes('research') || v.includes('exploration')) return 'discovery';
  if (v.includes('qualification') || v.includes('qualify') || v.includes('evaluat')) return 'qualification';
  if (v.includes('demo') || v.includes('demonstration') || v.includes('showcase')) return 'demo';
  if (v.includes('proposal') || v.includes('propose') || v.includes('offer')) return 'proposal';
  if (v.includes('negotiation') || v.includes('negotiate') || v.includes('bargain')) return 'negotiation';
  if (v.includes('closing') || v.includes('close') || v.includes('commit')) return 'closing';
  if (v.includes('retention') || v.includes('retain') || v.includes('keep') || v.includes('renew')) return 'retention';
  return 'awareness';
}

// ============================================
// VIDEO CONTENT AUTO-FILL MAPPING
// ============================================

export function computeVideoContentAutoFillMapping(analysis: Record<string, any>): Record<string, any> {
  const mapping: Record<string, any> = {};

  // Direct string fields
  const stringFields = ['name', 'description', 'summary', 'script', 'duration', 'department', 'targetAudience', 'usageNotes', 'videoUrl', 'thumbnailUrl', 'transcript', 'language'] as const;
  for (const field of stringFields) {
    if (analysis[field] && typeof analysis[field] === 'string') {
      mapping[field] = analysis[field];
    }
  }

  // Constrained enum: type
  if (analysis.type) {
    mapping.type = normaliseVideoContentType(analysis.type);
  }

  // Constrained enum: category
  if (analysis.category) {
    mapping.category = normaliseVideoContentCategory(analysis.category);
  }

  // Constrained enum: source
  if (analysis.source) {
    mapping.source = normaliseVideoContentSource(analysis.source);
  }

  // Constrained enum: accessLevel
  if (analysis.accessLevel) {
    mapping.accessLevel = normaliseVideoContentAccessLevel(analysis.accessLevel);
  } else {
    mapping.accessLevel = 'team';
  }

  // Constrained enum: status
  mapping.status = 'draft';

  // Constrained enum: watchStatus
  mapping.watchStatus = 'not-started';

  // Tags array
  if (analysis.tags) {
    mapping.tags = ensureArray(analysis.tags);
  }

  // Key notes array
  if (analysis.keyNotes) {
    mapping.keyNotes = ensureArray(analysis.keyNotes);
  }

  // Shot list array
  if (analysis.shotList) {
    mapping.shotList = ensureArray(analysis.shotList);
  }

  // Best practices array
  if (analysis.bestPractices) {
    mapping.bestPractices = ensureArray(analysis.bestPractices);
  }

  // Effectiveness tips array
  if (analysis.effectivenessTips) {
    mapping.effectivenessTips = ensureArray(analysis.effectivenessTips);
  }

  // PDF references array
  if (analysis.pdfReferences) {
    mapping.pdfReferences = ensureArray(analysis.pdfReferences);
  }

  // Downloadable resources array
  if (analysis.downloadableResources) {
    mapping.downloadableResources = ensureArray(analysis.downloadableResources);
  }

  // Sections array — normalise structure
  if (Array.isArray(analysis.sections) && analysis.sections.length > 0) {
    mapping.sections = analysis.sections.map((s: any, index: number) => ({
      id: s.id || `sec-${Date.now()}-${index}`,
      title: typeof s.title === 'string' ? s.title : `Section ${index + 1}`,
      content: typeof s.content === 'string' ? s.content : '',
      order: typeof s.order === 'number' ? s.order : index,
    }));
  }

  // Timestamp notes array
  if (Array.isArray(analysis.timestampNotes) && analysis.timestampNotes.length > 0) {
    mapping.timestampNotes = analysis.timestampNotes.map((t: any) => ({
      time: typeof t.time === 'string' ? t.time : '0:00',
      note: typeof t.note === 'string' ? t.note : '',
    }));
  }

  // Default values
  mapping.aiGenerated = true;
  mapping.isFavorite = false;
  mapping.isPinned = false;

  return mapping;
}

// ============================================
// VIDEO CONTENT NORMALISERS
// ============================================

function normaliseVideoContentType(value: string): string {
  const v = String(value).toLowerCase().trim().replace(/\s+/g, '-');
  const typeMap: Record<string, string> = {
    'educational': 'educational', 'education': 'educational', 'teaching': 'educational', 'learn': 'educational', 'tutorial': 'educational', 'explainer': 'educational',
    'product-demo': 'product-demo', 'productdemo': 'product-demo', 'demo': 'product-demo', 'product-walkthrough': 'product-demo', 'product-demonstration': 'product-demo', 'walkthrough': 'product-demo',
    'service-walkthrough': 'service-walkthrough', 'servicewalkthrough': 'service-walkthrough', 'service-demo': 'service-walkthrough', 'service-overview': 'service-walkthrough',
    'sop-video': 'sop-video', 'sopvideo': 'sop-video', 'sop': 'sop-video', 'process-video': 'sop-video', 'procedure': 'sop-video', 'standard-operating-procedure': 'sop-video',
    'company-policy': 'company-policy', 'companypolicy': 'company-policy', 'policy': 'company-policy', 'policy-video': 'company-policy',
    'hr-training': 'hr-training', 'hrtraining': 'hr-training', 'hr': 'hr-training', 'human-resources': 'hr-training', 'onboarding-hr': 'hr-training',
    'technical-tutorial': 'technical-tutorial', 'technicaltutorial': 'technical-tutorial', 'technical': 'technical-tutorial', 'tech-tutorial': 'technical-tutorial', 'tech-deep-dive': 'technical-tutorial', 'api-tutorial': 'technical-tutorial',
    'sales-training': 'sales-training', 'salestraining': 'sales-training', 'sales': 'sales-training', 'sales-enablement': 'sales-training', 'pitch-training': 'sales-training',
    'founder-message': 'founder-message', 'foundermessage': 'founder-message', 'founder': 'founder-message', 'ceo-message': 'founder-message', 'leadership-message': 'founder-message',
    'customer-onboarding': 'customer-onboarding', 'customeronboarding': 'customer-onboarding', 'onboarding': 'customer-onboarding', 'new-customer': 'customer-onboarding', 'client-onboarding': 'customer-onboarding',
    'webinar-recording': 'webinar-recording', 'webinarrecording': 'webinar-recording', 'webinar': 'webinar-recording', 'webinar-replay': 'webinar-recording',
    'team-training': 'team-training', 'teamtraining': 'team-training', 'team': 'team-training', 'internal-training': 'team-training',
    'interview': 'interview', 'expert-interview': 'interview', 'qa': 'interview', 'panel-discussion': 'interview',
    'feature-update': 'feature-update', 'featureupdate': 'feature-update', 'whats-new': 'feature-update', 'release': 'feature-update', 'changelog': 'feature-update', 'product-update': 'feature-update',
    'marketing-strategy': 'marketing-strategy', 'marketingstrategy': 'marketing-strategy', 'marketing': 'marketing-strategy', 'campaign': 'marketing-strategy',
    'internal-communication': 'internal-communication', 'internalcommunication': 'internal-communication', 'announcement': 'internal-communication', 'company-update': 'internal-communication', 'internal': 'internal-communication',
    'compliance-training': 'compliance-training', 'compliancetraining': 'compliance-training', 'compliance': 'compliance-training', 'regulatory': 'compliance-training',
    'support-tutorial': 'support-tutorial', 'supporttutorial': 'support-tutorial', 'support': 'support-tutorial', 'help': 'support-tutorial', 'troubleshooting': 'support-tutorial',
  };
  return typeMap[v] || 'educational';
}

function normaliseVideoContentCategory(value: string): string {
  const v = String(value).toLowerCase().trim().replace(/\s+/g, '-');
  const categoryMap: Record<string, string> = {
    'product-training': 'product-training', 'product': 'product-training', 'product-education': 'product-training',
    'service-training': 'service-training', 'service': 'service-training', 'service-education': 'service-training',
    'educational': 'educational', 'education': 'educational', 'learning': 'educational', 'teaching': 'educational',
    'company-policies': 'company-policies', 'policy': 'company-policies', 'policies': 'company-policies',
    'hr-training': 'hr-training', 'hr': 'hr-training', 'human-resources': 'hr-training',
    'sop-videos': 'sop-videos', 'sop': 'sop-videos', 'process': 'sop-videos', 'procedures': 'sop-videos',
    'sales-training': 'sales-training', 'sales': 'sales-training', 'sales-enablement': 'sales-training',
    'technical-tutorials': 'technical-tutorials', 'technical': 'technical-tutorials', 'tech': 'technical-tutorials', 'developer': 'technical-tutorials',
    'customer-support': 'customer-support', 'support': 'customer-support', 'help': 'customer-support',
    'founder-sessions': 'founder-sessions', 'founder': 'founder-sessions', 'leadership': 'founder-sessions',
    'team-onboarding': 'team-onboarding', 'onboarding': 'team-onboarding', 'new-hire': 'team-onboarding',
    'compliance': 'compliance', 'regulatory': 'compliance', 'governance': 'compliance',
    'marketing-training': 'marketing-training', 'marketing': 'marketing-training', 'campaigns': 'marketing-training',
    'crm-training': 'crm-training', 'crm': 'crm-training',
    'software-tutorials': 'software-tutorials', 'software': 'software-tutorials', 'app-tutorial': 'software-tutorials',
    'internal-meetings': 'internal-meetings', 'meetings': 'internal-meetings', 'all-hands': 'internal-meetings',
    'webinar-recordings': 'webinar-recordings', 'webinar': 'webinar-recordings', 'online-seminar': 'webinar-recordings',
    'client-training': 'client-training', 'client': 'client-training', 'customer-training': 'client-training',
    'knowledge-sharing': 'knowledge-sharing', 'knowledge': 'knowledge-sharing', 'sharing': 'knowledge-sharing',
  };
  return categoryMap[v] || 'educational';
}

function normaliseVideoContentSource(value: string): string {
  const v = String(value).toLowerCase().trim().replace(/\s+/g, '-');
  const sourceMap: Record<string, string> = {
    'youtube': 'youtube', 'yt': 'youtube',
    'vimeo': 'vimeo',
    'loom': 'loom',
    'google-drive': 'google-drive', 'googledrive': 'google-drive', 'gdrive': 'google-drive', 'drive': 'google-drive',
    'dropbox': 'dropbox',
    'wistia': 'wistia',
    'internal-cdn': 'internal-cdn', 'internalcdn': 'internal-cdn', 'internal': 'internal-cdn', 'self-hosted': 'internal-cdn', 'hosted': 'internal-cdn',
    'other': 'other',
  };
  return sourceMap[v] || 'other';
}

function normaliseVideoContentAccessLevel(value: string): string {
  const v = String(value).toLowerCase().trim().replace(/\s+/g, '-');
  const accessMap: Record<string, string> = {
    'public': 'public', 'everyone': 'public', 'all': 'public',
    'team': 'team', 'internal': 'team', 'company-wide': 'team',
    'department': 'department', 'dept': 'department',
    'manager-only': 'manager-only', 'manager': 'manager-only', 'management': 'manager-only',
    'hr-only': 'hr-only', 'hr': 'hr-only', 'human-resources-only': 'hr-only',
  };
  return accessMap[v] || 'team';
}

// ============================================
// BOOK AUTO-FILL MAPPING
// ============================================

export function computeBookAutoFillMapping(analysis: Record<string, any>): Record<string, any> {
  const mapping: Record<string, any> = {};

  // Direct string fields
  const stringFields = ['title', 'subtitle', 'slug', 'description', 'longDescription', 'executiveSummary', 'targetAudience', 'suggestedTopic', 'outline', 'launchStrategy', 'marketingNotes', 'authorRole'] as const;
  for (const field of stringFields) {
    if (analysis[field] && typeof analysis[field] === 'string') {
      mapping[field] = analysis[field];
    }
  }

  // Map AI-returned language to contentLanguage (not 'language' which conflicts with MongoDB text index)
  if (analysis.language && typeof analysis.language === 'string') {
    mapping.contentLanguage = analysis.language;
  }

  // Number fields — ensure numeric values
  if (analysis.estimatedReadTime) {
    const parsed = parseInt(String(analysis.estimatedReadTime).replace(/[^0-9]/g, ''), 10);
    if (!isNaN(parsed)) mapping.estimatedReadTime = parsed;
  }
  if (analysis.priceEbook && typeof analysis.priceEbook === 'number') mapping.priceEbook = analysis.priceEbook;
  if (analysis.pricePrint && typeof analysis.pricePrint === 'number') mapping.pricePrint = analysis.pricePrint;
  if (analysis.currency && typeof analysis.currency === 'string') mapping.currency = analysis.currency;

  // Constrained enum: type (PublicationType)
  if (analysis.type) {
    mapping.type = normalisePublicationType(analysis.type);
  }

  // Constrained enum: status
  mapping.status = 'idea';

  // Keywords array
  if (analysis.keywords) {
    mapping.keywords = ensureArray(analysis.keywords);
  }

  // Tags array
  if (analysis.tags) {
    mapping.tags = ensureArray(analysis.tags);
  }

  // SEO fields
  if (analysis.seoTitle) mapping.seoTitle = analysis.seoTitle;
  if (analysis.seoDescription) {
    mapping.seoDescription = String(analysis.seoDescription).substring(0, 159);
  }
  if (analysis.seoKeywords) mapping.seoKeywords = ensureArray(analysis.seoKeywords);

  // Formats — normalise to PublicationFormat enum values
  if (analysis.formats && Array.isArray(analysis.formats) && analysis.formats.length > 0) {
    const validFormats = ['print', 'ebook', 'audiobook', 'pdf', 'web', 'print-ebook', 'print-audio', 'ebook-audio', 'all-formats'];
    const normalised = analysis.formats.map((f: any) => {
      const v = String(f).toLowerCase().trim();
      const formatMap: Record<string, string> = {
        'print': 'print', 'hardcover': 'print', 'paperback': 'print',
        'ebook': 'ebook', 'epub': 'ebook', 'kindle': 'ebook', 'digital': 'ebook', 'mobi': 'ebook',
        'audiobook': 'audiobook', 'audio': 'audiobook',
        'pdf': 'pdf',
        'web': 'web', 'online': 'web',
      };
      return formatMap[v] || 'pdf';
    }).filter((v: string, i: number, a: string[]) => validFormats.includes(v) && a.indexOf(v) === i);
    if (normalised.length > 0) mapping.formats = normalised;
  }

  // Distribution links — convert strings to IDistributionLink objects with valid channel enum
  if (analysis.distributionChannels && Array.isArray(analysis.distributionChannels) && analysis.distributionChannels.length > 0) {
    const validChannels = ['amazon', 'apple-books', 'google-books', 'kobo', 'barnes-noble', 'smashwords', 'gumroad', 'website', 'linkedin', 'medium', 'substack', 'researchgate', 'ssrn', 'other'];
    const channelMap: Record<string, string> = {
      'amazon': 'amazon', 'amazon-kindle': 'amazon', 'kindle': 'amazon',
      'apple-books': 'apple-books', 'apple': 'apple-books', 'ibooks': 'apple-books',
      'google-books': 'google-books', 'google': 'google-books', 'google-play': 'google-books',
      'kobo': 'kobo',
      'barnes-noble': 'barnes-noble', 'bn': 'barnes-noble',
      'smashwords': 'smashwords',
      'gumroad': 'gumroad',
      'website': 'website', 'own-website': 'website', 'company-website': 'website',
      'linkedin': 'linkedin',
      'medium': 'medium',
      'substack': 'substack',
      'researchgate': 'researchgate',
      'ssrn': 'ssrn',
    };
    mapping.distributionLinks = analysis.distributionChannels.map((ch: any, i: number) => {
      const channelStr = typeof ch === 'string' ? ch : (ch.channel || 'website');
      const v = String(channelStr).toLowerCase().trim().replace(/\s+/g, '-');
      const mappedChannel = channelMap[v] || 'website';
      return { channel: mappedChannel, url: '#', isActive: true };
    });
  }

  // Best practices
  if (analysis.bestPractices) {
    mapping.bestPractices = ensureArray(analysis.bestPractices);
  }

  // Effectiveness tips
  if (analysis.effectivenessTips) {
    mapping.effectivenessTips = ensureArray(analysis.effectivenessTips);
  }

  // AI generation markers
  mapping.aiGenerated = true;
  mapping.isFeatured = false;
  mapping.chapterCount = 0;

  return mapping;
}

// ============================================
// BOOK NORMALISERS
// ============================================

function normalisePublicationType(value: string): string {
  const v = String(value).toLowerCase().trim().replace(/\s+/g, '-');
  const typeMap: Record<string, string> = {
    'book': 'book', 'hardcover': 'book', 'print-book': 'book',
    'ebook': 'ebook', 'e-book': 'ebook', 'digital-book': 'ebook', 'kindle': 'ebook',
    'whitepaper': 'whitepaper', 'white-paper': 'whitepaper',
    'research-paper': 'research-paper', 'research-report': 'research-paper',
    'report': 'report', 'industry-report': 'report',
    'magazine': 'magazine',
    'journal-article': 'journal-article', 'article': 'journal-article',
    'case-study': 'case-study', 'case-study-collection': 'case-study',
    'guide': 'guide', 'how-to': 'guide', 'playbook': 'guide',
    'handbook': 'handbook', 'field-guide': 'handbook', 'companion': 'handbook',
    'manual': 'manual', 'user-guide': 'manual', 'reference-guide': 'manual',
    'sop-book': 'sop-book', 'sop': 'sop-book',
    'training-manual': 'training-manual', 'training': 'training-manual',
    'marketing-guide': 'marketing-guide', 'marketing': 'marketing-guide',
    'product-guide': 'product-guide', 'product': 'product-guide',
    'onboarding-book': 'onboarding-book', 'onboarding': 'onboarding-book',
    'other': 'other',
  };
  return typeMap[v] || 'book';
}

// ============================================
// ADS AUTO-FILL MAPPING
// ============================================

export function computeCampaignAutoFillMapping(analysis: Record<string, any>): Record<string, any> {
  const mapping: Record<string, any> = {};

  // String fields
  const stringFields = ['name', 'description', 'notes'] as const;
  for (const field of stringFields) {
    if (analysis[field] && typeof analysis[field] === 'string') {
      mapping[field] = analysis[field];
    }
  }

  // Enum: goal
  if (analysis.goal) {
    mapping.goal = normaliseAdCampaignGoal(analysis.goal);
  }

  // Status
  mapping.status = 'draft';

  // Platforms array — normalise to AdPlatform enum
  if (analysis.platforms && Array.isArray(analysis.platforms) && analysis.platforms.length > 0) {
    mapping.platforms = analysis.platforms.map((p: any) => normaliseAdPlatform(String(p))).filter(Boolean);
    // Deduplicate
    mapping.platforms = [...new Set(mapping.platforms)];
  }

  // Platform type (meta/google)
  if (analysis.platformType) {
    mapping.platformType = analysis.platformType;
  }

  // Budget
  if (analysis.totalBudget && typeof analysis.totalBudget === 'number') {
    mapping.totalBudget = analysis.totalBudget;
  }
  mapping.currency = analysis.currency || 'USD';

  // Budget type and period
  if (analysis.budgetType) mapping.budgetType = analysis.budgetType;
  if (analysis.budgetPeriod) mapping.budgetPeriod = analysis.budgetPeriod;

  // Buying type (for Meta)
  if (analysis.buyingType) mapping.buyingType = analysis.buyingType;

  // Campaign subtype (for Meta/Google)
  if (analysis.campaignSubtype) mapping.campaignSubtype = analysis.campaignSubtype;

  // Special ad categories (for Meta)
  if (analysis.specialAdCategories && Array.isArray(analysis.specialAdCategories)) {
    mapping.specialAdCategories = analysis.specialAdCategories;
  }

  // Start date — default to today
  mapping.startDate = analysis.startDate || new Date().toISOString().split('T')[0];

  // End date (optional)
  if (analysis.endDate) mapping.endDate = analysis.endDate;

  // Tags
  if (analysis.tags) {
    mapping.tags = ensureArray(analysis.tags);
  }

  // ============================================
  // GOOGLE ADS SPECIFIC FIELDS
  // ============================================

  // Google campaign channel (search, display, video, etc.)
  if (analysis.googleCampaignChannel) {
    mapping.googleCampaignChannel = analysis.googleCampaignChannel;
  }

  // Google campaign objective (sales, leads, etc.)
  if (analysis.googleCampaignObjective) {
    mapping.googleCampaignObjective = analysis.googleCampaignObjective;
  }

  // Google bidding strategy
  if (analysis.googleBiddingStrategy) {
    mapping.googleBiddingStrategy = analysis.googleBiddingStrategy;
  }

  // Google networks (search partners, display network, etc.)
  if (analysis.googleNetworks && Array.isArray(analysis.googleNetworks)) {
    mapping.googleNetworks = analysis.googleNetworks;
  }

  // Google locations
  if (analysis.googleLocations && Array.isArray(analysis.googleLocations)) {
    mapping.googleLocations = analysis.googleLocations;
  }

  // Google languages
  if (analysis.googleLanguages && Array.isArray(analysis.googleLanguages)) {
    mapping.googleLanguages = analysis.googleLanguages;
  }

  // Google final URL
  if (analysis.googleFinalUrl) {
    mapping.googleFinalUrl = analysis.googleFinalUrl;
  }

  // Google unique selling points
  if (analysis.googleUniqueSellingPoints && Array.isArray(analysis.googleUniqueSellingPoints)) {
    mapping.googleUniqueSellingPoints = analysis.googleUniqueSellingPoints;
  }

  // AI markers
  mapping.aiGenerated = true;

  return mapping;
}

export function computeAdsAutoFillMapping(analysis: Record<string, any>): Record<string, any> {
  const mapping: Record<string, any> = {};

  // String fields
  const stringFields = ['name', 'headline', 'headline2', 'primaryText', 'description', 'description2', 'cta', 'mediaUrl', 'destinationUrl', 'displayUrl'] as const;
  for (const field of stringFields) {
    if (analysis[field] && typeof analysis[field] === 'string') {
      mapping[field] = analysis[field];
    }
  }

  // Enum: platform
  if (analysis.platform) {
    mapping.platform = normaliseAdPlatform(analysis.platform);
  }

  // Enum: status
  mapping.status = 'draft';

  // Enum: objective
  if (analysis.objective) {
    mapping.objective = normaliseAdObjective(analysis.objective);
  }

  // Enum: priority
  if (analysis.priority) {
    mapping.priority = normaliseAdPriority(analysis.priority);
  }

  // Creative type
  if (analysis.creativeType) {
    mapping.creativeType = analysis.creativeType;
  }

  // Targeting subdocument
  if (analysis.targeting && typeof analysis.targeting === 'object') {
    const t = analysis.targeting;
    mapping.targeting = {};
    if (t.locations && Array.isArray(t.locations)) mapping.targeting.locations = t.locations.map(String);
    if (typeof t.ageMin === 'number') mapping.targeting.ageMin = t.ageMin;
    if (typeof t.ageMax === 'number') mapping.targeting.ageMax = t.ageMax;
    if (t.genders && Array.isArray(t.genders)) mapping.targeting.genders = t.genders.map(String);
    if (t.interests && Array.isArray(t.interests)) mapping.targeting.interests = t.interests.map(String);
    if (t.behaviors && Array.isArray(t.behaviors)) mapping.targeting.behaviors = t.behaviors.map(String);
    // Countries, states, cities for location targeting
    if (t.countries && Array.isArray(t.countries)) mapping.targeting.countries = t.countries.map(String);
    if (t.states && Array.isArray(t.states)) mapping.targeting.states = t.states.map(String);
    if (t.cities && Array.isArray(t.cities)) mapping.targeting.cities = t.cities.map(String);
    if (t.languages && Array.isArray(t.languages)) mapping.targeting.languages = t.languages.map(String);
    // Detailed targeting (combined interests, behaviors, demographics)
    if (t.detailedTargeting && Array.isArray(t.detailedTargeting)) {
      mapping.targeting.detailedTargeting = t.detailedTargeting.map(String);
    }
  }

  // Tracking subdocument
  if (analysis.tracking && typeof analysis.tracking === 'object') {
    const tr = analysis.tracking;
    mapping.tracking = {};
    if (tr.utmSource) mapping.tracking.utmSource = String(tr.utmSource);
    if (tr.utmMedium) mapping.tracking.utmMedium = String(tr.utmMedium);
    if (tr.utmCampaign) mapping.tracking.utmCampaign = String(tr.utmCampaign);
    if (tr.utmContent) mapping.tracking.utmContent = String(tr.utmContent);
    if (tr.pixelId) mapping.tracking.pixelId = String(tr.pixelId);
  }

  // Tags
  if (analysis.tags) {
    mapping.tags = ensureArray(analysis.tags);
  }

  // ============================================
  // GOOGLE ADS SPECIFIC FIELDS
  // ============================================

  // Google Keywords - array of keyword objects with text, matchType, isNegative
  if (analysis.googleKeywords && Array.isArray(analysis.googleKeywords)) {
    mapping.googleKeywords = analysis.googleKeywords.map((kw: any) => ({
      text: String(kw.text || ''),
      matchType: kw.matchType || 'broad',
      isNegative: Boolean(kw.isNegative),
    }));
  }

  // Google Ad Assets - array of ad asset objects for responsive search ads
  if (analysis.googleAdAssets && Array.isArray(analysis.googleAdAssets)) {
    mapping.googleAdAssets = analysis.googleAdAssets.map((asset: any) => ({
      finalUrl: asset.finalUrl || '',
      displayPath1: asset.displayPath1 || '',
      displayPath2: asset.displayPath2 || '',
      headlines: (asset.headlines || []).map((h: any) => ({
        text: String(h.text || ''),
        pinnedPosition: h.pinnedPosition || undefined,
      })),
      descriptions: (asset.descriptions || []).map((d: any) => ({
        text: String(d.text || ''),
      })),
      sitelinks: (asset.sitelinks || []).map((s: any) => ({
        linkText: String(s.linkText || s.title || ''),
        finalUrl: s.finalUrl || s.url || '',
        description1: s.description1 || '',
        description2: s.description2 || '',
      })),
      callouts: (asset.callouts || []).map((c: any) => ({
        text: String(c.text || ''),
      })),
      structuredSnippets: (asset.structuredSnippets || []).map((sn: any) => ({
        header: String(sn.header || ''),
        values: (sn.values || []).map(String),
      })),
    }));
  }

  // AI markers
  mapping.aiGenerated = true;

  return mapping;
}

export function computeAudienceAutoFillMapping(analysis: Record<string, any>): Record<string, any> {
  const mapping: Record<string, any> = {};

  // String fields
  if (analysis.name && typeof analysis.name === 'string') mapping.name = analysis.name;
  if (analysis.description && typeof analysis.description === 'string') mapping.description = analysis.description;

  // Enum: type
  if (analysis.type) {
    const v = String(analysis.type).toLowerCase().trim();
    const typeMap: Record<string, string> = {
      'custom': 'custom', 'segment': 'custom', 'defined': 'custom',
      'lookalike': 'lookalike', 'look-alike': 'lookalike', 'similar': 'lookalike', 'lal': 'lookalike',
      'saved': 'saved', 'persistent': 'saved',
      'retargeting': 'retargeting', 'remarketing': 'retargeting', 'retarget': 'retargeting',
    };
    mapping.type = typeMap[v] || 'custom';
  } else {
    mapping.type = 'custom';
  }

  // Demographics subdocument - comprehensive mapping
  mapping.demographics = {};
  if (analysis.demographics && typeof analysis.demographics === 'object') {
    const d = analysis.demographics;
    mapping.demographics.ageMin = typeof d.ageMin === 'number' ? d.ageMin : 25;
    mapping.demographics.ageMax = typeof d.ageMax === 'number' ? d.ageMax : 55;
    if (d.genders && Array.isArray(d.genders)) mapping.demographics.genders = d.genders.map(String);
    if (d.locations && Array.isArray(d.locations)) mapping.demographics.locations = d.locations.map(String);
    if (d.languages && Array.isArray(d.languages)) mapping.demographics.languages = d.languages.map(String);
    // Countries, states, cities for granular location targeting
    if (d.countries && Array.isArray(d.countries)) mapping.demographics.countries = d.countries.map(String);
    if (d.states && Array.isArray(d.states)) mapping.demographics.states = d.states.map(String);
    if (d.cities && Array.isArray(d.cities)) mapping.demographics.cities = d.cities.map(String);
    // Radius targeting
    if (typeof d.radius === 'number') mapping.demographics.radius = d.radius;
    if (d.radiusUnit) mapping.demographics.radiusUnit = d.radiusUnit;
  } else {
    mapping.demographics.ageMin = 25;
    mapping.demographics.ageMax = 55;
  }

  // Also support top-level location fields (for AI output compatibility)
  if (analysis.countries && Array.isArray(analysis.countries)) {
    mapping.demographics.countries = analysis.countries.map(String);
  }
  if (analysis.states && Array.isArray(analysis.states)) {
    mapping.demographics.states = analysis.states.map(String);
  }
  if (analysis.cities && Array.isArray(analysis.cities)) {
    mapping.demographics.cities = analysis.cities.map(String);
  }
  if (analysis.locations && Array.isArray(analysis.locations)) {
    mapping.demographics.locations = analysis.locations.map(String);
  }
  if (analysis.languages && Array.isArray(analysis.languages)) {
    mapping.demographics.languages = analysis.languages.map(String);
  }

  // Age range from top-level fields (for AI output compatibility)
  if (typeof analysis.ageMin === 'number') mapping.demographics.ageMin = analysis.ageMin;
  if (typeof analysis.ageMax === 'number') mapping.demographics.ageMax = analysis.ageMax;
  if (analysis.genders && Array.isArray(analysis.genders)) mapping.demographics.genders = analysis.genders.map(String);

  // Interests and behaviors
  if (analysis.interests) mapping.interests = ensureArray(analysis.interests);
  if (analysis.behaviors) mapping.behaviors = ensureArray(analysis.behaviors);

  // Demographics tags (combined interests, behaviors, demographics for Meta detailed targeting)
  if (analysis.demographicsTags) mapping.demographicsTags = ensureArray(analysis.demographicsTags);
  if (analysis.detailedTargeting) mapping.detailedTargeting = ensureArray(analysis.detailedTargeting);

  // Estimated size
  if (analysis.estimatedSize && typeof analysis.estimatedSize === 'number') mapping.estimatedSize = analysis.estimatedSize;

  // Platforms
  if (analysis.platforms && Array.isArray(analysis.platforms) && analysis.platforms.length > 0) {
    mapping.platforms = analysis.platforms.map((p: any) => normaliseAdPlatform(String(p))).filter(Boolean);
    mapping.platforms = [...new Set(mapping.platforms)];
  }

  // ============================================
  // META ADS SPECIFIC FIELDS
  // ============================================

  // Placement mode (automatic/manual)
  if (analysis.placementMode) mapping.placementMode = analysis.placementMode;
  if (analysis.placements && Array.isArray(analysis.placements)) {
    mapping.placements = analysis.placements.map(String);
  }

  // Conversion settings for Meta
  if (analysis.optimizationGoal) mapping.optimizationGoal = analysis.optimizationGoal;
  if (analysis.conversionLocation) mapping.conversionLocation = analysis.conversionLocation;
  if (analysis.pixelId) mapping.pixelId = analysis.pixelId;

  // Ad set schedule
  if (analysis.adSetStartDate) mapping.adSetStartDate = analysis.adSetStartDate;
  if (analysis.adSetEndDate) mapping.adSetEndDate = analysis.adSetEndDate;

  // ============================================
  // GOOGLE ADS SPECIFIC FIELDS
  // ============================================

  // Google audience segments (In-market, Affinity, etc.)
  if (analysis.googleAudienceSegments && Array.isArray(analysis.googleAudienceSegments)) {
    mapping.googleAudienceSegments = analysis.googleAudienceSegments.map(String);
  }

  // Google audience targeting mode (targeting/observation)
  if (analysis.googleAudienceTargetingMode) {
    mapping.googleAudienceTargetingMode = analysis.googleAudienceTargetingMode;
  }

  // Tags
  if (analysis.tags) mapping.tags = ensureArray(analysis.tags);

  // AI markers
  mapping.aiGenerated = true;

  return mapping;
}

// ============================================
// BUDGET AUTO-FILL MAPPING
// ============================================

export function computeBudgetAutoFillMapping(analysis: Record<string, any>): Record<string, any> {
  const mapping: Record<string, any> = {};

  // String fields
  if (analysis.name && typeof analysis.name === 'string') mapping.name = analysis.name;
  if (typeof analysis.dailyBudget === 'number') mapping.dailyBudget = analysis.dailyBudget;
  if (typeof analysis.totalBudget === 'number') mapping.totalBudget = analysis.totalBudget;
  mapping.currency = analysis.currency || 'USD';

  // Enum: bidStrategy
  if (analysis.bidStrategy) {
    mapping.bidStrategy = normaliseBidStrategy(analysis.bidStrategy);
  } else {
    mapping.bidStrategy = 'enhanced-cpc';
  }

  // Target metrics
  if (typeof analysis.targetCpc === 'number') mapping.targetCpc = analysis.targetCpc;
  if (typeof analysis.targetCpa === 'number') mapping.targetCpa = analysis.targetCpa;
  if (typeof analysis.targetRoas === 'number') mapping.targetRoas = analysis.targetRoas;
  if (typeof analysis.maxBid === 'number') mapping.maxBid = analysis.maxBid;

  // Enum: pacing
  if (analysis.pacing) {
    mapping.pacing = normaliseBudgetPacing(analysis.pacing);
  }

  // Schedule
  mapping.periodStart = analysis.periodStart || new Date().toISOString().split('T')[0];
  if (analysis.periodEnd) mapping.periodEnd = analysis.periodEnd;

  // Notes
  if (analysis.notes && typeof analysis.notes === 'string') mapping.notes = analysis.notes;

  // AI markers
  mapping.aiGenerated = true;

  return mapping;
}

// ============================================
// CREATIVE ASSET AUTO-FILL MAPPING
// ============================================

export function computeCreativeAssetAutoFillMapping(analysis: Record<string, any>): Record<string, any> {
  const mapping: Record<string, any> = {};

  // String fields
  if (analysis.name && typeof analysis.name === 'string') mapping.name = analysis.name;
  if (analysis.headline && typeof analysis.headline === 'string') mapping.headline = analysis.headline;
  if (analysis.description && typeof analysis.description === 'string') mapping.description = analysis.description;

  // Enum: type
  if (analysis.type) {
    mapping.type = normaliseCreativeAssetType(analysis.type);
  } else {
    mapping.type = 'image';
  }

  // Default status
  mapping.status = 'draft';

  // CTA
  if (analysis.cta && typeof analysis.cta === 'string') mapping.cta = analysis.cta;

  // URLs
  if (analysis.destinationUrl && typeof analysis.destinationUrl === 'string') mapping.destinationUrl = analysis.destinationUrl;
  if (analysis.url && typeof analysis.url === 'string') mapping.url = analysis.url;

  // Platform
  if (analysis.platform) {
    mapping.platform = normaliseAdPlatform(String(analysis.platform));
  }

  // AI image prompt
  if (analysis.imagePrompt && typeof analysis.imagePrompt === 'string') mapping.imagePrompt = analysis.imagePrompt;

  // Tags
  if (analysis.tags) mapping.tags = ensureArray(analysis.tags);

  // AI markers
  mapping.aiGenerated = true;

  return mapping;
}

// ============================================
// ADS NORMALISERS
// ============================================

function normaliseAdPlatform(value: string): string {
  const v = String(value).toLowerCase().trim().replace(/\s+/g, '-');
  const platformMap: Record<string, string> = {
    'google-search': 'google-search', 'search': 'google-search', 'google-ads-search': 'google-search', 'google-search-ads': 'google-search',
    'google-display': 'google-display', 'display': 'google-display', 'gdn': 'google-display', 'google-display-ads': 'google-display',
    'google-shopping': 'google-shopping', 'shopping': 'google-shopping', 'google-merchant': 'google-shopping',
    'facebook': 'facebook', 'fb': 'facebook', 'meta': 'facebook', 'facebook-ads': 'facebook',
    'instagram': 'instagram', 'ig': 'instagram', 'insta': 'instagram',
    'tiktok': 'tiktok', 'tik-tok': 'tiktok',
    'linkedin': 'linkedin', 'li': 'linkedin',
    'youtube': 'youtube', 'yt': 'youtube', 'youtube-ads': 'youtube',
    'twitter': 'twitter', 'x': 'twitter', 'x-twitter': 'twitter',
    'pinterest': 'pinterest', 'pin': 'pinterest',
    'snapchat': 'snapchat', 'snap': 'snapchat',
    'reddit': 'reddit', 'reddit-ads': 'reddit',
    'microsoft': 'microsoft', 'bing': 'microsoft', 'microsoft-ads': 'microsoft', 'bing-ads': 'microsoft',
  };
  return platformMap[v] || 'google-search';
}

function normaliseAdCampaignGoal(value: string): string {
  const v = String(value).toLowerCase().trim().replace(/\s+/g, '-');
  const goalMap: Record<string, string> = {
    'awareness': 'awareness', 'brand-awareness': 'awareness', 'branding': 'awareness',
    'traffic': 'traffic', 'website-traffic': 'traffic', 'clicks': 'traffic',
    'leads': 'leads', 'lead-gen': 'leads', 'lead-generation': 'leads', 'leadgen': 'leads',
    'conversions': 'conversions', 'conversion': 'conversions', 'sales': 'conversions',
    'engagement': 'engagement', 'engage': 'engagement',
    'app-installs': 'app-installs', 'app-install': 'app-installs', 'installs': 'app-installs', 'app-downloads': 'app-installs',
    'video-views': 'video-views', 'video': 'video-views', 'views': 'video-views',
    'retargeting': 'retargeting', 'remarketing': 'retargeting', 'retarget': 'retargeting',
  };
  return goalMap[v] || 'awareness';
}

function normaliseAdObjective(value: string): string {
  const v = String(value).toLowerCase().trim().replace(/\s+/g, '-');
  const objMap: Record<string, string> = {
    'awareness': 'awareness', 'brand-awareness': 'awareness', 'branding': 'awareness',
    'traffic': 'traffic', 'website-traffic': 'traffic', 'clicks': 'traffic',
    'leads': 'leads', 'lead-gen': 'leads', 'lead-generation': 'leads',
    'conversions': 'conversions', 'conversion': 'conversions', 'sales': 'conversions',
  };
  return objMap[v] || 'awareness';
}

function normaliseAdPriority(value: string): string {
  const v = String(value).toLowerCase().trim();
  if (v.includes('urgent') || v.includes('critical') || v.includes('immediate')) return 'urgent';
  if (v.includes('high') || v.includes('important')) return 'high';
  if (v.includes('low') || v.includes('minor') || v.includes('nice')) return 'low';
  return 'medium';
}

function normaliseBidStrategy(value: string): string {
  const v = String(value).toLowerCase().trim().replace(/\s+/g, '-');
  const strategyMap: Record<string, string> = {
    'manual-cpc': 'manual-cpc', 'cpc': 'manual-cpc', 'manual': 'manual-cpc',
    'enhanced-cpc': 'enhanced-cpc', 'ecpc': 'enhanced-cpc',
    'target-cpa': 'target-cpa', 'cpa': 'target-cpa', 'tcpa': 'target-cpa',
    'target-roas': 'target-roas', 'roas': 'target-roas', 'troas': 'target-roas',
    'maximize-clicks': 'maximize-clicks', 'max-clicks': 'maximize-clicks',
    'maximize-conversions': 'maximize-conversions', 'max-conversions': 'maximize-conversions',
  };
  return strategyMap[v] || 'enhanced-cpc';
}

function normaliseBudgetPacing(value: string): string {
  const v = String(value).toLowerCase().trim();
  if (v.includes('accelerat') || v === 'asap' || v === 'fast') return 'accelerated';
  return 'even';
}

function normaliseCreativeAssetType(value: string): string {
  const v = String(value).toLowerCase().trim().replace(/\s+/g, '-');
  const typeMap: Record<string, string> = {
    'image': 'image', 'photo': 'image', 'static': 'image', 'graphic': 'image',
    'video': 'video', 'animation': 'video', 'motion': 'video',
    'carousel': 'carousel', 'slideshow': 'carousel', 'multi-image': 'carousel',
    'story': 'story', 'stories': 'story', 'stories-ad': 'story',
    'banner': 'banner', 'display': 'banner', 'ad-banner': 'banner',
    'logo': 'logo', 'brand-logo': 'logo',
    'headline-variant': 'headline-variant', 'headline': 'headline-variant', 'headline-variation': 'headline-variant',
    'description-variant': 'description-variant', 'description-variation': 'description-variant', 'text-variant': 'description-variant',
  };
  return typeMap[v] || 'image';
}

// ============================================
// PR AUTO-FILL MAPPING
// ============================================

export function computeInfluencerCampaignAutoFillMapping(analysis: Record<string, any>): Record<string, any> {
  const mapping: Record<string, any> = {};

  // String fields
  if (analysis.name && typeof analysis.name === 'string') mapping.name = analysis.name;
  if (analysis.objective && typeof analysis.objective === 'string') mapping.objective = analysis.objective;
  if (analysis.description && typeof analysis.description === 'string') mapping.description = analysis.description;
  if (analysis.notes && typeof analysis.notes === 'string') mapping.notes = analysis.notes;

  // Status
  mapping.status = 'draft';

  // Budget
  if (analysis.budget && typeof analysis.budget === 'number') mapping.budget = analysis.budget;
  mapping.currency = analysis.currency || 'USD';

  // Dates
  mapping.startDate = analysis.startDate || new Date().toISOString().split('T')[0];
  if (analysis.endDate) mapping.endDate = analysis.endDate;

  // Tags
  if (analysis.tags) mapping.tags = ensureArray(analysis.tags);

  // Deliverables
  if (analysis.deliverables && Array.isArray(analysis.deliverables) && analysis.deliverables.length > 0) {
    mapping.deliverables = analysis.deliverables.map((d: any) => ({
      type: d.type || 'Post',
      description: d.description || '',
      dueDate: d.dueDate || '',
      status: d.status || 'pending',
    }));
  }

  // AI markers
  mapping.aiGenerated = true;

  return mapping;
}

export function computeInfluencerAutoFillMapping(analysis: Record<string, any>): Record<string, any> {
  const mapping: Record<string, any> = {};

  // String fields
  if (analysis.name && typeof analysis.name === 'string') mapping.name = analysis.name;
  if (analysis.niche && typeof analysis.niche === 'string') mapping.niche = analysis.niche;
  if (analysis.country && typeof analysis.country === 'string') mapping.country = analysis.country;
  if (analysis.city && typeof analysis.city === 'string') mapping.city = analysis.city;
  if (analysis.notes && typeof analysis.notes === 'string') mapping.notes = analysis.notes;

  // Enum: platform
  if (analysis.platform) {
    mapping.platform = normaliseInfluencerPlatform(analysis.platform);
  }

  // Status
  mapping.status = 'potential';

  // Numbers
  if (analysis.followers && typeof analysis.followers === 'number') mapping.followers = analysis.followers;
  if (analysis.engagementRate && typeof analysis.engagementRate === 'number') mapping.engagementRate = analysis.engagementRate;
  if (analysis.rating && typeof analysis.rating === 'number') {
    mapping.rating = Math.min(5, Math.max(1, analysis.rating));
  }

  // Tags
  if (analysis.tags) mapping.tags = ensureArray(analysis.tags);

  // AI markers
  mapping.aiGenerated = true;

  return mapping;
}

export function computeAwardAutoFillMapping(analysis: Record<string, any>): Record<string, any> {
  const mapping: Record<string, any> = {};

  // String fields
  if (analysis.title && typeof analysis.title === 'string') mapping.title = analysis.title;
  if (analysis.description && typeof analysis.description === 'string') mapping.description = analysis.description;
  if (analysis.issuingOrganization && typeof analysis.issuingOrganization === 'string') mapping.issuingOrganization = analysis.issuingOrganization;
  if (analysis.notes && typeof analysis.notes === 'string') mapping.notes = analysis.notes;

  // Enum: type
  if (analysis.type) {
    mapping.type = normaliseAwardType(analysis.type);
  }

  // Enum: category
  if (analysis.category) {
    mapping.category = normaliseAwardCategory(analysis.category);
  }

  // Status
  mapping.status = 'draft';

  // Enum: level
  if (analysis.level) {
    mapping.level = normaliseAwardLevel(analysis.level);
  }

  // Award date
  mapping.awardDate = analysis.awardDate || new Date().toISOString().split('T')[0];

  // Recipient
  mapping.recipientType = 'company';

  // Featured
  mapping.featured = false;

  // Tags
  if (analysis.tags) mapping.tags = ensureArray(analysis.tags);

  // AI markers
  mapping.aiGenerated = true;

  return mapping;
}

// ============================================
// PR NORMALISERS
// ============================================

function normaliseInfluencerPlatform(value: string): string {
  const v = String(value).toLowerCase().trim();
  const map: Record<string, string> = {
    'instagram': 'instagram', 'ig': 'instagram', 'insta': 'instagram',
    'youtube': 'youtube', 'yt': 'youtube',
    'tiktok': 'tiktok', 'tik-tok': 'tiktok',
    'twitter': 'twitter', 'x': 'twitter',
    'linkedin': 'linkedin', 'li': 'linkedin',
    'facebook': 'facebook', 'fb': 'facebook', 'meta': 'facebook',
    'twitch': 'twitch',
    'podcast': 'podcast', 'podcasts': 'podcast',
    'blog': 'blog', 'blogger': 'blog',
    'other': 'other',
  };
  return map[v] || 'other';
}

function normaliseAwardType(value: string): string {
  const v = String(value).toLowerCase().trim().replace(/\s+/g, '-');
  const map: Record<string, string> = {
    'industry-award': 'industry-award', 'award': 'industry-award',
    'certification': 'certification', 'cert': 'certification',
    'recognition': 'recognition', 'recognised': 'recognition',
    'milestone': 'milestone',
    'accolade': 'accolade', 'honor': 'accolade', 'honour': 'accolade',
    'ranking': 'ranking', 'rank': 'ranking',
    'accreditation': 'accreditation', 'accredited': 'accreditation',
    'patent': 'patent',
    'trademark': 'trademark', 'tm': 'trademark',
    'other': 'other',
  };
  return map[v] || 'industry-award';
}

function normaliseAwardCategory(value: string): string {
  const v = String(value).toLowerCase().trim().replace(/\s+/g, '-');
  const map: Record<string, string> = {
    'innovation': 'innovation', 'innovative': 'innovation',
    'growth': 'growth', 'growing': 'growth',
    'leadership': 'leadership', 'leader': 'leadership',
    'customer-service': 'customer-service', 'cs': 'customer-service', 'support': 'customer-service',
    'product': 'product', 'product-excellence': 'product',
    'sustainability': 'sustainability', 'sustainable': 'sustainability', 'green': 'sustainability',
    'diversity': 'diversity', 'dei': 'diversity', 'inclusion': 'diversity',
    'technology': 'technology', 'tech': 'technology',
    'marketing': 'marketing',
    'sales': 'sales',
    'hr': 'hr', 'human-resources': 'hr', 'people': 'hr',
    'other': 'other',
  };
  return map[v] || 'other';
}

function normaliseAwardLevel(value: string): string {
  const v = String(value).toLowerCase().trim();
  const map: Record<string, string> = {
    'local': 'local', 'city': 'local',
    'regional': 'regional', 'province': 'regional', 'state': 'regional',
    'national': 'national', 'country': 'national',
    'international': 'international', 'multi-national': 'international',
    'global': 'global', 'worldwide': 'global', 'world': 'global',
  };
  return map[v] || 'national';
}

// ============================================
// EMAIL TEMPLATE AUTO-FILL MAPPING
// ============================================

export function computeEmailTemplateAutoFillMapping(analysis: Record<string, any>): Record<string, any> {
  const mapping: Record<string, any> = {};

  // String fields
  if (analysis.name && typeof analysis.name === 'string') mapping.name = analysis.name;
  if (analysis.subjectLine && typeof analysis.subjectLine === 'string') mapping.subjectLine = analysis.subjectLine;
  if (analysis.previewText && typeof analysis.previewText === 'string') mapping.previewText = analysis.previewText;
  if (analysis.body && typeof analysis.body === 'string') mapping.body = analysis.body;
  if (analysis.ctaText && typeof analysis.ctaText === 'string') mapping.ctaText = analysis.ctaText;
  if (analysis.ctaUrl && typeof analysis.ctaUrl === 'string') mapping.ctaUrl = analysis.ctaUrl;

  // Enum: type
  if (analysis.type) {
    mapping.type = normaliseEmailType(analysis.type);
  }

  // Enum: category
  if (analysis.category) {
    mapping.category = normaliseEmailCategory(analysis.category);
  }

  // Enum: tone
  if (analysis.tone) {
    mapping.tone = normaliseEmailTone(analysis.tone);
  }

  // Status
  mapping.status = analysis.status && normaliseEmailStatus(analysis.status) || 'draft';

  // Tags
  if (analysis.tags) mapping.tags = ensureArray(analysis.tags);

  // AI markers
  mapping.aiGenerated = true;

  return mapping;
}

// ============================================
// EMAIL TEMPLATE NORMALISERS
// ============================================

function normaliseEmailType(value: string): string {
  const v = String(value).toLowerCase().trim().replace(/\s+/g, '-');
  const map: Record<string, string> = {
    'marketing': 'marketing',
    'newsletter': 'newsletter', 'news': 'newsletter',
    'welcome': 'welcome', 'welcome-email': 'welcome', 'onboarding': 'welcome',
    'promotion': 'promotion', 'promo': 'promotion', 'promotional': 'promotion',
    'product-launch': 'product-launch', 'launch': 'product-launch',
    'event-invitation': 'event-invitation', 'event': 'event-invitation', 'invitation': 'event-invitation',
    'announcement': 'announcement', 'announce': 'announcement',
    'transactional': 'transactional', 'transaction': 'transactional',
    'custom': 'custom',
  };
  return map[v] || 'marketing';
}

function normaliseEmailCategory(value: string): string {
  const v = String(value).toLowerCase().trim().replace(/\s+/g, '-');
  const map: Record<string, string> = {
    'marketing': 'marketing',
    'newsletter': 'newsletter', 'news': 'newsletter',
    'welcome': 'welcome',
    'promotion': 'promotion', 'promo': 'promotion',
    'product-launch': 'product-launch', 'launch': 'product-launch',
    'event': 'event', 'event-invitation': 'event', 'invitation': 'event',
    'announcement': 'announcement', 'announce': 'announcement',
    'custom': 'custom',
  };
  return map[v] || 'marketing';
}

function normaliseEmailTone(value: string): string {
  const v = String(value).toLowerCase().trim();
  const map: Record<string, string> = {
    'professional': 'professional', 'prof': 'professional', 'business': 'professional',
    'friendly': 'friendly', 'warm': 'friendly',
    'casual': 'casual', 'informal': 'casual', 'relaxed': 'casual',
    'formal': 'formal', 'corporate': 'formal',
    'persuasive': 'persuasive', 'convincing': 'persuasive',
  };
  return map[v] || 'professional';
}

function normaliseEmailStatus(value: string): string {
  const v = String(value).toLowerCase().trim();
  const map: Record<string, string> = {
    'draft': 'draft',
    'published': 'published', 'active': 'published',
    'archived': 'archived', 'archive': 'archived',
  };
  return map[v] || 'draft';
}

// ============================================
// SOP MAPPING
// ============================================

export function computeSopAutoFillMapping(analysis: Record<string, any>, stepsData?: Record<string, any>[]): Record<string, any> {
  const mapping: Record<string, any> = {};
  const sop = analysis.sop || analysis;

  // Core string fields
  if (sop.title && typeof sop.title === 'string') mapping.title = sop.title;
  if (sop.shortDescription && typeof sop.shortDescription === 'string') mapping.shortDescription = sop.shortDescription;
  if (sop.detailedDescription && typeof sop.detailedDescription === 'string') mapping.detailedDescription = sop.detailedDescription;
  if (sop.objective && typeof sop.objective === 'string') mapping.objective = sop.objective;
  if (sop.scope && typeof sop.scope === 'string') mapping.scope = sop.scope;
  if (sop.internalNotes && typeof sop.internalNotes === 'string') mapping.internalNotes = sop.internalNotes;
  if (sop.metaTitle && typeof sop.metaTitle === 'string') mapping.metaTitle = sop.metaTitle;
  if (sop.metaDescription && typeof sop.metaDescription === 'string') mapping.metaDescription = sop.metaDescription;

  // Enum: department
  if (sop.department) {
    mapping.department = normaliseSopDepartment(sop.department);
  }

  // Enum: priority
  if (sop.priority) {
    mapping.priority = normaliseSopPriority(sop.priority);
  } else {
    mapping.priority = 'medium';
  }

  // Enum: visibility
  if (sop.visibility) {
    mapping.visibility = normaliseSopVisibility(sop.visibility);
  } else {
    mapping.visibility = 'internal';
  }

  // Enum: status
  mapping.status = 'draft';

  // Enum: approvalStatus
  mapping.approvalStatus = 'pending';

  // Tags
  if (sop.tags) mapping.tags = ensureArray(sop.tags);

  // Prerequisites
  if (sop.prerequisites) mapping.prerequisites = ensureArray(sop.prerequisites);

  // Steps
  if (Array.isArray(stepsData) && stepsData.length > 0) {
    mapping.steps = stepsData.map((s: any, i: number) => ({
      id: s.id || `step-${i + 1}`,
      title: s.title || `Step ${i + 1}`,
      description: s.description || '',
      order: s.order != null ? Number(s.order) : i + 1,
      type: normaliseSopStepType(s.type) || 'instruction',
      assignee: s.assignee || undefined,
      estimatedTime: s.estimatedTime || undefined,
      checklist: Array.isArray(s.checklist) ? s.checklist.filter((c: any) => typeof c === 'string') : [],
      conditions: Array.isArray(s.conditions) ? s.conditions.filter((c: any) => typeof c === 'string') : [],
      attachments: [],
    }));
  }

  // Version
  mapping.version = 1;
  mapping.versionHistory = [];

  // AI markers
  mapping.aiGenerated = true;

  return mapping;
}

// ============================================
// SOP NORMALISERS
// ============================================

function normaliseSopDepartment(value: string): string {
  const v = String(value).toLowerCase().trim().replace(/\s+/g, '-');
  const valid = ['engineering', 'marketing', 'sales', 'design', 'operations', 'hr', 'finance', 'customer-success', 'product', 'legal', 'other'];
  const map: Record<string, string> = {
    'engineering': 'engineering', 'dev': 'engineering', 'development': 'engineering', 'tech': 'engineering',
    'marketing': 'marketing', 'mkt': 'marketing', 'mktg': 'marketing',
    'sales': 'sales',
    'design': 'design', 'ux': 'design', 'ui': 'design', 'creative': 'design',
    'operations': 'operations', 'ops': 'operations',
    'hr': 'hr', 'human-resources': 'hr', 'people': 'hr',
    'finance': 'finance', 'accounting': 'finance', 'financial': 'finance',
    'customer-success': 'customer-success', 'cs': 'customer-success', 'customer': 'customer-success', 'support': 'customer-success',
    'product': 'product', 'product-management': 'product',
    'legal': 'legal', 'compliance': 'legal',
    'other': 'other',
  };
  return map[v] || (valid.includes(v) ? v : 'operations');
}

function normaliseSopPriority(value: string): string {
  const v = String(value).toLowerCase().trim();
  const map: Record<string, string> = {
    'low': 'low', 'l': 'low',
    'medium': 'medium', 'med': 'medium', 'm': 'medium', 'normal': 'medium',
    'high': 'high', 'h': 'high', 'important': 'high',
    'critical': 'critical', 'urgent': 'critical', 'c': 'critical',
  };
  return map[v] || 'medium';
}

function normaliseSopVisibility(value: string): string {
  const v = String(value).toLowerCase().trim();
  const map: Record<string, string> = {
    'private': 'private', 'restricted': 'private',
    'internal': 'internal', 'company': 'internal', 'team': 'internal',
    'public': 'public', 'external': 'public', 'shared': 'public',
  };
  return map[v] || 'internal';
}

function normaliseSopStepType(value: string): string | undefined {
  if (!value) return undefined;
  const v = String(value).toLowerCase().trim();
  const map: Record<string, string> = {
    'instruction': 'instruction', 'step': 'instruction', 'action': 'instruction',
    'decision': 'decision', 'choice': 'decision', 'branch': 'decision',
    'check': 'check', 'checklist': 'check', 'verification': 'check', 'verify': 'check',
    'note': 'note', 'info': 'note', 'information': 'note',
  };
  return map[v] || 'instruction';
}

// ============================================
// LOYALTY PROGRAMME MAPPING
// ============================================

export function computeLoyaltyProgrammeAutoFillMapping(analysis: Record<string, any>): Record<string, any> {
  const mapping: Record<string, any> = {};
  const prog = analysis.programme || analysis;

  // Core fields
  if (prog.name && typeof prog.name === 'string') mapping.name = prog.name;
  if (prog.description && typeof prog.description === 'string') mapping.description = prog.description;

  // Enum: type
  if (prog.type) {
    mapping.type = normaliseLoyaltyType(prog.type);
  }

  // Status - only set if AI provided a valid value, otherwise let frontend handle default
  if (prog.status) {
    const normalized = normaliseLoyaltyStatus(prog.status);
    if (normalized) mapping.status = normalized;
  }

  // Base earn rate
  if (prog.baseEarnRate != null) mapping.baseEarnRate = Number(prog.baseEarnRate) || 1;

  // Settings
  if (prog.settings && typeof prog.settings === 'object') {
    mapping.settings = {
      currency: prog.settings.currency || 'USD',
      pointsName: prog.settings.pointsName || 'Points',
      pointsPerCurrency: Number(prog.settings.pointsPerCurrency) || 1,
      pointsExpiry: {
        enabled: prog.settings.pointsExpiry?.enabled ?? false,
        months: Number(prog.settings.pointsExpiry?.months) || 12,
        warningDays: Number(prog.settings.pointsExpiry?.warningDays) || 30,
      },
      tierDowngrade: {
        enabled: prog.settings.tierDowngrade?.enabled ?? false,
        period: normaliseTierDowngradePeriod(prog.settings.tierDowngrade?.period) || 'yearly',
        retainPercentage: Number(prog.settings.tierDowngrade?.retainPercentage) || 0,
      },
      pointsTransfer: {
        enabled: prog.settings.pointsTransfer?.enabled ?? false,
        fee: prog.settings.pointsTransfer?.fee != null ? Number(prog.settings.pointsTransfer.fee) : undefined,
        minAmount: prog.settings.pointsTransfer?.minAmount != null ? Number(prog.settings.pointsTransfer.minAmount) : undefined,
        maxAmount: prog.settings.pointsTransfer?.maxAmount != null ? Number(prog.settings.pointsTransfer.maxAmount) : undefined,
      },
      fraudPrevention: {
        maxPointsPerDay: Number(prog.settings.fraudPrevention?.maxPointsPerDay) || 10000,
        velocityRules: {
          maxTransactionsPerHour: Number(prog.settings.fraudPrevention?.velocityRules?.maxTransactionsPerHour) || 10,
          maxPointsPerHour: Number(prog.settings.fraudPrevention?.velocityRules?.maxPointsPerHour) || 1000,
          maxTransactionsPerDay: Number(prog.settings.fraudPrevention?.velocityRules?.maxTransactionsPerDay) || 50,
          maxPointsPerDay: Number(prog.settings.fraudPrevention?.velocityRules?.maxPointsPerDay) || 5000,
        },
        suspiciousActivityThreshold: Number(prog.settings.fraudPrevention?.suspiciousActivityThreshold) || 50000,
      },
      integrations: prog.settings.integrations ? {
        channels: (prog.settings.integrations.channels || []).map((c: string) => normaliseChannelType(c)).filter(Boolean),
      } : { channels: ['web'] },
    };
  }

  // Tiers
  if (Array.isArray(analysis.tiers)) {
    mapping.tiers = analysis.tiers.map((t: any, i: number) => ({
      id: t.id || `tier-${i + 1}`,
      name: t.name || `Tier ${i + 1}`,
      level: Number(t.level) || i + 1,
      minPoints: Number(t.minPoints) || 0,
      maxPoints: t.maxPoints != null ? Number(t.maxPoints) : undefined,
      colour: t.colour || undefined,
      description: t.description || undefined,
      benefits: Array.isArray(t.benefits) ? t.benefits.map((b: any, j: number) => ({
        id: b.id || `tier${i + 1}-b${j + 1}`,
        name: b.name || 'Benefit',
        description: b.description || undefined,
        type: normaliseTierBenefitType(b.type) || 'perk',
        value: b.value != null ? Number(b.value) : undefined,
        unit: normaliseBenefitUnit(b.unit) || undefined,
      })) : [],
      earnMultiplier: Number(t.earnMultiplier) || 1,
      perks: Array.isArray(t.perks) ? t.perks.filter((p: any) => typeof p === 'string') : [],
    }));
  }

  // Earn rules
  if (Array.isArray(analysis.earnRules)) {
    mapping.earnRules = analysis.earnRules.map((r: any, i: number) => ({
      id: r.id || `er-${i + 1}`,
      name: r.name || `Earn Rule ${i + 1}`,
      description: r.description || undefined,
      trigger: normaliseEarnTrigger(r.trigger) || 'purchase',
      calculation: normaliseEarnCalculation(r.calculation) || 'fixed',
      value: Number(r.value) || 0,
      cap: r.cap ? {
        daily: r.cap.daily != null ? Number(r.cap.daily) : undefined,
        weekly: r.cap.weekly != null ? Number(r.cap.weekly) : undefined,
        monthly: r.cap.monthly != null ? Number(r.cap.monthly) : undefined,
        perTransaction: r.cap.perTransaction != null ? Number(r.cap.perTransaction) : undefined,
      } : undefined,
      active: r.active !== false,
      priority: r.priority != null ? Number(r.priority) : i + 1,
    }));
  }

  // Redeem rules
  if (Array.isArray(analysis.redeemRules)) {
    mapping.redeemRules = analysis.redeemRules.map((r: any, i: number) => ({
      id: r.id || `rr-${i + 1}`,
      name: r.name || `Redeem Rule ${i + 1}`,
      description: r.description || undefined,
      type: normaliseRedeemType(r.type) || 'discount',
      pointsCost: Number(r.pointsCost) || 0,
      value: r.value != null ? Number(r.value) : undefined,
      valueUnit: normaliseRedeemValueUnit(r.valueUnit) || undefined,
      active: r.active !== false,
    }));
  }

  // Rewards
  if (Array.isArray(analysis.rewards)) {
    mapping.rewards = analysis.rewards.map((r: any, i: number) => ({
      id: r.id || `rw-${i + 1}`,
      name: r.name || `Reward ${i + 1}`,
      description: r.description || undefined,
      type: normaliseRewardType(r.type) || 'discount',
      pointsRequired: Number(r.pointsRequired) || 0,
      cashValue: r.cashValue != null ? Number(r.cashValue) : undefined,
      tierRestrictions: Array.isArray(r.tierRestrictions) ? r.tierRestrictions : [],
      status: normaliseRewardStatus(r.status) || 'draft',
      featured: r.featured === true,
    }));
  }

  // Notifications
  if (Array.isArray(analysis.notifications)) {
    mapping.notifications = analysis.notifications.map((n: any, i: number) => ({
      id: n.id || `ntf-${i + 1}`,
      type: normaliseNotificationType(n.type) || 'points-earned',
      template: n.template || {},
      triggers: Array.isArray(n.triggers) ? n.triggers : [],
      active: n.active !== false,
    }));
  }

  // Documents
  if (prog.termsConditions) mapping.termsConditions = prog.termsConditions;
  if (prog.privacyPolicy) mapping.privacyPolicy = prog.privacyPolicy;

  // Branding
  if (prog.primaryColour) mapping.primaryColour = prog.primaryColour;
  if (prog.secondaryColour) mapping.secondaryColour = prog.secondaryColour;

  // AI markers
  mapping.aiGenerated = true;

  return mapping;
}

// ============================================
// LOYALTY PROGRAMME NORMALISERS
// ============================================

function normaliseLoyaltyType(value: string): string {
  const v = String(value).toLowerCase().trim().replace(/\s+/g, '-');
  const valid = ['points-based', 'tiered', 'cashback', 'punch-card', 'coalition', 'hybrid', 'custom'];
  const map: Record<string, string> = {
    'points-based': 'points-based', 'points': 'points-based', 'point-based': 'points-based',
    'tiered': 'tiered', 'tier': 'tiered', 'tier-based': 'tiered',
    'cashback': 'cashback', 'cash-back': 'cashback', 'cash': 'cashback',
    'punch-card': 'punch-card', 'punchcard': 'punch-card', 'stamp': 'punch-card',
    'coalition': 'coalition', 'partner': 'coalition',
    'hybrid': 'hybrid', 'mixed': 'hybrid',
    'custom': 'custom',
  };
  return map[v] || (valid.includes(v) ? v : 'points-based');
}

function normaliseLoyaltyStatus(value: string): string {
  const v = String(value).toLowerCase().trim();
  const map: Record<string, string> = {
    'draft': 'draft',
    'active': 'active', 'live': 'active',
    'paused': 'paused', 'suspended': 'paused',
    'archived': 'archived', 'archive': 'archived',
  };
  return map[v] || 'draft';
}

function normaliseTierDowngradePeriod(value: string): string {
  const v = String(value).toLowerCase().trim();
  const map: Record<string, string> = {
    'yearly': 'yearly', 'annual': 'yearly', 'year': 'yearly',
    'quarterly': 'quarterly', 'quarter': 'quarterly',
    'monthly': 'monthly', 'month': 'monthly',
  };
  return map[v] || 'yearly';
}

function normaliseChannelType(value: string): string {
  const v = String(value).toLowerCase().trim().replace(/\s+/g, '-');
  const valid = ['pos', 'ecommerce', 'mobile-app', 'kiosk', 'web'];
  const map: Record<string, string> = {
    'pos': 'pos', 'point-of-sale': 'pos',
    'ecommerce': 'ecommerce', 'e-commerce': 'ecommerce', 'online': 'ecommerce', 'shop': 'ecommerce',
    'mobile-app': 'mobile-app', 'mobile': 'mobile-app', 'app': 'mobile-app',
    'kiosk': 'kiosk',
    'web': 'web', 'website': 'web',
  };
  const result = map[v] || v;
  return valid.includes(result) ? result : '';
}

function normaliseTierBenefitType(value: string): string {
  const v = String(value).toLowerCase().trim().replace(/\s+/g, '-');
  const valid = ['discount', 'free-shipping', 'priority-support', 'exclusive-access', 'bonus-points', 'perk', 'custom'];
  const map: Record<string, string> = {
    'discount': 'discount', 'discounts': 'discount',
    'free-shipping': 'free-shipping', 'shipping': 'free-shipping',
    'priority-support': 'priority-support', 'support': 'priority-support',
    'exclusive-access': 'exclusive-access', 'exclusive': 'exclusive-access', 'vip-access': 'exclusive-access',
    'bonus-points': 'bonus-points', 'bonus': 'bonus-points', 'extra-points': 'bonus-points',
    'perk': 'perk', 'perks': 'perk',
    'custom': 'custom',
  };
  return map[v] || (valid.includes(v) ? v : 'perk');
}

function normaliseBenefitUnit(value: string): string | undefined {
  if (!value) return undefined;
  const v = String(value).toLowerCase().trim();
  const map: Record<string, string> = {
    'percentage': 'percentage', 'percent': 'percentage', '%': 'percentage',
    'fixed': 'fixed', 'flat': 'fixed',
    'points': 'points', 'point': 'points',
    'item': 'item', 'items': 'item',
  };
  return map[v] || undefined;
}

function normaliseEarnTrigger(value: string): string {
  const v = String(value).toLowerCase().trim().replace(/\s+/g, '-');
  const map: Record<string, string> = {
    'purchase': 'purchase', 'buy': 'purchase', 'order': 'purchase',
    'action': 'action', 'activity': 'action',
    'referral': 'referral', 'refer': 'referral', 'referral-bonus': 'referral',
    'social-share': 'social-share', 'share': 'social-share', 'social': 'social-share',
    'review': 'review', 'feedback': 'review', 'rating': 'review',
    'birthday': 'birthday',
    'anniversary': 'anniversary',
    'signup': 'signup', 'sign-up': 'signup', 'registration': 'signup', 'register': 'signup',
    'milestone': 'milestone',
  };
  return map[v] || 'purchase';
}

function normaliseEarnCalculation(value: string): string {
  const v = String(value).toLowerCase().trim().replace(/\s+/g, '-');
  const map: Record<string, string> = {
    'fixed': 'fixed', 'flat': 'fixed',
    'percentage': 'percentage', 'percent': 'percentage',
    'multiplier': 'multiplier', 'multiply': 'multiplier',
    'custom': 'custom',
  };
  return map[v] || 'fixed';
}

function normaliseRedeemType(value: string): string {
  const v = String(value).toLowerCase().trim().replace(/\s+/g, '-');
  const map: Record<string, string> = {
    'discount': 'discount', 'discounts': 'discount',
    'product': 'product', 'products': 'product', 'free-product': 'product',
    'voucher': 'voucher', 'coupon': 'voucher', 'vouchers': 'voucher',
    'experience': 'experience', 'experiences': 'experience',
    'cash-equivalent': 'cash-equivalent', 'cash': 'cash-equivalent', 'cashback': 'cash-equivalent',
    'donation': 'donation', 'charity': 'donation',
    'upgrade': 'upgrade', 'tier-upgrade': 'upgrade',
  };
  return map[v] || 'discount';
}

function normaliseRedeemValueUnit(value: string): string | undefined {
  if (!value) return undefined;
  const v = String(value).toLowerCase().trim();
  const map: Record<string, string> = {
    'percentage': 'percentage', 'percent': 'percentage', '%': 'percentage',
    'fixed': 'fixed', 'flat': 'fixed',
    'item': 'item', 'items': 'item',
  };
  return map[v] || undefined;
}

function normaliseRewardType(value: string): string {
  const v = String(value).toLowerCase().trim().replace(/\s+/g, '-');
  const map: Record<string, string> = {
    'discount': 'discount', 'discounts': 'discount',
    'free-product': 'free-product', 'product': 'free-product',
    'upgrade': 'upgrade', 'tier-upgrade': 'upgrade',
    'experience': 'experience', 'experiences': 'experience',
    'gift-card': 'gift-card', 'giftcard': 'gift-card', 'gift': 'gift-card',
    'donation': 'donation', 'charity': 'donation',
    'voucher': 'voucher', 'coupon': 'voucher',
  };
  return map[v] || 'discount';
}

function normaliseRewardStatus(value: string): string {
  const v = String(value).toLowerCase().trim();
  const map: Record<string, string> = {
    'draft': 'draft',
    'active': 'active',
    'sold-out': 'sold-out', 'soldout': 'sold-out',
    'archived': 'archived',
  };
  return map[v] || 'draft';
}

function normaliseNotificationType(value: string): string {
  const v = String(value).toLowerCase().trim().replace(/\s+/g, '-');
  const map: Record<string, string> = {
    'points-earned': 'points-earned', 'earn': 'points-earned', 'earned': 'points-earned',
    'points-expiring': 'points-expiring', 'expiring': 'points-expiring', 'expiry': 'points-expiring',
    'tier-upgrade': 'tier-upgrade', 'upgrade': 'tier-upgrade',
    'tier-downgrade': 'tier-downgrade', 'downgrade': 'tier-downgrade',
    'reward-available': 'reward-available', 'reward': 'reward-available',
    'milestone': 'milestone',
    'welcome': 'welcome',
    'birthday': 'birthday',
  };
  return map[v] || 'points-earned';
}

// ============================================
// MEMBERSHIP PLAN MAPPING
// ============================================

export function computeMembershipPlanAutoFillMapping(analysis: Record<string, any>): Record<string, any> {
  const mapping: Record<string, any> = {};

  // Core fields
  if (analysis.name && typeof analysis.name === 'string') mapping.name = analysis.name;
  if (analysis.description && typeof analysis.description === 'string') mapping.description = analysis.description;

  // Enum: type
  if (analysis.type) {
    mapping.type = normaliseMembershipType(analysis.type);
  }

  // Price
  if (analysis.price != null) {
    const p = Number(analysis.price);
    mapping.price = isNaN(p) ? 0 : Math.max(0, p);
  }

  // Billing cycle
  if (analysis.billingCycle) {
    mapping.billingCycle = normaliseBillingCycle(analysis.billingCycle);
  }

  // Max users
  if (analysis.maxUsers != null) {
    const m = Number(analysis.maxUsers);
    if (!isNaN(m) && m >= 1) mapping.maxUsers = m;
  }

  // Storage limit
  if (analysis.storageLimit && typeof analysis.storageLimit === 'string') {
    mapping.storageLimit = analysis.storageLimit;
  }

  // Features (key-value boolean map)
  if (analysis.features && typeof analysis.features === 'object' && !Array.isArray(analysis.features)) {
    mapping.features = {};
    for (const [key, value] of Object.entries(analysis.features)) {
      if (typeof value === 'boolean') {
        mapping.features[key] = value;
      } else if (typeof value === 'string') {
        mapping.features[key] = value.toLowerCase() === 'true';
      }
    }
  }

  // Text fields
  if (analysis.benefits && typeof analysis.benefits === 'string') mapping.benefits = analysis.benefits;
  if (analysis.limitations && typeof analysis.limitations === 'string') mapping.limitations = analysis.limitations;
  if (analysis.onboardingFlow && typeof analysis.onboardingFlow === 'string') mapping.onboardingFlow = analysis.onboardingFlow;
  if (analysis.terms && typeof analysis.terms === 'string') mapping.terms = analysis.terms;
  if (analysis.fullDocument && typeof analysis.fullDocument === 'string') mapping.fullDocument = analysis.fullDocument;

  // Status - only set if AI provided a valid value, otherwise let frontend handle default
  if (analysis.status) {
    const normalized = normaliseMembershipPlanStatus(analysis.status);
    if (normalized) mapping.status = normalized;
  }

  // AI markers
  mapping.aiGenerated = true;

  return mapping;
}

// ============================================
// MEMBERSHIP PLAN NORMALISERS
// ============================================

function normaliseMembershipType(value: string): string {
  const v = String(value).toLowerCase().trim().replace(/\s+/g, '-');
  const map: Record<string, string> = {
    'free': 'free', 'starter': 'free', 'trial': 'free',
    'basic': 'basic', 'essentials': 'basic', 'essential': 'basic',
    'standard': 'standard', 'pro': 'standard', 'professional': 'standard',
    'premium': 'premium', 'advanced': 'premium',
    'enterprise': 'enterprise', 'business': 'enterprise',
    'vip': 'vip',
    'lifetime': 'lifetime',
    'custom': 'custom',
  };
  return map[v] || 'basic';
}

function normaliseBillingCycle(value: string): string {
  const v = String(value).toLowerCase().trim().replace(/\s+/g, '-');
  const map: Record<string, string> = {
    'monthly': 'monthly', 'month': 'monthly', 'mo': 'monthly',
    'quarterly': 'quarterly', 'quarter': 'quarterly', 'q': 'quarterly',
    'annual': 'annual', 'annually': 'annual', 'year': 'annual', 'yearly': 'annual',
    'one-time': 'one-time', 'onetime': 'one-time', 'lifetime': 'one-time', 'one': 'one-time',
  };
  return map[v] || 'monthly';
}

function normaliseMembershipPlanStatus(value: string): string {
  const v = String(value).toLowerCase().trim();
  const map: Record<string, string> = {
    'active': 'active', 'live': 'active',
    'inactive': 'inactive', 'disabled': 'inactive',
  };
  return map[v] || 'active';
}

// ============================================
// REFERRAL PROGRAMME AUTO-FILL MAPPING
// ============================================

export function computeReferralAutoFillMapping(analysis: Record<string, any>): Record<string, any> {
  const mapping: Record<string, any> = {};
  const prog = analysis.programme || analysis;

  // Core fields
  if (prog.name && typeof prog.name === 'string') mapping.name = prog.name;
  if (prog.description && typeof prog.description === 'string') mapping.description = prog.description;

  // Enum: type
  if (prog.type) {
    mapping.type = normaliseReferralOfferType(prog.type);
  }

  // Status - only set if AI provided a valid value, otherwise let frontend handle default
  if (prog.status) {
    const normalized = normaliseReferralStatus(prog.status);
    if (normalized) mapping.status = normalized;
  }

  // Referral code config
  if (prog.referralCodePrefix && typeof prog.referralCodePrefix === 'string') mapping.referralCodePrefix = prog.referralCodePrefix;
  if (prog.referralCodeFormat && typeof prog.referralCodeFormat === 'string') mapping.referralCodeFormat = prog.referralCodeFormat;
  if (prog.maxReferralsPerUser != null) mapping.maxReferralsPerUser = Number(prog.maxReferralsPerUser) || undefined;
  if (prog.maxTotalReferrals != null) mapping.maxTotalReferrals = Number(prog.maxTotalReferrals) || undefined;

  // Payout timing
  if (prog.payoutTiming) {
    mapping.payoutTiming = normalisePayoutTiming(prog.payoutTiming);
  }

  // Settings
  if (prog.settings && typeof prog.settings === 'object') {
    mapping.settings = {
      autoApprove: prog.settings.autoApprove === true,
      requireEmailVerification: prog.settings.requireEmailVerification !== false,
      cooldownPeriodDays: prog.settings.cooldownPeriodDays != null ? Number(prog.settings.cooldownPeriodDays) : undefined,
      fraudPrevention: {
        level: normaliseFraudPreventionLevel(prog.settings.fraudPrevention?.level) || 'standard',
        maxReferralsPerDay: Number(prog.settings.fraudPrevention?.maxReferralsPerDay) || 5,
        maxReferralsPerIP: Number(prog.settings.fraudPrevention?.maxReferralsPerIP) || 3,
        blockDisposableEmails: prog.settings.fraudPrevention?.blockDisposableEmails === true,
      },
      notifications: {
        referralSent: prog.settings.notifications?.referralSent !== false,
        referralQualified: prog.settings.notifications?.referralQualified !== false,
        rewardEarned: prog.settings.notifications?.rewardEarned !== false,
        milestoneReached: prog.settings.notifications?.milestoneReached !== false,
      },
      expiryDays: prog.settings.expiryDays != null ? Number(prog.settings.expiryDays) : 30,
      termsUrl: prog.settings.termsUrl || '',
    };
  }

  // Rewards
  if (Array.isArray(analysis.rewards)) {
    mapping.rewards = analysis.rewards.map((r: any, i: number) => ({
      id: r.id || `rw-${i + 1}`,
      name: r.name || `Reward ${i + 1}`,
      type: normaliseReferralRewardType(r.type) || 'discount',
      description: r.description || undefined,
      referrerReward: {
        value: Number(r.referrerReward?.value) || 0,
        valueType: normaliseValueType(r.referrerReward?.valueType) || 'fixed',
        description: r.referrerReward?.description || undefined,
      },
      refereeReward: {
        value: Number(r.refereeReward?.value) || 0,
        valueType: normaliseValueType(r.refereeReward?.valueType) || 'fixed',
        description: r.refereeReward?.description || undefined,
      },
      tierRestrictions: Array.isArray(r.tierRestrictions) ? r.tierRestrictions : [],
      minimumSpend: r.minimumSpend != null ? Number(r.minimumSpend) : undefined,
      maximumSpend: r.maximumSpend != null ? Number(r.maximumSpend) : undefined,
      status: normaliseReferralRewardStatus(r.status) || 'draft',
      featured: r.featured === true,
    }));
  }

  // Rules
  if (Array.isArray(analysis.rules)) {
    mapping.rules = analysis.rules.map((r: any, i: number) => ({
      id: r.id || `rule-${i + 1}`,
      name: r.name || `Rule ${i + 1}`,
      trigger: normaliseReferralTriggerType(r.trigger) || 'signup',
      description: r.description || undefined,
      conditions: Array.isArray(r.conditions) ? r.conditions : [],
      rewardReferral: r.rewardReferral || undefined,
      rewardReferee: r.rewardReferee || undefined,
      qualificationCriteria: Array.isArray(r.qualificationCriteria) ? r.qualificationCriteria : [],
      cap: r.cap ? {
        maxPerUser: r.cap.maxPerUser != null ? Number(r.cap.maxPerUser) : undefined,
        maxTotal: r.cap.maxTotal != null ? Number(r.cap.maxTotal) : undefined,
      } : undefined,
      active: r.active !== false,
      priority: r.priority != null ? Number(r.priority) : i + 1,
    }));
  }

  // Product Referrals
  if (Array.isArray(analysis.productReferrals)) {
    mapping.productReferrals = analysis.productReferrals.map((pr: any, i: number) => ({
      id: pr.id || `pr-${i + 1}`,
      productId: pr.productId || undefined,
      productName: pr.productName || `Product ${i + 1}`,
      referralBonus: pr.referralBonus || undefined,
      conditions: Array.isArray(pr.conditions) ? pr.conditions : [],
      active: pr.active !== false,
    }));
  }

  // Strategies
  if (analysis.strategies && typeof analysis.strategies === 'object') {
    mapping.strategies = {
      channels: Array.isArray(analysis.strategies.channels)
        ? analysis.strategies.channels.map((c: string) => normaliseReferralChannel(c)).filter(Boolean)
        : ['email', 'social', 'link'],
      targetAudience: analysis.strategies.targetAudience || undefined,
      messaging: analysis.strategies.messaging || undefined,
      landingPageSuggestions: Array.isArray(analysis.strategies.landingPageSuggestions) ? analysis.strategies.landingPageSuggestions : [],
      promotionTips: Array.isArray(analysis.strategies.promotionTips) ? analysis.strategies.promotionTips : [],
      referralMilestoneRewards: Array.isArray(analysis.strategies.referralMilestoneRewards)
        ? analysis.strategies.referralMilestoneRewards.map((mr: any) => ({
            milestone: mr.milestone || '',
            reward: mr.reward || '',
          }))
        : [],
    };
  }

  // Documents
  if (prog.termsConditions) mapping.termsConditions = prog.termsConditions;
  if (prog.privacyPolicy) mapping.privacyPolicy = prog.privacyPolicy;

  // Branding
  if (prog.primaryColour) mapping.primaryColour = prog.primaryColour;
  if (prog.secondaryColour) mapping.secondaryColour = prog.secondaryColour;

  // Best practices and optimization tips
  if (Array.isArray(analysis.bestPractices)) mapping.bestPractices = analysis.bestPractices;
  if (Array.isArray(analysis.optimizationTips)) mapping.optimizationTips = analysis.optimizationTips;

  // AI markers
  mapping.aiGenerated = true;

  return mapping;
}

// ============================================
// REFERRAL NORMALISERS
// ============================================

function normaliseReferralOfferType(value: string): string {
  const v = String(value).toLowerCase().trim().replace(/\s+/g, '-');
  const valid = ['single-sided', 'double-sided', 'tiered', 'affiliate', 'ambassador', 'custom'];
  const map: Record<string, string> = {
    'single-sided': 'single-sided', 'single': 'single-sided', 'one-sided': 'single-sided',
    'double-sided': 'double-sided', 'two-sided': 'double-sided', 'dual': 'double-sided',
    'tiered': 'tiered', 'tier': 'tiered', 'multi-tier': 'tiered',
    'affiliate': 'affiliate', 'affiliate-program': 'affiliate',
    'ambassador': 'ambassador', 'brand-ambassador': 'ambassador',
    'custom': 'custom',
  };
  return map[v] || (valid.includes(v) ? v : 'single-sided');
}

function normaliseReferralStatus(value: string): string {
  const v = String(value).toLowerCase().trim();
  const map: Record<string, string> = {
    'draft': 'draft',
    'active': 'active', 'live': 'active',
    'paused': 'paused', 'suspended': 'paused',
    'archived': 'archived', 'archive': 'archived',
  };
  return map[v] || 'draft';
}

function normaliseReferralRewardType(value: string): string {
  const v = String(value).toLowerCase().trim().replace(/\s+/g, '-');
  const valid = ['discount', 'credit', 'cash', 'free-product', 'upgrade', 'gift-card', 'points', 'custom'];
  const map: Record<string, string> = {
    'discount': 'discount', 'percentage-off': 'discount', 'percent-off': 'discount',
    'credit': 'credit', 'store-credit': 'credit', 'account-credit': 'credit',
    'cash': 'cash', 'cashback': 'cash', 'cash-back': 'cash', 'money': 'cash',
    'free-product': 'free-product', 'free-item': 'free-product',
    'upgrade': 'upgrade', 'plan-upgrade': 'upgrade', 'tier-upgrade': 'upgrade',
    'gift-card': 'gift-card', 'giftcard': 'gift-card', 'voucher': 'gift-card',
    'points': 'points', 'loyalty-points': 'points',
    'custom': 'custom',
  };
  return map[v] || (valid.includes(v) ? v : 'discount');
}

function normaliseValueType(value: string): string {
  const v = String(value).toLowerCase().trim();
  const map: Record<string, string> = {
    'percentage': 'percentage', 'percent': 'percentage', '%': 'percentage',
    'fixed': 'fixed', 'flat': 'fixed', 'amount': 'fixed',
    'points': 'points', 'point': 'points',
  };
  return map[v] || 'fixed';
}

function normaliseReferralRewardStatus(value: string): string {
  const v = String(value).toLowerCase().trim();
  const map: Record<string, string> = {
    'draft': 'draft',
    'active': 'active', 'live': 'active',
    'sold-out': 'sold-out', 'soldout': 'sold-out', 'exhausted': 'sold-out',
    'archived': 'archived', 'archive': 'archived',
  };
  return map[v] || 'draft';
}

function normaliseReferralTriggerType(value: string): string {
  const v = String(value).toLowerCase().trim().replace(/\s+/g, '-');
  const valid = ['signup', 'purchase', 'subscription', 'referral-qualified', 'milestone', 'social-share', 'review'];
  const map: Record<string, string> = {
    'signup': 'signup', 'sign-up': 'signup', 'registration': 'signup', 'register': 'signup',
    'purchase': 'purchase', 'buy': 'purchase', 'order': 'purchase',
    'subscription': 'subscription', 'subscribe': 'subscription',
    'referral-qualified': 'referral-qualified', 'qualified': 'referral-qualified',
    'milestone': 'milestone', 'achievement': 'milestone',
    'social-share': 'social-share', 'share': 'social-share', 'social': 'social-share',
    'review': 'review', 'rating': 'review',
  };
  return map[v] || (valid.includes(v) ? v : 'signup');
}

function normaliseReferralChannel(value: string): string {
  const v = String(value).toLowerCase().trim().replace(/\s+/g, '-');
  const valid = ['email', 'social', 'link', 'qr-code', 'sms', 'in-app', 'affiliate', 'custom'];
  const map: Record<string, string> = {
    'email': 'email', 'e-mail': 'email', 'mail': 'email',
    'social': 'social', 'social-media': 'social',
    'link': 'link', 'referral-link': 'link', 'url': 'link',
    'qr-code': 'qr-code', 'qrcode': 'qr-code', 'qr': 'qr-code',
    'sms': 'sms', 'text': 'sms', 'text-message': 'sms',
    'in-app': 'in-app', 'app': 'in-app', 'mobile': 'in-app',
    'affiliate': 'affiliate', 'partner': 'affiliate',
    'custom': 'custom',
  };
  const result = map[v] || v;
  return valid.includes(result) ? result : '';
}

function normaliseFraudPreventionLevel(value: string): string {
  const v = String(value).toLowerCase().trim();
  const map: Record<string, string> = {
    'basic': 'basic', 'minimal': 'basic', 'low': 'basic',
    'standard': 'standard', 'normal': 'standard', 'medium': 'standard',
    'strict': 'strict', 'high': 'strict', 'maximum': 'strict',
    'custom': 'custom',
  };
  return map[v] || 'standard';
}

function normalisePayoutTiming(value: string): string {
  const v = String(value).toLowerCase().trim().replace(/\s+/g, '-');
  const map: Record<string, string> = {
    'immediate': 'immediate', 'instant': 'immediate', 'right-away': 'immediate',
    'on-qualification': 'on-qualification', 'qualified': 'on-qualification', 'after-qualification': 'on-qualification',
    'monthly': 'monthly', 'month': 'monthly',
    'quarterly': 'quarterly', 'quarter': 'quarterly',
    'annual': 'annual', 'yearly': 'annual', 'year': 'annual',
  };
  return map[v] || 'immediate';
}

// ============================================
// CRUD OPERATIONS
// ============================================

export const aiContextService = {
  /**
   * Create a new AiContext document.
   */
  async create(params: {
    companyId: string;
    moduleSource: string;
    analysisType: string;
    inputs: IAiContextInputs;
    analysis: IAiAnalysisResult;
    metadata: {
      pipelineVersion: string;
      provider: string;
      model: string;
      tokensUsed: number;
      inputTokens?: number;
      outputTokens?: number;
      processingTimeMs: number;
      latencyMs?: number;
      overallConfidence: number;
      fieldConfidences: Record<string, number>;
      finishReason?: string | null;
      apiKeyMasked?: string | null;
      webResearchPerformed?: boolean;
      webResearchWebsite?: string | null;
      error?: string | null;
    };
  }): Promise<IAiContext> {
    const { AiContext } = getModels();
    const context = new (AiContext as any)({
      companyId: params.companyId,
      moduleSource: params.moduleSource,
      analysisType: params.analysisType,
      inputs: params.inputs,
      analysis: params.analysis,
      pipelineVersion: params.metadata.pipelineVersion,
      provider: params.metadata.provider,
      aiModel: params.metadata.model,
      tokensUsed: params.metadata.tokensUsed,
      inputTokens: params.metadata.inputTokens,
      outputTokens: params.metadata.outputTokens,
      processingTimeMs: params.metadata.processingTimeMs,
      latencyMs: params.metadata.latencyMs,
      // Confidences are clamped to the schema's 0–100 range. A pipeline that
      // miscounts its stages (the competitor pipeline reported 133) would
      // otherwise fail validation here and throw away a generation whose AI
      // work had already completed — a reporting number must never cost the
      // user the result.
      overallConfidence: clampConfidence(params.metadata.overallConfidence),
      fieldConfidences: new Map(
        Object.entries(params.metadata.fieldConfidences || {}).map(
          ([field, value]) => [field, clampConfidence(value)] as [string, number],
        ),
      ),
      finishReason: params.metadata.finishReason,
      apiKeyMasked: params.metadata.apiKeyMasked,
      webResearchPerformed: params.metadata.webResearchPerformed || false,
      webResearchWebsite: params.metadata.webResearchWebsite || null,
      error: params.metadata.error || null,
      status: 'draft',
      isEditable: true,
      completedAt: new Date(),
    });
    await context.save();
    return context;
  },

  /**
   * Get all AiContext documents for a company, optionally filtered by moduleSource.
   */
  async getByCompany(companyId: string, moduleSource?: string): Promise<IAiContext[]> {
    const { AiContext } = getModels();
    const query: any = { companyId };
    if (moduleSource) query.moduleSource = moduleSource;
    const results = await AiContext.find(query);
    // Sort by createdAt descending (handles both Mongoose documents and plain objects)
    return Array.isArray(results)
      ? results.sort((a: any, b: any) => {
          const dateA = a.createdAt ? new Date(a.createdAt).getTime() : 0;
          const dateB = b.createdAt ? new Date(b.createdAt).getTime() : 0;
          return dateB - dateA;
        })
      : results;
  },

  /**
   * Get a single AiContext document by ID.
   */
  async getById(id: string): Promise<IAiContext | null> {
    const { AiContext } = getModels();
    // Validate id is a valid ObjectId before querying
    if (!id || !/^[a-fA-F0-9]{24}$/.test(id)) {
      return null;
    }
    return AiContext.findById(id);
  },

  /**
   * Update the analysis fields of an AiContext document (human edits).
   */
  async updateAnalysis(id: string, analysis: Partial<IAiAnalysisResult>): Promise<IAiContext | null> {
    const { AiContext } = getModels();
    // Validate id is a valid ObjectId before querying
    if (!id || !/^[a-fA-F0-9]{24}$/.test(id)) {
      return null;
    }
    const context = await AiContext.findById(id);
    if (!context) return null;

    // Merge updates into existing analysis
    const currentAnalysis = (context as any).analysis?.toObject?.() || (context as any).analysis || {};
    Object.assign(currentAnalysis, analysis);
    (context as any).analysis = currentAnalysis;
    (context as any).updatedAt = new Date();
    context.markModified('analysis');
    await context.save();
    return context;
  },

  /**
   * Update the status of an AiContext document.
   */
  async updateStatus(id: string, status: AiContextStatus): Promise<IAiContext | null> {
    const { AiContext } = getModels();
    // Validate id is a valid ObjectId before querying
    if (!id || !/^[a-fA-F0-9]{24}$/.test(id)) {
      console.warn(`[AiContext] updateStatus called with invalid ObjectId: ${id}`);
      return null;
    }
    const update: any = {
      status,
      isEditable: status === 'draft',
    };
    return AiContext.findByIdAndUpdate(id, update, { new: true });
  },

  /**
   * Approve an AiContext and compute the auto-fill mapping for BusinessProfile.
   */
  async approveAndApply(id: string): Promise<{ context: IAiContext; autoFillData: Record<string, any> } | null> {
    const { AiContext } = getModels();
    // Validate id is a valid ObjectId before querying
    if (!id || !/^[a-fA-F0-9]{24}$/.test(id)) {
      return null;
    }
    const context = await AiContext.findById(id);
    if (!context) return null;

    (context as any).status = 'approved';
    (context as any).isEditable = false;
    await context.save();

    const inputs = (context as any).inputs?.toObject?.() || (context as any).inputs || {};
    const analysis = (context as any).analysis?.toObject?.() || (context as any).analysis || {};
    const autoFillData = computeAutoFillMapping(inputs, analysis);

    return { context, autoFillData };
  },

  /**
   * Delete an AiContext document.
   */
  async delete(id: string): Promise<IAiContext | null> {
    const { AiContext } = getModels();
    // Validate id is a valid ObjectId before querying
    if (!id || !/^[a-fA-F0-9]{24}$/.test(id)) {
      return null;
    }
    return AiContext.findByIdAndDelete(id);
  },
};

// ============================================
// SOCIAL MEDIA OS AUTO-FILL MAPPING
// ============================================

const VALID_SOCIAL_PLATFORMS = [
  'instagram', 'facebook', 'linkedin', 'twitter', 'youtube',
  'tiktok', 'pinterest', 'threads', 'whatsapp-channels', 'telegram', 'google-business',
];

const VALID_CONTENT_PILLARS = [
  'awareness', 'engagement', 'education', 'conversion', 'community',
  'authority', 'entertainment', 'product', 'culture', 'news',
];

const VALID_CONTENT_TYPES = [
  'reel', 'carousel', 'static-post', 'story', 'shorts', 'long-video',
  'tweet', 'thread', 'poll', 'infographic', 'meme', 'announcement',
  'educational-content', 'testimonial', 'case-study', 'product-showcase',
  'founder-content', 'trend-based-content', 'promotional-content', 'event-content',
];

const VALID_FUNNEL_STAGES = ['top-of-funnel', 'middle-of-funnel', 'bottom-of-funnel', 'retention'];

const VALID_TEMPLATE_CATEGORIES = [
  'caption', 'hook', 'cta', 'hashtag-set', 'reel-script', 'carousel-framework', 'campaign', 'story-framework',
];

const VALID_HASHTAG_TYPES = ['trending', 'branded', 'evergreen', 'niche', 'campaign', 'community'];

const VALID_PRIORITIES = ['low', 'medium', 'high', 'urgent'];

const VALID_ENTRY_STATUSES = [
  'planned', 'content-pending', 'writing-in-progress', 'design-pending',
  'reel-editing', 'under-review', 'approved', 'scheduled', 'posted', 'rejected', 'delayed', 'archived',
];

function normaliseSocialPlatform(value: any): string {
  if (!value || typeof value !== 'string') return 'instagram';
  const lower = value.toLowerCase().trim();
  const platformMap: Record<string, string> = {
    'instagram': 'instagram', 'ig': 'instagram', 'insta': 'instagram',
    'facebook': 'facebook', 'fb': 'facebook',
    'linkedin': 'linkedin', 'li': 'linkedin',
    'twitter': 'twitter', 'x': 'twitter', 'x/twitter': 'twitter',
    'youtube': 'youtube', 'yt': 'youtube',
    'tiktok': 'tiktok', 'tt': 'tiktok',
    'pinterest': 'pinterest', 'pin': 'pinterest',
    'threads': 'threads', 'threadsapp': 'threads',
    'whatsapp': 'whatsapp-channels', 'whatsapp-channels': 'whatsapp-channels', 'wa': 'whatsapp-channels',
    'telegram': 'telegram', 'tg': 'telegram',
    'google-business': 'google-business', 'google': 'google-business', 'gmb': 'google-business',
  };
  return platformMap[lower] || (VALID_SOCIAL_PLATFORMS.includes(lower) ? lower : 'instagram');
}

function normaliseContentPillar(value: any): string {
  if (!value || typeof value !== 'string') return 'awareness';
  const lower = value.toLowerCase().trim();
  return VALID_CONTENT_PILLARS.includes(lower) ? lower : 'awareness';
}

function normaliseContentType(value: any): string {
  if (!value || typeof value !== 'string') return 'static-post';
  const lower = value.toLowerCase().trim().replace(/\s+/g, '-');
  return VALID_CONTENT_TYPES.includes(lower) ? lower : 'static-post';
}

function normaliseTemplateCategory(value: any): string {
  if (!value || typeof value !== 'string') return 'caption';
  const lower = value.toLowerCase().trim().replace(/\s+/g, '-');
  return VALID_TEMPLATE_CATEGORIES.includes(lower) ? lower : 'caption';
}

function normaliseHashtagType(value: any): string {
  if (!value || typeof value !== 'string') return 'evergreen';
  const lower = value.toLowerCase().trim();
  return VALID_HASHTAG_TYPES.includes(lower) ? lower : 'evergreen';
}

function normaliseEntryStatus(value: any): string {
  if (!value || typeof value !== 'string') return 'planned';
  const lower = value.toLowerCase().trim().replace(/\s+/g, '-');
  return VALID_ENTRY_STATUSES.includes(lower) ? lower : 'planned';
}

export function computeSocialMediaAutoFillMapping(analysis: Record<string, any>): Record<string, any> {
  const mapping: Record<string, any> = {};
  const strat = analysis.strategy || analysis;

  console.log(`[SocialMedia-Mapping] Input keys: ${Object.keys(analysis).join(', ')}. Templates: ${Array.isArray(analysis.templates) ? analysis.templates.length : 'not array'}. HashtagBanks: ${Array.isArray(analysis.hashtagBanks) ? analysis.hashtagBanks.length : 'not array'}`);

  // Strategy fields
  if (strat.name && typeof strat.name === 'string') mapping.strategyName = strat.name;
  if (strat.description && typeof strat.description === 'string') mapping.strategyDescription = strat.description;
  if (Array.isArray(strat.contentPillars)) {
    mapping.strategyContentPillars = strat.contentPillars.map(normaliseContentPillar);
  }
  if (Array.isArray(strat.objectives)) {
    mapping.strategyObjectives = strat.objectives;
  }
  if (strat.targetAudience && typeof strat.targetAudience === 'string') mapping.strategyTargetAudience = strat.targetAudience;
  if (strat.toneAndVoice) {
    if (typeof strat.toneAndVoice === 'string') {
      mapping.strategyToneAndVoice = strat.toneAndVoice;
    } else if (typeof strat.toneAndVoice === 'object') {
      mapping.strategyToneAndVoice = strat.toneAndVoice.primary || strat.toneAndVoice.toString?.() || '';
    }
  }
  if (strat.postingFrequencyGoal) mapping.strategyPostingFrequencyGoal = strat.postingFrequencyGoal;
  if (Array.isArray(strat.defaultPlatforms)) {
    mapping.strategyDefaultPlatforms = strat.defaultPlatforms.map(normaliseSocialPlatform);
  }
  if (strat.contentMixTargets) mapping.strategyContentMixTargets = strat.contentMixTargets;
  if (Array.isArray(strat.defaultPublishingTimes)) mapping.strategyDefaultPublishingTimes = strat.defaultPublishingTimes;
  if (strat.defaultTimezone) mapping.strategyDefaultTimezone = strat.defaultTimezone;
  if (strat.autoGenerateCaptions !== undefined) mapping.strategyAutoGenerateCaptions = !!strat.autoGenerateCaptions;
  if (strat.autoGenerateHashtags !== undefined) mapping.strategyAutoGenerateHashtags = !!strat.autoGenerateHashtags;

  // Calendar entries
  if (Array.isArray(analysis.entries)) {
    mapping.entries = analysis.entries.map((e: any, i: number) => ({
      id: e.id || `entry-${i + 1}`,
      title: e.title || `Post ${i + 1}`,
      platform: normaliseSocialPlatform(e.platform),
      contentType: normaliseContentType(e.contentType),
      pillar: normaliseContentPillar(e.pillar),
      funnelStage: normaliseFunnelStage(e.funnelStage),
      caption: e.caption || '',
      hook: e.hook || '',
      cta: e.cta || '',
      hashtags: Array.isArray(e.hashtags) ? e.hashtags : [],
      publishDayOffset: typeof e.publishDayOffset === 'number' ? e.publishDayOffset : i,
      publishTime: e.publishTime || '09:00',
      priority: normalisePriority(e.priority),
      status: normaliseEntryStatus(e.status) || 'planned',
      objective: e.objective || '',
      approvalStatus: 'pending' as const,
      platformVariations: e.platformVariations || {},
    }));
  }

  // Templates
  if (Array.isArray(analysis.templates)) {
    mapping.templates = analysis.templates.map((t: any, i: number) => ({
      id: t.id || `tmpl-${i + 1}`,
      name: t.name || `Template ${i + 1}`,
      description: t.description || '',
      category: normaliseTemplateCategory(t.category),
      platform: t.platform && t.platform !== 'all' ? normaliseSocialPlatform(t.platform) : undefined,
      contentType: t.contentType && t.contentType !== 'all' ? normaliseContentType(t.contentType) : undefined,
      pillar: t.pillar ? normaliseContentPillar(t.pillar) : undefined,
      funnelStage: t.funnelStage ? normaliseFunnelStage(t.funnelStage) : undefined,
      content: t.content || '',
      structure: t.structure || '',
      tags: Array.isArray(t.tags) ? t.tags : [],
      isDefault: t.isDefault === true,
    }));
  }

  // Hashtag banks
  if (Array.isArray(analysis.hashtagBanks)) {
    mapping.hashtagBanks = analysis.hashtagBanks.map((h: any, i: number) => ({
      id: h.id || `hash-${i + 1}`,
      name: h.name || `Hashtag Bank ${i + 1}`,
      platform: h.platform && h.platform !== 'all' ? normaliseSocialPlatform(h.platform) : undefined,
      type: normaliseHashtagType(h.type),
      campaign: h.campaign || undefined,
      hashtags: Array.isArray(h.hashtags) ? h.hashtags : [],
      avgReach: h.avgReach || undefined,
      avgEngagement: h.avgEngagement || undefined,
      isActive: h.isActive !== false,
    }));
  }

  console.log(`[SocialMedia-Mapping] Output keys: ${Object.keys(mapping).join(', ')}. Templates: ${Array.isArray(mapping.templates) ? mapping.templates.length : 'none'}. HashtagBanks: ${Array.isArray(mapping.hashtagBanks) ? mapping.hashtagBanks.length : 'none'}. Entries: ${Array.isArray(mapping.entries) ? mapping.entries.length : 'none'}`);

  return mapping;
}

// ============================================
// BUSINESS PROFILE AUTO-CREATION
// ============================================

/**
 * Creates a BusinessProfile from autoFillData after the AI enrichment pipeline completes.
 * Silently skips if a profile already exists for the company (one profile per company).
 * Sanitises enum values to match the BusinessProfile model's allowed values.
 */
export async function createBusinessProfileFromAutoFill(
  companyId: string,
  autoFillData: Record<string, any>,
  companyName: string,
): Promise<any> {
  const { BusinessProfile } = getModels();

  // Sanitize enum values to match the BusinessProfile model's allowed values
  const validBusinessModels = ['b2b', 'b2c', 'b2b2c', 'saas', 'marketplace', 'd2c', 'freemium', 'subscription', 'hybrid'];
  const validIndustries = ['technology', 'healthcare', 'finance', 'education', 'ecommerce', 'saas', 'consulting', 'manufacturing', 'retail', 'real-estate', 'hospitality', 'media', 'non-profit', 'legal', 'marketing', 'design', 'food-beverage', 'sports', 'other'];

  const sanitisedBusinessModel = autoFillData.businessModel && validBusinessModels.includes(autoFillData.businessModel.toLowerCase())
    ? autoFillData.businessModel.toLowerCase()
    : '';

  const sanitisedIndustries = Array.isArray(autoFillData.industries)
    ? (autoFillData.industries as string[])
        .map((i: string) => i.toLowerCase())
        .filter((i: string) => validIndustries.includes(i))
    : [];

  // AI-generated values keyed by profile field. Only non-empty values are applied.
  const generatedFields: Record<string, any> = {
    description: autoFillData.description || '',
    descriptionLong: autoFillData.descriptionLong || '',
    primaryIndustry: autoFillData.primaryIndustry || '',
    businessModel: sanitisedBusinessModel,
    vision: autoFillData.vision || '',
    mission: autoFillData.mission || '',
    coreValues: autoFillData.coreValues || '',
    usp: autoFillData.usp || '',
    targetGeography: autoFillData.targetGeography || '',
    primaryOffering: autoFillData.primaryOffering || '',
    secondaryOfferings: autoFillData.secondaryOfferings || '',
    pricingModel: autoFillData.pricingModel || '',
    website: autoFillData.website || '',
    industries: sanitisedIndustries,
  };

  const isEmptyValue = (value: any): boolean => {
    if (value === null || value === undefined) return true;
    if (typeof value === 'string') return value.trim().length === 0;
    if (Array.isArray(value)) return value.length === 0;
    return false;
  };

  // Check for existing profile (one per company). If one already exists — e.g. the
  // user created it during the onboarding "Describe Your Company" step — fill in
  // ONLY the fields that are still empty so generated content lands automatically
  // without overwriting anything the user already entered.
  const existing = await BusinessProfile.findOne({ companyId });
  if (existing) {
    let changed = false;
    for (const [field, value] of Object.entries(generatedFields)) {
      if (!isEmptyValue(value) && isEmptyValue((existing as any)[field])) {
        (existing as any)[field] = value;
        changed = true;
      }
    }
    if (changed) {
      await existing.save();
      console.log(`[BusinessProfile] Filled empty fields from auto-fill for company ${companyId}`);
    } else {
      console.log(`[BusinessProfile] Profile already populated for company ${companyId}, nothing to fill.`);
    }
    return existing;
  }

  const profileData = {
    companyId,
    name: autoFillData.name || companyName,
    ...generatedFields,
    stage: 'idea',
  };

  const profile = new BusinessProfile(profileData);
  await profile.save();
  console.log(`[BusinessProfile] Auto-created profile for company ${companyId}`);
  return profile;
}

// ============================================
// WHATSAPP NURTURING AUTO-FILL MAPPING
// ============================================

/**
 * Maps WhatsApp Nurturing pipeline output to WhatsAppCampaign entity fields.
 * Normalises enum values and structures for sequence plans, messages, and optimization data.
 */
export function computeWhatsAppNurturingAutoFillMapping(analysis: Record<string, any>): Record<string, any> {
  const mapping: Record<string, any> = {};

  // ---- A. Campaign Core Fields ----
  if (analysis.name) mapping.name = analysis.name;
  if (analysis.description) mapping.description = analysis.description;
  if (analysis.goal) mapping.goal = normaliseWhatsAppGoal(analysis.goal);
  if (analysis.framework) mapping.framework = normaliseWhatsAppFramework(analysis.framework);
  if (analysis.tone) mapping.tone = normaliseWhatsAppTone(analysis.tone);
  if (analysis.personalizationLevel) mapping.personalizationLevel = normaliseWhatsAppPersonalization(analysis.personalizationLevel);
  if (analysis.messageLength) mapping.messageLength = normaliseWhatsAppMessageLength(analysis.messageLength);
  if (analysis.language) mapping.language = analysis.language;
  if (analysis.targetRegion) mapping.targetRegion = analysis.targetRegion;

  // Numeric fields
  if (analysis.sequenceDuration) {
    const dur = parseInt(String(analysis.sequenceDuration), 10);
    if (!isNaN(dur) && dur >= 1 && dur <= 90) mapping.sequenceDuration = dur;
  }
  if (analysis.messageFrequency) mapping.messageFrequency = normaliseWhatsAppFrequency(analysis.messageFrequency);
  if (analysis.deliveryTime) mapping.deliveryTime = analysis.deliveryTime;

  // Target audience arrays
  if (Array.isArray(analysis.targetIcpIds)) {
    mapping.targetIcpIds = analysis.targetIcpIds.filter((id: any) => typeof id === 'string' && id.trim());
  }
  if (Array.isArray(analysis.targetPersonaIds)) {
    mapping.targetPersonaIds = analysis.targetPersonaIds.filter((id: any) => typeof id === 'string' && id.trim());
  }

  // Data sources array
  if (Array.isArray(analysis.dataSources)) {
    const validSources = ['business-profile', 'founder', 'product', 'icp', 'persona', 'competitor', 'brand', 'testimonial', 'case-study', 'faq', 'blog', 'landing-page', 'website', 'book', 'courses', 'events'];
    mapping.dataSources = analysis.dataSources.filter((ds: any) => validSources.includes(ds));
  }

  // ---- B. Sequence Plan ----
  if (Array.isArray(analysis.sequencePlan) && analysis.sequencePlan.length > 0) {
    mapping.sequencePlan = analysis.sequencePlan.map((day: any, i: number) => ({
      day: typeof day.day === 'number' ? day.day : (i + 1),
      theme: typeof day.theme === 'string' ? day.theme : `Day ${i + 1}`,
      objective: typeof day.objective === 'string' ? day.objective : '',
      messageAngle: typeof day.messageAngle === 'string' ? day.messageAngle : '',
      contentApproach: typeof day.contentApproach === 'string' ? day.contentApproach : '',
    }));
    mapping.planApproved = false; // Plans from AI need review
  }

  // ---- C. Messages ----
  if (Array.isArray(analysis.messages) && analysis.messages.length > 0) {
    mapping.messages = analysis.messages.map((msg: any, i: number) => ({
      id: msg.id || `msg-${Date.now()}-${i}`,
      day: typeof msg.day === 'number' ? msg.day : (i + 1),
      timeSlot: typeof msg.timeSlot === 'string' ? msg.timeSlot : '09:00',
      goal: typeof msg.goal === 'string' ? msg.goal : '',
      copy: typeof msg.copy === 'string' ? msg.copy : '',
      cta: typeof msg.cta === 'string' ? msg.cta : '',
      ctaUrl: typeof msg.ctaUrl === 'string' ? msg.ctaUrl : '',
      personalizationVariables: Array.isArray(msg.personalizationVariables)
        ? msg.personalizationVariables.filter((v: any) => typeof v === 'string')
        : [],
      media: msg.media || undefined,
      contentBlocks: Array.isArray(msg.contentBlocks)
        ? msg.contentBlocks.map((cb: any, j: number) => ({
            id: cb.id || `cb-${Date.now()}-${i}-${j}`,
            type: normaliseWhatsAppContentBlockType(cb.type),
            content: typeof cb.content === 'string' ? cb.content : '',
            sourceId: cb.sourceId || undefined,
            enabled: typeof cb.enabled === 'boolean' ? cb.enabled : true,
          }))
        : [],
      status: normaliseWhatsAppMessageStatus(msg.status),
    }));
  }

  // ---- D. Optimization ----
  if (analysis.optimization && typeof analysis.optimization === 'object') {
    const opt = analysis.optimization;
    mapping.optimization = {
      openRateScore: clampNumber(opt.openRateScore, 0, 100),
      responseRateScore: clampNumber(opt.responseRateScore, 0, 100),
      engagementScore: clampNumber(opt.engagementScore, 0, 100),
      conversionScore: clampNumber(opt.conversionScore, 0, 100),
      suggestions: Array.isArray(opt.suggestions) ? opt.suggestions.filter((s: any) => typeof s === 'string') : [],
      optimizedCopy: typeof opt.optimizedCopy === 'string' ? opt.optimizedCopy : undefined,
    };
  }

  // ---- E. Multi-Channel Assets ----
  if (Array.isArray(analysis.multiChannelAssets) && analysis.multiChannelAssets.length > 0) {
    const validChannels = ['email', 'landing-page', 'social-post', 'ad-copy'];
    mapping.multiChannelAssets = analysis.multiChannelAssets
      .filter((asset: any) => validChannels.includes(asset.channel))
      .map((asset: any, i: number) => ({
        id: asset.id || `mca-${Date.now()}-${i}`,
        channel: asset.channel,
        subject: asset.subject || undefined,
        headline: asset.headline || undefined,
        copy: typeof asset.copy === 'string' ? asset.copy : '',
        cta: asset.cta || '',
        status: 'generated',
      }));
  }

  // ---- F. Automation Config ----
  if (analysis.automation && typeof analysis.automation === 'object') {
    const auto = analysis.automation;
    mapping.automation = {
      triggerEvent: normaliseWhatsAppTriggerEvent(auto.triggerEvent),
      exitConditions: Array.isArray(auto.exitConditions)
        ? auto.exitConditions.map((ec: any) => normaliseWhatsAppExitCondition(ec))
        : ['sequence-completed'],
      workingDaysOnly: typeof auto.workingDaysOnly === 'boolean' ? auto.workingDaysOnly : true,
      deliveryStartTime: typeof auto.deliveryStartTime === 'string' ? auto.deliveryStartTime : '09:00',
      deliveryEndTime: typeof auto.deliveryEndTime === 'string' ? auto.deliveryEndTime : '18:00',
      timezone: typeof auto.timezone === 'string' ? auto.timezone : 'UTC',
      optOutKeyword: typeof auto.optOutKeyword === 'string' ? auto.optOutKeyword : 'STOP',
      maxMessagesPerDay: typeof auto.maxMessagesPerDay === 'number' ? auto.maxMessagesPerDay : 2,
    };
  }

  // ---- G. Status ----
  if (analysis.status) {
    mapping.status = normaliseWhatsAppCampaignStatus(analysis.status);
  }

  // ---- Enforce character limits ----
  mapping.name = enforceCharLimit(mapping.name, 200);
  mapping.description = enforceCharLimit(mapping.description, 2000);

  return mapping;
}

// ============================================
// WHATSAPP NURTURING NORMALISERS
// ============================================

function normaliseWhatsAppGoal(value: string): string {
  const v = String(value).toLowerCase().trim().replace(/\s+/g, '-');
  const goalMap: Record<string, string> = {
    'lead-nurturing': 'lead-nurturing', 'nurturing': 'lead-nurturing',
    'lead-qualification': 'lead-qualification', 'qualification': 'lead-qualification',
    'appointment-booking': 'appointment-booking', 'booking': 'appointment-booking',
    'product-sales': 'product-sales', 'sales': 'product-sales',
    'webinar-registration': 'webinar-registration', 'webinar': 'webinar-registration',
    'course-enrollment': 'course-enrollment', 'enrollment': 'course-enrollment',
    'community-building': 'community-building', 'community': 'community-building',
    'customer-onboarding': 'customer-onboarding', 'onboarding': 'customer-onboarding',
    'upsell-campaign': 'upsell-campaign', 'upsell': 'upsell-campaign',
    'retention-campaign': 'retention-campaign', 'retention': 'retention-campaign',
  };
  return goalMap[v] || 'lead-nurturing';
}

function normaliseWhatsAppFramework(value: string): string {
  const v = String(value).toLowerCase().trim().replace(/\s+/g, '-');
  const frameworkMap: Record<string, string> = {
    'educational': 'educational', 'education': 'educational',
    'problem-solution': 'problem-solution', 'problem': 'problem-solution',
    'storytelling': 'storytelling', 'story': 'storytelling',
    'founder-authority': 'founder-authority', 'founder': 'founder-authority',
    'product-demonstration': 'product-demonstration', 'product': 'product-demonstration', 'demo': 'product-demonstration',
    'case-study': 'case-study', 'case': 'case-study',
  };
  return frameworkMap[v] || 'educational';
}

function normaliseWhatsAppTone(value: string): string {
  const v = String(value).toLowerCase().trim();
  const toneMap: Record<string, string> = {
    'professional': 'professional', 'formal': 'professional',
    'friendly': 'friendly', 'warm': 'friendly',
    'conversational': 'conversational', 'casual': 'conversational',
    'educational': 'educational', 'informative': 'educational',
    'motivational': 'motivational', 'inspiring': 'motivational',
    'persuasive': 'persuasive', 'convincing': 'persuasive',
    'luxury': 'luxury', 'premium': 'luxury',
    'corporate': 'corporate', 'business': 'corporate',
  };
  return toneMap[v] || 'friendly';
}

function normaliseWhatsAppPersonalization(value: string): string {
  const v = String(value).toLowerCase().trim();
  if (v.includes('advanc') || v.includes('high') || v.includes('deep')) return 'advanced';
  if (v.includes('basic') || v.includes('low') || v.includes('minimal')) return 'basic';
  return 'medium';
}

function normaliseWhatsAppMessageLength(value: string): string {
  const v = String(value).toLowerCase().trim();
  if (v.includes('short') || v.includes('brief') || v.includes('concise')) return 'short';
  if (v.includes('long') || v.includes('detailed') || v.includes('extended')) return 'long';
  return 'medium';
}

function normaliseWhatsAppFrequency(value: string): string {
  const v = String(value).toLowerCase().trim().replace(/\s+/g, '-');
  const freqMap: Record<string, string> = {
    '1x-daily': '1x-daily', 'daily': '1x-daily', 'once-daily': '1x-daily',
    '2x-daily': '2x-daily', 'twice-daily': '2x-daily',
    'every-other-day': 'every-other-day', 'alternate-day': 'every-other-day',
    '2x-weekly': '2x-weekly', 'twice-weekly': '2x-weekly',
    'weekly': 'weekly', '1x-weekly': 'weekly',
  };
  return freqMap[v] || '1x-daily';
}

function normaliseWhatsAppContentBlockType(value: string): string {
  if (!value) return 'testimonial';
  const v = String(value).toLowerCase().trim().replace(/\s+/g, '-');
  const validTypes = ['founder-story', 'customer-success', 'testimonial', 'case-study', 'product-feature', 'faq', 'offer', 'event-invitation', 'webinar-promotion', 'downloadable-resource', 'statistic', 'urgency'];
  if (validTypes.includes(v)) return v;
  // Map common variations
  const typeMap: Record<string, string> = {
    'story': 'founder-story', 'founder': 'founder-story',
    'success': 'customer-success', 'customer': 'customer-success',
    'review': 'testimonial', 'quote': 'testimonial',
    'feature': 'product-feature', 'product': 'product-feature',
    'question': 'faq', 'question-answer': 'faq',
    'discount': 'offer', 'promotion': 'offer', 'deal': 'offer',
    'event': 'event-invitation', 'invite': 'event-invitation',
    'webinar': 'webinar-promotion', 'workshop': 'webinar-promotion',
    'download': 'downloadable-resource', 'resource': 'downloadable-resource', 'ebook': 'downloadable-resource',
    'stat': 'statistic', 'data': 'statistic', 'number': 'statistic',
    'scarcity': 'urgency', 'deadline': 'urgency', 'limited': 'urgency',
  };
  return typeMap[v] || 'testimonial';
}

function normaliseWhatsAppMessageStatus(value: string): string {
  if (!value) return 'pending';
  const v = String(value).toLowerCase().trim();
  const statusMap: Record<string, string> = {
    'pending': 'pending', 'queued': 'pending',
    'sent': 'sent', 'delivered': 'delivered',
    'read': 'read', 'seen': 'read',
    'replied': 'replied', 'responded': 'replied',
    'failed': 'failed', 'error': 'failed',
  };
  return statusMap[v] || 'pending';
}

function normaliseWhatsAppTriggerEvent(value: string): string {
  if (!value) return 'new-lead';
  const v = String(value).toLowerCase().trim().replace(/\s+/g, '-');
  const eventMap: Record<string, string> = {
    'new-lead': 'new-lead', 'lead': 'new-lead',
    'form-submitted': 'form-submitted', 'form': 'form-submitted',
    'landing-page-signup': 'landing-page-signup', 'signup': 'landing-page-signup',
    'webinar-registration': 'webinar-registration', 'webinar': 'webinar-registration',
    'product-inquiry': 'product-inquiry', 'inquiry': 'product-inquiry',
    'consultation-request': 'consultation-request', 'consultation': 'consultation-request',
    'course-enrollment': 'course-enrollment', 'enrollment': 'course-enrollment',
  };
  return eventMap[v] || 'new-lead';
}

function normaliseWhatsAppExitCondition(value: any): string {
  if (!value) return 'sequence-completed';
  const v = String(typeof value === 'string' ? value : '').toLowerCase().trim().replace(/\s+/g, '-');
  const conditionMap: Record<string, string> = {
    'lead-converted': 'lead-converted', 'converted': 'lead-converted',
    'meeting-booked': 'meeting-booked', 'booked': 'meeting-booked',
    'customer-purchased': 'customer-purchased', 'purchased': 'customer-purchased',
    'sequence-completed': 'sequence-completed', 'completed': 'sequence-completed',
    'opted-out': 'opted-out', 'opt-out': 'opted-out', 'unsubscribe': 'opted-out',
  };
  return conditionMap[v] || 'sequence-completed';
}

function normaliseWhatsAppCampaignStatus(value: string): string {
  if (!value) return 'draft';
  const v = String(value).toLowerCase().trim();
  const statusMap: Record<string, string> = {
    'draft': 'draft', 'planning': 'planning',
    'generating': 'generating', 'ready': 'ready',
    'active': 'active', 'running': 'active', 'live': 'active',
    'paused': 'paused', 'suspended': 'paused',
    'completed': 'completed', 'finished': 'completed',
    'archived': 'archived',
  };
  return statusMap[v] || 'draft';
}

// ============================================
// Marketing Calendar Auto-Fill Mapping
// ============================================

export function computeMarketingCalendarAutoFillMapping(analysis: Record<string, any>): Record<string, any> {
  const mapping: Record<string, any> = {};

  // Seasonal Plan fields
  if (analysis.seasonalPlan) {
    const sp = analysis.seasonalPlan;
    if (sp.name) mapping.name = sp.name;
    if (sp.description) mapping.description = sp.description;
    if (sp.season) mapping.season = normaliseCalendarSeason(sp.season);
    if (sp.year) mapping.year = Number(sp.year);
    if (Array.isArray(sp.themes)) mapping.themes = sp.themes;
    if (Array.isArray(sp.targetChannels)) mapping.targetChannels = sp.targetChannels;
    if (sp.budgetAllocation && typeof sp.budgetAllocation === 'object') {
      mapping.budgetAllocation = sp.budgetAllocation;
    }
    if (Array.isArray(sp.keyDates)) mapping.keyDates = sp.keyDates;
    if (Array.isArray(sp.goals)) mapping.goals = sp.goals;
    mapping.status = 'draft';
  }

  // Calendar Events
  if (Array.isArray(analysis.calendarEvents)) {
    mapping.calendarEvents = analysis.calendarEvents.map((event: Record<string, any>) => {
      const eventMap: Record<string, any> = {};
      if (event.title) eventMap.title = event.title;
      if (event.description) eventMap.description = event.description;
      if (event.eventType) eventMap.eventType = normaliseCalendarEventType(event.eventType);
      if (event.priority) eventMap.priority = normaliseCalendarPriority(event.priority);
      if (event.status) eventMap.status = normaliseCalendarEventStatus(event.status);
      if (event.startDate) eventMap.startDate = event.startDate;
      if (event.endDate) eventMap.endDate = event.endDate;
      if (event.recurrence) eventMap.recurrence = normaliseCalendarRecurrence(event.recurrence);
      if (event.budgetAllocated) eventMap.budgetAllocated = Number(event.budgetAllocated);
      if (event.budgetActual) eventMap.budgetActual = Number(event.budgetActual);
      if (Array.isArray(event.channels)) eventMap.channels = event.channels;
      if (event.targetAudience) eventMap.targetAudience = event.targetAudience;
      if (Array.isArray(event.goals)) eventMap.goals = event.goals;
      if (Array.isArray(event.kpis)) eventMap.kpis = event.kpis;
      if (event.quarter) eventMap.quarter = event.quarter;
      if (Array.isArray(event.tags)) eventMap.tags = event.tags;
      if (!eventMap.status) eventMap.status = 'draft';
      return eventMap;
    });
  }

  // Timing Recommendations
  if (Array.isArray(analysis.timingRecommendations)) {
    mapping.timingRecommendations = analysis.timingRecommendations;
  }

  // Budget Plan
  if (analysis.budgetPlan) {
    mapping.budgetPlan = analysis.budgetPlan;
  }

  return mapping;
}

function normaliseCalendarSeason(value: any): string {
  if (!value) return 'q1';
  const v = String(typeof value === 'string' ? value : '').toLowerCase().trim();
  const seasonMap: Record<string, string> = {
    'spring': 'spring', 'summer': 'summer', 'autumn': 'autumn', 'fall': 'autumn', 'winter': 'winter',
    'q1': 'q1', 'q2': 'q2', 'q3': 'q3', 'q4': 'q4',
    'holiday': 'holiday', 'christmas': 'holiday', 'festive': 'holiday',
  };
  return seasonMap[v] || 'q1';
}

function normaliseCalendarEventType(value: any): string {
  if (!value) return 'campaign';
  const v = String(typeof value === 'string' ? value : '').toLowerCase().trim().replace(/\s+/g, '-');
  const typeMap: Record<string, string> = {
    'campaign': 'campaign', 'content': 'content', 'event': 'event',
    'product-launch': 'product-launch', 'launch': 'product-launch',
    'seasonal': 'seasonal', 'promotion': 'promotion', 'milestone': 'milestone',
    'other': 'other',
  };
  return typeMap[v] || 'campaign';
}

function normaliseCalendarPriority(value: any): string {
  if (!value) return 'medium';
  const v = String(typeof value === 'string' ? value : '').toLowerCase().trim();
  const priorityMap: Record<string, string> = {
    'critical': 'critical', 'urgent': 'critical', 'high': 'high',
    'medium': 'medium', 'normal': 'medium', 'low': 'low',
  };
  return priorityMap[v] || 'medium';
}

function normaliseCalendarEventStatus(value: any): string {
  if (!value) return 'draft';
  const v = String(typeof value === 'string' ? value : '').toLowerCase().trim();
  const statusMap: Record<string, string> = {
    'draft': 'draft', 'scheduled': 'scheduled',
    'in-progress': 'in-progress', 'in_progress': 'in-progress', 'active': 'in-progress',
    'completed': 'completed', 'finished': 'completed', 'done': 'completed',
    'cancelled': 'cancelled', 'canceled': 'cancelled',
  };
  return statusMap[v] || 'draft';
}

function normaliseCalendarRecurrence(value: any): string {
  if (!value) return 'none';
  const v = String(typeof value === 'string' ? value : '').toLowerCase().trim();
  const recurrenceMap: Record<string, string> = {
    'none': 'none', 'one-time': 'none', 'once': 'none',
    'daily': 'daily', 'weekly': 'weekly', 'bi-weekly': 'biweekly', 'biweekly': 'biweekly',
    'monthly': 'monthly', 'quarterly': 'quarterly', 'annually': 'annually', 'yearly': 'annually',
  };
  return recurrenceMap[v] || 'none';
}

// ============================================
// Marketing Channels Auto-Fill Mapping
// ============================================

export function computeMarketingChannelsAutoFillMapping(analysis: Record<string, any>): Record<string, any> {
  const mapping: Record<string, any> = {};

  // Channels
  if (Array.isArray(analysis.channels)) {
    mapping.channels = analysis.channels.map((channel: Record<string, any>) => {
      const channelMap: Record<string, any> = {};
      if (channel.name) channelMap.name = channel.name;
      if (channel.description) channelMap.description = channel.description;
      if (channel.channelType) channelMap.channelType = normaliseMarketingChannelType(channel.channelType);
      if (channel.platform) channelMap.platform = channel.platform;
      if (Array.isArray(channel.funnelStages)) {
        channelMap.funnelStages = channel.funnelStages.map((s: any) => normaliseMarketingFunnelStage(s));
      }
      if (Array.isArray(channel.targetAudienceRoles)) {
        channelMap.targetAudienceRoles = channel.targetAudienceRoles.map((r: any) => normaliseAudienceRole(r));
      }
      if (channel.budgetAllocated) channelMap.budgetAllocated = Number(channel.budgetAllocated);
      if (channel.budgetSpent) channelMap.budgetSpent = Number(channel.budgetSpent);
      if (channel.roi) channelMap.roi = Number(channel.roi);
      if (channel.metrics && typeof channel.metrics === 'object') {
        channelMap.metrics = {
          impressions: channel.metrics.impressions ? Number(channel.metrics.impressions) : undefined,
          clicks: channel.metrics.clicks ? Number(channel.metrics.clicks) : undefined,
          conversions: channel.metrics.conversions ? Number(channel.metrics.conversions) : undefined,
          revenue: channel.metrics.revenue ? Number(channel.metrics.revenue) : undefined,
          cpa: channel.metrics.cpa ? Number(channel.metrics.cpa) : undefined,
          ctr: channel.metrics.ctr ? Number(channel.metrics.ctr) : undefined,
        };
      }
      if (Array.isArray(channel.touchpoints)) {
        channelMap.touchpoints = channel.touchpoints.map((tp: Record<string, any>) => ({
          name: tp.name || '',
          type: normaliseTouchpointType(tp.type),
          stage: normaliseMarketingFunnelStage(tp.stage),
          description: tp.description || '',
          url: tp.url || '',
          audienceRole: tp.audienceRole ? normaliseAudienceRole(tp.audienceRole) : undefined,
          frequency: tp.frequency || '',
          budget: tp.budget ? Number(tp.budget) : undefined,
        }));
      }
      if (channel.priority) channelMap.priority = normaliseChannelPriority(channel.priority);
      if (channel.status) channelMap.status = normaliseChannelStatus(channel.status);
      if (Array.isArray(channel.tags)) channelMap.tags = channel.tags;
      if (!channelMap.status) channelMap.status = 'planned';
      if (!channelMap.priority) channelMap.priority = 'secondary';
      return channelMap;
    });
  }

  // Optimised Channels (from budget stage)
  if (Array.isArray(analysis.optimisedChannels)) {
    mapping.optimisedChannels = analysis.optimisedChannels.map((channel: Record<string, any>) => {
      const channelMap: Record<string, any> = {};
      if (channel.name) channelMap.name = channel.name;
      if (channel.channelType) channelMap.channelType = normaliseMarketingChannelType(channel.channelType);
      if (Array.isArray(channel.funnelStages)) {
        channelMap.funnelStages = channel.funnelStages.map((s: any) => normaliseMarketingFunnelStage(s));
      }
      if (channel.budgetAllocated) channelMap.budgetAllocated = Number(channel.budgetAllocated);
      if (channel.budgetSpent) channelMap.budgetSpent = Number(channel.budgetSpent);
      if (channel.roi) channelMap.roi = Number(channel.roi);
      if (channel.metrics && typeof channel.metrics === 'object') {
        channelMap.metrics = channel.metrics;
      }
      if (channel.priority) channelMap.priority = normaliseChannelPriority(channel.priority);
      if (channel.status) channelMap.status = normaliseChannelStatus(channel.status);
      return channelMap;
    });
  }

  // Channel Analysis
  if (analysis.channelAnalysis) mapping.channelAnalysis = analysis.channelAnalysis;

  // Funnel Allocation
  if (Array.isArray(analysis.funnelAllocation)) mapping.funnelAllocation = analysis.funnelAllocation;

  // Touchpoints
  if (Array.isArray(analysis.touchpoints)) mapping.touchpoints = analysis.touchpoints;

  // Customer Journey Map
  if (analysis.customerJourneyMap) mapping.customerJourneyMap = analysis.customerJourneyMap;

  // Budget Recommendations
  if (analysis.budgetRecommendations) mapping.budgetRecommendations = analysis.budgetRecommendations;

  return mapping;
}

function normaliseMarketingChannelType(value: any): string {
  if (!value) return 'other';
  const v = String(typeof value === 'string' ? value : '').toLowerCase().trim().replace(/\s+/g, '-');
  const typeMap: Record<string, string> = {
    'social-media': 'social-media', 'social': 'social-media', 'socialmedia': 'social-media',
    'email': 'email', 'seo': 'seo', 'search-engine-optimization': 'seo',
    'ppc': 'ppc', 'paid-ads': 'ppc', 'paid-advertising': 'ppc', 'sem': 'ppc',
    'content': 'content', 'content-marketing': 'content',
    'pr': 'pr', 'public-relations': 'pr',
    'events': 'events', 'event': 'events',
    'referral': 'referral', 'referrals': 'referral',
    'direct': 'direct', 'partnership': 'partnership', 'partnerships': 'partnership',
    'influencer': 'influencer', 'influencers': 'influencer',
    'podcast': 'podcast', 'podcasts': 'podcast',
    'video': 'video', 'video-marketing': 'video',
    'other': 'other',
  };
  return typeMap[v] || 'other';
}

function normaliseMarketingFunnelStage(value: any): string {
  if (!value) return 'awareness';
  const v = String(typeof value === 'string' ? value : '').toLowerCase().trim();
  const stageMap: Record<string, string> = {
    'awareness': 'awareness', 'aware': 'awareness', 'discovery': 'awareness', 'top-of-funnel': 'awareness', 'tofu': 'awareness',
    'consideration': 'consideration', 'consider': 'consideration', 'evaluation': 'consideration', 'middle-of-funnel': 'consideration', 'mofu': 'consideration',
    'conversion': 'conversion', 'convert': 'conversion', 'purchase': 'conversion', 'bottom-of-funnel': 'conversion', 'bofu': 'conversion',
    'retention': 'retention', 'retain': 'retention', 'loyalty': 'retention',
    'advocacy': 'advocacy', 'advocate': 'advocacy', 'referral': 'advocacy',
  };
  return stageMap[v] || 'awareness';
}

function normaliseAudienceRole(value: any): string {
  if (!value) return 'end-user';
  const v = String(typeof value === 'string' ? value : '').toLowerCase().trim().replace(/\s+/g, '-');
  const roleMap: Record<string, string> = {
    'decision-maker': 'decision-maker', 'decisionmaker': 'decision-maker', 'buyer': 'decision-maker',
    'influencer': 'influencer', 'champion': 'influencer',
    'end-user': 'end-user', 'enduser': 'end-user', 'user': 'end-user',
    'gatekeeper': 'gatekeeper',
  };
  return roleMap[v] || 'end-user';
}

function normaliseChannelPriority(value: any): string {
  if (!value) return 'secondary';
  const v = String(typeof value === 'string' ? value : '').toLowerCase().trim();
  const priorityMap: Record<string, string> = {
    'primary': 'primary', 'main': 'primary', 'core': 'primary',
    'secondary': 'secondary', 'supporting': 'secondary',
    'experimental': 'experimental', 'test': 'experimental', 'trial': 'experimental',
  };
  return priorityMap[v] || 'secondary';
}

function normaliseChannelStatus(value: any): string {
  if (!value) return 'planned';
  const v = String(typeof value === 'string' ? value : '').toLowerCase().trim();
  const statusMap: Record<string, string> = {
    'active': 'active', 'running': 'active', 'live': 'active',
    'paused': 'paused', 'suspended': 'paused', 'on-hold': 'paused',
    'planned': 'planned', 'planning': 'planned', 'upcoming': 'planned',
    'deprecated': 'deprecated', 'retired': 'deprecated', 'inactive': 'deprecated',
  };
  return statusMap[v] || 'planned';
}

function normaliseTouchpointType(value: any): string {
  if (!value) return 'other';
  const v = String(typeof value === 'string' ? value : '').toLowerCase().trim();
  const typeMap: Record<string, string> = {
    'ad': 'ad', 'advertisement': 'ad',
    'email': 'email', 'post': 'post', 'social-post': 'post',
    'page': 'page', 'landing-page': 'page',
    'event': 'event', 'call': 'call', 'phone-call': 'call',
    'video': 'video', 'other': 'other',
  };
  return typeMap[v] || 'other';
}