//! Command runner view.
//!
//! Layout (selector mode):
//!   ┌─ Filter ──────────────────────────────────────┐
//!   │ query_                                        │
//!   ├─ Recent Commands ─────┬─ Commands ────────────┤
//!   │ editor.save           │ > app/               │
//!   │ view.open_file_...    │     quit              │
//!   │ …                     │   editor/             │
//!   └───────────────────────┴───────────────────────┘
//!
//! Layout (arg-form mode):
//!   ┌─ Run: editor.open_file ───────────────────────┐
//!   │  path  [________________________]             │
//!   │  (required · FilePath)                        │
//!   │                                               │
//!   │  [Enter] Run   [Esc] Cancel                   │
//!   └───────────────────────────────────────────────┘

use std::collections::HashMap;

use ratatui::{
    Frame,
    layout::{Constraint, Direction, Layout, Rect},
    style::{Color, Modifier, Style},
    text::{Line, Span},
    widgets::{Block, ListItem, Paragraph},
};

use crate::prelude::*;

use commands::{ArgKind, ArgValue, CommandId, CommandMeta, HistoryEntry, meta};
use input::{Key, KeyEvent, Modifiers, MouseButton, MouseEvent, MouseEventKind};
use operation::{CommandSelectorOp, Event, Operation};
use settings::Settings;
use utils::fuzzy::filter_and_rank_by;
use views::View;
use widgets::{
    focus::FocusRing,
    list::SelectableList,
    pane::TitledPane,
    tree::{FlatNode, TreeNode, TreeWidget},
};
use widgets::focusable::FocusOp;
use widgets::input_field::InputField;

#[derive(Debug)]
enum Mode {
    Selector(SelectorState),
    ArgForm(ArgFormState),
}

const FOCUS_FILTER: &str = "filter";
const FOCUS_HISTORY: &str = "history";
const FOCUS_TREE: &str = "tree";

#[derive(Debug)]
struct SelectorState {
    filter: InputField,
    focus: FocusRing,

    history_all: Vec<CommandId>,
    history_list: SelectableList<CommandId>,

    tree: CommandTreeView,
    tree_all: Vec<CommandId>,
    tree_filtered: SelectableList<CommandId>,
}

impl SelectorState {
    fn new(all_commands: Vec<CommandId>, history: Vec<CommandId>) -> Self {
        let tree = CommandTreeView::from_commands(&all_commands);
        let history_list = SelectableList::new(history.clone());
        let tree_filtered = SelectableList::new(all_commands.clone());

        Self {
            filter: InputField::new("Filter"),
            focus: FocusRing::new(vec![FOCUS_FILTER, FOCUS_HISTORY, FOCUS_TREE]),
            history_all: history,
            history_list,
            tree,
            tree_all: all_commands,
            tree_filtered,
        }
    }

    fn refilter(&mut self) {
        let q = self.filter.text().to_string();
        let id_text = |id: &CommandId| format!("{}.{}", id.group, id.name);

        let hist = filter_and_rank_by(&self.history_all, &q, id_text)
            .into_iter()
            .cloned()
            .collect();
        self.history_list.set_items(hist);

        let tree = filter_and_rank_by(&self.tree_all, &q, id_text)
            .into_iter()
            .cloned()
            .collect();
        self.tree_filtered.set_items(tree);
    }

    /// Which content pane is active (for selection/navigation purposes).
    fn active_pane(&self) -> &str {
        let cur = self.focus.current();
        if cur == FOCUS_TREE {
            FOCUS_TREE
        } else {
            FOCUS_HISTORY
        }
    }

    fn selected_command(&self) -> Option<CommandId> {
        match self.active_pane() {
            FOCUS_HISTORY => self.history_list.selected().cloned(),
            _ => {
                if self.filter.is_empty() {
                    self.tree.selected_leaf()
                } else {
                    self.tree_filtered.selected().cloned()
                }
            }
        }
    }

}

/// Groups commands by their `group` field into a two-level tree:
/// ```text
/// > app/
///     quit
///   editor/
///     save
///     …
/// ```
#[derive(Debug)]
struct CommandTreeView {
    inner: TreeWidget<CommandTreeNode>,
}

#[derive(Debug, Clone)]
enum CommandTreeNode {
    Group(String),      // interior: group name
    Command(CommandId), // leaf: full id
}

impl CommandTreeView {
    fn from_commands(ids: &[CommandId]) -> Self {
        // Collect groups preserving insertion order (already sorted by caller).
        let mut groups: Vec<(String, Vec<CommandId>)> = Vec::new();
        for id in ids {
            if let Some(g) = groups.iter_mut().find(|(g, _)| g == id.group.as_ref()) {
                g.1.push(id.clone());
            } else {
                groups.push((id.group.to_string(), vec![id.clone()]));
            }
        }

        let roots: Vec<TreeNode<CommandTreeNode>> = groups
            .into_iter()
            .map(|(group, cmds)| {
                let children = cmds
                    .into_iter()
                    .map(|id| TreeNode::leaf(CommandTreeNode::Command(id)))
                    .collect();
                let mut node = TreeNode::inner(CommandTreeNode::Group(group), children);
                node.expanded = true; // start expanded
                node
            })
            .collect();

        Self {
            inner: TreeWidget::new(roots),
        }
    }

    fn selected_leaf(&self) -> Option<CommandId> {
        let flat = self.inner.selected()?;
        match &flat.node.data {
            CommandTreeNode::Command(id) => Some(id.clone()),
            CommandTreeNode::Group(_) => None,
        }
    }

    fn key_up(&mut self) {
        self.inner.move_up();
    }
    fn key_down(&mut self) {
        self.inner.move_down();
    }
    fn key_left(&mut self) {
        self.inner.collapse_or_leave();
    }
    fn key_right(&mut self) {
        self.inner.expand_or_enter();
    }

    #[allow(clippy::too_many_arguments)]
    fn render(
        &self,
        frame: &mut Frame,
        area: Rect,
        _active: bool,
        pane_bg: Color,
        sel_bg: Color,
        sel_fg: Color,
        dir_fg: Color,
    ) {
        self.inner.render(area, frame.buffer_mut(), |flat, selected| {
            render_tree_node(flat, selected, pane_bg, sel_bg, sel_fg, dir_fg)
        });
    }
}

fn render_tree_node(
    flat: &FlatNode<'_, CommandTreeNode>,
    selected: bool,
    pane_bg: Color,
    sel_bg: Color,
    sel_fg: Color,
    dir_fg: Color,
) -> Line<'static> {
    let bg = if selected { sel_bg } else { pane_bg };

    match &flat.node.data {
        CommandTreeNode::Group(g) => {
            let icon = if flat.node.expanded { "▾ " } else { "▸ " };
            Line::from(vec![Span::styled(
                format!("{}{}/", icon, g),
                Style::default()
                    .fg(if selected { sel_fg } else { dir_fg })
                    .bg(bg)
                    .add_modifier(Modifier::BOLD),
            )])
        }
        CommandTreeNode::Command(id) => {
            Line::from(vec![
                Span::styled("  ", Style::default().bg(bg)),
                Span::styled(
                    id.name.to_string(),
                    Style::default()
                        .fg(if selected { sel_fg } else { Color::Gray })
                        .bg(bg)
                        .add_modifier(if selected { Modifier::BOLD } else { Modifier::empty() }),
                ),
            ])
        }
    }
}

#[derive(Debug)]
struct ArgFormState {
    command_id: CommandId,
    command_title: String,
    fields: Vec<ArgField>,
    focused: usize,
}

#[derive(Debug)]
struct ArgField {
    name: String,
    description: String,
    kind: ArgKind,
    required: bool,
    value: String,
}

impl ArgFormState {
    fn new(meta: &CommandMeta) -> Self {
        let fields = meta
            .args
            .iter()
            .map(|a| ArgField {
                name: a.name.to_string(),
                description: a.description.to_string(),
                kind: a.kind.clone(),
                required: a.required,
                value: String::new(),
            })
            .collect();

        Self {
            command_id: meta.id.clone(),
            command_title: meta.title.to_string(),
            fields,
            focused: 0,
        }
    }

    fn collect_args(&self) -> HashMap<String, ArgValue> {
        let mut map = HashMap::new();
        for f in &self.fields {
            if f.value.is_empty() {
                continue;
            }
            let val = match &f.kind {
                ArgKind::Bool => f
                    .value
                    .parse::<bool>()
                    .ok()
                    .map(ArgValue::Bool)
                    .unwrap_or(ArgValue::String(f.value.clone())),
                ArgKind::Int => f
                    .value
                    .parse::<i64>()
                    .ok()
                    .map(ArgValue::Int)
                    .unwrap_or(ArgValue::String(f.value.clone())),
                ArgKind::FilePath => ArgValue::FilePath(std::path::PathBuf::from(&f.value)),
                _ => ArgValue::String(f.value.clone()),
            };
            map.insert(f.name.clone(), val);
        }
        map
    }
}

pub struct CommandRunner {
    /// All command ids (sorted) — kept for rebuilding the tree after arg-form.
    all_commands: Vec<CommandId>,
    /// Full metadata — needed to build ArgFormState on selection.
    metas: HashMap<CommandId, CommandMetaSnapshot>,
    mode: Mode,
}

/// Lightweight clone of CommandMeta stored inside the view (avoids keeping a
/// reference to the registry across frames).
struct CommandMetaSnapshot {
    title: String,
    args: Vec<crate::commands::CommandArg>,
}

impl std::fmt::Debug for CommandRunner {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("CommandRunner").finish()
    }
}

impl CommandRunner {
    pub fn new(all_metas: &[&CommandMeta], history: &[HistoryEntry]) -> Self {
        // Sort commands by group then name (same as registry).
        let mut all_commands: Vec<CommandId> = all_metas.iter().map(|m| m.id.clone()).collect();
        all_commands.sort_by(|a, b| a.group.cmp(&b.group).then(a.name.cmp(&b.name)));

        let metas: HashMap<CommandId, CommandMetaSnapshot> = all_metas
            .iter()
            .map(|m| {
                (
                    m.id.clone(),
                    CommandMetaSnapshot {
                        title: m.title.to_string(),
                        args: m.args.clone(),
                    },
                )
            })
            .collect();

        let history_ids: Vec<CommandId> = history.iter().map(|e| e.id.clone()).collect();

        let selector = SelectorState::new(all_commands.clone(), history_ids);

        Self {
            all_commands,
            metas,
            mode: Mode::Selector(selector),
        }
    }

    fn confirm_selection(&self) -> Vec<Operation> {
        let Mode::Selector(sel) = &self.mode else {
            return vec![];
        };
        let Some(id) = sel.selected_command() else {
            // Maybe cursor is on a tree group node — toggle it.
            return vec![Operation::CommandSelectorLocal(
                CommandSelectorOp::CollapseOrLeft,
            )];
        };

        let Some(meta) = self.metas.get(&id) else {
            return vec![];
        };

        if meta.args.is_empty() {
            // No args needed → run immediately.
            vec![Operation::RunCommand {
                id,
                args: HashMap::new(),
            }]
        } else {
            // Has args → switch to arg form.
            vec![Operation::CommandSelectorLocal(CommandSelectorOp::Confirm)]
        }
    }

    fn render_selector(&self, frame: &mut Frame, area: Rect, theme: &crate::theme::Theme) {
        let Mode::Selector(sel) = &self.mode else {
            return;
        };

        let outer = Layout::default()
            .direction(Direction::Vertical)
            .constraints([Constraint::Length(1), Constraint::Min(1)])
            .split(area);

        let filter_focused = sel.focus.is_focused(FOCUS_FILTER);
        sel.filter.render(frame, outer[0], filter_focused, theme);

        let panes = Layout::default()
            .direction(Direction::Horizontal)
            .constraints([Constraint::Percentage(35), Constraint::Percentage(65)])
            .split(outer[1]);

        // History pane
        let hist_pane = TitledPane::new("Recent Commands", sel.focus.is_focused(FOCUS_HISTORY));
        let hist_content = hist_pane.prepare(frame, panes[0], theme);
        let hist_bg = hist_pane.bg(theme);
        let (sel_bg, sel_fg, fg_dim) = (theme.selection_bg(), theme.selection_fg(), theme.fg_dim());
        frame.render_widget(
            sel.history_list.widget(|id, selected| {
                cmd_list_item(id, selected, hist_bg, sel_bg, sel_fg, fg_dim)
            }),
            hist_content,
        );

        // Tree pane
        let tree_active = sel.focus.is_focused(FOCUS_TREE);
        let tree_pane = TitledPane::new("Commands", tree_active);
        let tree_content = tree_pane.prepare(frame, panes[1], theme);
        let tree_bg = tree_pane.bg(theme);

        if sel.filter.is_empty() {
            sel.tree.render(frame, tree_content, tree_active, tree_bg, sel_bg, sel_fg, theme.tree_dir());
        } else if sel.tree_filtered.is_empty() {
            frame.render_widget(
                Paragraph::new(Span::styled("(no matches)", Style::default().fg(theme.fg_dim()))),
                tree_content,
            );
        } else {
            frame.render_widget(
                sel.tree_filtered.widget(|id, selected| {
                    cmd_list_item(id, selected, tree_bg, sel_bg, sel_fg, fg_dim)
                }),
                tree_content,
            );
        }
    }

    fn render_arg_form(&self, frame: &mut Frame, area: Rect, theme: &crate::theme::Theme) {
        let Mode::ArgForm(form) = &self.mode else {
            return;
        };

        let pane_bg = theme.bg_active();
        frame.render_widget(Block::default().style(Style::new().bg(pane_bg)), area);

        // Title row
        let title = format!("  Run: {} — {}", form.command_id, form.command_title);
        let title_area = Rect { height: 1, ..area };
        frame.render_widget(
            Paragraph::new(Span::styled(
                title,
                Style::new().fg(theme.accent()).bg(pane_bg).add_modifier(Modifier::BOLD),
            )),
            title_area,
        );

        let inner = Rect {
            y: area.y + 1,
            height: area.height.saturating_sub(1),
            ..area
        };

        // Each field takes 3 rows (label+input, hint, blank).
        let mut constraints: Vec<Constraint> = form
            .fields
            .iter()
            .flat_map(|_| {
                [
                    Constraint::Length(1), // label
                    Constraint::Length(1), // input
                    Constraint::Length(1), // hint
                    Constraint::Length(1), // spacer
                ]
            })
            .collect();
        constraints.push(Constraint::Min(1)); // footer
        let rows = Layout::default()
            .direction(Direction::Vertical)
            .constraints(constraints)
            .split(inner);

        for (i, field) in form.fields.iter().enumerate() {
            let focused = i == form.focused;
            let label_row = i * 4;
            let input_row = label_row + 1;
            let hint_row = label_row + 2;

            // Label
            let label_style = if focused {
                Style::default().fg(theme.accent()).add_modifier(Modifier::BOLD)
            } else {
                Style::default().fg(Color::Gray)
            };
            let req_marker = if field.required { " *" } else { "" };
            frame.render_widget(
                Paragraph::new(Span::styled(
                    format!("  {}{}", field.name, req_marker),
                    label_style,
                )),
                rows[label_row],
            );

            // Input line (no border — use bg highlight for focus)
            let cursor = if focused { "_" } else { "" };
            let input_bg = if focused { theme.selection_bg() } else { theme.bg_inactive() };
            let input_fg = if focused { theme.fg_active() } else { Color::Gray };
            if input_row < rows.len() {
                let input_area = rows[input_row];
                frame.render_widget(
                    Paragraph::new(Span::styled(
                        format!("  {}{}", field.value, cursor),
                        Style::default().fg(input_fg).bg(input_bg),
                    ))
                    .style(Style::default().bg(input_bg)),
                    input_area,
                );
            }

            // Hint
            let kind_hint = match &field.kind {
                ArgKind::String => "text",
                ArgKind::Bool => "true / false",
                ArgKind::Int => "integer",
                ArgKind::FilePath => "file path",
                ArgKind::Choice(opts) => {
                    let _ = opts;
                    "choice"
                }
            };
            if hint_row < rows.len() {
                frame.render_widget(
                    Paragraph::new(Span::styled(
                        format!("  {} · {}", field.description, kind_hint),
                        Style::default().fg(theme.fg_dim()),
                    )),
                    rows[hint_row],
                );
            }
        }

        // Footer hints
        let footer_idx = form.fields.len() * 4;
        if footer_idx < rows.len() {
            frame.render_widget(
                Paragraph::new(Line::from(vec![
                    Span::styled(
                        "  [Enter]",
                        Style::default()
                            .fg(Color::Green)
                            .add_modifier(Modifier::BOLD),
                    ),
                    Span::raw(" Run   "),
                    Span::styled(
                        "[Tab]",
                        Style::default()
                            .fg(Color::Yellow)
                            .add_modifier(Modifier::BOLD),
                    ),
                    Span::raw(" Next field   "),
                    Span::styled(
                        "[Esc]",
                        Style::default().fg(Color::Red).add_modifier(Modifier::BOLD),
                    ),
                    Span::raw(" Back"),
                ])),
                rows[footer_idx],
            );
        }
    }
}

impl View for CommandRunner {
    const KIND: crate::views::ViewKind = crate::views::ViewKind::Modal;

    fn save_state(&mut self, _app: &mut crate::app_state::AppState) {}

    fn handle_key(&self, key: KeyEvent) -> Vec<Operation> {
        match &self.mode {
            Mode::Selector(sel) => handle_key_selector(sel, key, self),
            Mode::ArgForm(form) => handle_key_form(form, key),
        }
    }

    fn handle_mouse(&self, mouse: MouseEvent) -> Vec<Operation> {
        if !matches!(mouse.kind, MouseEventKind::Down(MouseButton::Left)) {
            return vec![];
        }

        let is_filter_row = mouse.row == 0;
        if is_filter_row {
            return vec![];
        }

        let Mode::Selector(sel) = &self.mode else {
            return vec![];
        };

        let is_left_pane = mouse.column < 80;
        if is_left_pane {
            if !sel.focus.is_focused(FOCUS_HISTORY) {
                return vec![Operation::CommandSelectorLocal(CommandSelectorOp::SwitchPane)];
            }
        } else if !sel.focus.is_focused(FOCUS_TREE) {
            return vec![Operation::CommandSelectorLocal(CommandSelectorOp::SwitchPane)];
        }

        vec![]
    }

    fn handle_operation(&mut self, op: &Operation, _settings: &Settings) -> Option<Event> {
        match op {
            Operation::Focus(focus_op) => {
                if let Mode::Selector(sel) = &mut self.mode {
                    match focus_op {
                        FocusOp::Next => sel.focus.focus_next(),
                        FocusOp::Prev => sel.focus.focus_prev(),
                        _ => {}
                    }
                }
                Some(Event::applied("command_runner", op.clone()))
            }
            Operation::CommandSelectorLocal(local) => {
                match local {
                    CommandSelectorOp::FilterInput(field_op) => {
                        if let Mode::Selector(sel) = &mut self.mode {
                            sel.filter.apply(field_op);
                            sel.refilter();
                        }
                    }
                    CommandSelectorOp::SwitchPane => {
                        if let Mode::Selector(sel) = &mut self.mode {
                            let cur = sel.focus.current();
                            if cur == FOCUS_HISTORY || cur == FOCUS_FILTER {
                                sel.focus.set_focus(FOCUS_TREE);
                            } else {
                                sel.focus.set_focus(FOCUS_HISTORY);
                            }
                        }
                    }
                    CommandSelectorOp::CollapseOrLeft => {
                        if let Mode::Selector(sel) = &mut self.mode
                            && sel.active_pane() == FOCUS_TREE
                            && sel.filter.is_empty()
                        {
                            sel.tree.key_left();
                        }
                    }
                    CommandSelectorOp::ExpandOrRight => {
                        if let Mode::Selector(sel) = &mut self.mode
                            && sel.active_pane() == FOCUS_TREE
                            && sel.filter.is_empty()
                        {
                            sel.tree.key_right();
                        }
                    }
                    CommandSelectorOp::Confirm => {
                        // Switch to arg form for the selected command.
                        if let Mode::Selector(sel) = &self.mode
                            && let Some(id) = sel.selected_command()
                            && let Some(snap) = self.metas.get(&id)
                        {
                            // Build a temporary CommandMeta to reuse ArgFormState::new.
                            let meta = build_temp_meta(&id, snap);
                            let form = ArgFormState::new(&meta);
                            self.mode = Mode::ArgForm(form);
                        }
                    }
                    CommandSelectorOp::ArgNext => {
                        if let Mode::ArgForm(form) = &mut self.mode
                            && form.focused + 1 < form.fields.len()
                        {
                            form.focused += 1;
                        }
                    }
                    CommandSelectorOp::ArgPrev => {
                        if let Mode::ArgForm(form) = &mut self.mode
                            && form.focused > 0
                        {
                            form.focused -= 1;
                        }
                    }
                    CommandSelectorOp::ArgChanged(s) => {
                        if let Mode::ArgForm(form) = &mut self.mode
                            && let Some(field) = form.fields.get_mut(form.focused)
                        {
                            field.value = s.clone();
                        }
                    }
                    CommandSelectorOp::ArgCancel => {
                        // Rebuild selector and go back.
                        let history_ids: Vec<CommandId> = vec![]; // History is lost on round-trip; acceptable.
                        self.mode = Mode::Selector(SelectorState::new(
                            self.all_commands.clone(),
                            history_ids,
                        ));
                    }
                }
                Some(Event::applied("command_runner", op.clone()))
            }

            Operation::NavigateUp => {
                if let Mode::Selector(sel) = &mut self.mode {
                    match sel.active_pane() {
                        FOCUS_HISTORY => sel.history_list.move_up(),
                        _ => {
                            if sel.filter.is_empty() {
                                sel.tree.key_up();
                            } else {
                                sel.tree_filtered.move_up();
                            }
                        }
                    }
                }
                Some(Event::applied("command_runner", op.clone()))
            }
            Operation::NavigateDown => {
                if let Mode::Selector(sel) = &mut self.mode {
                    match sel.active_pane() {
                        FOCUS_HISTORY => sel.history_list.move_down(),
                        _ => {
                            if sel.filter.is_empty() {
                                sel.tree.key_down();
                            } else {
                                sel.tree_filtered.move_down();
                            }
                        }
                    }
                }
                Some(Event::applied("command_runner", op.clone()))
            }
            Operation::NavigatePageUp => {
                const PAGE: usize = 10;
                if let Mode::Selector(sel) = &mut self.mode {
                    match sel.active_pane() {
                        FOCUS_HISTORY => {
                            for _ in 0..PAGE {
                                sel.history_list.move_up();
                            }
                        }
                        _ => {
                            if sel.filter.is_empty() {
                                for _ in 0..PAGE {
                                    sel.tree.key_up();
                                }
                            } else {
                                for _ in 0..PAGE {
                                    sel.tree_filtered.move_up();
                                }
                            }
                        }
                    }
                }
                Some(Event::applied("command_runner", op.clone()))
            }
            Operation::NavigatePageDown => {
                const PAGE: usize = 10;
                if let Mode::Selector(sel) = &mut self.mode {
                    match sel.active_pane() {
                        FOCUS_HISTORY => {
                            for _ in 0..PAGE {
                                sel.history_list.move_down();
                            }
                        }
                        _ => {
                            if sel.filter.is_empty() {
                                for _ in 0..PAGE {
                                    sel.tree.key_down();
                                }
                            } else {
                                for _ in 0..PAGE {
                                    sel.tree_filtered.move_down();
                                }
                            }
                        }
                    }
                }
                Some(Event::applied("command_runner", op.clone()))
            }

            _ => None,
        }
    }

    fn render(&self, frame: &mut Frame, area: Rect, theme: &crate::theme::Theme) {
        match &self.mode {
            Mode::Selector(_) => self.render_selector(frame, area, theme),
            Mode::ArgForm(_) => self.render_arg_form(frame, area, theme),
        }
    }
}

fn handle_key_selector(
    sel: &SelectorState,
    key: KeyEvent,
    runner: &CommandRunner,
) -> Vec<Operation> {
    use CommandSelectorOp::*;
    let cs = |op: CommandSelectorOp| vec![Operation::CommandSelectorLocal(op)];

    match (key.modifiers, key.key) {
        (_, Key::Enter) => runner.confirm_selection(),

        (_, Key::Tab) => vec![Operation::Focus(FocusOp::Next)],
        (_, Key::BackTab) => vec![Operation::Focus(FocusOp::Prev)],
        (_, Key::ArrowUp) => vec![Operation::NavigateUp],
        (_, Key::ArrowDown) => vec![Operation::NavigateDown],
        (_, Key::PageUp) => vec![Operation::NavigatePageUp],
        (_, Key::PageDown) => vec![Operation::NavigatePageDown],

        (_, Key::ArrowLeft) if sel.active_pane() == FOCUS_TREE && sel.filter.is_empty() => {
            cs(CollapseOrLeft)
        }
        (_, Key::ArrowRight) if sel.active_pane() == FOCUS_TREE && sel.filter.is_empty() => {
            cs(ExpandOrRight)
        }

        _ => {
            if let Some(field_op) = crate::widgets::input_field::key_to_op(key) {
                vec![Operation::CommandSelectorLocal(CommandSelectorOp::FilterInput(field_op))]
            } else {
                vec![]
            }
        }
    }
}

fn handle_key_form(form: &ArgFormState, key: KeyEvent) -> Vec<Operation> {
    use CommandSelectorOp::*;
    let cs = |op: CommandSelectorOp| vec![Operation::CommandSelectorLocal(op)];

    match (key.modifiers, key.key) {
        (_, Key::Escape) => cs(ArgCancel),

        (_, Key::Enter) => {
            // Collect args and emit RunCommand.
            let args = form.collect_args();
            vec![Operation::RunCommand {
                id: form.command_id.clone(),
                args,
            }]
        }

        (m, Key::Tab) if m.contains(Modifiers::SHIFT) => cs(ArgPrev),
        (_, Key::ArrowUp) => cs(ArgPrev),
        (_, Key::Tab) | (_, Key::ArrowDown) => cs(ArgNext),

        _ => {
            match (key.modifiers, key.key) {
                (m, Key::Char(c)) if !m.contains(Modifiers::CTRL) && !m.contains(Modifiers::ALT) => {
                    let mut v = form.fields.get(form.focused).map(|f| f.value.clone()).unwrap_or_default();
                    v.push(c);
                    cs(ArgChanged(v))
                }
                (_, Key::Backspace) => {
                    let mut v = form.fields.get(form.focused).map(|f| f.value.clone()).unwrap_or_default();
                    v.pop();
                    cs(ArgChanged(v))
                }
                _ => vec![],
            }
        }
    }
}

fn cmd_list_item(
    id: &CommandId,
    selected: bool,
    pane_bg: Color,
    sel_bg: Color,
    sel_fg: Color,
    fg_dim: Color,
) -> ListItem<'static> {
    let bg = if selected { sel_bg } else { pane_bg };
    ListItem::new(Line::from(vec![
        Span::styled(
            format!("{}/", id.group),
            Style::default().fg(fg_dim).bg(bg),
        ),
        Span::styled(
            id.name.to_string(),
            Style::default()
                .fg(if selected { sel_fg } else { Color::Gray })
                .bg(bg)
                .add_modifier(if selected { Modifier::BOLD } else { Modifier::empty() }),
        ),
    ]))
}

/// Build a temporary `CommandMeta` from a snapshot so we can reuse
/// `ArgFormState::new` without keeping a registry reference.
fn build_temp_meta(id: &CommandId, snap: &CommandMetaSnapshot) -> CommandMeta {
    use std::borrow::Cow;
    CommandMeta {
        id: id.clone(),
        title: Cow::Owned(snap.title.clone()),
        description: Cow::Borrowed(""),
        args: snap.args.clone(),
        contexts: vec![],
        visibility: meta::Visibility::Hidden,
    }
}


