use crate::{
    app::{
        states::{
            PerkDecks,
            skirmish_states::{GameMode, ZoomLevel},
        },
        threads::Soundtrack,
        view::View,
    },
    logs::init_logger,
};
use clap::{Error, Parser, error::ErrorKind, value_parser};
use std::num::ParseFloatError;

/// Holds all configurable options that can be passed via the command line.
///
/// Each field corresponds to a CLI flag (e.g. `--view`, `--username`, …).
/// The `clap` attributes describe the flag name, help text, default value,
/// and any validation constraints.  The struct derives `Parser` so that
/// `Cli::parse()` can be called directly to obtain a populated instance.
#[derive(Parser, Debug, Clone)]
#[command(version, about = "War in Tunnels", long_about = "War in Tunnels")]
pub struct Cli {
    /// The initial view/window to display.
    #[arg(
        long,
        help = "Default window",
        value_name = "...",
        default_value_t = View::MainMenu,
        value_enum
    )]
    pub view: View,

    /// Player name shown in the UI.
    #[arg(
        long,
        help = "Username",
        value_name = "String",
        default_value = "Player"
    )]
    pub username: String,

    /// Game mode selection.
    #[arg(
        long,
        help = "Game mode",
        value_name = "...",
        default_value_t = GameMode::LastManStanding,
        value_enum
    )]
    pub game_mode: GameMode,

    /// Width of the generated map (36–100 tiles).
    #[arg(
        long,
        help = "Map width",
        value_name = "Positive integer [36; 108]",
        default_value = "42",
        value_parser = value_parser!(u8).range(36..=108)
    )]
    pub map_width: u8,

    /// Height of the generated map (11–50 tiles).
    #[arg(
        long,
        help = "Map height",
        value_name = "Positive integer [11; 39]",
        default_value = "11",
        value_parser = value_parser!(u8).range(11..=39)
    )]
    pub map_height: u8,

    /// Zoom level used for the UI.
    #[arg(
        long,
        help = "Zoom level",
        value_name = "...",
        default_value_t = ZoomLevel::Default,
        value_enum
    )]
    pub zoom_level: ZoomLevel,

    /// Which perk deck to use.
    #[arg(
        long,
        help = "Perk Deck",
        value_name = "...",
        default_value_t = PerkDecks::Silesian,
        value_enum
    )]
    pub perk_deck: PerkDecks,

    /// Starting amount of wood (1–150).
    #[arg(
        long,
        help = "Starting wood",
        value_name = "Positive integer [1; 150]",
        default_value = "50",
        value_parser = value_parser!(u16).range(1..=150)
    )]
    pub starting_wood: u16,

    /// Starting amount of iron (1–150).
    #[arg(
        long,
        help = "Starting iron",
        value_name = "Positive integer [1; 150]",
        default_value = "25",
        value_parser = value_parser!(u16).range(1..=150)
    )]
    pub starting_iron: u16,

    /// Maximum amount of units (49–149).
    #[arg(
        long,
        help = "Supply limit",
        value_name = "Positive integer [49; 149]",
        default_value = "99",
        value_parser = value_parser!(u8).range(49..=149)
    )]
    pub supply_limit: u8,

    /// Modifier applied to experience points (0.5–2.0).
    #[arg(
        long,
        help = "XP modifier",
        value_name = "Float [0.5; 2.0]",
        default_value = "1.0",
        value_parser = parse_xp
    )]
    pub xp_modifier: f32,

    /// Upper bound for skill points that can be earned (90–690).
    #[arg(
        long,
        help = "Skill points limit",
        value_name = "Positive integer [90; 690]",
        default_value = "120",
        value_parser = value_parser!(u16).range(90..=690)
    )]
    pub skill_points_limit: u16,

    /// Which soundtrack to play (default: default)
    #[arg(
            long,
            help = "Soundtrack",
            value_name = "...",
            default_value_t = Soundtrack::Default,
            value_enum
        )]
    pub soundtrack: Soundtrack,

    /// Disable soundtrack (default: disabled).
    #[arg(
        long,
        help = "Disable soundtrack (default: disable)",
        default_value_t = false
    )]
    pub disable_audio: bool,

    /// Mute soundtrack (default: disabled).
    #[arg(
        long,
        help = "Mute soundtrack (default: disabled)",
        default_value_t = false
    )]
    pub mute: bool,

    /// Enable logging to a file (default: disabled).
    #[arg(
        long,
        help = "Enable logging to file (default: disabled)",
        default_value_t = false
    )]
    pub log: bool,
}

/// Parses the command line arguments and returns a fully populated `Cli`
/// instance.  This function simply forwards to `Cli::parse()`, which
/// handles argument validation and displays helpful error messages if
/// the user supplies invalid input.
pub fn get_args() -> Cli {
    let args: Cli = Cli::parse();

    if args.log {
        init_logger();
    }

    args
}

/// Parses a string into a floating‑point XP modifier and validates that it
/// lies within the inclusive range `0.5..=2.0`.
///
/// The function is used as a custom `value_parser` for the `--xp-modifier`
/// flag.  On failure it returns a `clap::Error` with `ErrorKind::InvalidValue`,
/// allowing `clap` to present a clear error message to the user.
///
/// # Errors
///
/// * Returns an error if the string cannot be parsed as `f32`.
/// * Returns an error if the parsed value is outside the allowed range.
fn parse_xp(s: &str) -> Result<f32, Error> {
    let v: f32 = s
        .parse()
        .map_err(|e: ParseFloatError| Error::raw(ErrorKind::InvalidValue, e.to_string()))?;
    if (0.5..=2.0).contains(&v) {
        Ok(v)
    } else {
        Err(Error::raw(
            ErrorKind::InvalidValue,
            format!("Value {} is out of range 0.5..=2.0", v),
        ))
    }
}
