use crate::ui::effect::{self, Effect};

use super::core::{Cmd, CmdContext};

#[cfg(test)]
mod tests {
    use super::*;
    use crate::command::cmd::test_helpers::*;
    use crate::model::cell::{CellKey, CellValue};

    #[test]
    fn clear_selected_cell_produces_clear_and_dirty() {
        let mut m = two_cat_model();
        let key = CellKey::new(vec![
            ("Type".to_string(), "Food".to_string()),
            ("Month".to_string(), "Jan".to_string()),
        ]);
        m.model.set_cell(key, CellValue::Number(42.0));
        let layout = make_layout(&m);
        let reg = make_registry();
        let ctx = make_ctx(&m, &layout, &reg);
        let cmd = ClearCellCommand {
            key: ctx.cell_key().clone().unwrap(),
        };
        let effects = cmd.execute(&ctx);
        assert_eq!(effects.len(), 2);
    }

    #[test]
    fn yank_cell_produces_set_yanked() {
        let mut m = two_cat_model();
        let key = CellKey::new(vec![
            ("Type".to_string(), "Food".to_string()),
            ("Month".to_string(), "Jan".to_string()),
        ]);
        m.model.set_cell(key, CellValue::Number(99.0));
        let layout = make_layout(&m);
        let reg = make_registry();
        let ctx = make_ctx(&m, &layout, &reg);
        let cmd = YankCell {
            key: ctx.cell_key().clone().unwrap(),
        };
        let effects = cmd.execute(&ctx);
        assert_eq!(effects.len(), 2);
    }

    #[test]
    fn paste_with_yanked_value_produces_set_cell() {
        let mut m = two_cat_model();
        m.model.set_cell(
            CellKey::new(vec![
                ("Type".into(), "Food".into()),
                ("Month".into(), "Jan".into()),
            ]),
            CellValue::Number(42.0),
        );
        let layout = make_layout(&m);
        let reg = make_registry();
        let yanked = Some(CellValue::Number(99.0));
        let mut ctx = make_ctx(&m, &layout, &reg);
        ctx.yanked = &yanked;
        let key = CellKey::new(vec![
            ("Type".into(), "Clothing".into()),
            ("Month".into(), "Feb".into()),
        ]);
        let cmd = PasteCell { key };
        let effects = cmd.execute(&ctx);
        assert_eq!(effects.len(), 2);
        let dbg = effects_debug(&effects);
        assert!(dbg.contains("SetCell"), "Expected SetCell, got: {dbg}");
    }

    #[test]
    fn paste_without_yanked_value_produces_nothing() {
        let m = two_cat_model();
        let layout = make_layout(&m);
        let reg = make_registry();
        let ctx = make_ctx(&m, &layout, &reg);
        let key = CellKey::new(vec![
            ("Type".into(), "Food".into()),
            ("Month".into(), "Jan".into()),
        ]);
        let cmd = PasteCell { key };
        let effects = cmd.execute(&ctx);
        assert!(effects.is_empty());
    }

    #[test]
    fn transpose_produces_transpose_and_dirty() {
        let m = two_cat_model();
        let layout = make_layout(&m);
        let reg = make_registry();
        let ctx = make_ctx(&m, &layout, &reg);
        let effects = TransposeAxes.execute(&ctx);
        assert_eq!(effects.len(), 2);
        let dbg = effects_debug(&effects);
        assert!(
            dbg.contains("TransposeAxes"),
            "Expected TransposeAxes, got: {dbg}"
        );
    }

    #[test]
    fn save_produces_save_effect() {
        let m = two_cat_model();
        let layout = make_layout(&m);
        let reg = make_registry();
        let ctx = make_ctx(&m, &layout, &reg);
        let effects = SaveCmd.execute(&ctx);
        assert_eq!(effects.len(), 1);
        let dbg = effects_debug(&effects);
        assert!(dbg.contains("Save"), "Expected Save, got: {dbg}");
    }
}

// ── Cell operations ──────────────────────────────────────────────────────────
// All cell commands take an explicit CellKey. The interactive spec fills it
// from ctx.cell_key(); the parser fills it from Cat/Item coordinate args.

/// Clear a cell.
#[derive(Debug)]
pub struct ClearCellCommand {
    pub key: crate::model::cell::CellKey,
}
impl Cmd for ClearCellCommand {
    fn name(&self) -> &'static str {
        "clear-cell"
    }
    fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> {
        vec![
            Box::new(effect::ClearCell(self.key.clone())),
            effect::mark_dirty(),
        ]
    }
}

/// Yank (copy) a cell value.
#[derive(Debug)]
pub struct YankCell {
    pub key: crate::model::cell::CellKey,
}
impl Cmd for YankCell {
    fn name(&self) -> &'static str {
        "yank"
    }
    fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {
        let value = ctx.model.evaluate_aggregated(&self.key, ctx.none_cats());
        vec![
            Box::new(effect::SetYanked(value)),
            effect::set_status("Yanked"),
        ]
    }
}

/// Paste the yanked value into a cell.
#[derive(Debug)]
pub struct PasteCell {
    pub key: crate::model::cell::CellKey,
}
impl Cmd for PasteCell {
    fn name(&self) -> &'static str {
        "paste"
    }
    fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {
        if let Some(value) = ctx.yanked.clone() {
            vec![
                Box::new(effect::SetCell(self.key.clone(), value)),
                effect::mark_dirty(),
            ]
        } else {
            vec![]
        }
    }
}

// ── View commands ────────────────────────────────────────────────────────────

#[derive(Debug)]
pub struct TransposeAxes;
impl Cmd for TransposeAxes {
    fn name(&self) -> &'static str {
        "transpose"
    }
    fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> {
        vec![Box::new(effect::TransposeAxes), effect::mark_dirty()]
    }
}

#[derive(Debug)]
pub struct SaveCmd;
impl Cmd for SaveCmd {
    fn name(&self) -> &'static str {
        "save"
    }
    fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> {
        vec![Box::new(effect::Save)]
    }
}
