/*
Kam CLI entrypoint with comprehensive help localization.

This file builds the clap Command from the derive-generated `Cli` struct,
injects localized about/help strings (top-level, subcommands and arguments)
based on keys present in `src/i18n/{en,zh}.toml`, then parses arguments
using the localized Command so `--help` and subcommand help display translations.

Notes:
- Translated keys used:
  - cli.about / cli.long_about
  - cli.flags.<arg_id>
  - cli.commands.<cmd>[.<subcmd>...].summary
  - cli.commands.<cmd>[.<subcmd>...].description
  - cli.commands.<cmd>[.<subcmd>...].flags.<arg_id>

- The i18n layer (`kam::i18n::tr_key`) returns the original key if there is no
  translation present; we use that to detect missing translations and fall back to
  the default static help strings generated by clap.
*/

use clap::{CommandFactory, FromArgMatches};
use colored::Colorize;
use dotenvy::dotenv;
use kam::errors::KamError;
use std::error::Error;

use kam::cli::{Cli, Commands};

fn print_error_chain(e: &KamError) {
    use kam::utils::Utils;
    Utils::error(&format!("{}", e));
    let mut source = e.source();
    while let Some(s) = source {
        eprintln!("  {} {}", "→".dimmed(), s.to_string().dimmed());
        source = s.source();
    }
}

fn main() {
    // Load environment variables (e.g., KAM_UI_LANGUAGE overrides)
    dotenv().ok();

    // Initialize i18n early so we can use translations when building the CLI help.
    kam::i18n::init();

    // Build the clap `Command` from the derive-generated parser so we can customize it.
    let mut cmd = Cli::command();

    // Localize top-level about / long_about (if translations exist).
    let about_text = kam::i18n::tr_key("cli.about");
    if about_text != "cli.about" {
        // Use an owned String to avoid lifetime issues with clap's API.
        cmd = cmd.about(about_text.to_string());
    }
    let long_about_text = kam::i18n::tr_key("cli.long_about");
    if long_about_text != "cli.long_about" {
        cmd = cmd.long_about(long_about_text.to_string());
    }

    // Localize top-level/global arguments (if translations exist).
    // Collect replacements first, then apply, to avoid borrowing issues.
    {
        let mut replacements = Vec::new();
        for arg in cmd.get_arguments() {
            let arg_id = arg.get_id().as_str();
            let key = format!("cli.flags.{}", arg_id);
            let tr = kam::i18n::tr_key(&key);
            if tr != key {
                replacements.push(arg.clone().help(tr.to_string()));
            }
        }
        if !replacements.is_empty() {
            for repl in replacements {
                cmd = cmd.clone().arg(repl);
            }
        }
    }

    // Recursive localization for subcommands and nested subcommands.
    fn localize_command(cmd: &mut clap::Command, prefix: &str) {
        for sub in cmd.get_subcommands_mut() {
            let name = sub.get_name();
            let sub_prefix = format!("{}.{}", prefix, name);

            // Localize summary/about
            let summary_key = format!("{}.summary", sub_prefix);
            let summary = kam::i18n::tr_key(&summary_key);
            if summary != summary_key {
                *sub = sub.clone().about(summary.to_string());
            }

            // Localize long description (if present)
            let desc_key = format!("{}.description", sub_prefix);
            let desc = kam::i18n::tr_key(&desc_key);
            if desc != desc_key {
                *sub = sub.clone().long_about(desc.to_string());
            }

            // Collect argument replacements (clone Arg objects and localized help)
            let mut arg_repls: Vec<clap::Arg> = Vec::new();
            for arg in sub.get_arguments() {
                let arg_id = arg.get_id().as_str();
                let arg_key = format!("{}.flags.{}", sub_prefix, arg_id);
                let arg_tr = kam::i18n::tr_key(&arg_key);
                if arg_tr != arg_key {
                    arg_repls.push(arg.clone().help(arg_tr.to_string()));
                }
            }

            // Apply argument replacements (adding an Arg with the same id will
            // overwrite the previous definition in clap's builder).
            for repl in arg_repls {
                *sub = sub.clone().arg(repl);
            }

            // Recurse into nested subcommands.
            localize_command(sub, &sub_prefix);
        }
    }

    // Kick off recursive localization for subcommands under "cli.commands".
    localize_command(&mut cmd, "cli.commands");

    // The `help` subcommand is provided as a real subcommand (see `Commands::Help`)
    // and is localized above via `localize_command`. Disable clap's built-in `help`
    // subcommand so it doesn't collide with the derived `Help` subcommand.
    cmd = cmd.clone().disable_help_subcommand(true);

    // Parse using the localized Command so help output is translated.
    // Use a cloned `cmd` so we can still reference the original for printing help later.
    //
    // Heuristic: when `-S`/`-s` is used and the next token looks like a target
    // (not an option and not a real subcommand) automatically insert `--` so
    // `kam -S <target>` works without the user having to type `--`.
    let matches = {
        // Collect argv and attempt to inject `--` after -S/-s when appropriate.
        let mut args: Vec<std::ffi::OsString> = std::env::args_os().collect();

        // If the user already supplied `--` we don't touch the args.
        if !args.iter().any(|a| a == &std::ffi::OsString::from("--")) {
            // Build a set of known subcommand names so we don't insert `--`
            // before a legitimate subcommand.
            let sub_names: std::collections::HashSet<String> = cmd
                .get_subcommands()
                .map(|s| s.get_name().to_string())
                .collect();

            // Look for -S/-s (or common short-combos like -Ss) tokens and, if the
            // immediate next token exists and is not an option and not a subcommand,
            // insert `--` before it.
            for i in 1..args.len() {
                if let Some(tok) = args[i].to_str() {
                    if tok == "-S"
                        || tok == "-s"
                        || tok == "-Ss"
                        || tok == "-sS"
                        || tok == "--sync"
                        || tok == "--search"
                    {
                        if i + 1 < args.len() {
                            if let Some(next) = args[i + 1].to_str() {
                                if !next.starts_with('-') && !sub_names.contains(next) {
                                    args.insert(i + 1, std::ffi::OsString::from("--"));
                                    break;
                                }
                            }
                        }
                    }
                }
            }
        }

        // Parse from the (possibly modified) argv list so clap receives the `--`
        // we've injected when appropriate.
        cmd.clone().get_matches_from(args)
    };

    // Convert ArgMatches to typed `Cli` (handling parse errors).
    let cli = match Cli::from_arg_matches(&matches) {
        Ok(c) => c,
        Err(e) => {
            eprintln!("Error parsing arguments: {}", e);
            std::process::exit(2);
        }
    };

    // Handle pacman-style top-level flags (-S / -s) before dispatching to subcommands.
    // - `-Ss <query>` performs a remote search
    // - `-S <moduleId>` downloads the latest release ZIP for the given module(s)
    if cli.sync || cli.search {
        // SPECIAL CASE: When sync/search flags are present but no targets were parsed,
        // it might be because clap interpreted a search term as a subcommand.
        // In this case, we check if the command is one that might be used as a search term.
        let effective_targets = if cli.targets.is_empty() {
            // If no targets were collected but a command was parsed, the command might have been
            // intended as a search term (e.g. "kam -Ss cache" where "cache" is a subcommand)
            // This is a limitation of clap where subcommands take precedence over positional args
            match &cli.command {
                Some(_) => {
                    // For now, we'll just use an empty vector and let the repo module handle the error
                    // The correct solution would require changing the CLI architecture to avoid this conflict
                    cli.targets.clone()
                }
                None => cli.targets.clone(),
            }
        } else {
            cli.targets.clone()
        };

        match kam::cmds::repo::handle_pacman_style(
            cli.sync,
            cli.search,
            effective_targets,
            cli.assume_yes,
            cli.modules_url.clone(),
        ) {
            Ok(()) => return,
            Err(e) => {
                print_error_chain(&e);
                std::process::exit(1);
            }
        }
    }

    // Dispatch to subcommand handlers (command is optional).
    // If no subcommand is provided (None) we treat it as a no-op returning Ok(()).
    let res: Result<(), KamError> = match cli.command {
        Some(Commands::Init(args)) => kam::cmds::init::run(args),
        Some(Commands::Build(args)) => kam::cmds::build::run(args),
        Some(Commands::Version(args)) => kam::cmds::version::run(args),
        Some(Commands::Cache(args)) => kam::cmds::cache::run(args),
        Some(Commands::Tmpl(args)) => kam::cmds::tmpl::run(args),
        Some(Commands::Validate(args)) => kam::cmds::validate::run(args),
        Some(Commands::Completions(args)) => kam::cmds::completion::run(args),
        Some(Commands::Secret(args)) => kam::cmds::secret::run(args),
        Some(Commands::Sign(args)) => kam::cmds::sign::run(args),
        Some(Commands::Verify(args)) => kam::cmds::verify::run(args),
        Some(Commands::Check(args)) => kam::cmds::check::run(args),
        Some(Commands::Export(args)) => kam::cmds::export::run(args),
        Some(Commands::Config(args)) => kam::cmds::config::run(args),
        Some(Commands::Toml(args)) => kam::cmds::toml::run(args),
        Some(Commands::Install(args)) => kam::cmds::install::run(args),
        Some(Commands::Repo(args)) => kam::cmds::repo::run(args),
        Some(Commands::Help(args)) => {
            // Print top-level or subcommand help using a cloned, mutable Command so
            // we can traverse nested subcommand definitions and call `print_long_help()`.
            if args.subcommand.is_empty() {
                let mut tmp = cmd.clone();
                if let Err(e) = tmp.print_long_help() {
                    kam::utils::Utils::error(&format!("Failed to write help: {}", e));
                    std::process::exit(1);
                }
                println!();
                Ok(())
            } else {
                // Clone the command and traverse mutable references to subcommands so
                // we can call the mutable `print_long_help()` API on the target.
                let mut cur_owned = cmd.clone();
                let mut cur = &mut cur_owned;
                for name in &args.subcommand {
                    match cur
                        .get_subcommands_mut()
                        .find(|s| s.get_name() == name.as_str())
                    {
                        Some(s) => cur = s,
                        None => {
                            kam::utils::Utils::error(&format!("Unknown subcommand: {}", name));
                            std::process::exit(2);
                        }
                    }
                }
                if let Err(e) = cur.print_long_help() {
                    kam::utils::Utils::error(&format!("Failed to write help: {}", e));
                    std::process::exit(1);
                }
                println!();
                Ok(())
            }
        }
        Some(Commands::About(args)) => kam::cmds::about::run(args),
        None => Ok(()),
    };

    if let Err(e) = res {
        print_error_chain(&e);
        std::process::exit(1);
    }
}
