//! Typed event description for bulk ingestion.
//!
//! `Event<T, K>` is the new public event shape (spec Section 4). Replaces
//! the nested `Vec<Vec<Vec<Index>>>`, `Vec<Vec<f64>>`, `Vec<Vec<Vec<f64>>>`
//! that the old `add_events_with_prior` took.

use smallvec::SmallVec;

use crate::{gaussian::Gaussian, outcome::Outcome, time::Time};

/// A single match at time `time` involving some number of teams.
#[derive(Clone, Debug)]
pub struct Event<T: Time, K> {
    pub time: T,
    pub teams: SmallVec<[Team<K>; 4]>,
    pub outcome: Outcome,
}

/// A team: list of members competing together.
#[derive(Clone, Debug)]
pub struct Team<K> {
    pub members: SmallVec<[Member<K>; 4]>,
}

impl<K> Team<K> {
    pub fn new() -> Self {
        Self {
            members: SmallVec::new(),
        }
    }

    pub fn with_members<I: IntoIterator<Item = Member<K>>>(members: I) -> Self {
        Self {
            members: members.into_iter().collect(),
        }
    }
}

impl<K> Default for Team<K> {
    fn default() -> Self {
        Self::new()
    }
}

/// One member of a team, identified by user key `K`.
///
/// `weight` defaults to 1.0; a per-event `prior` can override the competitor's
/// current skill estimate for this event only.
#[derive(Clone, Debug)]
pub struct Member<K> {
    pub key: K,
    pub weight: f64,
    pub prior: Option<Gaussian>,
}

impl<K> Member<K> {
    pub fn new(key: K) -> Self {
        Self {
            key,
            weight: 1.0,
            prior: None,
        }
    }

    pub fn with_weight(mut self, weight: f64) -> Self {
        self.weight = weight;
        self
    }

    pub fn with_prior(mut self, prior: Gaussian) -> Self {
        self.prior = Some(prior);
        self
    }
}

/// Convenience: a member is a user key with default weight 1.0 and no prior.
impl<K> From<K> for Member<K> {
    fn from(key: K) -> Self {
        Self::new(key)
    }
}

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

    #[test]
    fn member_new_has_unit_weight_no_prior() {
        let m = Member::new("alice");
        assert_eq!(m.key, "alice");
        assert_eq!(m.weight, 1.0);
        assert!(m.prior.is_none());
    }

    #[test]
    fn member_builder_methods_chain() {
        let m = Member::new("alice")
            .with_weight(0.5)
            .with_prior(Gaussian::from_ms(20.0, 5.0));
        assert_eq!(m.weight, 0.5);
        assert!(m.prior.is_some());
    }

    #[test]
    fn member_from_key() {
        let m: Member<&str> = "bob".into();
        assert_eq!(m.key, "bob");
        assert_eq!(m.weight, 1.0);
    }

    #[test]
    fn team_with_members_collects() {
        let t: Team<&str> = Team::with_members([Member::new("a"), Member::new("b")]);
        assert_eq!(t.members.len(), 2);
    }

    #[test]
    fn event_construction() {
        use smallvec::smallvec;
        let e: Event<i64, &str> = Event {
            time: 1,
            teams: smallvec![
                Team::with_members([Member::new("a")]),
                Team::with_members([Member::new("b")]),
            ],
            outcome: Outcome::winner(0, 2),
        };
        assert_eq!(e.teams.len(), 2);
        assert_eq!(e.time, 1);
    }
}
