use std::collections::HashSet;
use std::env;
use std::ops::Deref;
use std::sync::{Arc, Mutex, OnceLock, RwLock};

struct FlagDef {
    long: String,
    short: Option<String>,
    help: String,
    default_str: String,
    is_bool: bool,
    is_bool_switch: bool,
    is_required: bool,
    show_tooltip: bool,
    type_hint: String,
    has_value: Arc<RwLock<bool>>,
    parser_fn: Box<dyn Fn(&[String]) + Send + Sync>,
}

struct GlobalRegistry {
    flags: Vec<FlagDef>,
    seen_names: HashSet<String>,
    parsed: bool,
}

static REGISTRY: OnceLock<Mutex<GlobalRegistry>> = OnceLock::new();

fn get_registry() -> &'static Mutex<GlobalRegistry> {
    REGISTRY.get_or_init(|| {
        Mutex::new(GlobalRegistry {
            flags: Vec::new(),
            seen_names: HashSet::new(),
            parsed: false,
        })
    })
}

pub struct Var<T>(Arc<RwLock<T>>);

impl<T: Clone> Var<T> {
    pub fn get(&self) -> T {
        self.0.read().unwrap().clone()
    }
}

impl<T> Deref for Var<T> {
    type Target = RwLock<T>;
    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl<T: std::fmt::Debug> std::fmt::Debug for Var<T> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self.0.read() {
            Ok(val) => std::fmt::Debug::fmt(&*val, f),
            Err(_) => f.write_str("<locked>"),
        }
    }
}

impl<T: std::fmt::Display> std::fmt::Display for Var<T> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self.0.read() {
            Ok(val) => std::fmt::Display::fmt(&*val, f),
            Err(_) => f.write_str("<locked>"),
        }
    }
}

pub struct FlagBuilder {
    long: String,
    short: Option<char>,
    help: String,
    show_tooltip: bool,
}

pub fn flag(long: &str, help: &str) -> FlagBuilder {
    FlagBuilder {
        long: long.to_string(),
        short: None,
        help: help.to_string(),
        show_tooltip: false,
    }
}

#[doc(hidden)]
pub trait IntoVar<T> {
    fn register(
        self,
        builder: FlagBuilder,
        is_required: bool,
        has_value_flag: Arc<RwLock<bool>>,
    ) -> Var<T>;
}

fn register_core<T, F>(
    default_val: T,
    builder: FlagBuilder,
    is_required: bool,
    has_value_flag: Arc<RwLock<bool>>,
    type_hint: String,
    parser: F,
) -> Var<T>
where
    T: std::fmt::Debug + Clone + Send + Sync + 'static,
    F: Fn(&[String]) -> Option<T> + Send + Sync + 'static,
{
    let long_form = format!("--{}", builder.long.replace('_', "-"));
    let short_form = builder.short.map(|s| format!("-{}", s));

    let mut reg = get_registry().lock().unwrap();
    if reg.parsed {
        panic!("Cannot register new flags after parse() has been called!");
    }
    if !reg.seen_names.insert(long_form.clone())
        || short_form
            .as_ref()
            .map_or(false, |s| !reg.seen_names.insert(s.clone()))
    {
        panic!("Conflict detected for flag: {}", long_form);
    }

    let any_def = &default_val as &dyn std::any::Any;
    let is_bool = any_def.is::<bool>();
    let is_bool_switch = any_def.downcast_ref::<bool>().map_or(false, |b| !*b);

    let default_str = if is_required {
        String::new()
    } else {
        let s = format!("{:?}", default_val);
        s.trim_matches('"').to_string()
    };

    let cell = Arc::new(RwLock::new(default_val));
    let cell_clone = Arc::clone(&cell);
    let has_val_clone = Arc::clone(&has_value_flag);

    reg.flags.push(FlagDef {
        long: long_form,
        short: short_form,
        help: builder.help,
        default_str,
        is_bool,
        is_bool_switch,
        is_required,
        show_tooltip: builder.show_tooltip,
        type_hint,
        has_value: has_value_flag,
        parser_fn: Box::new(move |raw_vals| {
            if let Some(parsed) = parser(raw_vals) {
                *cell_clone.write().unwrap() = parsed;
                *has_val_clone.write().unwrap() = true;
            }
        }),
    });

    Var(cell)
}

macro_rules! impl_into_var_for_all {
    ($($t:ty),*) => {
        $(
            impl IntoVar<$t> for $t {
                fn register(self, builder: FlagBuilder, is_required: bool, has_value_flag: Arc<RwLock<bool>>) -> Var<$t> {
                    let full_name = std::any::type_name::<$t>();
                    let clean_name = full_name.split("::").last().unwrap_or(full_name).to_string();


                    register_core(self, builder, is_required, has_value_flag, clean_name, |raw_vals| {
                        raw_vals.last()?.parse::<$t>().ok()
                    })
                }
            }

            impl IntoVar<Vec<$t>> for Vec<$t> {
                fn register(self, builder: FlagBuilder, is_required: bool, has_value_flag: Arc<RwLock<bool>>) -> Var<Vec<$t>> {
                    let full_name = std::any::type_name::<$t>();
                    let clean_name = full_name.split("::").last().unwrap_or(full_name);
                    let array_hint = format!("[{}]", clean_name);


                    register_core(self, builder, is_required, has_value_flag, array_hint, |raw_vals| {
                        let mut parsed_vec = Vec::new();
                        for raw in raw_vals {
                            let is_string = std::any::type_name::<$t>().ends_with("String");
                            for part in raw.split(',') {
                                let processed = if is_string { part } else { part.trim() };
                                if let Ok(parsed) = processed.parse::<$t>() {
                                    parsed_vec.push(parsed);
                                }
                            }
                        }
                        if parsed_vec.is_empty() { None } else { Some(parsed_vec) }
                    })
                }
            }
        )*
    };
}

impl_into_var_for_all!(
    i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize, f32, f64,
    bool, String
);

impl IntoVar<String> for &'static str {
    fn register(
        self,
        builder: FlagBuilder,
        is_required: bool,
        has_value_flag: Arc<RwLock<bool>>,
    ) -> Var<String> {
        self.to_string()
            .register(builder, is_required, has_value_flag)
    }
}

impl FlagBuilder {
    pub fn short(mut self, s: char) -> Self {
        self.short = Some(s);
        self
    }

    pub fn tooltip(mut self) -> Self {
        self.show_tooltip = true;
        self
    }

    pub fn default<T, V>(self, default_val: V) -> Var<T>
    where
        V: IntoVar<T>,
    {
        default_val.register(self, false, Arc::new(RwLock::new(true)))
    }

    pub fn required<T>(self) -> Var<T>
    where
        T: std::fmt::Debug + Clone + Send + Sync + Default + 'static,
        T: IntoVar<T>,
    {
        T::default().register(self, true, Arc::new(RwLock::new(false)))
    }
}

pub fn parse() {
    parse_with_usage("");
}

pub fn parse_with_usage(description: &str) {
    let mut reg = get_registry().lock().unwrap();
    if reg.parsed {
        return;
    }
    reg.parsed = true;

    let args: Vec<String> = env::args().collect();

    if args.iter().any(|x| x == "-h" || x == "--help") {
        let exe_name = 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());

        if description.is_empty() {
            println!("Usage:\n  {} [OPTIONS]", exe_name);
        } else {
            println!("Usage:\n  {} [OPTIONS] - {}", exe_name, description);
        }

        println!("\nOptions:");
        for f in &reg.flags {
            let type_label = if f.show_tooltip && !f.is_bool {
                if f.type_hint.starts_with('[') {
                    format!(" {}", f.type_hint)
                } else {
                    format!(" [{}]", f.type_hint)
                }
            } else {
                "".to_string()
            };

            let has_visible_default = !f.default_str.is_empty()
                && f.default_str != "[]"
                && f.default_str != "[\"\"]"
                && !f.is_bool_switch;

            let mut full_desc = f.help.clone();

            if f.is_required {
                full_desc = format!("{} *required{}", full_desc, type_label);
            } else if has_visible_default {
                full_desc = format!(
                    "{} default: {}{}",
                    full_desc, f.default_str, type_label
                );
            } else {
                full_desc = format!("{}{}", full_desc, type_label);
            }

            if let Some(ref s) = f.short {
                println!(
                    "  {}, --{:<14} {}",
                    s,
                    f.long.trim_start_matches('-'),
                    full_desc
                );
            } else {
                println!(
                    "      --{:<14} {}",
                    f.long.trim_start_matches('-'),
                    full_desc
                );
            }
        }
        println!("  -h, --help");
        std::process::exit(0);
    }

    for f in &reg.flags {
        let mut found_vals: Vec<String> = Vec::new();
        let prefix = format!("{}=", f.long);

        for arg in &args {
            if arg.starts_with(&prefix) {
                found_vals.push(arg.split_at(prefix.len()).1.to_string());
            }
        }

        let mut idx = 0;
        while idx < args.len() {
            let arg = &args[idx];
            if arg == &f.long || f.short.as_ref().map_or(false, |s| arg == s) {
                if let Some(next_arg) = args.get(idx + 1) {
                    if next_arg.starts_with('-') {
                        if f.is_bool {
                            found_vals.push("true".to_string());
                        }
                    } else {
                        found_vals.push(next_arg.clone());
                        idx += 1;
                    }
                } else if f.is_bool {
                    found_vals.push("true".to_string());
                }
            }
            idx += 1;
        }

        if !found_vals.is_empty() {
            (f.parser_fn)(&found_vals);
        }
    }

    let mut missing_flags = Vec::new();
    for f in &reg.flags {
        if f.is_required && !*f.has_value.read().unwrap() {
            missing_flags.push(f.long.clone());
        }
    }

    if !missing_flags.is_empty() {
        eprintln!("error: the following required arguments were not provided:");
        for flag in missing_flags {
            eprintln!("  {}", flag);
        }
        std::process::exit(1);
    }
}
