//! Pure board renderer from engine state to styled terminal text.

use ratatui::style::{Color, Style};
use ratatui::text::{Line, Span, Text};

use crate::config::Palette;
use crate::engine::board::{BOARD_COLS, BOARD_ROWS, Board};

const FILLED: &str = "██";
const GHOST: &str = "░░";
const EMPTY: &str = "..";

pub fn piece_color(piece: char, palette: &Palette) -> Color {
    palette.piece(piece)
}

/// Renders the bottom `visible_rows` rows of the field.
pub fn render_board(board: &Board, visible_rows: usize, palette: &Palette) -> Text<'static> {
    let rows = visible_rows.clamp(1, BOARD_ROWS);
    let start = BOARD_ROWS - rows;

    let active: std::collections::HashSet<(i32, i32)> = match board.current {
        Some(cur) => cur.cells().into_iter().collect(),
        None => std::collections::HashSet::new(),
    };
    let ghost: std::collections::HashSet<(i32, i32)> = board.ghost_cells().into_iter().collect();

    let mut lines: Vec<Line<'static>> = Vec::with_capacity(rows);
    for r in start..BOARD_ROWS {
        let mut spans: Vec<Span<'static>> = Vec::with_capacity(BOARD_COLS);
        for c in 0..BOARD_COLS {
            let cell = (r as i32, c as i32);
            let (glyph, color) = if active.contains(&cell) {
                let p = board.current.map(|cur| cur.piece).unwrap_or('X');
                (FILLED, piece_color(p, palette))
            } else if let Some(piece) = board.grid[r][c] {
                (FILLED, piece_color(piece, palette))
            } else if ghost.contains(&cell) {
                (GHOST, palette.ghost)
            } else {
                (EMPTY, palette.empty)
            };
            spans.push(Span::styled(glyph, Style::default().fg(color)));
        }
        lines.push(Line::from(spans));
    }
    Text::from(lines)
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::config::Config;
    use crate::engine::board::{BOARD_COLS, Board};
    use crate::engine::piece::PieceState;
    use ratatui::style::Color;

    #[test]
    fn piece_colors_use_config_palette() {
        let palette = Config::default().palette;
        assert_eq!(piece_color('I', &palette), Color::Rgb(0x00, 0xff, 0x66));
        assert_eq!(piece_color('Z', &palette), Color::Rgb(0x4e, 0xd5, 0x6f));
        assert_eq!(piece_color('?', &palette), Color::Rgb(0x7b, 0xd8, 0x8f));
    }

    #[test]
    fn render_shows_active_piece_ghost_and_empty() {
        let palette = Config::default().palette;
        let mut board = Board::new(vec!['O', 'I', 'T', 'S', 'Z', 'J', 'L']);
        board.current = Some(PieceState {
            piece: 'O',
            rot: 0,
            row: 18,
            col: 4,
        });
        let text = render_board(&board, 22, &palette);
        assert_eq!(text.lines.len(), 22);
        for line in &text.lines {
            assert_eq!(line.spans.len(), BOARD_COLS);
            for span in &line.spans {
                assert!(span.content == "██" || span.content == "░░" || span.content == "..");
            }
        }
        let flat: Vec<_> = text.lines.iter().flat_map(|l| l.spans.iter()).collect();
        assert!(
            flat.iter()
                .any(|s| s.content == "██" && s.style.fg == Some(piece_color('O', &palette)))
        );
        assert!(flat.iter().any(|s| s.content == "░░"));
    }
}
