use std::env;
use std::fmt::Display;
use std::str::FromStr;
use std::sync::Mutex;
use std::sync::OnceLock;

struct FlagDef {
    long: String,
    short: String,
    help: String,
}

struct GlobalCtx {
    flags: Vec<FlagDef>,
    args: Vec<String>,
}

static CTX: OnceLock<Mutex<GlobalCtx>> = OnceLock::new();

fn get_ctx() -> &'static Mutex<GlobalCtx> {
    CTX.get_or_init(|| {
        Mutex::new(GlobalCtx {
            flags: Vec::new(),
            args: env::args().collect(),
        })
    })
}

pub struct Arg {
    long: String,
    short: String,
}

/// Declare a command-line flag.
pub fn flag(long: &str, short: &str, help: &str) -> Arg {
    let long_form = format!("--{}", long);
    let short_form = format!("-{}", short);

    let mut ctx = get_ctx().lock().unwrap();
    if !ctx.flags.iter().any(|f| f.long == long_form) {
        ctx.flags.push(FlagDef {
            long: long_form.clone(),
            short: short_form.clone(),
            help: help.to_string(),
        });
    }

    Arg {
        long: long_form,
        short: short_form,
    }
}

impl Arg {
    fn fetch_raw(&self) -> Option<String> {
        let ctx = get_ctx().lock().unwrap();
        ctx.args
            .iter()
            .skip_while(|x| *x != &self.long && *x != &self.short)
            .nth(1)
            .filter(|val| !val.starts_with('-'))
            .cloned()
    }

    pub fn required<T>(&self) -> T
    where
        T: FromStr,
        T::Err: Display,
    {
        check_help();

        let raw = self.fetch_raw().unwrap_or_else(|| {
            eprintln!(
                "Error: Missing required argument {} ({})",
                self.long, self.short
            );
            std::process::exit(1);
        });

        raw.parse::<T>().unwrap_or_else(|err| {
            eprintln!(
                "Error: Invalid value for {} ({}): {}",
                self.long, raw, err
            );
            std::process::exit(1);
        })
    }

    pub fn default<T>(&self, default_val: T) -> T
    where
        T: FromStr,
        T::Err: Display,
    {
        check_help();

        match self.fetch_raw() {
            None => default_val,
            Some(raw) => raw.parse::<T>().unwrap_or_else(|err| {
                eprintln!(
                    "Error: Invalid value for {} ({}): {}",
                    self.long, raw, err
                );
                std::process::exit(1);
            }),
        }
    }

    pub fn has(&self) -> bool {
        check_help();
        let ctx = get_ctx().lock().unwrap();
        ctx.args.iter().any(|x| x == &self.long || x == &self.short)
    }
}

pub fn check_help() {
    let ctx = get_ctx().lock().unwrap();

    // 检查用户是否传了 -h 或 --help
    if ctx.args.iter().any(|x| x == "--help" || x == "-h") {
        let exe_name = ctx
            .args
            .first()
            .and_then(|p| {
                std::path::Path::new(p)
                    .file_name()
                    .map(|n| n.to_string_lossy().into_owned())
            })
            .unwrap_or_else(|| "program".to_string());

        println!("Usage:\n  {} [OPTIONS]", exe_name);
        println!("\nOptions:");

        for arg in &ctx.flags {
            println!("  {}, {:<14} {}", arg.short, arg.long, arg.help);
        }
        println!("  -h, --help           display this help message");

        std::process::exit(0);
    }
}
