/// Weight and direction for a single stat in matchup scoring.
#[derive(Debug, Clone, Copy)]
pub struct StatWeight {
    pub weight: i32,
    pub high_is_better: bool,
}

impl StatWeight {
    const fn new(weight: i32, high_is_better: bool) -> Self {
        Self {
            weight,
            high_is_better,
        }
    }
}

/// Batter stat weights for matchup calculation.
/// Order: so, bb, hit, ob, tb, hr, bphr, bp1b, dp
pub const BATTER_WEIGHTS: &[(&str, StatWeight)] = &[
    ("so", StatWeight::new(1, false)),   // Strikeouts - low is better
    ("bb", StatWeight::new(1, true)),    // Walks - high is better
    ("hit", StatWeight::new(2, true)),   // Hits - high is better
    ("ob", StatWeight::new(5, true)),    // On-base - high is better
    ("tb", StatWeight::new(5, true)),    // Total bases - high is better
    ("hr", StatWeight::new(2, true)),    // Home runs - high is better
    ("bphr", StatWeight::new(3, true)),  // Ballpark HR - high is better
    ("bp1b", StatWeight::new(1, true)),  // Ballpark 1B - high is better
    ("dp", StatWeight::new(2, false)),   // Double plays - low is better
];

/// Pitcher stat weights for matchup calculation.
/// Note: directions are inverted vs batter (pitcher wants low hits, high K's).
pub const PITCHER_WEIGHTS: &[(&str, StatWeight)] = &[
    ("so", StatWeight::new(3, true)),    // Strikeouts - high is better for pitcher
    ("bb", StatWeight::new(1, false)),   // Walks - low is better for pitcher
    ("hit", StatWeight::new(2, false)),  // Hits - low is better for pitcher
    ("ob", StatWeight::new(5, false)),   // On-base - low is better for pitcher
    ("tb", StatWeight::new(2, false)),   // Total bases - low is better for pitcher
    ("hr", StatWeight::new(5, false)),   // Home runs - low is better for pitcher
    ("bphr", StatWeight::new(2, false)), // Ballpark HR - low is better for pitcher
    ("bp1b", StatWeight::new(1, false)), // Ballpark 1B - low is better for pitcher
    ("dp", StatWeight::new(2, true)),    // Double plays - high is better for pitcher
];

/// Max possible batter component score: sum of (3 * weight) for all stats.
pub const fn max_batter_score() -> i32 {
    // 3 * (1+1+2+5+5+2+3+1+2) = 3 * 22 = 66
    66
}

/// Max possible pitcher component score: sum of (3 * weight) for all stats.
pub const fn max_pitcher_score() -> i32 {
    // 3 * (3+1+2+5+2+5+2+1+2) = 3 * 23 = 69
    69
}

/// Max possible combined matchup score.
pub const fn max_matchup_score() -> i32 {
    max_batter_score() + max_pitcher_score() // 135
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn batter_weights_sum_to_22() {
        let sum: i32 = BATTER_WEIGHTS.iter().map(|(_, w)| w.weight).sum();
        assert_eq!(sum, 22, "batter weight sum changed — update max_batter_score");
    }

    #[test]
    fn pitcher_weights_sum_to_23() {
        let sum: i32 = PITCHER_WEIGHTS.iter().map(|(_, w)| w.weight).sum();
        assert_eq!(sum, 23, "pitcher weight sum changed — update max_pitcher_score");
    }

    #[test]
    fn max_scores_match_weight_sums() {
        let batter_sum: i32 = BATTER_WEIGHTS.iter().map(|(_, w)| w.weight).sum();
        assert_eq!(max_batter_score(), 3 * batter_sum);

        let pitcher_sum: i32 = PITCHER_WEIGHTS.iter().map(|(_, w)| w.weight).sum();
        assert_eq!(max_pitcher_score(), 3 * pitcher_sum);

        assert_eq!(max_matchup_score(), max_batter_score() + max_pitcher_score());
    }

    #[test]
    fn batter_weights_have_9_stats() {
        assert_eq!(BATTER_WEIGHTS.len(), 9);
    }

    #[test]
    fn pitcher_weights_have_9_stats() {
        assert_eq!(PITCHER_WEIGHTS.len(), 9);
    }

    #[test]
    fn batter_and_pitcher_share_stat_names() {
        let batter_names: Vec<&str> = BATTER_WEIGHTS.iter().map(|(n, _)| *n).collect();
        let pitcher_names: Vec<&str> = PITCHER_WEIGHTS.iter().map(|(n, _)| *n).collect();
        assert_eq!(batter_names, pitcher_names, "stat names must match between batter and pitcher weights");
    }
}
