use clap::{CommandFactory, FromArgMatches, Parser, Subcommand};
use std::collections::HashSet;
use std::ffi::OsString;

#[derive(Parser)]
#[command(
    name = "kam",
    about = "A CLI toolkit for scaffolding, building, and distributing ksu/APU/Magisk/AnyTemplate modules",
    long_about = "A CLI toolkit for scaffolding, building, packaging, and distributing Android modules and templates (ksu/APU/Magisk/AnyTemplate). It supports module initialization, packaging, template management, and repo metadata exports.",
    version,
    disable_help_subcommand = true,
    help_template = "{bin} — {about}\n\nUsage: {usage}\n\nCommands:\n{subcommands}\n\nOptions:\n{options}\n"
)]
pub struct Cli {
    /// Pacman-style sync (download) flag (equivalent to pacman -S)
    #[arg(short = 'S', action = clap::ArgAction::SetTrue)]
    pub sync_flag: bool,

    /// Pacman-style search modifier (use with -S as '-Ss' to search or '-s' alone to search)
    #[arg(short = 's', long = "search", action = clap::ArgAction::SetTrue)]
    pub search_flag: bool,

    /// Positional targets: module IDs or search terms (used with -S / -s)
    #[arg(value_name = "TARGETS", num_args = 0.., last = true)]
    pub targets: Vec<String>,

    /// URL for the modules registry API (default: https://modules.kernelsu.org). Overrides the built-in modules endpoint.
    #[arg(long = "modules-url", value_name = "URL", global = true)]
    pub modules_url: Option<String>,

    /// Assume "yes" to all confirmation prompts (equivalent to -y). Use `-y` or `--yes` to skip confirmation.
    #[arg(short = 'y', long = "yes", action = clap::ArgAction::SetTrue, global = true)]
    pub assume_yes: bool,

    /// Suppress progress/output (quiet mode). Useful with pacman-style syncs (-S) to avoid progress bars in CI.
    #[arg(short = 'q', long = "quiet", action = clap::ArgAction::SetTrue, global = true)]
    pub quiet: bool,

    /// Update (refresh) the modules registry index before sync/download (equivalent to `kam repo sync --force`). Use `-u` or `--update` to enable.
    #[arg(short = 'u', long = "update", action = clap::ArgAction::SetTrue, global = true)]
    pub update_index: bool,

    #[command(subcommand)]
    pub command: Option<Commands>,
}

#[derive(Subcommand)]
pub enum Commands {
    /// Initialize a new Kam project from templates (supports meta and kernel templates)
    Init(crate::cmds::init::InitArgs),

    /// Build and package a module into a deployable ZIP artifact
    Build(crate::cmds::build::BuildArgs),

    /// Manage module versions and bump policies
    Version(crate::cmds::version::VersionArgs),

    /// Manage module versions and bump policies
    Cache(crate::cmds::cache::CacheArgs),

    /// Manage templates: import, export, package, and list
    Tmpl(crate::cmds::tmpl::TmplArgs),

    /// Validate `kam.toml` configuration and templates
    Validate(crate::cmds::validate::ValidateArgs),

    /// Generate shell completion scripts for common shells
    Completions(crate::cmds::completion::CompletionArgs),

    /// Secret keyring management (used by sign/verify tasks)
    Secret(crate::cmds::secret::SecretArgs),

    /// Sign an artifact using a key from the keyring or a PEM file
    Sign(crate::cmds::sign::SignArgs),
    /// Verify an artifact signature (.sig) or a sigstore bundle (DSSE)
    Verify(crate::cmds::verify::VerifyArgs),

    /// Check project JSON/YAML/Markdown files (lint/format/parse)
    Check(crate::cmds::check::CheckArgs),

    /// Export `kam.toml` to `module.prop`, `module.json`, `repo.json`, `track.json`, `config.json`, `update.json`
    Export(crate::cmds::export::ExportArgs),

    /// Inspect and edit `kam.toml` using dot-path keys (get/set/unset/list)
    Toml(crate::cmds::toml::TomlArgs),

    /// Manage per-project or global kam configuration (similar to git config)
    Config(crate::cmds::config::ConfigArgs),

    /// Act as a compatibility layer for Magisk, KernelSU, and APatchSU to install modules (delegates to configured root manager)
    Install(crate::cmds::install::InstallArgs),

    /// Interact with module repository (search/download)
    Repo(crate::cmds::repo::RepoArgs),

    /// Display about information for Kam and credits
    About(crate::cmds::about::AboutArgs),

    /// Print environment variables starting with KAM_ (for debugging)
    Env(crate::cmds::env::EnvArgs),

    /// Print this message or the help of the given subcommand(s)
    Help(crate::cmds::help::HelpArgs),
}

impl Cli {
    /// Parse an argv-like iterator while supporting pacman-style short-flag combinations
    /// (e.g. `-Syu`) by injecting a `--` before positional `TARGETS` when appropriate.
    ///
    /// Accepts any iterator over items that can be borrowed as an `OsStr` (so `&str`,
    /// `String`, `OsString`, etc. all work). Returns a `Result<Cli, clap::Error>`
    /// mirroring clap's `try_parse_from` semantics.
    pub fn try_parse_from_with_pacman<I, T>(args: I) -> Result<Self, clap::Error>
    where
        I: IntoIterator<Item = T>,
        T: AsRef<std::ffi::OsStr>,
    {
        // Collect into OsString for manipulation
        let mut args_os: Vec<OsString> = args
            .into_iter()
            .map(|t| t.as_ref().to_os_string())
            .collect();

        // If the user already supplied `--` we don't touch the args.
        if args_os.iter().any(|a| a == &OsString::from("--")) {
            let matches = Self::command().try_get_matches_from(args_os)?;
            return Self::from_arg_matches(&matches);
        }

        // Collect subcommand names so we don't mistakenly insert `--` before a real subcommand.
        let sub_names: HashSet<String> = Self::command()
            .get_subcommands()
            .map(|s| s.get_name().to_string())
            .collect();

        // Look for tokens that are either explicit sync/search tokens or combined short flags
        // containing 'S' or 's' (e.g., `-Syu`, `-yuS`, `-Ss`) and insert `--` before the next
        // non-option/non-subcommand token so positional `TARGETS` are parsed correctly.
        for i in 1..args_os.len() {
            if let Some(tok) = args_os[i].to_str() {
                // Exact matches we historically supported.
                let explicit =
                    tok == "-S" || tok == "-s" || tok == "-Ss" || tok == "-sS" || tok == "--search";

                // Detect combined short flags like `-Syu` or `-yuS`. We only accept letters
                // from the known set to avoid surprising behavior with unrelated flags.
                let combined = if tok.starts_with('-') && !tok.starts_with("--") {
                    let rest = &tok[1..];
                    let mut all_known = true;
                    let mut contains_sync_or_search = false;
                    for ch in rest.chars() {
                        match ch {
                            'S' | 's' => contains_sync_or_search = true,
                            'y' | 'u' | 'q' => {}
                            _ => {
                                all_known = false;
                                break;
                            }
                        }
                    }
                    all_known && contains_sync_or_search
                } else {
                    false
                };

                if (explicit || combined)
                    && i + 1 < args_os.len()
                    && let Some(next) = args_os[i + 1].to_str()
                    && !next.starts_with('-')
                    && !sub_names.contains(next)
                {
                    args_os.insert(i + 1, OsString::from("--"));
                    break;
                }
            }
        }

        // Parse using the derived Command (mirrors clap's default parsing path).
        let matches = Self::command().try_get_matches_from(args_os)?;
        Self::from_arg_matches(&matches)
    }

    /// Convenience wrapper that panics on error (like clap's `parse_from`).
    pub fn parse_from_with_pacman<I, T>(args: I) -> Self
    where
        I: IntoIterator<Item = T>,
        T: AsRef<std::ffi::OsStr>,
    {
        Self::try_parse_from_with_pacman(args).unwrap()
    }
}

/// Preprocess argv and insert `--` before pacman-style targets when appropriate.
///
/// This is a pure helper function that can be used by `main()` if it wants to
/// apply the same heuristic used by `Cli::try_parse_from_with_pacman`. It accepts
/// a mutable `Vec<OsString>` (as produced by `env::args_os().collect()`) and a
/// mutable reference to a `clap::Command` so it can consult subcommand names.
pub fn inject_double_dash_for_targets(
    mut args: Vec<std::ffi::OsString>,
    cmd: &mut clap::Command,
) -> Vec<std::ffi::OsString> {
    use std::collections::HashSet;

    // If the user already supplied `--` do nothing.
    if args.iter().any(|a| a == &std::ffi::OsString::from("--")) {
        return args;
    }

    // Known subcommand names to avoid inserting `--` before a real subcommand.
    let sub_names: HashSet<String> = cmd
        .get_subcommands()
        .map(|s| s.get_name().to_string())
        .collect();

    for i in 1..args.len() {
        if let Some(tok) = args[i].to_str() {
            let mut should_inject = false;

            // Exact matches (long or simple short forms)
            if tok == "-S" || tok == "-s" || tok == "-Ss" || tok == "-sS" || tok == "--search" {
                should_inject = true;
            } else if tok.starts_with('-') && !tok.starts_with("--") {
                // Potential combined short flags like `-Syu`, `-yuS`, `-Ss`.
                // Only treat as combined short flags if every character after '-'
                // is one of the known short options we support here (S, s, y, u, q).
                let mut all_known = true;
                let mut contains_sync_or_search = false;
                for ch in tok.chars().skip(1) {
                    match ch {
                        'S' | 's' => contains_sync_or_search = true,
                        'y' | 'u' | 'q' => {}
                        _ => {
                            all_known = false;
                            break;
                        }
                    }
                }
                if all_known && contains_sync_or_search {
                    should_inject = true;
                }
            }

            if should_inject
                && i + 1 < args.len()
                && let Some(next) = args[i + 1].to_str()
                && !next.starts_with('-')
                && !sub_names.contains(next)
            {
                args.insert(i + 1, std::ffi::OsString::from("--"));
                break;
            }
        }
    }

    args
}
