// ============================================================================
// Axioma Debug API
// ============================================================================
// axioma! (Flash Engine - Lossless / Variables Tracked)
// Returns: (u8, usize, AxiValue) representing (NodeKind, next_c, Value)
//
// Topology Encoding (NodeKind u8):
//   1: Object Key
//   2: Object Value
//   3: Array Item
//   4: Empty Object {}
//   5: Empty Array []
//   0: Out of bounds / Null default
//
// `next_c` (usize) provides the exact sibling offset, allowing O(1) subtree skipping.
// ============================================================================

use crate::prelude::*;

#[derive(Debug)]
pub struct AxiomaGrid<F> {
    pub matrix: F,
    pub rows: usize,
    pub cols: usize,
}

#[derive(Debug)]
pub enum NodeKind {
    Null,
    ObjKey,
    ObjValue,
    ArrayItem,
    EmptyObj,
    EmptyArray,
    Unknown(u8),
}

impl From<u8> for NodeKind {
    fn from(val: u8) -> Self {
        match val {
            0 => NodeKind::Null,
            1 => NodeKind::ObjKey,
            2 => NodeKind::ObjValue,
            3 => NodeKind::ArrayItem,
            4 => NodeKind::EmptyObj,
            5 => NodeKind::EmptyArray,
            _ => NodeKind::Unknown(val),
        }
    }
}

impl<F> AxiomaGrid<F>
where
    F: Fn(usize, usize) -> (u8, usize, AxiValue),
{
    pub fn new(matrix: F) -> Self {
        let (rows, cols) = Self::measure_bounds(&matrix);

        Self { matrix, rows, cols }
    }

    fn measure_bounds(matrix: &F) -> (usize, usize) {
        // -----------------------------------------------
        // Custom Iterator
        // -----------------------------------------------
        let root_iter = || {
            let mut c = 0;
            std::iter::from_fn(move || {
                let (kind, next_c, _) = matrix(0, c);
                if kind == 0 {
                    None
                } else {
                    let current = c;
                    c = next_c;
                    Some((current, next_c))
                }
            })
        };

        // -----------------------------------------------
        // Finding max col
        // -----------------------------------------------
        let max_col = root_iter().last().map(|(_, next_c)| next_c).unwrap_or(0);

        if max_col == 0 {
            return (0, 0);
        }

        // -----------------------------------------------
        // Finding max row
        // -----------------------------------------------
        let mut max_row = 0;

        for (c, limit_c) in root_iter() {
            Self::walk(matrix, 0, c, limit_c, &mut max_row);
        }

        (max_row + 1, max_col)
    }

    fn walk(matrix: &F, r: usize, c: usize, limit_c: usize, max_row: &mut usize) {
        if r > *max_row {
            *max_row = r;
        }

        let (_, next_c, _) = matrix(r, c);

        if matrix(r + 1, c).0 != 0 {
            Self::walk(matrix, r + 1, c, next_c, max_row);
        }

        if r > 0 && next_c < limit_c && matrix(r, next_c).0 != 0 {
            Self::walk(matrix, r, next_c, limit_c, max_row);
        }
    }

    // =================================================================
    // === Debug Functions                                           ===
    // =================================================================

    /// Visualizes the 2D matrix structure of the Axioma topology.
    pub fn show_grid(&self, requested_cols: usize) {
        let mut start_c = 0;
        let mut tree_index = 0;

        const CELL_WIDTH: usize = 18;
        const CELL_TOTAL_WIDTH: usize = CELL_WIDTH + 3; // Total width including " | " (21 chars)

        // Define a safe maximum display width for the terminal (85 chars for typical split windows)
        const TERMINAL_MAX_WIDTH: usize = 85;

        // Calculate the maximum number of columns that can safely fit
        let safe_max_cols = std::cmp::max(1, TERMINAL_MAX_WIDTH / CELL_TOTAL_WIDTH);

        // Determine the actual number of columns to render, clamped to the physical limit
        let requested = std::cmp::max(1, requested_cols);
        let view_cols = std::cmp::min(requested, safe_max_cols);

        println!("===================================");
        println!("=== 2D Matrix Burned by Axioma! ===");
        println!("===================================\n");

        loop {
            let (root_kind, limit_c, _) = (self.matrix)(0, start_c);
            if root_kind == 0 {
                break;
            }

            for view_start in (start_c..limit_c).step_by(view_cols) {
                let view_end = std::cmp::min(view_start + view_cols, limit_c);

                println!(
                    "[ Tree {}: Column {} to {} ]",
                    tree_index,
                    view_start,
                    view_end - 1
                );

                let cell_sep = "-".repeat(CELL_TOTAL_WIDTH);
                for _ in view_start..view_end {
                    print!("{}", cell_sep);
                }
                println!();

                for r in 0..self.rows {
                    // Layer 1: Coordinates
                    for c in view_start..view_end {
                        print!("{0:<1$} | ", format!("({}, {})", c, r), CELL_WIDTH);
                    }
                    println!();

                    // Layer 2: Node Type
                    for c in view_start..view_end {
                        let (kind_u8, _, _) = (self.matrix)(r, c);
                        print!(
                            "{0:<1$} | ",
                            format!("{:?}", NodeKind::from(kind_u8)),
                            CELL_WIDTH
                        );
                    }
                    println!();

                    // Layer 3: Value (Truncates if length exceeds CELL_WIDTH)
                    for c in view_start..view_end {
                        let (_, _, ref val) = (self.matrix)(r, c);
                        print!("{0:<1$.1$} | ", format!("{:?}", val), CELL_WIDTH);
                    }
                    println!();

                    for _ in view_start..view_end {
                        print!("{}", cell_sep);
                    }
                    println!();
                }
                println!();
            }
            start_c = limit_c;
            tree_index += 1;
        }
    }

    // ------------------------------------------------------------------------
    // Axioma API: show_outline (Topology Skeleton Viewer)
    // ------------------------------------------------------------------------

    pub fn count_nodes(&self) -> usize {
        (0..self.rows)
            .flat_map(|r| (0..self.cols).map(move |c| (r, c)))
            .filter(|&(r, c)| (self.matrix)(r, c).0 != 0)
            .count()
    }

    /// Outputs a clean tree outline of the topology, filtering out scalar values (leaves).
    pub fn show_outline(&self) {
        println!("===============================");
        println!("=== Axioma Topology Outline ===");
        println!("===============================\n.");

        let mut branch_roots = Vec::new();
        let mut curr_c = 0;

        while curr_c < self.cols {
            let (kind, next_c, _) = (self.matrix)(0, curr_c);
            if kind == 0 {
                break;
            }

            if self.is_branch(0, curr_c) {
                branch_roots.push((curr_c, next_c));
            }
            curr_c = next_c;
        }

        let count = branch_roots.len();
        for (i, &(c, next_c)) in branch_roots.iter().enumerate() {
            let is_last = i == count - 1;
            self.print_outline_node(0, c, next_c, String::new(), is_last);
        }
        println!();
    }

    /// Determines if a given node is a "branch" (contains an Object or Array structure).
    fn is_branch(&self, r: usize, c: usize) -> bool {
        let (kind, _, _) = (self.matrix)(r, c);

        // Treat empty objects/arrays as branches terminating a structure
        if kind == 4 || kind == 5 {
            return true;
        }

        let (child_kind, _, _) = (self.matrix)(r + 1, c);

        // True if the child is an Object key, Array item, or an empty structure.
        // Skips ObjValue and Null, which represent scalar leaves.
        matches!(child_kind, 1 | 3 | 4 | 5)
    }

    fn print_outline_node(&self, r: usize, c: usize, next_c: usize, prefix: String, is_last: bool) {
        let (kind_u8, _, ref val) = (self.matrix)(r, c);
        let kind = NodeKind::from(kind_u8);

        let raw_val = format!("{:?}", val);

        // Strip noise from AxiValue debug output (e.g., Str("...")) to create a clean key name
        let clean_val = if (raw_val.starts_with("Str(\"") || raw_val.starts_with("Num(\""))
            && raw_val.ends_with("\")")
        {
            raw_val[5..raw_val.len() - 2].to_string()
        } else {
            raw_val
        };

        // Format display name according to structural type
        let display_name = match kind {
            NodeKind::EmptyObj => "{}".to_string(),
            NodeKind::EmptyArray => "[]".to_string(),
            NodeKind::ArrayItem => format!("[{}]", clean_val), // Indicate array element
            _ => clean_val,
        };

        let branch = if is_last { "└── " } else { "├── " };
        println!("{}{}{}", prefix, branch, display_name);

        let mut branch_children = Vec::new();
        let mut child_c = c;

        while child_c < next_c {
            let (child_kind, child_next_c, _) = (self.matrix)(r + 1, child_c);
            if child_kind == 0 {
                break;
            }

            if self.is_branch(r + 1, child_c) {
                branch_children.push((child_c, child_next_c));
            }
            child_c = child_next_c;
        }

        let child_prefix = format!("{}{}", prefix, if is_last { "    " } else { "│   " });
        let count = branch_children.len();

        for (i, &(bc, b_next_c)) in branch_children.iter().enumerate() {
            let child_is_last = i == count - 1;
            self.print_outline_node(r + 1, bc, b_next_c, child_prefix.clone(), child_is_last);
        }
    }
}
