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

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

#[cfg(test)]
mod tests {
    use super::*;
    use crate::command::cmd::test_helpers::*;

    #[test]
    fn tile_axis_cycle_produces_cycle_effect() {
        let m = two_cat_model();
        let layout = make_layout(&m);
        let reg = make_registry();
        let ctx = make_ctx(&m, &layout, &reg);
        let cmd = TileAxisOp { axis: None };
        let effects = cmd.execute(&ctx);
        assert!(!effects.is_empty());
        let dbg = effects_debug(&effects);
        assert!(dbg.contains("CycleAxis"), "Expected CycleAxis, got: {dbg}");
    }

    #[test]
    fn tile_axis_set_produces_set_axis_effect() {
        let m = two_cat_model();
        let layout = make_layout(&m);
        let reg = make_registry();
        let ctx = make_ctx(&m, &layout, &reg);
        let cmd = TileAxisOp {
            axis: Some(crate::view::Axis::Page),
        };
        let effects = cmd.execute(&ctx);
        assert!(!effects.is_empty());
        let dbg = effects_debug(&effects);
        assert!(dbg.contains("SetAxis"), "Expected SetAxis, got: {dbg}");
    }

    #[test]
    fn tile_axis_with_out_of_bounds_cursor_returns_empty() {
        let m = two_cat_model();
        let layout = make_layout(&m);
        let reg = make_registry();
        let mut ctx = make_ctx(&m, &layout, &reg);
        ctx.tile_cat_idx = 999;
        let cmd = TileAxisOp { axis: None };
        let effects = cmd.execute(&ctx);
        assert!(effects.is_empty());
    }

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

    #[test]
    fn move_tile_cursor_clamps_at_start() {
        let m = two_cat_model();
        let layout = make_layout(&m);
        let reg = make_registry();
        let ctx = make_ctx(&m, &layout, &reg);
        let cmd = MoveTileCursor(-1);
        let effects = cmd.execute(&ctx);
        assert_eq!(effects.len(), 1);
        let dbg = effects_debug(&effects);
        assert!(
            dbg.contains("SetTileCatIdx(0)"),
            "Expected clamped to 0, got: {dbg}"
        );
    }
}

// ── Tile select commands ────────────────────────────────────────────────────

/// Move the tile select cursor left or right.
#[derive(Debug)]
pub struct MoveTileCursor(pub i32);
impl Cmd for MoveTileCursor {
    fn name(&self) -> &'static str {
        "move-tile-cursor"
    }
    fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {
        let count = ctx.model.category_names().len();
        if count == 0 {
            return vec![];
        }
        let new = (ctx.tile_cat_idx as i32 + self.0).clamp(0, (count - 1) as i32) as usize;
        vec![Box::new(effect::SetTileCatIdx(new))]
    }
}

/// Cycle or set the axis for the category at the tile cursor.
/// Stays in TileSelect mode so the user can adjust multiple tiles.
/// `axis: None` -> cycle, `axis: Some(a)` -> set to `a`.
#[derive(Debug)]
pub struct TileAxisOp {
    pub axis: Option<Axis>,
}

fn axis_label(axis: Axis) -> &'static str {
    match axis {
        Axis::Row => "Row",
        Axis::Column => "Col",
        Axis::Page => "Page",
        Axis::None => "None",
    }
}

impl Cmd for TileAxisOp {
    fn name(&self) -> &'static str {
        if self.axis.is_some() {
            "set-axis-for-tile"
        } else {
            "cycle-axis-for-tile"
        }
    }
    fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {
        let cat_names = ctx.model.category_names();
        if let Some(name) = cat_names.get(ctx.tile_cat_idx) {
            let new_axis = match self.axis {
                Some(axis) => axis,
                None => {
                    let current = ctx.view.axis_of(name);
                    match current {
                        Axis::Row => Axis::Column,
                        Axis::Column => Axis::Page,
                        Axis::Page => Axis::None,
                        Axis::None => Axis::Row,
                    }
                }
            };
            let axis_effect: Box<dyn Effect> = match self.axis {
                Some(axis) => Box::new(effect::SetAxis {
                    category: name.to_string(),
                    axis,
                }),
                None => Box::new(effect::CycleAxis(name.to_string())),
            };
            let status = format!("{} → {}", name, axis_label(new_axis));
            vec![
                axis_effect,
                effect::mark_dirty(),
                effect::set_status(status),
            ]
        } else {
            vec![]
        }
    }
}
