type ForecastNarrativeContext = {
  homeTeam?: string | null;
  awayTeam?: string | null;
  homeShort?: string | null;
  awayShort?: string | null;
  league?: string | null;
  odds?: {
    spread?: {
      home?: { line?: number | null } | null;
      away?: { line?: number | null } | null;
    } | null;
  } | null;
};

function cleanText(value: any): string | null {
  if (typeof value !== 'string') return null;
  const trimmed = value.trim();
  return trimmed || null;
}

function toTeamShort(teamName: string | null, preferredShort: string | null | undefined): string | null {
  if (cleanText(preferredShort)) return cleanText(preferredShort);
  if (!teamName) return null;
  const parts = teamName.split(/\s+/).filter(Boolean);
  return parts.length > 0 ? parts[parts.length - 1].toUpperCase() : null;
}

function formatEdge(edge: any): string | null {
  if (typeof edge !== 'number' || !Number.isFinite(edge)) return null;
  const rounded = Math.round(edge * 10) / 10;
  const body = Number.isInteger(rounded) ? String(rounded) : rounded.toFixed(1);
  return `+${body}`;
}

function toNumber(value: any): number | null {
  if (value == null || value === '') return null;
  const parsed = Number(value);
  return Number.isFinite(parsed) ? parsed : null;
}

function round1(value: number): number {
  return Math.round(value * 10) / 10;
}

function sameTeam(left: string | null | undefined, right: string | null | undefined): boolean {
  const a = cleanText(left)?.toLowerCase();
  const b = cleanText(right)?.toLowerCase();
  return !!a && !!b && a === b;
}

function buildSpreadLabel(forecastSide: string | null, context: ForecastNarrativeContext): string | null {
  const homeLine = context.odds?.spread?.home?.line;
  if (typeof homeLine !== 'number' || !Number.isFinite(homeLine) || !forecastSide) return null;
  const isHome = sameTeam(forecastSide, context.homeTeam);
  const sideLine = isHome ? homeLine : -homeLine;
  const rounded = Math.round(sideLine * 10) / 10;
  const short = toTeamShort(
    isHome ? cleanText(context.homeTeam) : cleanText(context.awayTeam),
    isHome ? context.homeShort : context.awayShort,
  );
  const value = Number.isInteger(rounded) ? String(rounded) : rounded.toFixed(1);
  return short ? `${short} ${rounded > 0 ? '+' : ''}${value}` : `${rounded > 0 ? '+' : ''}${value}`;
}

export function reconcilePublicForecastProjection(
  forecastData: any,
  context: ForecastNarrativeContext = {},
): any {
  if (!forecastData || typeof forecastData !== 'object') return forecastData;

  const normalized = { ...forecastData };
  let projectedMargin = toNumber(normalized.projected_margin ?? normalized.projectedMargin);
  let projectedTotal = toNumber(normalized.projected_total_points ?? normalized.projectedTotalPoints);
  const homeScore = toNumber(normalized.projected_team_score_home ?? normalized.projectedTeamScoreHome);
  const awayScore = toNumber(normalized.projected_team_score_away ?? normalized.projectedTeamScoreAway);

  if (projectedMargin == null && homeScore != null && awayScore != null) {
    projectedMargin = round1(homeScore - awayScore);
  }
  if (projectedTotal == null && homeScore != null && awayScore != null) {
    projectedTotal = round1(homeScore + awayScore);
  }

  if (projectedMargin != null) {
    normalized.projected_margin = projectedMargin;
  }
  if (projectedTotal != null) {
    normalized.projected_total_points = projectedTotal;
  }

  if (projectedMargin != null && projectedTotal != null) {
    normalized.projected_team_score_home = round1((projectedTotal + projectedMargin) / 2);
    normalized.projected_team_score_away = round1((projectedTotal - projectedMargin) / 2);
  }

  if (projectedMargin != null) {
    const derivedWinner = projectedMargin > 0
      ? cleanText(context.homeTeam)
      : projectedMargin < 0
        ? cleanText(context.awayTeam)
        : null;
    if (derivedWinner) {
      normalized.winner_pick = derivedWinner;
      normalized.projected_winner = derivedWinner;
    } else if (!cleanText(normalized.projected_winner) && cleanText(normalized.winner_pick)) {
      normalized.projected_winner = cleanText(normalized.winner_pick);
    }
  }

  return normalized;
}

function getProjectionUnit(league: string | null | undefined): string {
  return (league || '').toLowerCase() === 'mlb' ? 'runs' : 'points';
}

function getEdgeUnit(league: string | null | undefined): string {
  return (league || '').toLowerCase() === 'mlb' ? 'runs' : 'pts';
}

function getSideMarketLabel(league: string | null | undefined): string {
  return (league || '').toLowerCase() === 'mlb' ? 'run line' : 'spread';
}

function buildSplitDecisionSummaryBody(forecastData: any, context: ForecastNarrativeContext): string | null {
  const projectedWinner = cleanText(forecastData.projected_winner || forecastData.projectedWinner || forecastData.winner_pick);
  const forecastSide = cleanText(forecastData.forecast_side || forecastData.forecastSide);
  if (!projectedWinner || !forecastSide || sameTeam(projectedWinner, forecastSide)) return null;

  const spreadLabel = buildSpreadLabel(forecastSide, context);
  const spreadEdge = formatEdge(forecastData.spread_edge);
  const edgeDescriptor = spreadEdge ? `${spreadEdge} ${getProjectionUnit(context.league)}` : 'the stronger number';
  const sideMarketLabel = getSideMarketLabel(context.league);
  const spreadDescriptor = spreadLabel ? ` at ${spreadLabel}` : '';

  return [
    `Outcome lean: ${projectedWinner}. Best value: ${forecastSide} against the ${sideMarketLabel}.`,
    `${projectedWinner} is still projected to win outright, but ${forecastSide}${spreadDescriptor} carries the cleaner cushion versus the modeled margin.`,
    `Treat this as a ${sideMarketLabel}-value call, not an outright winner flip. Current edge: ${edgeDescriptor}.`,
  ].join(' ');
}

function rebuildSummaryProjectionHeader(forecastData: any, context: ForecastNarrativeContext): any {
  if (!forecastData || typeof forecastData !== 'object') return forecastData;

  const projectedWinner = cleanText(forecastData.projected_winner || forecastData.projectedWinner || forecastData.winner_pick);
  const forecastSide = cleanText(forecastData.forecast_side || forecastData.forecastSide);
  const projectedMargin = toNumber(forecastData.projected_margin ?? forecastData.projectedMargin);
  const projectedTotal = toNumber(forecastData.projected_total_points ?? forecastData.projectedTotalPoints);
  const homeTeam = cleanText(context.homeTeam);
  const awayTeam = cleanText(context.awayTeam);
  const projectedLoser = projectedWinner === homeTeam ? awayTeam : projectedWinner === awayTeam ? homeTeam : null;

  if (!projectedWinner || !projectedLoser || projectedMargin == null || projectedTotal == null) {
    return forecastData;
  }

  const unit = getProjectionUnit(context.league);
  const header = [
    `Rain Man forecasts ${projectedWinner} to outscore ${projectedLoser} by ~${formatEdge(Math.abs(projectedMargin))?.slice(1) || round1(Math.abs(projectedMargin))} ${unit}.`,
    `Projected combined score: ~${Number.isInteger(round1(projectedTotal)) ? String(round1(projectedTotal)) : round1(projectedTotal).toFixed(1)} ${unit}.`,
    'Here\'s how Rain Man got there:',
  ].join('\n');

  const existingSummary = cleanText(forecastData.summary);
  const summaryBody = existingSummary
    ? existingSummary.replace(/^Rain Man forecasts[\s\S]*?Here's how Rain Man got there:\s*/i, '').trim()
    : '';
  const splitDecisionBody = buildSplitDecisionSummaryBody(forecastData, context);

  return {
    ...forecastData,
    summary: `${header}${[splitDecisionBody ?? summaryBody].filter(Boolean).length > 0 ? ` ${[splitDecisionBody ?? summaryBody].filter(Boolean).join(' ')}` : ''}`,
  };
}

export function normalizePublicForecastNarrative(
  forecastData: any,
  context: ForecastNarrativeContext = {},
): any {
  if (!forecastData || typeof forecastData !== 'object') return forecastData;

  const reconciled = reconcilePublicForecastProjection(forecastData, context);
  const normalized = rebuildSummaryProjectionHeader(reconciled, context);
  const forecastSide = cleanText(normalized.forecast_side || normalized.forecastSide);
  const projectedWinner = cleanText(normalized.projected_winner || normalized.projectedWinner || normalized.winner_pick);
  const splitDecisionBody = buildSplitDecisionSummaryBody(normalized, context);

  if (
    splitDecisionBody
    && typeof normalized.summary === 'string'
    && normalized.summary.trim()
    && !normalized.summary.includes(splitDecisionBody)
  ) {
    const hasProjectionHeader = /^Rain Man forecasts[\s\S]*?Here's how Rain Man got there:/i.test(normalized.summary);
    normalized.summary = hasProjectionHeader
      ? normalized.summary.replace(
          /Here's how Rain Man got there:\s*[\s\S]*$/i,
          `Here's how Rain Man got there: ${splitDecisionBody}`,
        )
      : splitDecisionBody;
  }

  if (!Array.isArray(normalized.clip_metadata) || normalized.clip_metadata.length === 0) {
    return normalized;
  }

  const awayShort = toTeamShort(cleanText(context.awayTeam), context.awayShort);
  const homeShort = toTeamShort(cleanText(context.homeTeam), context.homeShort);
  const displayContext =
    awayShort && homeShort
      ? `${awayShort} @ ${homeShort}`
      : cleanText(context.awayTeam) && cleanText(context.homeTeam)
        ? `${cleanText(context.awayTeam)} @ ${cleanText(context.homeTeam)}`
        : null;
  const spreadEdge = formatEdge(normalized.spread_edge);
  const edgeUnit = getEdgeUnit(context.league);
  const forecastSideShort =
    sameTeam(forecastSide, context.homeTeam)
      ? homeShort
      : sameTeam(forecastSide, context.awayTeam)
        ? awayShort
        : null;
  const spreadLabel = buildSpreadLabel(forecastSide, context);

  normalized.clip_metadata = normalized.clip_metadata.map((entry: any) => {
    if (!entry || typeof entry !== 'object') return entry;
    const clip = { ...entry, clip_data: { ...(entry.clip_data || {}) } };

    if (forecastSide) {
      clip.clip_data.forecast_side = forecastSide;
    }
    if (projectedWinner) {
      clip.clip_data.winner = projectedWinner;
      clip.clip_data.projected_winner = projectedWinner;
      clip.clip_data.winner_pick = projectedWinner;
    }
    if (displayContext) {
      clip.clip_data.display_context = displayContext;
    }

    if (clip.clip_type === 'GAME_FORECAST' && displayContext && forecastSide && projectedWinner) {
      clip.display_text = `${displayContext} | Value: ${forecastSideShort || forecastSide} | Edge ${spreadEdge || '+0'} ${edgeUnit} | Winner: ${projectedWinner}`;
    }

    if (clip.clip_type === 'SPREAD' && displayContext && forecastSide) {
      const spreadBody = spreadLabel || forecastSide;
      const margin = typeof normalized.projected_margin === 'number'
        ? Math.round(normalized.projected_margin * 10) / 10
        : null;
      clip.display_text = `${displayContext} | Forecast: ${spreadBody} | Model margin: ${margin ?? '?'} ${edgeUnit} | Edge ${spreadEdge || '+0'} ${edgeUnit}`;
    }

    return clip;
  });

  return normalized;
}
