use crate::*;

pub struct Track {
    audio_data: AudioData,
    current_state: TrackState,
    next_state: TrackState,
    volume: f32,
}

#[derive(Debug, Clone, PartialEq)]
pub enum TrackState {
    Empty,
    Idle,
    Playing,
    Recording {
        volume: f32,
    },
    RecordingAutoStop {
        volume: f32,
        target_samples: usize,
        sync_offset: usize,
    },
}

impl Track {
    pub fn new() -> Self {
        Self {
            audio_data: AudioData::new_empty(),
            current_state: TrackState::Empty,
            next_state: TrackState::Empty,
            volume: 1.0,
        }
    }

    pub fn is_recording(&self) -> bool {
        matches!(
            self.current_state,
            TrackState::Recording { .. } | TrackState::RecordingAutoStop { .. }
        )
    }

    pub fn is_playing(&self) -> bool {
        matches!(self.current_state, TrackState::Playing)
    }

    pub fn is_idle(&self) -> bool {
        !self.is_recording() && !self.is_playing()
    }

    pub fn len(&self) -> usize {
        self.audio_data.len()
    }

    pub fn set_volume(&mut self, new_volume: f32, controllers: &TrackControllers) -> Result<()> {
        let new_volume = new_volume.clamp(0.0, 1.0);

        match &mut self.current_state {
            TrackState::Recording { volume } => {
                *volume = new_volume; // Update recording volume only
                controllers.send_volume_update(new_volume)?;
            }
            TrackState::RecordingAutoStop { volume, .. } => {
                *volume = new_volume; // Update recording volume only
                controllers.send_volume_update(new_volume)?;
            }
            _ => {
                self.volume = new_volume; // Update committed volume
                controllers.update_track_volume(new_volume)?; // Update State
                controllers.send_volume_update(new_volume)?; // Update GUI
            }
        }
        Ok(())
    }

    pub fn record(&mut self, last_volume_setting: f32) {
        self.next_state = TrackState::Recording {
            volume: last_volume_setting,
        };
    }

    pub fn record_auto_stop(
        &mut self,
        target_samples: usize,
        sync_offset: usize,
        last_volume_setting: f32,
    ) {
        self.next_state = TrackState::RecordingAutoStop {
            volume: last_volume_setting,
            target_samples,
            sync_offset,
        };
    }

    pub fn play(&mut self) {
        self.next_state = TrackState::Playing;
    }

    pub fn stop(&mut self) {
        self.next_state = TrackState::Idle;
    }

    pub fn clear(&mut self) {
        self.next_state = TrackState::Empty;
    }

    pub fn set_consolidated_buffer(&mut self, buffer: Box<[f32]>) -> Result<()> {
        self.audio_data.set_consolidated_buffer(buffer)
    }

    pub fn is_empty(&self) -> bool {
        matches!(self.current_state, TrackState::Empty) && self.audio_data.is_empty()
    }

    pub fn handle_xrun(
        &mut self,
        beat_in_missed: Option<u32>,
        missed_frames: usize,
        chunk_factory: &mut impl ChunkFactory,
        controllers: &TrackControllers,
    ) -> Result<()> {
        match beat_in_missed {
            None => {
                if self.is_recording() {
                    self.audio_data
                        .append_silence(missed_frames as _, chunk_factory)
                } else {
                    Ok(())
                }
            }
            Some(beat_offset) => {
                // Insert silence before beat with current state
                if beat_offset > 0 && self.is_recording() {
                    self.audio_data
                        .append_silence(beat_offset as _, chunk_factory)?;
                }
                // Apply state transition at beat boundary
                self.apply_state_transition(chunk_factory, controllers)?;
                // Insert silence after beat with new state
                let frames_after_beat = missed_frames - beat_offset as usize;
                if frames_after_beat > 0 && self.is_recording() {
                    self.audio_data
                        .append_silence(frames_after_beat, chunk_factory)?;
                }
                Ok(())
            }
        }
    }

    /// Audio processing
    pub fn process(
        &mut self,
        playback_position: usize,
        beat_in_buffer: Option<u32>,
        input_buffer: &[f32],
        output_buffer: &mut [f32],
        chunk_factory: &mut impl ChunkFactory,
        controllers: &TrackControllers,
    ) -> Result<()> {
        match beat_in_buffer {
            None => {
                // No beat in this buffer - process entire buffer with current state
                self.process_audio_range(
                    input_buffer,
                    output_buffer,
                    playback_position,
                    chunk_factory,
                )
            }
            Some(beat_index_in_buffer) => {
                // Process samples before beat with current state
                if beat_index_in_buffer > 0 {
                    self.process_audio_range(
                        &input_buffer[..beat_index_in_buffer as _],
                        &mut output_buffer[..beat_index_in_buffer as _],
                        playback_position,
                        chunk_factory,
                    )?;
                }

                // Apply state transition at beat boundary
                self.apply_state_transition(chunk_factory, controllers)?;

                // Process samples after beat with new current state
                if (beat_index_in_buffer as usize) < output_buffer.len() {
                    // Calculate position after beat for remaining samples
                    let mut post_beat_position = playback_position + beat_index_in_buffer as usize;
                    if self.audio_data.len() > 0 {
                        post_beat_position %= self.audio_data.len();
                    }

                    self.process_audio_range(
                        &input_buffer[beat_index_in_buffer as _..],
                        &mut output_buffer[beat_index_in_buffer as _..],
                        post_beat_position,
                        chunk_factory,
                    )?;
                }

                Ok(())
            }
        }
    }

    fn process_audio_range<F: ChunkFactory>(
        &mut self,
        input_buffer: &[f32],
        output_buffer: &mut [f32],
        playback_position: usize,
        chunk_factory: &mut F,
    ) -> Result<()> {
        if output_buffer.is_empty() {
            return Ok(());
        }

        match &mut self.current_state {
            TrackState::Empty | TrackState::Idle => {
                output_buffer.fill(0.0);
            }
            TrackState::Recording { .. } => {
                self.audio_data
                    .append_samples(input_buffer, chunk_factory)?;
                output_buffer.fill(0.0);
            }
            TrackState::RecordingAutoStop { target_samples, .. } => {
                let current_length = self.audio_data.len();

                if current_length < *target_samples {
                    let samples_needed = *target_samples - current_length;
                    let samples_to_append = input_buffer.len().min(samples_needed);

                    if samples_to_append > 0 {
                        self.audio_data
                            .append_samples(&input_buffer[..samples_to_append], chunk_factory)?;
                    }

                    // Assert: auto-stop target should never be exceeded mid-buffer
                    assert!(
    self.audio_data.len() <= *target_samples,
    "Auto-stop recording target exceeded mid-buffer - this indicates incorrect usage. \
     Target: {}, actual: {}", *target_samples, self.audio_data.len()
);
                }

                output_buffer.fill(0.0);
            }
            TrackState::Playing => {
                self.audio_data.copy_samples_to_output(
                    output_buffer,
                    playback_position,
                    self.volume,
                )?;
            }
        }

        Ok(())
    }

    /// Apply state transition from next_state to current_state
    /// Returns true if track should be consolidated and saved
    fn apply_state_transition(
        &mut self,
        chunk_factory: &mut impl ChunkFactory,
        controllers: &TrackControllers,
    ) -> Result<()> {
        // Check for auto-stop recording completion and transition to playing if no other state transition
        if self.current_state == self.next_state {
            if let TrackState::RecordingAutoStop { target_samples, .. } = &self.current_state {
                if self.audio_data.len() >= *target_samples {
                    self.next_state = TrackState::Playing;
                }
            }
        }

        // remember recording before transition
        let was_recording = self.is_recording();
        let recording_volume = match &self.current_state {
            TrackState::Recording { volume } => Some(*volume),
            TrackState::RecordingAutoStop { volume, .. } => Some(*volume),
            _ => None,
        };

        // Handle transitions that require setup
        match (&self.current_state, &self.next_state) {
            (current_state, TrackState::Recording { .. })
                if !matches!(current_state, TrackState::Recording { .. }) =>
            {
                // Starting manual recording - clear previous data and create new unconsolidated data
                self.audio_data = AudioData::new_unconsolidated(chunk_factory, 0)?;
            }
            (current_state, TrackState::RecordingAutoStop { sync_offset, .. })
                if !matches!(current_state, TrackState::RecordingAutoStop { .. }) =>
            {
                // Starting auto-stop recording - clear previous data and create new unconsolidated data with offset
                self.audio_data = AudioData::new_unconsolidated(chunk_factory, *sync_offset)?;
            }
            (_, TrackState::Playing) => {
                // Starting playback - check if we have audio data
                if self.audio_data.is_empty() {
                    // No audio data - transition to Idle instead
                    self.next_state = TrackState::Idle;
                }
            }
            (_, TrackState::Empty) => {
                // Clear operation - remove audio data
                self.audio_data.clear()?;
            }
            _ => {
                // Other transitions don't require special handling
            }
        }

        // Apply the state transition
        if self.current_state != self.next_state {
            controllers.send_state_update(self.next_state.clone())?;
        }
        self.current_state = self.next_state.clone();

        // Handle post-record processing and volume updates
        if was_recording && !self.is_recording() {
            // Recording accepted - commit recording volume to track volume and update state
            if let Some(recording_vol) = recording_volume {
                self.volume = recording_vol;
                controllers.update_track_volume(recording_vol)?;
            }

            let (chunk, sync_offset) = self.audio_data.get_chunk_for_processing()?;
            controllers.send_post_record_request(chunk, sync_offset)?;
        } else if was_recording && self.current_state == TrackState::Empty {
            // Recording cancelled - send OSC with committed volume (Track.volume unchanged)
            controllers.send_volume_update(self.volume)?;
        }

        Ok(())
    }
}

impl Into<osc::TrackState> for TrackState {
    fn into(self) -> osc::TrackState {
        match self {
            TrackState::Empty => osc::TrackState::Empty,
            TrackState::Idle => osc::TrackState::Idle,
            TrackState::Recording { .. } => osc::TrackState::Recording,
            TrackState::RecordingAutoStop { .. } => osc::TrackState::Recording,
            TrackState::Playing => osc::TrackState::Playing,
        }
    }
}
