use crate::audio::midi::MidiEvent;
use crate::audio::node_graph::{AudioNode, AudioGraph, NodeCategory, NodePort, Parameter, ParameterUnit, SignalType};

const PARAM_VOICE_COUNT: u32 = 0;
const MAX_VOICES: usize = 16; // Maximum allowed voices
const DEFAULT_VOICES: usize = 8;

/// Voice state for voice allocation
#[derive(Clone)]
struct VoiceState {
    active: bool,
    note: u8,
    age: u32, // For voice stealing
    pending_events: Vec<MidiEvent>, // MIDI events to send to this voice
}

impl VoiceState {
    fn new() -> Self {
        Self {
            active: false,
            note: 0,
            age: 0,
            pending_events: Vec::new(),
        }
    }
}

/// VoiceAllocatorNode - A group node that creates N polyphonic instances of its internal graph
///
/// This node acts as a container for a "voice template" graph. At runtime, it creates
/// N instances of that graph (one per voice) and routes MIDI note events to them.
/// All voice outputs are mixed together into a single output.
pub struct VoiceAllocatorNode {
    name: String,

    /// The template graph (edited by user via UI)
    template_graph: AudioGraph,

    /// Runtime voice instances (clones of template)
    voice_instances: Vec<AudioGraph>,

    /// Voice allocation state
    voices: [VoiceState; MAX_VOICES],

    /// Number of active voices (configurable parameter)
    voice_count: usize,

    /// Mix buffer for combining voice outputs
    mix_buffer: Vec<f32>,

    inputs: Vec<NodePort>,
    outputs: Vec<NodePort>,
    parameters: Vec<Parameter>,
}

impl VoiceAllocatorNode {
    pub fn new(name: impl Into<String>, sample_rate: u32, buffer_size: usize) -> Self {
        let name = name.into();

        // MIDI input for receiving note events
        let inputs = vec![
            NodePort::new("MIDI In", SignalType::Midi, 0),
        ];

        // Single mixed audio output
        let outputs = vec![
            NodePort::new("Mixed Out", SignalType::Audio, 0),
        ];

        // Voice count parameter
        let parameters = vec![
            Parameter::new(PARAM_VOICE_COUNT, "Voices", 1.0, MAX_VOICES as f32, DEFAULT_VOICES as f32, ParameterUnit::Generic),
        ];

        // Create empty template graph
        let template_graph = AudioGraph::new(sample_rate, buffer_size);

        // Create voice instances (initially empty clones of template)
        let voice_instances: Vec<AudioGraph> = (0..MAX_VOICES)
            .map(|_| AudioGraph::new(sample_rate, buffer_size))
            .collect();

        Self {
            name,
            template_graph,
            voice_instances,
            voices: std::array::from_fn(|_| VoiceState::new()),
            voice_count: DEFAULT_VOICES,
            mix_buffer: vec![0.0; buffer_size * 2], // Stereo
            inputs,
            outputs,
            parameters,
        }
    }

    /// Get mutable reference to template graph (for UI editing)
    pub fn template_graph_mut(&mut self) -> &mut AudioGraph {
        &mut self.template_graph
    }

    /// Get reference to template graph (for serialization)
    pub fn template_graph(&self) -> &AudioGraph {
        &self.template_graph
    }

    /// Rebuild voice instances from template (called after template is edited)
    pub fn rebuild_voices(&mut self) {
        // Clone template to all voice instances
        for voice in &mut self.voice_instances {
            *voice = self.template_graph.clone_graph();

            // Find TemplateInput and TemplateOutput nodes
            let mut template_input_idx = None;
            let mut template_output_idx = None;

            for node_idx in voice.node_indices() {
                if let Some(node) = voice.get_node(node_idx) {
                    match node.node_type() {
                        "TemplateInput" => template_input_idx = Some(node_idx),
                        "TemplateOutput" => template_output_idx = Some(node_idx),
                        _ => {}
                    }
                }
            }

            // Mark ONLY TemplateInput as a MIDI target
            // MIDI will flow through graph connections to other nodes (like MidiToCV)
            if let Some(input_idx) = template_input_idx {
                voice.set_midi_target(input_idx, true);
            }

            // Set TemplateOutput as output node
            voice.set_output_node(template_output_idx);
        }
    }

    /// Find a free voice, or steal the oldest one
    fn find_voice_for_note_on(&mut self) -> usize {
        // Only search within active voice_count
        // First, look for an inactive voice
        for (i, voice) in self.voices[..self.voice_count].iter().enumerate() {
            if !voice.active {
                return i;
            }
        }

        // No free voices, steal the oldest one within voice_count
        self.voices[..self.voice_count]
            .iter()
            .enumerate()
            .max_by_key(|(_, v)| v.age)
            .map(|(i, _)| i)
            .unwrap_or(0)
    }

    /// Find all voices playing a specific note
    fn find_voices_for_note_off(&self, note: u8) -> Vec<usize> {
        self.voices[..self.voice_count]
            .iter()
            .enumerate()
            .filter_map(|(i, v)| {
                if v.active && v.note == note {
                    Some(i)
                } else {
                    None
                }
            })
            .collect()
    }
}

impl AudioNode for VoiceAllocatorNode {
    fn category(&self) -> NodeCategory {
        NodeCategory::Utility
    }

    fn inputs(&self) -> &[NodePort] {
        &self.inputs
    }

    fn outputs(&self) -> &[NodePort] {
        &self.outputs
    }

    fn parameters(&self) -> &[Parameter] {
        &self.parameters
    }

    fn set_parameter(&mut self, id: u32, value: f32) {
        match id {
            PARAM_VOICE_COUNT => {
                let new_count = (value.round() as usize).clamp(1, MAX_VOICES);
                if new_count != self.voice_count {
                    self.voice_count = new_count;
                    // Stop voices beyond the new count
                    for voice in &mut self.voices[new_count..] {
                        voice.active = false;
                    }
                }
            }
            _ => {}
        }
    }

    fn get_parameter(&self, id: u32) -> f32 {
        match id {
            PARAM_VOICE_COUNT => self.voice_count as f32,
            _ => 0.0,
        }
    }

    fn handle_midi(&mut self, event: &MidiEvent) {
        let status = event.status & 0xF0;

        match status {
            0x90 => {
                // Note on
                if event.data2 > 0 {
                    let voice_idx = self.find_voice_for_note_on();
                    self.voices[voice_idx].active = true;
                    self.voices[voice_idx].note = event.data1;
                    self.voices[voice_idx].age = 0;

                    // Store MIDI event for this voice to process
                    self.voices[voice_idx].pending_events.push(*event);
                } else {
                    // Velocity = 0 means note off - send to ALL voices playing this note
                    let voice_indices = self.find_voices_for_note_off(event.data1);
                    for voice_idx in voice_indices {
                        self.voices[voice_idx].active = false;
                        self.voices[voice_idx].pending_events.push(*event);
                    }
                }
            }
            0x80 => {
                // Note off - send to ALL voices playing this note
                let voice_indices = self.find_voices_for_note_off(event.data1);
                for voice_idx in voice_indices {
                    self.voices[voice_idx].active = false;
                    self.voices[voice_idx].pending_events.push(*event);
                }
            }
            _ => {
                // Other MIDI events (CC, pitch bend, etc.) - send to all active voices
                for voice_idx in 0..self.voice_count {
                    if self.voices[voice_idx].active {
                        self.voices[voice_idx].pending_events.push(*event);
                    }
                }
            }
        }
    }

    fn process(
        &mut self,
        _inputs: &[&[f32]],
        outputs: &mut [&mut [f32]],
        midi_inputs: &[&[MidiEvent]],
        _midi_outputs: &mut [&mut Vec<MidiEvent>],
        _sample_rate: u32,
    ) {
        // Process MIDI events from input (allocate notes to voices)
        if !midi_inputs.is_empty() {
            for event in midi_inputs[0] {
                self.handle_midi(event);
            }
        }

        if outputs.is_empty() {
            return;
        }

        let output = &mut outputs[0];
        let output_len = output.len();

        // Process each active voice and mix (only up to voice_count)
        for voice_idx in 0..self.voice_count {
            let voice_state = &mut self.voices[voice_idx];
            if voice_state.active {
                voice_state.age = voice_state.age.saturating_add(1);

                // Get pending MIDI events for this voice
                let midi_events = std::mem::take(&mut voice_state.pending_events);

                // IMPORTANT: Process only the slice of mix_buffer that matches output size
                // This prevents phase discontinuities in oscillators
                let mix_slice = &mut self.mix_buffer[..output_len];
                mix_slice.fill(0.0);

                // Process this voice's graph with its MIDI events
                // Note: playback_time is 0.0 since voice allocator doesn't track time
                self.voice_instances[voice_idx].process(mix_slice, &midi_events, 0.0);

                // Mix into output (accumulate)
                for (i, sample) in mix_slice.iter().enumerate() {
                    output[i] += sample;
                }
            }
        }

        // Apply normalization to prevent clipping (divide by active voice count)
        let active_count = self.voices[..self.voice_count].iter().filter(|v| v.active).count();
        if active_count > 1 {
            let scale = 1.0 / (active_count as f32).sqrt(); // Use sqrt for better loudness perception
            for sample in output.iter_mut() {
                *sample *= scale;
            }
        }
    }

    fn reset(&mut self) {
        for voice in &mut self.voices {
            voice.active = false;
            voice.pending_events.clear();
        }
        for graph in &mut self.voice_instances {
            graph.reset();
        }
        self.template_graph.reset();
    }

    fn node_type(&self) -> &str {
        "VoiceAllocator"
    }

    fn name(&self) -> &str {
        &self.name
    }

    fn clone_node(&self) -> Box<dyn AudioNode> {
        // Clone creates a new VoiceAllocator with the same template graph
        // Voice instances will be rebuilt when rebuild_voices() is called
        Box::new(Self {
            name: self.name.clone(),
            template_graph: self.template_graph.clone_graph(),
            voice_instances: self.voice_instances.iter().map(|g| g.clone_graph()).collect(),
            voices: std::array::from_fn(|_| VoiceState::new()), // Reset voices
            voice_count: self.voice_count,
            mix_buffer: vec![0.0; self.mix_buffer.len()],
            inputs: self.inputs.clone(),
            outputs: self.outputs.clone(),
            parameters: self.parameters.clone(),
        })
    }

    fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
        self
    }

    fn as_any(&self) -> &dyn std::any::Any {
        self
    }
}
