//! Built-in commands.
//!
//! Handlers are pure functions: read-only context, return `Vec<Operation>`.
//! All state mutation happens downstream when operations are applied by views.

use crate::app_state::{Screen, ScreenKind};
use crate::commands::{ArgKind, CommandArg, CommandRegistry, command};
use crate::issue_registry::Severity;
use crate::operation::{ClipOp, GoToLineOp, Operation, PersistentIssueOp, SearchOp, SelectionOp};
use crate::persistent_issues::PersistentNewIssue;
use crate::vcs::Vcs;

pub fn register_all(registry: &mut CommandRegistry) {
    register_app(registry);
    register_search(registry);
    register_view(registry);
    register_editor(registry);
    register_file_selector(registry);
    register_debug(registry);
    register_vcs(registry);
    register_todos(registry);
}

/// Register task definitions (from `.oo/tasks.yaml`) as user-visible commands.
///
/// Each shell task becomes a command with id `task.<task_id>`. Invoking the
/// command schedules the corresponding task via `Operation::ScheduleTask`.
pub fn register_tasks(registry: &mut CommandRegistry, app: &crate::app_state::AppState) {
    if let Some(cfg) = &app.task_config {
        register_tasks_from_tasksfile(registry, cfg);
    }
}

/// Register commands from a parsed TasksFile.
pub fn register_tasks_from_tasksfile(registry: &mut CommandRegistry, cfg: &crate::task_config::TasksFile) {
    use std::borrow::Cow;
    use std::collections::HashMap;
    use crate::task_config::{TaskDef, InputType};
    use crate::task_registry::{TaskKey, TaskQueueId, TaskTrigger};
    use crate::commands::ArgValue as CmdArgValue;

    for (task_id, task_def) in cfg.iter() {
        if let TaskDef::Shell(shell) = task_def {
            // Build a dynamic CommandId: group = "task", name = task_id
            let id = crate::commands::id::CommandId {
                group: Cow::Owned("task".to_string()),
                name: Cow::Owned(task_id.to_string()),
            };

            let title = shell
                .ui
                .as_ref()
                .and_then(|u| u.title.clone())
                .unwrap_or_else(|| format!("Run {}", task_id));

            let description = shell
                .ui
                .as_ref()
                .and_then(|u| u.description.clone())
                .unwrap_or_default();

            let mut builder = crate::commands::command(id)
                .title(title)
                .description(description)
                .context("global");

            // Register command args from declared inputs
            if let Some(inputs) = &shell.inputs {
                for (name, input_def) in inputs {
                    let arg_kind = match input_def.input_type {
                        InputType::Text => crate::commands::ArgKind::String,
                        InputType::Select => {
                            let opts = input_def
                                .options
                                .as_ref()
                                .map(|v| v.iter().map(|s| s.clone().into()).collect::<Vec<std::borrow::Cow<'static, str>>>())
                                .unwrap_or_default();
                            crate::commands::ArgKind::Choice(opts)
                        }
                        InputType::Boolean => crate::commands::ArgKind::Bool,
                        InputType::Number => crate::commands::ArgKind::Int,
                    };
                    let desc = input_def.placeholder.clone().unwrap_or_default();
                    let required = input_def.default.is_none();
                    builder = builder.arg(crate::commands::CommandArg::new(
                        name.clone(),
                        desc,
                        arg_kind,
                        required,
                    ));
                }
            }

            // Clone values for the handler closure
            let shell_clone = shell.clone();
            let task_id_owned = task_id.to_string();

            let cmd = builder.handler(move |_app, args| {
                // Compute input values: prefer provided args, fall back to defaults
                let mut values: HashMap<String, String> = HashMap::new();
                if let Some(inputs_def) = &shell_clone.inputs {
                    for (input_name, input_def) in inputs_def {
                        let val = match args.get(input_name.as_str()) {
                            Some(v) => match v {
                                CmdArgValue::String(s) => s.clone(),
                                CmdArgValue::Bool(b) => b.to_string(),
                                CmdArgValue::Int(i) => i.to_string(),
                                CmdArgValue::FilePath(p) => p.to_string_lossy().into_owned(),
                            },
                            None => input_def
                                .default
                                .as_ref()
                                .map(|d| d.to_string_value())
                                .unwrap_or_default(),
                        };
                        values.insert(input_name.clone(), val);
                    }
                }

                // Interpolate command template
                let mut command_str = shell_clone.command.clone();
                for (k, v) in &values {
                    command_str = command_str.replace(&format!("{{{}}}", k), v);
                }

                let queue = shell_clone.effective_queue(&task_id_owned).to_string();
                let key = TaskKey {
                    queue: TaskQueueId(queue),
                    target: task_id_owned.clone(),
                };

                vec![crate::operation::Operation::ScheduleTask {
                    key,
                    trigger: TaskTrigger::Manual,
                    command: command_str,
                }]
            });

            registry.register(cmd);
        }
    }
}

fn register_app(registry: &mut CommandRegistry) {
    registry.register(
        command("app.quit")
            .title("Quit")
            .description("Exit the application")
            .context("global")
            .handler(|_, _| vec![Operation::Quit]),
    );
}

fn register_search(registry: &mut CommandRegistry) {
    registry.register(
        command("search.open_global")
            .title("Global Search")
            .description("Open the project-wide search in the editor (Expanded mode)")
            .context("global")
            .handler(|_, _| {
                vec![
                    Operation::SwitchScreen { to: ScreenKind::Editor },
                    Operation::SearchLocal(SearchOp::Open { replace: false }),
                ]
            }),
    );
}

fn register_view(registry: &mut CommandRegistry) {
    registry.register(
        command("view.open_file_selector")
            .title("Open File Selector")
            .description("Switch to the file selector screen")
            .context("global")
            .hidden()
            .handler(|_, _| {
                vec![Operation::SwitchScreen {
                    to: ScreenKind::FileSelector,
                }]
            }),
    );

    registry.register(
        command("view.open_command_runner")
            .title("Open Command Runner")
            .description("Open the command palette to search and run commands")
            .context("global")
            .hidden()
            .handler(|_, _| {
                vec![Operation::SwitchScreen {
                    to: ScreenKind::CommandRunner,
                }]
            }),
    );

    registry.register(
        command("terminal.open")
            .title("Open Terminal")
            .description("Switch to the integrated terminal view")
            .context("global")
            .handler(|_, _| {
                vec![Operation::SwitchScreen {
                    to: ScreenKind::Terminal,
                }]
            }),
    );

    registry.register(
        command("view.open_task_archive")
            .title("Open Task Archive")
            .description("Open the Task Archive view to browse running and finished tasks")
            .context("global")
            .handler(|_, _| {
                vec![Operation::SwitchScreen { to: ScreenKind::TaskArchive }]
            }),
    );

    registry.register(
        command("view.open_issues")
            .title("Open Issue List")
            .description("Open the Issue List view to browse all diagnostics and TODOs")
            .context("global")
            .handler(|_, _| {
                vec![Operation::OpenIssueList]
            }),
    );

    registry.register(
        command("todo.view")
            .title("Open TODO List")
            .description("Open the Issue List view to browse TODOs and diagnostics")
            .context("global")
            .handler(|_, _| {
                vec![Operation::OpenIssueList]
            }),
    );

    registry.register(
        command("terminal.open_tab_selector")
            .title("Select Terminal Tab")
            .description("Open the tab switcher in the terminal view")
            .context("terminal")
            .hidden()
            .handler(|_, _| {
                vec![Operation::TerminalLocal(
                    crate::operation::TerminalOp::OpenTabSelector,
                )]
            }),
    );

    registry.register(
        command("terminal.new_tab")
            .title("New Terminal Tab")
            .description("Open a new terminal tab with the default shell")
            .context("terminal")
            .handler(|_, _| {
                vec![Operation::TerminalLocal(
                    crate::operation::TerminalOp::NewTab { command: None },
                )]
            }),
    );

    registry.register(
        command("terminal.close_active_tab")
            .title("Close Terminal Tab")
            .description("Close the currently active terminal tab")
            .context("terminal")
            .hidden()
            .handler(|app, _| {
                if let Screen::Terminal(tv) = &app.screen
                    && let Some(id) = tv.tabs.get(tv.active).map(|t| t.id) {
                        return vec![Operation::TerminalLocal(
                            crate::operation::TerminalOp::CloseTab { id },
                        )];
                    }
                vec![]
            }),
    );

    registry.register(
        command("terminal.rename_active_tab")
            .title("Rename Terminal Tab")
            .description("Rename the currently active terminal tab")
            .context("terminal")
            .hidden()
            .handler(|app, _| {
                if let Screen::Terminal(tv) = &app.screen
                    && let Some(id) = tv.tabs.get(tv.active).map(|t| t.id) {
                        return vec![Operation::TerminalLocal(
                            crate::operation::TerminalOp::OpenRename { id },
                        )];
                    }
                vec![]
            }),
    );

    registry.register(
        command("terminal.interrupt")
            .title("Send Ctrl+C")
            .description("Send interrupt signal (^C / ETX) to the active terminal process")
            .context("terminal")
            .hidden()
            .handler(|app, _| {
                if let Screen::Terminal(tv) = &app.screen
                    && let Some(id) = tv.active_id()
                {
                    return vec![
                        Operation::TerminalLocal(crate::operation::TerminalOp::ScrollReset),
                        Operation::TerminalInput { id, data: vec![0x03] },
                    ];
                }
                vec![]
            }),
    );

    registry.register(
        command("terminal.clear_active_tab")
            .title("Clear Terminal")
            .description("Clear the screen and scrollback of the active terminal tab")
            .context("terminal")
            .handler(|app, _| {
                if let Screen::Terminal(tv) = &app.screen
                    && let Some(id) = tv.tabs.get(tv.active).map(|t| t.id) {
                        return vec![Operation::TerminalLocal(
                            crate::operation::TerminalOp::ClearTab { id },
                        )];
                    }
                vec![]
            }),
    );

    registry.register(
        command("extensions.config")
            .title("Extensions Settings")
            .description("Configure installed extensions")
            .context("global")
            .handler(|_, _| {
                vec![Operation::SwitchScreen {
                    to: ScreenKind::ExtensionConfig,
                }]
            }),
    );

    registry.register(
        command("app.close_modal")
            .title("Close / Dismiss")
            .description("Close the topmost overlay, sub-view, or modal (completion menu, search bar, detail pane, …)")
            .context("global")
            .hidden()
            .handler(|_, _| {
                vec![Operation::Close]
            }),
    );

    registry.register(
        command("app.go_to")
            .title("Go To")
            .description("Go to symbol definition or file line depending on context")
            .context("global")
            .hidden()
            .handler(|app, _| {
                use crate::views::commit::CommitTab;
                match &app.screen {
                    Screen::Editor(_) => vec![Operation::GoTo {
                        target: crate::operation::GoToTarget::LspRequest,
                    }],
                    Screen::CommitWindow(cw) => {
                        // If in Diff tab, jump to the corresponding file/line
                        if cw.tab == CommitTab::Diff
                            && let Some(path) = cw.diff_file.clone() {
                                let line = cw.diff_jump_line();
                                return vec![Operation::GoTo {
                                    target: crate::operation::GoToTarget::FileLine {
                                        path,
                                        line,
                                        col: None,
                                    },
                                }];
                            }
                        // Fallback: open selected file if any
                        if let Some(path) = cw.selected_file_path() {
                            return vec![Operation::GoTo {
                                target: crate::operation::GoToTarget::FileLine {
                                    path,
                                    line: 0,
                                    col: None,
                                },
                            }];
                        }
                        vec![]
                    }
                    Screen::FileSelector(fs) => {
                        // open selected file if available
                        if let Some(path) = fs.selected_path() {
                            return vec![Operation::GoTo {
                                target: crate::operation::GoToTarget::FileLine {
                                    path: path.clone(),
                                    line: 0,
                                    col: None,
                                },
                            }];
                        }
                        vec![]
                    }
                    Screen::GitHistory(gh) => {
                        // If diff pane has a selected line, open that file at the line.
                        if let Some(path) = gh.selected_file_path().map(|p| p.to_path_buf()) {
                            if let Some(diff) = &gh.diff
                                && let Some(line_no) = diff.selected_line_no() {
                                    return vec![Operation::GoTo {
                                        target: crate::operation::GoToTarget::FileLine {
                                            path,
                                            line: line_no.saturating_sub(1),
                                            col: None,
                                        },
                                    }];
                                }
                            // Fallback: open the file at start
                            return vec![Operation::GoTo {
                                target: crate::operation::GoToTarget::FileLine {
                                    path,
                                    line: 0,
                                    col: None,
                                },
                            }];
                        }
                        vec![]
                    }
                    _ => vec![Operation::GoTo {
                        target: crate::operation::GoToTarget::LspRequest,
                    }],
                }
            }),
    );
}

fn register_editor(registry: &mut CommandRegistry) {
    registry.register(
        command("editor.save")
            .title("Save File")
            .description("Write the current buffer to disk")
            .context("editor")
            .handler(|app, _| {
                if let Screen::Editor(ed) = &app.screen
                    && let Some(path) = ed.buffer.path.clone()
                {
                    return vec![Operation::SaveFile { path }];
                }
                vec![]
            }),
    );

    registry.register(
        command("editor.reload")
            .title("Reload File")
            .description("Reload the current file from disk")
            .context("editor")
            .handler(|app, _| {
                if let Screen::Editor(ed) = &app.screen
                    && let Some(path) = ed.buffer.path.clone()
                {
                    return vec![Operation::ReloadFromDisk {
                        path,
                        accept_external: false,
                        silent: false,
                    }];
                }
                vec![]
            }),
    );

    registry.register(
        command("editor.undo")
            .title("Undo")
            .description("Undo the last edit")
            .context("editor")
            .handler(|app, _| {
                if let Screen::Editor(ed) = &app.screen {
                    vec![Operation::Undo {
                        path: ed.buffer.path.clone(),
                    }]
                } else {
                    vec![]
                }
            }),
    );

    registry.register(
        command("editor.redo")
            .title("Redo")
            .description("Redo the last undone edit")
            .context("editor")
            .handler(|app, _| {
                if let Screen::Editor(ed) = &app.screen {
                    vec![Operation::Redo {
                        path: ed.buffer.path.clone(),
                    }]
                } else {
                    vec![]
                }
            }),
    );

    registry.register(
        command("editor.toggle_fold")
            .title("Toggle Fold")
            .description("Collapse or expand the fold at the cursor line")
            .context("editor")
            .handler(|app, _| {
                if let Screen::Editor(ed) = &app.screen {
                    vec![Operation::ToggleFold {
                        path: ed.buffer.path.clone(),
                        line: ed.buffer.cursor().line,
                    }]
                } else {
                    vec![]
                }
            }),
    );

    registry.register(
        command("editor.toggle_marker")
            .title("Toggle Marker")
            .description("Place or remove a marker on the cursor line")
            .context("editor")
            .handler(|app, _| {
                if let Screen::Editor(ed) = &app.screen {
                    vec![Operation::ToggleMarker {
                        path: ed.buffer.path.clone(),
                        line: ed.buffer.cursor().line,
                    }]
                } else {
                    vec![]
                }
            }),
    );

    registry.register(
        command("editor.copy")
            .title("Copy")
            .description("Copy selected text (or current line) to clipboard")
            .context("editor")
            .handler(|_, _| vec![Operation::ClipboardLocal(ClipOp::Copy)]),
    );

    registry.register(
        command("editor.cut")
            .title("Cut")
            .description("Cut selected text (or current line) to clipboard")
            .context("editor")
            .handler(|_, _| vec![Operation::ClipboardLocal(ClipOp::Cut)]),
    );

    registry.register(
        command("editor.paste")
            .title("Paste")
            .description("Paste from system clipboard")
            .context("editor")
            .handler(|_, _| vec![Operation::ClipboardLocal(ClipOp::Paste)]),
    );

    registry.register(
        command("paste.history")
            .title("Paste from History")
            .description("Open clipboard history picker to paste a previous entry")
            .context("editor")
            .handler(|_, _| vec![Operation::ClipboardLocal(ClipOp::OpenHistory)]),
    );
    registry.register(
        command("editor.find")
            .title("Find")
            .description("Open the find bar")
            .context("editor")
            .handler(|_, _| vec![Operation::SearchLocal(SearchOp::Open { replace: false })]),
    );

    registry.register(
        command("editor.find_replace")
            .title("Find & Replace")
            .description("Open the find and replace bar")
            .context("editor")
            .handler(|_, _| vec![Operation::SearchLocal(SearchOp::Open { replace: true })]),
    );

    registry.register(
        command("editor.find_next")
            .title("Find Next")
            .description("Jump to the next search match")
            .context("editor")
            .handler(|_, _| vec![Operation::SearchLocal(SearchOp::NextMatch)]),
    );

    registry.register(
        command("editor.find_prev")
            .title("Find Previous")
            .description("Jump to the previous search match")
            .context("editor")
            .handler(|_, _| vec![Operation::SearchLocal(SearchOp::PrevMatch)]),
    );

    registry.register(
        command("editor.go_to_line")
            .title("Go to Line")
            .description("Jump to a specific line number")
            .context("editor")
            .handler(|_, _| vec![Operation::GoToLineLocal(GoToLineOp::Open)]),
    );

    registry.register(
        command("editor.toggle_word_wrap")
            .title("Toggle Word Wrap")
            .description("Toggle soft word-wrapping of long lines")
            .context("editor")
            .handler(|_, _| vec![Operation::ToggleWordWrap]),
    );

    registry.register(
        command("editor.delete_word_backward")
            .title("Delete Word Backward")
            .description("Delete from the start of the previous word to the cursor")
            .context("editor")
            .handler(|app, _| {
                if let Screen::Editor(ed) = &app.screen {
                    vec![Operation::DeleteWordBackward {
                        path: ed.buffer.path.clone(),
                    }]
                } else {
                    vec![]
                }
            }),
    );

    registry.register(
        command("editor.delete_word_forward")
            .title("Delete Word Forward")
            .description("Delete from the cursor to the start of the next word")
            .context("editor")
            .handler(|app, _| {
                if let Screen::Editor(ed) = &app.screen {
                    vec![Operation::DeleteWordForward {
                        path: ed.buffer.path.clone(),
                    }]
                } else {
                    vec![]
                }
            }),
    );

    registry.register(
        command("editor.delete_line")
            .title("Delete Line")
            .description("Delete the current line")
            .context("editor")
            .handler(|app, _| {
                if let Screen::Editor(ed) = &app.screen {
                    vec![Operation::DeleteLine {
                        path: ed.buffer.path.clone(),
                    }]
                } else {
                    vec![]
                }
            }),
    );

    registry.register(
        command("editor.indent")
            .title("Indent Lines")
            .description("Add indentation to current line or selected lines")
            .context("editor")
            .handler(|app, _| {
                if let Screen::Editor(ed) = &app.screen {
                    vec![Operation::IndentLines {
                        path: ed.buffer.path.clone(),
                    }]
                } else {
                    vec![]
                }
            }),
    );

    registry.register(
        command("editor.unindent")
            .title("Unindent Lines")
            .description("Remove indentation from current line or selected lines")
            .context("editor")
            .handler(|app, _| {
                if let Screen::Editor(ed) = &app.screen {
                    vec![Operation::UnindentLines {
                        path: ed.buffer.path.clone(),
                    }]
                } else {
                    vec![]
                }
            }),
    );

    registry.register(
        command("editor.word_next")
            .title("Jump to Next Word")
            .description("Move the cursor to the start of the next word")
            .context("editor")
            .handler(|app, _| {
                if let Screen::Editor(ed) = &app.screen {
                    vec![Operation::MoveCursor {
                        path: ed.buffer.path.clone(),
                        cursor: ed.buffer.word_boundary_next(ed.buffer.cursor()),
                    }]
                } else {
                    vec![]
                }
            }),
    );

    registry.register(
        command("editor.word_prev")
            .title("Jump to Previous Word")
            .description("Move the cursor to the start of the previous word")
            .context("editor")
            .handler(|app, _| {
                if let Screen::Editor(ed) = &app.screen {
                    vec![Operation::MoveCursor {
                        path: ed.buffer.path.clone(),
                        cursor: ed.buffer.word_boundary_prev(ed.buffer.cursor()),
                    }]
                } else {
                    vec![]
                }
            }),
    );

    registry.register(
        command("editor.select_word_next")
            .title("Select to Next Word")
            .description("Extend the selection to the start of the next word")
            .context("editor")
            .handler(|app, _| {
                if let Screen::Editor(ed) = &app.screen {
                    vec![Operation::SelectionLocal(SelectionOp::Extend {
                        head: ed.buffer.word_boundary_next_for_selection(ed.buffer.cursor()),
                    })]
                } else {
                    vec![]
                }
            }),
    );

    registry.register(
        command("editor.select_word_prev")
            .title("Select to Previous Word")
            .description("Extend the selection to the start of the previous word")
            .context("editor")
            .handler(|app, _| {
                if let Screen::Editor(ed) = &app.screen {
                    vec![Operation::SelectionLocal(SelectionOp::Extend {
                        head: ed.buffer.word_boundary_prev(ed.buffer.cursor()),
                    })]
                } else {
                    vec![]
                }
            }),
    );

    registry.register(
        command("editor.clear_selection")
            .title("Clear Selection")
            .description("Deselect the current selection without moving the cursor")
            .context("editor")
            .handler(|_, _| vec![Operation::SelectionLocal(SelectionOp::Clear)]),
    );

    registry.register(
        command("editor.select_all")
            .title("Select All")
            .description("Select all text in the current buffer")
            .context("editor")
            .handler(|_, _| vec![Operation::SelectionLocal(SelectionOp::SelectAll)]),
    );

    registry.register(
        command("editor.toggle_comment")
            .title("Toggle Comment")
            .description("Toggle comment for selected lines or current line")
            .context("editor")
            .handler(|_, _| {
                vec![Operation::ShowNotification {
                    message: "Toggle comment not implemented".into(),
                    level: crate::operation::NotificationLevel::Info,
                }]
            }),
    );

    registry.register(
        command("editor.format_document")
            .title("Format Document")
            .description("Format current buffer (not implemented)")
            .context("editor")
            .handler(|_, _| {
                vec![Operation::ShowNotification {
                    message: "Format not implemented".into(),
                    level: crate::operation::NotificationLevel::Info,
                }]
            }),
    );
}

fn register_debug(registry: &mut CommandRegistry) {
    registry.register(
        command("debug.show_log_view")
            .title("Show Log View")
            .description("Open the IDE log viewer")
            .context("global")
            .handler(|_, _| {
                vec![Operation::SwitchScreen {
                    to: ScreenKind::LogView,
                }]
            }),
    );
}

fn register_file_selector(registry: &mut CommandRegistry) {
    registry.register(
        command("file_selector.open")
            .title("Open Selected File")
            .description("Open the file currently selected in the file selector")
            .context("file_selector")
            .hidden()
            .handler(|app, _| {
                if let Screen::FileSelector(fs) = &app.screen
                    && let Some(path) = fs.selected_path()
                {
                    return vec![Operation::OpenFile { path }];
                }
                vec![]
            }),
    );
}

fn register_vcs(registry: &mut CommandRegistry) {
    registry.register(
        command("vcs.open_commit")
            .title("Open Commit Window")
            .description("Stage files, manage branches, and commit changes  [Ctrl+G]")
            .context("git_repo")
            .handler(|_, _| {
                vec![Operation::SwitchScreen {
                    to: ScreenKind::CommitWindow,
                }]
            }),
    );
    registry.register(
        command("vcs.history")
            .title("Open Git History")
            .description("Browse commit history with filterable revisions and diff viewer")
            .context("git_repo")
            .handler(|_, _| {
                vec![Operation::SwitchScreen {
                    to: ScreenKind::GitHistory,
                }]
            }),
    );

    registry.register(
        command("vcs.history_editor")
            .title("Git: History Editor")
            .description("Interactively reword, move, squash, or drop commits")
            .context("git_repo")
            .handler(|_, _| {
                vec![Operation::SwitchScreen {
                    to: ScreenKind::GitHistoryEditor,
                }]
            }),
    );

    registry.register(
        command("vcs.branches")
            .title("Git: Branches")
            .description("Browse and manage local/remote branches — checkout or create branches")
            .context("git_repo")
            .handler(|_, _| {
                vec![Operation::SwitchScreen {
                    to: ScreenKind::GitBranches,
                }]
            }),
    );

    registry.register(
        command("vcs.open_branch_selector")
            .title("Open Branch Selector")
            .description("Open a branch selector popup to switch branches")
            .context("git_repo")
            .hidden()
            .handler(|_, _| {
                vec![Operation::CommitLocal(crate::operation::CommitOp::BranchSelectorOpen)]
            }),
    );

    registry.register(
        command("vcs.amend_last_commit")
            .title("Amend Last Commit")
            .description("Open Commit Window and load the last commit message for amendment")
            .context("git_repo")
            .handler(|_, _| {
                vec![
                    Operation::SwitchScreen {
                        to: ScreenKind::CommitWindow,
                    },
                    Operation::CommitLocal(crate::operation::CommitOp::EnterAmendMode),
                ]
            }),
    );

    registry.register(
        command("vcs.pull")
            .title("Git: Pull")
            .description(
                "Fetch from remote then fast-forward, rebase, or merge — \
                 your explicit choice when history diverges",
            )
            .context("git_repo")
            .handler(|_, _| {
                vec![Operation::SwitchScreen {
                    to: ScreenKind::GitPull,
                }]
            }),
    );

    registry.register(
        command("vcs.push")
            .title("Git: Push")
            .description("Push current branch to configured remote (background task)")
            .context("git_repo")
            .arg(CommandArg::new("force", "Force push (use --force-with-lease)", ArgKind::Bool, false))
            .handler(|app, args| {
                use crate::commands::ArgValue;
                // Read boolean arg (default: false)
                let force = match args.get("force") {
                    Some(ArgValue::Bool(b)) => *b,
                    _ => false,
                };

                // Use the project root as the target to deduplicate per-repo.
                let root = app.project.project_path.clone();
                let root_str = root.to_string_lossy().into_owned();
                let safe_root = root_str.replace('"', "\\\"");
                // Default to a simple `git push` and try to enrich with branch+remote
                // when possible so the task key is specific and the push is explicit.
                let mut command = if force {
                    format!("git -C \"{}\" push --force-with-lease", safe_root)
                } else {
                    format!("git -C \"{}\" push", safe_root)
                };

                let key = if let Ok(v) = crate::vcs::GitVcs::open(&root) {
                    if let Ok((_branches, current_branch)) = v.branches() {
                        if !current_branch.is_empty() && !current_branch.starts_with('(') {
                            // Try to read configured remote for this branch; fall back to 'origin'.
                            let remote = std::process::Command::new("git")
                                .args(["-C", root_str.as_str(), "config", "--get", &format!("branch.{}.remote", current_branch)])
                                .output()
                                .ok()
                                .and_then(|o| if o.status.success() {
                                    Some(String::from_utf8_lossy(&o.stdout).trim().to_string())
                                } else {
                                    None
                                })
                                .filter(|s| !s.is_empty())
                                .unwrap_or_else(|| "origin".to_string());

                            if force {
                                command = format!("git -C \"{}\" push --force-with-lease {} {}", safe_root, remote, current_branch);
                            } else {
                                command = format!("git -C \"{}\" push {} {}", safe_root, remote, current_branch);
                            }

                            crate::task_registry::TaskKey {
                                queue: crate::task_registry::TaskQueueId("git".into()),
                                target: format!("push:{}:{}{}", root_str, current_branch, if force { ":force" } else { "" }),
                            }
                        } else {
                            crate::task_registry::TaskKey {
                                queue: crate::task_registry::TaskQueueId("git".into()),
                                target: format!("{}{}", root_str, if force { ":force" } else { "" }),
                            }
                        }
                    } else {
                        crate::task_registry::TaskKey {
                            queue: crate::task_registry::TaskQueueId("git".into()),
                            target: format!("{}{}", root_str, if force { ":force" } else { "" }),
                        }
                    }
                } else {
                    crate::task_registry::TaskKey {
                        queue: crate::task_registry::TaskQueueId("git".into()),
                        target: format!("{}{}", root_str, if force { ":force" } else { "" }),
                    }
                };

                vec![Operation::ScheduleTask {
                    key,
                    trigger: crate::task_registry::TaskTrigger::Manual,
                    command,
                }]
            }),
    );
}

fn register_todos(registry: &mut CommandRegistry) {
    registry.register(
        command("todo.create")
            .title("Create TODO")
            .description("Create a new persistent TODO saved to .oo/issues.yaml")
            .context("global")
            .arg(CommandArg::new(
                "title",
                "Short description of the TODO",
                ArgKind::String,
                true,
            ))
            .arg(CommandArg::new(
                "severity",
                "Severity level: info, warning, or error",
                ArgKind::Choice(vec!["info".into(), "warning".into(), "error".into()]),
                false,
            ))
            .handler(|_, args| {
                use crate::commands::ArgValue;
                let Some(ArgValue::String(title)) = args.get("title") else {
                    return vec![];
                };
                let severity = match args.get("severity") {
                    Some(ArgValue::String(s)) => match s.as_str() {
                        "warning" => Severity::Warning,
                        "error" => Severity::Error,
                        _ => Severity::Info,
                    },
                    _ => Severity::Info,
                };
                vec![Operation::PersistentIssueLocal(PersistentIssueOp::Add {
                    issue: PersistentNewIssue {
                        source: "user".into(),
                        path: None,
                        range: None,
                        message: title.clone(),
                        severity,
                    },
                })]
            }),
    );

    registry.register(
        command("todo.resolve")
            .title("Resolve TODO")
            .description("Mark a persistent TODO as resolved")
            .context("global")
            .arg(CommandArg::new(
                "id",
                "Numeric ID of the TODO to resolve",
                ArgKind::Int,
                true,
            ))
            .handler(|_, args| {
                use crate::commands::ArgValue;
                use crate::issue_registry::IssueId;
                let Some(ArgValue::Int(raw_id)) = args.get("id") else {
                    return vec![];
                };
                let id = *raw_id as IssueId;
                vec![Operation::PersistentIssueLocal(PersistentIssueOp::Resolve { id })]
            }),
    );

    registry.register(
        command("todo.dismiss")
            .title("Dismiss TODO")
            .description("Dismiss a persistent TODO without resolving it")
            .context("global")
            .arg(CommandArg::new(
                "id",
                "Numeric ID of the TODO to dismiss",
                ArgKind::Int,
                true,
            ))
            .handler(|_, args| {
                use crate::commands::ArgValue;
                use crate::issue_registry::IssueId;
                let Some(ArgValue::Int(raw_id)) = args.get("id") else {
                    return vec![];
                };
                let id = *raw_id as IssueId;
                vec![Operation::PersistentIssueLocal(PersistentIssueOp::Dismiss { id })]
            }),
    );
}