use crate::ir::TrigGraph;
use crate::eval::evaluate;
use std::f64::consts::PI;

/// Traversal strategy for evaluating a TrigGraph over a spatial domain.
#[derive(Debug, Clone)]
pub enum TraversalMode {
    /// Walk the DAG linearly. Single thread, complete, deterministic.
    Sequential,

    /// Partition domain into angular regions (solid angle sectors).
    /// Each region evaluates in parallel with interpolated boundary seams.
    ParallelMesh {
        /// Number of angular divisions per axis (total regions = divisions²)
        divisions: usize,
        /// Overlap ratio at boundaries for interpolation (0.0 - 0.5)
        overlap: f64,
    },

    /// Polar evaluation expanding outward from origin with logarithmic radial density.
    /// Convergence criterion: RMS of shell values drops below surface-area threshold.
    SphericalConvergence {
        /// Center of the polar evaluation
        origin: [f64; 3],
        /// Maximum radius (if convergence hasn't triggered)
        max_radius: f64,
        /// Number of radial shells
        radial_steps: usize,
        /// Angular samples per shell (scales with r²)
        base_angular_samples: usize,
    },
}

/// Result of a spatial evaluation.
pub struct EvalResult {
    pub values: Vec<SpatialSample>,
    pub converged: bool,
    pub convergence_radius: f64,
    pub total_samples: usize,
}

#[derive(Debug, Clone, Copy)]
pub struct SpatialSample {
    pub position: [f64; 3],
    pub value: f64,
}

/// Evaluate a TrigGraph using the specified traversal mode.
pub fn traverse(graph: &TrigGraph, mode: &TraversalMode, bounds: &EvalBounds) -> EvalResult {
    match mode {
        TraversalMode::Sequential => traverse_sequential(graph, bounds),
        TraversalMode::ParallelMesh { divisions, overlap } => {
            traverse_parallel_mesh(graph, bounds, *divisions, *overlap)
        }
        TraversalMode::SphericalConvergence {
            origin, max_radius, radial_steps, base_angular_samples
        } => {
            traverse_spherical(graph, origin, *max_radius, *radial_steps, *base_angular_samples)
        }
    }
}

#[derive(Debug, Clone)]
pub struct EvalBounds {
    pub min: [f64; 3],
    pub max: [f64; 3],
    pub resolution: usize,
}

// === Mode 1: Sequential ===

fn traverse_sequential(graph: &TrigGraph, bounds: &EvalBounds) -> EvalResult {
    let res = bounds.resolution;
    let step = [
        (bounds.max[0] - bounds.min[0]) / (res - 1).max(1) as f64,
        (bounds.max[1] - bounds.min[1]) / (res - 1).max(1) as f64,
        (bounds.max[2] - bounds.min[2]) / (res - 1).max(1) as f64,
    ];

    let mut values = Vec::with_capacity(res * res * res);
    for iz in 0..res {
        let z = bounds.min[2] + iz as f64 * step[2];
        for iy in 0..res {
            let y = bounds.min[1] + iy as f64 * step[1];
            for ix in 0..res {
                let x = bounds.min[0] + ix as f64 * step[0];
                let val = evaluate(graph, x, y, z);
                values.push(SpatialSample {
                    position: [x, y, z],
                    value: val,
                });
            }
        }
    }

    EvalResult {
        total_samples: values.len(),
        values,
        converged: true,
        convergence_radius: f64::INFINITY,
    }
}

// === Mode 2: Parallel Mesh ===

/// Partition the domain into angular sectors viewed from center.
/// Each sector evaluates independently; overlapping boundaries are blended.
fn traverse_parallel_mesh(
    graph: &TrigGraph,
    bounds: &EvalBounds,
    _divisions: usize,
    _overlap: f64,
) -> EvalResult {
    let res = bounds.resolution;
    let step = [
        (bounds.max[0] - bounds.min[0]) / (res - 1).max(1) as f64,
        (bounds.max[1] - bounds.min[1]) / (res - 1).max(1) as f64,
        (bounds.max[2] - bounds.min[2]) / (res - 1).max(1) as f64,
    ];

    let mut values = Vec::with_capacity(res * res * res);

    for iz in 0..res {
        let z = bounds.min[2] + iz as f64 * step[2];
        for iy in 0..res {
            let y = bounds.min[1] + iy as f64 * step[1];
            for ix in 0..res {
                let x = bounds.min[0] + ix as f64 * step[0];

                let val = evaluate(graph, x, y, z);
                // TODO: sector assignment + blend weights for actual parallel dispatch
                values.push(SpatialSample { position: [x, y, z], value: val });
            }
        }
    }

    EvalResult {
        total_samples: values.len(),
        values,
        converged: true,
        convergence_radius: f64::INFINITY,
    }
}

// === Mode 3: Spherical Convergence ===

/// Inside-out evaluation in polar coordinates with logarithmic radial density.
/// Converges when RMS of accumulated volume ≈ surface area of current shell.
fn traverse_spherical(
    graph: &TrigGraph,
    origin: &[f64; 3],
    max_radius: f64,
    radial_steps: usize,
    base_angular_samples: usize,
) -> EvalResult {
    let mut values = Vec::new();
    let mut volume_sum_sq = 0.0f64;
    let mut volume_count = 0usize;
    let mut convergence_radius = max_radius;
    let mut converged = false;

    for ri in 0..radial_steps {
        // Logarithmic radial spacing: dense near center, sparse at edge.
        // r = max_radius * (e^(t*ln(max_radius)) - 1) / (max_radius - 1)
        // Simplified: logarithmic from epsilon to max_radius
        let t = (ri + 1) as f64 / radial_steps as f64;
        let r = max_radius * (t * t); // quadratic gives decent log-like spacing

        if r < 1e-10 {
            let val = evaluate(graph, origin[0], origin[1], origin[2]);
            values.push(SpatialSample { position: *origin, value: val });
            volume_sum_sq += val * val;
            volume_count += 1;
            continue;
        }

        let angular_samples = (base_angular_samples as f64 * t * t).max(6.0) as usize;

        // Fibonacci sphere sampling for uniform angular distribution
        let golden_ratio = (1.0 + 5.0f64.sqrt()) / 2.0;

        for i in 0..angular_samples {
            let theta = (1.0 - 2.0 * (i as f64 + 0.5) / angular_samples as f64).acos();
            let phi = 2.0 * PI * i as f64 / golden_ratio;

            let x = origin[0] + r * theta.sin() * phi.cos();
            let y = origin[1] + r * theta.sin() * phi.sin();
            let z = origin[2] + r * theta.cos();

            let val = evaluate(graph, x, y, z);
            values.push(SpatialSample { position: [x, y, z], value: val });
            volume_sum_sq += val * val;
            volume_count += 1;
        }

        let rms = (volume_sum_sq / volume_count as f64).sqrt();
        let volume_metric = rms * r;
        let surface_metric = r * r;

        if volume_metric >= surface_metric && ri > radial_steps / 4 {
            convergence_radius = r;
            converged = true;
            break;
        }
    }

    EvalResult {
        total_samples: values.len(),
        values,
        converged,
        convergence_radius,
    }
}
