//! Programmatic rendering of Unicode block elements for the desktop backend.
//!
//! Real terminals render block characters (█, ▀, ▄, quadrants, sextants) as
//! pixel-perfect filled rectangles. The bitmap font backend can't guarantee
//! gap-free fills and lacks sextant glyphs entirely. This wrapper intercepts
//! block element code points and draws them directly on the pixmap, delegating
//! everything else to EmbeddedGraphics.

use ratatui::buffer::Cell;
use ratatui::style::{Color, Modifier};
use rustc_hash::FxHashSet;
use soft_ratatui::{EmbeddedGraphics, RasterBackend, RgbPixmap};

pub struct BlockCharBackend {
    pub inner: EmbeddedGraphics,
}

impl RasterBackend for BlockCharBackend {
    fn draw_cell(
        &mut self,
        x: u16,
        y: u16,
        cell: &Cell,
        always_redraw_list: &mut FxHashSet<(u16, u16)>,
        blinking_fast: bool,
        blinking_slow: bool,
        char_width: usize,
        char_height: usize,
        rgb_pixmap: &mut RgbPixmap,
    ) {
        let cp = cell.symbol().chars().next().unwrap_or(' ') as u32;

        if !is_block_element(cp) {
            self.inner.draw_cell(
                x,
                y,
                cell,
                always_redraw_list,
                blinking_fast,
                blinking_slow,
                char_width,
                char_height,
                rgb_pixmap,
            );
            return;
        }

        let (fg, bg) = resolve_colors(cell, always_redraw_list, x, y, blinking_fast, blinking_slow);
        let px = x as usize * char_width;
        let py = y as usize * char_height;

        fill_rect(rgb_pixmap, px, py, char_width, char_height, bg);
        draw_block_element(rgb_pixmap, cp, px, py, char_width, char_height, fg);
    }
}

// ---------------------------------------------------------------------------
// Block element classification and drawing
// ---------------------------------------------------------------------------

fn is_block_element(cp: u32) -> bool {
    matches!(cp, 0x2580..=0x2590 | 0x2594..=0x259F | 0x1FB00..=0x1FB3B)
}

fn draw_block_element(
    pixmap: &mut RgbPixmap,
    cp: u32,
    px: usize,
    py: usize,
    cw: usize,
    ch: usize,
    fg: [u8; 3],
) {
    match cp {
        0x2580 => fill_rect(pixmap, px, py, cw, ch / 2, fg),
        0x2581..=0x2587 => {
            let n = (cp - 0x2580) as usize;
            let h = ch * n / 8;
            fill_rect(pixmap, px, py + ch - h, cw, h, fg);
        }
        0x2588 => fill_rect(pixmap, px, py, cw, ch, fg),
        0x2589..=0x258F => {
            let n = (0x2590 - cp) as usize;
            fill_rect(pixmap, px, py, cw * n / 8, ch, fg);
        }
        0x2590 => {
            let hw = cw / 2;
            fill_rect(pixmap, px + hw, py, cw - hw, ch, fg);
        }
        0x2594 => fill_rect(pixmap, px, py, cw, (ch / 8).max(1), fg),
        0x2595 => {
            let w = (cw / 8).max(1);
            fill_rect(pixmap, px + cw - w, py, w, ch, fg);
        }
        0x2596..=0x259F => draw_quadrants(pixmap, px, py, cw, ch, fg, cp),
        0x1FB00..=0x1FB3B => draw_sextants(pixmap, px, py, cw, ch, fg, cp),
        _ => unreachable!(),
    }
}

// ---------------------------------------------------------------------------
// Quadrants (U+2596-U+259F): 2x2 grid
// ---------------------------------------------------------------------------

// Bits: 3=UL, 2=UR, 1=LL, 0=LR
const QUADRANT: [u8; 10] = [
    0b0010, // ▖ LL
    0b0001, // ▗ LR
    0b1000, // ▘ UL
    0b1011, // ▙ UL+LL+LR
    0b1001, // ▚ UL+LR
    0b1110, // ▛ UL+UR+LL
    0b1101, // ▜ UL+UR+LR
    0b0100, // ▝ UR
    0b0110, // ▞ UR+LL
    0b0111, // ▟ UR+LL+LR
];

fn draw_quadrants(
    pixmap: &mut RgbPixmap,
    px: usize,
    py: usize,
    cw: usize,
    ch: usize,
    fg: [u8; 3],
    cp: u32,
) {
    let pattern = QUADRANT[(cp - 0x2596) as usize];
    let hw = cw / 2;
    let hh = ch / 2;
    let rw = cw - hw;
    let rh = ch - hh;
    if pattern & 0b1000 != 0 { fill_rect(pixmap, px, py, hw, hh, fg); }
    if pattern & 0b0100 != 0 { fill_rect(pixmap, px + hw, py, rw, hh, fg); }
    if pattern & 0b0010 != 0 { fill_rect(pixmap, px, py + hh, hw, rh, fg); }
    if pattern & 0b0001 != 0 { fill_rect(pixmap, px + hw, py + hh, rw, rh, fg); }
}

// ---------------------------------------------------------------------------
// Sextants (U+1FB00-U+1FB3B): 2x3 grid
// ---------------------------------------------------------------------------

// Bit layout: 0=TL, 1=TR, 2=ML, 3=MR, 4=BL, 5=BR
// The 60 characters encode patterns 1-62, skipping 0 (space), 21 (left half),
// 42 (right half), and 63 (full block) which exist as standard block elements.
fn sextant_pattern(cp: u32) -> u8 {
    let mut p = (cp - 0x1FB00) as u8 + 1;
    if p >= 21 { p += 1; }
    if p >= 42 { p += 1; }
    p
}

fn draw_sextants(
    pixmap: &mut RgbPixmap,
    px: usize,
    py: usize,
    cw: usize,
    ch: usize,
    fg: [u8; 3],
    cp: u32,
) {
    let pattern = sextant_pattern(cp);
    let hw = cw / 2;
    let rw = cw - hw;
    let h0 = ch / 3;
    let h1 = (ch - h0) / 2;
    let h2 = ch - h0 - h1;
    let y1 = py + h0;
    let y2 = y1 + h1;

    if pattern & 0b000001 != 0 { fill_rect(pixmap, px, py, hw, h0, fg); }
    if pattern & 0b000010 != 0 { fill_rect(pixmap, px + hw, py, rw, h0, fg); }
    if pattern & 0b000100 != 0 { fill_rect(pixmap, px, y1, hw, h1, fg); }
    if pattern & 0b001000 != 0 { fill_rect(pixmap, px + hw, y1, rw, h1, fg); }
    if pattern & 0b010000 != 0 { fill_rect(pixmap, px, y2, hw, h2, fg); }
    if pattern & 0b100000 != 0 { fill_rect(pixmap, px + hw, y2, rw, h2, fg); }
}

// ---------------------------------------------------------------------------
// Pixel operations
// ---------------------------------------------------------------------------

fn fill_rect(pixmap: &mut RgbPixmap, x0: usize, y0: usize, w: usize, h: usize, color: [u8; 3]) {
    let pw = pixmap.width;
    let x_end = (x0 + w).min(pw);
    let y_end = (y0 + h).min(pixmap.height);
    let data = &mut pixmap.data;
    for y in y0..y_end {
        let start = 3 * (y * pw + x0);
        let end = 3 * (y * pw + x_end);
        for chunk in data[start..end].chunks_exact_mut(3) {
            chunk.copy_from_slice(&color);
        }
    }
}

// ---------------------------------------------------------------------------
// Color resolution (mirrors soft_ratatui::colors which is private)
// ---------------------------------------------------------------------------

fn resolve_colors(
    cell: &Cell,
    always_redraw_list: &mut FxHashSet<(u16, u16)>,
    x: u16,
    y: u16,
    blinking_fast: bool,
    blinking_slow: bool,
) -> ([u8; 3], [u8; 3]) {
    let mut fg = color_to_rgb(&cell.fg, true);
    let mut bg = color_to_rgb(&cell.bg, false);

    for modifier in cell.modifier.iter() {
        match modifier {
            Modifier::DIM => {
                fg = dim_rgb(fg);
                bg = dim_rgb(bg);
            }
            Modifier::REVERSED => std::mem::swap(&mut fg, &mut bg),
            Modifier::HIDDEN => fg = bg,
            Modifier::SLOW_BLINK => {
                always_redraw_list.insert((x, y));
                if blinking_slow { fg = bg; }
            }
            Modifier::RAPID_BLINK => {
                always_redraw_list.insert((x, y));
                if blinking_fast { fg = bg; }
            }
            _ => {}
        }
    }

    (fg, bg)
}

fn color_to_rgb(color: &Color, is_fg: bool) -> [u8; 3] {
    match color {
        Color::Reset if is_fg => [204, 204, 255],
        Color::Reset => [5, 1, 121],
        Color::Black => [0, 0, 0],
        Color::Red => [139, 0, 0],
        Color::Green => [0, 100, 0],
        Color::Yellow => [255, 215, 0],
        Color::Blue => [0, 0, 139],
        Color::Magenta => [255, 0, 255],
        Color::Cyan => [0, 0, 255],
        Color::Gray => [128, 128, 128],
        Color::DarkGray => [64, 64, 64],
        Color::LightRed => [255, 0, 0],
        Color::LightGreen => [0, 255, 0],
        Color::LightBlue => [173, 216, 230],
        Color::LightYellow => [255, 255, 224],
        Color::LightMagenta => [139, 0, 139],
        Color::LightCyan => [224, 255, 255],
        Color::White => [255, 255, 255],
        Color::Indexed(i) => [i.wrapping_mul(*i), i.wrapping_add(*i), *i],
        Color::Rgb(r, g, b) => [*r, *g, *b],
    }
}

fn dim_rgb(c: [u8; 3]) -> [u8; 3] {
    const F: u32 = 77; // ~30% brightness
    [
        ((c[0] as u32 * F + 127) / 255) as u8,
        ((c[1] as u32 * F + 127) / 255) as u8,
        ((c[2] as u32 * F + 127) / 255) as u8,
    ]
}
