//! Builder patterns for ergonomic spec construction
//!
//! These builders allow constructing specs without manual Vec allocation,
//! using variadic-friendly methods.
//!
//! # Examples
//!
//! ```
//! use usage::{SpecFlagBuilder, SpecArgBuilder, SpecCommandBuilder};
//!
//! let flag = SpecFlagBuilder::new()
//!     .name("verbose")
//!     .short('v')
//!     .long("verbose")
//!     .help("Enable verbose output")
//!     .build();
//!
//! let arg = SpecArgBuilder::new()
//!     .name("files")
//!     .var(true)
//!     .var_min(1)
//!     .help("Input files")
//!     .build();
//!
//! let cmd = SpecCommandBuilder::new()
//!     .name("install")
//!     .aliases(["i", "add"])
//!     .flag(flag)
//!     .arg(arg)
//!     .build();
//! ```

use crate::spec::cmd::SpecExample;
use crate::{spec::arg::SpecDoubleDashChoices, SpecArg, SpecChoices, SpecCommand, SpecFlag};

/// Builder for SpecFlag
#[derive(Debug, Default, Clone)]
pub struct SpecFlagBuilder {
    inner: SpecFlag,
    allow_hyphen_values: bool,
}

impl SpecFlagBuilder {
    /// Create a new SpecFlagBuilder
    pub fn new() -> Self {
        Self::default()
    }

    /// Set the flag name
    pub fn name(mut self, name: impl Into<String>) -> Self {
        self.inner.name = name.into();
        self
    }

    /// Add a short flag character (can be called multiple times)
    pub fn short(mut self, c: char) -> Self {
        self.inner.short.push(c);
        self
    }

    /// Add multiple short flags at once
    pub fn shorts(mut self, chars: impl IntoIterator<Item = char>) -> Self {
        self.inner.short.extend(chars);
        self
    }

    /// Add a long flag name (can be called multiple times)
    pub fn long(mut self, name: impl Into<String>) -> Self {
        self.inner.long.push(name.into());
        self
    }

    /// Add multiple long flags at once
    pub fn longs<I, S>(mut self, names: I) -> Self
    where
        I: IntoIterator<Item = S>,
        S: Into<String>,
    {
        self.inner.long.extend(names.into_iter().map(Into::into));
        self
    }

    /// Add a default value (can be called multiple times for var flags)
    pub fn default_value(mut self, value: impl Into<String>) -> Self {
        self.inner.default.push(value.into());
        self.inner.required = false;
        self
    }

    /// Add multiple default values at once
    pub fn default_values<I, S>(mut self, values: I) -> Self
    where
        I: IntoIterator<Item = S>,
        S: Into<String>,
    {
        self.inner
            .default
            .extend(values.into_iter().map(Into::into));
        if !self.inner.default.is_empty() {
            self.inner.required = false;
        }
        self
    }

    /// Set help text
    pub fn help(mut self, text: impl Into<String>) -> Self {
        self.inner.help = Some(text.into());
        self
    }

    /// Set long help text
    pub fn help_long(mut self, text: impl Into<String>) -> Self {
        self.inner.help_long = Some(text.into());
        self
    }

    /// Set markdown help text
    pub fn help_md(mut self, text: impl Into<String>) -> Self {
        self.inner.help_md = Some(text.into());
        self
    }

    /// Set as variadic (can be specified multiple times)
    pub fn var(mut self, is_var: bool) -> Self {
        self.inner.var = is_var;
        self
    }

    /// Set minimum count for variadic flag
    pub fn var_min(mut self, min: usize) -> Self {
        self.inner.var_min = Some(min);
        self
    }

    /// Set maximum count for variadic flag
    pub fn var_max(mut self, max: usize) -> Self {
        self.inner.var_max = Some(max);
        self
    }

    /// Set as required
    pub fn required(mut self, is_required: bool) -> Self {
        self.inner.required = is_required;
        self
    }

    /// Set as global (available to subcommands)
    pub fn global(mut self, is_global: bool) -> Self {
        self.inner.global = is_global;
        self
    }

    /// Set as hidden
    pub fn hide(mut self, is_hidden: bool) -> Self {
        self.inner.hide = is_hidden;
        self
    }

    /// Set as count flag
    pub fn count(mut self, is_count: bool) -> Self {
        self.inner.count = is_count;
        self
    }

    /// Allow this flag's value to start with `-`
    pub fn allow_hyphen_values(mut self, allow: bool) -> Self {
        self.allow_hyphen_values = allow;
        if let Some(arg) = &mut self.inner.arg {
            arg.double_dash = if allow {
                crate::spec::arg::SpecDoubleDashChoices::Automatic
            } else {
                crate::spec::arg::SpecDoubleDashChoices::Optional
            };
        }
        self
    }

    /// Set the argument spec for flags that take values
    pub fn arg(mut self, arg: SpecArg) -> Self {
        self.inner.arg = Some(arg);
        if self.allow_hyphen_values {
            if let Some(arg) = &mut self.inner.arg {
                arg.double_dash = crate::spec::arg::SpecDoubleDashChoices::Automatic;
            }
        }
        self
    }

    /// Set negate string
    pub fn negate(mut self, negate: impl Into<String>) -> Self {
        self.inner.negate = Some(negate.into());
        self
    }

    /// Set environment variable name
    pub fn env(mut self, env: impl Into<String>) -> Self {
        self.inner.env = Some(env.into());
        self
    }

    /// Set deprecated message
    pub fn deprecated(mut self, msg: impl Into<String>) -> Self {
        self.inner.deprecated = Some(msg.into());
        self
    }

    /// Build the final SpecFlag
    #[must_use]
    pub fn build(mut self) -> SpecFlag {
        if self.allow_hyphen_values {
            if let Some(arg) = &mut self.inner.arg {
                arg.double_dash = crate::spec::arg::SpecDoubleDashChoices::Automatic;
            }
        }
        self.inner.usage = self.inner.usage();
        if self.inner.name.is_empty() {
            // Derive name from long or short flags
            if let Some(long) = self.inner.long.first() {
                self.inner.name = long.clone();
            } else if let Some(short) = self.inner.short.first() {
                self.inner.name = short.to_string();
            }
        }
        self.inner
    }
}

/// Builder for SpecArg
#[derive(Debug, Default, Clone)]
pub struct SpecArgBuilder {
    inner: SpecArg,
}

impl SpecArgBuilder {
    /// Create a new SpecArgBuilder
    pub fn new() -> Self {
        Self::default()
    }

    /// Set the argument name
    pub fn name(mut self, name: impl Into<String>) -> Self {
        self.inner.name = name.into();
        self
    }

    /// Add a default value (can be called multiple times for var args)
    pub fn default_value(mut self, value: impl Into<String>) -> Self {
        self.inner.default.push(value.into());
        self.inner.required = false;
        self
    }

    /// Add multiple default values at once
    pub fn default_values<I, S>(mut self, values: I) -> Self
    where
        I: IntoIterator<Item = S>,
        S: Into<String>,
    {
        self.inner
            .default
            .extend(values.into_iter().map(Into::into));
        if !self.inner.default.is_empty() {
            self.inner.required = false;
        }
        self
    }

    /// Set help text
    pub fn help(mut self, text: impl Into<String>) -> Self {
        self.inner.help = Some(text.into());
        self
    }

    /// Set long help text
    pub fn help_long(mut self, text: impl Into<String>) -> Self {
        self.inner.help_long = Some(text.into());
        self
    }

    /// Set markdown help text
    pub fn help_md(mut self, text: impl Into<String>) -> Self {
        self.inner.help_md = Some(text.into());
        self
    }

    /// Set as variadic (accepts multiple values)
    pub fn var(mut self, is_var: bool) -> Self {
        self.inner.var = is_var;
        self
    }

    /// Set minimum count for variadic argument
    pub fn var_min(mut self, min: usize) -> Self {
        self.inner.var_min = Some(min);
        self
    }

    /// Set maximum count for variadic argument
    pub fn var_max(mut self, max: usize) -> Self {
        self.inner.var_max = Some(max);
        self
    }

    /// Set as required
    pub fn required(mut self, is_required: bool) -> Self {
        self.inner.required = is_required;
        self
    }

    /// Set as hidden
    pub fn hide(mut self, is_hidden: bool) -> Self {
        self.inner.hide = is_hidden;
        self
    }

    /// Set environment variable name
    pub fn env(mut self, env: impl Into<String>) -> Self {
        self.inner.env = Some(env.into());
        self
    }

    /// Set the double-dash behavior
    pub fn double_dash(mut self, behavior: SpecDoubleDashChoices) -> Self {
        self.inner.double_dash = behavior;
        self
    }

    /// Set choices for this argument
    pub fn choices<I, S>(mut self, choices: I) -> Self
    where
        I: IntoIterator<Item = S>,
        S: Into<String>,
    {
        let spec_choices = self.inner.choices.get_or_insert_with(SpecChoices::default);
        #[cfg(feature = "unstable_choices_env")]
        let env = spec_choices.env().map(ToString::to_string);
        spec_choices.choices = choices.into_iter().map(Into::into).collect();
        #[cfg(feature = "unstable_choices_env")]
        spec_choices.set_env(env);
        self
    }

    /// Set choices from an environment variable
    #[cfg(feature = "unstable_choices_env")]
    pub fn choices_env(mut self, env: impl Into<String>) -> Self {
        let choices = self.inner.choices.get_or_insert_with(SpecChoices::default);
        choices.set_env(Some(env.into()));
        self
    }

    /// Build the final SpecArg
    #[must_use]
    pub fn build(mut self) -> SpecArg {
        self.inner.usage = self.inner.usage();
        self.inner
    }
}

/// Builder for SpecCommand
#[derive(Debug, Default, Clone)]
pub struct SpecCommandBuilder {
    inner: SpecCommand,
}

impl SpecCommandBuilder {
    /// Create a new SpecCommandBuilder
    pub fn new() -> Self {
        Self::default()
    }

    /// Set the command name
    pub fn name(mut self, name: impl Into<String>) -> Self {
        self.inner.name = name.into();
        self
    }

    /// Add an alias (can be called multiple times)
    pub fn alias(mut self, alias: impl Into<String>) -> Self {
        self.inner.aliases.push(alias.into());
        self
    }

    /// Add multiple aliases at once
    pub fn aliases<I, S>(mut self, aliases: I) -> Self
    where
        I: IntoIterator<Item = S>,
        S: Into<String>,
    {
        self.inner
            .aliases
            .extend(aliases.into_iter().map(Into::into));
        self
    }

    /// Add a hidden alias (can be called multiple times)
    pub fn hidden_alias(mut self, alias: impl Into<String>) -> Self {
        self.inner.hidden_aliases.push(alias.into());
        self
    }

    /// Add multiple hidden aliases at once
    pub fn hidden_aliases<I, S>(mut self, aliases: I) -> Self
    where
        I: IntoIterator<Item = S>,
        S: Into<String>,
    {
        self.inner
            .hidden_aliases
            .extend(aliases.into_iter().map(Into::into));
        self
    }

    /// Add a flag to the command
    pub fn flag(mut self, flag: SpecFlag) -> Self {
        self.inner.flags.push(flag);
        self
    }

    /// Add multiple flags at once
    pub fn flags(mut self, flags: impl IntoIterator<Item = SpecFlag>) -> Self {
        self.inner.flags.extend(flags);
        self
    }

    /// Add an argument to the command
    pub fn arg(mut self, arg: SpecArg) -> Self {
        self.inner.args.push(arg);
        self
    }

    /// Add multiple arguments at once
    pub fn args(mut self, args: impl IntoIterator<Item = SpecArg>) -> Self {
        self.inner.args.extend(args);
        self
    }

    /// Set help text
    pub fn help(mut self, text: impl Into<String>) -> Self {
        self.inner.help = Some(text.into());
        self
    }

    /// Set long help text
    pub fn help_long(mut self, text: impl Into<String>) -> Self {
        self.inner.help_long = Some(text.into());
        self
    }

    /// Set markdown help text
    pub fn help_md(mut self, text: impl Into<String>) -> Self {
        self.inner.help_md = Some(text.into());
        self
    }

    /// Set as hidden
    pub fn hide(mut self, is_hidden: bool) -> Self {
        self.inner.hide = is_hidden;
        self
    }

    /// Set subcommand required
    pub fn subcommand_required(mut self, required: bool) -> Self {
        self.inner.subcommand_required = required;
        self
    }

    /// Set deprecated message
    pub fn deprecated(mut self, msg: impl Into<String>) -> Self {
        self.inner.deprecated = Some(msg.into());
        self
    }

    /// Set restart token for resetting argument parsing
    /// e.g., `mise run lint ::: test ::: check` with restart_token=":::"
    pub fn restart_token(mut self, token: impl Into<String>) -> Self {
        self.inner.restart_token = Some(token.into());
        self
    }

    /// Add a subcommand (can be called multiple times)
    pub fn subcommand(mut self, cmd: SpecCommand) -> Self {
        self.inner.subcommands.insert(cmd.name.clone(), cmd);
        self
    }

    /// Add multiple subcommands at once
    pub fn subcommands(mut self, cmds: impl IntoIterator<Item = SpecCommand>) -> Self {
        for cmd in cmds {
            self.inner.subcommands.insert(cmd.name.clone(), cmd);
        }
        self
    }

    /// Set before_help text (displayed before the help message)
    pub fn before_help(mut self, text: impl Into<String>) -> Self {
        self.inner.before_help = Some(text.into());
        self
    }

    /// Set before_help_long text
    pub fn before_help_long(mut self, text: impl Into<String>) -> Self {
        self.inner.before_help_long = Some(text.into());
        self
    }

    /// Set before_help markdown text
    pub fn before_help_md(mut self, text: impl Into<String>) -> Self {
        self.inner.before_help_md = Some(text.into());
        self
    }

    /// Set after_help text (displayed after the help message)
    pub fn after_help(mut self, text: impl Into<String>) -> Self {
        self.inner.after_help = Some(text.into());
        self
    }

    /// Set after_help_long text
    pub fn after_help_long(mut self, text: impl Into<String>) -> Self {
        self.inner.after_help_long = Some(text.into());
        self
    }

    /// Set after_help markdown text
    pub fn after_help_md(mut self, text: impl Into<String>) -> Self {
        self.inner.after_help_md = Some(text.into());
        self
    }

    /// Add an example (can be called multiple times)
    pub fn example(mut self, code: impl Into<String>) -> Self {
        self.inner.examples.push(SpecExample::new(code.into()));
        self
    }

    /// Add an example with header and help text
    pub fn example_with_help(
        mut self,
        code: impl Into<String>,
        header: impl Into<String>,
        help: impl Into<String>,
    ) -> Self {
        let mut example = SpecExample::new(code.into());
        example.header = Some(header.into());
        example.help = Some(help.into());
        self.inner.examples.push(example);
        self
    }

    /// Build the final SpecCommand
    #[must_use]
    pub fn build(mut self) -> SpecCommand {
        self.inner.usage = self.inner.usage();
        self.inner
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_flag_builder_basic() {
        let flag = SpecFlagBuilder::new()
            .name("verbose")
            .short('v')
            .long("verbose")
            .help("Enable verbose output")
            .build();

        assert_eq!(flag.name, "verbose");
        assert_eq!(flag.short, vec!['v']);
        assert_eq!(flag.long, vec!["verbose".to_string()]);
        assert_eq!(flag.help, Some("Enable verbose output".to_string()));
    }

    #[test]
    fn test_flag_builder_multiple_values() {
        let flag = SpecFlagBuilder::new()
            .shorts(['v', 'V'])
            .longs(["verbose", "loud"])
            .default_values(["info", "warn"])
            .build();

        assert_eq!(flag.short, vec!['v', 'V']);
        assert_eq!(flag.long, vec!["verbose".to_string(), "loud".to_string()]);
        assert_eq!(flag.default, vec!["info".to_string(), "warn".to_string()]);
        assert!(!flag.required); // Should be false due to defaults
    }

    #[test]
    fn test_flag_builder_variadic() {
        let flag = SpecFlagBuilder::new()
            .long("file")
            .var(true)
            .var_min(1)
            .var_max(10)
            .build();

        assert!(flag.var);
        assert_eq!(flag.var_min, Some(1));
        assert_eq!(flag.var_max, Some(10));
    }

    #[test]
    fn test_flag_builder_name_derivation() {
        let flag = SpecFlagBuilder::new().short('v').long("verbose").build();

        // Name should be derived from long flag
        assert_eq!(flag.name, "verbose");

        let flag2 = SpecFlagBuilder::new().short('v').build();

        // Name should be derived from short flag if no long
        assert_eq!(flag2.name, "v");
    }

    #[test]
    fn test_arg_builder_basic() {
        let arg = SpecArgBuilder::new()
            .name("file")
            .help("Input file")
            .required(true)
            .build();

        assert_eq!(arg.name, "file");
        assert_eq!(arg.help, Some("Input file".to_string()));
        assert!(arg.required);
    }

    #[test]
    fn test_arg_builder_variadic() {
        let arg = SpecArgBuilder::new()
            .name("files")
            .var(true)
            .var_min(1)
            .var_max(10)
            .help("Input files")
            .build();

        assert_eq!(arg.name, "files");
        assert!(arg.var);
        assert_eq!(arg.var_min, Some(1));
        assert_eq!(arg.var_max, Some(10));
    }

    #[test]
    fn test_arg_builder_defaults() {
        let arg = SpecArgBuilder::new()
            .name("file")
            .default_values(["a.txt", "b.txt"])
            .build();

        assert_eq!(arg.default, vec!["a.txt".to_string(), "b.txt".to_string()]);
        assert!(!arg.required);
    }

    #[test]
    fn test_command_builder_basic() {
        let cmd = SpecCommandBuilder::new()
            .name("install")
            .help("Install packages")
            .build();

        assert_eq!(cmd.name, "install");
        assert_eq!(cmd.help, Some("Install packages".to_string()));
    }

    #[test]
    fn test_command_builder_aliases() {
        let cmd = SpecCommandBuilder::new()
            .name("install")
            .alias("i")
            .aliases(["add", "get"])
            .hidden_aliases(["inst"])
            .build();

        assert_eq!(
            cmd.aliases,
            vec!["i".to_string(), "add".to_string(), "get".to_string()]
        );
        assert_eq!(cmd.hidden_aliases, vec!["inst".to_string()]);
    }

    #[test]
    fn test_command_builder_with_flags_and_args() {
        let flag = SpecFlagBuilder::new().short('f').long("force").build();

        let arg = SpecArgBuilder::new().name("package").required(true).build();

        let cmd = SpecCommandBuilder::new()
            .name("install")
            .flag(flag)
            .arg(arg)
            .build();

        assert_eq!(cmd.flags.len(), 1);
        assert_eq!(cmd.flags[0].name, "force");
        assert_eq!(cmd.args.len(), 1);
        assert_eq!(cmd.args[0].name, "package");
    }

    #[test]
    fn test_arg_builder_choices() {
        let arg = SpecArgBuilder::new()
            .name("format")
            .choices(["json", "yaml", "toml"])
            .build();

        assert!(arg.choices.is_some());
        let choices = arg.choices.unwrap();
        assert_eq!(
            choices.choices,
            vec!["json".to_string(), "yaml".to_string(), "toml".to_string()]
        );
        assert_eq!(choices.env(), None);
    }

    #[cfg(feature = "unstable_choices_env")]
    #[test]
    fn test_arg_builder_choices_env() {
        let arg = SpecArgBuilder::new()
            .name("env")
            .choices(["local"])
            .choices_env("DEPLOY_ENVS")
            .build();

        let choices = arg.choices.unwrap();
        assert_eq!(choices.choices, vec!["local".to_string()]);
        assert_eq!(choices.env(), Some("DEPLOY_ENVS"));
    }

    #[cfg(feature = "unstable_choices_env")]
    #[test]
    fn test_arg_builder_choices_preserves_choices_env() {
        let arg = SpecArgBuilder::new()
            .name("env")
            .choices_env("DEPLOY_ENVS")
            .choices(["local"])
            .build();

        let choices = arg.choices.unwrap();
        assert_eq!(choices.choices, vec!["local".to_string()]);
        assert_eq!(choices.env(), Some("DEPLOY_ENVS"));
    }

    #[test]
    fn test_command_builder_subcommands() {
        let sub1 = SpecCommandBuilder::new().name("sub1").build();
        let sub2 = SpecCommandBuilder::new().name("sub2").build();

        let cmd = SpecCommandBuilder::new()
            .name("main")
            .subcommand(sub1)
            .subcommand(sub2)
            .build();

        assert_eq!(cmd.subcommands.len(), 2);
        assert!(cmd.subcommands.contains_key("sub1"));
        assert!(cmd.subcommands.contains_key("sub2"));
    }

    #[test]
    fn test_command_builder_before_after_help() {
        let cmd = SpecCommandBuilder::new()
            .name("test")
            .before_help("Before help text")
            .before_help_long("Before help long text")
            .after_help("After help text")
            .after_help_long("After help long text")
            .build();

        assert_eq!(cmd.before_help, Some("Before help text".to_string()));
        assert_eq!(
            cmd.before_help_long,
            Some("Before help long text".to_string())
        );
        assert_eq!(cmd.after_help, Some("After help text".to_string()));
        assert_eq!(
            cmd.after_help_long,
            Some("After help long text".to_string())
        );
    }

    #[test]
    fn test_command_builder_examples() {
        let cmd = SpecCommandBuilder::new()
            .name("test")
            .example("mycli run")
            .example_with_help("mycli build", "Build example", "Build the project")
            .build();

        assert_eq!(cmd.examples.len(), 2);
        assert_eq!(cmd.examples[0].code, "mycli run");
        assert_eq!(cmd.examples[1].code, "mycli build");
        assert_eq!(cmd.examples[1].header, Some("Build example".to_string()));
        assert_eq!(cmd.examples[1].help, Some("Build the project".to_string()));
    }
}
