/* ============================================================
   VYNDR 2.0 — parlay correlation math (§12).
   Ported from the prototype's vyndr-parlay.jsx. Plain CommonJS so the
   Parlay Lab imports it (allowJs) AND Jest exercises it directly.

   NOTE: the BACKEND parlayService (Session 28) owns server-side combined
   odds + suggestions. This module is the FRONTEND correlation model that
   powers the Parlay Lab's live matrix + grade penalty.
   ============================================================ */

// Grade → representative American odds (the prototype's GRADE_ODDS).
const GRADE_ODDS = { 'A+': -135, A: 110, 'A-': 115, 'B+': 125, B: 135, 'B-': 150, C: 175, D: 240 };

function amToDec(am) {
  return am > 0 ? am / 100 + 1 : 100 / -am + 1;
}
function decToAm(dec) {
  return dec >= 2 ? Math.round((dec - 1) * 100) : Math.round(-100 / (dec - 1));
}
function fmtAmerican(am) {
  return (am > 0 ? '+' : '') + am;
}

/**
 * Pairwise correlation (§12): same player legs move together hardest,
 * then same team, then same league, then ~independent across sports.
 */
function getCorrelation(legA, legB) {
  if (!legA || !legB) return 0;
  if (legA.player && legA.player === legB.player) return 0.62;
  if (legA.team && legA.team === legB.team && legA.sport === legB.sport) return 0.34;
  if (legA.sport && legA.sport === legB.sport) return 0.06;
  return 0;
}

/** Highest pairwise correlation across the slip (the worst hidden drag). */
function maxCorrelation(legs) {
  let max = 0;
  for (let i = 0; i < legs.length; i++) {
    for (let j = i + 1; j < legs.length; j++) {
      max = Math.max(max, getCorrelation(legs[i], legs[j]));
    }
  }
  return max;
}

/** All correlated pairs as [i, j, correlation]. */
function correlationPairs(legs) {
  const pairs = [];
  for (let i = 0; i < legs.length; i++) {
    for (let j = i + 1; j < legs.length; j++) {
      pairs.push([i, j, getCorrelation(legs[i], legs[j])]);
    }
  }
  return pairs;
}

const GRADE_ORDER = ['A+', 'A', 'B+', 'B', 'C', 'D'];

/**
 * Combined parlay grade. Averages the leg grades, then bumps the slip DOWN
 * one tier when any pair is strongly correlated (>0.4) — correlated stacks
 * are riskier than the books' independent pricing implies.
 */
function parlayGrade(legs) {
  if (!legs || !legs.length) return '—';
  const avg = legs.reduce((s, l) => s + Math.max(0, GRADE_ORDER.indexOf(String(l.grade || 'B').replace('-', ''))), 0) / legs.length;
  const penalty = maxCorrelation(legs) > 0.4 ? 1 : 0;
  const idx = Math.min(GRADE_ORDER.length - 1, Math.round(avg + penalty));
  return GRADE_ORDER[idx];
}

/** Combined decimal odds (product of per-leg decimal odds). */
function combinedDecimal(legs) {
  return legs.reduce((d, l) => d * amToDec(GRADE_ODDS[l.grade] != null ? GRADE_ODDS[l.grade] : 110), 1);
}

/** Combined odds as an American string, or "—" for an empty slip. */
function combinedAmerican(legs) {
  if (!legs || !legs.length) return '—';
  return fmtAmerican(decToAm(combinedDecimal(legs)));
}

module.exports = {
  GRADE_ODDS,
  GRADE_ORDER,
  amToDec,
  decToAm,
  fmtAmerican,
  getCorrelation,
  maxCorrelation,
  correlationPairs,
  parlayGrade,
  combinedDecimal,
  combinedAmerican,
};
