//! Outline (table of contents) extracted from the structure tree.
//!
//! Generated by walking the [`DocumentNode`] tree produced by the structure
//! engine and collecting every heading with its level, text, and page.

use serde::{Deserialize, Serialize};

use crate::model::{BoundingBox, DocumentNode, NodeKind};

/// One entry in a document's outline.
///
/// Page numbers are 1-based.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct OutlineEntry {
    /// Heading level (1 = top-level, 2 = subsection, …).
    pub level: u8,
    /// Heading text, trimmed.
    pub text: String,
    /// 1-based page on which the heading appears (first page if it spans).
    pub page: u32,
    /// Bounding box of the heading on its page (normalized 0.0–1.0).
    pub bbox: BoundingBox,
}

impl OutlineEntry {
    /// Collect all headings in document order from a structure tree.
    pub(crate) fn collect(root: &DocumentNode) -> Vec<Self> {
        let mut out = Vec::new();
        walk(root, &mut out);
        out
    }
}

fn walk(node: &DocumentNode, out: &mut Vec<OutlineEntry>) {
    if let NodeKind::Heading { level, text } = &node.kind {
        // source_pages is 0-based internally; the public outline is 1-based.
        let page = node.source_pages.start.saturating_add(1);
        out.push(OutlineEntry {
            level: *level,
            text: text.trim().to_string(),
            page,
            bbox: node.bbox,
        });
    }
    for child in &node.children {
        walk(child, out);
    }
}
