#[cfg(not(test))]
macro_rules! return_usage_error {
    ($($arg:tt)*) => {{
        eprintln!($($arg)*);
        return crate::shell::BuiltinExitCode::Usage as ::libc::c_int;
    }};
}

#[cfg(not(test))]
use clap::error::ErrorKind;
use clap::{CommandFactory, Parser, Subcommand};
use clap_complete::{ArgValueCompleter, CompletionCandidate};
#[cfg(not(test))]
use libc::c_int;
use strum::VariantArray;

#[allow(unused_imports)]
use crate::{
    app::actions::{self},
    content,
    cursor::{self, CursorStyleConfig},
    grammar::{self, dparser},
    logging, palette, settings, shell, term_info, tutorial,
};

#[cfg(not(test))]
use crate::shell::bash::symbols as bash_symbols;

fn get_styles() -> clap::builder::Styles {
    clap::builder::Styles::styled()
        .header(
            clap::builder::styling::AnsiColor::Yellow.on_default()
                | clap::builder::styling::Effects::BOLD,
        )
        .usage(
            clap::builder::styling::AnsiColor::Yellow.on_default()
                | clap::builder::styling::Effects::BOLD,
        )
        .literal(
            clap::builder::styling::AnsiColor::Green.on_default()
                | clap::builder::styling::Effects::BOLD,
        )
        .placeholder(clap::builder::styling::AnsiColor::White.on_default())
        .error(
            clap::builder::styling::AnsiColor::Red.on_default()
                | clap::builder::styling::Effects::BOLD,
        )
        .valid(
            clap::builder::styling::AnsiColor::Green.on_default()
                | clap::builder::styling::Effects::BOLD,
        )
        .invalid(
            clap::builder::styling::AnsiColor::Red.on_default()
                | clap::builder::styling::Effects::BOLD,
        )
}

fn parse_matrix_animation(s: &str) -> Result<settings::MatrixAnimation, String> {
    match s {
        "on" => Ok(settings::MatrixAnimation::On),
        "off" => Ok(settings::MatrixAnimation::Off),
        _ => s
            .parse::<u64>()
            .map(settings::MatrixAnimation::IdleSecs)
            .map_err(|_| format!("expected `on`, `off`, or a non-negative integer, got `{s}`")),
    }
}

fn parse_effect_speed(s: &str) -> Result<f32, String> {
    let val: f32 = s.parse().map_err(|e| format!("invalid float: {e}"))?;
    if (0.0..=10.0).contains(&val) {
        Ok(val)
    } else {
        Err(format!("value {val} not in range 0.0..=10.0"))
    }
}

fn parse_idle_frame_rate(s: &str) -> Result<f64, String> {
    let val: f64 = s.parse().map_err(|e| format!("invalid float: {e}"))?;
    if val > 0.0 && val <= 120.0 {
        Ok(val)
    } else {
        Err(format!("value {val} not in range (0.0, 120.0]"))
    }
}

#[derive(Parser, Debug)]
#[command(
    name = "flyline",
    styles = get_styles(),
    after_help = "Read more at https://github.com/HalFrgrd/flyline",
)]
struct FlylineArgs {
    /// Show version information
    #[arg(long)]
    version: bool,
    /// Load Zsh history in addition to Bash history. Optionally specify a PATH to the Zsh history file
    #[arg(long = "load-zsh-history", value_name = "PATH", default_missing_value = "", num_args = 0..=1)]
    load_zsh_history: Option<String>,
    /// Show animations
    #[arg(long = "show-animations", default_missing_value = "true", num_args = 0..=1)]
    show_animations: Option<bool>,
    /// Run matrix animation in the terminal background. Use `on` to always show it, `off` to
    /// disable it, or an integer number of seconds to show it after that many seconds of
    /// inactivity (no keypress or mouse event). Defaults to `off`; passing the flag without a
    /// value is equivalent to `on`.
    #[arg(long = "matrix-animation", default_missing_value = "on", num_args = 0..=1, value_parser = parse_matrix_animation)]
    matrix_animation: Option<settings::MatrixAnimation>,
    /// Render frame rate in frames per second (1–120, default 24)
    #[arg(long = "set-frame-rate", value_name = "FPS", value_parser = clap::value_parser!(u8).range(1..=120))]
    frame_rate: Option<u8>,
    /// Idle frame rate in frames per second when inactive (default 0.2)
    #[arg(long = "set-idle-frame-rate", value_name = "FPS", value_parser = parse_idle_frame_rate)]
    idle_frame_rate: Option<f64>,
    /// Mouse capture mode (disabled, simple, smart). Default is smart.
    #[arg(long = "set-mouse-mode", value_name = "MODE", hide = true)]
    mouse_mode: Option<settings::MouseMode>,
    /// Send shell integration escape codes (OSC 133 / OSC 633): none, only-prompt-pos, or full
    #[arg(long = "send-shell-integration-codes", default_missing_value = "only-prompt-pos", num_args = 0..=1)]
    send_shell_integration_codes: Option<settings::ShellIntegrationLevel>,
    /// Whether to request the use of extended (kitty-protocol) keyboard codes during startup.
    /// Enabled by default; pass `--enable-extended-key-codes false` to
    /// disable it on terminals that misbehave when the request is sent.
    #[arg(long = "enable-extended-key-codes", default_missing_value = "true", num_args = 0..=1)]
    enable_extended_key_codes: Option<bool>,
    /// Whether easter eggs (such as animated command words like `python`) are enabled.
    /// Enabled by default; pass `--enable-easter-eggs false` to disable.
    #[arg(long = "enable-easter-eggs", default_missing_value = "true", num_args = 0..=1)]
    enable_easter_eggs: Option<bool>,
    /// Resize strategy for cursor placement on window resize (auto-cleared, reflowed-apart-from-cursor, reflowed-all).
    #[arg(long = "set-resize-logic", value_name = "STRATEGY", hide = true)]
    set_resize_logic: Option<settings::ResizeLogic>,
    /// Delay in milliseconds before performing delayed startup initialization (such as CPR and focus tracking).
    #[arg(long = "set-delayed-startup-ms", value_name = "MS")]
    set_delayed_startup_ms: Option<u64>,
    #[command(subcommand)]
    command: Option<Commands>,
}

pub fn complete_flyline_args(
    raw_command: &str,
    wuc: &str,
    cursor_byte: usize,
) -> anyhow::Result<Vec<clap_complete::CompletionCandidate>> {
    let command_pre_wuc = raw_command_before_word_under_cursor(raw_command, wuc, cursor_byte)?;
    log::info!("Completing flyline args after: {:?}", command_pre_wuc);

    let current_dir = std::env::current_dir().ok();
    let current_dir_asdf = current_dir.as_ref().map(|p| p.to_path_buf());

    let mut parser = dparser::DParser::from(command_pre_wuc);
    parser.walk_to_end();
    let tokens = parser
        .into_tokens()
        .into_iter()
        .map(|annoted_token| annoted_token.token)
        .collect::<Vec<_>>();

    use flash::lexer;

    let relevant_tokens = tokens
        .into_iter()
        .filter(|x| !x.kind.is_whitespace() || x.byte_range().contains(&cursor_byte))
        .filter(|x| {
            !matches!(
                x.kind,
                lexer::TokenKind::Comment
                    | lexer::TokenKind::Newline
                    | lexer::TokenKind::Quote
                    | lexer::TokenKind::SingleQuote
            )
        })
        .collect::<Vec<_>>();

    let mut args_os_string: Vec<std::ffi::OsString> = relevant_tokens
        .iter()
        .map(|t| {
            if t.kind.is_whitespace() {
                std::ffi::OsString::new()
            } else {
                std::ffi::OsString::from(&t.value)
            }
        })
        .collect();

    let dequoted_wuc = grammar::dequoting_function_rust(wuc);
    let mut opt_prefix_to_strip = None;

    let merged = if let Some(last_arg) = args_os_string.last_mut() {
        let last_str = last_arg.to_string_lossy();
        if last_str.ends_with('=') || last_str.ends_with(':') {
            opt_prefix_to_strip = Some(last_str.into_owned());
            last_arg.push(&dequoted_wuc);
            true
        } else {
            false
        }
    } else {
        false
    };

    if !merged {
        args_os_string.push(std::ffi::OsString::from(dequoted_wuc));
    }

    let index = args_os_string.len() - 1;

    log::info!(
        "Completing command: cursor_byte: {}, parsed args: {:?}, index: {}, ",
        cursor_byte,
        args_os_string,
        index
    );

    let mut clap_command = FlylineArgs::command();

    match clap_complete::engine::complete(
        &mut clap_command,
        args_os_string,
        index,
        current_dir_asdf.as_deref(),
    ) {
        Ok(candidates) => {
            let processed_candidates = if let Some(prefix) = opt_prefix_to_strip {
                candidates
                    .into_iter()
                    .map(|c| {
                        let val = c.get_value().to_string_lossy();
                        if val.contains("PREFIX_DELIM") {
                            c
                        } else if let Some(suffix) = val.strip_prefix(&prefix) {
                            let new_val = format!("{}PREFIX_DELIM{}", prefix, suffix);
                            let mut new_c = clap_complete::CompletionCandidate::new(new_val);
                            if let Some(help) = c.get_help() {
                                new_c = new_c.help(Some(help.clone()));
                            }
                            new_c
                        } else {
                            c
                        }
                    })
                    .collect()
            } else {
                candidates
            };
            log::trace!(
                "{:#?}",
                processed_candidates.iter().take(10).collect::<Vec<_>>()
            );
            Ok(processed_candidates)
        }
        Err(e) => {
            log::error!("Error generating bash completion: {e}");
            Err(anyhow::anyhow!("Error generating bash completion: {e}"))
        }
    }
}

fn raw_command_before_word_under_cursor<'a>(
    raw_command: &'a str,
    wuc: &str,
    cursor_byte: usize,
) -> anyhow::Result<&'a str> {
    if cursor_byte > raw_command.len() {
        anyhow::bail!("cursor is outside raw command");
    }

    if wuc.is_empty() {
        if !raw_command.is_char_boundary(cursor_byte) {
            anyhow::bail!("cursor is not on a char boundary");
        }
        return Ok(&raw_command[..cursor_byte]);
    }

    raw_command
        .match_indices(wuc)
        .find_map(|(start, _)| {
            let end = start + wuc.len();
            (start <= cursor_byte && cursor_byte <= end).then_some(&raw_command[..start])
        })
        .ok_or_else(|| anyhow::anyhow!("word under cursor not found at cursor"))
}

fn possible_interpolate_easing_completions(current: &std::ffi::OsStr) -> Vec<CompletionCandidate> {
    cursor::CursorEasing::VARIANTS
        .iter()
        .filter(|s| s.as_ref().starts_with(current.to_string_lossy().as_ref()))
        .map(|s| {
            let description = content::easing_animation_frames(*s);
            let description_str = description
                .into_iter()
                .map(|line| line.iter().map(content::span_to_ansi).collect::<String>())
                .collect::<Vec<_>>()
                .join("\t");

            CompletionCandidate::new(s.as_ref().to_string()).help(Some(description_str.into()))
        })
        .collect()
}

fn possible_effect_easing_completions(current: &std::ffi::OsStr) -> Vec<CompletionCandidate> {
    cursor::CursorEasing::VARIANTS
        .iter()
        .filter(|s| s.as_ref().starts_with(current.to_string_lossy().as_ref()))
        .map(|s| {
            let description = cursor::cursor_effect_animation_frames(
                *s,
                cursor::CursorConfig::default().effect_speed,
            );
            let description_str = description
                .into_iter()
                .map(|line| line.iter().map(content::span_to_ansi).collect::<String>())
                .collect::<Vec<_>>()
                .join("\t");

            CompletionCandidate::new(s.as_ref().to_string()).help(Some(description_str.into()))
        })
        .collect()
}

#[derive(Subcommand, Debug)]
enum Commands {
    /// Show version information.
    #[command(name = "version")]
    Version {
        /// Copy version information to clipboard
        #[arg(long)]
        copy: bool,
    },
    /// Print a timestamp.

    ///
    /// With no flags, prints nanoseconds since the Unix epoch.
    /// With --format, formats the current local time using a Chrono strftime
    /// format string (e.g. "%Y-%m-%dT%H:%M:%S").
    ///
    /// To display elapsed time since the last command, use the
    /// `last-command-duration` prompt widget instead:
    ///   flyline create-prompt-widget last-command-duration
    ///
    /// Examples:
    ///   flyline time
    ///   flyline time --format "%Y-%m-%dT%H:%M:%S"
    #[command(name = "time", verbatim_doc_comment)]
    Time {
        /// Format string passed to Chrono's `strftime` formatter.
        /// When omitted, prints nanoseconds since the Unix epoch.
        #[arg(long = "format", value_name = "FORMAT")]
        format: Option<String>,
    },
    /// Configure AI agent mode.
    ///
    /// When Alt+Enter is pressed, flyline invokes COMMAND with the current buffer
    /// (optionally prepended by SYSTEM_PROMPT) as the final argument.
    ///
    /// When --trigger-prefix is set, pressing Enter also activates agent mode
    /// if the buffer starts with the given prefix (the prefix is stripped before
    /// the buffer is sent to the command).
    ///
    /// Examples:
    ///   flyline set-agent-mode \
    ///     --system-prompt "Answer with a JSON array of at most 3 items with objects containing: command and description. Command will be a Bash command." \
    ///     --command 'copilot --reasoning-effort low --prompt'
    ///   flyline set-agent-mode --trigger-prefix ": " --command 'copilot --reasoning-effort low --prompt'
    ///
    /// See https://github.com/HalFrgrd/flyline/blob/master/examples/agent_mode.sh for more details and example usage.
    #[command(name = "set-agent-mode", verbatim_doc_comment)]
    AgentMode {
        /// Optional system prompt prepended to the buffer.
        /// The subprocess receives "<system-prompt>\n<buffer>" as its final argument.
        #[arg(long = "system-prompt")]
        system_prompt: Option<String>,
        /// Optional trigger prefix. When set, pressing Enter with a buffer that
        /// starts with this prefix activates agent mode (the prefix is stripped).
        #[arg(long = "trigger-prefix")]
        trigger_prefix: Option<String>,
        /// Command string to invoke; include any flags in the same string, e.g.
        /// --command 'copilot --reasoning-effort low --prompt'.
        /// The current buffer is appended as the final argument when Alt+Enter is pressed.
        #[arg(long = "command", required = true)]
        command: String,
    },
    /// Create a custom prompt widget.
    ///
    /// Instances of NAME in prompt strings (PS1, RPS1, PS1_FILL, PROMPT_RULER, and their _FINAL counterparts) are replaced
    /// with the widget output on every render.
    ///
    /// Widget types:
    ///   animation   Cycles through a list of frames at a given fps.
    ///   mouse-mode             Shows different text depending on whether mouse capture is enabled.
    ///   copy-buffer            Shows clickable text that copies the current buffer to the clipboard.
    ///   custom                 Runs a shell command and displays its output.
    ///   last-command-duration  Shows how long ago the flyline app last closed.
    ///
    /// Examples:
    ///   flyline create-prompt-widget animation --name "MY_ANIMATION" --fps 10  ⣾ ⣷ ⣯ ⣟ ⡿ ⢿ ⣻ ⣽
    ///   flyline create-prompt-widget animation --name "john" --ping-pong --fps 5  '\e[33m\u' '\e[31m\u' '\e[35m\u' '\e[36m\u'
    ///   flyline create-prompt-widget mouse-mode --name FLYLINE_MOUSE_MODE 'mouse is enabled' 'mouse is disabled'
    ///   flyline create-prompt-widget copy-buffer --name COPY_BUFFER '[copy]'
    ///   flyline create-prompt-widget custom --name CUSTOM_WIDGET1 --command 'run_something.sh' --placeholder prev
    ///   flyline create-prompt-widget custom --name CUSTOM_WIDGET1 --command 'run_something.sh' --block
    ///   flyline create-prompt-widget last-command-duration
    ///   flyline create-prompt-widget last-command-duration --name LAST_CMD_DUR
    #[command(name = "create-prompt-widget", verbatim_doc_comment)]
    CreatePromptWidget {
        #[command(subcommand)]
        subcommand: PromptWidgetSubcommands,
    },
    /// Configure the colour palette.
    ///
    /// Style strings follow rich's syntax: a space-separated list of attributes
    /// (bold, dim, italic, underline, blink, reverse, strike) and colours
    /// (e.g. red, #ff0000, rgb(255,0,0), color(196)).
    ///
    /// Valid style names:
    ///   recognised-command, unrecognised-command, single-quoted-text,
    ///   double-quoted-text, secondary-text, inline-suggestion, tutorial-hint,
    ///   matching-char, opening-and-closing-pair, normal-text, comment,
    ///   env-var, markdown-heading1, markdown-heading2, markdown-heading3,
    ///   markdown-code, key-sequence-style, selected-text, bash-reserved
    ///
    /// Examples:
    ///   flyline set-style --default-theme dark
    ///   flyline set-style inline-suggestion="dim italic"
    ///   flyline set-style matching-char="bold green"
    ///   flyline set-style --default-theme light matching-char="bold blue"
    ///   flyline set-style recognised-command="green" unrecognised-command="bold red"
    ///   flyline set-style secondary-text="dim" tutorial-hint="bold italic"
    #[command(name = "set-style", verbatim_doc_comment)]
    SetColour {
        /// Apply a built-in colour preset for dark or light terminals.
        #[arg(long = "default-theme", value_name = "MODE")]
        default_theme: Option<settings::ColourTheme>,
        /// One or more palette style assignments as NAME=STYLE.
        /// NAME is the kebab-case style slot name; STYLE is a rich-style string.
        #[arg(value_name = "NAME=STYLE", add = ArgValueCompleter::new(palette::possible_style_name_completions))]
        styles: Vec<String>,
    },
    /// Configure the cursor appearance and animation.
    ///
    /// Controls which backend renders the cursor, how it moves (interpolation),
    /// what it looks like (style), and any blinking/fading effect.
    ///
    /// Style strings follow rich's syntax: a space-separated list of colours
    /// and attributes.  For cursor styles a single colour (e.g. `red`) is
    /// interpreted as the **background** colour of the cursor cell.
    /// Use `"pink on white"` for an explicit foreground and background.
    /// The special value `"reverse"` inverts the colours of the cell under
    /// the cursor.
    ///
    /// Examples:
    ///   flyline set-cursor --backend flyline
    ///   flyline set-cursor --style "reverse"
    ///   flyline set-cursor --style "red"
    ///   flyline set-cursor --style "pink on white"
    ///   flyline set-cursor --interpolate 16 --interpolate-easing out-cubic
    ///   flyline set-cursor --effect blink --effect-speed 2.0
    ///   flyline set-cursor --effect fade --effect-easing in-out-sine
    ///   flyline set-cursor --interpolate none
    #[command(name = "set-cursor", verbatim_doc_comment)]
    SetCursor {
        /// Cursor rendering backend.  `flyline` renders a custom cursor (the default);
        /// `terminal` defers to the terminal emulator.
        #[arg(long)]
        backend: Option<cursor::CursorBackend>,
        /// Interpolation speed (1/second), or `none` to disable
        /// interpolation.  Default is `16`.
        #[arg(long, value_name = "SPEED|none")]
        interpolate: Option<String>,
        /// Easing function for position interpolation.  Default is `linear`.
        #[arg(long, value_name = "EASING", add = ArgValueCompleter::new(possible_interpolate_easing_completions))]
        interpolate_easing: Option<cursor::CursorEasing>,
        /// Cursor style.  A single colour (e.g. `red`) is the cursor background.
        /// `"pink on white"` sets foreground and background.  `"reverse"` inverts
        /// the cell colours.  Default is a white block modulated by the effect.
        #[arg(long, value_name = "STYLE")]
        style: Option<String>,
        /// Visual effect applied to the cursor: `fade`, `blink`, or `none`.
        #[arg(long)]
        effect: Option<cursor::CursorEffect>,
        /// Speed multiplier for the cursor effect (default is `1.0`).
        #[arg(long, value_name = "SPEED", value_parser = parse_effect_speed)]
        effect_speed: Option<f32>,
        /// Easing function for the cursor effect intensity.  Default is `linear`.
        #[arg(long, value_name = "EASING", add = ArgValueCompleter::new(possible_effect_easing_completions))]
        effect_easing: Option<cursor::CursorEasing>,
    },
    /// Manage keybindings.
    ///
    /// Use 'flyline key bind <KEY> <CONTEXT_EXPR>=<ACTION>' to bind a key sequence to an action.
    /// Use 'flyline key list' to view all current bindings.
    /// Use 'flyline key remap <FROM> <TO>' to translate one key or modifier to another before
    /// bindings are matched.
    ///
    /// KEY is a combination like "Ctrl+Enter", "Alt+Left", or "F1".
    /// Modifiers: Ctrl (Control), Shift, Alt (Option), Meta,
    ///   Super (Cmd, Command, Gui, Win), Hyper.
    /// Keys: Enter (Ret, Return), Backspace (Bkspc, Bs), Tab, BackTab, Esc (Escape),
    ///   Space (Spc), Delete (Del), Insert (Ins), Left, Right, Up, Down, Home, End,
    ///   PageUp (PgUp), PageDown (PgDown, PgDn), Null,
    ///   CapsLock (Caps, Caps_Lock), ScrollLock (Scroll_Lock), NumLock (Num_Lock),
    ///   PrintScreen (PrtScn, Print_Screen), Pause, Menu, KeypadBegin (Keypad_Begin),
    ///   F1-F255, Media:<name> (e.g. Media:Play, Media:Pause, Media:Stop,
    ///   Media:FastForward, Media:Rewind, Media:TrackNext, Media:TrackPrevious,
    ///   Media:RaiseVolume, Media:LowerVolume, Media:Mute),
    ///   Modifier:<name> (e.g. Modifier:LeftShift, Modifier:RightCtrl,
    ///   Modifier:LeftAlt, Modifier:LeftSuper).
    ///
    /// Tab completion is available: type 'flyline key bind <KEY> <Tab>' to browse
    /// all available context variables and actions interactively.
    ///
    /// Examples:
    ///   flyline key bind Ctrl+Enter always=submitOrNewline
    ///   flyline key list
    #[command(name = "key", verbatim_doc_comment)]
    Key {
        /// Show the last key event and dispatched action above the prompt.
        #[arg(long = "debug", default_missing_value = "true", num_args = 0..=1)]
        debug: Option<bool>,
        /// Clear all default built-in keybindings.
        #[arg(long = "clear-defaults", default_missing_value = "true", num_args = 0..=1)]
        clear_defaults: Option<bool>,
        #[command(subcommand)]
        subcommand: Option<KeySubcommands>,
    },
    /// Logging commands: dump, configure level, or stream logs.
    ///
    /// Examples:
    ///   flyline log dump
    ///   flyline log set-level debug
    ///   flyline log stream /tmp/flyline.log
    ///   flyline log stream terminal
    #[command(name = "log", verbatim_doc_comment)]
    Log {
        #[command(subcommand)]
        subcommand: LogSubcommands,
    },
    /// Run the interactive tutorial for first-time users.
    ///
    /// Pass `false` to disable the tutorial.
    ///
    /// Examples:
    ///   flyline run-tutorial
    ///   flyline run-tutorial false
    #[command(name = "run-tutorial", verbatim_doc_comment)]
    RunTutorial {
        /// Enable or disable the tutorial. Defaults to `true`.
        #[arg(default_missing_value = "true", num_args = 0..=1)]
        enabled: Option<bool>,
    },
    /// Configure the inline editor.
    ///
    /// Controls behaviours of the buffer editor: automatic closing of bracket
    /// pairs and quotes, inline history suggestions, and whether mouse clicks
    /// and drags change the buffer cursor and selection.
    ///
    /// Examples:
    ///   flyline editor --auto-close-chars false
    ///   flyline editor --show-inline-history false
    ///   flyline editor --select-with-mouse false
    ///   flyline editor --auto-close-chars true --select-with-mouse true
    #[command(name = "editor", verbatim_doc_comment)]
    Editor {
        /// Enable automatic closing character insertion (e.g. insert `)` after `(`).
        #[arg(long = "auto-close-chars", default_missing_value = "true", num_args = 0..=1)]
        auto_close_chars: Option<bool>,
        /// Show inline history suggestions.
        #[arg(long = "show-inline-history", default_missing_value = "true", num_args = 0..=1)]
        show_inline_history: Option<bool>,
        /// Whether mouse clicks and drags on the command buffer change the
        /// cursor position and selection. Default is `true`. When `false`,
        /// mouse interaction with the buffer does not change the selection.
        #[arg(long = "select-with-mouse", default_missing_value = "true", num_args = 0..=1)]
        select_with_mouse: Option<bool>,
    },
    /// Configure history backend and behavior.
    ///
    /// Examples:
    ///   flyline history import /path/to/history_file
    ///   flyline history --backend flyline
    ///   flyline history --backend bash
    #[command(name = "history", hide = true, verbatim_doc_comment)]
    History {
        /// Subcommand for history operations (e.g. import).
        #[command(subcommand)]
        subcommand: Option<HistorySubcommands>,

        /// Select the history storage backend (flyline or bash).
        #[arg(long = "backend", value_enum)]
        backend: Option<crate::settings::HistoryBackend>,

        /// Specify a custom path for Flyline's JSONL history file (defaults to ~/.local/share/flyline/history.jsonl)
        #[arg(long = "jsonl-path", value_name = "PATH")]
        jsonl_path: Option<String>,
    },
    /// Configure suggestion behavior.
    ///
    /// Examples:
    ///   flyline suggestions --auto-suggest false
    ///   flyline suggestions --num-suggestion-rows 10
    ///   flyline suggestions --auto-suggest true --num-suggestion-rows 12
    #[command(name = "suggestions", verbatim_doc_comment)]
    Suggestions {
        /// Optional subcommand for suggestion actions.
        #[command(subcommand)]
        subcommand: Option<SuggestionsSubcommands>,
        /// Enable or disable auto-suggest (auto-started tab completion suggestions).
        #[arg(long = "auto-suggest", default_missing_value = "true", num_args = 0..=1)]
        auto_suggest: Option<bool>,
        /// Enable or disable showing modification times for Git references (branches, tags, stashes).
        #[arg(
            long = "git-ref-mtime",
            default_missing_value = "true",
            num_args = 0..=1,
            hide = true
        )]
        git_ref_mtime: Option<bool>,
        /// Enable or disable flycomp for synthesizing shell completions when no useful compspec is found.
        #[arg(
            long = "use-flycomp",
            default_missing_value = "true",
            num_args = 0..=1,
            hide = true
        )]
        use_flycomp: Option<bool>,
        /// How to sort suggestions when fuzzy scores are tied (mtime, alphabetical).
        #[arg(long = "sort-order", value_name = "ORDER")]
        sort_order: Option<settings::SuggestionSortOrder>,
        /// Maximum number of suggestion rows to render for tab-completion lists.
        #[arg(long = "num-suggestion-rows", value_name = "NUM")]
        num_suggestion_rows: Option<u16>,
        /// Directory where flycomp output should be saved.
        /// You should source the completions from this directory in your bashrc so flyline can use them next time.
        #[arg(long = "flycomp-output", value_name = "DIR", hide = true)]
        flycomp_output: Option<String>,
        /// Blacklist of command words for which flycomp prompt should be bypassed.
        #[arg(long = "flycomp-blacklist", value_name = "COMMANDS", num_args = 1.., hide = true)]
        flycomp_blacklist: Option<Vec<String>>,
    },
    /// Configure mouse options and debugging.
    #[command(name = "mouse", verbatim_doc_comment)]
    Mouse {
        /// Show the last mouse event above the prompt.
        #[arg(long = "debug", default_missing_value = "true", num_args = 0..=1)]
        debug: Option<bool>,
        /// Whether to change the mouse cursor shape depending on what is hovered.
        #[arg(long = "change-shape", default_missing_value = "true", num_args = 0..=1)]
        change_shape: Option<bool>,
        /// Mouse capture mode (disabled, simple, smart).
        #[arg(long = "mode", value_name = "MODE")]
        mode: Option<settings::MouseMode>,
    },
    /// Performance profiling commands: start, stop, or dump stats.
    #[command(name = "perf", verbatim_doc_comment)]
    Perf {
        #[command(subcommand)]
        subcommand: PerfSubcommands,
    },
    /// Display the changelog of user-facing changes.
    ///
    /// Examples:
    ///   flyline changelog
    #[command(name = "changelog", verbatim_doc_comment)]
    Changelog,
    /// Display instructions to upgrade flyline.
    ///
    /// Examples:
    ///   flyline upgrade
    #[command(name = "upgrade", verbatim_doc_comment)]
    Upgrade,
    /// View active session settings and inspect differences from defaults.
    ///
    /// Examples:
    ///   flyline settings
    ///   flyline settings --all
    #[command(name = "settings", verbatim_doc_comment)]
    Settings {
        /// Show all settings, including those at their default values.
        #[arg(long = "all")]
        all: bool,
    },
}

#[derive(Subcommand, Debug)]
enum SuggestionsSubcommands {
    /// Set fuzzy matching mode (all, none, no folders).
    #[command(name = "set-fuzzy-mode", verbatim_doc_comment)]
    SetFuzzyMode {
        /// The fuzzy mode to set (all, none, no folders).
        #[arg(value_name = "MODE")]
        mode: settings::FuzzyMode,
    },
    /// Configure flycomp settings.
    #[command(name = "flycomp", verbatim_doc_comment)]
    Flycomp(flycomp::FlycompSettings),
}

#[derive(Subcommand, Debug)]
enum HistorySubcommands {
    /// Import bash/zsh history file or Atuin SQLite database into Flyline JSONL history.
    #[command(name = "import")]
    Import {
        /// Path to the bash/zsh history file or Atuin SQLite DB to import. If omitted, imports from default Atuin SQLite database.
        path: Option<std::path::PathBuf>,
    },
}

#[derive(Subcommand, Debug)]
enum PerfSubcommands {
    /// Start recording performance metrics.
    #[command(name = "start")]
    Start,
    /// Stop recording performance metrics.
    #[command(name = "stop")]
    Stop,
    /// Dump aggregated performance metrics to stdout.
    #[command(name = "dump")]
    Dump,
}

#[derive(Subcommand, Debug)]
enum KeySubcommands {
    /// Bind a key sequence to an action, optionally guarded by a context expression.
    ///
    /// KEY_SEQUENCE is a key combination such as "Ctrl+Enter" or "Alt+Left".
    /// CONTEXT_AND_ACTION has the form `<contextExpr>=<actionName>`, where
    /// `<contextExpr>` is a `+`-separated chain of camelCase context variables
    /// (each optionally prefixed with `!` to negate).  Use `always` for
    /// unconditional bindings.  Parentheses and `||` are not supported.
    ///
    /// Available context variables: always, bufferIsEmpty, bufferIsTrimmedEmpty,
    ///   fuzzyHistorySearch, tabCompletionWaiting, tabCompletion,
    ///   tabCompletionAvailable, tabCompletionOneResult,
    ///   tabCompletionMultiColAvailable, tabCompletionNoFilteredResults,
    ///   tabCompletionNoResults, agentModeWaiting, agentOutputSelection,
    ///   agentModeError, inlineSuggestionAvailable, cursorAtEnd,
    ///   cursorAtEndTrimmed, cursorAtStart, promptDirSelection,
    ///   textSelected, multilineBuffer, bufferHasAgentModePrefix,
    ///   editingBufferMode.
    ///
    /// Examples:
    ///   flyline key bind Ctrl+Enter always=submitOrNewline
    ///   flyline key bind Tab inlineSuggestionAvailable+cursorAtEnd=inlineSuggestionAccept
    ///   flyline key bind Alt+Left always=moveLeftOneWordPart
    ///   flyline key bind Ctrl+g 'always=insertString(git checkout -b )'  # Must be quoted to avoid shell syntax errors
    #[command(name = "bind", verbatim_doc_comment, disable_help_flag = true)]
    Bind {
        /// Key sequence to bind (e.g. "Ctrl+Enter", "Alt+Left").
        #[arg(num_args = 1, hide = true, add = ArgValueCompleter::new(actions::key_sequence_completer))]
        key_sequence: String,
        /// Context expression and action in the form `<contextExpr>=<actionName>`
        /// (e.g. "always=submitOrNewline").
        #[arg(add = ArgValueCompleter::new(actions::possible_context_action_completions), num_args = 1)]
        context_and_action: String,
    },
    /// List all keybindings from lowest to highest priority.
    ///
    /// User-defined bindings are marked with * in the User column and have
    /// higher priority than the built-in defaults.
    ///
    /// Optionally supply a KEY_SEQUENCE (e.g. "Tab", "Ctrl+r") to show only
    /// bindings that the given key would trigger.
    #[command(name = "list")]
    List {
        /// Optional key sequence to filter by (e.g. "Tab", "Ctrl+r").
        #[arg(add = ArgValueCompleter::new(actions::key_sequence_completer))]
        key_sequence: Option<String>,
    },
    /// Remap a key or modifier to another key or modifier.
    ///
    /// When a key event arrives, FROM is translated to TO before being matched
    /// against bindings.  Keys can only be remapped to keys; modifiers can only
    /// be remapped to modifiers.
    ///
    /// Examples:
    ///   flyline key remap tab z       # pressing Tab acts like pressing z
    ///   flyline key remap alt ctrl    # pressing Alt acts like pressing Ctrl
    ///   flyline key remap ctrl alt    # combined with above: swap Ctrl and Alt
    #[command(name = "remap", verbatim_doc_comment)]
    Remap {
        /// The key or modifier to remap from (e.g. "tab", "alt").
        #[arg(add = ArgValueCompleter::new(actions::remap_key_completer))]
        from: String,
        /// The key or modifier to remap to (e.g. "z", "ctrl").
        #[arg(add = ArgValueCompleter::new(actions::remap_key_completer))]
        to: String,
    },
}

#[derive(Subcommand, Debug)]
enum LogSubcommands {
    /// Dump all in-memory log entries to stdout.
    ///
    /// Examples:
    ///   flyline log dump
    ///   flyline log dump --last 5s
    #[command(name = "dump", verbatim_doc_comment)]
    Dump {
        /// Only show log entries from the last duration (e.g. 5s, 2m, 1h)
        #[arg(long)]
        last: Option<String>,
    },
    /// Copy in-memory log entries to the clipboard.
    ///
    /// Examples:
    ///   flyline log copy
    ///   flyline log copy --last 5s
    #[command(name = "copy", verbatim_doc_comment)]
    Copy {
        /// Only copy log entries from the last duration (e.g. 5s, 2m, 1h)
        #[arg(long)]
        last: Option<String>,
    },
    /// Set the logging level.
    ///
    /// LEVEL is one of: error, warn, info, debug, trace
    ///
    /// Examples:
    ///   flyline log set-level debug
    ///   flyline log set-level trace
    #[command(name = "set-level", verbatim_doc_comment)]
    SetLevel {
        /// Logging level to apply.
        #[arg(value_name = "LEVEL")]
        level: LogLevelArg,
    },
    /// Stream logs to a file path or to the terminal.
    ///
    /// Use `terminal` to display the last 20 log lines inside the flyline TUI
    /// on every render.  Otherwise supply a file path; existing log entries
    /// are written to the file and subsequent entries are appended.
    /// Use `stderr` to stream to standard error.
    ///
    /// Examples:
    ///   flyline log stream /tmp/flyline.log
    ///   flyline log stream stderr
    ///   flyline log stream terminal
    #[command(name = "stream", verbatim_doc_comment)]
    Stream {
        /// Destination: a file path, `stderr`, or `terminal`.
        #[arg(value_name = "FILEPATH|stderr|terminal")]
        dest: String,
    },
}

#[derive(clap::ValueEnum, Clone, Debug)]
enum LogLevelArg {
    Error,
    Warn,
    Info,
    Debug,
    Trace,
}

impl From<LogLevelArg> for log::LevelFilter {
    fn from(level: LogLevelArg) -> Self {
        match level {
            LogLevelArg::Error => log::LevelFilter::Error,
            LogLevelArg::Warn => log::LevelFilter::Warn,
            LogLevelArg::Info => log::LevelFilter::Info,
            LogLevelArg::Debug => log::LevelFilter::Debug,
            LogLevelArg::Trace => log::LevelFilter::Trace,
        }
    }
}

#[derive(Subcommand, Debug)]
enum PromptWidgetSubcommands {
    /// Create a custom prompt animation that cycles through frames.
    ///
    /// Instances of NAME in prompt strings (PS1, RPS1, PS1_FILL, PROMPT_RULER, and their _FINAL counterparts) are replaced
    /// with the current animation frame on every render.  Frames may include
    /// ANSI colour sequences written as `\e` (e.g. `\e[33m`).
    ///
    /// Examples:
    ///   flyline create-prompt-widget animation --name "MY_ANIMATION" --fps 10  ⣾ ⣷ ⣯ ⣟ ⡿ ⢿ ⣻ ⣽
    ///   flyline create-prompt-widget animation --name "john" --ping-pong --fps 5  '\e[33m\u' '\e[31m\u' '\e[35m\u' '\e[36m\u'
    ///
    /// See https://github.com/HalFrgrd/flyline/blob/master/examples/animations.sh for more details and example usage.
    #[command(name = "animation", verbatim_doc_comment)]
    Animation {
        /// Name to embed in prompt strings as the animation placeholder.
        #[arg(long)]
        name: String,
        /// Playback speed in frames per second (default: 10).
        #[arg(long, default_value = "10")]
        fps: f64,
        /// Reverse direction at each end instead of wrapping (ping-pong / bounce mode).
        #[arg(long)]
        ping_pong: bool,
        /// One or more animation frames (positional).  Use `\e` for the ESC character.
        frames: Vec<String>,
    },
    /// Show different text depending on whether mouse capture is enabled.
    ///
    /// Instances of NAME in prompt strings (PS1, RPS1, PS1_FILL, PROMPT_RULER, and their _FINAL counterparts) are replaced
    /// with ENABLED_TEXT when mouse capture is on, and DISABLED_TEXT when off.
    ///
    /// Examples:
    ///   flyline create-prompt-widget mouse-mode '🖱️' '🔴'
    ///   # Now use FLYLINE_MOUSE_MODE in your prompt:
    ///   PS1='\u@\h:\w [FLYLINE_MOUSE_MODE] $ '
    ///
    ///   flyline create-prompt-widget mouse-mode --name MOUSE_MODE "on " "off"
    #[command(name = "mouse-mode", verbatim_doc_comment)]
    MouseMode {
        /// Name to embed in prompt strings as the widget placeholder.
        /// Defaults to `FLYLINE_MOUSE_MODE`.
        #[arg(long, default_value = "FLYLINE_MOUSE_MODE")]
        name: String,
        /// Text to display when mouse capture is enabled.
        enabled_text: String,
        /// Text to display when mouse capture is disabled.
        disabled_text: String,
    },
    /// Show clickable text that copies the current command buffer to the clipboard.
    ///
    /// Instances of NAME in prompt strings (PS1, RPS1, PS1_FILL, PROMPT_RULER, and their _FINAL counterparts) are replaced
    /// with TEXT. Clicking the rendered widget copies the current command buffer
    /// to the clipboard via OSC 52.
    ///
    /// Examples:
    ///   flyline create-prompt-widget copy-buffer '[copy]'
    ///   # Now use FLYLINE_COPY_BUFFER in your prompt:
    ///   RPS1=' FLYLINE_COPY_BUFFER'
    #[command(name = "copy-buffer", verbatim_doc_comment)]
    CopyBuffer {
        /// Name to embed in prompt strings as the widget placeholder.
        /// Defaults to `FLYLINE_COPY_BUFFER`.
        #[arg(long, default_value = "FLYLINE_COPY_BUFFER")]
        name: String,
        /// Text to display in the prompt.
        text: String,
    },
    /// Run a shell command and display its output in the prompt.
    ///
    /// The output is passed through Bash's decode_prompt_string so Bash prompt
    /// escape sequences (e.g. \u, \w, ANSI colour codes) are fully supported.
    ///
    /// Examples:
    ///   # Non-blocking (default): runs in the background; shows the previous output
    ///   # while the command is running (empty on the first render).
    ///   flyline create-prompt-widget custom --name CUSTOM_WIDGET1 --command 'run_slow_git_metrics.sh'
    ///   # PS1 usage:
    ///   PS1='\u@\h:\w [CUSTOM_WIDGET1] $ '
    ///
    ///   # Non-blocking with previous output placeholder while the new output is being computed.
    ///   flyline create-prompt-widget custom --name CUSTOM_WIDGET1 --command 'run_slow_git_metrics.sh' --placeholder prev
    ///
    ///   # Blocking: waits for the command to finish before showing the prompt.
    ///   flyline create-prompt-widget custom --name CUSTOM_WIDGET2 --command 'run_something.sh' --block
    ///
    ///   # Blocking with a 500 ms timeout; falls back to placeholder if slower.
    ///   flyline create-prompt-widget custom --name CUSTOM_WIDGET3 --command 'run_slow.sh --flag' --block 500 --placeholder prev
    #[command(name = "custom", verbatim_doc_comment)]
    Custom {
        /// Name to embed in prompt strings as the widget placeholder.
        #[arg(long)]
        name: String,
        /// Command string to run; include any flags in the same string, e.g.
        /// --command './widget.sh --someflag'.
        #[arg(long)]
        command: String,
        /// Block until the command finishes, optionally with a timeout in milliseconds.
        /// With no value, polls indefinitely (i32::MAX ms ≈ 24.8 days).  If the
        /// timeout expires the command continues running in the background and
        /// subsequent renders will pick up its output.
        // default_missing_value "2147483647" == i32::MAX; proc-macro attributes
        // require a string literal so the constant cannot be referenced directly.
        #[arg(long, num_args = 0..=1, default_missing_value = "2147483647", value_name = "MS")]
        block: Option<i32>,
        /// What to show while the command is running.  Either a number (spaces) or
        /// 'prev' (use the previous output of the command).
        #[arg(long)]
        placeholder: Option<String>,
    },
    /// Show how long ago the flyline app last closed in the prompt.
    ///
    /// Instances of NAME in prompt strings (PS1, RPS1, PS1_FILL, PROMPT_RULER, and their _FINAL counterparts) are replaced
    /// with the elapsed duration on every render.  The format is compact and
    /// human-readable, for example: 9.2s, 1m23s, 1h02m03s, 1d20h43m.
    ///
    /// The widget text inherits (and may override) the style of the prompt span
    /// it is embedded in, just like other widgets.
    ///
    /// Examples:
    ///   flyline create-prompt-widget last-command-duration
    ///   # Now use FLYLINE_LAST_COMMAND_DURATION in your prompt:
    ///   RPS1=' FLYLINE_LAST_COMMAND_DURATION'
    ///
    ///   flyline create-prompt-widget last-command-duration --name MY_DURATION
    #[command(name = "last-command-duration", verbatim_doc_comment)]
    LastCommandDuration {
        /// Name to embed in prompt strings as the widget placeholder.
        /// Defaults to `FLYLINE_LAST_COMMAND_DURATION`.
        #[arg(long, default_value = "FLYLINE_LAST_COMMAND_DURATION")]
        name: String,
    },
    /// Show different text depending on whether the leader key is active.
    ///
    /// Instances of NAME in prompt strings are replaced with ACTIVE_TEXT when
    /// the leader key is active, and INACTIVE_TEXT when not active.
    ///
    /// Examples:
    ///   flyline create-prompt-widget leader-mode ' X ' ''
    #[command(name = "leader-mode", verbatim_doc_comment)]
    LeaderMode {
        /// Name to embed in prompt strings as the widget placeholder.
        /// Defaults to `FLYLINE_LEADER_MODE`.
        #[arg(long, default_value = "FLYLINE_LEADER_MODE")]
        name: String,
        /// Text to display when the leader key is active.
        active_text: String,
        /// Text to display when the leader key is inactive.
        inactive_text: String,
    },
}
#[cfg(not(test))]
pub(crate) fn call(words: *const bash_symbols::WordList) -> c_int {
    let _sigchld_guard = crate::SigchldGuard::new();
    let settings = crate::settings();
    let mut args = vec![];
    unsafe {
        let mut current = words;
        while !current.is_null() {
            let word_desc = &*(*current).word;
            if !word_desc.word.is_null() {
                let c_str = std::ffi::CStr::from_ptr(word_desc.word);
                if let Ok(str_slice) = c_str.to_str() {
                    args.push(str_slice);
                    // TODO what do the flags mean?
                    // println!("arg: {} flags: {}", str_slice, word_desc.flags);
                }
            }
            current = (*current).next;
        }
    }
    log::debug!("flyline called with args: {:?}", args);

    // args contains words from WordList; first word is not the command name unlike argv
    let args_with_prog = std::iter::once("flyline").chain(args.iter().copied());

    match FlylineArgs::try_parse_from(args_with_prog) {
        Ok(parsed) if !args.is_empty() => {
            log::debug!("Parsed flyline arguments: {:?}", parsed);

            if parsed.version {
                show_version(false);
                return shell::BuiltinExitCode::ExecutionSuccess as c_int;
            }

            if let Some(path) = parsed.load_zsh_history {
                settings.zsh_history_path = Some(path);
            }

            if let Some(enabled) = parsed.show_animations {
                log::info!("Animations disabled: {}", enabled);
                settings.show_animations = enabled;
            }

            if let Some(val) = parsed.matrix_animation {
                log::info!("Matrix animation set to {:?}", val);
                settings.matrix_animation = val;
            }

            if let Some(fps) = parsed.frame_rate {
                log::info!("Frame rate set to {}", fps);
                settings.frame_rate = fps;
            }

            if let Some(fps) = parsed.idle_frame_rate {
                log::info!("Idle frame rate set to {}", fps);
                settings.idle_frame_rate = fps;
            }

            if let Some(mode) = parsed.mouse_mode {
                log::info!("Mouse mode set to {:?}", mode);
                settings.mouse_mode = mode;
            }

            if let Some(level) = parsed.send_shell_integration_codes {
                log::info!("Shell integration codes set to {:?}", level);
                settings.send_shell_integration_codes = level;
            }

            if let Some(enabled) = parsed.enable_extended_key_codes {
                log::info!("Extended keyboard codes enabled: {}", enabled);
                settings.enable_extended_key_codes = enabled;
            }

            if let Some(enabled) = parsed.enable_easter_eggs {
                log::info!("Easter eggs enabled: {}", enabled);
                settings.enable_easter_eggs = enabled;
            }

            if let Some(logic) = parsed.set_resize_logic {
                log::info!("Resize logic set to {:?}", logic);
                settings.resize_logic = logic;
            }

            if let Some(ms) = parsed.set_delayed_startup_ms {
                log::info!("Delayed startup delay set to {}ms", ms);
                settings.delayed_startup_ms = ms;
            }

            match parsed.command {
                Some(Commands::Version { copy }) => {
                    show_version(copy);
                    return shell::BuiltinExitCode::ExecutionSuccess as c_int;
                }
                Some(Commands::AgentMode {
                    system_prompt,
                    trigger_prefix,
                    command,
                }) => {
                    let command_args: Vec<String> = shlex::split(&command)
                        .unwrap_or_else(|| command.split_whitespace().map(String::from).collect());
                    if command_args.is_empty() {
                        return_usage_error!("flyline set-agent-mode: --command must not be empty");
                    }
                    log::info!(
                        "AI command set: {:?} (trigger_prefix={:?})",
                        command_args,
                        trigger_prefix
                    );
                    settings.agent_commands.insert(
                        trigger_prefix.clone(),
                        settings::AgentModeCommand {
                            command: command_args,
                            system_prompt: system_prompt.clone(),
                        },
                    );
                }
                Some(Commands::CreatePromptWidget { subcommand }) => match subcommand {
                    PromptWidgetSubcommands::Animation {
                        name,
                        fps,
                        frames,
                        ping_pong,
                    } => {
                        if fps <= 0.0 {
                            return_usage_error!(
                                "flyline create-prompt-widget animation: --fps must be greater than 0 (got {}); animation '{}' not registered",
                                fps,
                                name
                            );
                        }
                        log::info!(
                            "Registering animation '{}' at {} fps with {} frame(s) (ping_pong={})",
                            name,
                            fps,
                            frames.len(),
                            ping_pong
                        );
                        settings.custom_animations.insert(
                            name.clone(),
                            settings::PromptAnimation {
                                name,
                                fps,
                                frames,
                                ping_pong,
                            },
                        );
                    }
                    PromptWidgetSubcommands::MouseMode {
                        name,
                        enabled_text,
                        disabled_text,
                    } => {
                        log::info!(
                            "Registering mouse-mode widget '{}' (enabled={:?}, disabled={:?})",
                            name,
                            enabled_text,
                            disabled_text
                        );
                        settings.custom_prompt_widgets.insert(
                            name.clone(),
                            settings::PromptWidget::MouseMode {
                                name,
                                enabled_text,
                                disabled_text,
                            },
                        );
                    }
                    PromptWidgetSubcommands::CopyBuffer { name, text } => {
                        log::info!(
                            "Registering copy-buffer widget '{}' (text={:?})",
                            name,
                            text
                        );
                        settings.custom_prompt_widgets.insert(
                            name.clone(),
                            settings::PromptWidget::CopyBuffer { name, text },
                        );
                    }
                    PromptWidgetSubcommands::Custom {
                        name,
                        command,
                        block,
                        placeholder,
                    } => {
                        let command_args: Vec<String> =
                            shlex::split(&command).unwrap_or_else(|| {
                                command.split_whitespace().map(String::from).collect()
                            });
                        if command_args.is_empty() {
                            return_usage_error!(
                                "flyline create-prompt-widget custom: --command must not be empty"
                            );
                        }
                        if let Some(ms) = block
                            && ms < 0
                        {
                            return_usage_error!(
                                "flyline create-prompt-widget custom: --block timeout must be non-negative (got {})",
                                ms
                            );
                        }
                        let placeholder_spec = match placeholder {
                            None => None,
                            Some(ref s) if s == "prev" => Some(settings::Placeholder::Prev),
                            Some(ref s) => match s.parse::<usize>() {
                                Ok(n) => Some(settings::Placeholder::Spaces(n)),
                                Err(_) => {
                                    return_usage_error!(
                                        "flyline create-prompt-widget custom: --placeholder must be a number or 'prev', got {:?}",
                                        s
                                    );
                                }
                            },
                        };
                        log::info!(
                            "Registering custom widget '{}' (command={:?}, block={:?}, placeholder={:?})",
                            name,
                            command_args,
                            block,
                            placeholder
                        );
                        settings.custom_prompt_widgets.insert(
                            name.clone(),
                            settings::PromptWidget::Custom(settings::PromptWidgetCustom {
                                name,
                                command: command_args,
                                block,
                                placeholder: placeholder_spec.unwrap_or_default(),
                                prev_output: std::sync::Arc::new(std::sync::Mutex::new(vec![])),
                            }),
                        );
                    }
                    PromptWidgetSubcommands::LastCommandDuration { name } => {
                        log::info!("Registering last-command-duration widget '{}'", name);
                        settings.custom_prompt_widgets.insert(
                            name.clone(),
                            settings::PromptWidget::LastCommandDuration { name },
                        );
                    }
                    PromptWidgetSubcommands::LeaderMode {
                        name,
                        active_text,
                        inactive_text,
                    } => {
                        log::info!(
                            "Registering leader-mode widget '{}' (active={:?}, inactive={:?})",
                            name,
                            active_text,
                            inactive_text
                        );
                        settings.custom_prompt_widgets.insert(
                            name.clone(),
                            settings::PromptWidget::LeaderMode {
                                name,
                                active_text,
                                inactive_text,
                            },
                        );
                    }
                },
                Some(Commands::SetColour {
                    default_theme,
                    styles,
                }) => {
                    if let Some(preset) = default_theme {
                        settings.colour_theme = preset;
                        settings.colour_palette.apply_theme(preset);
                        log::info!("Colour theme set to {:?}", preset);
                    }

                    for spec in &styles {
                        let Some((name, style_str)) = spec.split_once('=') else {
                            return_usage_error!(
                                "flyline set-style: argument must be NAME=STYLE, got {:?}",
                                spec
                            );
                        };
                        let kind = match name.parse::<palette::PaletteStyleKind>() {
                            Ok(k) => k,
                            Err(_) => {
                                return_usage_error!(
                                    "flyline set-style: unknown style name {:?}. Run 'flyline set-style --help' for valid names.",
                                    name
                                );
                            }
                        };
                        match palette::parse_str_to_style(style_str) {
                            Ok(style) => {
                                settings.colour_palette.set(kind, style);
                                log::info!("{} style set to {:?}", name, style_str);
                            }
                            Err(e) => {
                                return_usage_error!(
                                    "flyline set-style: invalid style for {:?}: {}",
                                    name,
                                    e
                                );
                            }
                        }
                    }
                }
                Some(Commands::Key {
                    debug,
                    clear_defaults,
                    subcommand,
                }) => {
                    if let Some(enabled) = debug {
                        log::info!("Key debug mode enabled: {}", enabled);
                        settings.key_debug = enabled;
                    }
                    if let Some(enabled) = clear_defaults {
                        log::info!("Clear default keybindings: {}", enabled);
                        settings.clear_default_keybindings = enabled;
                        actions::CLEAR_DEFAULTS
                            .store(enabled, std::sync::atomic::Ordering::Relaxed);
                    }

                    match subcommand {
                        Some(KeySubcommands::Bind {
                            key_sequence,
                            context_and_action,
                        }) => {
                            let binding = actions::Binding::try_new_from_strs(
                                &key_sequence,
                                &context_and_action,
                            );
                            match binding {
                                Ok(binding) => {
                                    log::info!(
                                        "Registering key binding: {} -> {}",
                                        key_sequence,
                                        context_and_action
                                    );
                                    settings.keybindings.push(binding);
                                }
                                Err(e) => {
                                    return_usage_error!("flyline key bind: {}", e);
                                }
                            }
                        }
                        Some(KeySubcommands::List { key_sequence }) => {
                            actions::print_bindings_table(
                                &settings.keybindings,
                                key_sequence.as_deref(),
                                &settings.key_remappings,
                            );
                        }
                        Some(KeySubcommands::Remap { from, to }) => {
                            match actions::try_parse_remap(&from, &to) {
                                Ok(remap) => {
                                    log::info!("Registering key remap: {} -> {}", from, to);
                                    settings.key_remappings.push(remap);
                                }
                                Err(e) => {
                                    return_usage_error!(
                                        "flyline key remap: failed to parse remap '{}' -> '{}': {}",
                                        from,
                                        to,
                                        e
                                    );
                                }
                            }
                        }
                        None => {}
                    }
                }
                Some(Commands::Mouse {
                    debug,
                    change_shape,
                    mode,
                }) => {
                    if let Some(enabled) = debug {
                        log::info!("Mouse debug mode enabled: {}", enabled);
                        settings.mouse_debug = enabled;
                    }
                    if let Some(enabled) = change_shape {
                        log::info!("Mouse change shape enabled: {}", enabled);
                        settings.mouse_change_shape = enabled;
                    }
                    if let Some(m) = mode {
                        log::info!("Mouse mode set to {:?}", m);
                        settings.mouse_mode = m;
                    }
                }
                None => {}
                Some(Commands::Log { subcommand }) => match subcommand {
                    LogSubcommands::Dump { last } => {
                        match crate::logging::get_filtered_logs(last.as_deref()) {
                            Ok(entries) => {
                                use std::io::Write;
                                let stdout = std::io::stdout();
                                let mut out = stdout.lock();
                                for entry in entries {
                                    if let Err(e) = writeln!(out, "{}", entry) {
                                        eprintln!("Failed to write log entry: {}", e);
                                    }
                                }
                            }
                            Err(e) => {
                                eprintln!("Failed to retrieve logs: {}", e);
                            }
                        }
                    }
                    LogSubcommands::Copy { last } => {
                        match crate::logging::get_filtered_logs(last.as_deref()) {
                            Ok(entries) => {
                                let len = entries.len();
                                let logs_to_copy = if len > 10_000 {
                                    entries[len - 10_000..].to_vec()
                                } else {
                                    entries
                                };
                                let joined_logs = logs_to_copy.join("\n");
                                if let Err(e) = crate::flush_stdout!(
                                    "{}",
                                    termina::escape::osc::Osc::SetSelection(
                                        termina::escape::osc::Selection::CLIPBOARD,
                                        &joined_logs
                                    )
                                ) {
                                    eprintln!("Failed to copy logs to clipboard via OSC 52: {}", e);
                                } else {
                                    println!("Copied {} log lines!", logs_to_copy.len());
                                }
                            }
                            Err(e) => {
                                eprintln!("Failed to retrieve logs: {}", e);
                            }
                        }
                    }
                    LogSubcommands::SetLevel { level } => {
                        let filter = log::LevelFilter::from(level);
                        log::set_max_level(filter);
                        log::info!("Log level set to {:?}", filter);
                    }
                    LogSubcommands::Stream { dest } => match logging::stream_logs(&dest) {
                        Ok(()) => {
                            if dest == "terminal" {
                                log::info!("Log streaming to terminal");
                            } else {
                                println!("Flyline logs streaming to {}", dest);
                            }
                        }
                        Err(e) => eprintln!("Failed to stream logs: {}", e),
                    },
                },
                Some(Commands::RunTutorial { enabled }) => {
                    let enabled = enabled.unwrap_or(true);
                    log::info!("Run tutorial set to {}", enabled);
                    settings.run_tutorial = enabled;
                    if enabled {
                        settings.tutorial_step = tutorial::TutorialStep::Welcome;
                        // clear the terminal:
                        use termina::escape::csi::{Csi, Cursor, Edit, EraseInDisplay};
                        if let Err(e) = crate::flush_stdout!(
                            "{}{}",
                            Csi::Edit(Edit::EraseInDisplay(EraseInDisplay::EraseDisplay)),
                            Csi::Cursor(Cursor::goto(0, 0))
                        ) {
                            log::warn!("Failed to clear terminal: {}", e);
                        }
                    } else {
                        settings.tutorial_step = tutorial::TutorialStep::NotRunning;
                    }
                }
                Some(Commands::Editor {
                    auto_close_chars,
                    show_inline_history,
                    select_with_mouse,
                }) => {
                    if let Some(enabled) = auto_close_chars {
                        log::info!("Auto closing char set to {}", enabled);
                        settings.auto_close_chars = enabled;
                    }
                    if let Some(enabled) = show_inline_history {
                        log::info!("Inline history suggestions set to {}", enabled);
                        settings.show_inline_history = enabled;
                    }
                    if let Some(enabled) = select_with_mouse {
                        log::info!("Select with mouse set to {}", enabled);
                        settings.select_with_mouse = enabled;
                    }
                }
                Some(Commands::History {
                    subcommand,
                    backend,
                    jsonl_path,
                }) => {
                    if let Some(path) = jsonl_path {
                        let expanded =
                            std::path::PathBuf::from(shell::backend().expand_filename(&path));
                        settings.history_jsonl_path = expanded;
                    }
                    if let Some(sub) = subcommand {
                        match sub {
                            HistorySubcommands::Import { path } => {
                                let target_jsonl_path = settings.history_jsonl_path.clone();
                                let result = if let Some(ref p) = path {
                                    let expanded_p = std::path::PathBuf::from(
                                        shell::backend().expand_filename(&p.to_string_lossy()),
                                    );
                                    crate::history::import_history_file(
                                        &expanded_p,
                                        &target_jsonl_path,
                                    )
                                    .map(|count| (count, expanded_p.display().to_string()))
                                } else {
                                    crate::history::import_atuin_history(&target_jsonl_path)
                                        .map(|count| (count, "Atuin".to_string()))
                                };

                                match result {
                                    Ok((count, src)) => {
                                        println!(
                                            "Successfully imported {} history entries from {} into {}",
                                            count,
                                            src,
                                            target_jsonl_path.display()
                                        );
                                    }
                                    Err(e) => {
                                        eprintln!("Failed to import history: {}", e);
                                    }
                                }
                            }
                        }
                    }
                    if let Some(b) = backend {
                        log::info!("History backend set to {:?}", b);
                        settings.history_backend = b;
                    }
                }
                Some(Commands::Suggestions {
                    subcommand,
                    auto_suggest,
                    git_ref_mtime,
                    use_flycomp,
                    sort_order,
                    num_suggestion_rows,
                    flycomp_output,
                    flycomp_blacklist,
                }) => {
                    if let Some(sub) = subcommand {
                        match sub {
                            SuggestionsSubcommands::SetFuzzyMode { mode } => {
                                log::info!("Fuzzy mode set to {:?}", mode);
                                settings.fuzzy_mode = mode;
                            }
                            SuggestionsSubcommands::Flycomp(opts) => {
                                log::info!("Flycomp settings updated: {:?}", opts);
                                settings.flycomp.update(opts);
                            }
                        }
                    }

                    if let Some(list) = flycomp_blacklist {
                        log::info!("Flycomp blacklist set to {:?}", list);
                        settings.flycomp.blacklist = Some(list);
                    }
                    if let Some(enabled) = auto_suggest {
                        log::info!("Auto tab-completion suggestions set to {}", enabled);
                        settings.auto_suggest = enabled;
                    }
                    if let Some(enabled) = git_ref_mtime {
                        log::info!("Git reference modification time display set to {}", enabled);
                        settings.git_ref_mtime = enabled;
                    }
                    if let Some(enabled) = use_flycomp {
                        log::info!("Use flycomp set to {}", enabled);
                        settings.flycomp.enabled = Some(enabled);
                    }
                    if let Some(order) = sort_order {
                        log::info!("Suggestion sort order set to {:?}", order);
                        settings.suggestion_sort_order = order;
                    }
                    if let Some(num) = num_suggestion_rows {
                        if num == 0 {
                            return_usage_error!(
                                "flyline suggestions: --num-suggestion-rows must be greater than 0"
                            );
                        }
                        log::info!("Suggestion row limit set to {}", num);
                        settings.num_suggestion_rows = num;
                    }
                    if let Some(path) = flycomp_output {
                        log::info!("Flycomp output directory set to '{}'", path);
                        settings.flycomp.output_dir = Some(path);
                    }
                }
                Some(Commands::Time { format }) => {
                    if let Some(fmt) = format {
                        let has_error = chrono::format::strftime::StrftimeItems::new(&fmt)
                            .any(|item| matches!(item, chrono::format::Item::Error));
                        if has_error {
                            return_usage_error!(
                                "flyline time: invalid Chrono format string: {:?}",
                                fmt
                            );
                        }
                        println!("{}", chrono::Local::now().format(&fmt));
                    } else {
                        let ns = std::time::SystemTime::now()
                            .duration_since(std::time::UNIX_EPOCH)
                            .unwrap_or_default()
                            .as_nanos();
                        println!("{}", ns);
                    }
                }
                Some(Commands::SetCursor {
                    backend,
                    interpolate,
                    interpolate_easing,
                    style,
                    effect,
                    effect_speed,
                    effect_easing,
                }) => {
                    // If the user configures flyline-only options without explicitly setting the backend,
                    // and we defaulted to terminal (e.g. on kitty), automatically switch to flyline.
                    if backend.is_none()
                        && settings.cursor_config.is_backend_unset()
                        && (style.is_some()
                            || effect.is_some()
                            || effect_speed.is_some()
                            || effect_easing.is_some())
                    {
                        log::info!(
                            "Auto-switching cursor backend to Flyline for configured options"
                        );
                        settings
                            .cursor_config
                            .set_backend(Some(cursor::CursorBackend::Flyline));
                    }

                    // set backend first since it affects the validity of other options
                    if let Some(b) = backend {
                        log::info!("Cursor backend set to {:?}", b);
                        settings.cursor_config.set_backend(Some(b));
                        if b == cursor::CursorBackend::Terminal
                            && (style.is_some()
                                || effect.is_some()
                                || effect_speed.is_some()
                                || effect_easing.is_some())
                        {
                            return_usage_error!(
                                "flyline set-cursor: --style, --effect, --effect-speed, and --effect-easing require --backend flyline"
                            );
                        }
                    }

                    // Helper closure: every flyline-only option emits the same error.
                    // Returning a `bool` lets callers chain it with the option-presence check.
                    let backend_is_terminal =
                        settings.cursor_config.backend() == cursor::CursorBackend::Terminal;

                    if let Some(interp_str) = interpolate {
                        if interp_str.eq_ignore_ascii_case("none") {
                            log::info!("Cursor interpolation disabled");
                            settings.cursor_config.interpolate = None;
                        } else {
                            match interp_str.parse::<f32>() {
                                Ok(speed) if speed > 0.0 => {
                                    log::info!("Cursor interpolation speed set to {}", speed);
                                    settings.cursor_config.interpolate = Some(speed);
                                }
                                _ => {
                                    return_usage_error!(
                                        "flyline set-cursor: --interpolate must be a positive number or 'none' (got {:?})",
                                        interp_str
                                    );
                                }
                            }
                        }
                    }

                    if let Some(easing) = interpolate_easing {
                        log::info!("Cursor interpolation easing set to {:?}", easing);
                        settings.cursor_config.interpolate_easing = easing;
                    }

                    if let Some(style_str) = style {
                        if backend_is_terminal {
                            return_usage_error!(
                                "flyline set-cursor: --style requires --backend flyline"
                            );
                        }
                        match palette::parse_cursor_style_str(&style_str) {
                            Ok(s) => {
                                log::info!("Cursor style set to {:?}", s);
                                settings.cursor_config.style = s;
                            }
                            Err(e) => {
                                return_usage_error!(
                                    "flyline set-cursor: invalid --style {:?}: {}",
                                    style_str,
                                    e
                                );
                            }
                        }
                    }

                    if let Some(eff) = effect {
                        if backend_is_terminal {
                            return_usage_error!(
                                "flyline set-cursor: --effect requires --backend flyline"
                            );
                        }
                        if eff == cursor::CursorEffect::Fade
                            && let CursorStyleConfig::Custom(style) = settings.cursor_config.style
                            && !matches!(style.bg, Some(ratatui::style::Color::Rgb(..)))
                        {
                            return_usage_error!(
                                "flyline set-cursor: --effect fade requires a custom style with an RGB background color (e.g. '#ff0000')"
                            );
                        }
                        log::info!("Cursor effect set to {:?}", eff);
                        settings.cursor_config.effect = eff;
                    }

                    if let Some(speed) = effect_speed {
                        if backend_is_terminal {
                            return_usage_error!(
                                "flyline set-cursor: --effect-speed requires --backend flyline"
                            );
                        }
                        if speed > 0.0 {
                            log::info!("Cursor effect speed set to {}", speed);
                            settings.cursor_config.effect_speed = speed;
                        } else {
                            return_usage_error!(
                                "flyline set-cursor: --effect-speed must be positive (got {})",
                                speed
                            );
                        }
                    }

                    if let Some(easing) = effect_easing {
                        if backend_is_terminal {
                            return_usage_error!(
                                "flyline set-cursor: --effect-easing requires --backend flyline"
                            );
                        }
                        log::info!("Cursor effect easing set to {:?}", easing);
                        settings.cursor_config.effect_easing = easing;
                    }
                }
                Some(Commands::Perf { subcommand }) => match subcommand {
                    PerfSubcommands::Start => {
                        crate::perf::start_recording();
                        println!("Performance recording started.");
                    }
                    PerfSubcommands::Stop => {
                        crate::perf::stop_recording();
                        println!("Performance recording stopped.");
                    }
                    PerfSubcommands::Dump => {
                        crate::perf::dump_to_stdout();
                    }
                },
                Some(Commands::Changelog) => {
                    let content = crate::changelog::pretty_changelog();
                    println!("{}", content);
                }
                Some(Commands::Upgrade) => {
                    println!("Flyline is a purely offline piece of software. Please run:");
                    println!(
                        "source <(curl -sSfL https://github.com/HalFrgrd/flyline/releases/latest/download/install.sh)"
                    );
                    settings.initial_buffer = Some("source <(curl -sSfL https://github.com/HalFrgrd/flyline/releases/latest/download/install.sh)".to_string());
                }
                Some(Commands::Settings { all }) => {
                    show_settings(&*settings, all);
                }
            }

            shell::BuiltinExitCode::ExecutionSuccess as c_int
        }
        Ok(_) => {
            log::debug!("No arguments provided to flyline");
            FlylineArgs::command().print_help().ok();
            shell::BuiltinExitCode::Usage as c_int
        }
        Err(err) => {
            match err.kind() {
                ErrorKind::DisplayHelp | ErrorKind::DisplayVersion => {
                    // user asked for --help / --version
                    err.print().unwrap();
                    shell::BuiltinExitCode::ExecutionSuccess as c_int
                }
                ErrorKind::UnknownArgument
                | ErrorKind::InvalidValue
                | ErrorKind::InvalidSubcommand
                | ErrorKind::MissingRequiredArgument
                | ErrorKind::TooManyValues
                | ErrorKind::TooFewValues
                | ErrorKind::ValueValidation => {
                    // user mistake → show error + usage
                    err.print().unwrap();
                    shell::BuiltinExitCode::Usage as c_int
                }
                _ => {
                    // unexpected / internal error
                    eprintln!("{err}");
                    shell::BuiltinExitCode::Usage as c_int
                }
            }
        }
    }
}

fn get_os_pretty_name() -> Option<String> {
    if let Ok(content) = std::fs::read_to_string("/etc/os-release") {
        for line in content.lines() {
            if let Some(rest) = line.strip_prefix("PRETTY_NAME=") {
                let name = rest.trim_matches('"').trim_matches('\'');
                return Some(name.to_string());
            }
        }
    }
    None
}

#[cfg(target_os = "linux")]
fn get_libc_version() -> Option<String> {
    let output = std::process::Command::new("ldd")
        .arg("--version")
        .output()
        .ok()?;

    // Check stdout first
    let out_str = String::from_utf8_lossy(&output.stdout);
    if let Some(line) = out_str.lines().next() {
        let trimmed = line.trim();
        if !trimmed.is_empty() {
            return Some(trimmed.to_string());
        }
    }

    // Fallback to stderr (e.g. musl ldd outputting to stderr)
    let err_str = String::from_utf8_lossy(&output.stderr);
    if let Some(line) = err_str.lines().next() {
        let trimmed = line.trim();
        if !trimmed.is_empty() {
            return Some(trimmed.to_string());
        }
    }

    None
}

fn get_os_info() -> String {
    unsafe {
        let mut uts: libc::utsname = std::mem::zeroed();
        if libc::uname(&mut uts) == 0 {
            let sysname = std::ffi::CStr::from_ptr(uts.sysname.as_ptr()).to_string_lossy();
            let release = std::ffi::CStr::from_ptr(uts.release.as_ptr()).to_string_lossy();
            let machine = std::ffi::CStr::from_ptr(uts.machine.as_ptr()).to_string_lossy();
            let os_name = if sysname == "Darwin" {
                "macOS".to_string()
            } else {
                get_os_pretty_name().unwrap_or_else(|| sysname.into_owned())
            };
            format!("{} (kernel {} {})", os_name, release, machine)
        } else {
            "Unknown OS".to_string()
        }
    }
}

fn get_system_linker_version() -> Option<String> {
    let output = std::process::Command::new("ld").arg("-v").output().ok()?;

    // Check stdout first
    let out_str = String::from_utf8_lossy(&output.stdout);
    if let Some(line) = out_str.lines().next() {
        let trimmed = line.trim();
        if !trimmed.is_empty() {
            return Some(trimmed.to_string());
        }
    }

    // Fallback to stderr (Apple's ld -v prints to stderr)
    let err_str = String::from_utf8_lossy(&output.stderr);
    if let Some(line) = err_str.lines().next() {
        let trimmed = line.trim();
        if !trimmed.is_empty() {
            return Some(trimmed.to_string());
        }
    }

    None
}

fn get_dynamic_linker_version() -> Option<String> {
    #[cfg(target_os = "linux")]
    {
        get_libc_version()
    }
    #[cfg(target_os = "macos")]
    {
        let output = std::process::Command::new("sw_vers").output().ok()?;
        if output.status.success() {
            let stdout = String::from_utf8_lossy(&output.stdout);
            let mut product = "macOS".to_string();
            let mut build = "".to_string();
            for line in stdout.lines() {
                if let Some(version) = line.strip_prefix("ProductVersion:") {
                    product = format!("macOS {}", version.trim());
                } else if let Some(b) = line.strip_prefix("BuildVersion:") {
                    build = format!(" (Build {})", b.trim());
                }
            }
            return Some(format!("dyld (tied to {}{})", product, build));
        }
        None
    }
    #[cfg(target_os = "freebsd")]
    {
        let output = std::process::Command::new("freebsd-version")
            .arg("-u")
            .output()
            .ok()?;
        if output.status.success() {
            let stdout = String::from_utf8_lossy(&output.stdout);
            if let Some(line) = stdout.lines().next() {
                let trimmed = line.trim();
                if !trimmed.is_empty() {
                    return Some(format!("rtld (tied to FreeBSD userland {})", trimmed));
                }
            }
        }
        None
    }
    #[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "freebsd")))]
    {
        None
    }
}

fn get_cpu_info() -> String {
    #[cfg(target_os = "linux")]
    {
        if let Ok(content) = std::fs::read_to_string("/proc/cpuinfo") {
            for line in content.lines() {
                if (line.starts_with("model name")
                    || line.starts_with("Processor")
                    || line.starts_with("Hardware"))
                    && let Some(pos) = line.find(':')
                {
                    let model = line[pos + 1..].trim().to_string();
                    if !model.is_empty() {
                        let cores = unsafe { libc::sysconf(libc::_SC_NPROCESSORS_ONLN) };
                        let cores_str = if cores > 0 {
                            format!(" ({} cores)", cores)
                        } else {
                            String::new()
                        };
                        return format!("{}{}", model, cores_str);
                    }
                }
            }
        }
    }
    format!("{} architecture", std::env::consts::ARCH)
}

fn get_bash_version() -> String {
    shell::backend()
        .env_var("BASH_VERSION")
        .unwrap_or_else(|| "unknown".to_string())
}

fn show_version(copy: bool) {
    let version = env!("CARGO_PKG_VERSION");
    let git_hash = env!("GIT_HASH");
    let build_mode = if cfg!(debug_assertions) {
        "debug"
    } else {
        "release"
    };
    let build_time = env!("BUILD_TIME");
    let build_target = env!("BUILD_TARGET");
    let rustc_version = env!("RUSTC_VERSION");

    let bash_version = get_bash_version();
    let shell = shell::backend()
        .env_var("SHELL")
        .unwrap_or_else(|| "unknown".to_string());
    let term = term_info::term().unwrap_or_else(|| "unknown".to_string());
    let term_program = term_info::term_program().unwrap_or_else(|| "unknown".to_string());
    let term_program_version =
        term_info::term_program_version().unwrap_or_else(|| "unknown".to_string());
    let info = term_info::get_term_info(&crate::app::GLOBAL_EVENT_READER);
    let detected_emulator = info.emulator.name().to_string();
    let detected_multiplexer = info
        .multiplexer
        .as_ref()
        .map(|m| m.name().to_string())
        .unwrap_or_else(|| "none".to_string());
    let (da_raw, da_version) = match &info.device_attributes {
        Some(da) => (da.raw.as_str(), da.version.as_deref().unwrap_or("none")),
        None => ("none", "none"),
    };
    let lang = shell::backend()
        .env_var("LANG")
        .unwrap_or_else(|| "unknown".to_string());
    let lc_all = shell::backend()
        .env_var("LC_ALL")
        .unwrap_or_else(|| "unknown".to_string());
    let lc_ctype = shell::backend()
        .env_var("LC_CTYPE")
        .unwrap_or_else(|| "unknown".to_string());

    let os_info = get_os_info();
    let sys_linker = get_system_linker_version().unwrap_or_else(|| "unknown".to_string());
    let dyn_linker = get_dynamic_linker_version().unwrap_or_else(|| "unknown".to_string());
    let cpu_info = get_cpu_info();

    let version_text = format!(
        "flyline {}\n\
         git hash: {}\n\
         build mode: {}\n\
         build datetime: {}\n\
         build target: {}\n\
         rustc version: {}\n\
         \n\
         Running in: Bash {}\n\
         shell: {}\n\
         \n\
         Running in: {} ({})\n\
         multiplexer: {}\n\
         device attributes raw: {}\n\
         device attributes version: {}\n\
         program: {}\n\
         program version: {}\n\
         locale: {} (LC_ALL: {}, LC_CTYPE: {})\n\
         \n\
         Running in: {}\n\
         system linker: {}\n\
         dynamic linker: {}\n\
         \n\
         Running on: {}\n\
         \n\
         Running in: a simulation",
        version,
        git_hash,
        build_mode,
        build_time,
        build_target,
        rustc_version,
        bash_version,
        shell,
        term,
        detected_emulator,
        detected_multiplexer,
        da_raw,
        da_version,
        term_program,
        term_program_version,
        lang,
        lc_all,
        lc_ctype,
        os_info,
        sys_linker,
        dyn_linker,
        cpu_info
    );

    println!("{}", version_text);

    if copy {
        if let Err(e) = crate::flush_stdout!(
            "{}",
            termina::escape::osc::Osc::SetSelection(
                termina::escape::osc::Selection::CLIPBOARD,
                &version_text
            )
        ) {
            log::error!("Failed to copy version text to clipboard via OSC 52: {}", e);
        }
        println!();
        if !termina::style::Stylized::is_ansi_color_disabled() {
            use termina::escape::csi::{Csi, Sgr};
            use termina::style::ColorSpec;
            println!(
                "{}Copied to clipboard!{}",
                Csi::Sgr(Sgr::Foreground(ColorSpec::GREEN)),
                Csi::Sgr(Sgr::Reset)
            );
        } else {
            println!("Copied to clipboard!");
        }
    }
}

fn show_settings(settings: &settings::Settings, all: bool) {
    let diff = settings.diff();
    let entries: Vec<_> = diff.into_iter().filter(|e| all || !e.is_default).collect();

    if entries.is_empty() {
        println!("All flyline settings are at their default values.");
        println!("Use 'flyline settings --all' to view all settings.");
        return;
    }

    for (i, entry) in entries.iter().enumerate() {
        if i > 0 {
            println!();
        }
        if !termina::style::Stylized::is_ansi_color_disabled() {
            use termina::escape::csi::{Csi, Sgr};
            use termina::style::ColorSpec;
            println!(
                "{}{}{}",
                Csi::Sgr(Sgr::Foreground(ColorSpec::GREEN)),
                entry.name,
                Csi::Sgr(Sgr::Reset)
            );
        } else {
            println!("{}", entry.name);
        }
        print_setting_val("Current", &entry.current);
        print_setting_val("Default", &entry.default);
    }
}

fn print_setting_val(label: &str, val: &str) {
    if val.contains('\n') {
        println!("  {label}:");
        for line in val.lines() {
            println!("    {line}");
        }
    } else {
        println!("  {label}: {val}");
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_perf_subcommand_completions() {
        let raw_cmd = "flyline perf ";
        let wuc = "";
        let cursor_byte = raw_cmd.len();
        let comps = complete_flyline_args(raw_cmd, wuc, cursor_byte).unwrap();
        let values: Vec<String> = comps
            .into_iter()
            .map(|c| c.get_value().to_string_lossy().into_owned())
            .collect();
        assert!(values.contains(&"start".to_string()));
        assert!(values.contains(&"stop".to_string()));
        assert!(values.contains(&"dump".to_string()));
    }

    #[test]
    fn test_flyline_key_bind_completion() {
        let raw_cmd = "flyline key bind BackTab agentModeError=";
        let wuc = "";
        let cursor_byte = raw_cmd.len();
        let comps = complete_flyline_args(raw_cmd, wuc, cursor_byte).unwrap();
        let values: Vec<String> = comps
            .into_iter()
            .map(|c| c.get_value().to_string_lossy().into_owned())
            .collect();
        assert!(!values.is_empty());
    }

    #[test]
    fn test_flyline_key_list_completion() {
        let raw_cmd = "flyline key list Ctrl+";
        let wuc = "Ctrl+";
        let cursor_byte = raw_cmd.len();
        let comps = complete_flyline_args(raw_cmd, wuc, cursor_byte).unwrap();
        let values: Vec<String> = comps
            .into_iter()
            .map(|c| c.get_value().to_string_lossy().into_owned())
            .collect();
        assert!(!values.is_empty());
        assert!(values.iter().any(|v| v.contains("Ctrl+")));
    }

    #[test]
    fn test_flyline_option_completion() {
        let raw_cmd = "flyline --show-animations=t";
        let wuc = "t";
        let cursor_byte = raw_cmd.len();
        let comps = complete_flyline_args(raw_cmd, wuc, cursor_byte).unwrap();
        let values: Vec<String> = comps
            .into_iter()
            .map(|c| c.get_value().to_string_lossy().into_owned())
            .collect();
        assert!(
            values.contains(&"--show-animations=PREFIX_DELIMtrue".to_string())
                || values.contains(&"--show-animations=PREFIX_DELIMfalse".to_string())
        );
    }

    #[test]
    fn test_version_helpers() {
        let os_info = get_os_info();
        let cpu_info = get_cpu_info();
        let bash_ver = get_bash_version();

        assert!(!os_info.is_empty());
        assert!(!cpu_info.is_empty());
        assert!(!bash_ver.is_empty());
    }

    #[test]
    fn test_flyline_suggestions_flycomp_cli_parse() {
        let raw_cmd = "flyline suggestions ";
        let wuc = "";
        let cursor_byte = raw_cmd.len();
        let comps = complete_flyline_args(raw_cmd, wuc, cursor_byte).unwrap();
        let values: Vec<String> = comps
            .into_iter()
            .map(|c| c.get_value().to_string_lossy().into_owned())
            .collect();
        assert!(values.contains(&"flycomp".to_string()));

        let args = FlylineArgs::try_parse_from([
            "flyline",
            "suggestions",
            "flycomp",
            "--enabled",
            "false",
            "--strategy",
            "run-help",
            "--sandbox",
            "false",
            "--timeout-ms",
            "8000",
            "--recurse-limit",
            "4",
            "--output-dir",
            "/tmp/completions",
            "--blacklist",
            "git",
            "cargo",
        ])
        .unwrap();

        let mut settings = settings::Settings::default();
        if let Some(Commands::Suggestions {
            subcommand: Some(SuggestionsSubcommands::Flycomp(opts)),
            ..
        }) = args.command
        {
            settings.flycomp.update(opts);
        } else {
            panic!("Expected SuggestionsSubcommands::Flycomp");
        }

        assert!(!settings.flycomp.enabled());
        assert_eq!(
            settings.flycomp.strategy(),
            flycomp::SynthesisStrategy::RunHelp
        );
        assert!(!settings.flycomp.sandbox());
        assert_eq!(settings.flycomp.timeout_ms(), 8000);
        assert_eq!(settings.flycomp.recurse_limit(), 4);
        assert_eq!(settings.flycomp.output_dir(), Some("/tmp/completions"));
        assert!(settings.flycomp.is_blacklisted("git"));
        assert!(settings.flycomp.is_blacklisted("cargo"));
        assert!(!settings.flycomp.is_blacklisted("vim"));
    }

    #[test]
    fn test_flyline_enable_easter_eggs_parse() {
        let args =
            FlylineArgs::try_parse_from(["flyline", "--enable-easter-eggs", "false"]).unwrap();
        assert_eq!(args.enable_easter_eggs, Some(false));

        let args_true =
            FlylineArgs::try_parse_from(["flyline", "--enable-easter-eggs", "true"]).unwrap();
        assert_eq!(args_true.enable_easter_eggs, Some(true));

        let args_default =
            FlylineArgs::try_parse_from(["flyline", "--enable-easter-eggs"]).unwrap();
        assert_eq!(args_default.enable_easter_eggs, Some(true));
    }

    #[test]
    fn test_flyline_settings_command_parse() {
        let args = FlylineArgs::try_parse_from(["flyline", "settings"]).unwrap();
        assert!(matches!(
            args.command,
            Some(Commands::Settings { all: false })
        ));

        let args_all = FlylineArgs::try_parse_from(["flyline", "settings", "--all"]).unwrap();
        assert!(matches!(
            args_all.command,
            Some(Commands::Settings { all: true })
        ));
    }

    #[test]
    fn test_flyline_set_idle_frame_rate_parse() {
        let args =
            FlylineArgs::try_parse_from(["flyline", "--set-idle-frame-rate", "0.5"]).unwrap();
        assert_eq!(args.idle_frame_rate, Some(0.5));

        assert!(FlylineArgs::try_parse_from(["flyline", "--set-idle-frame-rate", "0.0"]).is_err());
        assert!(FlylineArgs::try_parse_from(["flyline", "--set-idle-frame-rate", "-1.0"]).is_err());
        assert!(
            FlylineArgs::try_parse_from(["flyline", "--set-idle-frame-rate", "121.0"]).is_err()
        );
    }

    #[test]
    fn test_flyline_set_delayed_startup_ms_parse() {
        let args =
            FlylineArgs::try_parse_from(["flyline", "--set-delayed-startup-ms", "300"]).unwrap();
        assert_eq!(args.set_delayed_startup_ms, Some(300));
    }
}
