use smallvec::SmallVec;

use crate::{
    InferenceError, Outcome,
    drift::Drift,
    event::{Event, Member, Team},
    history::History,
    observer::Observer,
    time::Time,
};

pub struct EventBuilder<'h, T, D, O, K>
where
    T: Time,
    D: Drift<T>,
    O: Observer<T>,
    K: Eq + std::hash::Hash + Clone,
{
    history: &'h mut History<T, D, O, K>,
    event: Event<T, K>,
    current_team_idx: Option<usize>,
}

impl<'h, T, D, O, K> EventBuilder<'h, T, D, O, K>
where
    T: Time,
    D: Drift<T>,
    O: Observer<T>,
    K: Eq + std::hash::Hash + Clone,
{
    pub(crate) fn new(history: &'h mut History<T, D, O, K>, time: T) -> Self {
        Self {
            history,
            event: Event {
                time,
                teams: SmallVec::new(),
                outcome: Outcome::Ranked(SmallVec::new()),
            },
            current_team_idx: None,
        }
    }

    /// Add a team by its member keys (weight 1.0 each, no prior overrides).
    pub fn team<I: IntoIterator<Item = K>>(mut self, keys: I) -> Self {
        let members: SmallVec<[Member<K>; 4]> = keys.into_iter().map(Member::new).collect();
        self.event.teams.push(Team { members });
        self.current_team_idx = Some(self.event.teams.len() - 1);
        self
    }

    /// Set per-member weights for the most recently added team.
    ///
    /// Panics in debug builds if called before `.team(...)` or if the length
    /// doesn't match the team's member count.
    pub fn weights<I: IntoIterator<Item = f64>>(mut self, weights: I) -> Self {
        let idx = self
            .current_team_idx
            .expect(".weights(...) called before any .team(...)");
        let ws: Vec<f64> = weights.into_iter().collect();
        let team = &mut self.event.teams[idx];
        debug_assert_eq!(
            ws.len(),
            team.members.len(),
            "weights length must match team size"
        );
        for (m, w) in team.members.iter_mut().zip(ws) {
            m.weight = w;
        }
        self
    }

    /// Set explicit ranks per team (length must equal number of teams).
    pub fn ranking<I: IntoIterator<Item = u32>>(mut self, ranks: I) -> Self {
        self.event.outcome = Outcome::ranking(ranks);
        self
    }

    /// Set explicit per-team continuous scores; higher = better.
    pub fn scores<I: IntoIterator<Item = f64>>(mut self, scores: I) -> Self {
        self.event.outcome = crate::Outcome::scores(scores);
        self
    }

    /// Set explicit per-team continuous scores with a per-event noise override.
    ///
    /// `sigma` overrides `HistoryBuilder::score_sigma` for this event only.
    /// Must be `> 0.0`; debug-asserts otherwise via `Outcome::scores_with_sigma`.
    pub fn scores_with_sigma<I: IntoIterator<Item = f64>>(mut self, scores: I, sigma: f64) -> Self {
        self.event.outcome = crate::Outcome::scores_with_sigma(scores, sigma);
        self
    }

    /// Mark team `winner_idx` as winner; others tied for last.
    pub fn winner(mut self, winner_idx: u32) -> Self {
        self.event.outcome = Outcome::winner(winner_idx, self.event.teams.len() as u32);
        self
    }

    /// All teams tied.
    pub fn draw(mut self) -> Self {
        self.event.outcome = Outcome::draw(self.event.teams.len() as u32);
        self
    }

    /// Commit the event to the history.
    pub fn commit(self) -> Result<(), InferenceError> {
        self.history.add_events(std::iter::once(self.event))
    }
}
