/**
 * Pure de-vig helpers for the Value Board (no I/O, unit-testable).
 *
 * The bookmaker's decimal odds encode probability + margin. De-vigging removes
 * the margin: p_i = (1/odds_i) / Σ(1/odds_j). The result is the market's
 * "fair" probability — empirically calibrated to <2% ECE (see DATA_FINDINGS.md).
 * We NEVER fabricate numbers: if a market leg is missing/placeholder, return null.
 */

export type OddsBlob = Record<string, Record<string, number | string>>;

/** Coerce a raw odds value (number or numeric string) to a finite number > 1.01. */
function toOdd(v: number | string | undefined): number | null {
  if (v === undefined || v === null) return null;
  const n = typeof v === "number" ? v : parseFloat(String(v));
  return Number.isFinite(n) && n > 1.01 ? n : null;
}

/** Vig-removed probabilities for a group of selections, in the given key order. */
export function devig(
  market: Record<string, number | string> | undefined,
  keys: string[],
): number[] | null {
  if (!market) return null;
  const odds = keys.map((k) => toOdd(market[k]));
  if (odds.some((o) => o === null)) return null;
  const inv = (odds as number[]).map((o) => 1 / o);
  const sum = inv.reduce((a, b) => a + b, 0);
  if (sum <= 0) return null;
  return inv.map((x) => x / sum);
}

/** Bookmaker margin (overround) for a market, as a fraction (e.g. 0.19 = 19%). */
export function overround(
  market: Record<string, number | string> | undefined,
  keys: string[],
): number | null {
  if (!market) return null;
  const odds = keys.map((k) => toOdd(market[k]));
  if (odds.some((o) => o === null)) return null;
  return (odds as number[]).reduce((a, o) => a + 1 / o, 0) - 1;
}

export interface ScoreDistribution {
  topScores: { score: string; prob: number }[];
  expectedGoals: number | null;
}

/**
 * De-vig the "Maç Skoru" (correct-score) market into a calibrated score
 * distribution. Normalises over the EXPLICIT scorelines (ignoring the "Diğer"
 * bucket for the displayed picks) and derives expected total goals from them.
 */
export function scoreDistribution(
  market: Record<string, number | string> | undefined,
  topN = 3,
): ScoreDistribution {
  const empty: ScoreDistribution = { topScores: [], expectedGoals: null };
  if (!market) return empty;
  const explicit: { score: string; inv: number; goals: number }[] = [];
  for (const [score, raw] of Object.entries(market)) {
    const m = /^(\d+)-(\d+)$/.exec(score);
    const odd = toOdd(raw);
    if (!m || odd === null) continue; // skip "Diğer" and bad legs
    explicit.push({
      score,
      inv: 1 / odd,
      goals: parseInt(m[1], 10) + parseInt(m[2], 10),
    });
  }
  if (explicit.length === 0) return empty;
  const sum = explicit.reduce((a, e) => a + e.inv, 0);
  if (sum <= 0) return empty;
  const withProb = explicit
    .map((e) => ({ score: e.score, prob: e.inv / sum, goals: e.goals }))
    .sort((a, b) => b.prob - a.prob);
  const expectedGoals = withProb.reduce((a, e) => a + e.prob * e.goals, 0);
  return {
    topScores: withProb
      .slice(0, topN)
      .map((e) => ({ score: e.score, prob: e.prob })),
    expectedGoals: Math.round(expectedGoals * 100) / 100,
  };
}

export function isCupLeague(name: string | null | undefined): boolean {
  const n = (name || "").toLowerCase();
  return ["kupa", "cup", "trophy"].some((w) => n.includes(w));
}
