//! 32-band frequency spectrum display with optional peak hold.

use crate::theme;
use ratatui::buffer::Buffer;
use ratatui::layout::Rect;
use ratatui::style::Color;
use ratatui::widgets::Widget;
use std::cell::RefCell;

const BLOCKS: [char; 8] = ['\u{2581}', '\u{2582}', '\u{2583}', '\u{2584}', '\u{2585}', '\u{2586}', '\u{2587}', '\u{2588}'];

#[derive(Clone, Copy, PartialEq, Eq, Default)]
pub enum SpectrumStyle {
    #[default]
    Bars,
    Line,
    Filled,
}

thread_local! {
    static PEAKS: RefCell<[f32; 32]> = const { RefCell::new([0.0; 32]) };
    static PATTERNS: RefCell<Vec<u8>> = const { RefCell::new(Vec::new()) };
}

/// 32-band spectrum analyzer using block characters.
pub struct Spectrum<'a> {
    data: &'a [f32; 32],
    gain: f32,
    style: SpectrumStyle,
    peaks: bool,
}

impl<'a> Spectrum<'a> {
    pub fn new(data: &'a [f32; 32]) -> Self {
        Self {
            data,
            gain: 1.0,
            style: SpectrumStyle::Bars,
            peaks: false,
        }
    }

    pub fn gain(mut self, g: f32) -> Self {
        self.gain = g;
        self
    }

    pub fn style(mut self, s: SpectrumStyle) -> Self {
        self.style = s;
        self
    }

    pub fn peaks(mut self, enabled: bool) -> Self {
        self.peaks = enabled;
        self
    }
}

impl Widget for Spectrum<'_> {
    fn render(self, area: Rect, buf: &mut Buffer) {
        if area.width == 0 || area.height == 0 {
            return;
        }

        // Update peak hold state
        let peak_values = if self.peaks {
            Some(PEAKS.with(|p| {
                let mut peaks = p.borrow_mut();
                for (i, &mag) in self.data.iter().enumerate() {
                    let v = (mag * self.gain).min(1.0);
                    if v >= peaks[i] {
                        peaks[i] = v;
                    } else {
                        peaks[i] = (peaks[i] - 0.02).max(v);
                    }
                }
                *peaks
            }))
        } else {
            None
        };

        match self.style {
            SpectrumStyle::Bars => render_bars(self.data, area, buf, self.gain, peak_values.as_ref()),
            SpectrumStyle::Line => render_braille(self.data, area, buf, self.gain, false, peak_values.as_ref()),
            SpectrumStyle::Filled => render_braille(self.data, area, buf, self.gain, true, peak_values.as_ref()),
        }
    }
}

fn band_color(ratio: f32, colors: &theme::ThemeColors) -> Color {
    if ratio < 0.33 {
        Color::Rgb(colors.meter.low_rgb.0, colors.meter.low_rgb.1, colors.meter.low_rgb.2)
    } else if ratio < 0.66 {
        Color::Rgb(colors.meter.mid_rgb.0, colors.meter.mid_rgb.1, colors.meter.mid_rgb.2)
    } else {
        Color::Rgb(colors.meter.high_rgb.0, colors.meter.high_rgb.1, colors.meter.high_rgb.2)
    }
}

fn render_bars(data: &[f32; 32], area: Rect, buf: &mut Buffer, gain: f32, peaks: Option<&[f32; 32]>) {
    let colors = theme::get();
    let height = area.height as f32;
    let base = area.width as usize / 32;
    let remainder = area.width as usize % 32;
    if base == 0 && remainder == 0 {
        return;
    }

    let mut x_start = area.x;
    for (band, &mag) in data.iter().enumerate() {
        let w = base + if band < remainder { 1 } else { 0 };
        if w == 0 {
            continue;
        }
        let bar_height = (mag * gain).min(1.0) * height;
        let full_cells = bar_height as usize;
        let frac = bar_height - full_cells as f32;
        let frac_idx = (frac * 8.0) as usize;

        // Peak hold row
        let peak_row = peaks.map(|p| {
            let ph = p[band] * height;
            let row = (height - ph).max(0.0) as usize;
            row.min(area.height as usize - 1)
        });

        for row in 0..area.height as usize {
            let y = area.y + area.height - 1 - row as u16;
            let ratio = row as f32 / area.height as f32;
            let color = band_color(ratio, &colors);

            for dx in 0..w as u16 {
                let x = x_start + dx;
                if row < full_cells {
                    buf[(x, y)].set_char(BLOCKS[7]).set_fg(color);
                } else if row == full_cells && frac_idx > 0 {
                    buf[(x, y)].set_char(BLOCKS[frac_idx - 1]).set_fg(color);
                } else if let Some(pr) = peak_row {
                    // peak_row is from top (0 = top), row is from bottom
                    let from_top = area.height as usize - 1 - row;
                    if from_top == pr {
                        buf[(x, y)].set_char('─').set_fg(colors.meter.high);
                    }
                }
            }
        }
        x_start += w as u16;
    }
}

fn render_braille(
    data: &[f32; 32],
    area: Rect,
    buf: &mut Buffer,
    gain: f32,
    filled: bool,
    peaks: Option<&[f32; 32]>,
) {
    let colors = theme::get();
    let width = area.width as usize;
    let height = area.height as usize;
    let fine_w = width * 2;
    let fine_h = height * 4;

    PATTERNS.with(|p| {
        let mut patterns = p.borrow_mut();
        patterns.clear();
        patterns.resize(width * height, 0);

        // Interpolate 32 bands across fine_w columns
        for fx in 0..fine_w {
            let band_f = fx as f32 * 31.0 / (fine_w - 1).max(1) as f32;
            let lo = band_f as usize;
            let hi = (lo + 1).min(31);
            let t = band_f - lo as f32;
            let mag = ((data[lo] * (1.0 - t) + data[hi] * t) * gain).min(1.0);
            let fy = ((1.0 - mag) * (fine_h - 1) as f32).round() as usize;
            let fy = fy.min(fine_h - 1);

            if filled {
                for y in fy..fine_h {
                    let cy = y / 4;
                    let dy = y % 4;
                    let cx = fx / 2;
                    let dx = fx % 2;
                    patterns[cy * width + cx] |= braille_bit(dx, dy);
                }
            } else {
                let cy = fy / 4;
                let dy = fy % 4;
                let cx = fx / 2;
                let dx = fx % 2;
                patterns[cy * width + cx] |= braille_bit(dx, dy);
            }

            // Peak dots
            if let Some(pk) = peaks {
                let pv = (pk[lo] * (1.0 - t) + pk[hi] * t).min(1.0);
                let py = ((1.0 - pv) * (fine_h - 1) as f32).round() as usize;
                let py = py.min(fine_h - 1);
                let cy = py / 4;
                let dy = py % 4;
                let cx = fx / 2;
                let dx = fx % 2;
                patterns[cy * width + cx] |= braille_bit(dx, dy);
            }
        }

        for cy in 0..height {
            for cx in 0..width {
                let pattern = patterns[cy * width + cx];
                if pattern != 0 {
                    let ratio = 1.0 - (cy as f32 / height as f32);
                    let color = band_color(ratio, &colors);
                    let ch = char::from_u32(0x2800 + pattern as u32).unwrap_or(' ');
                    buf[(area.x + cx as u16, area.y + cy as u16)]
                        .set_char(ch)
                        .set_fg(color);
                }
            }
        }
    });
}

fn braille_bit(dot_x: usize, dot_y: usize) -> u8 {
    match (dot_x, dot_y) {
        (0, 0) => 0x01,
        (0, 1) => 0x02,
        (0, 2) => 0x04,
        (0, 3) => 0x40,
        (1, 0) => 0x08,
        (1, 1) => 0x10,
        (1, 2) => 0x20,
        (1, 3) => 0x80,
        _ => unreachable!(),
    }
}
