/**
 * RIE — Composite Engine
 *
 * Unified scoring engine that replaces the branching logic in composite-score.ts.
 * Uses strategy-defined weights, handles unavailable signals via redistribution,
 * and produces the final IntelligenceResult.
 */

import { SignalResult, IntelligenceResult, RagInsight, InputQuality, Grade, MatchupContext } from './types';
import { BaseStrategy } from './strategies/base.strategy';

function clamp(val: number, min: number, max: number): number {
  return Math.max(min, Math.min(max, val));
}

function getStormCategory(confidence: number, valueRating: number): number {
  if (confidence >= 0.80 && valueRating >= 8) return 5;
  if (confidence >= 0.70 && valueRating >= 7) return 4;
  if (confidence >= 0.60 && valueRating >= 5) return 3;
  if (confidence >= 0.50) return 2;
  return 1;
}

function gradeSignal(signal: SignalResult | undefined, criteria: (s: SignalResult) => Grade): Grade {
  if (!signal || !signal.available) return 'N/A';
  return criteria(signal);
}

function getSignalSupportScore(signal: SignalResult, matchupContext?: MatchupContext): number {
  if (signal.scoreSemantics === 'directional_home') {
    const winnerSide = matchupContext?.winnerSide || null;
    if (winnerSide === 'home') return clamp(signal.score, 0, 1);
    if (winnerSide === 'away') return clamp(1 - signal.score, 0, 1);
    return 0.5;
  }

  return clamp(signal.score, 0, 1);
}

function computeInputQuality(signals: SignalResult[]): InputQuality {
  const piff = signals.find(s => s.signalId === 'piff');
  const dvp = signals.find(s => s.signalId === 'dvp');
  const digimon = signals.find(s => s.signalId === 'digimon');
  const rag = signals.find(s => s.signalId === 'rag');

  const piffGrade = gradeSignal(piff, s => {
    const legs = s.rawData?.legCount || 0;
    const avgEdge = s.rawData?.avgEdge || 0;
    if (legs >= 5 && avgEdge >= 10) return 'A';
    if (legs >= 3) return 'B';
    if (legs >= 1) return 'C';
    return 'D';
  });

  const dvpGrade = gradeSignal(dvp, s => {
    const home = s.rawData?.home;
    const away = s.rawData?.away;
    if (home && away) return 'A';
    if (home || away) return 'B';
    return 'D';
  });

  const digimonGrade = gradeSignal(digimon, s => {
    const locks = s.rawData?.lockCount || 0;
    if (locks >= 3) return 'A';
    if (locks >= 1) return 'B';
    return 'C';
  });

  const ragGrade = gradeSignal(rag, s => {
    const chunks = s.rawData?.chunkCount || 0;
    if (chunks >= 6) return 'A';
    if (chunks >= 3) return 'B';
    if (chunks >= 1) return 'C';
    return 'D';
  });

  // Overall: average of available grades
  const gradeMap: Record<string, number> = { A: 4, B: 3, C: 2, D: 1, 'N/A': 0 };
  const grades = [piffGrade, dvpGrade, digimonGrade, ragGrade].filter(g => g !== 'N/A');
  const avgScore = grades.length > 0
    ? grades.reduce((s, g) => s + gradeMap[g], 0) / grades.length
    : 2;

  let overall: Grade;
  if (avgScore >= 3.5) overall = 'A';
  else if (avgScore >= 2.5) overall = 'B';
  else if (avgScore >= 1.5) overall = 'C';
  else overall = 'D';

  // odds grade is always at least B if we have live odds (assumed true at this stage)
  return {
    piff: piffGrade,
    dvp: dvpGrade,
    digimon: digimonGrade,
    odds: 'B',
    rag: ragGrade,
    overall,
  };
}

/**
 * Compute unified intelligence from collected signals + strategy.
 */
export function computeIntelligence(params: {
  signals: SignalResult[];
  strategy: BaseStrategy;
  ragInsights: RagInsight[];
  grokConfidence: number;
  grokValueRating: number;
  matchupContext?: MatchupContext;
}): IntelligenceResult {
  const { signals, strategy, ragInsights, grokConfidence, grokValueRating, matchupContext } = params;

  // 1. Inject Grok as a virtual signal
  const grokSignal: SignalResult = {
    signalId: 'grok',
    score: clamp(grokConfidence, 0, 1),
    weight: 0,
    available: true,
    rawData: { confidence: grokConfidence, valueRating: grokValueRating },
    metadata: { latencyMs: 0, source: 'api', freshness: new Date().toISOString() },
  };

  const allSignals = [grokSignal, ...signals];

  // 2. Assign weights from strategy
  const weightedSignals = strategy.assignWeights(allSignals);
  const effectiveWeightedSignals = weightedSignals.map((signal) => ({
    ...signal,
    score: getSignalSupportScore(signal, matchupContext),
  }));
  const baseStrategyProfile = strategy.getProfile();
  const missingRequiredSignals = strategy.getMissingRequiredSignals(weightedSignals);
  const requirementsMet = missingRequiredSignals.length === 0;

  // 3. Compute base composite (handles redistribution from unavailable signals)
  const baseComposite = requirementsMet
    ? strategy.computeComposite(effectiveWeightedSignals)
    : 0.5;

  // 4. Apply sport-specific adjustments
  const adjustedComposite = requirementsMet
    ? strategy.adjustComposite(baseComposite, weightedSignals, ragInsights, matchupContext)
    : 0.5;
  const finalConfidence = clamp(adjustedComposite, 0, 1);

  // 5. Storm category
  const stormCategory = getStormCategory(finalConfidence, grokValueRating);

  // 6. Edge breakdown
  const edgeBreakdown: Record<string, { score: number; weight: number; contribution: number }> = {};
  const availableWeightedSignals = effectiveWeightedSignals.filter(s => s.available && s.weight > 0);
  const totalWeight = availableWeightedSignals.reduce((s, sig) => s + sig.weight, 0);

  for (const sig of effectiveWeightedSignals) {
    const effectiveWeight = sig.available && totalWeight > 0 ? sig.weight / totalWeight : 0;
    edgeBreakdown[sig.signalId] = {
      score: Math.round(sig.score * 1000) / 1000,
      weight: Math.round(effectiveWeight * 1000) / 1000,
      contribution: Math.round(sig.score * effectiveWeight * 1000) / 1000,
    };
  }

  // 7. Input quality
  const inputQuality = computeInputQuality(weightedSignals);

  // 8. Strategy profile
  const strategyProfile = {
    ...baseStrategyProfile,
    requirementsMet,
    missingRequiredSignals,
  };

  return {
    compositeConfidence: Math.round(finalConfidence * 1000) / 1000,
    stormCategory,
    signals: weightedSignals,
    strategyProfile,
    ragInsights,
    edgeBreakdown,
    inputQuality,
    compositeVersion: 'rie-v1',
  };
}
