use super::transaction::Transaction;

const DEFAULT_MAX_HISTORY: usize = 100;

pub struct UndoManager {
    undo_stack: Vec<Transaction>,
    redo_stack: Vec<Transaction>,
    max_history: usize,
}

impl UndoManager {
    pub fn new() -> Self {
        Self {
            undo_stack: Vec::new(),
            redo_stack: Vec::new(),
            max_history: DEFAULT_MAX_HISTORY,
        }
    }

    pub fn with_max_history(max: usize) -> Self {
        Self {
            undo_stack: Vec::new(),
            redo_stack: Vec::new(),
            max_history: max,
        }
    }

    pub fn push(&mut self, tx: Transaction) {
        self.redo_stack.clear();
        self.undo_stack.push(tx);
        if self.undo_stack.len() > self.max_history {
            self.undo_stack.remove(0);
        }
    }

    /// Pop from undo stack and push to redo. Returns the transaction to undo.
    pub fn undo(&mut self) -> Option<Transaction> {
        let tx = self.undo_stack.pop()?;
        self.redo_stack.push(tx.clone());
        Some(tx)
    }

    /// Pop from redo stack and push to undo. Returns the transaction to redo.
    pub fn redo(&mut self) -> Option<Transaction> {
        let tx = self.redo_stack.pop()?;
        self.undo_stack.push(tx.clone());
        Some(tx)
    }

    pub fn can_undo(&self) -> bool {
        !self.undo_stack.is_empty()
    }

    pub fn can_redo(&self) -> bool {
        !self.redo_stack.is_empty()
    }

    pub fn clear(&mut self) {
        self.undo_stack.clear();
        self.redo_stack.clear();
    }
}

impl Default for UndoManager {
    fn default() -> Self {
        Self::new()
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::model::block::BlockId;
    use crate::op::operation::Operation;

    fn dummy_tx() -> Transaction {
        Transaction {
            ops: vec![Operation::InsertTextAt {
                block_id: BlockId::new("b1"),
                offset: 0,
                text: "a".into(),
                marks: vec![],
            }],
            inverse_ops: vec![Operation::DeleteRange {
                block_id: BlockId::new("b1"),
                start: 0,
                end: 1,
            }],
            selection_before: None,
            selection_after: None,
        }
    }

    #[test]
    fn undo_redo_flow() {
        let mut mgr = UndoManager::new();
        assert!(!mgr.can_undo());

        mgr.push(dummy_tx());
        assert!(mgr.can_undo());
        assert!(!mgr.can_redo());

        let _tx = mgr.undo().unwrap();
        assert!(!mgr.can_undo());
        assert!(mgr.can_redo());

        let _tx = mgr.redo().unwrap();
        assert!(mgr.can_undo());
        assert!(!mgr.can_redo());
    }

    #[test]
    fn new_push_clears_redo() {
        let mut mgr = UndoManager::new();
        mgr.push(dummy_tx());
        mgr.undo();
        assert!(mgr.can_redo());

        mgr.push(dummy_tx());
        assert!(!mgr.can_redo());
    }

    #[test]
    fn max_history_enforced() {
        let mut mgr = UndoManager::with_max_history(3);
        for _ in 0..5 {
            mgr.push(dummy_tx());
        }
        let mut count = 0;
        while mgr.undo().is_some() {
            count += 1;
        }
        assert_eq!(count, 3);
    }
}
