use ascii_agents_core::sprite::blit::{
    blit_frame, blit_frame_outlined, draw_dotted_hline, draw_line, half_block_cells, HalfCell,
};
use ascii_agents_core::sprite::{Frame, Pixel, Rgb, RgbBuffer};

fn px(r: u8, g: u8, b: u8) -> Pixel {
    Some(Rgb(r, g, b))
}
fn t() -> Pixel {
    None
}

#[test]
fn blit_writes_opaque_pixels_and_skips_transparent() {
    let frame = Frame {
        width: 2,
        height: 2,
        pixels: vec![px(10, 0, 0), t(), t(), px(0, 0, 30)],
    };
    let mut buf = RgbBuffer::filled(4, 4, Rgb(99, 99, 99));
    blit_frame(&frame, 1, 1, &mut buf);

    assert_eq!(buf.get(1, 1), Rgb(10, 0, 0));
    assert_eq!(buf.get(2, 1), Rgb(99, 99, 99));
    assert_eq!(buf.get(1, 2), Rgb(99, 99, 99));
    assert_eq!(buf.get(2, 2), Rgb(0, 0, 30));
    assert_eq!(buf.get(0, 0), Rgb(99, 99, 99));
}

#[test]
fn blit_ignores_out_of_bounds() {
    let frame = Frame {
        width: 3,
        height: 3,
        pixels: vec![px(1, 1, 1); 9],
    };
    let mut buf = RgbBuffer::filled(2, 2, Rgb(0, 0, 0));
    blit_frame(&frame, 1, 1, &mut buf);
    assert_eq!(buf.get(1, 1), Rgb(1, 1, 1));
}

#[test]
fn half_block_cells_pairs_rows() {
    let buf = RgbBuffer {
        width: 2,
        height: 4,
        pixels: vec![
            Rgb(1, 0, 0),
            Rgb(2, 0, 0),
            Rgb(3, 0, 0),
            Rgb(4, 0, 0),
            Rgb(5, 0, 0),
            Rgb(6, 0, 0),
            Rgb(7, 0, 0),
            Rgb(8, 0, 0),
        ],
    };
    let cells = half_block_cells(&buf);
    assert_eq!(cells.len(), 2);
    assert_eq!(cells[0].len(), 2);
    assert_eq!(
        cells[0][0],
        HalfCell {
            fg: Rgb(1, 0, 0),
            bg: Rgb(3, 0, 0)
        }
    );
    assert_eq!(
        cells[0][1],
        HalfCell {
            fg: Rgb(2, 0, 0),
            bg: Rgb(4, 0, 0)
        }
    );
    assert_eq!(
        cells[1][0],
        HalfCell {
            fg: Rgb(5, 0, 0),
            bg: Rgb(7, 0, 0)
        }
    );
    assert_eq!(
        cells[1][1],
        HalfCell {
            fg: Rgb(6, 0, 0),
            bg: Rgb(8, 0, 0)
        }
    );
}

#[test]
fn outlined_blit_paints_halo_around_silhouette() {
    // A simple 3x3 sprite that's opaque only in the center pixel.
    // Outline should be painted at all 4 cardinal neighbors of the center.
    let frame = Frame {
        width: 3,
        height: 3,
        pixels: vec![t(), t(), t(), t(), px(200, 0, 0), t(), t(), t(), t()],
    };
    let mut buf = RgbBuffer::filled(5, 5, Rgb(0, 0, 0));
    blit_frame_outlined(&frame, 1, 1, &mut buf, Rgb(50, 50, 50));

    // Center has the sprite pixel.
    assert_eq!(buf.get(2, 2), Rgb(200, 0, 0));
    // 4 cardinal neighbors got the outline color.
    assert_eq!(buf.get(1, 2), Rgb(50, 50, 50));
    assert_eq!(buf.get(3, 2), Rgb(50, 50, 50));
    assert_eq!(buf.get(2, 1), Rgb(50, 50, 50));
    assert_eq!(buf.get(2, 3), Rgb(50, 50, 50));
    // Diagonals unchanged.
    assert_eq!(buf.get(1, 1), Rgb(0, 0, 0));
    assert_eq!(buf.get(3, 3), Rgb(0, 0, 0));
}

#[test]
fn outlined_blit_does_not_outline_interior_opaque_pixels() {
    // Fully-opaque 2x2 sprite — no transparent pixels, so no internal outline.
    let frame = Frame {
        width: 2,
        height: 2,
        pixels: vec![px(100, 0, 0), px(100, 0, 0), px(100, 0, 0), px(100, 0, 0)],
    };
    let mut buf = RgbBuffer::filled(4, 4, Rgb(0, 0, 0));
    blit_frame_outlined(&frame, 1, 1, &mut buf, Rgb(50, 50, 50));

    // Sprite blitted intact.
    assert_eq!(buf.get(1, 1), Rgb(100, 0, 0));
    assert_eq!(buf.get(2, 2), Rgb(100, 0, 0));
    // No outline pixels painted around the sprite — `blit_frame_outlined`
    // only paints outline at transparent positions within the frame bounds.
    assert_eq!(buf.get(0, 1), Rgb(0, 0, 0));
    assert_eq!(buf.get(3, 3), Rgb(0, 0, 0));
}

#[test]
fn line_horizontal() {
    let mut buf = RgbBuffer::filled(10, 5, Rgb(0, 0, 0));
    draw_line(&mut buf, 2, 2, 8, 2, Rgb(255, 0, 0));
    for x in 2..=8 {
        assert_eq!(buf.get(x, 2), Rgb(255, 0, 0));
    }
    assert_eq!(buf.get(1, 2), Rgb(0, 0, 0));
    assert_eq!(buf.get(2, 3), Rgb(0, 0, 0));
}

#[test]
fn line_diagonal() {
    let mut buf = RgbBuffer::filled(5, 5, Rgb(0, 0, 0));
    draw_line(&mut buf, 0, 0, 4, 4, Rgb(255, 255, 255));
    for i in 0..5 {
        assert_eq!(buf.get(i, i), Rgb(255, 255, 255));
    }
}

#[test]
fn line_clips_out_of_bounds_endpoints() {
    let mut buf = RgbBuffer::filled(5, 5, Rgb(0, 0, 0));
    draw_line(&mut buf, -3, -3, 7, 7, Rgb(255, 255, 255));
    for i in 0..5 {
        assert_eq!(buf.get(i, i), Rgb(255, 255, 255));
    }
}

#[test]
fn dotted_hline_alternates() {
    let mut buf = RgbBuffer::filled(12, 1, Rgb(0, 0, 0));
    draw_dotted_hline(&mut buf, 0, 0, 11, Rgb(255, 0, 0), 2, 2);
    // dash dash gap gap dash dash gap gap ...
    assert_eq!(buf.get(0, 0), Rgb(255, 0, 0));
    assert_eq!(buf.get(1, 0), Rgb(255, 0, 0));
    assert_eq!(buf.get(2, 0), Rgb(0, 0, 0));
    assert_eq!(buf.get(3, 0), Rgb(0, 0, 0));
    assert_eq!(buf.get(4, 0), Rgb(255, 0, 0));
    assert_eq!(buf.get(5, 0), Rgb(255, 0, 0));
}

#[test]
fn half_block_cells_pads_odd_height_with_repeated_row() {
    let buf = RgbBuffer {
        width: 1,
        height: 3,
        pixels: vec![Rgb(1, 0, 0), Rgb(2, 0, 0), Rgb(3, 0, 0)],
    };
    let cells = half_block_cells(&buf);
    assert_eq!(cells.len(), 2);
    assert_eq!(
        cells[0][0],
        HalfCell {
            fg: Rgb(1, 0, 0),
            bg: Rgb(2, 0, 0)
        }
    );
    assert_eq!(
        cells[1][0],
        HalfCell {
            fg: Rgb(3, 0, 0),
            bg: Rgb(3, 0, 0)
        }
    );
}
