use crate::model::cell::CellKey;
use crate::ui::app::AppMode;
use crate::ui::effect::Panel;

/// Decode a mode-name string (as supplied by `enter-mode`/`edit-or-drill`
/// keymap bindings) into an `AppMode`.
fn parse_mode_name(s: &str) -> Result<AppMode, String> {
    match s {
        "normal" => Ok(AppMode::Normal),
        "help" => Ok(AppMode::Help),
        "formula-panel" => Ok(AppMode::FormulaPanel),
        "category-panel" => Ok(AppMode::CategoryPanel),
        "view-panel" => Ok(AppMode::ViewPanel),
        "tile-select" => Ok(AppMode::TileSelect),
        "command" => Ok(AppMode::command_mode()),
        "category-add" => Ok(AppMode::category_add()),
        "editing" => Ok(AppMode::editing()),
        "records-normal" => Ok(AppMode::RecordsNormal),
        "records-editing" => Ok(AppMode::records_editing()),
        "formula-edit" => Ok(AppMode::formula_edit()),
        "export-prompt" => Ok(AppMode::export_prompt()),
        other => Err(format!("Unknown mode: {other}")),
    }
}

use super::cell::*;
use super::commit::*;
use super::core::*;
use super::effect_cmds::*;
use super::grid::*;
use super::mode::*;
use super::navigation::*;
use super::panel::*;
use super::search::*;
use super::text_buffer::*;
use super::tile::*;

/// Build the default command registry with all commands.
/// Registry names MUST match the `Cmd::name()` return value.
pub fn default_registry() -> CmdRegistry {
    let mut r = CmdRegistry::new();

    // ── Model mutations (effect_cmd! wrappers) ───────────────────────────
    r.register_pure(&AddCategoryCmd(vec![]), AddCategoryCmd::parse);
    r.register_pure(&AddItemCmd(vec![]), AddItemCmd::parse);
    r.register_pure(&AddItemsCmd(vec![]), AddItemsCmd::parse);
    r.register_pure(&AddItemInGroupCmd(vec![]), AddItemInGroupCmd::parse);
    r.register_pure(&SetCellCmd(vec![]), SetCellCmd::parse);
    r.register_pure(&ClearBufferCmd(vec![]), ClearBufferCmd::parse);
    r.register(
        &ClearCellCommand {
            key: CellKey::new(vec![]),
        },
        |args| {
            if args.is_empty() {
                return Err("clear-cell requires at least one Cat/Item coordinate".into());
            }
            Ok(Box::new(ClearCellCommand {
                key: parse_cell_key_from_args(args),
            }))
        },
        |_args, ctx| {
            let key = ctx.cell_key().clone().ok_or("no cell at cursor")?;
            Ok(Box::new(ClearCellCommand { key }))
        },
    );
    r.register_pure(&AddFormulaCmd(vec![]), AddFormulaCmd::parse);
    r.register_pure(&RemoveFormulaCmd(vec![]), RemoveFormulaCmd::parse);
    r.register_pure(&CreateViewCmd(vec![]), CreateViewCmd::parse);
    r.register_pure(&DeleteViewCmd(vec![]), DeleteViewCmd::parse);
    r.register_pure(&SwitchViewCmd(vec![]), SwitchViewCmd::parse);
    r.register_pure(&SetAxisCmd(vec![]), SetAxisCmd::parse);
    r.register_pure(&SetPageCmd(vec![]), SetPageCmd::parse);
    r.register_pure(&ToggleGroupCmd(vec![]), ToggleGroupCmd::parse);
    r.register_pure(&HideItemCmd(vec![]), HideItemCmd::parse);
    r.register_pure(&ShowItemCmd(vec![]), ShowItemCmd::parse);
    r.register_pure(&SaveAsCmd(vec![]), SaveAsCmd::parse);
    r.register_pure(&LoadModelCmd(vec![]), LoadModelCmd::parse);
    r.register_pure(&ExportCsvCmd(vec![]), ExportCsvCmd::parse);
    r.register_pure(&ImportJsonCmd(vec![]), ImportJsonCmd::parse);
    r.register_pure(&SetFormatCmd(vec![]), SetFormatCmd::parse);
    r.register_pure(&ImportCmd(vec![]), ImportCmd::parse);
    r.register_pure(&ExportCmd(vec![]), ExportCmd::parse);
    r.register_pure(&WriteCmd(vec![]), WriteCmd::parse);
    r.register_pure(&HelpCmd(vec![]), HelpCmd::parse);
    r.register(
        &HelpPageNextCmd(vec![]),
        HelpPageNextCmd::parse,
        |_args, _ctx| Ok(Box::new(HelpPageNextCmd(vec![]))),
    );
    r.register(
        &HelpPagePrevCmd(vec![]),
        HelpPagePrevCmd::parse,
        |_args, _ctx| Ok(Box::new(HelpPagePrevCmd(vec![]))),
    );

    // ── Navigation (unified Move) ──────────────────────────────────────
    r.register(
        &Move {
            kind: MoveKind::Relative(0, 0),
            cursor: CursorState::default(),
            cmd_name: "move-selection",
        },
        |args| {
            require_args("move-selection", args, 2)?;
            let dr = args[0].parse::<i32>().map_err(|e| e.to_string())?;
            let dc = args[1].parse::<i32>().map_err(|e| e.to_string())?;
            Ok(Box::new(Move {
                kind: MoveKind::Relative(dr, dc),
                cursor: CursorState::default(),
                cmd_name: "move-selection",
            }))
        },
        |args, ctx| {
            require_args("move-selection", args, 2)?;
            let dr = args[0].parse::<i32>().map_err(|e| e.to_string())?;
            let dc = args[1].parse::<i32>().map_err(|e| e.to_string())?;
            Ok(Box::new(Move {
                kind: MoveKind::Relative(dr, dc),
                cursor: CursorState::from_ctx(ctx),
                cmd_name: "move-selection",
            }))
        },
    );
    // Jump-to-edge commands: first/last row/col
    macro_rules! reg_jump {
        ($r:expr, $is_row:expr, $to_end:expr, $name:expr) => {
            $r.register(
                &Move {
                    kind: if $to_end {
                        MoveKind::ToEnd($is_row)
                    } else {
                        MoveKind::ToStart($is_row)
                    },
                    cursor: CursorState::default(),
                    cmd_name: $name,
                },
                |_| {
                    Ok(Box::new(Move {
                        kind: if $to_end {
                            MoveKind::ToEnd($is_row)
                        } else {
                            MoveKind::ToStart($is_row)
                        },
                        cursor: CursorState::default(),
                        cmd_name: $name,
                    }))
                },
                |_, ctx| {
                    Ok(Box::new(Move {
                        kind: if $to_end {
                            MoveKind::ToEnd($is_row)
                        } else {
                            MoveKind::ToStart($is_row)
                        },
                        cursor: CursorState::from_ctx(ctx),
                        cmd_name: $name,
                    }))
                },
            );
        };
    }
    reg_jump!(r, true, false, "jump-first-row");
    reg_jump!(r, true, true, "jump-last-row");
    reg_jump!(r, false, false, "jump-first-col");
    reg_jump!(r, false, true, "jump-last-col");
    r.register(
        &Move {
            kind: MoveKind::Relative(0, 0),
            cursor: CursorState::default(),
            cmd_name: "scroll-rows",
        },
        |args| {
            require_args("scroll-rows", args, 1)?;
            let n = args[0].parse::<i32>().map_err(|e| e.to_string())?;
            Ok(Box::new(Move {
                kind: MoveKind::Relative(n, 0),
                cursor: CursorState::default(),
                cmd_name: "scroll-rows",
            }))
        },
        |args, ctx| {
            require_args("scroll-rows", args, 1)?;
            let n = args[0].parse::<i32>().map_err(|e| e.to_string())?;
            Ok(Box::new(Move {
                kind: MoveKind::Relative(n, 0),
                cursor: CursorState::from_ctx(ctx),
                cmd_name: "scroll-rows",
            }))
        },
    );
    r.register(
        &Move {
            kind: MoveKind::Page(0),
            cursor: CursorState::default(),
            cmd_name: "page-scroll",
        },
        |args| {
            require_args("page-scroll", args, 1)?;
            let dir = args[0].parse::<i32>().map_err(|e| e.to_string())?;
            Ok(Box::new(Move {
                kind: MoveKind::Page(dir),
                cursor: CursorState::default(),
                cmd_name: "page-scroll",
            }))
        },
        |args, ctx| {
            require_args("page-scroll", args, 1)?;
            let dir = args[0].parse::<i32>().map_err(|e| e.to_string())?;
            Ok(Box::new(Move {
                kind: MoveKind::Page(dir),
                cursor: CursorState::from_ctx(ctx),
                cmd_name: "page-scroll",
            }))
        },
    );
    r.register(
        &EnterAdvance {
            cursor: CursorState::default(),
        },
        |_| {
            Ok(Box::new(EnterAdvance {
                cursor: CursorState {
                    row: 0,
                    col: 0,
                    row_count: 0,
                    col_count: 0,
                    row_offset: 0,
                    col_offset: 0,
                    visible_rows: 20,
                    visible_cols: 8,
                },
            }))
        },
        |_, ctx| {
            Ok(Box::new(EnterAdvance {
                cursor: CursorState::from_ctx(ctx),
            }))
        },
    );

    // ── Cell operations ──────────────────────────────────────────────────
    r.register(
        &YankCell {
            key: CellKey::new(vec![]),
        },
        |args| {
            if args.is_empty() {
                return Err("yank requires at least one Cat/Item coordinate".into());
            }
            Ok(Box::new(YankCell {
                key: parse_cell_key_from_args(args),
            }))
        },
        |_args, ctx| {
            let key = ctx.cell_key().clone().ok_or("no cell at cursor")?;
            Ok(Box::new(YankCell { key }))
        },
    );
    r.register(
        &PasteCell {
            key: CellKey::new(vec![]),
        },
        |args| {
            if args.is_empty() {
                return Err("paste requires at least one Cat/Item coordinate".into());
            }
            Ok(Box::new(PasteCell {
                key: parse_cell_key_from_args(args),
            }))
        },
        |_args, ctx| {
            let key = ctx.cell_key().clone().ok_or("no cell at cursor")?;
            Ok(Box::new(PasteCell { key }))
        },
    );
    // clear-cell is registered above (unified: ctx.cell_key() or explicit coords)

    // ── View / page ──────────────────────────────────────────────────────
    r.register_nullary(|| Box::new(TransposeAxes));
    r.register_nullary(|| Box::new(PageNext));
    r.register_nullary(|| Box::new(PagePrev));

    // ── Mode changes ─────────────────────────────────────────────────────
    r.register_nullary(|| Box::new(ForceQuit));
    r.register_nullary(|| Box::new(Quit));
    r.register_nullary(|| Box::new(SaveAndQuit));
    r.register_nullary(|| Box::new(SaveCmd));
    r.register_nullary(|| Box::new(EnterSearchMode));
    r.register_pure(&NamedCmd("edit-or-drill"), |args| {
        require_args("edit-or-drill", args, 1)?;
        let edit_mode = parse_mode_name(&args[0])?;
        Ok(Box::new(EditOrDrill { edit_mode }))
    });
    r.register_pure(&NamedCmd("enter-edit-at-cursor"), |args| {
        require_args("enter-edit-at-cursor", args, 1)?;
        let target_mode = parse_mode_name(&args[0])?;
        Ok(Box::new(EnterEditAtCursorCmd { target_mode }))
    });
    r.register_nullary(|| Box::new(EnterExportPrompt));
    r.register_nullary(|| Box::new(EnterFormulaEdit));
    r.register_nullary(|| Box::new(EnterTileSelect));
    r.register(
        &DrillIntoCell {
            key: crate::model::cell::CellKey::new(vec![]),
        },
        |args| {
            if args.is_empty() {
                return Err("drill-into-cell requires Cat/Item coordinates".into());
            }
            Ok(Box::new(DrillIntoCell {
                key: parse_cell_key_from_args(args),
            }))
        },
        |_args, ctx| {
            let key = ctx.cell_key().clone().ok_or("no cell at cursor")?;
            Ok(Box::new(DrillIntoCell { key }))
        },
    );
    r.register_nullary(|| Box::new(ViewNavigate { forward: false }));
    r.register(
        &ViewNavigate { forward: true },
        |_| Ok(Box::new(ViewNavigate { forward: true })),
        |_, _| Ok(Box::new(ViewNavigate { forward: true })),
    );
    r.register_pure(&NamedCmd("enter-mode"), |args| {
        require_args("enter-mode", args, 1)?;
        Ok(Box::new(EnterMode(parse_mode_name(&args[0])?)))
    });

    // ── Search ───────────────────────────────────────────────────────────
    r.register_pure(&NamedCmd("search-navigate"), |args| {
        let forward = args.first().map(|s| s != "backward").unwrap_or(true);
        Ok(Box::new(SearchNavigate(forward)))
    });
    r.register_nullary(|| Box::new(SearchOrCategoryAdd));
    r.register_nullary(|| Box::new(ExitSearchMode));

    // ── Panel operations ─────────────────────────────────────────────────
    r.register(
        &TogglePanelAndFocus {
            panel: Panel::Formula,
            open: true,
            focused: true,
        },
        |args| {
            // Parse: toggle-panel-and-focus <panel> [open] [focused]
            require_args("toggle-panel-and-focus", args, 1)?;
            let panel = parse_panel(&args[0])?;
            let open = args.get(1).map(|s| s == "true").unwrap_or(true);
            let focused = args.get(2).map(|s| s == "true").unwrap_or(open);
            Ok(Box::new(TogglePanelAndFocus {
                panel,
                open,
                focused,
            }))
        },
        |args, ctx| {
            require_args("toggle-panel-and-focus", args, 1)?;
            let panel = parse_panel(&args[0])?;
            // Default interactive: if already open+focused -> close, else open+focus
            let currently_open = match panel {
                Panel::Formula => ctx.formula_panel_open,
                Panel::Category => ctx.category_panel_open,
                Panel::View => ctx.view_panel_open,
            };
            let currently_focused = match panel {
                Panel::Formula => matches!(
                    ctx.mode,
                    AppMode::FormulaPanel | AppMode::FormulaEdit { .. }
                ),
                Panel::Category => matches!(
                    ctx.mode,
                    AppMode::CategoryPanel | AppMode::CategoryAdd { .. } | AppMode::ItemAdd { .. }
                ),
                Panel::View => matches!(ctx.mode, AppMode::ViewPanel),
            };
            let (open, focused) = if currently_open && currently_focused {
                (false, false) // close
            } else {
                (true, true) // open + focus
            };
            Ok(Box::new(TogglePanelAndFocus {
                panel,
                open,
                focused,
            }))
        },
    );
    r.register(
        &TogglePanelVisibility {
            panel: Panel::Formula,
            currently_open: false,
        },
        |args| {
            require_args("toggle-panel-visibility", args, 1)?;
            let panel = parse_panel(&args[0])?;
            Ok(Box::new(TogglePanelVisibility {
                panel,
                currently_open: false,
            }))
        },
        |args, ctx| {
            require_args("toggle-panel-visibility", args, 1)?;
            let panel = parse_panel(&args[0])?;
            let currently_open = match panel {
                Panel::Formula => ctx.formula_panel_open,
                Panel::Category => ctx.category_panel_open,
                Panel::View => ctx.view_panel_open,
            };
            Ok(Box::new(TogglePanelVisibility {
                panel,
                currently_open,
            }))
        },
    );
    r.register(
        &CyclePanelFocus {
            formula_open: false,
            category_open: false,
            view_open: false,
        },
        |_| {
            Ok(Box::new(CyclePanelFocus {
                formula_open: false,
                category_open: false,
                view_open: false,
            }))
        },
        |_, ctx| {
            Ok(Box::new(CyclePanelFocus {
                formula_open: ctx.formula_panel_open,
                category_open: ctx.category_panel_open,
                view_open: ctx.view_panel_open,
            }))
        },
    );
    r.register(
        &MovePanelCursor {
            panel: Panel::Formula,
            delta: 0,
            current: 0,
            max: 0,
        },
        |args| {
            require_args("move-panel-cursor", args, 2)?;
            let panel = parse_panel(&args[0])?;
            let delta = args[1].parse::<i32>().map_err(|e| e.to_string())?;
            Ok(Box::new(MovePanelCursor {
                panel,
                delta,
                current: 0,
                max: 0,
            }))
        },
        |args, ctx| {
            require_args("move-panel-cursor", args, 2)?;
            let panel = parse_panel(&args[0])?;
            let delta = args[1].parse::<i32>().map_err(|e| e.to_string())?;
            let (current, max) = match panel {
                Panel::Formula => (ctx.formula_cursor, ctx.model.formulas().len()),
                Panel::Category => (ctx.cat_panel_cursor, ctx.cat_tree_len()),
                Panel::View => (ctx.view_panel_cursor, ctx.workbook.views.len()),
            };
            Ok(Box::new(MovePanelCursor {
                panel,
                delta,
                current,
                max,
            }))
        },
    );
    r.register_nullary(|| Box::new(DeleteFormulaAtCursor));
    r.register_nullary(|| Box::new(AddRecordRow));
    r.register_nullary(|| Box::new(TogglePruneEmpty));
    r.register_nullary(|| Box::new(ToggleRecordsMode));
    r.register_nullary(|| Box::new(CycleAxisAtCursor));
    r.register_nullary(|| Box::new(OpenItemAddAtCursor));
    r.register_nullary(|| Box::new(DeleteCategoryAtCursor));
    r.register_nullary(|| Box::new(ToggleCatExpand));
    r.register_nullary(|| Box::new(FilterToItem));
    r.register_nullary(|| Box::new(SwitchViewAtCursor));
    r.register_nullary(|| Box::new(CreateAndSwitchView));
    r.register_nullary(|| Box::new(DeleteViewAtCursor));

    // ── Tile select ──────────────────────────────────────────────────────
    r.register_pure(&NamedCmd("move-tile-cursor"), |args| {
        require_args("move-tile-cursor", args, 1)?;
        let delta = args[0].parse::<i32>().map_err(|e| e.to_string())?;
        Ok(Box::new(MoveTileCursor(delta)))
    });
    r.register_nullary(|| Box::new(TileAxisOp { axis: None }));
    r.register_pure(&NamedCmd("set-axis-for-tile"), |args| {
        require_args("set-axis-for-tile", args, 1)?;
        let axis = parse_axis(&args[0])?;
        Ok(Box::new(TileAxisOp { axis: Some(axis) }))
    });

    // ── Grid operations ──────────────────────────────────────────────────
    r.register_nullary(|| Box::new(ToggleGroupAtCursor { is_row: true }));
    r.register(
        &ToggleGroupAtCursor { is_row: false },
        |_| Ok(Box::new(ToggleGroupAtCursor { is_row: false })),
        |_, _| Ok(Box::new(ToggleGroupAtCursor { is_row: false })),
    );
    r.register_nullary(|| Box::new(HideSelectedRowItem));

    // ── Text buffer ──────────────────────────────────────────────────────
    r.register_pure(&NamedCmd("append-char"), |args| {
        require_args("append-char", args, 1)?;
        Ok(Box::new(AppendChar {
            buffer: args[0].clone(),
        }))
    });
    r.register_pure(&NamedCmd("pop-char"), |args| {
        require_args("pop-char", args, 1)?;
        Ok(Box::new(PopChar {
            buffer: args[0].clone(),
        }))
    });
    r.register_nullary(|| Box::new(CommandModeBackspace));

    // ── Commit ───────────────────────────────────────────────────────────
    // commit-cell-edit / commit-and-advance-right take a mode-name arg
    // (e.g. "editing" or "records-editing") as args[0]. The keymap supplies
    // it; the command never inspects ctx.mode.
    r.register(
        &CommitAndAdvance {
            key: CellKey::new(vec![]),
            value: String::new(),
            advance: AdvanceDir::Down,
            cursor: CursorState::default(),
            edit_mode: AppMode::editing(),
        },
        |args| {
            if args.len() < 3 {
                return Err("commit-cell-edit requires a mode, value, and coords".into());
            }
            let edit_mode = parse_mode_name(&args[0])?;
            Ok(Box::new(CommitAndAdvance {
                key: parse_cell_key_from_args(&args[2..]),
                value: args[1].clone(),
                advance: AdvanceDir::Down,
                cursor: CursorState::default(),
                edit_mode,
            }))
        },
        |args, ctx| {
            require_args("commit-cell-edit", args, 1)?;
            let edit_mode = parse_mode_name(&args[0])?;
            let value = read_buffer(ctx, "edit");
            let key = ctx.cell_key().clone().ok_or("no cell at cursor")?;
            Ok(Box::new(CommitAndAdvance {
                key,
                value,
                advance: AdvanceDir::Down,
                cursor: CursorState::from_ctx(ctx),
                edit_mode,
            }))
        },
    );
    r.register(
        &CommitAndAdvance {
            key: CellKey::new(vec![]),
            value: String::new(),
            advance: AdvanceDir::Right,
            cursor: CursorState::default(),
            edit_mode: AppMode::editing(),
        },
        |_| Err("commit-and-advance-right requires context".into()),
        |args, ctx| {
            require_args("commit-and-advance-right", args, 1)?;
            let edit_mode = parse_mode_name(&args[0])?;
            let value = read_buffer(ctx, "edit");
            let key = ctx.cell_key().clone().ok_or("no cell at cursor")?;
            Ok(Box::new(CommitAndAdvance {
                key,
                value,
                advance: AdvanceDir::Right,
                cursor: CursorState::from_ctx(ctx),
                edit_mode,
            }))
        },
    );
    r.register_nullary(|| Box::new(CommitFormula));
    r.register_nullary(|| Box::new(CommitCategoryAdd));
    r.register_nullary(|| Box::new(CommitItemAdd));
    r.register_nullary(|| Box::new(CommitExport));
    r.register_nullary(|| Box::new(ExecuteCommand));

    // ── Wizard ───────────────────────────────────────────────────────────
    r.register_nullary(|| Box::new(HandleWizardKey));

    // ── Aliases (short names for common commands) ────────────────────────
    r.alias("add-cat", "add-category");
    r.alias("formula", "add-formula");
    r.alias("add-view", "create-view");
    r.alias("q!", "force-quit");

    r
}
