#![deny(warnings)]
#![allow(dead_code)]
//! [TSM.ID].[11031972] -- Platform X Ecosystem
//! xcu-tesseract -- Multi-dimensional indexing engine
//! KD-Tree spatial search for multi-parameter queries

use std::collections::HashMap;

#[derive(Debug)]
pub enum TesseractError {
    DimensionMismatch(String),
    EmptyTree(String),
    NotFound(String),
}
impl std::fmt::Display for TesseractError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self { Self::DimensionMismatch(e) => write!(f, "Dim: {e}"),
            Self::EmptyTree(e) => write!(f, "Empty: {e}"),
            Self::NotFound(e) => write!(f, "Not found: {e}"), }
    }
}
impl std::error::Error for TesseractError {}

#[derive(Debug, Clone)]
pub struct TesseractPoint {
    pub id: String,
    pub coords: Vec<f64>,
    pub metadata: HashMap<String, String>,
}

struct KdNode {
    point: TesseractPoint,
    left: Option<Box<KdNode>>,
    right: Option<Box<KdNode>>,
    split_dim: usize,
}

pub struct Tesseract {
    root: Option<Box<KdNode>>,
    dimensions: usize,
    size: usize,
}

impl Tesseract {
    pub fn new(dimensions: usize) -> Self {
        Self { root: None, dimensions, size: 0 }
    }

    pub fn build(mut points: Vec<TesseractPoint>, dimensions: usize) -> Result<Self, TesseractError> {
        if points.is_empty() {
            return Ok(Self { root: None, dimensions, size: 0 });
        }
        for p in &points {
            if p.coords.len() != dimensions {
                return Err(TesseractError::DimensionMismatch(
                    format!("Expected {dimensions}, got {}", p.coords.len())));
            }
        }
        let size = points.len();
        let root = Self::build_tree(&mut points, 0, dimensions);
        Ok(Self { root, dimensions, size })
    }

    fn build_tree(points: &mut [TesseractPoint], depth: usize, dims: usize) -> Option<Box<KdNode>> {
        if points.is_empty() { return None; }
        let axis = depth % dims;
        points.sort_by(|a, b| a.coords[axis].partial_cmp(&b.coords[axis]).unwrap_or(std::cmp::Ordering::Equal));
        let mid = points.len() / 2;
        let (left_slice, rest) = points.split_at_mut(mid);
        let (median, right_slice) = match rest.split_first_mut() {
            Some(v) => v,
            None => return None,
        };
        Some(Box::new(KdNode {
            point: median.clone(),
            left: Self::build_tree(left_slice, depth + 1, dims),
            right: Self::build_tree(right_slice, depth + 1, dims),
            split_dim: axis,
        }))
    }

    /// Nearest neighbor search
    pub fn nearest(&self, query: &[f64]) -> Result<(TesseractPoint, f64), TesseractError> {
        if query.len() != self.dimensions {
            return Err(TesseractError::DimensionMismatch(format!("Query dim {} != {}", query.len(), self.dimensions)));
        }
        let root = self.root.as_ref().ok_or_else(|| TesseractError::EmptyTree("No points".into()))?;
        let mut best = root.point.clone();
        let mut best_dist = Self::distance(&root.point.coords, query);
        Self::search_nearest(root, query, &mut best, &mut best_dist);
        Ok((best, best_dist))
    }

    fn search_nearest(node: &KdNode, query: &[f64], best: &mut TesseractPoint, best_dist: &mut f64) {
        let dist = Self::distance(&node.point.coords, query);
        if dist < *best_dist {
            *best_dist = dist;
            *best = node.point.clone();
        }
        let axis = node.split_dim;
        let diff = query[axis] - node.point.coords[axis];
        let (first, second) = if diff < 0.0 { (&node.left, &node.right) } else { (&node.right, &node.left) };
        if let Some(child) = first { Self::search_nearest(child, query, best, best_dist); }
        if diff.abs() < *best_dist {
            if let Some(child) = second { Self::search_nearest(child, query, best, best_dist); }
        }
    }

    /// Range search: find all points within radius
    pub fn range_search(&self, center: &[f64], radius: f64) -> Result<Vec<(TesseractPoint, f64)>, TesseractError> {
        if center.len() != self.dimensions {
            return Err(TesseractError::DimensionMismatch("".into()));
        }
        let mut results = Vec::new();
        if let Some(root) = &self.root {
            Self::search_range(root, center, radius, &mut results);
        }
        results.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal));
        Ok(results)
    }

    fn search_range(node: &KdNode, center: &[f64], radius: f64, results: &mut Vec<(TesseractPoint, f64)>) {
        let dist = Self::distance(&node.point.coords, center);
        if dist <= radius { results.push((node.point.clone(), dist)); }
        let axis = node.split_dim;
        let diff = center[axis] - node.point.coords[axis];
        if let Some(left) = &node.left { if diff - radius <= 0.0 { Self::search_range(left, center, radius, results); } }
        if let Some(right) = &node.right { if diff + radius >= 0.0 { Self::search_range(right, center, radius, results); } }
    }

    fn distance(a: &[f64], b: &[f64]) -> f64 {
        a.iter().zip(b.iter()).map(|(x, y)| (x - y) * (x - y)).sum::<f64>().sqrt()
    }

    pub fn size(&self) -> usize { self.size }
}

#[cfg(test)]
mod tests {
    use super::*;
    fn pt(id: &str, coords: Vec<f64>) -> TesseractPoint {
        TesseractPoint { id: id.into(), coords, metadata: HashMap::new() }
    }
    #[test]
    fn test_nearest() {
        let points = vec![pt("a", vec![1.0, 2.0]), pt("b", vec![5.0, 6.0]), pt("c", vec![3.0, 3.0])];
        let t = Tesseract::build(points, 2).unwrap();
        let (nearest, dist) = t.nearest(&[2.5, 2.5]).unwrap();
        assert_eq!(nearest.id, "c");
        assert!(dist < 1.0);
    }
    #[test]
    fn test_range() {
        let points = vec![pt("a", vec![0.0, 0.0]), pt("b", vec![1.0, 1.0]), pt("c", vec![10.0, 10.0])];
        let t = Tesseract::build(points, 2).unwrap();
        let results = t.range_search(&[0.0, 0.0], 2.0).unwrap();
        assert_eq!(results.len(), 2);
    }
}
