//! Per-node-type widgets.

use ratatui::buffer::Buffer;
use ratatui::layout::Rect;
use ratatui::widgets::{Block, Borders, Paragraph, Widget, Wrap};

use crate::canvas::Node;

use super::theme::Theme;

pub fn draw_node(node: &Node, area: Rect, theme: &Theme, buf: &mut Buffer) {
    match node {
        Node::Text(t) => draw_text(t, area, theme, buf),
        Node::File(f) => draw_file(f, area, theme, buf),
        Node::Link(l) => draw_link(l, area, theme, buf),
        Node::Group(g) => draw_group(g, area, theme, buf),
    }
}

fn draw_text(n: &crate::canvas::TextNode, area: Rect, theme: &Theme, buf: &mut Buffer) {
    let border_color = theme.map_color(n.color.as_deref(), theme.border);
    let block = Block::default()
        .borders(Borders::ALL)
        .border_style(theme.style(border_color));
    Paragraph::new(n.text.as_str())
        .style(theme.style(theme.text))
        .wrap(Wrap { trim: false })
        .block(block)
        .render(area, buf);
}

fn draw_file(n: &crate::canvas::FileNode, area: Rect, theme: &Theme, buf: &mut Buffer) {
    let border_color = theme.map_color(n.color.as_deref(), theme.border);
    let block = Block::default()
        .title("[F]")
        .borders(Borders::ALL)
        .border_style(theme.style(border_color));
    Paragraph::new(n.file.as_str())
        .style(theme.style(theme.text))
        .wrap(Wrap { trim: true })
        .block(block)
        .render(area, buf);
}

fn draw_link(n: &crate::canvas::LinkNode, area: Rect, theme: &Theme, buf: &mut Buffer) {
    let border_color = theme.map_color(n.color.as_deref(), theme.border);
    let block = Block::default()
        .title("[L]")
        .borders(Borders::ALL)
        .border_style(theme.style(border_color));
    Paragraph::new(n.url.as_str())
        .style(theme.style(theme.text))
        .wrap(Wrap { trim: true })
        .block(block)
        .render(area, buf);
}

fn draw_group(n: &crate::canvas::GroupNode, area: Rect, theme: &Theme, buf: &mut Buffer) {
    let border_color = theme.map_color(n.color.as_deref(), theme.group);
    let mut block = Block::default()
        .borders(Borders::ALL)
        .border_style(theme.style(border_color));
    if let Some(label) = &n.label {
        block = block.title(label.as_str());
    }
    block.render(area, buf);
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::canvas::{Canvas, FileNode, GroupNode, LinkNode, TextNode};
    use crate::layout::Viewport;
    use crate::render::render_canvas;
    use ratatui::layout::Rect;

    pub fn buffer_to_string(buf: &Buffer) -> String {
        let mut out = String::new();
        for y in 0..buf.area.height {
            for x in 0..buf.area.width {
                out.push_str(buf[(x, y)].symbol());
            }
            out.push('\n');
        }
        out
    }

    fn render(canvas: Canvas, w: u16, h: u16) -> String {
        let vp = Viewport::new(w, h);
        let theme = Theme::with_colors(false);
        let area = Rect::new(0, 0, w, h);
        let mut buf = Buffer::empty(area);
        render_canvas(&canvas, &vp, &theme, &mut buf);
        buffer_to_string(&buf)
    }

    #[test]
    fn renders_single_text_node() {
        let canvas = Canvas {
            nodes: vec![Node::Text(TextNode {
                id: "n1".into(),
                x: 0,
                y: 0,
                width: 80,
                height: 48,
                text: "hi".into(),
                color: None,
            })],
            edges: vec![],
        };
        let s = render(canvas, 20, 10);
        assert!(s.contains('┌'));
        assert!(s.contains("hi"));
    }

    #[test]
    fn renders_file_node_with_marker() {
        let canvas = Canvas {
            nodes: vec![Node::File(FileNode {
                id: "f".into(),
                x: 0,
                y: 0,
                width: 80,
                height: 48,
                file: "notes.md".into(),
                subpath: None,
                color: None,
            })],
            edges: vec![],
        };
        let s = render(canvas, 20, 10);
        assert!(s.contains("[F]"), "file marker missing: {s}");
        assert!(s.contains("notes.md"), "file path missing: {s}");
    }

    #[test]
    fn renders_link_node_with_marker() {
        let canvas = Canvas {
            nodes: vec![Node::Link(LinkNode {
                id: "l".into(),
                x: 0,
                y: 0,
                width: 200,
                height: 48,
                url: "https://x".into(),
                color: None,
            })],
            edges: vec![],
        };
        let s = render(canvas, 30, 10);
        assert!(s.contains("[L]"), "link marker missing: {s}");
        assert!(s.contains("https://x"), "url missing: {s}");
    }

    #[test]
    fn renders_group_with_label() {
        let canvas = Canvas {
            nodes: vec![Node::Group(GroupNode {
                id: "g".into(),
                x: 0,
                y: 0,
                width: 200,
                height: 80,
                label: Some("Phase 1".into()),
                background: None,
                background_style: None,
                color: None,
            })],
            edges: vec![],
        };
        let s = render(canvas, 30, 10);
        assert!(s.contains("Phase 1"), "group label missing: {s}");
        assert!(s.contains('┌'), "group border missing: {s}");
    }

    #[test]
    fn group_draws_behind_children() {
        // Child sits inside a containing group. After render, the
        // child's borders must be present; the group's borders behind
        // the child should be replaced. We assert by counting top
        // borders: 1 child only (group's top is above child's, but
        // the child draws over the group at its own corners).
        let canvas = Canvas {
            nodes: vec![
                Node::Text(TextNode {
                    id: "child".into(),
                    x: 16, // 2 cells right of group origin
                    y: 16,
                    width: 80,
                    height: 48,
                    text: "C".into(),
                    color: None,
                }),
                Node::Group(GroupNode {
                    id: "g".into(),
                    x: 0,
                    y: 0,
                    width: 200,
                    height: 80,
                    label: None,
                    background: None,
                    background_style: None,
                    color: None,
                }),
            ],
            edges: vec![],
        };
        let s = render(canvas, 30, 10);
        assert!(s.contains('C'), "child text should be visible: {s}");
        // Group is drawn first; the child rectangle then overwrites
        // a subset of cells. The group's top border must still be
        // visible to the left/right of the child.
        let first_line = s.lines().next().unwrap();
        let dashes = first_line.matches('─').count();
        assert!(
            dashes > 5,
            "expected group's top border to remain visible: {first_line:?}"
        );
    }

    #[test]
    fn renders_color_preset_when_colors_enabled() {
        let theme = Theme::with_colors(true);
        let canvas = Canvas {
            nodes: vec![Node::Text(TextNode {
                id: "n".into(),
                x: 0,
                y: 0,
                width: 80,
                height: 32,
                text: "x".into(),
                color: Some("1".into()),
            })],
            edges: vec![],
        };
        let area = Rect::new(0, 0, 20, 8);
        let mut buf = Buffer::empty(area);
        let vp = Viewport::new(20, 8);
        render_canvas(&canvas, &vp, &theme, &mut buf);
        // Top-left corner cell must carry the red foreground from preset "1".
        let cell = &buf[(0, 0)];
        let style = cell.style();
        assert_eq!(style.fg, Some(ratatui::style::Color::Red));
    }
}
