use crate::types::{DiceSpec, EntityId, PhaseId, PoolId, SeatId};
use crate::trait_data::TraitSet;

#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone)]
pub struct PhaseDescriptor {
    pub key:              PhaseId,         // namespaced: "catan:normal_play"
    pub name:             &'static str,
    pub active_players:   ActivePlayers,
    pub turns_per_actor:  TurnsPerActor,
    pub turn_completion:  TurnCompletion,
    pub turn_style:       TurnStyle,
    pub turn_order:       TurnOrder,
    pub next:             PhaseNext,
}

impl PhaseDescriptor {
    /// Construct a terminal phase: no active players, no turns, transitions to
    /// `PhaseNext::GameOver`. Use for "the game has ended" states like
    /// checkmate, stalemate, or final-score reveal — the canonical
    /// `(Exactly(0), HandlerDriven, ActivePlayers::None, GameOver)` combo
    /// without spelling each enum out.
    pub fn terminal(key: PhaseId, name: &'static str) -> Self {
        Self {
            key,
            name,
            active_players:  ActivePlayers::None,
            turns_per_actor: TurnsPerActor::Exactly(0),
            turn_completion: TurnCompletion::HandlerDriven,
            turn_style:      TurnStyle::Sequential,
            turn_order:      TurnOrder::SessionOrder,
            next:            PhaseNext::GameOver,
        }
    }
}

#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq)]
pub enum PhaseNext {
    To(PhaseId),       // advance to this phase
    Repeat,            // restart this phase from the first actor
    GameOver,          // session ends
    /// Call registered phase_end_deciders; first to return DecidePhase(Some(id)) wins.
    /// Supersedes HandlerDecides.
    PhaseDeciders,
    /// Deprecated: use PhaseDeciders. Dispatches phase_end_reactors expecting PhaseTransitioned.
    HandlerDecides,
    Push(PhaseId),     // suspend current phase state, enter new phase
    Pop,               // restore the paused phase from the stack
}

#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq)]
pub enum TurnsPerActor {
    Exactly(u32),   // host counts TurnEnded from handlers; actor cannot end early
    UpTo(u32),      // actor may send ClientFrame::EndTurn early; host still ends if count reached
}

/// What terminates a single actor's turn within a phase.
/// Orthogonal to `TurnsPerActor` (which counts how many turns the actor gets per cycle).
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Default, PartialEq)]
pub enum TurnCompletion {
    /// Turn ends only when ClientFrame::EndTurn is received or an OnTurnEnd handler emits TurnEnded.
    #[default]
    Explicit,
    /// Turn ends automatically after the actor has triggered N Activated events this turn.
    AfterActions(u32),
    /// Turn ends when all listed pools for the actor's entities reach 0.
    WhenPoolsDepleted(Vec<PoolId>),
    /// An OnPhaseStart/OnTurnEnd handler is responsible for emitting TurnEnded.
    HandlerDriven,
}

#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq)]
pub enum TurnStyle {
    Sequential,
    /// All actors in the phase submit actions simultaneously via `session.submit_turn()`.
    /// The host holds submissions until every participant has submitted, then resolves them
    /// in deterministic (lexicographic SeatId) order before advancing the phase.
    Simultaneous,
}

#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq)]
pub enum ActivePlayers {
    AllPlayers,
    OnePlayer,
    AllEntities { filter: TraitSet },
    None,
}

#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Default, PartialEq)]
pub enum TurnOrder {
    #[default]
    RandomStatic,                                // default — shuffled once at phase entry, frozen
    SessionOrder,                                // session-join order
    SessionOrderReversed,                        // reverse of session-join order
    ByPool { pool: PoolId, descending: bool },   // sort by pool value at phase entry; actors lacking pool go last
    ByRoll { spec: DiceSpec, descending: bool }, // roll per actor at phase entry, sort by total
    Fixed(Vec<SeatId>),                          // exact list; overrides ActivePlayers resolution
    Collaborative,                               // per-turn: any actor nominates who goes next; unanimous accept → TurnAdvanced
    HandlerDecides,                              // OnPhaseStart handler must emit TurnOrderResolved
}

#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone)]
pub struct NominationState {
    pub candidate:    SeatId,
    pub nominated_by: SeatId,
    pub accepted_by:  Vec<SeatId>,   // includes the nominator
}

/// The active turn state for the current phase. Replaces bare `Option<SeatId>` so the three
/// distinct cases are named and cannot be confused.
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Default)]
pub enum ActiveTurn {
    /// No phase is currently active.
    #[default]
    Inactive,
    /// A Sequential phase: exactly one seat holds the active turn.
    Sequential(SeatId),
    /// A Simultaneous phase: all listed seats are submitting concurrently.
    Simultaneous(Vec<SeatId>),
}

impl ActiveTurn {
    /// Returns the single active seat for a Sequential turn; `None` otherwise.
    pub fn as_sequential(&self) -> Option<SeatId> {
        match self { Self::Sequential(s) => Some(*s), _ => None }
    }

    /// Returns all active actors (`[]` when `Inactive`).
    pub fn actors(&self) -> &[SeatId] {
        match self {
            Self::Inactive => &[],
            Self::Sequential(s) => std::slice::from_ref(s),
            Self::Simultaneous(v) => v.as_slice(),
        }
    }

    pub fn is_active(&self) -> bool { !matches!(self, Self::Inactive) }
}

/// One frame of the phase stack: what was paused (or is active) and where it left off.
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone)]
pub struct PhaseState {
    pub phase_id:            PhaseId,
    pub actors:              Vec<SeatId>,   // resolved at phase entry
    pub actor_index:         usize,
    pub credits_used:        u32,
    pub nomination:          Option<NominationState>,  // Some(_) when Collaborative and turn is open
    /// Pool allocated when this phase uses `TurnStyle::Simultaneous`.
    /// `None` for Sequential phases and after the pool has been resolved.
    pub submission_pool_id:  Option<EntityId>,
}

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

    #[test]
    fn terminal_descriptor_has_game_over_next() {
        let d = PhaseDescriptor::terminal(PhaseId::new("test:end"), "End");
        assert_eq!(d.next, PhaseNext::GameOver);
    }

    #[test]
    fn terminal_descriptor_has_no_active_players() {
        let d = PhaseDescriptor::terminal(PhaseId::new("test:end"), "End");
        assert_eq!(d.active_players, ActivePlayers::None);
        assert_eq!(d.turns_per_actor, TurnsPerActor::Exactly(0));
    }

    #[test]
    fn terminal_descriptor_uses_handler_driven_completion() {
        let d = PhaseDescriptor::terminal(PhaseId::new("test:end"), "End");
        assert_eq!(d.turn_completion, TurnCompletion::HandlerDriven);
    }

    #[test]
    fn terminal_descriptor_preserves_key_and_name() {
        let d = PhaseDescriptor::terminal(PhaseId::new("chess:checkmate"), "Checkmate");
        assert_eq!(d.key, PhaseId::new("chess:checkmate"));
        assert_eq!(d.name, "Checkmate");
    }
}
