#![deny(warnings)]
#![allow(dead_code)]
//! [TSM.ID].[11031972] -- Platform X Ecosystem
//! xcu-state-machine -- Hierarchical State Machine with hot-reload
use std::collections::HashMap;

#[derive(Debug)]
pub enum SmError { InvalidTransition(String), StateNotFound(String), GuardFailed(String) }
impl std::fmt::Display for SmError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self { Self::InvalidTransition(e) => write!(f, "Invalid: {e}"), Self::StateNotFound(e) => write!(f, "State: {e}"), Self::GuardFailed(e) => write!(f, "Guard: {e}") }
    }
}
impl std::error::Error for SmError {}

#[derive(Debug, Clone)]
pub struct State { pub name: String, pub parent: Option<String>, pub is_final: bool }

#[derive(Debug, Clone)]
pub struct Transition { pub from: String, pub to: String, pub event: String, pub guard: Option<String> }

pub struct StateMachine {
    states: HashMap<String, State>,
    transitions: Vec<Transition>,
    current: String,
    history: Vec<(String, String, String)>, // (from, event, to)
    guards: HashMap<String, Box<dyn Fn() -> bool + Send + Sync>>,
}

impl StateMachine {
    pub fn new(initial: &str) -> Self {
        let mut states = HashMap::new();
        states.insert(initial.into(), State { name: initial.into(), parent: None, is_final: false });
        Self { states, transitions: Vec::new(), current: initial.into(), history: Vec::new(), guards: HashMap::new() }
    }

    pub fn add_state(&mut self, state: State) { self.states.insert(state.name.clone(), state); }
    pub fn add_transition(&mut self, t: Transition) { self.transitions.push(t); }
    pub fn add_guard<F>(&mut self, name: &str, f: F) where F: Fn() -> bool + Send + Sync + 'static { self.guards.insert(name.into(), Box::new(f)); }

    pub fn fire(&mut self, event: &str) -> Result<&str, SmError> {
        let matching: Vec<&Transition> = self.transitions.iter()
            .filter(|t| t.from == self.current && t.event == event).collect();
        if matching.is_empty() {
            return Err(SmError::InvalidTransition(format!("No transition from '{}' on '{}'", self.current, event)));
        }
        for t in &matching {
            if let Some(ref guard_name) = t.guard {
                if let Some(guard_fn) = self.guards.get(guard_name) {
                    if !guard_fn() { continue; }
                } else { return Err(SmError::GuardFailed(format!("Guard '{guard_name}' not found"))); }
            }
            let from = self.current.clone();
            self.current = t.to.clone();
            self.history.push((from, event.into(), t.to.clone()));
            return Ok(&self.current);
        }
        Err(SmError::GuardFailed(format!("All guards failed for '{event}'")))
    }

    pub fn current(&self) -> &str { &self.current }
    pub fn is_final(&self) -> bool { self.states.get(&self.current).map(|s| s.is_final).unwrap_or(false) }
    pub fn history(&self) -> &[(String, String, String)] { &self.history }

    /// Serialize state for hot-reload
    pub fn snapshot(&self) -> (String, Vec<(String, String, String)>) {
        (self.current.clone(), self.history.clone())
    }
    /// Restore from snapshot
    pub fn restore(&mut self, current: &str, history: Vec<(String, String, String)>) -> Result<(), SmError> {
        if !self.states.contains_key(current) { return Err(SmError::StateNotFound(current.into())); }
        self.current = current.into();
        self.history = history;
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    fn call_sm() -> StateMachine {
        let mut sm = StateMachine::new("idle");
        sm.add_state(State { name: "ringing".into(), parent: None, is_final: false });
        sm.add_state(State { name: "connected".into(), parent: None, is_final: false });
        sm.add_state(State { name: "ended".into(), parent: None, is_final: true });
        sm.add_transition(Transition { from: "idle".into(), to: "ringing".into(), event: "call".into(), guard: None });
        sm.add_transition(Transition { from: "ringing".into(), to: "connected".into(), event: "answer".into(), guard: None });
        sm.add_transition(Transition { from: "connected".into(), to: "ended".into(), event: "hangup".into(), guard: None });
        sm.add_transition(Transition { from: "ringing".into(), to: "ended".into(), event: "reject".into(), guard: None });
        sm
    }
    #[test]
    fn test_transitions() {
        let mut sm = call_sm();
        sm.fire("call").unwrap();
        assert_eq!(sm.current(), "ringing");
        sm.fire("answer").unwrap();
        assert_eq!(sm.current(), "connected");
        sm.fire("hangup").unwrap();
        assert!(sm.is_final());
    }
    #[test]
    fn test_invalid() { let mut sm = call_sm(); assert!(sm.fire("hangup").is_err()); }
    #[test]
    fn test_snapshot_restore() {
        let mut sm = call_sm();
        sm.fire("call").unwrap();
        let (state, hist) = sm.snapshot();
        let mut sm2 = call_sm();
        sm2.restore(&state, hist).unwrap();
        assert_eq!(sm2.current(), "ringing");
    }
}
