//! Bottom-pane `/tree` viewer — the session branch tree. Pure rendering;
//! keys in `update.rs::handle_branch_tree_key`.

use ratatui::layout::{Constraint, Direction, Layout, Rect};
use ratatui::style::Modifier;
use ratatui::text::{Line, Span};
use ratatui::widgets::{Block, Borders, Paragraph};
use ratatui::Frame;

use capo_agent::BranchTree;

use crate::state::BranchTreeState;
use crate::theme;

pub const MAX_ROWS: usize = 10;

/// Depth of node `i` — the number of `parent` hops to the root.
fn depth(tree: &BranchTree, i: usize) -> usize {
    let mut d = 0;
    let mut cur = tree.nodes.get(i).and_then(|n| n.parent);
    while let Some(p) = cur {
        d += 1;
        cur = tree.nodes.get(p).and_then(|n| n.parent);
        if d > 64 {
            break; // defensive: a cycle should be impossible, but never hang
        }
    }
    d
}

/// Rows the pane wants: borders(2) + one per node (capped 1..10) + hint(1).
pub fn desired_height(tree: Option<&BranchTree>) -> u16 {
    let n = tree.map(|t| t.nodes.len()).unwrap_or(0);
    let rows = (n.min(MAX_ROWS) as u16).clamp(1, MAX_ROWS as u16);
    2 + rows + 1
}

pub fn render(state: &BranchTreeState, tree: Option<&BranchTree>, frame: &mut Frame, area: Rect) {
    let block = Block::default()
        .title(" Session tree ")
        .borders(Borders::ALL)
        .border_style(theme::modal_border());
    let inner = block.inner(area);
    frame.render_widget(block, area);

    let chunks = Layout::default()
        .direction(Direction::Vertical)
        .constraints([Constraint::Min(0), Constraint::Length(1)])
        .split(inner);

    let mut body: Vec<Line> = Vec::new();
    match tree {
        None => body.push(Line::from(Span::styled(
            "(no session tree yet)",
            theme::dim(),
        ))),
        Some(t) if t.nodes.is_empty() => {
            body.push(Line::from(Span::styled("(empty session)", theme::dim())));
        }
        Some(t) => {
            for (i, node) in t.nodes.iter().take(MAX_ROWS).enumerate() {
                let selected = i == state.selected;
                let is_active = t.active_leaf == Some(i);
                let is_branch_point = node.children.len() > 1;
                let indent = "  ".repeat(depth(t, i));
                let marker = if selected { "› " } else { "  " };
                let glyph = if is_branch_point { "⑂ " } else { "• " };
                let label_style = if selected {
                    theme::primary().add_modifier(Modifier::BOLD)
                } else if is_active {
                    theme::assistant()
                } else {
                    theme::primary()
                };
                let mut spans = vec![
                    Span::styled(
                        marker,
                        if selected {
                            theme::assistant()
                        } else {
                            theme::dim()
                        },
                    ),
                    Span::raw(indent),
                    Span::styled(glyph, theme::dim()),
                    Span::styled(node.label.clone(), label_style),
                ];
                if is_active {
                    spans.push(Span::styled("  (active)", theme::dim()));
                }
                body.push(Line::from(spans));
            }
        }
    }
    frame.render_widget(Paragraph::new(body), chunks[0]);

    frame.render_widget(
        Paragraph::new(Line::from(vec![
            Span::styled("↑↓", theme::assistant()),
            Span::raw(" navigate  "),
            Span::styled("Enter", theme::assistant()),
            Span::raw(" branch from here  "),
            Span::styled("Esc", theme::error()),
            Span::raw(" cancel"),
        ])),
        chunks[1],
    );
}

#[cfg(test)]
mod tests {
    use super::*;
    use capo_agent::BranchNode;
    use ratatui::backend::TestBackend;
    use ratatui::Terminal;

    fn sample_tree() -> BranchTree {
        // root → a; root also → b (a branch point at root).
        BranchTree {
            nodes: vec![
                BranchNode {
                    id: "r".into(),
                    parent: None,
                    children: vec![1, 2],
                    label: "ask one".into(),
                },
                BranchNode {
                    id: "a".into(),
                    parent: Some(0),
                    children: vec![],
                    label: "answer A".into(),
                },
                BranchNode {
                    id: "b".into(),
                    parent: Some(0),
                    children: vec![],
                    label: "answer B".into(),
                },
            ],
            root: Some(0),
            active_leaf: Some(2),
        }
    }

    #[test]
    fn depth_walks_parents() {
        let t = sample_tree();
        assert_eq!(depth(&t, 0), 0);
        assert_eq!(depth(&t, 1), 1);
        assert_eq!(depth(&t, 2), 1);
    }

    #[test]
    fn renders_branch_tree() -> Result<(), Box<dyn std::error::Error>> {
        let mut term = Terminal::new(TestBackend::new(60, 7))?;
        term.draw(|f| {
            render(
                &BranchTreeState::default(),
                Some(&sample_tree()),
                f,
                f.area(),
            )
        })?;
        insta::assert_snapshot!(term.backend());
        Ok(())
    }

    #[test]
    fn desired_height_handles_none_and_sized() {
        assert_eq!(desired_height(None), 2 + 1 + 1);
        assert_eq!(desired_height(Some(&sample_tree())), 2 + 3 + 1);
    }
}
