Skip to content
use crate::nanoargs::parser::ArgParser;
use crate::nanoargs::types::{
    ConflictDef, FlagDef, GroupDef, OptionDef, ParseError, PositionalDef, SubcommandDef,
};
use crate::nanoargs::validators::Validator;
use std::fmt::Write as _;

/// Standalone builder for defining a boolean flag argument.
///
/// Construct with [`Flag::new()`], chain modifiers like [`.short()`](Flag::short)
/// and [`.hidden()`](Flag::hidden), then pass to [`ArgBuilder::flag()`].
#[derive(Clone, Debug)]
pub struct Flag {
    long: String,
    short: Option<char>,
    description: String,
    hidden: bool,
}

impl Flag {
    /// Create a new flag with a long name. Description defaults to empty string.
    pub fn new(long: &str) -> Self {
        Self {
            long: long.to_string(),
            short: None,
            description: String::new(),
            hidden: false,
        }
    }

    /// Set the description shown in help text.
    pub fn desc(mut self, description: &str) -> Self {
        self.description = description.to_string();
        self
    }

    /// Set the optional single-character short form.
    pub fn short(mut self, ch: char) -> Self {
        self.short = Some(ch);
        self
    }

    /// Mark this flag as hidden (excluded from help text).
    pub fn hidden(mut self) -> Self {
        self.hidden = true;
        self
    }
}

impl From<Flag> for FlagDef {
    fn from(f: Flag) -> FlagDef {
        FlagDef {
            long: f.long,
            short: f.short,
            description: f.description,
            hidden: f.hidden,
        }
    }
}

/// Standalone builder for defining a key-value option argument.
///
/// Construct with [`Opt::new()`], chain modifiers like [`.short()`](Opt::short),
/// [`.required()`](Opt::required), [`.default()`](Opt::default),
/// [`.env()`](Opt::env), [`.multi()`](Opt::multi), and [`.hidden()`](Opt::hidden),
/// then pass to [`ArgBuilder::option()`].
#[derive(Clone, Debug)]
pub struct Opt {
    long: String,
    short: Option<char>,
    placeholder: String,
    description: String,
    required: bool,
    default: Option<String>,
    env_var: Option<String>,
    multi: bool,
    hidden: bool,
    validator: Option<Validator>,
}

impl Opt {
    /// Create a new option with only the long name.
    /// Placeholder defaults to the uppercased long name; description defaults to empty.
    pub fn new(long: &str) -> Self {
        let placeholder = long.to_uppercase();
        Self {
            long: long.to_string(),
            short: None,
            placeholder,
            description: String::new(),
            required: false,
            default: None,
            env_var: None,
            multi: false,
            hidden: false,
            validator: None,
        }
    }

    /// Set the placeholder shown in help text (e.g. "FILE").
    pub fn placeholder(mut self, placeholder: &str) -> Self {
        self.placeholder = placeholder.to_string();
        self
    }

    /// Set the description shown in help text.
    pub fn desc(mut self, description: &str) -> Self {
        self.description = description.to_string();
        self
    }

    /// Set the optional single-character short form.
    pub fn short(mut self, ch: char) -> Self {
        self.short = Some(ch);
        self
    }

    /// Mark this option as required.
    pub fn required(mut self) -> Self {
        self.required = true;
        self
    }

    /// Set a default value for this option.
    pub fn default(mut self, value: &str) -> Self {
        self.default = Some(value.to_string());
        self
    }

    /// Set an environment variable fallback for this option.
    pub fn env(mut self, var_name: &str) -> Self {
        self.env_var = Some(var_name.to_string());
        self
    }

    /// Mark this option as accepting multiple values.
    pub fn multi(mut self) -> Self {
        self.multi = true;
        self
    }

    /// Mark this option as hidden (excluded from help text).
    pub fn hidden(mut self) -> Self {
        self.hidden = true;
        self
    }

    /// Attach a validator to this option.
    pub fn validate(mut self, v: Validator) -> Self {
        self.validator = Some(v);
        self
    }
}

impl From<Opt> for OptionDef {
    fn from(o: Opt) -> OptionDef {
        OptionDef {
            long: o.long,
            short: o.short,
            placeholder: o.placeholder,
            description: o.description,
            required: o.required,
            default: o.default,
            env_var: o.env_var,
            multi: o.multi,
            hidden: o.hidden,
            validator: o.validator,
        }
    }
}

/// Standalone builder for defining a positional argument.
///
/// Construct with [`Pos::new()`], chain modifiers like [`.desc()`](Pos::desc),
/// [`.required()`](Pos::required), [`.default()`](Pos::default), and
/// [`.multi()`](Pos::multi), then pass to [`ArgBuilder::positional()`].
#[derive(Clone, Debug)]
pub struct Pos {
    name: String,
    description: String,
    required: bool,
    default: Option<String>,
    multi: bool,
    validator: Option<Validator>,
}

impl Pos {
    /// Create a new positional with a name and description.
    pub fn new(name: &str) -> Self {
        Self {
            name: name.to_string(),
            description: String::new(),
            required: false,
            default: None,
            multi: false,
            validator: None,
        }
    }

    /// Set the description shown in help text.
    pub fn desc(mut self, description: &str) -> Self {
        self.description = description.to_string();
        self
    }

    /// Mark this positional as required.
    pub fn required(mut self) -> Self {
        self.required = true;
        self
    }

    /// Set a default value for this positional argument.
    pub fn default(mut self, value: &str) -> Self {
        self.default = Some(value.to_string());
        self
    }

    /// Mark this positional as collecting all remaining arguments.
    pub fn multi(mut self) -> Self {
        self.multi = true;
        self
    }

    /// Attach a validator to this positional.
    pub fn validate(mut self, v: Validator) -> Self {
        self.validator = Some(v);
        self
    }
}

impl From<Pos> for PositionalDef {
    fn from(p: Pos) -> PositionalDef {
        PositionalDef {
            name: p.name,
            description: p.description,
            required: p.required,
            default: p.default,
            multi: p.multi,
            validator: p.validator,
        }
    }
}

/// Fluent builder for constructing an [`ArgParser`].
///
/// Chain calls to [`flag()`](ArgBuilder::flag), [`option()`](ArgBuilder::option),
/// [`positional()`](ArgBuilder::positional), and [`subcommand()`](ArgBuilder::subcommand)
/// to define the argument schema, then call [`build()`](ArgBuilder::build) to produce
/// the parser. Construct argument definitions using [`Flag`], [`Opt`], and [`Pos`]
/// and pass them directly to the builder methods.
#[must_use = "builder does nothing until .build() is called"]
#[derive(Clone, Debug)]
pub struct ArgBuilder {
    program_name: Option<String>,
    program_desc: Option<String>,
    version: Option<String>,
    flags: Vec<FlagDef>,
    options: Vec<OptionDef>,
    positionals: Vec<PositionalDef>,
    subcommands: Vec<SubcommandDef>,
    groups: Vec<(String, Vec<String>)>,
    conflicts: Vec<(String, Vec<String>)>,
}

impl ArgBuilder {
    /// Create a new builder with no arguments defined.
    pub fn new() -> Self {
        Self {
            program_name: None,
            program_desc: None,
            version: None,
            flags: Vec::new(),
            options: Vec::new(),
            positionals: Vec::new(),
            subcommands: Vec::new(),
            groups: Vec::new(),
            conflicts: Vec::new(),
        }
    }

    /// Set the program name shown in usage and version text.
    pub fn name(mut self, name: &str) -> Self {
        self.program_name = Some(name.to_string());
        self
    }

    /// Set the program description shown at the top of help text.
    pub fn description(mut self, desc: &str) -> Self {
        self.program_desc = Some(desc.to_string());
        self
    }

    /// Set the version string. Enables `--version` / `-V` flags.
    pub fn version(mut self, version: &str) -> Self {
        self.version = Some(version.to_string());
        self
    }

    /// Add a flag definition to the builder.
    pub fn flag(mut self, flag: Flag) -> Self {
        self.flags.push(FlagDef::from(flag));
        self
    }

    /// Add an option definition to the builder.
    pub fn option(mut self, opt: Opt) -> Self {
        self.options.push(OptionDef::from(opt));
        self
    }

    /// Add a positional argument definition to the builder.
    pub fn positional(mut self, pos: Pos) -> Self {
        self.positionals.push(PositionalDef::from(pos));
        self
    }

    /// Register a subcommand with a name, description, and its own pre-built `ArgParser`.
    pub fn subcommand(mut self, name: &str, desc: &str, parser: ArgParser) -> Self {
        if let Some(existing) = self.subcommands.iter_mut().find(|s| s.name == name) {
            existing.description = desc.to_string();
            existing.parser = parser;
        } else {
            self.subcommands.push(SubcommandDef {
                name: name.to_string(),
                description: desc.to_string(),
                parser,
            });
        }
        self
    }

    /// Declare an argument group: at least one of the named arguments must be provided.
    pub fn group(mut self, name: &str, members: &[&str]) -> Self {
        self.groups.push((
            name.to_string(),
            members
                .iter()
                .map(std::string::ToString::to_string)
                .collect(),
        ));
        self
    }

    /// Declare a conflict set: at most one of the named arguments may be provided.
    pub fn conflict(mut self, name: &str, members: &[&str]) -> Self {
        self.conflicts.push((
            name.to_string(),
            members
                .iter()
                .map(std::string::ToString::to_string)
                .collect(),
        ));
        self
    }

    /// Validate the schema and produce an [`ArgParser`].
    ///
    /// Returns [`ParseError::InvalidFormat`] if there are duplicate long names,
    /// duplicate short characters, or a `-V` conflict when a version is set.
    #[must_use = "returns the built ArgParser; did you forget to assign it?"]
    #[allow(clippy::too_many_lines)]
    pub fn build(self) -> Result<ArgParser, ParseError> {
        // Validate no duplicate long names across flags and options
        let mut seen_longs = std::collections::HashSet::new();
        for flag in &self.flags {
            if !seen_longs.insert(&flag.long) {
                return Err(ParseError::InvalidFormat(format!(
                    "duplicate long argument name: --{}",
                    flag.long
                )));
            }
        }
        for opt in &self.options {
            if !seen_longs.insert(&opt.long) {
                return Err(ParseError::InvalidFormat(format!(
                    "duplicate long argument name: --{}",
                    opt.long
                )));
            }
        }

        // Validate no duplicate short chars across flags and options
        let mut seen_shorts = std::collections::HashSet::new();
        for flag in &self.flags {
            if let Some(ch) = flag.short
                && !seen_shorts.insert(ch)
            {
                return Err(ParseError::InvalidFormat(format!(
                    "duplicate short argument: -{ch}"
                )));
            }
        }
        for opt in &self.options {
            if let Some(ch) = opt.short
                && !seen_shorts.insert(ch)
            {
                return Err(ParseError::InvalidFormat(format!(
                    "duplicate short argument: -{ch}"
                )));
            }
        }

        // Validate no -V conflict when version is configured
        if self.version.is_some() {
            for flag in &self.flags {
                if flag.short == Some('V') {
                    return Err(ParseError::InvalidFormat(
                        "duplicate short argument: -V (reserved for --version)".to_string(),
                    ));
                }
            }
            for opt in &self.options {
                if opt.short == Some('V') {
                    return Err(ParseError::InvalidFormat(
                        "duplicate short argument: -V (reserved for --version)".to_string(),
                    ));
                }
            }
        }

        // Validate positional configurations
        for pos in &self.positionals {
            if pos.required && pos.default.is_some() {
                return Err(ParseError::InvalidFormat(format!(
                    "positional '{}' cannot be both required and have a default",
                    pos.name
                )));
            }
            if pos.required && pos.multi {
                return Err(ParseError::InvalidFormat(format!(
                    "positional '{}' cannot be both required and multi",
                    pos.name
                )));
            }
        }
        if let Some(pos) = self
            .positionals
            .iter()
            .enumerate()
            .find(|(i, p)| p.multi && *i < self.positionals.len() - 1)
            .map(|(_, p)| p)
        {
            return Err(ParseError::InvalidFormat(format!(
                "multi positional '{}' must be the last positional",
                pos.name
            )));
        }

        // Validate groups and conflicts
        let mut validated_groups = Vec::new();
        for (name, members) in &self.groups {
            if members.len() < 2 {
                return Err(ParseError::InvalidFormat(format!(
                    "group '{name}' requires at least two members"
                )));
            }
            for member in members {
                if !self.flags.iter().any(|f| f.long == *member)
                    && !self.options.iter().any(|o| o.long == *member)
                {
                    return Err(ParseError::InvalidFormat(format!(
                        "group '{name}' references unknown argument: --{member}"
                    )));
                }
            }
            validated_groups.push(GroupDef {
                name: name.clone(),
                members: members.clone(),
            });
        }

        let mut validated_conflicts = Vec::new();
        for (name, members) in &self.conflicts {
            if members.len() < 2 {
                return Err(ParseError::InvalidFormat(format!(
                    "conflict '{name}' requires at least two members"
                )));
            }
            for member in members {
                if !self.flags.iter().any(|f| f.long == *member)
                    && !self.options.iter().any(|o| o.long == *member)
                {
                    return Err(ParseError::InvalidFormat(format!(
                        "conflict '{name}' references unknown argument: --{member}"
                    )));
                }
            }
            validated_conflicts.push(ConflictDef {
                name: name.clone(),
                members: members.clone(),
            });
        }

        Ok(ArgParser {
            program_name: self.program_name,
            program_desc: self.program_desc,
            version: self.version,
            flags: self.flags,
            options: self.options,
            positionals: self.positionals,
            subcommands: self.subcommands,
            groups: validated_groups,
            conflicts: validated_conflicts,
        })
    }
}

impl Default for ArgBuilder {
    fn default() -> Self {
        Self::new()
    }
}