/// Audio recording system for capturing microphone input
use crate::audio::{ClipId, MidiClipId, TrackId};
use crate::io::{WavWriter, WaveformPeak};
use std::collections::HashMap;
use std::path::PathBuf;

/// State of an active recording session
pub struct RecordingState {
    /// Track being recorded to
    pub track_id: TrackId,
    /// Clip ID for the intermediate clip
    pub clip_id: ClipId,
    /// Path to temporary WAV file
    pub temp_file_path: PathBuf,
    /// WAV file writer (only used at finalization, not during recording)
    pub writer: WavWriter,
    /// Sample rate of recording
    pub sample_rate: u32,
    /// Number of channels
    pub channels: u32,
    /// Timeline start position in seconds
    pub start_time: f64,
    /// Total frames recorded
    pub frames_written: usize,
    /// Whether recording is currently paused
    pub paused: bool,
    /// Number of samples remaining to skip (to discard stale buffer data)
    pub samples_to_skip: usize,
    /// Waveform peaks generated incrementally during recording
    pub waveform: Vec<WaveformPeak>,
    /// Temporary buffer for collecting samples for next waveform peak
    pub waveform_buffer: Vec<f32>,
    /// Number of frames per waveform peak
    pub frames_per_peak: usize,
    /// All recorded audio data accumulated in memory (written to disk at finalization)
    pub audio_data: Vec<f32>,
}

impl RecordingState {
    /// Create a new recording state
    pub fn new(
        track_id: TrackId,
        clip_id: ClipId,
        temp_file_path: PathBuf,
        writer: WavWriter,
        sample_rate: u32,
        channels: u32,
        start_time: f64,
        _flush_interval_seconds: f64, // No longer used - kept for API compatibility
    ) -> Self {
        // Calculate frames per waveform peak
        // Target ~300 peaks per second with minimum 1000 samples per peak
        let target_peaks_per_second = 300;
        let frames_per_peak = (sample_rate / target_peaks_per_second).max(1000) as usize;

        Self {
            track_id,
            clip_id,
            temp_file_path,
            writer,
            sample_rate,
            channels,
            start_time,
            frames_written: 0,
            paused: false,
            samples_to_skip: 0, // Will be set by engine when it knows buffer size
            waveform: Vec::new(),
            waveform_buffer: Vec::new(),
            frames_per_peak,
            audio_data: Vec::new(),
        }
    }

    /// Add samples to the accumulation buffer
    /// Returns true if a flush occurred
    pub fn add_samples(&mut self, samples: &[f32]) -> Result<bool, std::io::Error> {
        if self.paused {
            return Ok(false);
        }

        // Determine which samples to process
        let samples_to_process = if self.samples_to_skip > 0 {
            let to_skip = self.samples_to_skip.min(samples.len());
            self.samples_to_skip -= to_skip;

            if to_skip == samples.len() {
                // Skip entire batch
                return Ok(false);
            }

            // Skip partial batch and process the rest
            &samples[to_skip..]
        } else {
            samples
        };

        // Add to audio data (accumulate in memory - disk write happens at finalization only)
        self.audio_data.extend_from_slice(samples_to_process);

        // Add to waveform buffer and generate peaks incrementally
        self.waveform_buffer.extend_from_slice(samples_to_process);
        self.generate_waveform_peaks();

        // Track frames for duration calculation (no disk I/O in audio callback!)
        let frames_added = samples_to_process.len() / self.channels as usize;
        self.frames_written += frames_added;

        Ok(false)
    }

    /// Generate waveform peaks from accumulated samples
    /// This is called incrementally as samples arrive
    fn generate_waveform_peaks(&mut self) {
        let samples_per_peak = self.frames_per_peak * self.channels as usize;

        while self.waveform_buffer.len() >= samples_per_peak {
            let mut min = 0.0f32;
            let mut max = 0.0f32;

            // Scan all samples for this peak
            for sample in &self.waveform_buffer[..samples_per_peak] {
                min = min.min(*sample);
                max = max.max(*sample);
            }

            self.waveform.push(WaveformPeak { min, max });

            // Remove processed samples from waveform buffer
            self.waveform_buffer.drain(..samples_per_peak);
        }
    }

    /// Get current recording duration in seconds
    pub fn duration(&self) -> f64 {
        self.frames_written as f64 / self.sample_rate as f64
    }

    /// Finalize the recording and return the temp file path, waveform, and audio data
    pub fn finalize(mut self) -> Result<(PathBuf, Vec<WaveformPeak>, Vec<f32>), std::io::Error> {
        // Write all audio data to disk at once (outside audio callback - safe to do I/O)
        if !self.audio_data.is_empty() {
            self.writer.write_samples(&self.audio_data)?;
        }

        // Generate final waveform peak from any remaining samples
        if !self.waveform_buffer.is_empty() {
            let mut min = 0.0f32;
            let mut max = 0.0f32;

            for sample in &self.waveform_buffer {
                min = min.min(*sample);
                max = max.max(*sample);
            }

            self.waveform.push(WaveformPeak { min, max });
        }

        // Finalize the WAV file
        self.writer.finalize()?;

        Ok((self.temp_file_path, self.waveform, self.audio_data))
    }

    /// Pause recording
    pub fn pause(&mut self) {
        self.paused = true;
    }

    /// Resume recording
    pub fn resume(&mut self) {
        self.paused = false;
    }
}

/// Active MIDI note waiting for its noteOff event
#[derive(Debug, Clone)]
struct ActiveMidiNote {
    /// MIDI note number (0-127)
    note: u8,
    /// Velocity (0-127)
    velocity: u8,
    /// Absolute time when note started (seconds)
    start_time: f64,
}

/// State of an active MIDI recording session
pub struct MidiRecordingState {
    /// Track being recorded to
    pub track_id: TrackId,
    /// MIDI clip ID
    pub clip_id: MidiClipId,
    /// Timeline start position in seconds
    pub start_time: f64,
    /// Currently active notes (noteOn without matching noteOff)
    /// Maps note number to ActiveMidiNote
    active_notes: HashMap<u8, ActiveMidiNote>,
    /// Completed notes ready to be added to clip
    /// Format: (time_offset, note, velocity, duration)
    pub completed_notes: Vec<(f64, u8, u8, f64)>,
}

impl MidiRecordingState {
    /// Create a new MIDI recording state
    pub fn new(track_id: TrackId, clip_id: MidiClipId, start_time: f64) -> Self {
        Self {
            track_id,
            clip_id,
            start_time,
            active_notes: HashMap::new(),
            completed_notes: Vec::new(),
        }
    }

    /// Handle a MIDI note on event
    pub fn note_on(&mut self, note: u8, velocity: u8, absolute_time: f64) {
        // Store this note as active
        self.active_notes.insert(note, ActiveMidiNote {
            note,
            velocity,
            start_time: absolute_time,
        });
    }

    /// Handle a MIDI note off event
    pub fn note_off(&mut self, note: u8, absolute_time: f64) {
        // Find the matching noteOn
        if let Some(active_note) = self.active_notes.remove(&note) {
            // Calculate relative time offset and duration
            let time_offset = active_note.start_time - self.start_time;
            let duration = absolute_time - active_note.start_time;

            eprintln!("[MIDI_RECORDING_STATE] Completing note {}: note_start={:.3}s, note_end={:.3}s, recording_start={:.3}s, time_offset={:.3}s, duration={:.3}s",
                      note, active_note.start_time, absolute_time, self.start_time, time_offset, duration);

            // Add to completed notes
            self.completed_notes.push((
                time_offset,
                active_note.note,
                active_note.velocity,
                duration,
            ));
        }
        // If no matching noteOn found, ignore the noteOff
    }

    /// Get all completed notes
    pub fn get_notes(&self) -> &[(f64, u8, u8, f64)] {
        &self.completed_notes
    }

    /// Get the number of completed notes
    pub fn note_count(&self) -> usize {
        self.completed_notes.len()
    }

    /// Get the note numbers of all currently held (active) notes
    pub fn active_note_numbers(&self) -> Vec<u8> {
        self.active_notes.keys().copied().collect()
    }

    /// Close out all active notes at the given time
    /// This should be called when stopping recording to end any held notes
    pub fn close_active_notes(&mut self, end_time: f64) {
        // Collect all active notes and close them
        let active_notes: Vec<_> = self.active_notes.drain().collect();

        for (_note_num, active_note) in active_notes {
            // Calculate relative time offset and duration
            let time_offset = active_note.start_time - self.start_time;
            let duration = end_time - active_note.start_time;

            // Add to completed notes
            self.completed_notes.push((
                time_offset,
                active_note.note,
                active_note.velocity,
                duration,
            ));
        }
    }
}
