//! The bots. Each [`Strategy`] is a pure function of a [`View`] (a read-only snapshot
//! of the public game state). The four strategies are deliberately built around the
//! design's three exits, so the sweeps can answer "is there a dominant exit?".
//!
//! - [`Strategy::CashOut`]  — Cautious: bank early and safe, the pure Decline player.
//! - [`Strategy::PressOn`]  — Greedy: build the engine, hold ground, defend with a
//!   late Cash-Out rather than risk the Maw.
//! - [`Strategy::WorthyEnd`] — Martyr: dive deepest and ride a Last Stand into a blaze.
//! - [`Strategy::Balanced`] — Adaptive: read the clock, bank good hauls, opportunistic
//!   martyrdom when deep and doomed. The "good human" baseline.

use serde::{Deserialize, Serialize};

/// A read-only snapshot handed to a [`Strategy`] for one decision.
#[derive(Clone, Debug)]
pub struct View {
    pub round: u32,
    pub frontier: u32,
    pub floors_total: u32,
    pub my_glory: f64,
    pub leader_glory: f64,
    pub rank: u32,
    pub num_players: u32,

    pub has_party: bool,
    pub floor: u32,
    pub deepest: u32,
    pub unbanked: f64,
    pub bag_size: u32,
    pub last_stand: bool,
    pub held_count: u32,
    pub held_value: f64,
    pub holds_deep_room: bool,
    pub free_last_stand: bool,
    /// What a Worthy End would pay right now (`worthy_factor * deepest * worthy_mult`).
    pub worthy_estimate: f64,
    /// Number of collapse events until *this party's* floor is eaten (1 = next).
    pub collapses_until_my_floor: u32,

    /// Tunable thresholds passed through from config so bots read the same table.
    pub cashout_min_bank: f64,
    pub cashout_depth_dividend: f64,
}

impl View {
    /// What a Cash-Out would bank right now (loot + depth dividend).
    fn bankable(&self) -> f64 {
        self.unbanked + self.cashout_depth_dividend * self.deepest as f64
    }
    fn can_last_stand(&self) -> bool {
        self.holds_deep_room || self.free_last_stand
    }
}

/// The party's plan for the action phase of a turn.
#[derive(Clone, Copy, Debug)]
pub struct Intent {
    pub target_depth: u32,
    pub press_target: u32,
    pub want_recruit: bool,
    pub dive_moves: bool,
}

/// The exit decision at the end of a turn.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Exit {
    Stay,
    CashOut,
    SetLastStand,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum Strategy {
    CashOut,
    PressOn,
    WorthyEnd,
    Balanced,
}

pub const ALL_STRATEGIES: [Strategy; 4] = [
    Strategy::CashOut,
    Strategy::PressOn,
    Strategy::WorthyEnd,
    Strategy::Balanced,
];

impl Strategy {
    pub fn name(self) -> &'static str {
        match self {
            Strategy::CashOut => "CashOut",
            Strategy::PressOn => "PressOn",
            Strategy::WorthyEnd => "WorthyEnd",
            Strategy::Balanced => "Balanced",
        }
    }

    pub fn intent(self, v: &View) -> Intent {
        match self {
            // Take-profits banker: contest the frontier like everyone else, but never
            // gamble — bank gains fast. (v2 iter3: "play shallow" was just losing; the
            // real cautious identity is *early exit timing*, not avoiding the action.)
            Strategy::CashOut => {
                let target = if v.collapses_until_my_floor <= 1 {
                    v.floor.saturating_sub(1).max(1)
                } else {
                    (v.floor + 1).min(v.frontier).max(1)
                };
                Intent {
                    target_depth: target,
                    press_target: 0,
                    want_recruit: v.bag_size < 9,
                    dive_moves: false,
                }
            }
            // Build the engine; advance when safe, flee up when the Maw is one away.
            Strategy::PressOn => {
                let target = if v.collapses_until_my_floor <= 1 {
                    v.floor.saturating_sub(1).max(1) // flee up
                } else {
                    (v.floor + 1).min(v.frontier).max(1) // press deeper
                };
                Intent {
                    target_depth: target,
                    press_target: 2,
                    want_recruit: v.bag_size < 12,
                    dive_moves: false,
                }
            }
            // Rush the deepest live floor and press hard for the claim + the dive.
            Strategy::WorthyEnd => Intent {
                target_depth: v.frontier.max(1),
                press_target: 3,
                want_recruit: v.bag_size < 8 && v.round <= 3,
                dive_moves: true,
            },
            // Adaptive: dive toward the mid-deep frontier early; ease off when close.
            Strategy::Balanced => {
                let target = if v.collapses_until_my_floor <= 1 {
                    v.floor.saturating_sub(1).max(1)
                } else {
                    (v.floor + 1).min(v.frontier).max(1)
                };
                Intent {
                    target_depth: target,
                    press_target: if v.collapses_until_my_floor <= 1 { 0 } else { 1 },
                    want_recruit: v.bag_size < 10,
                    dive_moves: v.round <= 4,
                }
            }
        }
    }

    pub fn exit(self, v: &View) -> Exit {
        if !v.has_party {
            return Exit::Stay;
        }
        match self {
            // Bank as soon as the haul is worthwhile, or the moment danger appears.
            Strategy::CashOut => {
                if v.collapses_until_my_floor <= 1 || v.bankable() >= v.cashout_min_bank {
                    Exit::CashOut
                } else {
                    Exit::Stay
                }
            }
            // Defend territory with a late Cash-Out rather than feed the Maw.
            Strategy::PressOn => {
                if v.collapses_until_my_floor <= 1 {
                    Exit::CashOut
                } else {
                    Exit::Stay
                }
            }
            // The gambler: commit to a Last Stand when deep and doomed, but only if it
            // actually out-pays a Cash-Out (v2 iter6: blindly preferring martyrdom threw
            // parties away for a worse payout — a Wardancer's *bigger* End is what makes
            // it worth it). Otherwise salvage.
            Strategy::WorthyEnd => {
                if v.last_stand {
                    Exit::Stay
                } else if v.can_last_stand()
                    && v.deepest >= 3
                    && v.collapses_until_my_floor <= 2
                    && v.worthy_estimate >= v.bankable() * 0.9
                {
                    Exit::SetLastStand
                } else if v.collapses_until_my_floor <= 1 {
                    Exit::CashOut
                } else {
                    Exit::Stay
                }
            }
            // Lock in good hauls; martyr only when a Worthy End clearly beats banking —
            // this is what separates Wardancer (2.2x End) from everyone else.
            Strategy::Balanced => {
                if v.last_stand {
                    Exit::Stay
                } else if v.can_last_stand()
                    && v.deepest >= 3
                    && v.collapses_until_my_floor <= 2
                    && v.worthy_estimate > v.bankable()
                {
                    Exit::SetLastStand
                } else if v.collapses_until_my_floor <= 1 {
                    Exit::CashOut
                } else if v.collapses_until_my_floor <= 2 && v.bankable() >= v.cashout_min_bank * 1.5 {
                    Exit::CashOut
                } else {
                    Exit::Stay
                }
            }
        }
    }
}
