//! Paint — turn a laid-out widget tree into a flat list of draw commands.
//!
//! Layout (piece 1) is purely geometric: rects in, rects out. Paint (piece 4)
//! adds the *visual* dimension: solid fills for backgrounds, textured quads
//! for text. The output is a [`PaintedFrame`] — a flat list of
//! [`DrawCommand`]s the [`UiOverlayPass`](super::super::render::UiOverlayPass)
//! consumes directly. Keeping paint pure-CPU and the GPU pass downstream
//! lets every paint test run headlessly; the GPU pass only has to know how
//! to *consume* commands, not how to derive them.
//!
//! # Algorithm
//!
//! 1. Walk the [`LayoutTree`] in node order (root first, children after).
//! 2. For each laid-out node:
//!    - Resolve its [`VisualStyle`] under the active [`Theme`].
//!    - If the resolved style has a background, emit one [`DrawCommand::Quad`]
//!      filling `node.rect`.
//!    - If the source widget has `text`, shape it inside `node.content_rect`
//!      with the resolved font / size / color, then emit one
//!      [`DrawCommand::Glyph`] per non-space glyph.
//! 3. The frame's overall `size` mirrors the layout root's rect so the GPU
//!    pass knows how big the viewport for this batch is.
//!
//! Render order is the layout order: parents before children, so the
//! children draw *on top of* their parents (matching standard UI layering).

use glam::Vec2;

use super::layout::LayoutTree;
use super::text::{shape, FontStore, GlyphKey, ShapeParams, TextStyle};
use super::theme::Theme;
use super::visual::VisualStyle;
use super::widget::Widget;
use crate::math::{Color, Rect};

/// One draw call in a painted UI frame.
///
/// All commands share a single GPU pipeline and one texture (the glyph
/// atlas). Solid quads emit a sentinel UV the shader recognises as
/// "untextured" so a single fragment path handles both cases.
#[derive(Debug, Clone, PartialEq)]
pub enum DrawCommand {
    /// A solid-colored axis-aligned rectangle.
    Quad { rect: Rect, color: Color },
    /// One glyph quad — the renderer turns the [`GlyphKey`] into an atlas
    /// region at draw time. `pen_position` is the **baseline** point; the
    /// atlas's per-glyph bearing positions the quad relative to it.
    Glyph {
        key: GlyphKey,
        pen_position: Vec2,
        color: Color,
    },
}

/// Output of [`paint`] — the size of the painted area and the ordered list
/// of draw commands.
#[derive(Debug, Default, Clone, PartialEq)]
pub struct PaintedFrame {
    /// Size of the painted area in (post-scale) pixels — usually the
    /// layout root's rect size.
    pub size: Vec2,
    /// Draw commands in the order they should be submitted (back-to-front).
    pub commands: Vec<DrawCommand>,
}

/// Walk a laid-out widget tree under a theme and produce the draw commands
/// for one frame.
///
/// `scale` matches the value passed to
/// [`layout`](super::layout::layout) — paint uses it to pass the same DPI
/// factor to [`shape`] for text.
pub fn paint(
    root: &Widget,
    tree: &LayoutTree,
    theme: &Theme,
    fonts: &FontStore,
    scale: f32,
) -> PaintedFrame {
    let mut commands = Vec::new();
    paint_widget(root, tree, 0, theme, fonts, scale, &mut commands);
    let size = tree
        .root()
        .map(|node| node.rect.size())
        .unwrap_or(Vec2::ZERO);
    PaintedFrame { size, commands }
}

fn paint_widget(
    widget: &Widget,
    tree: &LayoutTree,
    node_index: usize,
    theme: &Theme,
    fonts: &FontStore,
    scale: f32,
    out: &mut Vec<DrawCommand>,
) {
    let node = &tree.nodes()[node_index];
    let resolved = widget.resolve_visual(theme);

    // Background fill — only emit if the rect has area and a background was
    // resolved. A `corner_radius` is captured in the resolved style for
    // future use but ignored by piece-4's rectangular renderer.
    if let Some(bg) = resolved.background {
        if !node.rect.is_empty() {
            out.push(DrawCommand::Quad {
                rect: node.rect,
                color: bg,
            });
        }
    }

    // Text — shape inside `content_rect` (so padding is respected) and emit
    // one glyph per non-empty position.
    if let Some(text) = widget.text.as_ref() {
        paint_text(text, node.content_rect, &resolved, fonts, scale, out);
    }

    // Children draw on top of self.
    for (child_widget, child_index) in widget.children().iter().zip(node.children.iter()) {
        paint_widget(
            child_widget,
            tree,
            *child_index as usize,
            theme,
            fonts,
            scale,
            out,
        );
    }
}

fn paint_text(
    text: &str,
    content_rect: Rect,
    resolved: &VisualStyle,
    fonts: &FontStore,
    scale: f32,
    out: &mut Vec<DrawCommand>,
) {
    let Some(font_ref) = resolved.font.as_ref() else {
        return;
    };
    let Some(font_id) = fonts.resolve(font_ref) else {
        return;
    };
    let size_px = resolved.font_size.unwrap_or(14.0);
    let color = resolved.foreground.unwrap_or(Color::BLACK);
    let style = TextStyle {
        font: font_id,
        size_px,
    };
    let params = ShapeParams {
        max_width: Some(content_rect.width()),
        scale,
        ..ShapeParams::default()
    };
    let shaped = shape(text, style, &params, fonts);
    for line in &shaped.lines {
        for g in &line.glyphs {
            out.push(DrawCommand::Glyph {
                key: g.key,
                pen_position: content_rect.min + g.position,
                color,
            });
        }
    }
}

#[cfg(test)]
mod tests {
    use super::super::layout::layout;
    use super::super::text::{common_system_font_paths, Font};
    use super::super::visual::FontRef;
    use super::super::widget::Widget;
    use super::*;
    use glam::Vec2;

    fn viewport(w: f32, h: f32) -> Rect {
        Rect::from_min_size(Vec2::ZERO, Vec2::new(w, h))
    }

    fn solid_panel(color: Color, w: f32, h: f32) -> Widget {
        Widget::leaf(Vec2::new(w, h)).with_visual(VisualStyle {
            background: Some(color),
            ..VisualStyle::EMPTY
        })
    }

    #[test]
    fn solid_widget_emits_one_quad_at_its_rect() {
        let root = solid_panel(Color::RED, 40.0, 20.0);
        let tree = layout(&root, viewport(100.0, 50.0), 1.0);
        let theme = Theme::new();
        let fonts = FontStore::new();
        let painted = paint(&root, &tree, &theme, &fonts, 1.0);
        assert_eq!(painted.size, Vec2::new(40.0, 20.0));
        assert_eq!(painted.commands.len(), 1);
        match &painted.commands[0] {
            DrawCommand::Quad { rect, color } => {
                assert_eq!(
                    *rect,
                    Rect::from_min_size(Vec2::ZERO, Vec2::new(40.0, 20.0))
                );
                assert_eq!(*color, Color::RED);
            }
            _ => panic!("expected a Quad"),
        }
    }

    #[test]
    fn widget_without_visual_emits_no_quads() {
        // Default widget has empty visual — nothing to paint.
        let root = Widget::leaf(Vec2::new(40.0, 20.0));
        let tree = layout(&root, viewport(100.0, 50.0), 1.0);
        let painted = paint(&root, &tree, &Theme::new(), &FontStore::new(), 1.0);
        assert!(painted.commands.is_empty());
    }

    #[test]
    fn child_quad_is_emitted_after_parent_quad() {
        let root = Widget::row()
            .with_visual(VisualStyle {
                background: Some(Color::WHITE),
                ..VisualStyle::EMPTY
            })
            .with_style(super::super::style::LayoutStyle {
                width: super::super::style::Sizing::Fixed(100.0),
                height: super::super::style::Sizing::Fixed(50.0),
                ..Default::default()
            })
            .with_child(solid_panel(Color::RED, 40.0, 20.0));
        let tree = layout(&root, viewport(200.0, 100.0), 1.0);
        let painted = paint(&root, &tree, &Theme::new(), &FontStore::new(), 1.0);
        assert_eq!(painted.commands.len(), 2);
        // Parent (white) painted before child (red), so child draws on top.
        match &painted.commands[0] {
            DrawCommand::Quad { color, .. } => assert_eq!(*color, Color::WHITE),
            _ => panic!(),
        }
        match &painted.commands[1] {
            DrawCommand::Quad { color, .. } => assert_eq!(*color, Color::RED),
            _ => panic!(),
        }
    }

    fn try_load_font() -> Option<Font> {
        for path in common_system_font_paths() {
            if std::path::Path::new(path).exists() {
                if let Ok(font) = Font::from_path(path) {
                    return Some(font);
                }
            }
        }
        eprintln!("SKIP: no system font available for paint tests");
        None
    }

    #[test]
    fn text_emits_one_glyph_per_visible_char() {
        let Some(font) = try_load_font() else {
            return;
        };
        let descriptor = FontRef::regular("Sys");
        let mut fonts = FontStore::new();
        fonts.insert_with_descriptor(descriptor.clone(), font);
        let theme = Theme::new().with_default(VisualStyle {
            font: Some(descriptor),
            font_size: Some(14.0),
            foreground: Some(Color::BLACK),
            ..VisualStyle::EMPTY
        });

        let root = Widget::leaf(Vec2::new(80.0, 20.0))
            .with_id("label")
            .with_text("Hi")
            .with_style(super::super::style::LayoutStyle {
                width: super::super::style::Sizing::Fixed(80.0),
                height: super::super::style::Sizing::Fixed(20.0),
                ..Default::default()
            });
        let tree = layout(&root, viewport(200.0, 100.0), 1.0);
        let painted = paint(&root, &tree, &theme, &fonts, 1.0);

        // "Hi" → 2 glyphs (H, i). No background → no Quad commands.
        let glyph_count = painted
            .commands
            .iter()
            .filter(|c| matches!(c, DrawCommand::Glyph { .. }))
            .count();
        let quad_count = painted
            .commands
            .iter()
            .filter(|c| matches!(c, DrawCommand::Quad { .. }))
            .count();
        assert_eq!(glyph_count, 2);
        assert_eq!(quad_count, 0);

        // Both glyphs sit at the same baseline.
        let baselines: Vec<f32> = painted
            .commands
            .iter()
            .filter_map(|c| match c {
                DrawCommand::Glyph { pen_position, .. } => Some(pen_position.y),
                _ => None,
            })
            .collect();
        assert_eq!(baselines[0], baselines[1]);
    }

    #[test]
    fn text_without_font_in_theme_silently_emits_nothing() {
        // No font registered → text resolves but shape returns no lines.
        // Paint must not panic.
        let root = Widget::leaf(Vec2::new(40.0, 20.0)).with_text("Hi");
        let tree = layout(&root, viewport(100.0, 50.0), 1.0);
        let painted = paint(&root, &tree, &Theme::new(), &FontStore::new(), 1.0);
        assert!(painted.commands.is_empty());
    }
}
