use serde::{Deserialize, Serialize};

/// Campaign speedrun timer: tracks total time and per-act splits, mirroring the
/// behavior of the original tool's leveling-tracker timer.
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(default)]
pub struct RunTimer {
    /// A run is in progress (started, not reset).
    pub active: bool,
    /// The run reached the endgame and stopped accumulating.
    pub finished: bool,
    /// User-requested pause (distinct from automatic town/hideout pause).
    pub manual_pause: bool,
    /// Whether the timer is currently auto-paused (town/hideout) — derived, but
    /// stored so the UI can show why it's paused.
    pub auto_paused: bool,
    /// Whether the timer is currently auto-paused due to inactivity (AFK).
    pub afk_paused: bool,
    /// Seconds accumulated in completed acts.
    pub total_seconds: f64,
    /// Seconds accumulated in the current act.
    pub act_seconds: f64,
    /// 1-based current act.
    pub current_act: usize,
    /// Per-act splits, indexed by act number (0 unused).
    pub splits: Vec<f64>,
    /// Human label for the run (start date/time).
    pub run_name: Option<String>,
}

impl Default for RunTimer {
    fn default() -> Self {
        RunTimer {
            active: false,
            finished: false,
            manual_pause: false,
            auto_paused: false,
            afk_paused: false,
            total_seconds: 0.0,
            act_seconds: 0.0,
            current_act: 1,
            splits: vec![0.0; 16],
            run_name: None,
        }
    }
}

/// An area id that should auto-pause the timer (towns, hideouts, login).
pub fn is_safe_zone(area_id: &str) -> bool {
    let a = area_id.to_lowercase();
    a.is_empty() || a == "login" || a.contains("town") || a.contains("hideout")
}

/// Detect the campaign starting zone (to auto-start a run).
fn is_campaign_start(area_id: &str) -> bool {
    let a = area_id.to_lowercase();
    a == "g1_1" || a == "1_1_1"
}

fn is_endgame(area_id: &str) -> bool {
    area_id.to_lowercase().contains("endgame")
}

impl RunTimer {
    fn ensure_act(&mut self, act: usize) {
        if self.splits.len() <= act {
            self.splits.resize(act + 1, 0.0);
        }
    }

    /// Begin a fresh run.
    pub fn start(&mut self, act: usize, name: String) {
        *self = RunTimer::default();
        self.active = true;
        self.current_act = act.max(1);
        self.run_name = Some(name);
    }

    pub fn reset(&mut self) {
        *self = RunTimer::default();
    }

    pub fn toggle_pause(&mut self) {
        if self.active && !self.finished {
            self.manual_pause = !self.manual_pause;
        }
    }

    /// Advance the elapsed time by `delta` seconds, honoring pause rules.
    /// `afk` is true when no input was detected for the configured delay.
    /// Returns true if the UI should be refreshed (time advanced, or a
    /// pause state just changed).
    pub fn tick(&mut self, delta: f64, area_id: &str, pause_in_town: bool, afk: bool) -> bool {
        if !self.active || self.finished || self.manual_pause {
            let changed = self.auto_paused || self.afk_paused;
            self.auto_paused = false;
            self.afk_paused = false;
            return changed;
        }
        // AFK takes precedence over town pause for the displayed reason.
        if afk {
            let was = self.afk_paused;
            self.afk_paused = true;
            self.auto_paused = false;
            return !was;
        }
        let was_afk = self.afk_paused;
        self.afk_paused = false;
        let safe = pause_in_town && is_safe_zone(area_id);
        let was = self.auto_paused;
        self.auto_paused = safe;
        if safe {
            // Emit once on entering the paused state, then stay quiet.
            return !was || was_afk;
        }
        self.act_seconds += delta;
        true
    }

    /// React to an area change. May auto-start a run, advance acts, or finish.
    /// `enabled` is the timer feature toggle. Returns true if state changed.
    pub fn on_area(&mut self, area_id: &str, act: usize, enabled: bool, start_name: impl Fn() -> String) -> bool {
        if !enabled {
            return false;
        }
        let mut changed = false;

        // Auto-start when entering the campaign start with no active run.
        if !self.active && is_campaign_start(area_id) {
            self.start(act.max(1), start_name());
            return true;
        }
        if !self.active || self.finished {
            return changed;
        }

        // Endgame reached: bank the final act and stop.
        if is_endgame(area_id) {
            self.ensure_act(self.current_act);
            self.splits[self.current_act] = self.act_seconds;
            self.total_seconds += self.act_seconds;
            self.act_seconds = 0.0;
            self.finished = true;
            return true;
        }

        // Advanced to a later act: bank the current act's split.
        if act > self.current_act {
            self.ensure_act(self.current_act);
            self.splits[self.current_act] = self.act_seconds;
            self.total_seconds += self.act_seconds;
            self.act_seconds = 0.0;
            self.current_act = act;
            changed = true;
        }
        changed
    }
}

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

    fn name() -> String {
        "run".to_string()
    }

    #[test]
    fn auto_starts_at_campaign_start() {
        let mut t = RunTimer::default();
        assert!(t.on_area("g1_1", 1, true, name));
        assert!(t.active);
        assert_eq!(t.current_act, 1);
    }

    #[test]
    fn does_not_start_when_disabled() {
        let mut t = RunTimer::default();
        assert!(!t.on_area("g1_1", 1, false, name));
        assert!(!t.active);
    }

    #[test]
    fn ticks_and_pauses_in_town() {
        let mut t = RunTimer::default();
        t.start(1, name());
        t.tick(5.0, "g1_5", true, false);
        assert!((t.act_seconds - 5.0).abs() < 1e-6);
        // entering a town pauses accumulation
        t.tick(5.0, "g1_town", true, false);
        assert!((t.act_seconds - 5.0).abs() < 1e-6);
        assert!(t.auto_paused);
    }

    #[test]
    fn afk_pauses_accumulation() {
        let mut t = RunTimer::default();
        t.start(1, name());
        t.tick(5.0, "g1_5", true, false);
        assert!((t.act_seconds - 5.0).abs() < 1e-6);
        // afk: time should not accumulate
        t.tick(5.0, "g1_5", true, true);
        assert!((t.act_seconds - 5.0).abs() < 1e-6);
        assert!(t.afk_paused);
        // resume on activity
        t.tick(5.0, "g1_5", true, false);
        assert!((t.act_seconds - 10.0).abs() < 1e-6);
        assert!(!t.afk_paused);
    }

    #[test]
    fn banks_split_on_act_change() {
        let mut t = RunTimer::default();
        t.start(1, name());
        t.tick(10.0, "g1_5", true, false);
        t.on_area("g2_1", 2, true, name);
        assert_eq!(t.current_act, 2);
        assert!((t.splits[1] - 10.0).abs() < 1e-6);
        assert!((t.total_seconds - 10.0).abs() < 1e-6);
        assert_eq!(t.act_seconds, 0.0);
    }

    #[test]
    fn finishes_at_endgame() {
        let mut t = RunTimer::default();
        t.start(3, name());
        t.tick(7.0, "g3_1", true, false);
        t.on_area("g_endgame_town", 8, true, name);
        assert!(t.finished);
        assert!(!t.tick(5.0, "g_endgame_town", true, false)); // no accumulation after finish
    }
}
