//! Canvas-pixel → cell-grid transforms and viewport intersection.

use crate::canvas::Node;

use super::viewport::{Viewport, CELLS_PER_PX_X, CELLS_PER_PX_Y};

/// A rectangle in cell coordinates relative to the visible viewport.
/// `x`/`y` may be negative if the node extends to the left/top of the
/// screen — callers are expected to clip before drawing.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CellRect {
    pub x: i32,
    pub y: i32,
    pub w: u16,
    pub h: u16,
}

/// Map a canvas-pixel value to a viewport-relative cell coordinate.
fn px_to_cell_x(px: i32, vp: &Viewport) -> i32 {
    (px as f32 * CELLS_PER_PX_X * vp.zoom).round() as i32 - vp.offset_x
}

fn px_to_cell_y(px: i32, vp: &Viewport) -> i32 {
    (px as f32 * CELLS_PER_PX_Y * vp.zoom).round() as i32 - vp.offset_y
}

/// Compute a node's bounding box in viewport-relative cells, or `None`
/// if it lies entirely outside the visible area. The returned rect is
/// always at least 1×1 so degenerate nodes still render as a marker.
pub fn node_bbox_in_cells(node: &Node, vp: &Viewport) -> Option<CellRect> {
    let (px, py) = node.position();
    let (pw, ph) = node.size();

    let x0 = px_to_cell_x(px, vp);
    let y0 = px_to_cell_y(py, vp);
    let x1 = px_to_cell_x(px.saturating_add(pw as i32), vp);
    let y1 = px_to_cell_y(py.saturating_add(ph as i32), vp);

    let w = (x1 - x0).max(1) as u16;
    let h = (y1 - y0).max(1) as u16;

    let off_left = x0 + w as i32 <= 0;
    let off_top = y0 + h as i32 <= 0;
    let off_right = x0 >= vp.cols as i32;
    let off_bottom = y0 >= vp.rows as i32;
    if off_left || off_top || off_right || off_bottom {
        return None;
    }

    Some(CellRect { x: x0, y: y0, w, h })
}

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

    fn t(x: i32, y: i32, w: u32, h: u32) -> Node {
        Node::Text(TextNode {
            id: "t".into(),
            x,
            y,
            width: w,
            height: h,
            text: String::new(),
            color: None,
        })
    }

    #[test]
    fn px_to_cell_default_density() {
        let vp = Viewport::new(80, 24);
        assert_eq!(px_to_cell_x(8, &vp), 1);
        assert_eq!(px_to_cell_y(16, &vp), 1);
    }

    #[test]
    fn negative_canvas_coordinates_are_handled() {
        let mut vp = Viewport::new(80, 24);
        vp.set_offset(-50, -10);
        // node at canvas (-200,-32) → cell (-25,-2) → viewport-relative (25,8)
        let n = t(-200, -32, 80, 32);
        let r = node_bbox_in_cells(&n, &vp).unwrap();
        assert_eq!(r.x, 25);
        assert_eq!(r.y, 8);
    }
}
