use super::parameter_map::ParameterValue;
use crate::cables::{MonoLayout, PolyLayout};

/// Describes a single port on a module by name and index.
///
/// The `index` field is the user-visible number in a multi-port group (e.g.
/// `in/2` has `name = "in"` and `index = 2`). For modules with a single port
/// of a given name, `index` is `0`. The position of a `PortDescriptor` in
/// `ModuleDescriptor::inputs` / `outputs` determines the slice offset passed to
/// `Module::process`; `index` is semantically distinct from that position.
///
/// `kind` declares whether the port carries a mono or poly signal. Port arity
/// is fixed at module-definition time and used by [`ModuleGraph::connect`] to
/// reject kind-mismatched connections at graph-construction time.
///
/// `mono_layout` / `poly_layout` declare the structured frame or semantic
/// format carried by the cable (ADR 0033, ADR 0047). Only the layout for the
/// port's own arity is consulted by validation:
///
/// - `Mono` ports are validated against `mono_layout` (`Audio` or `Trigger`).
/// - `Poly` ports are validated against `poly_layout`
///   (`Audio`, `Trigger`, `Transport`, `Midi`).
///
/// Layouts must match exactly on both sides of a connection. Both fields
/// default to `Audio`; helper builders (`trigger_in`, `midi_out`, …) set them.
#[derive(Debug, Clone)]
pub struct PortDescriptor {
    pub name: &'static str,
    pub index: usize,
    pub kind: crate::cables::CableKind,
    pub mono_layout: MonoLayout,
    pub poly_layout: PolyLayout,
}

/// A reference to a named, indexed port used in `ModuleGraph::connect()`.
///
/// Port names are always `&'static str` (defined by module implementations at
/// compile time), so producing a `PortRef` never allocates.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PortRef {
    pub name: &'static str,
    pub index: usize,
}

/// A static-lifetime reference to a named, indexed parameter.
///
/// Mirrors [`PortRef`] for parameters. Parameter names are compile-time
/// constants defined by module implementations, so producing a `ParameterRef`
/// never allocates.
///
/// Use [`ParameterKey`](super::parameter_map::ParameterKey) when an owned key
/// is needed (e.g. as a map entry from DSL parsing).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ParameterRef {
    pub name: &'static str,
    pub index: usize,
}

#[derive(Debug, Clone)]
pub enum ParameterKind {
    Float { min: f32, max: f32, default: f32 },
    Int   { min: i64, max: i64, default: i64 },
    Bool  { default: bool },
    Enum  { variants: &'static [&'static str], default: &'static str },
    /// A structural file path parameter (ADR 0060). Declared via
    /// [`ModuleDescriptor::structural_string_param`]; only valid in
    /// `structural_params`, never in `realtime_params`. The DSL writes
    /// `file("path")` which the interpreter resolves to an absolute path
    /// against the patch file's directory and threads through the structural
    /// blob to `Module::prepare`. `extensions` declares the set of accepted
    /// file extensions (e.g. `&["wav", "aiff"]`); the interpreter validates
    /// the extension before the value reaches the planner.
    File { extensions: &'static [&'static str] },
    /// A song name parameter. The DSL writes a string (`song: "my_song"`)
    /// which the interpreter resolves to an integer index into the
    /// alphabetically-sorted song bank. The module receives a
    /// `ParameterValue::Int` (−1 if unresolved/empty).
    SongName,
}

impl ParameterKind {
    /// Return the default value for this parameter kind as a [`ParameterValue`].
    pub fn default_value(&self) -> ParameterValue {
        match self {
            ParameterKind::Float { default, .. } => ParameterValue::Float(*default),
            ParameterKind::Int   { default, .. } => ParameterValue::Int(*default),
            ParameterKind::Bool  { default }     => ParameterValue::Bool(*default),
            ParameterKind::Enum  { variants, default } => {
                let idx = variants
                    .iter()
                    .position(|v| v == default)
                    .unwrap_or(0) as u32;
                ParameterValue::Enum(idx)
            }
            ParameterKind::File { .. } => unreachable!(
                "ParameterKind::File only valid in structural_params, not realtime_params"
            ),
            ParameterKind::SongName => ParameterValue::Int(-1),
        }
    }

    /// Return a short type name suitable for error messages.
    pub fn kind_name(&self) -> &'static str {
        match self {
            ParameterKind::Float { .. } => "float",
            ParameterKind::Int   { .. } => "int",
            ParameterKind::Bool  { .. } => "bool",
            ParameterKind::Enum   { .. } => "enum",
            ParameterKind::File  { .. } => "file",
            ParameterKind::SongName => "song_name",
        }
    }
}

pub struct ParameterSpec {
    pub name: &'static str,
    pub kind: ParameterKind,
}

#[derive(Debug, Clone)]
pub struct ParameterDescriptor {
    pub name: &'static str,
    pub index: usize,
    pub parameter_type: ParameterKind,
}

impl ParameterDescriptor {
    /// Return `true` if `name` and `index` refer to this parameter.
    pub fn matches(&self, name: &str, index: usize) -> bool {
        self.name == name && self.index == index
    }

    /// Return a [`ParameterRef`] identifying this parameter.
    pub fn as_ref(&self) -> ParameterRef {
        ParameterRef { name: self.name, index: self.index }
    }
}

/// Descriptor-shape (ADR 0060): the only field that changes the descriptor's
/// port counts and identity hash. Construction-time knobs that don't shape
/// the descriptor (former `length`, `high_quality`) live in
/// `structural_params` on the modules that consume them.
#[derive(Debug, Clone, PartialEq)]
pub struct ModuleShape {
    pub channels: usize,
}

impl ModuleShape {
    /// Validate shape invariants every module relies on. `channels` is the
    /// count of `*_multi` port families and parameter array entries — 0 means
    /// the descriptor would carry no entries for those families and the
    /// module would silently behave as if they didn't exist. Treat as an
    /// authoring error.
    ///
    /// Returns a static reason string on failure so callers (Registry, LSP,
    /// manifest consumers) can wrap the error in their own type.
    pub fn validate(&self) -> Result<(), &'static str> {
        if self.channels == 0 {
            return Err("channels must be >= 1");
        }
        Ok(())
    }
}

impl Default for ModuleShape {
    /// `channels = 1`. Modules with `*_multi` port families use this as
    /// the implicit count when the DSL omits a shape arg, so a default
    /// shape still surfaces one entry per family (hover, descriptor
    /// lookup, completions). Use `ModuleShape { channels: N }` directly
    /// for higher counts.
    fn default() -> Self {
        Self { channels: 1 }
    }
}

/// Describes the full layout of a module.
///
/// Inputs, outputs, and parameters are stored in separate vecs.
///
/// The index of a port in `inputs` corresponds to the index in the `inputs` slice passed to
/// [`Module::process`], and similarly for `outputs`. The graph and patch builder
/// use this to resolve port names to slice indices at build time.
#[derive(Debug, Clone)]
pub struct ModuleDescriptor {
    pub module_name: &'static str,
    pub shape: ModuleShape,
    pub inputs: Vec<PortDescriptor>,
    pub outputs: Vec<PortDescriptor>,
    /// Numeric parameters packed into [`crate::param_frame::ParamFrame`] and
    /// consumed on the audio thread (ADR 0060).
    pub realtime_params: Vec<ParameterDescriptor>,
    /// Construction-time, control-thread-only parameters (ADR 0060). Read by
    /// [`super::Module::prepare`]; never reach the audio thread. May carry
    /// non-packable types such as strings.
    pub structural_params: Vec<ParameterDescriptor>,
}

// Generates a pair of builder methods for a single port kind/direction:
//   $single(name) — appends one PortDescriptor with index 0
//   $multi(name, count) — appends `count` PortDescriptors with indices 0..count
macro_rules! port_builder {
    ($single:ident, $multi:ident, $vec:ident, $kind:expr, $mono_layout:expr, $poly_layout:expr) => {
        pub fn $single(mut self, name: &'static str) -> Self {
            self.$vec.push(PortDescriptor {
                name, index: 0, kind: $kind,
                mono_layout: $mono_layout, poly_layout: $poly_layout,
            });
            self
        }

        pub fn $multi(mut self, name: &'static str, count: usize) -> Self {
            for i in 0..count {
                self.$vec.push(PortDescriptor {
                    name, index: i, kind: $kind,
                    mono_layout: $mono_layout, poly_layout: $poly_layout,
                });
            }
            self
        }
    };
}

// Generates a pair of builder methods for a single parameter kind:
//   $single(name, $args…) — appends one ParameterDescriptor with index 0
//   $multi(name, count, $args…) — appends `count` ParameterDescriptors with indices 0..count
macro_rules! param_builder {
    ($single:ident, $multi:ident, $NameTy:ty, $ArrayTy:ty, ($($arg:ident : $ty:ty),*), $kind:expr) => {
        pub fn $single(mut self, name: impl Into<$NameTy>, $($arg: $ty),*) -> Self {
            let name: $NameTy = name.into();
            self.realtime_params.push(ParameterDescriptor {
                name: name.as_str(),
                index: 0,
                parameter_type: $kind,
            });
            self
        }

        pub fn $multi(mut self, name: impl Into<$ArrayTy>, count: usize, $($arg: $ty),*) -> Self {
            let name: $ArrayTy = name.into();
            for i in 0..count {
                self.realtime_params.push(ParameterDescriptor {
                    name: name.as_str(),
                    index: i,
                    parameter_type: $kind,
                });
            }
            self
        }
    };
}

impl ModuleDescriptor {
    /// Construct an empty descriptor with no ports or parameters.
    pub fn new(name: &'static str, shape: ModuleShape) -> Self {
        Self {
            module_name: name,
            shape,
            inputs: Vec::new(),
            outputs: Vec::new(),
            realtime_params: Vec::new(),
            structural_params: Vec::new(),
        }
    }

    // ── Port builder methods (generated) ────────────────────────────────────

    port_builder!(mono_in,         mono_in_multi,         inputs,
        crate::cables::CableKind::Mono, MonoLayout::Audio, PolyLayout::Audio);
    port_builder!(poly_in,         poly_in_multi,         inputs,
        crate::cables::CableKind::Poly, MonoLayout::Audio, PolyLayout::Audio);
    port_builder!(mono_out,        mono_out_multi,        outputs,
        crate::cables::CableKind::Mono, MonoLayout::Audio, PolyLayout::Audio);
    port_builder!(poly_out,        poly_out_multi,        outputs,
        crate::cables::CableKind::Poly, MonoLayout::Audio, PolyLayout::Audio);
    port_builder!(trigger_in,      trigger_in_multi,      inputs,
        crate::cables::CableKind::Mono, MonoLayout::Trigger, PolyLayout::Audio);
    port_builder!(trigger_out,     trigger_out_multi,     outputs,
        crate::cables::CableKind::Mono, MonoLayout::Trigger, PolyLayout::Audio);
    port_builder!(poly_trigger_in, poly_trigger_in_multi, inputs,
        crate::cables::CableKind::Poly, MonoLayout::Audio, PolyLayout::Trigger);
    port_builder!(poly_trigger_out,poly_trigger_out_multi,outputs,
        crate::cables::CableKind::Poly, MonoLayout::Audio, PolyLayout::Trigger);
    port_builder!(midi_in,         midi_in_multi,         inputs,
        crate::cables::CableKind::Poly, MonoLayout::Audio, PolyLayout::Midi);
    port_builder!(midi_out,        midi_out_multi,        outputs,
        crate::cables::CableKind::Poly, MonoLayout::Audio, PolyLayout::Midi);
    port_builder!(stereo_in,       stereo_in_multi,       inputs,
        crate::cables::CableKind::Stereo, MonoLayout::Audio, PolyLayout::Audio);
    port_builder!(stereo_out,      stereo_out_multi,      outputs,
        crate::cables::CableKind::Stereo, MonoLayout::Audio, PolyLayout::Audio);

    /// Declare a poly input port with a specific [`PolyLayout`].
    pub fn poly_in_layout(mut self, name: &'static str, layout: PolyLayout) -> Self {
        self.inputs.push(PortDescriptor {
            name, index: 0, kind: crate::cables::CableKind::Poly,
            mono_layout: MonoLayout::Audio, poly_layout: layout,
        });
        self
    }

    /// Declare a poly output port with a specific [`PolyLayout`].
    pub fn poly_out_layout(mut self, name: &'static str, layout: PolyLayout) -> Self {
        self.outputs.push(PortDescriptor {
            name, index: 0, kind: crate::cables::CableKind::Poly,
            mono_layout: MonoLayout::Audio, poly_layout: layout,
        });
        self
    }

    // ── Parameter builder methods (generated) ───────────────────────────────

    param_builder!(float_param, float_param_multi,
        crate::params::FloatParamName, crate::params::FloatParamArray,
        (min: f32, max: f32, default: f32),
        ParameterKind::Float { min, max, default });

    param_builder!(int_param, int_param_multi,
        crate::params::IntParamName, crate::params::IntParamArray,
        (min: i64, max: i64, default: i64),
        ParameterKind::Int { min, max, default });

    param_builder!(bool_param, bool_param_multi,
        crate::params::BoolParamName, crate::params::BoolParamArray,
        (default: bool),
        ParameterKind::Bool { default });

    /// Typed enum parameter. Variants come from `E::VARIANTS`; default value
    /// is supplied as a Rust enum value.
    pub fn enum_param<E: crate::params::ParamEnum>(
        mut self,
        name: impl Into<crate::params::EnumParamName<E>>,
        default: E,
    ) -> Self {
        let name: crate::params::EnumParamName<E> = name.into();
        let variants = E::VARIANTS;
        let default_s = variants[default.to_variant() as usize];
        self.realtime_params.push(ParameterDescriptor {
            name: name.as_str(),
            index: 0,
            parameter_type: ParameterKind::Enum { variants, default: default_s },
        });
        self
    }

    pub fn enum_param_multi<E: crate::params::ParamEnum>(
        mut self,
        name: impl Into<crate::params::EnumParamArray<E>>,
        count: usize,
        default: E,
    ) -> Self {
        let name: crate::params::EnumParamArray<E> = name.into();
        let variants = E::VARIANTS;
        let default_s = variants[default.to_variant() as usize];
        for i in 0..count {
            self.realtime_params.push(ParameterDescriptor {
                name: name.as_str(),
                index: i,
                parameter_type: ParameterKind::Enum { variants, default: default_s },
            });
        }
        self
    }

    /// Declare a song-name parameter. The DSL supplies a string; the
    /// interpreter resolves it to a `ParameterValue::Int` song-bank index.
    pub fn song_name_param(mut self, name: impl Into<crate::params::SongNameParamName>) -> Self {
        self.realtime_params.push(ParameterDescriptor {
            name: name.into().as_str(),
            index: 0,
            parameter_type: ParameterKind::SongName,
        });
        self
    }

    // ── Structural parameter builders (ADR 0060) ────────────────────────────
    //
    // Structural params are construction-time inputs read by
    // `Module::prepare`. They are not packed into `ParamFrame`, never reach
    // the audio thread, and may carry non-packable types (strings).

    pub fn structural_bool_param(mut self, name: &'static str, default: bool) -> Self {
        self.structural_params.push(ParameterDescriptor {
            name,
            index: 0,
            parameter_type: ParameterKind::Bool { default },
        });
        self
    }

    pub fn structural_int_param(
        mut self,
        name: &'static str,
        min: i64,
        max: i64,
        default: i64,
    ) -> Self {
        self.structural_params.push(ParameterDescriptor {
            name,
            index: 0,
            parameter_type: ParameterKind::Int { min, max, default },
        });
        self
    }

    pub fn structural_float_param(
        mut self,
        name: &'static str,
        min: f32,
        max: f32,
        default: f32,
    ) -> Self {
        self.structural_params.push(ParameterDescriptor {
            name,
            index: 0,
            parameter_type: ParameterKind::Float { min, max, default },
        });
        self
    }

    pub fn structural_string_param(
        mut self,
        name: &'static str,
        extensions: &'static [&'static str],
    ) -> Self {
        self.structural_params.push(ParameterDescriptor {
            name,
            index: 0,
            parameter_type: ParameterKind::File { extensions },
        });
        self
    }

}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::cables::CableKind;
    use crate::params::{EnumParamArray, EnumParamName};

    crate::params_enum! {
        pub enum Wave {
            Sine => "sine",
            Saw => "saw",
            Square => "square",
        }
    }

    crate::params_enum! {
        pub enum Wave2 {
            Sine => "sine",
            Saw => "saw",
        }
    }

    #[test]
    fn build_a_module_descriptor() {
        let gain_amount = ParameterKind::Float { min: 0.0, max: 1.2, default: 1.0 };
        let pan_amount = ParameterKind::Float { min: -1.0, max: 1.0, default: 0.0 };
        let toggle_off_on = ParameterKind::Bool { default: false };

        let m = ModuleDescriptor {
            module_name: "Mixer",
            shape: ModuleShape { channels: 2 },
            inputs: vec![
                PortDescriptor { name: "in", index: 0, kind: CableKind::Mono, mono_layout: MonoLayout::Audio, poly_layout: PolyLayout::Audio },
                PortDescriptor { name: "in", index: 1, kind: CableKind::Mono, mono_layout: MonoLayout::Audio, poly_layout: PolyLayout::Audio },
                PortDescriptor { name: "gain_mod", index: 0, kind: CableKind::Mono, mono_layout: MonoLayout::Audio, poly_layout: PolyLayout::Audio },
                PortDescriptor { name: "gain_mod", index: 1, kind: CableKind::Mono, mono_layout: MonoLayout::Audio, poly_layout: PolyLayout::Audio },
                PortDescriptor { name: "pan_mod", index: 0, kind: CableKind::Mono, mono_layout: MonoLayout::Audio, poly_layout: PolyLayout::Audio },
                PortDescriptor { name: "pan_mod", index: 1, kind: CableKind::Mono, mono_layout: MonoLayout::Audio, poly_layout: PolyLayout::Audio },
            ],
            outputs: vec![
                PortDescriptor { name: "out_l", index: 0, kind: CableKind::Mono, mono_layout: MonoLayout::Audio, poly_layout: PolyLayout::Audio },
                PortDescriptor { name: "out_r", index: 0, kind: CableKind::Mono, mono_layout: MonoLayout::Audio, poly_layout: PolyLayout::Audio },
            ],
            realtime_params: vec![
                ParameterDescriptor { name: "gain", index: 0, parameter_type: gain_amount.clone() },
                ParameterDescriptor { name: "gain", index: 1, parameter_type: gain_amount },
                ParameterDescriptor { name: "pan", index: 0, parameter_type: pan_amount.clone() },
                ParameterDescriptor { name: "pan", index: 1, parameter_type: pan_amount },
                ParameterDescriptor { name: "mute", index: 0, parameter_type: toggle_off_on.clone() },
                ParameterDescriptor { name: "mute", index: 1, parameter_type: toggle_off_on.clone() },
                ParameterDescriptor { name: "solo", index: 0, parameter_type: toggle_off_on.clone() },
                ParameterDescriptor { name: "solo", index: 1, parameter_type: toggle_off_on },
            ],
            structural_params: vec![],
        };
        assert_eq!(m.module_name, "Mixer");
        assert_eq!(m.shape.channels, 2);
        assert_eq!(m.inputs.len(), 6);
        assert_eq!(m.outputs.len(), 2);
        assert_eq!(m.realtime_params.len(), 8);
    }

    #[test]
    fn builder_single_port_methods() {
        let m = ModuleDescriptor::new("Vca", ModuleShape { channels: 1 })
            .mono_in("in")
            .mono_in("cv")
            .mono_out("out")
            .poly_in("poly_in")
            .poly_out("poly_out");

        assert_eq!(m.inputs.len(), 3);
        assert_eq!(m.inputs[0].name, "in");
        assert_eq!(m.inputs[0].index, 0);
        assert_eq!(m.inputs[0].kind, CableKind::Mono);
        assert_eq!(m.inputs[1].name, "cv");
        assert_eq!(m.inputs[1].index, 0);
        assert_eq!(m.inputs[1].kind, CableKind::Mono);
        assert_eq!(m.inputs[2].name, "poly_in");
        assert_eq!(m.inputs[2].index, 0);
        assert_eq!(m.inputs[2].kind, CableKind::Poly);

        assert_eq!(m.outputs.len(), 2);
        assert_eq!(m.outputs[0].name, "out");
        assert_eq!(m.outputs[0].index, 0);
        assert_eq!(m.outputs[0].kind, CableKind::Mono);
        assert_eq!(m.outputs[1].name, "poly_out");
        assert_eq!(m.outputs[1].index, 0);
        assert_eq!(m.outputs[1].kind, CableKind::Poly);

    }

    #[test]
    fn builder_multi_port_methods_count_3() {
        let m = ModuleDescriptor::new("Mixer", ModuleShape { channels: 3 })
            .mono_in_multi("in", 3)
            .poly_out_multi("out", 3);

        assert_eq!(m.inputs.len(), 3);
        for i in 0..3usize {
            assert_eq!(m.inputs[i].name, "in");
            assert_eq!(m.inputs[i].index, i);
            assert_eq!(m.inputs[i].kind, CableKind::Mono);
        }

        assert_eq!(m.outputs.len(), 3);
        for i in 0..3usize {
            assert_eq!(m.outputs[i].name, "out");
            assert_eq!(m.outputs[i].index, i);
            assert_eq!(m.outputs[i].kind, CableKind::Poly);
        }
    }

    #[test]
    fn builder_parameter_methods() {
        let m = ModuleDescriptor::new("Synth", ModuleShape { channels: 1 })
            .float_param("gain", 0.0, 1.0, 0.5)
            .int_param("voices", 1, 8, 4)
            .bool_param("active", true)
            .enum_param(EnumParamName::<Wave>::new("wave"), Wave::Sine);

        assert_eq!(m.realtime_params.len(), 4);

        let p = &m.realtime_params[0];
        assert_eq!(p.name, "gain");
        assert_eq!(p.index, 0);
        assert!(matches!(p.parameter_type, ParameterKind::Float { min, max, default } if min == 0.0 && max == 1.0 && default == 0.5));

        let p = &m.realtime_params[1];
        assert_eq!(p.name, "voices");
        assert_eq!(p.index, 0);
        assert!(matches!(p.parameter_type, ParameterKind::Int { min, max, default } if min == 1 && max == 8 && default == 4));

        let p = &m.realtime_params[2];
        assert_eq!(p.name, "active");
        assert_eq!(p.index, 0);
        assert!(matches!(p.parameter_type, ParameterKind::Bool { default } if default));

        let p = &m.realtime_params[3];
        assert_eq!(p.name, "wave");
        assert_eq!(p.index, 0);
        assert!(matches!(p.parameter_type, ParameterKind::Enum { default, .. } if default == "sine"));
    }

    #[test]
    fn builder_multi_parameter_methods() {
        let m = ModuleDescriptor::new("Mixer", ModuleShape { channels: 3 })
            .float_param_multi("gain", 3, 0.0, 1.2, 1.0)
            .int_param_multi("steps", 3, 1, 32, 16)
            .bool_param_multi("mute", 3, false)
            .enum_param_multi(EnumParamArray::<Wave2>::new("wave"), 3, Wave2::Sine);

        assert_eq!(m.realtime_params.len(), 12);

        // Check indices for first group
        for i in 0..3 {
            assert_eq!(m.realtime_params[i].name, "gain");
            assert_eq!(m.realtime_params[i].index, i);
        }
        for i in 0..3 {
            assert_eq!(m.realtime_params[3 + i].name, "steps");
            assert_eq!(m.realtime_params[3 + i].index, i);
        }
        for i in 0..3 {
            assert_eq!(m.realtime_params[6 + i].name, "mute");
            assert_eq!(m.realtime_params[6 + i].index, i);
        }
        for i in 0..3 {
            assert_eq!(m.realtime_params[9 + i].name, "wave");
            assert_eq!(m.realtime_params[9 + i].index, i);
        }
    }

}
