/**
 * P(Over) — estimated probability that the player goes over the line.
 *
 * This is the *quantile-based* probability we surface to users ("73%
 * chance over") and feed into Engine 2's prompt. It is NOT the implied
 * probability from the book — that's odds-derived and includes vig. This
 * one is from the player's actual distribution.
 *
 * Formula layers:
 *   1. Base — empirical frequency of stat > line across the sample
 *   2. Recency — last 5 games weighted 2× to capture trend
 *   3. Opponent — bump for weak D, fade for top D (uses 0..1 opp_rank_stat)
 *   4. Home / away — +1.5% / -1.5%
 *   5. Consistency — volatile players get pulled toward 0.50
 *
 * Clamp at [0.10, 0.95] — we never claim certainty in either direction.
 */

const CV_VOLATILE_THRESHOLD = 0.40;
const PROB_FLOOR = 0.10;
const PROB_CEIL = 0.95;

function statFromRow(row, statType) {
  if (!row) return null;
  switch (statType) {
    case 'pts_reb_ast':
      return (Number(row.points) || 0) + (Number(row.rebounds) || 0) + (Number(row.assists) || 0);
    case 'pts_reb':
      return (Number(row.points) || 0) + (Number(row.rebounds) || 0);
    case 'pts_ast':
      return (Number(row.points) || 0) + (Number(row.assists) || 0);
    case 'reb_ast':
      return (Number(row.rebounds) || 0) + (Number(row.assists) || 0);
    case 'stl_blk':
      return (Number(row.steals) || 0) + (Number(row.blocks) || 0);
    default: {
      const v = Number(row[statType]);
      return Number.isFinite(v) ? v : null;
    }
  }
}

function frequencyOver(values, line) {
  const decisive = values.filter((v) => v !== line); // push games don't count
  if (decisive.length === 0) return null;
  const over = decisive.filter((v) => v > line).length;
  return over / decisive.length;
}

function clamp(p) {
  return Math.max(PROB_FLOOR, Math.min(PROB_CEIL, p));
}

function estimateProbability({ gameLogs = [], line, statType, features = {} } = {}) {
  if (!Array.isArray(gameLogs) || gameLogs.length === 0 || !Number.isFinite(Number(line))) {
    return { p_over: null, p_under: null, components: {}, reason: 'insufficient_data' };
  }
  const numericLine = Number(line);
  const values = gameLogs.map((r) => statFromRow(r, statType)).filter((v) => v != null);
  if (values.length === 0) {
    return { p_over: null, p_under: null, components: {}, reason: 'no_stat_values' };
  }

  const base = frequencyOver(values, numericLine);
  if (base == null) return { p_over: null, p_under: null, components: {}, reason: 'all_pushes' };

  // Recency: last 5 games count 2× in a weighted blend.
  const recent = values.slice(0, Math.min(5, values.length));
  const recencyRate = frequencyOver(recent, numericLine);
  const weighted = recencyRate != null
    ? 0.6 * base + 0.4 * recencyRate
    : base;

  let p = weighted;

  // Opponent adjustment using 0..1 normalized rank.
  //   opp_rank_stat ≥ 0.70 → weak defense, bump toward over
  //   opp_rank_stat ≤ 0.30 → strong defense, fade
  const oppAdj = (() => {
    const r = Number(features.opp_rank_stat);
    if (!Number.isFinite(r)) return 0;
    if (r >= 0.70) return +0.03;
    if (r <= 0.30) return -0.03;
    return 0;
  })();
  p += oppAdj;

  const homeAdj = features.home_away === 1.0 ? +0.015 : features.home_away === 0.0 ? -0.015 : 0;
  p += homeAdj;

  // Consistency pull: volatile players are uncertain — drag p toward 0.50.
  const cv = Number(features.l10_stddev) > 0 && Number(features.l20_avg) > 0
    ? Number(features.l10_stddev) / Number(features.l20_avg)
    : null;
  const consistencyAdj = (() => {
    if (!Number.isFinite(cv)) return null;
    if (cv > CV_VOLATILE_THRESHOLD) {
      // p' = p * 0.9 + 0.5 * 0.1
      const before = p;
      p = p * 0.9 + 0.05;
      return p - before;
    }
    return 0;
  })();

  const pOver = clamp(p);
  return {
    p_over: pOver,
    p_under: 1 - pOver,
    components: {
      base,
      recency: recencyRate,
      weighted,
      opp_adjustment: oppAdj,
      home_adjustment: homeAdj,
      consistency_adjustment: consistencyAdj,
      cv,
    },
  };
}

module.exports = {
  estimateProbability,
  __internals: { statFromRow, frequencyOver, clamp, CV_VOLATILE_THRESHOLD, PROB_FLOOR, PROB_CEIL },
};
