//! Rendering pipeline: nodes → ratatui widgets onto a frame.

pub mod edges;
pub mod nodes;
pub mod overlay;
pub mod theme;

use ratatui::buffer::Buffer;
use ratatui::layout::Rect;

use crate::canvas::Canvas;
use crate::layout::{node_bbox_in_cells, Viewport};

use self::nodes::draw_node;
use self::theme::Theme;

/// Render every visible node in `canvas` onto `buf`. Groups draw
/// first so that child nodes overlap them, matching the layered
/// look of the Obsidian canvas.
pub fn render_canvas(canvas: &Canvas, vp: &Viewport, theme: &Theme, buf: &mut Buffer) {
    let area = buf.area;
    let (groups, others): (Vec<_>, Vec<_>) = canvas
        .nodes
        .iter()
        .partition(|n| matches!(n, crate::canvas::Node::Group(_)));

    for node in groups.iter().chain(others.iter()) {
        let Some(rect) = node_bbox_in_cells(node, vp) else {
            continue;
        };
        let clipped = clip_to_area(rect, area);
        let Some(rect) = clipped else { continue };
        draw_node(node, rect, theme, buf);
    }

    edges::render_edges(canvas, vp, theme, buf);
}

fn clip_to_area(rect: crate::layout::CellRect, area: Rect) -> Option<Rect> {
    let x0 = rect.x.max(0);
    let y0 = rect.y.max(0);
    let x1 = (rect.x + rect.w as i32).min(area.x as i32 + area.width as i32);
    let y1 = (rect.y + rect.h as i32).min(area.y as i32 + area.height as i32);
    if x1 <= x0 || y1 <= y0 {
        return None;
    }
    Some(Rect {
        x: x0 as u16,
        y: y0 as u16,
        width: (x1 - x0) as u16,
        height: (y1 - y0) as u16,
    })
}
