use crate::libmaccel::fixedptc::Fpt;
use paste::paste;

/// Declare an enum for every parameter.
macro_rules! declare_common_params {
    ($($param:tt,)+) => {
        #[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
        #[derive(Debug, Clone, Copy, PartialEq)]
        pub enum Param {
            $($param),+
        }

        paste!(
            #[derive(Debug)]
            pub struct AllParamArgs {
                $( pub [< $param:snake:lower >]: Fpt ),+
            }
        );

        pub const ALL_PARAMS: &[Param] = &[ $(Param::$param),+ ];
    };
}

// Helper macro to create FFI-safe curve parameter structs
macro_rules! make_curve_params_struct {
    // Case: has parameters
    ($mode:tt, $($param:tt),+) => {
        paste! {
            #[repr(C)]
            #[derive(Debug, Clone, Copy, PartialEq, Default)]
            pub struct [< $mode CurveParams >] {
                $( pub [< $param:snake:lower >]: Fpt ),+
            }
        }
    };
    // Case: no parameters, add a ZST field for FFI safety
    ($mode:tt, ) => {
        paste! {
            #[repr(C)]
            #[derive(Debug, Clone, Copy, PartialEq, Default)]
            pub struct [< $mode CurveParams >] {
                pub _ffi_guard: [u8; 0],
            }
        }
    };
}

macro_rules! declare_params {
    ( Common { $($common_param:tt),+$(,)? } , $( $mode:tt { $($param:tt),*$(,)? }, )+) => {
        declare_common_params! {
            $( $common_param, )+
            $( $( $param, )* )+
        }

        /// Array of all the common parameters for convenience.
        pub const ALL_COMMON_PARAMS: &[Param] = &[ $( Param::$common_param),+ ];


        #[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
        #[derive(Debug, Default, PartialEq, Clone, Copy)]
        #[repr(u8)]
        pub enum AccelMode {
            #[default]
            $( $mode, )+
        }

        pub const ALL_MODES: &[AccelMode] = &[ $( AccelMode::$mode, )+ ];

        paste! {
            /// Define the complete shape (and memory layout) of the argument
            /// of the sensitivity function as it is expected to be in `C`
            #[repr(C)]
            pub struct AccelParams {
                $( pub [< $common_param:snake:lower >] : Fpt, )+
                pub by_mode: AccelParamsByMode,
            }

            /// Represents the tagged union of curve-specific parameters.
            #[repr(C, u8)]
            pub enum AccelParamsByMode {
                $(
                    $mode([< $mode CurveParams >] ),
                )+
            }

            /// Represents the common parameters and their float values.
            /// Use it to bulk set the common parameters.
            #[cfg_attr(feature = "clap", derive(clap::Args))]
            #[derive(Debug, Clone, Copy, PartialEq)]
            pub struct CommonParamArgs {
                $( pub [< $common_param:snake:lower >]: f64 ),+
            }
        }

        paste! {
            $(
                // Use the helper macro to define the struct
                make_curve_params_struct!($mode, $($param),*);

                #[doc = "Array of all parameters for the `"  $mode "` mode for convenience." ]
                pub const [< ALL_ $mode:upper _PARAMS >]: &[Param] = &[ $( Param::$param),* ];

                #[doc = "Represents the parameters for `" $mode "` curve and their float values"]
                /// Use it to bulk set the curve's parameters.
                #[cfg_attr(feature = "clap", derive(clap::Args))]
                #[derive(Debug, Clone, Copy, PartialEq)]
                pub struct [< $mode ParamArgs >] {
                    $( pub [< $param:snake:lower >]: f64 ),*
                }
            )+

            /// Subcommands for the CLI
            #[cfg(feature = "clap")]
            pub mod subcommads {
                #[derive(clap::Subcommand)]
                pub enum SetParamByModesSubcommands {
                    /// Set all the common parameters
                    Common(super::CommonParamArgs),
                    $(
                        #[doc = "Set all the parameters for the " $mode " curve" ]
                        $mode(super::[< $mode ParamArgs >]),
                    )+
                }

                #[derive(clap::Subcommand)]
                pub enum CliSubcommandSetParams {
                    /// Set the value for a single parameter
                    Param { name: crate::params::Param, value: f64 },
                    /// Set the acceleration mode (curve)
                    Mode { mode: crate::params::AccelMode },
                    /// Set the values for all parameters for a curve in order
                    All {
                        #[clap(subcommand)]
                        command: SetParamByModesSubcommands
                    },
                }

                #[derive(clap::Subcommand)]
                pub enum GetParamsByModesSubcommands {
                    /// Get all the common parameters
                    Common,
                    $(
                        #[doc = "Get all the parameters for the " $mode " curve" ]
                        $mode
                     ),+
                }

                #[derive(clap::Subcommand)]
                pub enum CliSubcommandGetParams {
                    /// Get the value for a single parameter
                    Param { name: crate::params::Param },
                    /// Get the current acceleration mode (curve)
                    Mode,
                    /// Get the values for all parameters for a curve in order
                    All {
                        /// Print the values in one line, separated by a space
                        #[arg(long)]
                        oneline: bool,
                        #[arg(short, long)]
                        /// Print only the values
                        quiet: bool,

                        #[clap(subcommand)]
                        command: GetParamsByModesSubcommands
                    },
                }
            }
        }
    };
}

declare_params!(
    Common {
        SensMult,
        YxRatio,
        InputDpi,
        AngleRotation
    },
    Linear {
        Accel,
        OffsetLinear,
        OutputCap,
    },
    Natural {
        DecayRate,
        OffsetNatural,
        Limit,
    },
    Synchronous {
        Gamma,
        Smooth,
        Motivity,
        SyncSpeed,
    },
    NoAccel {},
);

impl AccelMode {
    pub fn as_title(&self) -> &'static str {
        match self {
            AccelMode::Linear => "Linear Acceleration",
            AccelMode::Natural => "Natural (w/ Gain)",
            AccelMode::Synchronous => "Synchronous",
            AccelMode::NoAccel => "No Acceleration",
        }
    }
}

impl AccelMode {
    pub const PARAM_NAME: &'static str = "MODE";

    pub fn ordinal(&self) -> i64 {
        (*self as i8).into()
    }
}

impl Param {
    /// The canonical internal name of the parameter.
    pub fn name(&self) -> &'static str {
        match self {
            Param::SensMult => "SENS_MULT",
            Param::YxRatio => "YX_RATIO",
            Param::InputDpi => "INPUT_DPI",
            Param::Accel => "ACCEL",
            Param::OffsetLinear => "OFFSET",
            Param::OffsetNatural => "OFFSET",
            Param::OutputCap => "OUTPUT_CAP",
            Param::DecayRate => "DECAY_RATE",
            Param::Limit => "LIMIT",
            Param::Gamma => "GAMMA",
            Param::Smooth => "SMOOTH",
            Param::Motivity => "MOTIVITY",
            Param::SyncSpeed => "SYNC_SPEED",
            Param::AngleRotation => "ANGLE_ROTATION",
        }
    }

    pub fn display_name(&self) -> &'static str {
        match self {
            Param::SensMult => "Sens-Multiplier",
            Param::Accel => "Accel",
            Param::InputDpi => "Input DPI",
            Param::OffsetLinear => "Offset",
            Param::OffsetNatural => "Offset",
            Param::OutputCap => "Output-Cap",
            Param::YxRatio => "Y/x Ratio",
            Param::DecayRate => "Decay-Rate",
            Param::Limit => "Limit",
            Param::Gamma => "Gamma",
            Param::Smooth => "Smooth",
            Param::Motivity => "Motivity",
            Param::SyncSpeed => "Sync Speed",
            Param::AngleRotation => "Angle Rotation",
        }
    }

    pub fn description(&self) -> &'static str {
        match self {
            Param::SensMult => "Base sensitivity multiplier. Adjusts overall mouse speed.",
            Param::YxRatio => {
                "Y to X axis sensitivity ratio. Values > 1 increase vertical sensitivity."
            }
            Param::InputDpi => {
                "Mouse DPI. Used to normalize to 1000 DPI equivalent for consistent acceleration."
            }
            Param::AngleRotation => "Rotation angle in degrees for sensitivity direction.",
            Param::Accel => "Acceleration strength. Higher values = faster cursor at high speeds.",
            Param::OffsetLinear => "Speed threshold (counts/ms) before acceleration begins.",
            Param::OutputCap => "Maximum sensitivity multiplier cap. Prevents excessive speed.",
            Param::DecayRate => "How quickly acceleration decays. Higher = faster decay.",
            Param::OffsetNatural => "Speed threshold (counts/ms) for natural curve activation.",
            Param::Limit => "Maximum gain limit for natural acceleration.",
            Param::Gamma => "Exponent controlling curve shape. Higher = more aggressive ramp-up.",
            Param::Smooth => "Smoothing factor (0-1). Higher = more gradual transitions.",
            Param::Motivity => "Degree of acceleration effect. Must be > 1.",
            Param::SyncSpeed => "Synchronization speed. Controls how fast sync responds.",
        }
    }
}

pub(crate) fn format_param_value(value: f64) -> String {
    let mut number = format!("{value:.5}");

    for idx in (1..number.len()).rev() {
        let this_char = &number[idx..idx + 1];
        if this_char != "0" {
            if this_char == "." {
                number.remove(idx);
            }
            break;
        }
        number.remove(idx);
    }

    number
}

#[cfg(test)]
#[test]
fn format_param_value_works() {
    assert_eq!(format_param_value(1.5), "1.5");
    assert_eq!(format_param_value(1.50), "1.5");
    assert_eq!(format_param_value(100.0), "100");
    assert_eq!(format_param_value(0.0600), "0.06");
    assert_eq!(format_param_value(0.055000), "0.055");
}

pub(crate) fn validate_param_value(param_tag: Param, value: f64) -> anyhow::Result<()> {
    match param_tag {
        Param::SensMult => {}
        Param::YxRatio => {}
        Param::InputDpi => {
            if value <= 0.0 {
                anyhow::bail!("Input DPI must be positive");
            }
        }
        Param::AngleRotation => {}
        Param::Accel => {}
        Param::OutputCap => {}
        Param::OffsetLinear | Param::OffsetNatural => {
            if value < 0.0 {
                anyhow::bail!("offset cannot be less than 0");
            }
        }
        Param::DecayRate => {
            if value <= 0.0 {
                anyhow::bail!("decay rate must be positive");
            }
        }
        Param::Limit => {
            if value < 1.0 {
                anyhow::bail!("limit cannot be less than 1");
            }
        }
        Param::Gamma => {
            if value <= 0.0 {
                anyhow::bail!("Gamma must be positive");
            }
        }
        Param::Smooth => {
            if !(0.0..=1.0).contains(&value) {
                anyhow::bail!("Smooth must be between 0 and 1");
            }
        }
        Param::Motivity => {
            if value <= 1.0 {
                anyhow::bail!("Motivity must be greater than 1");
            }
        }
        Param::SyncSpeed => {
            if value <= 0.0 {
                anyhow::bail!("'Synchronous speed' must be positive");
            }
        }
    }

    Ok(())
}
