/**
 * Narrative Engine — Rainmaker Content Middleware
 *
 * Sits between raw forecast output and final content rendering.
 * Pipeline: classify → enrich → assemble → guardrail → render.
 *
 * Every forecast narrative passes through this engine to ensure:
 * - Situational variance alignment
 * - Confidence-tier tone matching
 * - Market-direction awareness
 * - Editorial guardrails (banned language, Rain Man naming, safe closes)
 * - Modular language assembly
 */

// ═══════════════════════════════════════════════════════════════
// TYPES
// ═══════════════════════════════════════════════════════════════

export type SpreadScenario = 'HEAVY_FAVORITE' | 'CLEAR_FAVORITE' | 'COIN_FLIP' | 'HEAVY_UNDERDOG';
export type VarianceLevel = 'LOW' | 'MODERATE' | 'HIGH' | 'CONTRADICTORY';
export type ConfidenceTier = 'A_PLUS' | 'A' | 'B_PLUS' | 'B' | 'B_MINUS' | 'BELOW_B';
export type MarketDirection = 'WITH_FORECAST' | 'AGAINST_FORECAST' | 'STABLE';
export type ContentType = 'SPREAD' | 'TOTAL' | 'HCW' | 'DVP' | 'SUMMARY' | 'SIGNAL' | 'BLOG';

export interface NarrativeContext {
  // Core forecast data
  homeTeam: string;
  awayTeam: string;
  league: string;
  // Market data
  marketSpread: number | null;           // home spread (negative = home favored)
  projectedMargin: number | null;        // model projected margin
  marketTotal: number | null;
  projectedTotal: number | null;
  // Confidence
  confidence: number;                    // 0-1 composite confidence
  valueRating: number;                   // 1-10
  stormCategory: number;                 // 1-5
  // Signals
  forecastSide: string;                  // team with value
  spreadEdge: number;                    // edge in points
  totalDirection: 'OVER' | 'UNDER' | 'NONE';
  totalEdge: number;
  // Optional enrichment data
  injuryUncertainty?: boolean;           // key players questionable/GTD
  recentFormInstability?: boolean;        // teams on losing/inconsistent streaks
  rosterChanges?: boolean;               // recent trades/roster moves
  sharpMovesDetected?: boolean;          // sharp/steam moves present
  marketMovedSinceOpen?: boolean;        // significant line movement
  marketMoveDirection?: 'TOWARD_FORECAST' | 'AWAY_FROM_FORECAST' | null;
}

export interface NarrativeClassification {
  spreadScenario: SpreadScenario;
  varianceLevel: VarianceLevel;
  confidenceTier: ConfidenceTier;
  marketDirection: MarketDirection;
  totalsAuditFlag: boolean;
}

export interface NarrativeMetadata extends NarrativeClassification {
  suggestedTone: string;
  suggestedClose: string;
  spreadLanguageSample: string;
  varianceLanguageSample: string;
  marketLanguageSample: string;
}

// ═══════════════════════════════════════════════════════════════
// CLASSIFIERS
// ═══════════════════════════════════════════════════════════════

/**
 * Classify the spread scenario based on the market spread magnitude.
 * Sport-aware thresholds.
 */
export function classifySpreadScenario(marketSpread: number | null, league: string): SpreadScenario {
  if (marketSpread == null) return 'COIN_FLIP';

  const absSpread = Math.abs(marketSpread);

  // Sport-specific thresholds
  const thresholds = getSpreadThresholds(league);

  if (absSpread >= thresholds.heavy) return marketSpread < 0 ? 'HEAVY_FAVORITE' : 'HEAVY_UNDERDOG';
  if (absSpread >= thresholds.clear) return marketSpread < 0 ? 'CLEAR_FAVORITE' : 'HEAVY_UNDERDOG';
  if (absSpread <= thresholds.coinFlip) return 'COIN_FLIP';
  return marketSpread < 0 ? 'CLEAR_FAVORITE' : 'HEAVY_UNDERDOG';
}

function getSpreadThresholds(league: string): { heavy: number; clear: number; coinFlip: number } {
  switch (league.toLowerCase()) {
    case 'nba':
    case 'ncaab':
      return { heavy: 8, clear: 4, coinFlip: 3 };
    case 'nfl':
    case 'ncaaf':
      return { heavy: 10, clear: 4, coinFlip: 3 };
    case 'nhl':
      return { heavy: 2.5, clear: 1.5, coinFlip: 1 };
    case 'mlb':
      return { heavy: 2.5, clear: 1.5, coinFlip: 1 };
    case 'epl':
    case 'la_liga':
    case 'bundesliga':
    case 'serie_a':
    case 'ligue_1':
    case 'champions_league':
      return { heavy: 2, clear: 1, coinFlip: 0.5 };
    default:
      return { heavy: 8, clear: 4, coinFlip: 3 };
  }
}

/**
 * Classify the variance level based on multiple situational factors.
 */
export function classifyVariance(ctx: NarrativeContext): VarianceLevel {
  let score = 0; // Higher score = higher variance

  // Edge proximity: weak edge = higher variance
  if (ctx.spreadEdge < 1) score += 3;
  else if (ctx.spreadEdge < 2) score += 2;
  else if (ctx.spreadEdge < 3) score += 1;

  // Confidence: lower confidence = higher variance
  if (ctx.confidence < 0.50) score += 3;
  else if (ctx.confidence < 0.60) score += 2;
  else if (ctx.confidence < 0.70) score += 1;

  // Market conflict: market moving against forecast
  if (ctx.marketMoveDirection === 'AWAY_FROM_FORECAST') score += 3;

  // Situational factors
  if (ctx.injuryUncertainty) score += 2;
  if (ctx.recentFormInstability) score += 1;
  if (ctx.rosterChanges) score += 1;

  // Contradictory check: market + forecast disagreement + injury issues
  if (ctx.marketMoveDirection === 'AWAY_FROM_FORECAST' && ctx.injuryUncertainty) {
    return 'CONTRADICTORY';
  }
  if (score >= 8 && ctx.marketMoveDirection === 'AWAY_FROM_FORECAST') {
    return 'CONTRADICTORY';
  }

  if (score >= 6) return 'HIGH';
  if (score >= 3) return 'MODERATE';
  return 'LOW';
}

/**
 * Classify confidence tier from composite confidence (0-1).
 */
export function classifyConfidenceTier(confidence: number): ConfidenceTier {
  if (confidence >= 0.82) return 'A_PLUS';
  if (confidence >= 0.72) return 'A';
  if (confidence >= 0.65) return 'B_PLUS';
  if (confidence >= 0.58) return 'B';
  if (confidence >= 0.50) return 'B_MINUS';
  return 'BELOW_B';
}

/**
 * Classify market direction relative to the forecast.
 */
export function classifyMarketDirection(ctx: NarrativeContext): MarketDirection {
  if (ctx.marketMoveDirection === 'TOWARD_FORECAST') return 'WITH_FORECAST';
  if (ctx.marketMoveDirection === 'AWAY_FROM_FORECAST') return 'AGAINST_FORECAST';

  // Infer from spread edge vs current market if no explicit direction
  if (ctx.marketSpread != null && ctx.projectedMargin != null) {
    const expectedMargin = -ctx.marketSpread;
    const deviation = Math.abs(ctx.projectedMargin - expectedMargin);
    if (deviation < 1) return 'STABLE';
  }

  return 'STABLE';
}

/**
 * Detect if totals output appears muted relative to context.
 */
export function shouldAuditTotals(ctx: NarrativeContext): boolean {
  if (ctx.marketTotal == null || ctx.projectedTotal == null) return false;
  const totalEdge = Math.abs(ctx.projectedTotal - ctx.marketTotal);
  // If projected total is extremely close to market despite other strong signals
  return totalEdge < 0.5 && ctx.confidence > 0.65;
}

/**
 * Run all classifiers and return a complete classification.
 */
export function classifyNarrative(ctx: NarrativeContext): NarrativeClassification {
  return {
    spreadScenario: classifySpreadScenario(ctx.marketSpread, ctx.league),
    varianceLevel: classifyVariance(ctx),
    confidenceTier: classifyConfidenceTier(ctx.confidence),
    marketDirection: classifyMarketDirection(ctx),
    totalsAuditFlag: shouldAuditTotals(ctx),
  };
}

// ═══════════════════════════════════════════════════════════════
// LANGUAGE PACKAGES
// ═══════════════════════════════════════════════════════════════

const SPREAD_LANGUAGE: Record<SpreadScenario, string[]> = {
  HEAVY_FAVORITE: [
    'that number looks inflated — the market may be overreacting to the name',
    'a spread this large invites regression — favorites rarely cover numbers this big consistently',
    'the market is pricing perfection from the favorite, which rarely materializes',
    'big numbers like this carry risk — one bad quarter and the cover evaporates',
    'the question isn\'t who wins, it\'s whether the favorite can sustain this margin',
  ],
  CLEAR_FAVORITE: [
    'the market leans one way, but is it pricing the right factors?',
    'the favorite has market backing, but the margin leaves room for doubt',
    'market consensus points here, though consensus isn\'t always right',
    'the spread reflects recent form — the question is whether recent form is predictive',
  ],
  COIN_FLIP: [
    'the market can\'t separate these two, and there may be a reason for that',
    'tight pricing usually means the edge is in the details, not the headline',
    'when the spread is this thin, situational factors become the swing',
    'the market sees a pick-em — the question is what it\'s missing',
    'razor-thin separation — this is where rest, travel, and matchup specifics matter most',
  ],
  HEAVY_UNDERDOG: [
    'the market is giving points here — is it enough, or is it an overreaction?',
    'a cushion this large usually means the market sees a talent gap, but talent gaps shrink with context',
    'the points look generous until you examine why the line is there',
    'getting points is nice, but the question is whether the gap is earned or assumed',
    'big underdogs cover more often than the market implies — is this one of those spots?',
  ],
};

const VARIANCE_LANGUAGE: Record<VarianceLevel, string[]> = {
  LOW: [
    'multiple independent signals converge here — that\'s uncommon and worth noting',
    'the data aligns across models, matchup metrics, and market structure',
    'low variance means the edge is measurable, not speculative',
    'when signals agree this cleanly, the market is usually slow to adjust',
  ],
  MODERATE: [
    'the data leans one way, but with enough noise to demand scrutiny',
    'there\'s a signal here, but it comes with conditions worth examining',
    'the edge exists on paper — the question is whether the conditions hold',
    'enough supporting evidence to take seriously, but not enough to ignore the risks',
  ],
  HIGH: [
    'high variance — the model sees something, but the confidence interval is wide',
    'the data is noisy here, which means the market price may be just as uncertain',
    'elevated variance suggests the true line could land anywhere in a wide range',
    'this is a spot where process matters more than conviction',
  ],
  CONTRADICTORY: [
    'signals are contradicting each other — proceed with healthy skepticism',
    'the data points in multiple directions, which usually means information is still developing',
    'conflicting signals often resolve closer to game time — patience may be rewarded',
    'when the model disagrees with itself, the market is usually mispricing uncertainty',
  ],
};

const MARKET_DIRECTION_LANGUAGE: Record<MarketDirection, string[]> = {
  WITH_FORECAST: [
    'the market is moving toward the model\'s read — the window may be closing',
    'sharp money appears aligned with this direction, which compresses the available edge',
    'the price has already started correcting — the inefficiency is shrinking',
    'market movement confirms the signal, but also reduces the remaining value',
  ],
  AGAINST_FORECAST: [
    'the market is moving the other way — either new information exists, or the price is improving',
    'contrary movement could mean the market knows something, or it could mean better entry timing',
    'when the market moves against the model, it\'s worth asking why before acting',
    'the price may improve if this movement continues — patience could be the sharper play',
    'check for late injury reports or lineup changes that could explain the shift',
  ],
  STABLE: [
    'the market hasn\'t moved, which means either everyone agrees or nobody is paying attention yet',
    'stable pricing suggests the market is comfortable with this number — is it right to be?',
    'no movement can itself be a signal — it means the current price isn\'t attracting sharp interest',
  ],
};

const CLOSING_LANGUAGE: Record<string, string[]> = {
  HIGH_CONFIDENCE_LOW_VARIANCE: [
    'the data is unusually aligned here — that kind of convergence is rare and worth examining',
    'when multiple independent signals agree, the market usually catches up eventually',
    'this is one of the cleaner setups on the board — the Forecast shows why',
  ],
  MODERATE_CONFIDENCE: [
    'the edge is there on paper — the Forecast breaks down whether the conditions support it',
    'a spot worth investigating further before drawing conclusions',
    'enough signal to warrant a closer look at the underlying factors',
  ],
  HIGH_VARIANCE: [
    'high variance means this could go either way — the Forecast quantifies the range',
    'the uncertainty here is the story — understanding it is more valuable than picking a side',
    'volatile setups reward discipline over conviction',
  ],
  MARKET_CONFLICT: [
    'the market and the model disagree — the Forecast breaks down who\'s more likely right',
    'conflicting signals mean information is still developing — check back closer to game time',
    'when market and model diverge, the edge usually lives in understanding why',
  ],
};

const TOTALS_LANGUAGE = {
  HIGH_SCORING: [
    'pace and defensive efficiency suggest the total may be set too low',
    'both teams have the offensive profile to push this number — is the market pricing that in?',
    'recent scoring trends point higher, but the question is whether the total already accounts for it',
    'the offensive matchup favors volume — check whether the total reflects that reality',
  ],
  LOW_SCORING: [
    'defensive matchup metrics suggest this total may be inflated',
    'scoring has been suppressed in this matchup profile — the market may be slow to adjust',
    'pace and shot quality point to a lower-scoring game than the number implies',
    'defensive structure on both sides suggests the total is worth questioning',
  ],
};

/**
 * Pick a random sample from a language package.
 */
function sample(arr: string[]): string {
  return arr[Math.floor(Math.random() * arr.length)];
}

/**
 * Get contextual closing language based on classification.
 */
export function getClosingLanguage(classification: NarrativeClassification): string {
  if (classification.marketDirection === 'AGAINST_FORECAST') {
    return sample(CLOSING_LANGUAGE.MARKET_CONFLICT);
  }
  if (classification.varianceLevel === 'HIGH' || classification.varianceLevel === 'CONTRADICTORY') {
    return sample(CLOSING_LANGUAGE.HIGH_VARIANCE);
  }
  if (classification.confidenceTier === 'A_PLUS' || classification.confidenceTier === 'A') {
    if (classification.varianceLevel === 'LOW') {
      return sample(CLOSING_LANGUAGE.HIGH_CONFIDENCE_LOW_VARIANCE);
    }
  }
  return sample(CLOSING_LANGUAGE.MODERATE_CONFIDENCE);
}

// ═══════════════════════════════════════════════════════════════
// EDITORIAL GUARDRAILS
// ═══════════════════════════════════════════════════════════════

const BANNED_HARD_CLAIMS: Array<[string, string]> = [
  // [pattern, replacement] — replacements preserve sentence flow
  ['mortal lock', 'strong signal'],
  ['lock of the day', 'top signal of the day'],
  ['lock of the week', 'top signal of the week'],
  ['lock of the night', 'top signal of the night'],
  ['lock', 'signal'],
  ['guarantees', 'projects'],
  ['guarantee', 'projection'],
  ['guaranteed', 'projected'],
  ['sure thing', 'strong setup'],
  ["can't miss", 'high-confidence signal'],
  ["can't-miss", 'high-confidence'],
  ['free money', 'strong edge'],
  ['automatic winner', 'strong lean'],
  ['no-brainer', 'clear signal'],
  ['no brainer', 'clear signal'],
  ['impossible to lose', 'well-supported setup'],
  ['slam dunk pick', 'strong signal'],
  ['slam dunk', 'strong'],
];

const LANGUAGE_REPLACEMENTS: Array<[RegExp, string]> = [
  [/\bbetting markets?\b/gi, 'market'],
  [/\bbetting lines?\b/gi, 'current market numbers'],
  [/\bbetting\b/gi, 'market'],
  [/\bbettors?\b/gi, 'market speculators'],
  [/\bsportsbooks?\b/gi, 'market venues'],
  [/\bbooks\b/gi, 'markets'],
  [/\bodds\b/gi, 'market price'],
  [/\bwagers?\b/gi, 'positions'],
  [/\bgambling\b/gi, 'forecasting'],
  [/\bline movement\b/gi, 'market movement'],
  [/\bbest bet\b/gi, 'top signal'],
];

/**
 * Remove banned hard-claim language, replacing with safer alternatives.
 */
export function removeBannedClaims(text: string): string {
  let result = text;
  for (const [banned, replacement] of BANNED_HARD_CLAIMS) {
    // Case-insensitive whole-word replacement with contextual alternative
    const regex = new RegExp(`\\b${banned.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b`, 'gi');
    result = result.replace(regex, replacement);
  }
  // Collapse multiple spaces
  return result.replace(/\s{2,}/g, ' ').trim();
}

/**
 * Replace overt wagering language with market language.
 */
export function replaceWageringLanguage(text: string): string {
  let result = text;
  for (const [pattern, replacement] of LANGUAGE_REPLACEMENTS) {
    result = result.replace(pattern, replacement);
  }
  return result;
}

/**
 * Enforce single "Rain Man" mention per paragraph/block.
 * After the first occurrence, replace subsequent uses with rotating substitutes.
 */
export function enforceSingleRainManReference(text: string): string {
  const substitutes = ['the model', 'the forecast', 'the signal', 'the analysis', 'the engine', 'RM'];
  let subIdx = 0;
  let firstSeen = false;

  // Split by paragraph boundaries
  const paragraphs = text.split(/(\n\n|\n(?=<)|(?<=>\n))/);

  return paragraphs.map(paragraph => {
    firstSeen = false;
    subIdx = 0;
    return paragraph.replace(/\bRain\s+Man\b/g, (match) => {
      if (!firstSeen) {
        firstSeen = true;
        return match;
      }
      const sub = substitutes[subIdx % substitutes.length];
      subIdx++;
      return sub;
    });
  }).join('');
}

/**
 * Soften absolute language (will → may, always → often, never → rarely).
 */
export function softenAbsoluteLanguage(text: string): string {
  return text
    .replace(/\bwill definitely\b/gi, 'is projected to')
    .replace(/\bwill certainly\b/gi, 'is likely to')
    .replace(/\bwill absolutely\b/gi, 'is expected to')
    .replace(/\bguaranteed to\b/gi, 'projected to')
    .replace(/\balways wins?\b/gi, 'has historically performed well')
    .replace(/\bnever lose[s]?\b/gi, 'rarely struggles')
    .replace(/\bno chance\b/gi, 'limited upside')
    .replace(/\bno way\b/gi, 'unlikely')
    .replace(/\babsolutely will\b/gi, 'is expected to')
    .replace(/\bwithout question\b/gi, 'with strong indicators');
}

/**
 * Ensure text ends with a suggestive/observational close rather than a forceful instruction.
 * Only modifies the last sentence if it contains forceful phrasing.
 */
export function ensureSuggestiveClose(text: string): string {
  if (!text || text.length < 20) return text;

  const forcefulEndings = [
    /\btake (the|this)\b/i,
    /\bbet (on|the)\b/i,
    /\bhammer (this|it)\b/i,
    /\bsmash (this|the)\b/i,
    /\bpound (this|the)\b/i,
    /\bjump on\b/i,
    /\bdon'?t miss\b/i,
    /\byou must\b/i,
    /\byou need to\b/i,
    /\byou should (definitely|absolutely)\b/i,
  ];

  const suggestiveCloses = [
    'Worth monitoring closely.',
    'A spot that deserves a closer look.',
    'One to handle carefully.',
    'Something to keep an eye on.',
    'A setup that may reward patience.',
  ];

  // Check the last sentence
  const sentences = text.split(/(?<=[.!?])\s+/);
  const lastSentence = sentences[sentences.length - 1] || '';

  for (const pattern of forcefulEndings) {
    if (pattern.test(lastSentence)) {
      sentences[sentences.length - 1] = suggestiveCloses[Math.floor(Math.random() * suggestiveCloses.length)];
      return sentences.join(' ');
    }
  }

  return text;
}

/**
 * Master editorial guardrail pass — runs all guardrails in sequence.
 */
export function editorialGuardrails(text: string): string {
  if (!text) return text;
  let result = text;
  result = removeBannedClaims(result);
  result = replaceWageringLanguage(result);
  result = enforceSingleRainManReference(result);
  result = softenAbsoluteLanguage(result);
  result = ensureSuggestiveClose(result);
  return result;
}

// ═══════════════════════════════════════════════════════════════
// TONE ALIGNMENT
// ═══════════════════════════════════════════════════════════════

/**
 * Check if text has overly aggressive conviction language that conflicts
 * with the confidence tier or variance level.
 */
export function alignToneToConfidence(text: string, tier: ConfidenceTier): string {
  if (!text) return text;

  // Below B+: suppress aggressive conviction language
  const belowBPlus = ['B', 'B_MINUS', 'BELOW_B'].includes(tier);

  if (belowBPlus) {
    return text
      .replace(/\bstrong(ly)?\s+(favor|lean|signal|edge|conviction)\b/gi, 'slight $2')
      .replace(/\bclear(ly)?\s+(favor|lean|prefer)\b/gi, 'modestly $2')
      .replace(/\boverwhelmingly?\b/gi, 'moderately')
      .replace(/\bdominant\s+edge\b/gi, 'modest edge')
      .replace(/\bcommanding\s+edge\b/gi, 'slight edge')
      .replace(/\bsignificant\s+edge\b/gi, 'workable edge')
      .replace(/\bhuge\s+edge\b/gi, 'modest edge');
  }

  return text;
}

/**
 * Override aggressive language when variance is high.
 */
export function alignToneToVariance(text: string, variance: VarianceLevel): string {
  if (!text) return text;

  if (variance === 'HIGH' || variance === 'CONTRADICTORY') {
    return text
      .replace(/\bstrong(ly)?\s+favor/gi, 'slightly lean')
      .replace(/\bclear edge\b/gi, 'marginal edge')
      .replace(/\bconviction play\b/gi, 'speculative look')
      .replace(/\bhigh-confidence\b/gi, 'cautious')
      .replace(/\bslam\b/gi, 'speculative');
  }

  return text;
}

/**
 * Inject market-direction awareness into text.
 */
export function alignToneToMarketDirection(text: string, direction: MarketDirection, tier: ConfidenceTier): string {
  if (!text) return text;

  // Market moving against forecast: patience overrides urgency
  if (direction === 'AGAINST_FORECAST') {
    return text
      .replace(/\bact (fast|quickly|now)\b/gi, 'consider waiting')
      .replace(/\bhurry\b/gi, 'be patient')
      .replace(/\block (this |it )?in\b/gi, 'monitor this')
      .replace(/\bjump on\b/gi, 'keep an eye on');
  }

  // Market moving with forecast: allow mild urgency only if confidence is high
  if (direction === 'WITH_FORECAST' && ['A_PLUS', 'A', 'B_PLUS'].includes(tier)) {
    // No modification needed — urgency is allowed
    return text;
  }

  return text;
}

// ═══════════════════════════════════════════════════════════════
// NARRATIVE ASSEMBLY
// ═══════════════════════════════════════════════════════════════

/**
 * Build the full narrative metadata for a forecast.
 * This metadata is stored alongside the forecast for frontend rendering.
 */
export function buildNarrativeMetadata(ctx: NarrativeContext): NarrativeMetadata {
  const classification = classifyNarrative(ctx);

  // Determine suggested tone
  let suggestedTone: string;
  if (classification.varianceLevel === 'HIGH' || classification.varianceLevel === 'CONTRADICTORY') {
    suggestedTone = 'cautious';
  } else if (classification.confidenceTier === 'A_PLUS' || classification.confidenceTier === 'A') {
    suggestedTone = 'measured confidence';
  } else if (classification.confidenceTier === 'B_PLUS') {
    suggestedTone = 'balanced';
  } else {
    suggestedTone = 'restrained';
  }

  // Priority rule: variance overrides confidence
  if (classification.varianceLevel === 'HIGH' && ['A_PLUS', 'A'].includes(classification.confidenceTier)) {
    suggestedTone = 'cautious despite signals';
  }

  // Priority rule: market contradiction overrides urgency
  if (classification.marketDirection === 'AGAINST_FORECAST') {
    suggestedTone = 'patient and investigative';
  }

  return {
    ...classification,
    suggestedTone,
    suggestedClose: getClosingLanguage(classification),
    spreadLanguageSample: sample(SPREAD_LANGUAGE[classification.spreadScenario]),
    varianceLanguageSample: sample(VARIANCE_LANGUAGE[classification.varianceLevel]),
    marketLanguageSample: sample(MARKET_DIRECTION_LANGUAGE[classification.marketDirection]),
  };
}

/**
 * Process all narrative text fields through the full pipeline.
 * This is the main post-processing entry point.
 */
export function processNarrativeFields(
  fields: {
    summary: string;
    spread_analysis: string;
    total_analysis: string;
    key_factors: string[];
    sharp_money_indicator: string;
    line_movement_analysis: string;
    weather_impact?: string;
    injury_notes?: string;
    historical_trend: string;
  },
  ctx: NarrativeContext,
): typeof fields {
  const classification = classifyNarrative(ctx);

  const process = (text: string): string => {
    if (!text) return text;
    let result = text;
    // 1. Editorial guardrails (banned language, Rain Man, wagering terms)
    result = editorialGuardrails(result);
    // 2. Confidence-tier tone alignment
    result = alignToneToConfidence(result, classification.confidenceTier);
    // 3. Variance tone alignment (overrides aggressive language when variance is high)
    result = alignToneToVariance(result, classification.varianceLevel);
    // 4. Market-direction tone alignment
    result = alignToneToMarketDirection(result, classification.marketDirection, classification.confidenceTier);
    return result;
  };

  return {
    summary: process(fields.summary),
    spread_analysis: process(fields.spread_analysis),
    total_analysis: process(fields.total_analysis),
    key_factors: fields.key_factors.map(f => process(f)),
    sharp_money_indicator: process(fields.sharp_money_indicator),
    line_movement_analysis: process(fields.line_movement_analysis),
    weather_impact: fields.weather_impact ? process(fields.weather_impact) : undefined,
    injury_notes: fields.injury_notes ? process(fields.injury_notes) : undefined,
    historical_trend: process(fields.historical_trend),
  };
}

// ═══════════════════════════════════════════════════════════════
// PROMPT INJECTION — Narrative Engine Rules for LLM Prompts
// ═══════════════════════════════════════════════════════════════

/**
 * Generate the Narrative Engine system prompt addendum.
 * Inject this into the LLM system prompt for forecast generation.
 */
export function getNarrativeSystemPromptAddendum(ctx: NarrativeContext): string {
  const classification = classifyNarrative(ctx);
  const meta = buildNarrativeMetadata(ctx);

  const parts: string[] = [];

  parts.push(`
NARRATIVE ENGINE DIRECTIVES (follow these strictly):

RAIN MAN NAMING DISCIPLINE:
- Use "Rain Man" only ONCE per paragraph or content block.
- After the first mention, rotate substitutes: RM, he, the model, the forecast, the signal, the analysis, the engine.
- NEVER repeat "Rain Man" multiple times in a short paragraph.`);

  // Confidence tier instructions
  parts.push(`
CONFIDENCE TIER: ${classification.confidenceTier} (composite: ${Math.round(ctx.confidence * 100)}%)
TONE DIRECTIVE: ${meta.suggestedTone}`);

  if (['B', 'B_MINUS', 'BELOW_B'].includes(classification.confidenceTier)) {
    parts.push(`- Below B+ confidence: Use heavily cautionary framing. Prefer coin-flip language. Avoid presenting as a meaningful stand.
- Use phrases like: "could go either way", "not much separation", "one to stay selective on"`);
  } else if (classification.confidenceTier === 'B_PLUS') {
    parts.push(`- B+ confidence: Clear preference allowed, but still acknowledge context and risk.`);
  } else {
    parts.push(`- A/A+ confidence: Strongest measured conviction allowed. Still NO absolutes. Reference multiple supporting signals.`);
  }

  // Variance level instructions
  parts.push(`
VARIANCE LEVEL: ${classification.varianceLevel}`);
  if (classification.varianceLevel === 'HIGH' || classification.varianceLevel === 'CONTRADICTORY') {
    parts.push(`- HIGH/CONTRADICTORY VARIANCE: Caution language OVERRIDES aggressive confidence language.
- Use restrained phrasing. Avoid forceful conviction.
- Suggest: "${sample(VARIANCE_LANGUAGE[classification.varianceLevel])}"`);
  }

  // Spread scenario instructions
  parts.push(`
SPREAD SCENARIO: ${classification.spreadScenario}`);
  parts.push(`- Appropriate spread language: "${sample(SPREAD_LANGUAGE[classification.spreadScenario])}"`);

  // Market direction instructions
  parts.push(`
MARKET DIRECTION: ${classification.marketDirection}`);
  if (classification.marketDirection === 'AGAINST_FORECAST') {
    parts.push(`- Market is moving AGAINST the forecast. Use patience language. Prompt verification behavior.
- Reduce forcefulness. Highlight the possibility of late-breaking information.
- Suggest: "${sample(MARKET_DIRECTION_LANGUAGE.AGAINST_FORECAST)}"`);
  } else if (classification.marketDirection === 'WITH_FORECAST') {
    parts.push(`- Market is moving WITH the forecast. Mild urgency is appropriate if confidence is B+ or higher.
- Suggest: "${sample(MARKET_DIRECTION_LANGUAGE.WITH_FORECAST)}"`);
  }

  // Totals audit
  if (classification.totalsAuditFlag) {
    parts.push(`
TOTALS AUDIT FLAG: The projected total is very close to market despite strong signals elsewhere. Review pace weighting, efficiency weighting, and recent scoring distributions. Provide stronger justification or adjust.`);
  }

  // Close instruction
  parts.push(`
EDITORIAL CLOSE: End with observational or cautionary language. Suggested: "${meta.suggestedClose}"
Do NOT end with forceful instruction or recommendation language.`);

  // Language compliance
  parts.push(`
LANGUAGE COMPLIANCE:
- NEVER use: lock, guarantee, sure thing, can't miss, free money, automatic winner, no-brainer
- Replace: "betting market" → "market", "bettors" → "speculators", "books" → "markets", "betting lines" → "current market numbers"
- Frame outputs as forecast, model signal, matchup outlook, or market edge — NOT as recommendations.`);

  return parts.join('\n');
}

/**
 * Generate enrichment instructions for HCW (Home Crowd + Weather) prompts.
 */
export function getHcwEnrichmentPrompt(): string {
  return `
SIGNAL ENRICHMENT RULES (follow these strictly):

For the ROAD team, prioritize analysis of:
- Road performance over the last 10 games
- Travel schedule and travel fatigue
- Rest disadvantage and back-to-back frequency
- Offensive output on the road vs at home
- Defensive stability away from home
- Pace changes away from home
- Time-zone or travel stress when applicable

For the HOME team, prioritize analysis of:
- Home performance over the last 10 games
- Comfort in venue and home crowd impact
- Offensive rhythm at home
- Defensive consistency at home
- Homestand continuity and rest advantage
- Familiarity and routine benefits

IMPORTANT: Add real analytical context. Do NOT simply restate the raw forecast.
Explain WHY home/road splits matter for THIS specific matchup.`;
}

/**
 * Generate enrichment instructions for DVP (Defense vs Position) prompts.
 */
export function getDvpEnrichmentPrompt(): string {
  return `
DVP ANALYSIS RULES (follow these strictly):

Do NOT merely print outputs. For each mismatch, explain:
- What stands out about the positional mismatch
- Where the defense struggles by role
- Where the offense is likely to exploit the matchup
- What the reader should look out for during the game
- Why the signal matters within the game environment
- Whether the mismatch feels stable or fragile based on matchup conditions

The DVP section must read like REAL ANALYSIS, not a data stub.
Be specific about positions, roles, and expected exploitation paths.`;
}

/**
 * Generate enrichment instructions for totals analysis.
 */
export function getTotalsEnrichmentPrompt(): string {
  return `
TOTALS ANALYSIS RULES (follow these strictly):

Totals copy must incorporate:
- Pace (possessions per game, tempo matchup)
- Offensive efficiency (points per possession, eFG%)
- Defensive efficiency (opponent points per possession)
- Recent scoring distributions (last 5-10 games)
- Venue effects (indoor/outdoor, altitude)
- Weather effects (for outdoor sports)
- Foul environment if applicable
- Possession volatility
- Recent form trends
- Market total comparison (model vs market)

Use language like:
- "this could turn into a high-scoring affair"
- "tempo could drive this one"
- "scoring may come at a premium"
- "this projects more like a grind"
- "defensive pressure could keep this one in check"
- "offensive rhythm may be the swing factor"`;
}

/**
 * Build the complete prompt addendum for blog/Rain Wire generation.
 */
export function getBlogNarrativePrompt(classification: NarrativeClassification): string {
  const parts: string[] = [];

  parts.push(`
NARRATIVE ENGINE — RAIN WIRE EDITORIAL RULES:

1. RAIN MAN NAMING: Use "Rain Man" only ONCE in the entire post. Rotate: RM, the model, the forecast, the signal, the analysis.

2. TONE ALIGNMENT (${classification.confidenceTier} confidence, ${classification.varianceLevel} variance):
${classification.varianceLevel === 'HIGH' || classification.varianceLevel === 'CONTRADICTORY'
    ? '- Use cautionary, restrained tone. Avoid implying strong conviction.'
    : classification.confidenceTier === 'A_PLUS' || classification.confidenceTier === 'A'
      ? '- Measured confidence allowed. Still hold back hard numbers for the Forecast.'
      : '- Balanced, observational tone. Present the matchup as worth exploring.'
  }

3. SPREAD CONTEXT (${classification.spreadScenario}):
${classification.spreadScenario === 'HEAVY_FAVORITE' ? '- Highlight the burden of the spread without implying conviction.' : ''}
${classification.spreadScenario === 'COIN_FLIP' ? '- Frame as a closely contested matchup with genuine uncertainty.' : ''}
${classification.spreadScenario === 'HEAVY_UNDERDOG' ? '- Note the cushion/margin for error without over-selling the underdog.' : ''}

4. MARKET DIRECTION (${classification.marketDirection}):
${classification.marketDirection === 'AGAINST_FORECAST' ? '- Hint at late developments or shifting dynamics without revealing the conflict.' : ''}
${classification.marketDirection === 'WITH_FORECAST' ? '- Build intrigue around market alignment without revealing specifics.' : ''}

5. BANNED LANGUAGE: No "lock", "guarantee", "sure thing", "can\'t miss", "free money". No "betting" language.

6. CLOSE: End the narrative portion with observational, curiosity-building language before the CTA.`);

  return parts.join('\n');
}

// ═══════════════════════════════════════════════════════════════
// PIPELINE — Full narrative processing pipeline
// ═══════════════════════════════════════════════════════════════

/**
 * Full narrative engine pipeline entry point.
 * Call this after forecast generation to process all narrative fields
 * and generate metadata for the frontend.
 */
export function runNarrativeEngine(
  forecastData: any,
  ctx: NarrativeContext,
): { processedFields: any; metadata: NarrativeMetadata } {
  // 1. Classify the narrative context
  const metadata = buildNarrativeMetadata(ctx);

  // 2. Process all narrative text fields
  const processedFields = processNarrativeFields({
    summary: forecastData.summary || '',
    spread_analysis: forecastData.spread_analysis || '',
    total_analysis: forecastData.total_analysis || '',
    key_factors: forecastData.key_factors || [],
    sharp_money_indicator: forecastData.sharp_money_indicator || '',
    line_movement_analysis: forecastData.line_movement_analysis || '',
    weather_impact: forecastData.weather_impact,
    injury_notes: forecastData.injury_notes,
    historical_trend: forecastData.historical_trend || '',
  }, ctx);

  // 3. Merge processed fields back into forecast data
  const processed = { ...forecastData, ...processedFields };

  return { processedFields: processed, metadata };
}
