use std::{num::NonZero, time::Duration};

use rodio::{ChannelCount, SampleRate, Source};

const RATE: SampleRate = NonZero::new(44100).unwrap();

/// A segment in a [`Sequence`]: one tone with a fixed frequency and duration.
#[derive(Clone, Copy)]
pub struct SequenceSegment {
    /// Frequency in Hz.
    pub frequency: f32,
    /// Duration in seconds.
    pub duration: f32,
}

/// Multi-segment tone sequence with per-segment ADSR-style envelopes.
///
/// Plays a series of tones back-to-back, each with smooth attack/decay ramps
/// to avoid clicks. Useful for building sound effects like pickups, penalties,
/// menu bleeps, and other game audio.
///
/// # Example
/// ```
/// use game_audio::Sequence;
///
/// let pickup = Sequence::new(&[(800.0, 0.04), (1100.0, 0.06)]);
/// let penalty = Sequence::new(&[(250.0, 0.1), (180.0, 0.12), (120.0, 0.15)]);
/// ```
pub struct Sequence {
    segments: Vec<SequenceSegment>,
    current: usize,
    sample_index: u32,
    sample_rate: u32,
    volume: f32,
}

impl Sequence {
    /// Creates a sequence from `(frequency_hz, duration_seconds)` pairs.
    pub fn new(tones: &[(f32, f32)]) -> Self {
        Self {
            segments: tones
                .iter()
                .map(|&(frequency, duration)| SequenceSegment {
                    frequency,
                    duration,
                })
                .collect(),
            current: 0,
            sample_index: 0,
            sample_rate: 44100,
            volume: 0.25,
        }
    }

    /// Creates a sequence from pre-built segments.
    pub fn from_segments(segments: &[SequenceSegment]) -> Self {
        Self {
            segments: segments.to_vec(),
            current: 0,
            sample_index: 0,
            sample_rate: 44100,
            volume: 0.25,
        }
    }

    /// Sets the output volume multiplier (default 0.25).
    pub fn with_volume(mut self, volume: f32) -> Self {
        self.volume = volume;
        self
    }

    fn total_samples(&self) -> u32 {
        self.segments
            .iter()
            .map(|segment| (self.sample_rate as f32 * segment.duration) as u32)
            .sum()
    }
}

impl Iterator for Sequence {
    type Item = f32;

    fn next(&mut self) -> Option<f32> {
        let segment = self.segments.get(self.current)?;
        let segment_samples = (self.sample_rate as f32 * segment.duration) as u32;

        if self.sample_index >= segment_samples {
            self.current += 1;
            self.sample_index = 0;
            return self.next();
        }

        let time = self.sample_index as f32 / self.sample_rate as f32;
        let wave = (std::f32::consts::TAU * segment.frequency * time).sin();

        let attack = 200.min(segment_samples / 4);
        let decay = (segment_samples / 3).max(1);
        let envelope = if self.sample_index < attack {
            self.sample_index as f32 / attack as f32
        } else if self.sample_index > segment_samples - decay {
            (segment_samples - self.sample_index) as f32 / decay as f32
        } else {
            1.0
        };

        self.sample_index += 1;
        Some(wave * envelope * self.volume)
    }
}

impl Source for Sequence {
    fn current_span_len(&self) -> Option<usize> {
        None
    }

    fn channels(&self) -> ChannelCount {
        ChannelCount::MIN
    }

    fn sample_rate(&self) -> SampleRate {
        RATE
    }

    fn total_duration(&self) -> Option<Duration> {
        Some(Duration::from_secs_f32(
            self.total_samples() as f32 / self.sample_rate as f32,
        ))
    }
}
