pub use clap::{Args, Parser, Subcommand};

use crate::shell::Shell;

/// amoxide — the alias manager
///
/// Manage your shell aliases — globally, via profiles, or per-project via .aliases files.
/// Activate multiple profiles simultaneously with `am profile use`.
#[derive(Parser)]
#[command(name = "am")]
#[command(about = "The Alias-Manager", long_about = None, version, author)]
#[command(propagate_version = true)]
pub struct Cli {
    #[command(subcommand)]
    pub command: Commands,
}

#[derive(Subcommand)]
pub enum Commands {
    /// Add a new alias
    #[command(alias = "a", trailing_var_arg = true)]
    Add(Alias),

    /// Remove an alias
    #[command(alias = "r")]
    Remove {
        #[command(flatten)]
        scope: TargetScopeArgs,

        /// The alias name to remove
        name: String,

        /// Subcommand path segments to complete the key (e.g. --sub b --sub l removes jj:b:l)
        #[arg(long = "sub")]
        sub: Vec<String>,
    },

    /// List all profiles and project aliases
    #[command(alias = "l")]
    Ls {
        /// Show only active profiles and loaded project aliases
        #[arg(short, long, default_value_t = false)]
        used: bool,
        /// Show descriptions next to aliases (when present)
        #[arg(short = 'd', long = "descriptions", default_value_t = false)]
        descriptions: bool,
    },
    /// List all active profiles and project aliases (always shows descriptions)
    La,
    /// Check if the shell is set up correctly
    Status,

    /// Manage profiles (defaults to listing when no subcommand given)
    #[command(alias = "p")]
    Profile {
        #[command(subcommand)]
        action: Option<ProfileAction>,
    },

    /// Manage alias variables — substituted as `{{name}}` in alias commands.
    #[command(alias = "v")]
    Var {
        #[command(subcommand)]
        action: VarAction,
    },

    /// Print shell init code
    ///
    /// This outputs shell code that loads your profile aliases and installs
    /// a cd hook for automatic project alias loading. Add one of these lines
    /// to your shell's config file:
    ///
    ///   Bash        ~/.bashrc                     eval "$(am init bash)"
    ///   Brush       ~/.brushrc                    eval "$(am init brush)"
    ///   Fish        ~/.config/fish/config.fish    am init fish | source
    ///   Nushell     ~/.config/nushell/config.nu   am init nu | source
    ///   PowerShell  $PROFILE                      (am init powershell) -join "`n" | Invoke-Expression
    ///   Zsh         ~/.zshrc                      eval "$(am init zsh)"
    ///
    /// Note: Nushell is not yet supported.
    #[command(verbatim_doc_comment)]
    Init {
        /// Force re-initialisation: unload all previously tracked aliases (both
        /// alias and function forms) before re-loading. Use after config changes
        /// such as toggling `use_abbr`.
        #[arg(short = 'f', long)]
        force: bool,
        shell: Shell,
    },

    /// Guided setup — adds amoxide to your shell profile, or wires `am context`
    /// into an AI coding agent's session-start hook.
    ///
    /// Examples:
    ///   am setup zsh       Wire amoxide into ~/.zshrc
    ///   am setup claude    Wire `am context` into ~/.claude/settings.json
    #[command(verbatim_doc_comment)]
    Setup { target: SetupTarget },

    /// Print a model-friendly snapshot of the active alias set for AI coding agents.
    ///
    /// Designed to be wired into a session-start hook so the agent can expand
    /// short forms (e.g. `git cm`, `gst`) into the canonical commands before running.
    ///
    /// Use `am setup claude` to wire this into ~/.claude/settings.json.
    #[command(verbatim_doc_comment)]
    Context {
        /// Expand shadowed-section into a full table and include invalid-alias diagnostics
        #[arg(short, long)]
        verbose: bool,
    },

    /// Shortcut for `am profile use` — toggle one or more profiles
    #[command(alias = "u")]
    Use(ProfileUse),

    /// Launch the interactive TUI for managing aliases and profiles
    #[command(alias = "t")]
    Tui,

    /// Export aliases to stdout as TOML
    #[command(alias = "e")]
    Export(ExportArgs),

    /// Import aliases from a URL or file
    #[command(alias = "i")]
    Import(ImportArgs),

    /// Generate a share command for posting aliases to a pastebin service
    #[command(alias = "s")]
    Share(ShareArgs),

    /// Review and trust the project .aliases file in the current directory
    Trust,

    /// Remove trust for the project .aliases file in the current directory
    Untrust {
        /// Forget the path entirely (remove from security tracking instead of marking untrusted)
        #[arg(short, long)]
        forget: bool,
    },

    /// Internal: compute and emit the minimal shell ops to sync the shell with
    /// the effective merged alias state (global + profile + project).
    #[command(hide = true)]
    Sync {
        /// Suppress info and warning messages (still unloads/loads aliases).
        #[arg(short, long)]
        quiet: bool,
        shell: Shell,
    },

    /// Internal: refresh the update-check cache from crates.io. Spawned
    /// detached by the listing commands; never invoked directly by users.
    #[command(name = "__update-check", hide = true)]
    UpdateCheck,
}

/// Positional target for `am setup`. Flat over shells and AI agents so
/// `am setup zsh` and `am setup claude` share one ergonomic shape.
#[derive(clap::ValueEnum, Clone, Debug, PartialEq)]
pub enum SetupTarget {
    Bash,
    Brush,
    Fish,
    Powershell,
    Zsh,
    Claude,
}

#[derive(Args)]
pub struct ProfileUse {
    /// Enable given profile(s), does not toggle
    #[arg(short = 'e', long = "enable", conflicts_with = "disable")]
    pub enable: bool,
    /// Disable given profile(s), does not toggle
    #[arg(short = 'd', long = "disable", conflicts_with = "enable")]
    pub disable: bool,
    /// Activate at specific priority position (1-based). Repositions if already active.
    #[arg(short = 'n', long = "priority", conflicts_with = "inverse")]
    pub priority: Option<usize>,
    /// Reverse the processing order (first listed = highest priority)
    #[arg(short, long, conflicts_with = "priority")]
    pub inverse: bool,
    /// Profile names
    pub names: Vec<String>,
}

#[derive(Subcommand)]
pub enum ProfileAction {
    /// Add a new profile
    #[command(alias = "a")]
    Add {
        /// Profile name
        name: String,
    },

    /// Toggle one or more profiles as active/inactive, optionally at a specific priority
    #[command(alias = "u")]
    Use(ProfileUse),

    /// Remove a profile
    #[command(alias = "r")]
    Remove {
        /// Profile name
        name: String,

        /// Skip confirmation prompt
        #[arg(short, long)]
        force: bool,
    },

    /// List all profiles
    #[command(alias = "l")]
    List {
        /// Show only active profiles and loaded project aliases
        #[arg(short, long, default_value_t = false)]
        used: bool,
    },
}

#[derive(Args)]
pub struct Alias {
    #[command(flatten)]
    pub scope: TargetScopeArgs,

    /// Disable {{N}} template detection (treat command as literal)
    #[arg(long)]
    pub raw: bool,

    /// Optional description shown next to the alias in `am ls -d` and the TUI
    #[arg(short = 'd', long = "description")]
    pub description: Option<String>,

    /// The alias name
    pub name: String,

    /// The command to alias
    pub command: Option<Vec<String>>,

    /// Define a subcommand alias (repeatable: --sub short long)
    #[arg(long = "sub", num_args = 2, value_names = ["SHORT", "LONG"], action = clap::ArgAction::Append)]
    pub sub: Vec<String>,
}

/// Shared scope flags for export/import/share commands.
/// Flags can be combined (e.g. `-l -g -p git`) to select multiple scopes.
/// `--all` is a shortcut for all scopes and cannot be combined with others.
#[derive(Args, Debug, Clone)]
pub struct ScopeArgs {
    /// Operate on project-local aliases
    #[arg(short, long, conflicts_with = "all")]
    pub local: bool,

    /// Operate on global aliases
    #[arg(short, long, conflicts_with = "all")]
    pub global: bool,

    /// Operate on specific profile(s) — can be repeated
    #[arg(short, long, conflicts_with = "all")]
    pub profile: Vec<String>,

    /// Operate on everything (global + all profiles + local)
    #[arg(long, conflicts_with_all = ["local", "global", "profile"])]
    pub all: bool,
}

#[derive(Args)]
pub struct ExportArgs {
    #[command(flatten)]
    pub scope: ScopeArgs,

    /// Encode output as base64
    #[arg(short = 'b', long, alias = "b64")]
    pub base64: bool,
}

#[derive(Args)]
pub struct ShareArgs {
    #[command(flatten)]
    pub scope: ScopeArgs,

    /// Generate command for termbin.com (netcat)
    #[arg(long, conflicts_with = "paste_rs")]
    pub termbin: bool,

    /// Generate command for paste.rs (curl)
    #[arg(long, conflicts_with = "termbin")]
    pub paste_rs: bool,
}

/// Scope flags for single-target operations (alias add/remove, var set/unset/get/list).
/// Distinct from `ScopeArgs`, which is for bulk operations (export/import/share)
/// and supports `--all` plus repeatable `-p`.
#[derive(Args, Debug, Clone)]
pub struct TargetScopeArgs {
    /// Operate on a specific profile (defaults to active profile)
    #[arg(short, long, conflicts_with_all = ["local", "global"])]
    pub profile: Option<String>,

    /// Operate on the project's .aliases file
    #[arg(short, long, conflicts_with = "global")]
    pub local: bool,

    /// Operate on global config
    #[arg(short, long)]
    pub global: bool,
}

#[derive(Subcommand)]
pub enum VarAction {
    /// Set a variable's value (upsert)
    #[command(alias = "s")]
    Set {
        #[command(flatten)]
        scope: TargetScopeArgs,
        /// Variable name
        name: String,
        /// Variable value
        #[arg(allow_hyphen_values = true)]
        value: String,
    },
    /// Remove a variable
    #[command(alias = "u")]
    Unset {
        #[command(flatten)]
        scope: TargetScopeArgs,
        /// Variable name
        name: String,
    },
    /// Print a variable's value
    #[command(alias = "g")]
    Get {
        #[command(flatten)]
        scope: TargetScopeArgs,
        /// Variable name
        name: String,
    },
    /// List variables (all scopes if no flag given)
    #[command(alias = "l")]
    List {
        #[command(flatten)]
        scope: TargetScopeArgs,
    },
}

#[derive(Args)]
pub struct ImportArgs {
    /// URL or file path to import from
    pub source: String,

    #[command(flatten)]
    pub scope: ScopeArgs,

    /// Decode base64 input before parsing
    #[arg(short = 'b', long, alias = "b64")]
    pub base64: bool,

    /// Skip all confirmation prompts
    #[arg(short = 'y', long = "yes")]
    pub yes: bool,

    /// DANGER: Skip safety checks for suspicious content (escape sequences).
    /// Only use for your own exports. Never trust external input blindly —
    /// it can carry invisible escape sequences that hide malicious commands.
    #[arg(long, requires = "yes")]
    pub trust: bool,
}

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

    #[test]
    fn context_subcommand_parses_with_no_flags() {
        let cli = Cli::try_parse_from(["am", "context"]).expect("should parse");
        match cli.command {
            Commands::Context { verbose } => {
                assert!(!verbose);
            }
            _ => panic!("expected Commands::Context"),
        }
    }

    #[test]
    fn context_subcommand_parses_with_verbose() {
        let cli = Cli::try_parse_from(["am", "context", "--verbose"]).expect("should parse");
        match cli.command {
            Commands::Context { verbose } => {
                assert!(verbose);
            }
            _ => panic!("expected Commands::Context"),
        }
    }

    #[test]
    fn setup_subcommand_parses_shell_target() {
        let cli = Cli::try_parse_from(["am", "setup", "zsh"]).expect("should parse");
        match cli.command {
            Commands::Setup { target } => assert_eq!(target, SetupTarget::Zsh),
            _ => panic!("expected Commands::Setup"),
        }
    }

    #[test]
    fn setup_subcommand_parses_claude_target() {
        let cli = Cli::try_parse_from(["am", "setup", "claude"]).expect("should parse");
        match cli.command {
            Commands::Setup { target } => assert_eq!(target, SetupTarget::Claude),
            _ => panic!("expected Commands::Setup"),
        }
    }

    #[test]
    fn setup_subcommand_rejects_unknown_target() {
        let result = Cli::try_parse_from(["am", "setup", "openai"]);
        assert!(result.is_err(), "unknown target must be rejected");
    }

    /// Var values often contain compiler/tool flags like `-C opt-level=3`.
    /// Without `allow_hyphen_values` clap rejects them as unknown flags.
    #[test]
    fn var_set_accepts_hyphen_leading_value() {
        let cli = Cli::try_parse_from(["am", "var", "set", "-g", "opt-flags", "-C opt-level=3"])
            .expect("should parse a hyphen-leading var value");
        match cli.command {
            Commands::Var {
                action: VarAction::Set { name, value, .. },
            } => {
                assert_eq!(name, "opt-flags");
                assert_eq!(value, "-C opt-level=3");
            }
            _ => panic!("expected Var::Set, got something else"),
        }
    }
}
