const DISTRIBUTION_SHAPES = {
  points: 'normal',
  rebounds: 'normal',
  assists: 'normal',
  home_runs: 'negative_binomial',
  stolen_bases: 'negative_binomial',
  pitcher_strikeouts: 'bimodal_mixture',
  walks: 'poisson',
  hits: 'normal',
  total_bases: 'normal',
  rbis: 'normal',
  runs_scored: 'poisson',
  strikeouts_batter: 'poisson',
  earned_runs: 'poisson',
  outs_recorded: 'normal',
  walks_allowed: 'poisson',
  hits_allowed: 'normal',
  pitches_thrown: 'normal',
};

/**
 * Get the distribution shape for a stat type.
 * @param {string} statType
 * @returns {string} Distribution shape name
 */
function getDistributionShape(statType) {
  return DISTRIBUTION_SHAPES[statType] || 'normal';
}

/**
 * Normal CDF using rational approximation.
 */
function normalCDF(x, mean, stddev) {
  if (stddev <= 0) return x >= mean ? 1 : 0;
  const z = (x - mean) / stddev;
  const t = 1 / (1 + 0.2316419 * Math.abs(z));
  const d = 0.3989422804014327;
  const p = d * Math.exp(-z * z / 2) *
    (t * (0.3193815 + t * (-0.3565638 + t * (1.781478 + t * (-1.8212560 + t * 1.3302744)))));
  return z > 0 ? 1 - p : p;
}

/**
 * Poisson CDF: P(X <= x) for Poisson(lambda).
 * @param {number} x - Value (floored to integer)
 * @param {number} lambda - Rate parameter
 * @returns {number} Cumulative probability
 */
function poissonCDF(x, lambda) {
  if (lambda <= 0) return 1;
  const k = Math.floor(x);
  if (k < 0) return 0;

  let cdf = 0;
  let term = Math.exp(-lambda);
  cdf += term;

  for (let i = 1; i <= k; i++) {
    term *= lambda / i;
    cdf += term;
  }

  return Math.min(1, cdf);
}

/**
 * Negative Binomial CDF: P(X <= x) for NB(r, p).
 * Uses direct summation of PMF.
 * @param {number} x - Value (floored to integer)
 * @param {number} r - Number of successes
 * @param {number} p - Probability of success per trial
 * @returns {number} Cumulative probability
 */
function negativeBinomialCDF(x, r, p) {
  if (r <= 0 || p <= 0 || p > 1) return 0;
  const k = Math.floor(x);
  if (k < 0) return 0;

  let cdf = 0;

  // log of binomial coefficient using lgamma approximation
  function logGamma(z) {
    // Stirling approximation for lgamma
    if (z < 0.5) return Math.log(Math.PI / Math.sin(Math.PI * z)) - logGamma(1 - z);
    z -= 1;
    const coeffs = [
      76.18009172947146, -86.50532032941677, 24.01409824083091,
      -1.231739572450155, 0.001208650973866179, -0.000005395239384953,
    ];
    let x = 0.99999999999980993;
    for (let i = 0; i < coeffs.length; i++) {
      x += coeffs[i] / (z + i + 1);
    }
    const t = z + coeffs.length - 0.5;
    return 0.5 * Math.log(2 * Math.PI) + (z + 0.5) * Math.log(t) - t + Math.log(x);
  }

  for (let i = 0; i <= k; i++) {
    const logCoeff = logGamma(i + r) - logGamma(i + 1) - logGamma(r);
    const logProb = logCoeff + r * Math.log(p) + i * Math.log(1 - p);
    cdf += Math.exp(logProb);
  }

  return Math.min(1, Math.max(0, cdf));
}

/**
 * Calculate probability based on distribution shape.
 * @param {string} shape - Distribution type
 * @param {object} params - Distribution parameters
 * @param {number} line - Prop line
 * @param {string} direction - 'over' or 'under'
 * @returns {number} Probability 0-1
 */
function calculateProbability(shape, params, line, direction) {
  let cdf;

  switch (shape) {
    case 'normal':
      cdf = normalCDF(line, params.mean, params.stddev);
      break;
    case 'poisson':
      cdf = poissonCDF(line, params.lambda);
      break;
    case 'negative_binomial':
      cdf = negativeBinomialCDF(line, params.r, params.p);
      break;
    case 'bimodal_mixture':
      // Weighted mixture of two normals
      const w1 = params.weight1 || 0.5;
      const cdf1 = normalCDF(line, params.mean1, params.stddev1);
      const cdf2 = normalCDF(line, params.mean2, params.stddev2);
      cdf = w1 * cdf1 + (1 - w1) * cdf2;
      break;
    default:
      cdf = normalCDF(line, params.mean, params.stddev);
  }

  return direction === 'over' ? 1 - cdf : cdf;
}

module.exports = {
  DISTRIBUTION_SHAPES,
  getDistributionShape,
  calculateProbability,
  normalCDF,
  poissonCDF,
  negativeBinomialCDF,
};
