//! Matrix-rain idle screensaver. Kicks in after `IDLE_THRESHOLD_MS` of no
//! keypress on the splash. Each column has its own head-position + drop
//! speed; the head is bright, the trail fades toward dim. Braille glyphs
//! are picked deterministically from `(x, y, seed)` so the pattern is
//! stable per-column (no flicker) but varies across columns.
//!
//! Brand-pure: glyphs only ever render in the palette's mono tier — no
//! green "Matrix" tint. The point isn't to ape the movie; it's to give
//! Kranth a quiet "still alive" texture while the prompt sits idle.

use ratatui::buffer::Buffer;
use ratatui::layout::Rect;
use ratatui::style::{Modifier, Style};

use crate::theme::Palette;

const IDLE_THRESHOLD_MS: u128 = 30_000;
const COLUMN_STRIDE: u16 = 2;
const BRAILLE_POOL: &[char] = &[
    '⠁', '⠃', '⠇', '⠏', '⠟', '⠿', '⡿', '⣿',
    '⠈', '⠘', '⠸', '⠼', '⠾', '⢿', '⣻', '⣟',
    '⠐', '⠰', '⠴', '⠶', '⠷', '⠿', '⡷', '⣧',
];

pub fn is_idle(idle_ms: u128) -> bool {
    idle_ms >= IDLE_THRESHOLD_MS
}

pub struct Rain {
    #[allow(dead_code)] // kept for future area-change detection (lazy rebuild on resize)
    pub area_w: u16,
    pub area_h: u16,
    columns: Vec<Column>,
}

#[derive(Clone, Copy)]
struct Column {
    head_y: f32,
    speed: f32,
    length: u16,
    seed: u64,
}

impl Rain {
    pub fn new(area: Rect) -> Self {
        let n = (area.width / COLUMN_STRIDE).max(1) as usize;
        let mut state = 0x_cbf2_9ce4_8422_2325u64;
        let columns = (0..n)
            .map(|i| {
                xorshift(&mut state);
                let head_y = -((state.wrapping_mul(7) % 40) as f32);
                xorshift(&mut state);
                let speed = 8.0 + (state % 18) as f32; // cells per second
                xorshift(&mut state);
                let length = 6 + (state % 12) as u16;
                xorshift(&mut state);
                let seed = state.wrapping_add(i as u64);
                Column {
                    head_y,
                    speed,
                    length,
                    seed,
                }
            })
            .collect();
        Self {
            area_w: area.width,
            area_h: area.height,
            columns,
        }
    }

    pub fn tick(&mut self, dt_ms: u128) {
        let dt = dt_ms as f32 / 1000.0;
        let height = self.area_h as f32;
        let mut state = 0xdeadbeefcafebabeu64;
        for col in &mut self.columns {
            col.head_y += col.speed * dt;
            if col.head_y > height + col.length as f32 {
                xorshift(&mut state);
                col.head_y = -((state.wrapping_mul(3) % 25) as f32);
                xorshift(&mut state);
                col.speed = 8.0 + (state % 18) as f32;
                xorshift(&mut state);
                col.length = 6 + (state % 12) as u16;
                xorshift(&mut state);
                col.seed = state;
            }
        }
    }

    pub fn render(&self, buf: &mut Buffer, area: Rect, palette: &Palette) {
        for (i, col) in self.columns.iter().enumerate() {
            let x = area.x + (i as u16 * COLUMN_STRIDE);
            if x >= area.x + area.width {
                break;
            }
            let head = col.head_y as i32;
            for t in 0..col.length as i32 {
                let y = head - t;
                if y < area.y as i32 || y >= (area.y + area.height) as i32 {
                    continue;
                }
                let yu = y as u16;
                // Pick a stable glyph per (col, abs_y) so single chars don't
                // flicker frame-to-frame.
                let glyph_idx = (col.seed
                    .wrapping_mul(31)
                    .wrapping_add(yu as u64)
                    .wrapping_mul(2654435761))
                    as usize
                    % BRAILLE_POOL.len();
                let glyph = BRAILLE_POOL[glyph_idx];

                let style = if t == 0 {
                    // Head — bright white.
                    Style::default()
                        .fg(palette.text_primary())
                        .add_modifier(Modifier::BOLD)
                } else if t <= 2 {
                    // Just-behind-head — primary.
                    Style::default().fg(palette.text_primary())
                } else if t <= 6 {
                    // Body — muted.
                    Style::default().fg(palette.text_muted())
                } else {
                    // Tail — ghost / dim.
                    Style::default()
                        .fg(palette.text_ghost())
                        .add_modifier(Modifier::DIM)
                };

                if let Some(cell) = buf.cell_mut((x, yu)) {
                    cell.set_char(glyph);
                    cell.set_style(style);
                }
            }
        }
    }
}

fn xorshift(state: &mut u64) -> u64 {
    let mut s = *state;
    s ^= s << 13;
    s ^= s >> 7;
    s ^= s << 17;
    *state = s;
    s
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn is_idle_thresholds_at_30s() {
        assert!(!is_idle(0));
        assert!(!is_idle(29_999));
        assert!(is_idle(30_000));
        assert!(is_idle(60_000));
    }

    #[test]
    fn rain_columns_scale_with_width() {
        let r1 = Rain::new(Rect::new(0, 0, 20, 10));
        let r2 = Rain::new(Rect::new(0, 0, 100, 10));
        assert!(r2.columns.len() > r1.columns.len());
    }

    #[test]
    fn tick_advances_head() {
        let mut r = Rain::new(Rect::new(0, 0, 20, 50));
        let before = r.columns[0].head_y;
        r.tick(500);
        let after = r.columns[0].head_y;
        assert!(after > before, "tick should advance head_y forward");
    }
}
