use crate::font;
use image::RgbaImage;
use image::imageops::FilterType;
use std::collections::HashMap;
use std::sync::LazyLock;

static PALETTE: LazyLock<[[u8; 3]; 256]> = LazyLock::new(build_palette);

fn build_palette() -> [[u8; 3]; 256] {
    let mut p = [[0u8; 3]; 256];
    let basic: [[u8; 3]; 16] = [
        [0, 0, 0],
        [205, 0, 0],
        [0, 205, 0],
        [205, 205, 0],
        [0, 0, 238],
        [205, 0, 205],
        [0, 205, 205],
        [229, 229, 229],
        [127, 127, 127],
        [255, 0, 0],
        [0, 255, 0],
        [255, 255, 0],
        [92, 92, 255],
        [255, 0, 255],
        [0, 255, 255],
        [255, 255, 255],
    ];
    p[..16].copy_from_slice(&basic);
    for i in 0..216 {
        let r = i / 36;
        let g = (i % 36) / 6;
        let b = i % 6;
        let f = |v: usize| {
            if v == 0 { 0 } else { (v * 40 + 55) as u8 }
        };
        p[16 + i] = [f(r), f(g), f(b)];
    }
    for i in 0..24 {
        let v = (i * 10 + 8) as u8;
        p[232 + i] = [v, v, v];
    }
    p
}

const DEFAULT_FG: [u8; 3] = [229, 229, 229];
const DEFAULT_BG: [u8; 3] = [0, 0, 0];

fn color_to_rgb(c: vt100::Color, bold: bool, is_fg: bool) -> [u8; 3] {
    match c {
        vt100::Color::Default => {
            if is_fg {
                DEFAULT_FG
            } else {
                DEFAULT_BG
            }
        }
        vt100::Color::Idx(i) => {
            let idx = if bold && is_fg && i < 8 {
                i as usize + 8
            } else {
                i as usize
            };
            PALETTE[idx]
        }
        vt100::Color::Rgb(r, g, b) => [r, g, b],
    }
}

pub fn render_screen(
    screen: &vt100::Screen,
    show_cursor: bool,
    pad: u32,
    scale_pct: u32,
) -> Vec<u8> {
    let (rows, cols) = screen.size();
    let cell_w = font::glyph_w() + pad;
    let cell_h = font::glyph_h() + pad;
    let src_w = cols as u32 * cell_w;
    let src_h = rows as u32 * cell_h;
    let mut pixels = vec![0u8; (src_w * src_h * 4) as usize];

    // Fill default bg (alpha=255)
    for i in 0..(src_w * src_h) as usize {
        pixels[i * 4 + 3] = 255;
    }

    let (crow, ccol) = screen.cursor_position();
    let cursor_hidden = screen.hide_cursor();

    for row in 0..rows {
        for col in 0..cols {
            let cell = screen.cell(row, col).unwrap();
            let ch = cell.contents().chars().next().unwrap_or(' ');
            let bold = cell.bold();
            let mut fg = color_to_rgb(cell.fgcolor(), bold, true);
            let mut bg = color_to_rgb(cell.bgcolor(), false, false);
            if cell.inverse() {
                std::mem::swap(&mut fg, &mut bg);
            }
            if show_cursor && !cursor_hidden && row == crow && col == ccol {
                std::mem::swap(&mut fg, &mut bg);
            }
            font::blit_glyph(
                &mut pixels, src_w,
                col as u32, row as u32,
                ch, fg, bg, bold, pad,
            );
        }
    }

    if scale_pct == 100 {
        return encode_png(&pixels, src_w, src_h);
    }

    let img = RgbaImage::from_raw(src_w, src_h, pixels)
        .unwrap();
    let dst_w = src_w * scale_pct / 100;
    let dst_h = src_h * scale_pct / 100;
    let filter = if scale_pct > 100 {
        FilterType::Nearest
    } else {
        FilterType::Lanczos3
    };
    let scaled = image::imageops::resize(
        &img, dst_w, dst_h, filter,
    );
    encode_png(scaled.as_raw(), dst_w, dst_h)
}

fn encode_png(pixels: &[u8], w: u32, h: u32) -> Vec<u8> {
    let npx = (w * h) as usize;

    // Count unique colors; bail to RGB early if >256
    let mut palette_map: HashMap<[u8; 3], u8> =
        HashMap::new();
    let mut palette_vec: Vec<u8> = Vec::new();
    let mut use_indexed = true;

    for i in 0..npx {
        let rgb = [
            pixels[i * 4],
            pixels[i * 4 + 1],
            pixels[i * 4 + 2],
        ];
        if !palette_map.contains_key(&rgb) {
            let len = palette_map.len();
            if len >= 256 {
                use_indexed = false;
                break;
            }
            palette_vec.extend_from_slice(&rgb);
            palette_map.insert(rgb, len as u8);
        }
    }

    let mut buf = Vec::new();
    if use_indexed {
        let indices: Vec<u8> = (0..npx)
            .map(|i| {
                let rgb = [
                    pixels[i * 4],
                    pixels[i * 4 + 1],
                    pixels[i * 4 + 2],
                ];
                palette_map[&rgb]
            })
            .collect();
        let mut enc = png::Encoder::new(&mut buf, w, h);
        enc.set_color(png::ColorType::Indexed);
        enc.set_depth(png::BitDepth::Eight);
        enc.set_palette(&palette_vec);
        let mut writer = enc.write_header().unwrap();
        writer.write_image_data(&indices).unwrap();
    } else {
        // Strip alpha, encode as RGB
        let rgb: Vec<u8> = (0..npx)
            .flat_map(|i| {
                let o = i * 4;
                [pixels[o], pixels[o + 1], pixels[o + 2]]
            })
            .collect();
        let mut enc = png::Encoder::new(&mut buf, w, h);
        enc.set_color(png::ColorType::Rgb);
        enc.set_depth(png::BitDepth::Eight);
        let mut writer = enc.write_header().unwrap();
        writer.write_image_data(&rgb).unwrap();
    }
    buf
}

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

    #[test]
    fn render_produces_valid_png() {
        let mut parser = vt100::Parser::new(24, 80, 0);
        parser.process(b"hello world");
        let png = render_screen(parser.screen(), false, 0, 100);
        assert_eq!(&png[..4], &[137, 80, 78, 71]);
        let cursor = std::io::Cursor::new(&png);
        let decoder = png::Decoder::new(cursor);
        let reader = decoder.read_info().unwrap();
        let info = reader.info();
        let gw = crate::font::glyph_w();
        let gh = crate::font::glyph_h();
        assert_eq!(info.width, 80 * gw);
        assert_eq!(info.height, 24 * gh);
    }

    #[test]
    fn bold_text_has_nonzero_pixels() {
        let mut parser = vt100::Parser::new(4, 10, 0);
        // ESC[1m = bold, then "AB"
        parser.process(b"\x1b[1mAB");
        let screen = parser.screen();
        let cell = screen.cell(0, 0).unwrap();
        assert!(cell.bold(), "cell should be bold");
        assert_eq!(
            cell.contents(), "A",
            "cell should contain A",
        );

        let gw = crate::font::glyph_w();
        let gh = crate::font::glyph_h();
        let pad = 1u32;
        let cw = gw + pad;
        let ch = gh + pad;
        let img_w = 10 * cw;
        let img_h = 4 * ch;
        let mut buf = vec![0u8; (img_w * img_h * 4) as usize];
        // Fill alpha
        for i in 0..(img_w * img_h) as usize {
            buf[i * 4 + 3] = 255;
        }

        crate::font::blit_glyph(
            &mut buf, img_w,
            0, 0, 'A',
            [229, 229, 229], [0, 0, 0],
            true, pad,
        );

        // Check that some pixels in the cell are non-zero
        let mut nonzero = 0;
        for dy in 0..ch {
            for dx in 0..cw {
                let off = (dy * img_w + dx) as usize * 4;
                if buf[off] > 0 || buf[off+1] > 0
                    || buf[off+2] > 0
                {
                    nonzero += 1;
                }
            }
        }
        eprintln!("Bold 'A': {nonzero} non-zero pixels");
        assert!(nonzero > 0, "bold glyph should draw pixels");
    }
}
