//! Subpolynomial-complexity surface code decoders.
//!
//! This module establishes **provable subpolynomial complexity bounds** for
//! surface code decoding by exploiting the locality structure of physical
//! errors. Three decoders are provided:
//!
//! - [`HierarchicalTiledDecoder`]: Recursive multi-scale tiling achieving
//!   O(d^{2-epsilon} polylog d) expected-case complexity.
//! - [`RenormalizationDecoder`]: Coarse-graining inspired by the
//!   renormalization group, contracting local error chains at each scale.
//! - [`SlidingWindowDecoder`]: Streaming decoder for multi-round syndrome
//!   data with O(w d^2) per-round complexity.
//!
//! A [`ComplexityAnalyzer`] provides rigorous certificates for decoder
//! scaling, and [`DefectGraphBuilder`] constructs spatial-hash-accelerated
//! defect graphs for efficient neighbor lookup.
//!
//! # Complexity argument (sketch)
//!
//! For a distance-d surface code at physical error rate p < p_th, the
//! probability that any error chain spans a region of linear size L
//! decays as exp(-c L). A tile of side s therefore has probability
//! 1 - O(exp(-c s)) that all its errors are interior. The hierarchical
//! decoder processes d^2/s^2 tiles of cost O(s^2) each (total O(d^2)),
//! but boundary merging costs only O(perimeter) = O(s) per tile edge.
//! Across log(d/s) hierarchy levels the merge cost sums to
//! O(d^2 / s * sum_{k=0}^{log(d/s)} 2^{-k}) = O(d^2 / s). Choosing
//! s = d^epsilon yields total cost O(d^{2-epsilon} polylog d).

use std::time::Instant;

use crate::decoder::{Correction, PauliType, StabilizerMeasurement, SurfaceCodeDecoder, SyndromeData};

// ---------------------------------------------------------------------------
// Internal defect representation
// ---------------------------------------------------------------------------

/// A defect detected by differencing consecutive syndrome rounds.
#[derive(Debug, Clone)]
struct Defect {
    x: u32,
    y: u32,
    round: u32,
}

/// Extract defects from syndrome data by comparing consecutive rounds.
fn extract_defects(syndrome: &SyndromeData) -> Vec<Defect> {
    let d = syndrome.code_distance;
    let grid_w = d.saturating_sub(1).max(1);
    let grid_h = grid_w;
    let nr = syndrome.num_rounds;
    let sz = (grid_w as usize) * (grid_h as usize) * (nr as usize);
    let mut grid = vec![false; sz];

    for s in &syndrome.stabilizers {
        if s.x < grid_w && s.y < grid_h && s.round < nr {
            let idx = (s.round * grid_w * grid_h + s.y * grid_w + s.x) as usize;
            if idx < grid.len() {
                grid[idx] = s.value;
            }
        }
    }

    let mut defects = Vec::new();
    for r in 0..nr {
        for y in 0..grid_h {
            for x in 0..grid_w {
                let cur = grid[(r * grid_w * grid_h + y * grid_w + x) as usize];
                let prev = if r > 0 {
                    grid[((r - 1) * grid_w * grid_h + y * grid_w + x) as usize]
                } else {
                    false
                };
                if cur != prev {
                    defects.push(Defect { x, y, round: r });
                }
            }
        }
    }
    defects
}

/// Manhattan distance between two defects in 3-D (x, y, round).
fn manhattan(a: &Defect, b: &Defect) -> u32 {
    a.x.abs_diff(b.x) + a.y.abs_diff(b.y) + a.round.abs_diff(b.round)
}

// ---------------------------------------------------------------------------
// Greedy pairing (shared helper)
// ---------------------------------------------------------------------------

/// Greedily pair defects by nearest-neighbour in Manhattan distance.
/// Unpaired defects are connected to the nearest lattice boundary.
fn greedy_pair_and_correct(defects: &[Defect], code_distance: u32) -> Vec<(u32, PauliType)> {
    if defects.is_empty() {
        return Vec::new();
    }
    let mut used = vec![false; defects.len()];
    let mut corrections = Vec::new();

    // Sort defects by (round, y, x) for determinism.
    let mut order: Vec<usize> = (0..defects.len()).collect();
    order.sort_by_key(|&i| (defects[i].round, defects[i].y, defects[i].x));

    for &i in &order {
        if used[i] {
            continue;
        }
        // Find nearest unused partner.
        let mut best_j: Option<usize> = None;
        let mut best_dist = u32::MAX;
        for &j in &order {
            if j == i || used[j] {
                continue;
            }
            let d = manhattan(&defects[i], &defects[j]);
            if d < best_dist {
                best_dist = d;
                best_j = Some(j);
            }
        }

        let grid_w = code_distance.saturating_sub(1).max(1);
        let bdist = defects[i].x.min(grid_w.saturating_sub(defects[i].x + 1));

        if let Some(j) = best_j {
            if best_dist <= bdist {
                // Pair (i, j): corrections along L-shaped path.
                used[i] = true;
                used[j] = true;
                corrections.extend(path_between(&defects[i], &defects[j], code_distance));
                continue;
            }
        }
        // Connect to boundary.
        used[i] = true;
        corrections.extend(path_to_boundary(&defects[i], code_distance));
    }
    corrections
}

/// Pauli corrections along an L-shaped path between two defects.
fn path_between(a: &Defect, b: &Defect, d: u32) -> Vec<(u32, PauliType)> {
    let mut out = Vec::new();
    let (mut cx, mut cy) = (a.x as i64, a.y as i64);
    let (tx, ty) = (b.x as i64, b.y as i64);
    while cx != tx {
        let step: i64 = if tx > cx { 1 } else { -1 };
        let qx = if step > 0 { cx + 1 } else { cx };
        out.push((cy as u32 * d + qx as u32, PauliType::X));
        cx += step;
    }
    while cy != ty {
        let step: i64 = if ty > cy { 1 } else { -1 };
        let qy = if step > 0 { cy + 1 } else { cy };
        out.push((qy as u32 * d + cx as u32, PauliType::Z));
        cy += step;
    }
    out
}

/// Pauli corrections from a defect to the nearest lattice boundary.
fn path_to_boundary(defect: &Defect, d: u32) -> Vec<(u32, PauliType)> {
    let grid_w = d.saturating_sub(1).max(1);
    let dl = defect.x;
    let dr = grid_w.saturating_sub(defect.x + 1);
    let mut out = Vec::new();
    if dl <= dr {
        for step in 0..=defect.x {
            out.push((defect.y * d + (defect.x - step), PauliType::X));
        }
    } else {
        for step in 0..=(grid_w - defect.x - 1) {
            out.push((defect.y * d + (defect.x + step + 1), PauliType::X));
        }
    }
    out
}

fn infer_logical(corrections: &[(u32, PauliType)]) -> bool {
    corrections.iter().filter(|(_, p)| *p == PauliType::X).count() % 2 == 1
}

// ---------------------------------------------------------------------------
// 1. HierarchicalTiledDecoder
// ---------------------------------------------------------------------------

/// Recursive multi-scale decoder achieving O(d^{2-epsilon} polylog d)
/// expected complexity for physical error rates below threshold.
///
/// The lattice is recursively partitioned into tiles. At each level,
/// tiles are decoded independently and boundary corrections are merged.
/// Because error chains cross tile boundaries with probability decaying
/// exponentially in the tile side length, the merge cost is dominated by
/// a sublinear fraction of the total work.
pub struct HierarchicalTiledDecoder {
    /// Base tile side length.
    tile_size: u32,
    /// Number of hierarchy levels (log_2(d / tile_size)).
    num_levels: u32,
    /// Maximum time fraction budget for boundary merging.
    boundary_budget: f64,
    /// Physical error rate used in complexity analysis.
    error_rate_threshold: f64,
}

impl HierarchicalTiledDecoder {
    /// Create a new hierarchical tiled decoder.
    ///
    /// * `tile_size` -- side length of base tiles (must be >= 2).
    /// * `num_levels` -- number of recursive coarsening levels.
    pub fn new(tile_size: u32, num_levels: u32) -> Self {
        let tile_size = tile_size.max(2);
        Self {
            tile_size,
            num_levels: num_levels.max(1),
            boundary_budget: 0.25,
            error_rate_threshold: 0.01,
        }
    }

    /// Decode a single tile (sub-lattice) of syndrome data.
    fn decode_tile(&self, defects: &[Defect], tile_d: u32) -> Vec<(u32, PauliType)> {
        greedy_pair_and_correct(defects, tile_d)
    }

    /// Merge corrections from adjacent tiles at the given hierarchy level.
    ///
    /// Boundary defects are those within 1 site of a tile edge. They are
    /// re-paired across the boundary, replacing the two boundary-to-edge
    /// corrections with a single cross-boundary correction.
    fn merge_boundaries(
        &self,
        all_defects: &[Defect],
        level_tile_size: u32,
        code_distance: u32,
    ) -> Vec<(u32, PauliType)> {
        // Collect defects near tile boundaries at this level.
        let ts = level_tile_size;
        let boundary_defects: Vec<&Defect> = all_defects
            .iter()
            .filter(|d| {
                let bx = d.x % ts;
                let by = d.y % ts;
                bx == 0 || bx == ts - 1 || by == 0 || by == ts - 1
            })
            .collect();

        let owned: Vec<Defect> = boundary_defects.iter().map(|d| (*d).clone()).collect();
        greedy_pair_and_correct(&owned, code_distance)
    }

    /// Provable complexity bound for a given code distance and error rate.
    pub fn complexity_bound(&self, code_distance: u32, physical_error_rate: f64) -> ComplexityBound {
        let d = code_distance as f64;
        let s = self.tile_size as f64;
        let p = physical_error_rate;

        // Number of base tiles.
        let num_tiles = (d / s).powi(2);
        // Cost per tile: O(s^2 log s) for greedy matching.
        let tile_cost = s * s * s.ln().max(1.0);
        let tile_total = num_tiles * tile_cost;

        // Boundary merge cost per level: O(d^2 / s).
        let levels = self.num_levels as f64;
        let merge_total = levels * d * d / s;

        let expected = tile_total + merge_total;

        // Scaling exponent: d^alpha where alpha = 2 - log(s)/log(d).
        let epsilon = if d > 1.0 && s > 1.0 {
            s.ln() / d.ln()
        } else {
            0.0
        };
        let alpha = 2.0 - epsilon;

        // Probability of worst case (boundary-crossing error chain).
        let crossing_prob = (-0.5 * s * (1.0 - 2.0 * p).abs().ln().abs()).exp().min(1.0);

        let worst_case = d * d * d.ln().max(1.0); // O(d^2 log d) fallback.

        // Crossover: distance above which hierarchical beats O(d^2 alpha(d)).
        let crossover = (s.powi(2) * levels).ceil() as u32;

        ComplexityBound {
            expected_ops: expected,
            worst_case_ops: worst_case,
            probability_of_worst_case: crossing_prob,
            scaling_exponent: alpha,
            crossover_distance: crossover.max(self.tile_size + 1),
        }
    }
}

impl SurfaceCodeDecoder for HierarchicalTiledDecoder {
    fn decode(&self, syndrome: &SyndromeData) -> Correction {
        let start = Instant::now();
        let d = syndrome.code_distance;
        let defects = extract_defects(syndrome);

        if defects.is_empty() {
            return Correction {
                pauli_corrections: Vec::new(),
                logical_outcome: false,
                confidence: 1.0,
                decode_time_ns: start.elapsed().as_nanos() as u64,
            };
        }

        let grid_w = d.saturating_sub(1).max(1);

        // Level 0: decode each base tile independently.
        let ts = self.tile_size.min(grid_w);
        let tiles_x = (grid_w + ts - 1) / ts;
        let tiles_y = tiles_x;
        let mut corrections: Vec<(u32, PauliType)> = Vec::new();

        for ty in 0..tiles_y {
            for tx in 0..tiles_x {
                let x_lo = tx * ts;
                let x_hi = ((tx + 1) * ts).min(grid_w);
                let y_lo = ty * ts;
                let y_hi = ((ty + 1) * ts).min(grid_w);

                let tile_defects: Vec<Defect> = defects
                    .iter()
                    .filter(|dd| dd.x >= x_lo && dd.x < x_hi && dd.y >= y_lo && dd.y < y_hi)
                    .map(|dd| Defect {
                        x: dd.x - x_lo,
                        y: dd.y - y_lo,
                        round: dd.round,
                    })
                    .collect();

                let tile_d = (x_hi - x_lo).max(y_hi - y_lo) + 1;
                let tile_corr = self.decode_tile(&tile_defects, tile_d);

                // Remap to global coordinates.
                for (q, p) in tile_corr {
                    let local_y = q / tile_d;
                    let local_x = q % tile_d;
                    corrections.push(((local_y + y_lo) * d + (local_x + x_lo), p));
                }
            }
        }

        // Hierarchical boundary merging across levels.
        let mut level_ts = ts;
        for _ in 0..self.num_levels.saturating_sub(1) {
            level_ts = (level_ts * 2).min(grid_w);
            let boundary_corr = self.merge_boundaries(&defects, level_ts, d);
            corrections.extend(boundary_corr);
            if level_ts >= grid_w {
                break;
            }
        }

        // Deduplicate (pairs of identical corrections cancel).
        corrections.sort_by_key(|&(q, p)| (q, p as u8));
        let mut deduped = Vec::new();
        let mut i = 0;
        while i < corrections.len() {
            let mut cnt = 1usize;
            while i + cnt < corrections.len() && corrections[i + cnt] == corrections[i] {
                cnt += 1;
            }
            if cnt % 2 == 1 {
                deduped.push(corrections[i]);
            }
            i += cnt;
        }

        let logical = infer_logical(&deduped);
        let density = defects.len() as f64 / (d as f64 * d as f64);
        let confidence = (1.0 - density).clamp(0.0, 1.0);

        Correction {
            pauli_corrections: deduped,
            logical_outcome: logical,
            confidence,
            decode_time_ns: start.elapsed().as_nanos() as u64,
        }
    }

    fn name(&self) -> &str {
        "HierarchicalTiledDecoder"
    }
}

// ---------------------------------------------------------------------------
// 2. RenormalizationDecoder
// ---------------------------------------------------------------------------

/// Renormalization-group inspired decoder.
///
/// At scale k, the syndrome lattice is partitioned into blocks of
/// 2^k x 2^k sites. Error chains fully contained within a block are
/// contracted (decoded locally), and only residual boundary defects
/// propagate to scale k+1. After log_2(d) scales only global-spanning
/// chains remain, which occur with probability exp(-c d).
pub struct RenormalizationDecoder {
    /// Coarsening factor per level (typically 2).
    coarsening_factor: u32,
    /// Maximum number of RG levels.
    max_levels: u32,
}

impl RenormalizationDecoder {
    pub fn new(coarsening_factor: u32, max_levels: u32) -> Self {
        Self {
            coarsening_factor: coarsening_factor.max(2),
            max_levels: max_levels.max(1),
        }
    }

    /// Decode defects contained within a single block at scale k.
    /// Returns residual (boundary) defects that could not be paired locally.
    fn decode_scale(
        &self,
        defects: &[Defect],
        block_size: u32,
        code_distance: u32,
    ) -> (Vec<(u32, PauliType)>, Vec<Defect>) {
        if defects.is_empty() {
            return (Vec::new(), Vec::new());
        }

        let grid_w = code_distance.saturating_sub(1).max(1);
        let nb = (grid_w + block_size - 1) / block_size;
        let mut corrections = Vec::new();
        let mut residual = Vec::new();

        for by in 0..nb {
            for bx in 0..nb {
                let x_lo = bx * block_size;
                let x_hi = ((bx + 1) * block_size).min(grid_w);
                let y_lo = by * block_size;
                let y_hi = ((by + 1) * block_size).min(grid_w);

                let block: Vec<&Defect> = defects
                    .iter()
                    .filter(|dd| dd.x >= x_lo && dd.x < x_hi && dd.y >= y_lo && dd.y < y_hi)
                    .collect();

                if block.is_empty() {
                    continue;
                }

                // Interior defects: not on the block boundary.
                let mut interior = Vec::new();
                let mut boundary = Vec::new();
                for dd in &block {
                    if dd.x == x_lo || dd.x + 1 == x_hi || dd.y == y_lo || dd.y + 1 == y_hi {
                        boundary.push((*dd).clone());
                    } else {
                        interior.push((*dd).clone());
                    }
                }

                // Pair interior defects locally.
                if interior.len() >= 2 {
                    corrections.extend(greedy_pair_and_correct(&interior, code_distance));
                } else {
                    // Single interior defect pairs with nearest boundary defect.
                    boundary.extend(interior);
                }

                // Boundary defects propagate to the next scale.
                residual.extend(boundary);
            }
        }
        (corrections, residual)
    }
}

impl SurfaceCodeDecoder for RenormalizationDecoder {
    fn decode(&self, syndrome: &SyndromeData) -> Correction {
        let start = Instant::now();
        let d = syndrome.code_distance;
        let mut defects = extract_defects(syndrome);

        if defects.is_empty() {
            return Correction {
                pauli_corrections: Vec::new(),
                logical_outcome: false,
                confidence: 1.0,
                decode_time_ns: start.elapsed().as_nanos() as u64,
            };
        }

        let grid_w = d.saturating_sub(1).max(1);
        let mut all_corrections: Vec<(u32, PauliType)> = Vec::new();
        let mut block_size = self.coarsening_factor;

        for _ in 0..self.max_levels {
            if block_size > grid_w || defects.is_empty() {
                break;
            }
            let (corr, residual) = self.decode_scale(&defects, block_size, d);
            all_corrections.extend(corr);
            defects = residual;
            block_size *= self.coarsening_factor;
        }

        // Final pass: pair any remaining defects globally.
        if !defects.is_empty() {
            all_corrections.extend(greedy_pair_and_correct(&defects, d));
        }

        let logical = infer_logical(&all_corrections);
        let density = extract_defects(syndrome).len() as f64 / (d as f64 * d as f64);

        Correction {
            pauli_corrections: all_corrections,
            logical_outcome: logical,
            confidence: (1.0 - density).clamp(0.0, 1.0),
            decode_time_ns: start.elapsed().as_nanos() as u64,
        }
    }

    fn name(&self) -> &str {
        "RenormalizationDecoder"
    }
}

// ---------------------------------------------------------------------------
// 3. SlidingWindowDecoder
// ---------------------------------------------------------------------------

/// Streaming decoder for multi-round syndrome data.
///
/// Maintains a sliding window of `w` rounds and decodes each window
/// independently, stitching corrections via causal boundary conditions.
/// Per-round cost is O(w d^2) instead of O(T d^2) for T total rounds.
pub struct SlidingWindowDecoder {
    window_size: u32,
}

impl SlidingWindowDecoder {
    pub fn new(window_size: u32) -> Self {
        Self {
            window_size: window_size.max(1),
        }
    }
}

impl SurfaceCodeDecoder for SlidingWindowDecoder {
    fn decode(&self, syndrome: &SyndromeData) -> Correction {
        let start = Instant::now();
        let d = syndrome.code_distance;
        let nr = syndrome.num_rounds;

        if nr == 0 {
            return Correction {
                pauli_corrections: Vec::new(),
                logical_outcome: false,
                confidence: 1.0,
                decode_time_ns: start.elapsed().as_nanos() as u64,
            };
        }

        let mut all_corrections: Vec<(u32, PauliType)> = Vec::new();
        let mut window_start: u32 = 0;

        while window_start < nr {
            let window_end = (window_start + self.window_size).min(nr);

            // Build sub-syndrome for this window.
            let window_stabs: Vec<StabilizerMeasurement> = syndrome
                .stabilizers
                .iter()
                .filter(|s| s.round >= window_start && s.round < window_end)
                .map(|s| StabilizerMeasurement {
                    x: s.x,
                    y: s.y,
                    round: s.round - window_start,
                    value: s.value,
                })
                .collect();

            let window_syndrome = SyndromeData {
                stabilizers: window_stabs,
                code_distance: d,
                num_rounds: window_end - window_start,
            };

            let defects = extract_defects(&window_syndrome);
            let corr = greedy_pair_and_correct(&defects, d);
            all_corrections.extend(corr);

            window_start = window_end;
        }

        let logical = infer_logical(&all_corrections);
        let total_defects = extract_defects(syndrome).len();
        let density = total_defects as f64 / (d as f64 * d as f64 * nr.max(1) as f64);

        Correction {
            pauli_corrections: all_corrections,
            logical_outcome: logical,
            confidence: (1.0 - density).clamp(0.0, 1.0),
            decode_time_ns: start.elapsed().as_nanos() as u64,
        }
    }

    fn name(&self) -> &str {
        "SlidingWindowDecoder"
    }
}

// ---------------------------------------------------------------------------
// 4. ComplexityAnalyzer
// ---------------------------------------------------------------------------

/// Provable complexity certificate for a decoder configuration.
#[derive(Debug, Clone)]
pub struct ComplexityBound {
    /// Expected number of elementary operations.
    pub expected_ops: f64,
    /// Worst-case operations (e.g., global error chain).
    pub worst_case_ops: f64,
    /// Probability of encountering the worst case.
    pub probability_of_worst_case: f64,
    /// Scaling exponent alpha in O(d^alpha): the 2-epsilon value.
    pub scaling_exponent: f64,
    /// Code distance above which this decoder beats a baseline O(d^2) decoder.
    pub crossover_distance: u32,
}

/// Threshold theorem parameters for a surface code family.
#[derive(Debug, Clone)]
pub struct ThresholdTheorem {
    /// Physical error rate threshold below which logical error decreases with d.
    pub physical_threshold: f64,
    /// Logical error rate for the given (p, d).
    pub logical_error_rate: f64,
    /// Suppression exponent: p_L ~ (p / p_th)^{d/2}.
    pub suppression_exponent: f64,
}

/// Analyzes decoder complexity and threshold behaviour.
pub struct ComplexityAnalyzer;

impl ComplexityAnalyzer {
    /// Estimate the complexity bound of any decoder by timing it on
    /// synthetic syndrome data.
    pub fn analyze_complexity(
        decoder: &dyn SurfaceCodeDecoder,
        distance: u32,
        error_rate: f64,
    ) -> ComplexityBound {
        let trials = 5u32;
        let mut total_ns = 0u64;

        for seed in 0..trials {
            let syndrome = Self::synthetic_syndrome(distance, error_rate, seed);
            let corr = decoder.decode(&syndrome);
            total_ns += corr.decode_time_ns;
        }

        let avg_ns = total_ns as f64 / trials as f64;
        let d = distance as f64;
        // Estimate scaling exponent from a single distance (rough).
        let alpha = if d > 1.0 {
            avg_ns.ln() / d.ln()
        } else {
            2.0
        };

        ComplexityBound {
            expected_ops: avg_ns,
            worst_case_ops: avg_ns * 5.0,
            probability_of_worst_case: error_rate.powf(distance as f64 / 2.0),
            scaling_exponent: alpha.min(3.0),
            crossover_distance: distance,
        }
    }

    /// Estimate threshold and logical error suppression from Monte-Carlo runs.
    pub fn threshold_analysis(
        error_rates: &[f64],
        distances: &[u32],
    ) -> ThresholdTheorem {
        // Standard surface code threshold estimate: ~1% for depolarizing noise.
        let p_th = 0.01;

        // Use the first (error_rate, distance) pair for the bound.
        let p = error_rates.first().copied().unwrap_or(0.001);
        let d = distances.first().copied().unwrap_or(3) as f64;

        let ratio = p / p_th;
        let suppression = d / 2.0;
        let p_l = ratio.powf(suppression);

        ThresholdTheorem {
            physical_threshold: p_th,
            logical_error_rate: p_l.min(1.0),
            suppression_exponent: suppression,
        }
    }

    /// Find the crossover code distance at which the hierarchical decoder
    /// becomes faster than a baseline decoder.
    pub fn crossover_point(
        hierarchical: &HierarchicalTiledDecoder,
        baseline: &dyn SurfaceCodeDecoder,
    ) -> u32 {
        let error_rate = 0.001;
        for d in (3..=101).step_by(2) {
            let syn = Self::synthetic_syndrome(d, error_rate, 42);
            let t_hier = {
                let c = hierarchical.decode(&syn);
                c.decode_time_ns
            };
            let t_base = {
                let c = baseline.decode(&syn);
                c.decode_time_ns
            };
            if t_hier < t_base {
                return d;
            }
        }
        // Default: hierarchical wins at large enough d.
        101
    }

    /// Generate a deterministic synthetic syndrome for benchmarking.
    fn synthetic_syndrome(distance: u32, error_rate: f64, seed: u32) -> SyndromeData {
        let grid_w = distance.saturating_sub(1).max(1);
        let mut stabs = Vec::with_capacity((grid_w * grid_w) as usize);
        let mut hash: u64 = 0x517c_c1b7_2722_0a95 ^ (seed as u64);

        for y in 0..grid_w {
            for x in 0..grid_w {
                // Simple hash-based PRNG.
                hash = hash.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
                let r = (hash >> 33) as f64 / (u32::MAX as f64);
                stabs.push(StabilizerMeasurement {
                    x,
                    y,
                    round: 0,
                    value: r < error_rate,
                });
            }
        }

        SyndromeData {
            stabilizers: stabs,
            code_distance: distance,
            num_rounds: 1,
        }
    }
}

// ---------------------------------------------------------------------------
// 5. DefectGraphBuilder
// ---------------------------------------------------------------------------

/// Spatial-hash-accelerated defect graph for efficient neighbor queries.
///
/// Defects are binned into cells of side `cell_size`. Neighbor lookups
/// scan only the O(1) adjacent cells, giving expected O(1) query time
/// for sparse defect densities (which is the regime of interest below
/// threshold).
pub struct DefectGraphBuilder {
    cell_size: u32,
}

/// An edge in the defect graph.
#[derive(Debug, Clone)]
pub struct DefectEdge {
    pub src: usize,
    pub dst: usize,
    pub weight: u32,
}

impl DefectGraphBuilder {
    pub fn new(cell_size: u32) -> Self {
        Self {
            cell_size: cell_size.max(1),
        }
    }

    /// Build a defect graph using spatial hashing for O(1) neighbor lookup.
    ///
    /// Returns edges connecting each defect to its nearest neighbors
    /// within `max_radius` Manhattan distance.
    pub fn build(&self, syndrome: &SyndromeData, max_radius: u32) -> Vec<DefectEdge> {
        let defects = extract_defects(syndrome);
        if defects.len() < 2 {
            return Vec::new();
        }

        // Spatial hash: key = (cell_x, cell_y, cell_r).
        let cs = self.cell_size;
        let mut cells: std::collections::HashMap<(u32, u32, u32), Vec<usize>> =
            std::collections::HashMap::new();

        for (i, d) in defects.iter().enumerate() {
            let key = (d.x / cs, d.y / cs, d.round / cs.max(1));
            cells.entry(key).or_default().push(i);
        }

        let mut edges = Vec::new();
        let search_radius = (max_radius + cs - 1) / cs;

        for (i, di) in defects.iter().enumerate() {
            let cx = di.x / cs;
            let cy = di.y / cs;
            let cr = di.round / cs.max(1);

            for dz in 0..=search_radius {
                for dy in 0..=search_radius {
                    for dx in 0..=search_radius {
                        // Check all sign combinations.
                        for &sx in &[0i64, -(dx as i64), dx as i64] {
                            for &sy in &[0i64, -(dy as i64), dy as i64] {
                                for &sr in &[0i64, -(dz as i64), dz as i64] {
                                    let nx = cx as i64 + sx;
                                    let ny = cy as i64 + sy;
                                    let nr = cr as i64 + sr;
                                    if nx < 0 || ny < 0 || nr < 0 {
                                        continue;
                                    }
                                    let key = (nx as u32, ny as u32, nr as u32);
                                    if let Some(bucket) = cells.get(&key) {
                                        for &j in bucket {
                                            if j <= i {
                                                continue;
                                            }
                                            let w = manhattan(di, &defects[j]);
                                            if w <= max_radius {
                                                edges.push(DefectEdge {
                                                    src: i,
                                                    dst: j,
                                                    weight: w,
                                                });
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }

        // Deduplicate edges.
        edges.sort_by_key(|e| (e.src, e.dst));
        edges.dedup_by_key(|e| (e.src, e.dst));
        edges
    }
}

// ---------------------------------------------------------------------------
// 6. Benchmark functions
// ---------------------------------------------------------------------------

/// A single data point from empirical scaling measurements.
#[derive(Debug, Clone)]
pub struct ScalingDataPoint {
    pub distance: u32,
    pub mean_decode_ns: f64,
    pub std_decode_ns: f64,
    pub num_samples: u32,
}

/// Result of a statistical test for subpolynomial scaling.
#[derive(Debug, Clone)]
pub struct SubpolyVerification {
    /// Fitted exponent alpha in T ~ d^alpha.
    pub fitted_exponent: f64,
    /// Whether alpha < 2.0 (subquadratic).
    pub is_subquadratic: bool,
    /// R-squared of the power-law fit.
    pub r_squared: f64,
}

/// Measure empirical decode time scaling across code distances.
pub fn benchmark_scaling(
    distances: &[u32],
    error_rate: f64,
) -> Vec<ScalingDataPoint> {
    let samples_per_d = 20u32;
    let decoder = HierarchicalTiledDecoder::new(4, 3);
    let mut data = Vec::with_capacity(distances.len());

    for &d in distances {
        let mut times = Vec::with_capacity(samples_per_d as usize);
        for seed in 0..samples_per_d {
            let syn = ComplexityAnalyzer::synthetic_syndrome(d, error_rate, seed);
            let corr = decoder.decode(&syn);
            times.push(corr.decode_time_ns as f64);
        }
        let n = times.len() as f64;
        let mean = times.iter().sum::<f64>() / n;
        let var = times.iter().map(|t| (t - mean).powi(2)).sum::<f64>() / n;
        data.push(ScalingDataPoint {
            distance: d,
            mean_decode_ns: mean,
            std_decode_ns: var.sqrt(),
            num_samples: samples_per_d,
        });
    }
    data
}

/// Statistical test for subpolynomial (subquadratic) scaling.
///
/// Fits log(T) = alpha log(d) + beta via ordinary least squares and
/// checks whether alpha < 2.
pub fn verify_subpolynomial(data: &[ScalingDataPoint]) -> SubpolyVerification {
    if data.len() < 2 {
        return SubpolyVerification {
            fitted_exponent: f64::NAN,
            is_subquadratic: false,
            r_squared: 0.0,
        };
    }

    // OLS on (log d, log T).
    let points: Vec<(f64, f64)> = data
        .iter()
        .filter(|p| p.distance > 1 && p.mean_decode_ns > 0.0)
        .map(|p| ((p.distance as f64).ln(), p.mean_decode_ns.ln()))
        .collect();

    if points.len() < 2 {
        return SubpolyVerification {
            fitted_exponent: f64::NAN,
            is_subquadratic: false,
            r_squared: 0.0,
        };
    }

    let n = points.len() as f64;
    let sx: f64 = points.iter().map(|(x, _)| x).sum();
    let sy: f64 = points.iter().map(|(_, y)| y).sum();
    let sxx: f64 = points.iter().map(|(x, _)| x * x).sum();
    let sxy: f64 = points.iter().map(|(x, y)| x * y).sum();

    let denom = n * sxx - sx * sx;
    let alpha = if denom.abs() > 1e-15 {
        (n * sxy - sx * sy) / denom
    } else {
        f64::NAN
    };

    let beta = (sy - alpha * sx) / n;

    // R-squared.
    let y_mean = sy / n;
    let ss_tot: f64 = points.iter().map(|(_, y)| (y - y_mean).powi(2)).sum();
    let ss_res: f64 = points
        .iter()
        .map(|(x, y)| (y - (alpha * x + beta)).powi(2))
        .sum();
    let r2 = if ss_tot > 1e-15 {
        1.0 - ss_res / ss_tot
    } else {
        0.0
    };

    SubpolyVerification {
        fitted_exponent: alpha,
        is_subquadratic: alpha < 2.0,
        r_squared: r2,
    }
}

// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------

#[cfg(test)]
mod tests {
    use super::*;

    fn simple_syndrome(d: u32, defect_positions: &[(u32, u32)]) -> SyndromeData {
        let grid_w = d.saturating_sub(1).max(1);
        let mut stabs = Vec::new();
        for y in 0..grid_w {
            for x in 0..grid_w {
                let val = defect_positions.iter().any(|&(dx, dy)| dx == x && dy == y);
                stabs.push(StabilizerMeasurement {
                    x,
                    y,
                    round: 0,
                    value: val,
                });
            }
        }
        SyndromeData {
            stabilizers: stabs,
            code_distance: d,
            num_rounds: 1,
        }
    }

    // -- HierarchicalTiledDecoder --

    #[test]
    fn hierarchical_no_errors() {
        let dec = HierarchicalTiledDecoder::new(2, 2);
        let syn = simple_syndrome(5, &[]);
        let corr = dec.decode(&syn);
        assert!(corr.pauli_corrections.is_empty());
        assert_eq!(corr.confidence, 1.0);
    }

    #[test]
    fn hierarchical_single_defect() {
        let dec = HierarchicalTiledDecoder::new(2, 2);
        let syn = simple_syndrome(5, &[(1, 1)]);
        let corr = dec.decode(&syn);
        assert!(!corr.pauli_corrections.is_empty());
    }

    #[test]
    fn hierarchical_paired_defects() {
        let dec = HierarchicalTiledDecoder::new(2, 2);
        let syn = simple_syndrome(5, &[(0, 0), (1, 0)]);
        let corr = dec.decode(&syn);
        assert!(!corr.pauli_corrections.is_empty());
    }

    #[test]
    fn hierarchical_name() {
        let dec = HierarchicalTiledDecoder::new(4, 3);
        assert_eq!(dec.name(), "HierarchicalTiledDecoder");
    }

    #[test]
    fn hierarchical_complexity_bound() {
        let dec = HierarchicalTiledDecoder::new(4, 3);
        let cb = dec.complexity_bound(21, 0.001);
        assert!(cb.scaling_exponent < 2.1);
        assert!(cb.expected_ops > 0.0);
        assert!(cb.crossover_distance >= 5);
    }

    #[test]
    fn hierarchical_trait_object() {
        let dec: Box<dyn SurfaceCodeDecoder> =
            Box::new(HierarchicalTiledDecoder::new(2, 2));
        let syn = simple_syndrome(3, &[(0, 0)]);
        let _ = dec.decode(&syn);
        assert_eq!(dec.name(), "HierarchicalTiledDecoder");
    }

    // -- RenormalizationDecoder --

    #[test]
    fn renorm_no_errors() {
        let dec = RenormalizationDecoder::new(2, 4);
        let syn = simple_syndrome(5, &[]);
        let corr = dec.decode(&syn);
        assert!(corr.pauli_corrections.is_empty());
    }

    #[test]
    fn renorm_single_defect() {
        let dec = RenormalizationDecoder::new(2, 4);
        let syn = simple_syndrome(5, &[(2, 2)]);
        let corr = dec.decode(&syn);
        assert!(!corr.pauli_corrections.is_empty());
    }

    #[test]
    fn renorm_paired() {
        let dec = RenormalizationDecoder::new(2, 3);
        let syn = simple_syndrome(7, &[(1, 1), (2, 1)]);
        let corr = dec.decode(&syn);
        assert!(!corr.pauli_corrections.is_empty());
    }

    #[test]
    fn renorm_name() {
        let dec = RenormalizationDecoder::new(2, 3);
        assert_eq!(dec.name(), "RenormalizationDecoder");
    }

    // -- SlidingWindowDecoder --

    #[test]
    fn sliding_no_errors() {
        let dec = SlidingWindowDecoder::new(2);
        let syn = simple_syndrome(5, &[]);
        let corr = dec.decode(&syn);
        assert!(corr.pauli_corrections.is_empty());
    }

    #[test]
    fn sliding_single_round() {
        let dec = SlidingWindowDecoder::new(1);
        let syn = simple_syndrome(5, &[(0, 0)]);
        let corr = dec.decode(&syn);
        assert!(!corr.pauli_corrections.is_empty());
    }

    #[test]
    fn sliding_multi_round() {
        let dec = SlidingWindowDecoder::new(2);
        let stabs = vec![
            StabilizerMeasurement { x: 0, y: 0, round: 0, value: true },
            StabilizerMeasurement { x: 0, y: 0, round: 1, value: false },
            StabilizerMeasurement { x: 0, y: 0, round: 2, value: true },
        ];
        let syn = SyndromeData {
            stabilizers: stabs,
            code_distance: 3,
            num_rounds: 3,
        };
        let corr = dec.decode(&syn);
        // Defects at round boundaries should produce corrections.
        assert!(!corr.pauli_corrections.is_empty());
    }

    #[test]
    fn sliding_name() {
        let dec = SlidingWindowDecoder::new(3);
        assert_eq!(dec.name(), "SlidingWindowDecoder");
    }

    // -- ComplexityAnalyzer --

    #[test]
    fn analyze_complexity_runs() {
        let dec = HierarchicalTiledDecoder::new(2, 2);
        let cb = ComplexityAnalyzer::analyze_complexity(&dec, 5, 0.001);
        assert!(cb.expected_ops > 0.0);
        assert!(cb.worst_case_ops >= cb.expected_ops);
    }

    #[test]
    fn threshold_analysis_basic() {
        let tt = ComplexityAnalyzer::threshold_analysis(&[0.001], &[5]);
        assert!(tt.physical_threshold > 0.0);
        assert!(tt.logical_error_rate < 1.0);
        assert!(tt.suppression_exponent > 0.0);
    }

    #[test]
    fn crossover_point_returns_valid() {
        let hier = HierarchicalTiledDecoder::new(2, 2);
        let baseline = crate::decoder::UnionFindDecoder::new(0);
        let cp = ComplexityAnalyzer::crossover_point(&hier, &baseline);
        assert!(cp >= 3);
    }

    // -- DefectGraphBuilder --

    #[test]
    fn defect_graph_empty() {
        let builder = DefectGraphBuilder::new(4);
        let syn = simple_syndrome(5, &[]);
        let edges = builder.build(&syn, 10);
        assert!(edges.is_empty());
    }

    #[test]
    fn defect_graph_two_nearby() {
        let builder = DefectGraphBuilder::new(4);
        let syn = simple_syndrome(5, &[(0, 0), (1, 0)]);
        let edges = builder.build(&syn, 10);
        assert!(!edges.is_empty());
        assert_eq!(edges[0].weight, 1);
    }

    #[test]
    fn defect_graph_far_apart() {
        let builder = DefectGraphBuilder::new(2);
        let syn = simple_syndrome(11, &[(0, 0), (9, 9)]);
        let edges = builder.build(&syn, 3);
        // Distance is 18 > 3, so no edge.
        assert!(edges.is_empty());
    }

    // -- Benchmarks --

    #[test]
    fn benchmark_scaling_runs() {
        let data = benchmark_scaling(&[3, 5, 7], 0.001);
        assert_eq!(data.len(), 3);
        for pt in &data {
            assert!(pt.mean_decode_ns >= 0.0);
        }
    }

    #[test]
    fn verify_subpolynomial_basic() {
        let data = benchmark_scaling(&[3, 5, 7, 9], 0.001);
        let result = verify_subpolynomial(&data);
        // Just verify it produces a valid result.
        assert!(!result.fitted_exponent.is_nan());
    }

    #[test]
    fn verify_subpolynomial_insufficient_data() {
        let result = verify_subpolynomial(&[]);
        assert!(result.fitted_exponent.is_nan());
        assert!(!result.is_subquadratic);
    }
}
