/**
 * roleProfileEngine.js
 * Role profiling and classification engine for player analysis.
 * Estimates what basketball role(s) a player fills and detects shifts.
 */

const ROLE_TAXONOMY = [
  'PRIMARY_BALL_HANDLER',
  'SECONDARY_PLAYMAKER',
  'CATCH_SHOOT_SPACER',
  'OFF_BALL_CUTTER',
  'FLOOR_RAISER',
  'SWITCHABLE_DEFENDER',
  'PAINT_PRESENCE',
  'CONNECTOR',
];

const CONDITIONAL_KEYS = [
  'star_out',
  'losing_10_plus',
  'foul_trouble',
  'closing_lineup',
  'winning_15_plus',
];

/**
 * Shannon entropy normalized to 0-1 range.
 * 0 = single role, 1 = equally distributed across all active roles.
 *
 * H = -sum(p_i * log2(p_i)) for all p_i > 0
 * Normalized: H / log2(n) where n = number of non-zero roles
 *
 * @param {Object} roleProfile — keys are role names, values are weights (should sum to ~1)
 * @returns {number} role_variance_score in [0, 1]
 */
function calculateRoleVariance(roleProfile) {
  const weights = Object.values(roleProfile).filter((w) => w > 0);
  const n = weights.length;

  if (n <= 1) return 0;

  const total = weights.reduce((sum, w) => sum + w, 0);
  if (total === 0) return 0;

  // Normalize to probabilities
  const probs = weights.map((w) => w / total);

  // Shannon entropy
  const H = -probs.reduce((sum, p) => {
    return sum + (p > 0 ? p * Math.log2(p) : 0);
  }, 0);

  // Normalize by max possible entropy for n categories
  const maxH = Math.log2(n);
  if (maxH === 0) return 0;

  return Math.min(1, Math.max(0, H / maxH));
}

/**
 * Returns the dominant (highest-weight) role from a profile.
 * @param {Object} roleProfile
 * @returns {string|null} role key with highest weight, or null if empty
 */
function getDominantRole(roleProfile) {
  if (!roleProfile || Object.keys(roleProfile).length === 0) return null;

  let maxKey = null;
  let maxVal = -Infinity;

  for (const [key, val] of Object.entries(roleProfile)) {
    if (val > maxVal) {
      maxVal = val;
      maxKey = key;
    }
  }

  return maxKey;
}

/**
 * Detect whether tonight's role profile represents a meaningful elevation
 * from the player's baseline.
 *
 * @param {Object} baseProfile — season/rolling baseline role distribution
 * @param {Object} tonightProfile — tonight's role distribution
 * @param {number} threshold — delta above which we flag elevation (default 0.20)
 * @returns {{ elevated: boolean, elevatedRole: string|null, delta: number }}
 */
function detectRoleElevation(baseProfile, tonightProfile, threshold = 0.20) {
  const baseDominant = getDominantRole(baseProfile);
  const tonightDominant = getDominantRole(tonightProfile);

  if (!baseDominant || !tonightDominant) {
    return { elevated: false, elevatedRole: null, delta: 0 };
  }

  // Find the role with the largest positive shift from base to tonight
  let maxDelta = 0;
  let elevatedRole = null;

  for (const role of Object.keys(tonightProfile)) {
    const baseWeight = baseProfile[role] || 0;
    const tonightWeight = tonightProfile[role] || 0;
    const delta = tonightWeight - baseWeight;

    if (delta > maxDelta) {
      maxDelta = delta;
      elevatedRole = role;
    }
  }

  const elevated = maxDelta > threshold;

  return {
    elevated,
    elevatedRole: elevated ? elevatedRole : null,
    delta: Math.round(maxDelta * 1000) / 1000,
  };
}

/**
 * Look up the conditional role profile for a given game condition.
 *
 * @param {Object} conditionalRoles — map of condition -> roleProfile
 * @param {string} condition — one of CONDITIONAL_KEYS
 * @returns {Object|null} the conditional role profile, or null if not found
 */
function getConditionalProfile(conditionalRoles, condition) {
  if (!conditionalRoles || !condition) return null;
  if (!CONDITIONAL_KEYS.includes(condition)) return null;
  return conditionalRoles[condition] || null;
}

/**
 * Estimate a role profile distribution from raw game log stats.
 *
 * Heuristic mapping:
 * - HIGH usage_rate + HIGH assist_rate => PRIMARY_BALL_HANDLER
 * - MED usage_rate + HIGH assist_rate => SECONDARY_PLAYMAKER
 * - LOW usage_rate + HIGH 3pt_share   => CATCH_SHOOT_SPACER
 * - HIGH off_ball_movement + cuts      => OFF_BALL_CUTTER
 * - HIGH usage_rate + LOW assist_rate  => FLOOR_RAISER
 * - Defensive metrics                  => SWITCHABLE_DEFENDER
 * - HIGH paint touches + rebounds      => PAINT_PRESENCE
 * - MED everything                     => CONNECTOR
 *
 * @param {Array<Object>} gameLogStats — array of game stat objects
 * @returns {Object} role profile with weights summing to ~1.0
 */
function calculateRoleProfile(gameLogStats) {
  if (!gameLogStats || gameLogStats.length === 0) {
    return {};
  }

  // Average the stats across games
  const avg = {};
  const statKeys = [
    'usage_rate',
    'assist_rate',
    'three_point_share',
    'off_ball_movement',
    'paint_touches',
    'rebounds_per_game',
    'defensive_versatility',
    'screen_assists',
  ];

  for (const key of statKeys) {
    const vals = gameLogStats
      .map((g) => g[key])
      .filter((v) => v !== undefined && v !== null);
    avg[key] = vals.length > 0 ? vals.reduce((a, b) => a + b, 0) / vals.length : 0;
  }

  // Raw role signals (0-1 scale heuristics)
  const raw = {};

  // PRIMARY_BALL_HANDLER: high usage + high assists
  raw.PRIMARY_BALL_HANDLER = Math.min(1, (avg.usage_rate / 35) * 0.6 + (avg.assist_rate / 40) * 0.4);

  // SECONDARY_PLAYMAKER: moderate usage + high assists
  const secondaryUsage = avg.usage_rate >= 15 && avg.usage_rate <= 25 ? 1 : 0.3;
  raw.SECONDARY_PLAYMAKER = Math.min(1, secondaryUsage * 0.4 + (avg.assist_rate / 30) * 0.6);

  // CATCH_SHOOT_SPACER: low usage + high 3pt share
  const lowUsageBonus = avg.usage_rate < 20 ? 0.7 : 0.2;
  raw.CATCH_SHOOT_SPACER = Math.min(1, lowUsageBonus * 0.4 + (avg.three_point_share / 80) * 0.6);

  // OFF_BALL_CUTTER: off-ball movement driven
  raw.OFF_BALL_CUTTER = Math.min(1, (avg.off_ball_movement / 100) * 0.8 + (1 - avg.usage_rate / 40) * 0.2);

  // FLOOR_RAISER: high usage + low assists (score-first)
  const lowAssistBonus = avg.assist_rate < 15 ? 0.7 : 0.2;
  raw.FLOOR_RAISER = Math.min(1, (avg.usage_rate / 35) * 0.6 + lowAssistBonus * 0.4);

  // SWITCHABLE_DEFENDER: defensive versatility
  raw.SWITCHABLE_DEFENDER = Math.min(1, (avg.defensive_versatility / 100));

  // PAINT_PRESENCE: paint touches + rebounds
  raw.PAINT_PRESENCE = Math.min(1, (avg.paint_touches / 15) * 0.5 + (avg.rebounds_per_game / 12) * 0.5);

  // CONNECTOR: screen assists + moderate everything
  raw.CONNECTOR = Math.min(1, (avg.screen_assists / 8) * 0.5 + 0.5 * (1 - calculateRoleVariance(raw)));

  // Normalize to sum to 1
  const total = Object.values(raw).reduce((s, v) => s + v, 0);
  const profile = {};

  if (total === 0) {
    // Fallback: equal distribution
    for (const role of ROLE_TAXONOMY) {
      profile[role] = 1 / ROLE_TAXONOMY.length;
    }
  } else {
    for (const role of ROLE_TAXONOMY) {
      profile[role] = Math.round(((raw[role] || 0) / total) * 1000) / 1000;
    }
  }

  return profile;
}

module.exports = {
  ROLE_TAXONOMY,
  CONDITIONAL_KEYS,
  calculateRoleVariance,
  getDominantRole,
  detectRoleElevation,
  getConditionalProfile,
  calculateRoleProfile,
};
