//! Command metadata and the `Command` trait.
//!
//! The derive macro generates implementations of `Command`, producing
//! static `CommandDef` descriptors for introspection and help text.

use crate::error::Error;
use crate::parse::{FlagDef, OnUnknown};
use crate::style::Style;

/// The trait implemented by `#[derive(Command)]` structs.
pub trait Command: Sized {
    /// Static metadata describing this command.
    fn def() -> &'static CommandDef;

    /// Parse from an argument slice.
    ///
    /// # Errors
    ///
    /// Returns a parse error if arguments don't match the definition.
    fn parse(args: &[&str]) -> Result<Self, Error>;
}

/// Static metadata for a command, generated by the derive macro.
#[derive(Debug, Clone)]
#[expect(clippy::exhaustive_structs, reason = "static config generated by derive")]
pub struct CommandDef {
    /// Command name (e.g., "cd").
    pub name: &'static str,
    /// Parsing style.
    pub style: Style,
    /// How unrecognized flags are handled.
    pub on_unknown: OnUnknown,
    /// Declared flags.
    pub flags: &'static [FlagDef],
    /// Declared positional arguments (in order).
    pub positionals: &'static [PositionalDef],
    /// Whether a rest-args (`Operands`) field exists.
    pub has_rest: bool,
}

/// Metadata for a positional argument slot.
#[derive(Debug, Clone)]
#[expect(clippy::exhaustive_structs, reason = "static config generated by derive")]
pub struct PositionalDef {
    /// Field name (used in error messages).
    pub name: &'static str,
    /// Whether the user must provide this argument.
    pub required: bool,
}

#[cfg(test)]
#[expect(clippy::unwrap_used, reason = "tests verify known structure")]
mod tests {
    use super::{CommandDef, PositionalDef};
    use crate::parse::OnUnknown;
    use crate::style::Style;

    #[test]
    fn command_def_is_const_constructible() {
        static DEF: CommandDef = CommandDef {
            name: "test",
            style: Style::Posix,
            on_unknown: OnUnknown::Reject,
            flags: &[],
            positionals: &[PositionalDef {
                name: "target",
                required: true,
            }],
            has_rest: false,
        };
        assert_eq!(DEF.name, "test");
        assert!(DEF.positionals.first().unwrap().required);
    }
}
