//! Lissajous XY oscilloscope widget using braille characters.

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

thread_local! {
    static PATTERNS: RefCell<Vec<u8>> = const { RefCell::new(Vec::new()) };
    static TRAIL: RefCell<TrailState> = const { RefCell::new(TrailState { fine_w: 0, fine_h: 0, heat: Vec::new() }) };
}

struct TrailState {
    fine_w: usize,
    fine_h: usize,
    heat: Vec<f32>,
}

/// XY oscilloscope plotting left vs right channels as a Lissajous curve.
pub struct Lissajous<'a> {
    left: &'a [f32],
    right: &'a [f32],
    color: Option<Color>,
    gain: f32,
    trails: bool,
}

impl<'a> Lissajous<'a> {
    pub fn new(left: &'a [f32], right: &'a [f32]) -> Self {
        Self {
            left,
            right,
            color: None,
            gain: 1.0,
            trails: false,
        }
    }

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

    pub fn color(mut self, c: Color) -> Self {
        self.color = Some(c);
        self
    }

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

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

        if self.trails {
            self.render_trails(area, buf);
        } else {
            self.render_normal(area, buf);
        }
    }
}

impl Lissajous<'_> {
    fn render_normal(self, area: Rect, buf: &mut Buffer) {
        let color = self.color.unwrap_or_else(|| theme::get().meter.low);
        let width = area.width as usize;
        let height = area.height as usize;
        let fine_width = width * 2;
        let fine_height = height * 4;
        let len = self.left.len().min(self.right.len());

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

            for i in 0..len {
                let l = (self.left[i] * self.gain).clamp(-1.0, 1.0);
                let r = (self.right[i] * self.gain).clamp(-1.0, 1.0);

                let fine_x = ((r + 1.0) * 0.5 * (fine_width - 1) as f32).round() as usize;
                let fine_y = ((1.0 - l) * 0.5 * (fine_height - 1) as f32).round() as usize;
                let fine_x = fine_x.min(fine_width - 1);
                let fine_y = fine_y.min(fine_height - 1);

                let char_x = fine_x / 2;
                let char_y = fine_y / 4;
                let dot_x = fine_x % 2;
                let dot_y = fine_y % 4;

                patterns[char_y * width + char_x] |= braille_bit(dot_x, dot_y);
            }

            for cy in 0..height {
                for cx in 0..width {
                    let pattern = patterns[cy * width + cx];
                    if pattern != 0 {
                        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 render_trails(self, area: Rect, buf: &mut Buffer) {
        let theme = theme::get();
        let width = area.width as usize;
        let height = area.height as usize;
        let fine_w = width * 2;
        let fine_h = height * 4;
        let len = self.left.len().min(self.right.len());

        TRAIL.with(|t| {
            let mut trail = t.borrow_mut();

            // Reset if dimensions changed
            if trail.fine_w != fine_w || trail.fine_h != fine_h {
                trail.fine_w = fine_w;
                trail.fine_h = fine_h;
                trail.heat.clear();
                trail.heat.resize(fine_w * fine_h, 0.0);
            }

            // Decay existing heat
            for h in trail.heat.iter_mut() {
                *h *= 0.85;
            }

            // Plot new sample points
            for i in 0..len {
                let l = (self.left[i] * self.gain).clamp(-1.0, 1.0);
                let r = (self.right[i] * self.gain).clamp(-1.0, 1.0);

                let fx = ((r + 1.0) * 0.5 * (fine_w - 1) as f32).round() as usize;
                let fy = ((1.0 - l) * 0.5 * (fine_h - 1) as f32).round() as usize;
                let fx = fx.min(fine_w - 1);
                let fy = fy.min(fine_h - 1);

                trail.heat[fy * fine_w + fx] = 1.0;
            }

            // Convert heat map to braille
            PATTERNS.with(|p| {
                let mut patterns = p.borrow_mut();
                patterns.clear();
                patterns.resize(width * height, 0);

                // Track brightest color per cell
                let mut colors: Vec<Option<Color>> = vec![None; width * height];

                for fy in 0..fine_h {
                    for fx in 0..fine_w {
                        let h = trail.heat[fy * fine_w + fx];
                        if h < 0.05 {
                            continue;
                        }

                        let cx = fx / 2;
                        let cy = fy / 4;
                        let dx = fx % 2;
                        let dy = fy % 4;

                        let idx = cy * width + cx;
                        patterns[idx] |= braille_bit(dx, dy);

                        let dot_color = if h > 0.7 {
                            theme.meter.high
                        } else if h > 0.25 {
                            theme.meter.mid
                        } else {
                            theme.meter.low
                        };

                        let replace = match colors[idx] {
                            None => true,
                            Some(cur) => {
                                rank_color(dot_color, &theme) > rank_color(cur, &theme)
                            }
                        };
                        if replace {
                            colors[idx] = Some(dot_color);
                        }
                    }
                }

                for cy in 0..height {
                    for cx in 0..width {
                        let idx = cy * width + cx;
                        let pattern = patterns[idx];
                        if pattern != 0 {
                            let ch = char::from_u32(0x2800 + pattern as u32).unwrap_or(' ');
                            let color = colors[idx].unwrap_or(theme.meter.low);
                            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!(),
    }
}

fn rank_color(c: Color, theme: &crate::theme::ThemeColors) -> u8 {
    if c == theme.meter.high { 2 }
    else if c == theme.meter.mid { 1 }
    else { 0 }
}
