//! Large-scale triangulation debug harness.
//!
//! This module is intended as a **manual debugging tool** for issues that only show up
//! at large vertex counts (slow cases, rare geometric degeneracies, topology/Delaunay
//! validation failures, etc.).
//!
//! The tests are `#[ignore]` by default.
//!
//! ## Usage
//!
//! Run one dimension with full output:
//! ```bash
//! cargo test --release --test large_scale_debug debug_large_scale_2d -- --ignored --nocapture
//! ```
//!
//! The `just debug-large-scale-{2,3,4,5}d` helpers all accept the same
//! `[n] [repair_every]` arguments. The checked-in defaults are calibrated as
//! roughly one-minute release-mode acceptance/profiling runs on maintainer Apple
//! M4 Max hardware:
//!
//! - 2D: 40,000 vertices
//! - 3D: 8,000 vertices
//! - 4D: 900 vertices
//! - 5D: 150 vertices
//!
//! Each should insert all vertices with zero skips, run final repair, and pass
//! `validation_report` for Levels 1–4. Use local harness output for exact
//! timing.
//!
//! Override defaults via environment variables:
//! ```bash
//! # Base RNG seed (decimal or 0x-hex)
//! DELAUNAY_LARGE_DEBUG_SEED=0xDEADBEEF \
//! # Optional explicit case seed (overrides derived seed_for_case)
//! DELAUNAY_LARGE_DEBUG_CASE_SEED=0x12345678 \
//! # Override point count for the selected test
//! DELAUNAY_LARGE_DEBUG_N=10000 \
//! # Point distribution: "ball" (default) or "box"
//! DELAUNAY_LARGE_DEBUG_DISTRIBUTION=ball \
//! # Ball radius (default: 100) [used when distribution=ball]
//! DELAUNAY_LARGE_DEBUG_BALL_RADIUS=100 \
//! # Box half-width (default: 100) [used when distribution=box]
//! DELAUNAY_LARGE_DEBUG_BOX_HALF_WIDTH=100 \
//! # Construction mode:
//! # - "new" (default): build via DelaunayTriangulation::new() which applies Hilbert ordering
//! # - "incremental": manual insert loop (debug/profiling)
//! DELAUNAY_LARGE_DEBUG_CONSTRUCTION_MODE=new \
//! # Initial simplex strategy for batch construction: "max-volume" (default), "balanced", or "first"
//! DELAUNAY_LARGE_DEBUG_INITIAL_SIMPLEX=max-volume \
//! # Debug mode:
//! # - "cadenced" (default): PLManifold, ridge-link validation during insertion,
//! #   vertex-link validation at completion
//! # - "strict": PLManifoldStrict, vertex-link validation after every insertion
//! DELAUNAY_LARGE_DEBUG_DEBUG_MODE=cadenced \
//! # Deterministically shuffle insertion order (incremental mode only)
//! DELAUNAY_LARGE_DEBUG_SHUFFLE_SEED=123 \
//! # Print progress every N insertions (incremental mode only)
//! DELAUNAY_LARGE_DEBUG_PROGRESS_EVERY=1000 \
//! # (Optional) validate topology every N insertions once cells exist (incremental mode only; can be expensive)
//! DELAUNAY_LARGE_DEBUG_VALIDATE_EVERY=2000 \
//! # Maximum skipped-vertex percentage before the run fails (default: 5.0)
//! DELAUNAY_LARGE_DEBUG_MAX_SKIP_PCT=5.0 \
//! # Allow any number of skipped vertices (bypasses DELAUNAY_LARGE_DEBUG_MAX_SKIP_PCT)
//! DELAUNAY_LARGE_DEBUG_ALLOW_SKIPS=1 \
//! # Skip the final flip-based repair pass (faster, but may leave Delaunay violations)
//! DELAUNAY_LARGE_DEBUG_SKIP_FINAL_REPAIR=1 \
//! # Run bounded flip repair every N successful insertions (0 disables; default: 1)
//! DELAUNAY_LARGE_DEBUG_REPAIR_EVERY=1 \
//! # Optional: trace cadenced local-repair seed counts, flips, queues, and elapsed time
//! DELAUNAY_BATCH_REPAIR_TRACE=1 \
//! # Hard wall-clock cap in seconds before the harness aborts (0 = no cap; default: 600)
//! DELAUNAY_LARGE_DEBUG_MAX_RUNTIME_SECS=600 \
//! # Optional: emit periodic batch-construction summaries for new()/Hilbert runs
//! DELAUNAY_BULK_PROGRESS_EVERY=100 \
//! # Optional: dump the first cavity reduction chain once per run
//! DELAUNAY_DEBUG_CAVITY_REDUCTION_ONCE=1 \
//! # Optional: trace retryable conflict-region skips with attempt/rollback details
//! DELAUNAY_DEBUG_RETRYABLE_SKIP=1 \
//! # Optional: dump the first detected ridge-fan cavity snapshot once per run
//! DELAUNAY_DEBUG_RIDGE_FAN_ONCE=1 \
//! # Optional: dump the first unresolved repair postcondition facet once per run
//! DELAUNAY_REPAIR_DEBUG_POSTCONDITION_FACET=1 \
//! # Optional: only emit ridge repair debug when multiplicity is at least N
//! DELAUNAY_REPAIR_DEBUG_RIDGE_MIN_MULTIPLICITY=4 \
//! cargo test --release --test large_scale_debug debug_large_scale_4d -- --ignored --nocapture
//! ```

#![forbid(unsafe_code)]

use delaunay::geometry::kernel::{ExactPredicates, Kernel, RobustKernel};
use delaunay::geometry::util::{
    generate_random_points_in_ball_seeded, generate_random_points_seeded, safe_usize_to_scalar,
};
use delaunay::prelude::tds::{InvariantKind, TriangulationValidationReport};
use delaunay::prelude::triangulation::construction::{
    ConstructionOptions, ConstructionStatistics, DelaunayRepairPolicy, DelaunayTriangulation,
    DelaunayTriangulationConstructionErrorWithStatistics, InitialSimplexStrategy,
    TopologyGuarantee, Vertex, vertex,
};
use delaunay::prelude::triangulation::diagnostics::ConstructionTelemetry;
#[cfg(feature = "diagnostics")]
use delaunay::prelude::triangulation::insertion::InsertionResult;
use delaunay::prelude::triangulation::insertion::{InsertionOutcome, InsertionStatistics};
use delaunay::prelude::triangulation::repair::{
    DelaunayCheckPolicy, DelaunayRepairHeuristicConfig,
};
use delaunay::prelude::triangulation::validation::ValidationCadence;
use rand::{SeedableRng, rngs::StdRng, seq::SliceRandom};
use std::env;
use std::fmt;
use std::io::{self, Write};
use std::num::NonZeroUsize;
use std::process;
use std::sync::{
    Once,
    mpsc::{self, RecvTimeoutError, SyncSender},
};
use std::thread;
use std::time::{Duration, Instant};

/// Writes the timeout diagnostic synchronously so it survives the watchdog abort.
fn write_timeout_abort_message<W: Write>(mut writer: W, max_secs: u64) -> io::Result<()> {
    writeln!(
        writer,
        "=== TIMEOUT: wall time exceeded {max_secs} seconds — aborting ==="
    )?;
    writer.flush()
}

/// Installs a per-test wall-clock cap.
///
/// Spawns a watchdog thread that calls [`process::abort`] if `max_secs` elapses.
/// Returns a [`SyncSender`] whose **drop** cancels the watchdog: when
/// the sender is dropped (i.e. the test completes normally), the channel disconnects and
/// the watchdog thread exits without aborting.  This prevents a stale watchdog installed
/// for one test from firing during a subsequent test.
fn install_runtime_cap(max_secs: u64) -> SyncSender<()> {
    let (tx, rx) = mpsc::sync_channel::<()>(0);
    thread::spawn(move || {
        match rx.recv_timeout(Duration::from_secs(max_secs)) {
            // Sender dropped (test finished) or explicit send — exit cleanly.
            Ok(()) | Err(RecvTimeoutError::Disconnected) => {}
            // Deadline exceeded — hard abort.
            Err(RecvTimeoutError::Timeout) => {
                if let Err(err) = write_timeout_abort_message(io::stderr().lock(), max_secs) {
                    tracing::warn!(?err, "failed to flush timeout message before abort");
                }
                process::abort();
            }
        }
    });
    tx
}

#[derive(Debug, Clone)]
struct SkipSample<const D: usize> {
    index: usize,
    uuid: uuid::Uuid,
    coords: Option<[f64; D]>,
    attempts: usize,
    error: String,
}

#[derive(Debug, Clone)]
#[cfg(feature = "diagnostics")]
struct SlowInsertionSample {
    index: usize,
    uuid: uuid::Uuid,
    attempts: usize,
    result: InsertionResult,
    elapsed_nanos: u64,
    cells_after: usize,
    locate_calls: usize,
    locate_walk_steps_total: usize,
    conflict_region_calls: usize,
    conflict_region_cells_total: usize,
    cavity_insertion_calls: usize,
    global_conflict_scans: usize,
    hull_extension_calls: usize,
    topology_validation_calls: usize,
}

#[derive(Debug, Default, Clone)]
struct InsertionSummary<const D: usize> {
    inserted: usize,
    skipped_duplicate: usize,
    skipped_degeneracy: usize,

    total_attempts: usize,
    max_attempts: usize,
    attempts_histogram: Vec<usize>,

    used_perturbation: usize,

    cells_removed_total: usize,
    cells_removed_max: usize,

    telemetry: ConstructionTelemetry,

    #[cfg(feature = "diagnostics")]
    slow_insertions: Vec<SlowInsertionSample>,
    skip_samples: Vec<SkipSample<D>>,
}

impl<const D: usize> InsertionSummary<D> {
    fn record(&mut self, stats: InsertionStatistics) {
        self.total_attempts = self.total_attempts.saturating_add(stats.attempts);
        self.max_attempts = self.max_attempts.max(stats.attempts);

        if self.attempts_histogram.len() <= stats.attempts {
            self.attempts_histogram.resize(stats.attempts + 1, 0);
        }
        self.attempts_histogram[stats.attempts] =
            self.attempts_histogram[stats.attempts].saturating_add(1);

        if stats.used_perturbation() {
            self.used_perturbation = self.used_perturbation.saturating_add(1);
        }

        self.cells_removed_total = self
            .cells_removed_total
            .saturating_add(stats.cells_removed_during_repair);
        self.cells_removed_max = self
            .cells_removed_max
            .max(stats.cells_removed_during_repair);
    }

    fn record_inserted(&mut self, stats: InsertionStatistics) {
        self.inserted = self.inserted.saturating_add(1);
        self.record(stats);
    }

    fn record_skipped(&mut self, sample: SkipSample<D>, stats: InsertionStatistics) {
        if stats.skipped_duplicate() {
            self.skipped_duplicate = self.skipped_duplicate.saturating_add(1);
        } else {
            self.skipped_degeneracy = self.skipped_degeneracy.saturating_add(1);
        }

        // Keep the first few skip samples so we have concrete reproduction anchors.
        if self.skip_samples.len() < 8 {
            self.skip_samples.push(sample);
        }

        self.record(stats);
    }

    const fn total_skipped(&self) -> usize {
        self.skipped_duplicate + self.skipped_degeneracy
    }
}

impl<const D: usize> From<ConstructionStatistics> for InsertionSummary<D> {
    fn from(stats: ConstructionStatistics) -> Self {
        #[cfg(feature = "diagnostics")]
        let slow_insertions = stats
            .slow_insertions
            .into_iter()
            .map(|sample| SlowInsertionSample {
                index: sample.index,
                uuid: sample.uuid,
                attempts: sample.attempts,
                result: sample.result,
                elapsed_nanos: sample.elapsed_nanos,
                cells_after: sample.cells_after,
                locate_calls: sample.locate_calls,
                locate_walk_steps_total: sample.locate_walk_steps_total,
                conflict_region_calls: sample.conflict_region_calls,
                conflict_region_cells_total: sample.conflict_region_cells_total,
                cavity_insertion_calls: sample.cavity_insertion_calls,
                global_conflict_scans: sample.global_conflict_scans,
                hull_extension_calls: sample.hull_extension_calls,
                topology_validation_calls: sample.topology_validation_calls,
            })
            .collect();
        let skip_samples: Vec<SkipSample<D>> = stats
            .skip_samples
            .into_iter()
            .map(|s| {
                let coords = if s.coords_available {
                    s.coords.as_slice().try_into().map_or_else(
                        |_| {
                            tracing::warn!(
                                index = s.index,
                                uuid = %s.uuid,
                                coords_len = s.coords.len(),
                                expected_dim = D,
                                "preserving skip sample without coordinates due to coordinate dimension mismatch"
                            );
                            None
                        },
                        Some,
                    )
                } else {
                    None
                };
                SkipSample {
                    index: s.index,
                    uuid: s.uuid,
                    coords,
                    attempts: s.attempts,
                    error: s.error,
                }
            })
            .collect();

        Self {
            inserted: stats.inserted,
            skipped_duplicate: stats.skipped_duplicate,
            skipped_degeneracy: stats.skipped_degeneracy,
            total_attempts: stats.total_attempts,
            max_attempts: stats.max_attempts,
            attempts_histogram: stats.attempts_histogram,
            used_perturbation: stats.used_perturbation,
            cells_removed_total: stats.cells_removed_total,
            cells_removed_max: stats.cells_removed_max,
            telemetry: stats.telemetry,
            #[cfg(feature = "diagnostics")]
            slow_insertions,
            skip_samples,
        }
    }
}

fn parse_u64(s: &str) -> Option<u64> {
    let s = s.trim();
    s.strip_prefix("0x")
        .or_else(|| s.strip_prefix("0X"))
        .map_or_else(|| s.parse().ok(), |hex| u64::from_str_radix(hex, 16).ok())
}

fn env_u64(name: &str) -> Option<u64> {
    env::var(name).ok().and_then(|v| parse_u64(&v))
}

fn env_usize(name: &str) -> Option<usize> {
    env::var(name).ok().and_then(|v| {
        let trimmed = v.trim();
        trimmed.parse().ok().or_else(|| {
            trimmed
                .split_once('=')
                .and_then(|(_, rhs)| rhs.trim().parse().ok())
        })
    })
}

fn env_flag(name: &str) -> bool {
    env::var(name).ok().is_some_and(|v| {
        let v = v.trim();
        !v.is_empty() && v != "0" && v != "false"
    })
}

fn init_tracing() {
    static INIT: Once = Once::new();
    INIT.call_once(|| {
        // Debug-level tracing is needed to surface the release-visible diagnostic hooks
        // (retryable-skip, cavity-reduction, ridge/postcondition repair debug,
        // bulk-progress, bulk-retry) emitted through `tracing::debug!` inside the library.
        let debug_env_vars = [
            "DELAUNAY_INSERT_TRACE",
            "DELAUNAY_DEBUG_RETRYABLE_SKIP",
            "DELAUNAY_DEBUG_CAVITY_REDUCTION_ONCE",
            "DELAUNAY_DEBUG_RIDGE_FAN_ONCE",
            "DELAUNAY_REPAIR_DEBUG_POSTCONDITION_FACET",
            "DELAUNAY_REPAIR_DEBUG_RIDGE_MIN_MULTIPLICITY",
            "DELAUNAY_BULK_PROGRESS_EVERY",
            "DELAUNAY_BATCH_REPAIR_TRACE",
            "DELAUNAY_DEBUG_SHUFFLE",
        ];
        let default_filter = if debug_env_vars
            .iter()
            .any(|name| env::var_os(name).is_some())
        {
            "debug"
        } else {
            "warn"
        };
        let filter = tracing_subscriber::EnvFilter::try_from_default_env()
            .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new(default_filter));
        let _ = tracing_subscriber::fmt()
            .with_env_filter(filter)
            .with_test_writer()
            .try_init();
    });
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum PointDistribution {
    Ball,
    Box,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum ConstructionMode {
    /// Build via `DelaunayTriangulation::new()` (batch construction + Hilbert ordering).
    New,
    /// Insert vertices one by one (manual incremental construction).
    Incremental,
}

impl ConstructionMode {
    const fn name(self) -> &'static str {
        match self {
            Self::New => "new",
            Self::Incremental => "incremental",
        }
    }
}

fn initial_simplex_strategy_name(strategy: InitialSimplexStrategy) -> &'static str {
    match strategy {
        InitialSimplexStrategy::First => "first",
        InitialSimplexStrategy::Balanced => "balanced",
        InitialSimplexStrategy::MaxVolume => "max-volume",
        _ => {
            tracing::debug!(?strategy, "unknown initial simplex strategy");
            "unknown"
        }
    }
}

fn initial_simplex_strategy_from_name(raw: &str) -> Option<InitialSimplexStrategy> {
    let raw = raw.trim();
    if raw.is_empty() {
        return Some(InitialSimplexStrategy::MaxVolume);
    }

    if raw.eq_ignore_ascii_case("first") {
        return Some(InitialSimplexStrategy::First);
    }

    if raw.eq_ignore_ascii_case("balanced") {
        return Some(InitialSimplexStrategy::Balanced);
    }

    if raw.eq_ignore_ascii_case("max-volume")
        || raw.eq_ignore_ascii_case("max_volume")
        || raw.eq_ignore_ascii_case("maxvolume")
    {
        return Some(InitialSimplexStrategy::MaxVolume);
    }

    None
}

fn initial_simplex_strategy_from_env() -> InitialSimplexStrategy {
    let Ok(raw) = env::var("DELAUNAY_LARGE_DEBUG_INITIAL_SIMPLEX") else {
        return InitialSimplexStrategy::MaxVolume;
    };

    initial_simplex_strategy_from_name(&raw).unwrap_or_else(|| {
        panic!(
            "invalid DELAUNAY_LARGE_DEBUG_INITIAL_SIMPLEX={raw:?} (expected 'max-volume', 'balanced', or 'first')"
        )
    })
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum DebugMode {
    /// Faster default: repair/check in cadence, with suspicion-driven automatic validation.
    Cadenced,
    /// Maximal diagnostics: strict guarantee + per-insertion automatic validation.
    Strict,
}

impl DebugMode {
    const fn name(self) -> &'static str {
        match self {
            Self::Cadenced => "cadenced",
            Self::Strict => "strict",
        }
    }
}

/// Selects the topology guarantee that matches the requested debug intensity.
const fn topology_for_debug_mode(debug_mode: DebugMode) -> TopologyGuarantee {
    match debug_mode {
        DebugMode::Cadenced => TopologyGuarantee::PLManifold,
        DebugMode::Strict => TopologyGuarantee::PLManifoldStrict,
    }
}

/// Converts the repair cadence knob into the policy shared by batch and incremental modes.
const fn repair_policy_from_repair_every(repair_every: usize) -> DelaunayRepairPolicy {
    match NonZeroUsize::new(repair_every) {
        None => DelaunayRepairPolicy::Never,
        Some(n) if n.get() == 1 => DelaunayRepairPolicy::EveryInsertion,
        Some(n) => DelaunayRepairPolicy::EveryN(n),
    }
}

impl PointDistribution {
    const fn name(self) -> &'static str {
        match self {
            Self::Ball => "ball",
            Self::Box => "box",
        }
    }
}

/// Reads the skipped-vertex percentage budget for large-scale debug runs.
fn max_skip_pct_from_env() -> f64 {
    let max_skip_pct = env_f64("DELAUNAY_LARGE_DEBUG_MAX_SKIP_PCT").unwrap_or(5.0);
    assert!(
        max_skip_pct.is_finite() && max_skip_pct >= 0.0,
        "invalid DELAUNAY_LARGE_DEBUG_MAX_SKIP_PCT={max_skip_pct:?} (expected finite non-negative percentage)"
    );
    max_skip_pct
}

/// Computes the skipped-vertex percentage for budget checks and reporting.
fn skip_percentage(skipped: usize, total: usize) -> f64 {
    if total == 0 {
        return 0.0;
    }

    let skipped = safe_usize_to_scalar::<f64>(skipped)
        .expect("skipped-vertex count should fit in f64 for debug reporting");
    let total = safe_usize_to_scalar::<f64>(total)
        .expect("point count should fit in f64 for debug reporting");
    (skipped / total) * 100.0
}

fn env_f64(name: &str) -> Option<f64> {
    let Ok(raw) = env::var(name) else {
        return None;
    };

    let raw = raw.trim();
    if raw.is_empty() {
        return None;
    }

    Some(
        raw.parse()
            .unwrap_or_else(|e| panic!("invalid {name}={raw:?}: {e}")),
    )
}

fn point_distribution_from_env() -> PointDistribution {
    let Ok(raw) = env::var("DELAUNAY_LARGE_DEBUG_DISTRIBUTION") else {
        return PointDistribution::Ball;
    };

    let raw = raw.trim();
    if raw.is_empty() || raw.eq_ignore_ascii_case("ball") {
        return PointDistribution::Ball;
    }

    if raw.eq_ignore_ascii_case("box") {
        return PointDistribution::Box;
    }

    panic!("invalid DELAUNAY_LARGE_DEBUG_DISTRIBUTION={raw:?} (expected 'ball' or 'box')");
}

fn construction_mode_from_env() -> ConstructionMode {
    let Ok(raw) = env::var("DELAUNAY_LARGE_DEBUG_CONSTRUCTION_MODE") else {
        return ConstructionMode::New;
    };

    let raw = raw.trim();
    if raw.is_empty() || raw.eq_ignore_ascii_case("new") {
        return ConstructionMode::New;
    }

    if raw.eq_ignore_ascii_case("incremental") {
        return ConstructionMode::Incremental;
    }

    panic!(
        "invalid DELAUNAY_LARGE_DEBUG_CONSTRUCTION_MODE={raw:?} (expected 'new' or 'incremental')"
    );
}

fn debug_mode_from_env() -> DebugMode {
    let Ok(raw) = env::var("DELAUNAY_LARGE_DEBUG_DEBUG_MODE") else {
        return DebugMode::Cadenced;
    };

    let raw = raw.trim();
    if raw.is_empty() || raw.eq_ignore_ascii_case("cadenced") {
        return DebugMode::Cadenced;
    }

    if raw.eq_ignore_ascii_case("strict") {
        return DebugMode::Strict;
    }

    panic!("invalid DELAUNAY_LARGE_DEBUG_DEBUG_MODE={raw:?} (expected 'cadenced' or 'strict')");
}

fn seed_for_case<const D: usize>(base_seed: u64, n_points: usize) -> u64 {
    const SEED_SALT: u64 = 0x9E37_79B9_7F4A_7C15;
    base_seed
        .wrapping_add((n_points as u64).wrapping_mul(SEED_SALT))
        .wrapping_add((D as u64).wrapping_mul(SEED_SALT.rotate_left(17)))
}
/// Classifies debug harness outcomes into distinct categories for structured reporting.
///
/// The harness still prints full diagnostic output during the run; this enum captures
/// the final outcome so callers can emit a single summary line and fail tests explicitly.
#[derive(Debug, Clone)]
enum DebugOutcome {
    Success,
    ConstructionFailure {
        error: String,
    },
    SkippedVertices {
        skipped: usize,
        total: usize,
        skip_pct: f64,
        max_skip_pct: f64,
    },
    RepairNonConvergence {
        error: String,
    },
    ValidationFailure {
        kind: InvariantKind,
        details: String,
    },
}

impl fmt::Display for DebugOutcome {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Success => write!(f, "Success"),
            Self::ConstructionFailure { error } => {
                write!(f, "ConstructionFailure | {error}")
            }
            Self::SkippedVertices {
                skipped,
                total,
                skip_pct,
                max_skip_pct,
            } => {
                write!(
                    f,
                    "SkippedVertices | {skipped}/{total} skipped ({skip_pct:.2}%, max {max_skip_pct:.2}%)"
                )
            }
            Self::RepairNonConvergence { error } => {
                write!(f, "RepairNonConvergence | {error}")
            }
            Self::ValidationFailure { kind, details } => {
                write!(f, "ValidationFailure({kind:?}) | {details}")
            }
        }
    }
}

fn print_abort_summary<const D: usize>(
    outcome: &DebugOutcome,
    seed: u64,
    n_points: usize,
    phase: &str,
) {
    println!();
    println!("OUTCOME: {outcome}");
    println!("Phase:   {phase}");
    println!();
    println!("Replay command:");
    println!(
        "  DELAUNAY_LARGE_DEBUG_N_{D}D={n_points} DELAUNAY_LARGE_DEBUG_CASE_SEED_{D}D=0x{seed:X} DELAUNAY_LARGE_DEBUG_ALLOW_SKIPS=1 cargo test --test large_scale_debug debug_large_scale_{D}d -- --ignored --nocapture"
    );
}

fn classify_validation_report(report: &TriangulationValidationReport) -> DebugOutcome {
    let first = report.violations.first();
    let (kind, details) = first.map_or_else(
        || {
            (
                InvariantKind::Topology,
                "no violations captured".to_string(),
            )
        },
        |violation| (violation.kind, format!("{}", violation.error)),
    );
    DebugOutcome::ValidationFailure { kind, details }
}

fn print_validation_report(report: &TriangulationValidationReport) {
    println!(
        "Validation report: {} violation(s)",
        report.violations.len()
    );
    for (idx, violation) in report.violations.iter().enumerate() {
        println!("  {}. {:?}: {}", idx + 1, violation.kind, violation.error);
    }
}

#[cfg(feature = "diagnostics")]
fn usize_to_u128(value: usize) -> u128 {
    u128::try_from(value).expect("usize should always fit in u128")
}

#[cfg(feature = "diagnostics")]
fn format_ratio_2(numerator: usize, denominator: usize) -> String {
    format_ratio_2_u128(usize_to_u128(numerator), usize_to_u128(denominator))
}

#[cfg(feature = "diagnostics")]
fn format_ratio_2_u128(numerator: u128, denominator: u128) -> String {
    if denominator == 0 {
        return "0.00".to_string();
    }

    let scaled = numerator.saturating_mul(100) / denominator;
    format!("{}.{:02}", scaled / 100, scaled % 100)
}

#[cfg(feature = "diagnostics")]
fn format_nanos_as_ms(nanos: u64) -> String {
    let micros = u128::from(nanos) / 1_000;
    format!("{}.{:03}", micros / 1_000, micros % 1_000)
}

#[cfg(feature = "diagnostics")]
fn format_avg_nanos_as_ms(total_nanos: u64, count: usize) -> String {
    if count == 0 {
        return "0.000".to_string();
    }

    let count = u64::try_from(count).expect("sample count should fit in u64 for debug reporting");
    format_nanos_as_ms(total_nanos / count)
}

#[cfg(feature = "diagnostics")]
fn print_timing_summary(label: &str, calls: usize, total_nanos: u64, max_nanos: u64) {
    if calls == 0 {
        return;
    }

    println!(
        "    {label}: calls={calls} total_ms={} avg_ms={} max_ms={}",
        format_nanos_as_ms(total_nanos),
        format_avg_nanos_as_ms(total_nanos, calls),
        format_nanos_as_ms(max_nanos)
    );
}

#[cfg(feature = "diagnostics")]
fn print_repair_seed_accumulation_telemetry(telemetry: &ConstructionTelemetry) {
    if telemetry.repair_seed_accumulation_calls == 0 {
        return;
    }

    println!(
        "    repair_seed_accumulation: calls={} cells_added_total={} avg_cells_added={} max_cells_added={} total_ms={} avg_ms={} max_ms={}",
        telemetry.repair_seed_accumulation_calls,
        telemetry.repair_seed_cells_added_total,
        format_ratio_2(
            telemetry.repair_seed_cells_added_total,
            telemetry.repair_seed_accumulation_calls,
        ),
        telemetry.repair_seed_cells_added_max,
        format_nanos_as_ms(telemetry.repair_seed_accumulation_nanos),
        format_avg_nanos_as_ms(
            telemetry.repair_seed_accumulation_nanos,
            telemetry.repair_seed_accumulation_calls,
        ),
        format_nanos_as_ms(telemetry.repair_seed_accumulation_nanos_max)
    );
}

#[cfg(feature = "diagnostics")]
fn print_local_repair_frontier_telemetry(telemetry: &ConstructionTelemetry) {
    if telemetry.local_repair_calls == 0 {
        return;
    }

    println!(
        "    local_repair_frontiers: seed_cells_total={} avg_seed_cells={} max_seed_cells={} cadence_triggers={} backlog_triggers={}",
        telemetry.local_repair_seed_cells_total,
        format_ratio_2(
            telemetry.local_repair_seed_cells_total,
            telemetry.local_repair_calls,
        ),
        telemetry.local_repair_seed_cells_max,
        telemetry.local_repair_cadence_triggers,
        telemetry.local_repair_backlog_triggers
    );
}

#[cfg(feature = "diagnostics")]
fn print_local_repair_work_telemetry(telemetry: &ConstructionTelemetry) {
    if telemetry.local_repair_calls == 0 {
        return;
    }

    println!(
        "    local_repair_work: checked_total={} avg_checked={} flips_total={} avg_flips={} max_flips={} max_queue={} no_flip_calls={}",
        telemetry.local_repair_items_checked_total,
        format_ratio_2(
            telemetry.local_repair_items_checked_total,
            telemetry.local_repair_calls,
        ),
        telemetry.local_repair_flips_total,
        format_ratio_2(
            telemetry.local_repair_flips_total,
            telemetry.local_repair_calls
        ),
        telemetry.local_repair_flips_max,
        telemetry.local_repair_queue_len_max,
        telemetry.local_repair_no_flip_calls
    );
}

#[cfg(feature = "diagnostics")]
fn print_local_repair_slow_samples(telemetry: &ConstructionTelemetry) {
    if telemetry.local_repair_slow_samples.is_empty() {
        return;
    }

    println!(
        "    local_repair_slow_samples (top {}):",
        telemetry.local_repair_slow_samples.len()
    );
    for sample in &telemetry.local_repair_slow_samples {
        println!(
            "      idx={} trigger={:?} seed_cells={} elapsed_ms={} checked={} flips={} max_queue={} facet_ms={} ridge_ms={} postcondition_ms={}",
            sample.index,
            sample.trigger,
            sample.seed_cells,
            format_nanos_as_ms(sample.elapsed_nanos),
            sample.items_checked,
            sample.flips_performed,
            sample.max_queue_len,
            format_nanos_as_ms(sample.facet_nanos),
            format_nanos_as_ms(sample.ridge_nanos),
            format_nanos_as_ms(sample.postcondition_nanos)
        );
    }
}

#[cfg(feature = "diagnostics")]
fn print_local_repair_phase_telemetry(telemetry: &ConstructionTelemetry) {
    let phase_total = telemetry
        .local_repair_snapshot_nanos
        .saturating_add(telemetry.local_repair_attempt_nanos)
        .saturating_add(telemetry.local_repair_postcondition_nanos)
        .saturating_add(telemetry.local_repair_restore_nanos);
    if phase_total == 0 {
        return;
    }

    print_timing_summary(
        "local_repair_snapshot",
        telemetry.local_repair_calls,
        telemetry.local_repair_snapshot_nanos,
        telemetry.local_repair_snapshot_nanos_max,
    );
    print_timing_summary(
        "local_repair_attempts",
        telemetry.local_repair_calls,
        telemetry.local_repair_attempt_nanos,
        telemetry.local_repair_attempt_nanos_max,
    );
    print_timing_summary(
        "local_repair_attempt_seed",
        telemetry.local_repair_calls,
        telemetry.local_repair_attempt_seed_nanos,
        telemetry.local_repair_attempt_seed_nanos_max,
    );
    print_timing_summary(
        "local_repair_attempt_facets",
        telemetry.local_repair_calls,
        telemetry.local_repair_attempt_facet_nanos,
        telemetry.local_repair_attempt_facet_nanos_max,
    );
    print_timing_summary(
        "local_repair_attempt_ridges",
        telemetry.local_repair_calls,
        telemetry.local_repair_attempt_ridge_nanos,
        telemetry.local_repair_attempt_ridge_nanos_max,
    );
    print_timing_summary(
        "local_repair_attempt_edges",
        telemetry.local_repair_calls,
        telemetry.local_repair_attempt_edge_nanos,
        telemetry.local_repair_attempt_edge_nanos_max,
    );
    print_timing_summary(
        "local_repair_attempt_triangles",
        telemetry.local_repair_calls,
        telemetry.local_repair_attempt_triangle_nanos,
        telemetry.local_repair_attempt_triangle_nanos_max,
    );
    print_timing_summary(
        "local_repair_postconditions",
        telemetry.local_repair_calls,
        telemetry.local_repair_postcondition_nanos,
        telemetry.local_repair_postcondition_nanos_max,
    );
    print_timing_summary(
        "local_repair_restores",
        telemetry.local_repair_calls,
        telemetry.local_repair_restore_nanos,
        telemetry.local_repair_restore_nanos_max,
    );
}

#[cfg(feature = "diagnostics")]
fn print_construction_phase_telemetry(telemetry: &ConstructionTelemetry) {
    let outer_total_nanos = telemetry
        .construction_preprocessing_nanos
        .saturating_add(telemetry.construction_insert_loop_nanos)
        .saturating_add(telemetry.construction_finalize_nanos)
        .saturating_add(telemetry.construction_final_delaunay_validation_nanos);
    if outer_total_nanos == 0 {
        return;
    }

    println!(
        "    construction_phases: preprocessing_ms={} insert_loop_ms={} finalize_ms={} final_delaunay_validation_ms={} outer_total_ms={}",
        format_nanos_as_ms(telemetry.construction_preprocessing_nanos),
        format_nanos_as_ms(telemetry.construction_insert_loop_nanos),
        format_nanos_as_ms(telemetry.construction_finalize_nanos),
        format_nanos_as_ms(telemetry.construction_final_delaunay_validation_nanos),
        format_nanos_as_ms(outer_total_nanos)
    );

    let finalize_breakdown_nanos = telemetry
        .construction_completion_repair_nanos
        .saturating_add(telemetry.construction_orientation_nanos)
        .saturating_add(telemetry.construction_topology_validation_nanos);
    if finalize_breakdown_nanos == 0 {
        return;
    }

    println!(
        "    finalize_breakdown: completion_repair_ms={} orientation_ms={} topology_validation_ms={}",
        format_nanos_as_ms(telemetry.construction_completion_repair_nanos),
        format_nanos_as_ms(telemetry.construction_orientation_nanos),
        format_nanos_as_ms(telemetry.construction_topology_validation_nanos)
    );
}

#[cfg_attr(
    not(feature = "diagnostics"),
    expect(
        clippy::missing_const_for_fn,
        reason = "the diagnostics build of this helper emits detailed output"
    )
)]
fn print_construction_telemetry(telemetry: &ConstructionTelemetry) {
    #[cfg(not(feature = "diagnostics"))]
    let _ = telemetry.has_data();

    #[cfg(feature = "diagnostics")]
    if telemetry.has_data() {
        println!();
        println!("  insertion telemetry:");
        print_construction_phase_telemetry(telemetry);
        print_timing_summary(
            "insertion_wall",
            telemetry.insertion_wall_time_calls,
            telemetry.insertion_wall_time_nanos,
            telemetry.insertion_wall_time_nanos_max,
        );
        println!(
            "    locate: calls={} walk_steps_total={} avg_walk={} max_walk={} hint_uses={} scan_fallbacks={}",
            telemetry.locate_calls,
            telemetry.locate_walk_steps_total,
            format_ratio_2(telemetry.locate_walk_steps_total, telemetry.locate_calls),
            telemetry.locate_walk_steps_max,
            telemetry.locate_hint_uses,
            telemetry.locate_scan_fallbacks
        );
        println!(
            "    locate_results: inside={} outside={} boundary={}",
            telemetry.located_inside, telemetry.located_outside, telemetry.located_on_boundary
        );

        if telemetry.conflict_region_calls > 0 {
            println!(
                "    conflict_regions: calls={} cells_total={} avg_cells={} max_cells={} total_ms={} avg_ms={} max_ms={}",
                telemetry.conflict_region_calls,
                telemetry.conflict_region_cells_total,
                format_ratio_2(
                    telemetry.conflict_region_cells_total,
                    telemetry.conflict_region_calls,
                ),
                telemetry.conflict_region_cells_max,
                format_nanos_as_ms(telemetry.conflict_region_nanos),
                format_avg_nanos_as_ms(
                    telemetry.conflict_region_nanos,
                    telemetry.conflict_region_calls,
                ),
                format_nanos_as_ms(telemetry.conflict_region_nanos_max)
            );
        }

        print_timing_summary(
            "cavity_insertions",
            telemetry.cavity_insertion_calls,
            telemetry.cavity_insertion_nanos,
            telemetry.cavity_insertion_nanos_max,
        );
        print_timing_summary(
            "hull_extensions",
            telemetry.hull_extension_calls,
            telemetry.hull_extension_nanos,
            telemetry.hull_extension_nanos_max,
        );
        print_timing_summary(
            "topology_validations",
            telemetry.topology_validation_calls,
            telemetry.topology_validation_nanos,
            telemetry.topology_validation_nanos_max,
        );
        print_timing_summary(
            "local_repairs",
            telemetry.local_repair_calls,
            telemetry.local_repair_nanos,
            telemetry.local_repair_nanos_max,
        );
        print_local_repair_phase_telemetry(telemetry);
        print_local_repair_frontier_telemetry(telemetry);
        print_local_repair_work_telemetry(telemetry);
        print_local_repair_slow_samples(telemetry);
        print_repair_seed_accumulation_telemetry(telemetry);

        if telemetry.global_conflict_scans > 0 {
            let scans = u64::try_from(telemetry.global_conflict_scans)
                .expect("scan count should fit in u64 for debug reporting");
            println!(
                "    global_conflict_scans: scans={} cells_scanned_total={} avg_cells_scanned={} cells_found_total={} avg_cells_found={} max_cells_found={} total_ms={} avg_ms={}",
                telemetry.global_conflict_scans,
                telemetry.global_conflict_cells_scanned,
                format_ratio_2(
                    telemetry.global_conflict_cells_scanned,
                    telemetry.global_conflict_scans,
                ),
                telemetry.global_conflict_cells_found_total,
                format_ratio_2(
                    telemetry.global_conflict_cells_found_total,
                    telemetry.global_conflict_scans,
                ),
                telemetry.global_conflict_cells_found_max,
                format_nanos_as_ms(telemetry.global_conflict_scan_nanos),
                format_nanos_as_ms(telemetry.global_conflict_scan_nanos / scans)
            );
        }
    }
}

fn print_insertion_summary<const D: usize>(
    summary: &InsertionSummary<D>,
    elapsed: Duration,
    include_batch_diagnostics: bool,
) {
    println!("Insertion summary:");
    println!("  inserted:          {}", summary.inserted);
    println!("  skipped_duplicate: {}", summary.skipped_duplicate);
    println!("  skipped_degeneracy:{}", summary.skipped_degeneracy);
    println!("  total_skipped:     {}", summary.total_skipped());
    println!();

    println!("  total_attempts:    {}", summary.total_attempts);
    println!("  max_attempts:      {}", summary.max_attempts);
    println!("  used_perturbation: {}", summary.used_perturbation);
    println!(
        "  cells_removed_during_repair: total={}, max={} (insertion safety-net / repair bookkeeping)",
        summary.cells_removed_total, summary.cells_removed_max
    );

    if !summary.attempts_histogram.is_empty() {
        println!("  attempts_histogram (attempts -> count):");
        for (attempts, count) in summary.attempts_histogram.iter().enumerate().skip(1) {
            if *count > 0 {
                println!("    {attempts} -> {count}");
            }
        }
    }

    if include_batch_diagnostics {
        print_construction_telemetry(&summary.telemetry);
    }

    #[cfg(feature = "diagnostics")]
    if include_batch_diagnostics && !summary.slow_insertions.is_empty() {
        println!();
        println!(
            "  slow_insertions (top {} by transactional insertion wall time):",
            summary.slow_insertions.len()
        );
        for s in &summary.slow_insertions {
            println!(
                "    idx={} uuid={} attempts={} result={:?} elapsed_ms={} cells_after={} locate_calls={} walk_steps={} conflict_calls={} conflict_cells={} cavity_calls={} global_scans={} hull_calls={} validation_calls={}",
                s.index,
                s.uuid,
                s.attempts,
                s.result,
                format_nanos_as_ms(s.elapsed_nanos),
                s.cells_after,
                s.locate_calls,
                s.locate_walk_steps_total,
                s.conflict_region_calls,
                s.conflict_region_cells_total,
                s.cavity_insertion_calls,
                s.global_conflict_scans,
                s.hull_extension_calls,
                s.topology_validation_calls
            );
        }
    }

    if !summary.skip_samples.is_empty() {
        println!();
        println!("  skip_samples (first {}):", summary.skip_samples.len());
        for s in &summary.skip_samples {
            let coords = s.coords.as_ref().map_or_else(
                || "<unavailable>".to_string(),
                |coords| format!("{coords:?}"),
            );
            println!(
                "    idx={} uuid={} attempts={} coords={} error={}",
                s.index, s.uuid, s.attempts, coords, s.error
            );
        }
    }

    println!();
    println!("Insertion wall time: {elapsed:?}");
}

#[expect(
    clippy::too_many_lines,
    reason = "Intentional debug harness; kept as a single flow for manual inspection"
)]
fn debug_large_case<const D: usize>(dimension_name: &str, default_n_points: usize) -> DebugOutcome
where
    RobustKernel<f64>: ExactPredicates<D> + Kernel<D, Scalar = f64>,
{
    init_tracing();

    // Install a hard wall-clock cap so the harness doesn't hang indefinitely.
    // Override with DELAUNAY_LARGE_DEBUG_MAX_RUNTIME_SECS (0 = no cap).
    let max_runtime_secs = env_usize("DELAUNAY_LARGE_DEBUG_MAX_RUNTIME_SECS").unwrap_or(600);
    // Hold the sender for the lifetime of this function; dropping it at return
    // cancels the watchdog thread so it does not outlive this test.
    let _watchdog = (max_runtime_secs > 0).then(|| install_runtime_cap(max_runtime_secs as u64));

    let base_seed = env_u64("DELAUNAY_LARGE_DEBUG_SEED").unwrap_or(42);

    let n_points = env_usize(&format!("DELAUNAY_LARGE_DEBUG_N_{D}D"))
        .or_else(|| env_usize("DELAUNAY_LARGE_DEBUG_N"))
        .unwrap_or(default_n_points)
        .max(D + 1);

    let seed = env_u64(&format!("DELAUNAY_LARGE_DEBUG_CASE_SEED_{D}D"))
        .or_else(|| env_u64("DELAUNAY_LARGE_DEBUG_CASE_SEED"))
        .unwrap_or_else(|| seed_for_case::<D>(base_seed, n_points));
    let distribution = point_distribution_from_env();
    let ball_radius = env_f64("DELAUNAY_LARGE_DEBUG_BALL_RADIUS").unwrap_or(100.0);
    let box_half_width = env_f64("DELAUNAY_LARGE_DEBUG_BOX_HALF_WIDTH").unwrap_or(100.0);

    let mode = construction_mode_from_env();
    let initial_simplex_strategy = initial_simplex_strategy_from_env();
    let debug_mode = debug_mode_from_env();
    let topology_guarantee = topology_for_debug_mode(debug_mode);

    let shuffle_seed = env_u64("DELAUNAY_LARGE_DEBUG_SHUFFLE_SEED");
    let progress_every = env_usize("DELAUNAY_LARGE_DEBUG_PROGRESS_EVERY")
        .unwrap_or(1000)
        .max(1);

    let allow_skips = env_flag("DELAUNAY_LARGE_DEBUG_ALLOW_SKIPS");
    let max_skip_pct = max_skip_pct_from_env();
    let skip_final_repair = env_flag("DELAUNAY_LARGE_DEBUG_SKIP_FINAL_REPAIR");

    // Delaunay repair scheduling
    // - 0 disables incremental repair
    // - 1 runs repair after every insertion
    // - N>1 runs repair after every N successful insertions
    let repair_every = env_usize("DELAUNAY_LARGE_DEBUG_REPAIR_EVERY").unwrap_or(1);
    let repair_policy = repair_policy_from_repair_every(repair_every);
    let repair_max_flips = env_usize("DELAUNAY_LARGE_DEBUG_REPAIR_MAX_FLIPS");
    let validate_every = env_usize("DELAUNAY_LARGE_DEBUG_VALIDATE_EVERY").or_else(|| {
        if matches!(debug_mode, DebugMode::Cadenced) {
            (repair_every != 0).then_some(repair_every)
        } else {
            None
        }
    });
    let validation_cadence = ValidationCadence::from_optional_every(validate_every);

    println!("=============================================");
    println!("Large-scale triangulation debug: {dimension_name}");
    println!("=============================================");
    println!("Config:");
    println!("  D:             {D}");
    println!("  n_points:      {n_points}");
    println!("  base_seed:     0x{base_seed:X} ({base_seed})");
    println!("  case_seed:     0x{seed:X} ({seed})");
    println!(
        "  distribution: {distribution}",
        distribution = distribution.name()
    );
    match distribution {
        PointDistribution::Ball => println!("  ball_radius:  {ball_radius}"),
        PointDistribution::Box => println!("  box_half_width:{box_half_width}"),
    }
    println!("  construction_mode: {}", mode.name());
    println!(
        "  initial_simplex: {}",
        initial_simplex_strategy_name(initial_simplex_strategy)
    );
    println!("  debug_mode:    {}", debug_mode.name());
    println!("  topology_guarantee: {topology_guarantee:?}");
    println!("  shuffle_seed:  {shuffle_seed:?}");
    println!("  progress_every:{progress_every}");
    println!("  validation_cadence: {validation_cadence:?}");
    println!("  allow_skips:   {allow_skips}");
    println!("  max_skip_pct:  {max_skip_pct}");
    println!("  skip_final_repair: {skip_final_repair}");
    println!("  repair_every:  {repair_every}");
    println!("  repair_policy: {repair_policy:?}");
    println!("  repair_max_flips: {repair_max_flips:?}");
    if max_runtime_secs > 0 {
        println!("  max_runtime_secs: {max_runtime_secs}");
    }
    println!();

    println!("Generating points...");
    let t_gen = Instant::now();
    let points = match distribution {
        PointDistribution::Ball => {
            generate_random_points_in_ball_seeded::<f64, D>(n_points, ball_radius, seed)
                .unwrap_or_else(|e| {
                    panic!(
                        "failed to generate deterministic ball points (radius={ball_radius}): {e}"
                    )
                })
        }
        PointDistribution::Box => {
            let range = (-box_half_width, box_half_width);
            generate_random_points_seeded::<f64, D>(n_points, range, seed).unwrap_or_else(|e| {
                panic!("failed to generate deterministic box points (range={range:?}): {e}")
            })
        }
    };
    println!("Generated {} points in {:?}", points.len(), t_gen.elapsed());

    println!("Building vertices...");
    let t_vertices = Instant::now();
    let mut vertices: Vec<Vertex<f64, (), D>> = points.into_iter().map(|p| vertex!(p)).collect();
    println!(
        "Built {} vertices in {:?}",
        vertices.len(),
        t_vertices.elapsed()
    );

    let t_insert = Instant::now();

    let mut dt: DelaunayTriangulation<_, (), (), D> = match mode {
        ConstructionMode::New => {
            // `DelaunayTriangulation::new()` applies Hilbert ordering during batch construction.
            // Use the statistics-returning variant so we can report aggregate insertion telemetry.
            let kernel = RobustKernel::<f64>::new();
            println!("Starting batch construction (new)...");
            let t_batch = Instant::now();
            let options = ConstructionOptions::default()
                .with_initial_simplex_strategy(initial_simplex_strategy)
                .with_batch_repair_policy(repair_policy);
            match DelaunayTriangulation::with_options_and_statistics(
                &kernel,
                &vertices,
                topology_guarantee,
                options,
            ) {
                Ok((dt, stats)) => {
                    let summary: InsertionSummary<D> = stats.into();
                    print_insertion_summary(&summary, t_insert.elapsed(), true);
                    println!("Batch construction completed in {:?}", t_batch.elapsed());
                    dt
                }
                Err(e) => {
                    let DelaunayTriangulationConstructionErrorWithStatistics {
                        error,
                        statistics,
                        ..
                    } = e;
                    let summary: InsertionSummary<D> = statistics.into();
                    print_insertion_summary(&summary, t_insert.elapsed(), true);
                    println!("Batch construction failed after {:?}", t_batch.elapsed());
                    println!("construction failed: {error}");
                    let outcome = DebugOutcome::ConstructionFailure {
                        error: format!("{error}"),
                    };
                    print_abort_summary::<D>(&outcome, seed, n_points, "batch construction");
                    return outcome;
                }
            }
        }
        ConstructionMode::Incremental => {
            if let Some(shuffle_seed) = shuffle_seed {
                let mut rng = StdRng::seed_from_u64(shuffle_seed);
                vertices.shuffle(&mut rng);
                println!("Shuffled insertion order with seed {shuffle_seed}");
            }

            let validation_policy = topology_guarantee.default_validation_policy();

            let mut dt: DelaunayTriangulation<_, (), (), D> =
                DelaunayTriangulation::with_empty_kernel_and_topology_guarantee(
                    RobustKernel::<f64>::new(),
                    topology_guarantee,
                );

            // Delaunay policies:
            // - Enable bounded incremental repair (local flip queue) every N successful insertions.
            // - Keep global Delaunay checks off during insertion; the harness can optionally run a final
            //   global repair pass at the end.
            dt.set_delaunay_repair_policy(repair_policy);
            dt.set_delaunay_check_policy(DelaunayCheckPolicy::EndOnly);

            // Debug-mode-dependent topology validation strategy:
            // - cadenced: suspicion-driven automatic checks + periodic explicit checks
            // - strict: per-insertion automatic checks
            dt.set_validation_policy(validation_policy);

            println!("Policies:");
            println!("  topology_guarantee:   {:?}", dt.topology_guarantee());
            println!("  validation_policy:    {:?}", dt.validation_policy());
            println!("  delaunay_repair_policy:{:?}", dt.delaunay_repair_policy());
            println!("  delaunay_check_policy: {:?}", dt.delaunay_check_policy());
            println!();

            let mut summary: InsertionSummary<D> = InsertionSummary::default();
            let mut had_cells = false;
            let mut t_last_progress = Instant::now();

            for (idx, vertex) in vertices.iter().copied().enumerate() {
                let coords = *vertex.point().coords();
                let uuid = vertex.uuid();

                let inserted_this_loop = match dt.insert_with_statistics(vertex) {
                    Ok((InsertionOutcome::Inserted { .. }, stats)) => {
                        summary.record_inserted(stats);
                        true
                    }
                    Ok((InsertionOutcome::Skipped { error }, stats)) => {
                        let sample = SkipSample {
                            index: idx,
                            uuid,
                            coords: Some(coords),
                            attempts: stats.attempts,
                            error: error.to_string(),
                        };
                        summary.record_skipped(sample, stats);
                        false
                    }
                    Err(err) => {
                        println!(
                            "Non-retryable insertion error at idx={idx} uuid={uuid} coords={coords:?}"
                        );
                        println!("  error: {err}");

                        if let Err(report) = dt.validation_report() {
                            print_validation_report(&report);
                        } else {
                            println!("validation_report: OK (after rollback)");
                        }
                        let outcome = DebugOutcome::ConstructionFailure {
                            error: format!("{err}"),
                        };
                        print_abort_summary::<D>(&outcome, seed, n_points, "incremental insertion");
                        return outcome;
                    }
                };

                if !had_cells && dt.number_of_cells() > 0 {
                    had_cells = true;
                    println!("Initial simplex created at insertion {}", idx + 1);
                }

                if inserted_this_loop
                    && had_cells
                    && validation_cadence.should_validate(summary.inserted)
                    && let Err(e) = dt.as_triangulation().is_valid()
                {
                    println!("Topology validation failed at idx={idx}: {e}");
                    let outcome = if let Err(report) = dt.validation_report() {
                        print_validation_report(&report);
                        classify_validation_report(&report)
                    } else {
                        DebugOutcome::ValidationFailure {
                            kind: InvariantKind::Topology,
                            details: format!("{e}"),
                        }
                    };
                    print_abort_summary::<D>(&outcome, seed, n_points, "periodic validation");
                    return outcome;
                }

                if (idx + 1) % progress_every == 0 {
                    let chunk_elapsed = t_last_progress.elapsed();
                    let progress_f64: f64 =
                        safe_usize_to_scalar(progress_every).unwrap_or(f64::NAN);
                    let rate = progress_f64 / chunk_elapsed.as_secs_f64().max(1e-9);
                    println!(
                        "progress: {}/{} inserted={} skipped={} cells={} elapsed={:?} ({:.1} pts/s last {})",
                        idx + 1,
                        n_points,
                        summary.inserted,
                        summary.total_skipped(),
                        dt.number_of_cells(),
                        t_insert.elapsed(),
                        rate,
                        progress_every,
                    );
                    t_last_progress = Instant::now();
                }
            }

            print_insertion_summary(&summary, t_insert.elapsed(), false);

            dt
        }
    };

    // Infer skipped count from the resulting triangulation size.
    let skipped_total = n_points.saturating_sub(dt.number_of_vertices());

    println!(
        "Triangulation size: vertices={} (skipped={skipped_total}) cells={} dim={}",
        dt.number_of_vertices(),
        dt.number_of_cells(),
        dt.dim()
    );

    let skipped_pct = skip_percentage(skipped_total, n_points);
    if !allow_skips && skipped_pct > max_skip_pct {
        let outcome = DebugOutcome::SkippedVertices {
            skipped: skipped_total,
            total: n_points,
            skip_pct: skipped_pct,
            max_skip_pct,
        };
        print_abort_summary::<D>(&outcome, seed, n_points, "skip check");
        return outcome;
    }

    let mut repair_failure: Option<String> = None;
    if !skip_final_repair && dt.number_of_cells() > 0 {
        println!();
        println!("Running final flip-based repair (advanced)...");
        let t_repair = Instant::now();
        let mut repair_config = DelaunayRepairHeuristicConfig::default();
        repair_config.max_flips = repair_max_flips;
        match dt.repair_delaunay_with_flips_advanced(repair_config) {
            Ok(outcome) => {
                println!(
                    "repair: checked={} flips={} max_queue={} used_heuristic={}",
                    outcome.stats.facets_checked,
                    outcome.stats.flips_performed,
                    outcome.stats.max_queue_len,
                    outcome.used_heuristic()
                );
                if let Some(seeds) = outcome.heuristic {
                    println!(
                        "repair heuristic seeds: shuffle_seed={} perturbation_seed={}",
                        seeds.shuffle_seed, seeds.perturbation_seed
                    );
                }
            }
            Err(e) => {
                println!("repair failed: {e}");
                repair_failure = Some(format!("{e}"));
            }
        }
        println!("repair wall time: {:?}", t_repair.elapsed());
    }

    println!();
    println!("Running validation_report (Levels 1–4)...");
    let t_validate = Instant::now();
    let validation_result = dt.validation_report();
    println!("validation_report wall time: {:?}", t_validate.elapsed());
    match validation_result {
        Ok(()) => println!("validation_report: OK"),
        Err(report) => {
            print_validation_report(&report);
            let outcome = classify_validation_report(&report);
            print_abort_summary::<D>(&outcome, seed, n_points, "final validation");
            return outcome;
        }
    }

    // If repair failed but validation passed, surface the repair failure as the outcome.
    // This ensures operators see repair non-convergence even when the triangulation
    // happens to pass L1-L4 validation (e.g. the violations are below the flip budget).
    if let Some(error) = repair_failure {
        let outcome = DebugOutcome::RepairNonConvergence { error };
        print_abort_summary::<D>(&outcome, seed, n_points, "final repair");
        return outcome;
    }

    println!();
    println!("Total wall time: {:?}", t_gen.elapsed());
    DebugOutcome::Success
}

#[derive(Clone, Copy)]
enum FailingWriterMode {
    Write,
    Flush,
}

struct FailingWriter {
    mode: FailingWriterMode,
    kind: io::ErrorKind,
}

impl Write for FailingWriter {
    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
        if matches!(self.mode, FailingWriterMode::Write) {
            return Err(io::Error::new(self.kind, "synthetic write failure"));
        }

        Ok(buf.len())
    }

    fn flush(&mut self) -> io::Result<()> {
        if matches!(self.mode, FailingWriterMode::Flush) {
            return Err(io::Error::new(self.kind, "synthetic flush failure"));
        }

        Ok(())
    }
}

#[test]
fn test_write_timeout_abort_message_flushes_message() {
    let mut output = Vec::new();

    write_timeout_abort_message(&mut output, 17).expect("timeout diagnostic write should succeed");

    let message = String::from_utf8(output).expect("timeout diagnostic should be UTF-8");
    assert_eq!(
        message,
        "=== TIMEOUT: wall time exceeded 17 seconds — aborting ===\n"
    );
}

#[test]
fn test_write_timeout_abort_message_propagates_error() {
    // `install_runtime_cap` aborts immediately after this helper returns, so the
    // caller must be able to observe write or flush failures before aborting.
    let cases = [
        (FailingWriterMode::Write, io::ErrorKind::BrokenPipe),
        (FailingWriterMode::Flush, io::ErrorKind::WriteZero),
    ];

    for (mode, kind) in cases {
        let err = write_timeout_abort_message(FailingWriter { mode, kind }, 17)
            .expect_err("timeout diagnostic should propagate writer failures");
        assert_eq!(err.kind(), kind);
    }
}

#[test]
fn test_topology_for_debug_mode_uses_ridge_links_by_default() {
    assert_eq!(
        topology_for_debug_mode(DebugMode::Cadenced),
        TopologyGuarantee::PLManifold
    );
    assert_eq!(
        topology_for_debug_mode(DebugMode::Strict),
        TopologyGuarantee::PLManifoldStrict
    );
}

#[test]
fn test_initial_simplex_strategy_from_name_maps_supported_values() {
    assert_eq!(
        initial_simplex_strategy_from_name(""),
        Some(InitialSimplexStrategy::MaxVolume)
    );
    assert_eq!(
        initial_simplex_strategy_from_name("first"),
        Some(InitialSimplexStrategy::First)
    );
    assert_eq!(
        initial_simplex_strategy_from_name("BALANCED"),
        Some(InitialSimplexStrategy::Balanced)
    );
    assert_eq!(
        initial_simplex_strategy_from_name("max-volume"),
        Some(InitialSimplexStrategy::MaxVolume)
    );
    assert_eq!(
        initial_simplex_strategy_from_name("MAX_VOLUME"),
        Some(InitialSimplexStrategy::MaxVolume)
    );
    assert_eq!(initial_simplex_strategy_from_name("unknown"), None);
    assert_eq!(
        initial_simplex_strategy_name(InitialSimplexStrategy::Balanced),
        "balanced"
    );
    assert_eq!(
        initial_simplex_strategy_name(InitialSimplexStrategy::MaxVolume),
        "max-volume"
    );
}

#[test]
fn test_skip_percentage_reports_ratio() {
    assert!((skip_percentage(0, 100) - 0.0).abs() < f64::EPSILON);
    assert!((skip_percentage(0, 0) - 0.0).abs() < f64::EPSILON);
    assert!((skip_percentage(4, 400) - 1.0).abs() < f64::EPSILON);
    assert!((skip_percentage(12, 100) - 12.0).abs() < f64::EPSILON);
}

#[test]
fn test_repair_policy_from_repair_every_maps_cadence() {
    assert_eq!(
        repair_policy_from_repair_every(0),
        DelaunayRepairPolicy::Never
    );
    assert_eq!(
        repair_policy_from_repair_every(1),
        DelaunayRepairPolicy::EveryInsertion
    );
    assert_eq!(
        repair_policy_from_repair_every(2),
        DelaunayRepairPolicy::EveryN(NonZeroUsize::new(2).unwrap())
    );
    assert_eq!(
        repair_policy_from_repair_every(4),
        DelaunayRepairPolicy::EveryN(NonZeroUsize::new(4).unwrap())
    );
}

/// Regression test for issue #228: 3D 1000-point flip-repair non-convergence.
///
/// Before the fix, `AdaptiveKernel`'s exact+SoS predicates were overridden by
/// `use_robust_on_ambiguous` in the flip repair code, causing tolerance-based
/// predicates to return wrong signs for near-degenerate cases and triggering
/// flip cycles.
///
/// This test uses the exact seed that reproduced the original failure:
/// `seed_for_case::<3>(42, 1000)` with ball distribution (radius=100).
///
/// Uses `PLManifold` topology for full PL-manifold validation including
/// geometric cell orientation (#258 fixed the negative-orientation gap that
/// previously required the `Pseudomanifold` workaround).
///
/// Gated behind `slow-tests` and `#[ignore]` because 1000-point 3D
/// construction takes minutes in debug mode, exceeding CI timeout.
/// Run manually with:
/// ```bash
/// cargo test --test large_scale_debug --features slow-tests regression_issue_228 -- --ignored --nocapture
/// ```
#[cfg(feature = "slow-tests")]
#[test]
#[ignore = "1000-point 3D construction exceeds CI timeout (~30min debug)"]
fn regression_issue_228_3d_1000_flip_repair_convergence() {
    let seed = seed_for_case::<3>(42, 1000);
    let points = generate_random_points_in_ball_seeded::<f64, 3>(1000, 100.0, seed)
        .expect("point generation should succeed");
    let vertices: Vec<Vertex<f64, (), 3>> = points.into_iter().map(|p| vertex!(p)).collect();

    // Use the default kernel (AdaptiveKernel — exact+SoS predicates) to match the
    // actual regression scenario.  PLManifold enables full orientation validation
    // now that #258 is fixed.
    let dt: DelaunayTriangulation<_, (), (), 3> =
        DelaunayTriangulation::new_with_topology_guarantee(
            &vertices,
            TopologyGuarantee::PLManifold,
        )
        .expect("construction must not fail (#228 regression)");

    assert!(
        dt.as_triangulation().validate().is_ok(),
        "Topology validation (L1-L3) must pass (#228 regression, seed=0x{seed:X})"
    );
    assert!(
        dt.is_delaunay_via_flips().is_ok(),
        "Delaunay property must hold (#228 regression, seed=0x{seed:X})"
    );
}

/// Regression test for issue #230: 4D 100-point orientation failure path.
///
/// This locks in the 4D seed used by the debug harness:
/// `seed_for_case::<4>(42, 100)` with ball distribution (radius=100).
///
/// Current expected behavior:
/// - Batch construction succeeds
/// - Some vertices may be skipped due to degeneracy handling/retries
/// - Resulting triangulation passes topology validation (L1-L3)
///
/// Gated behind `slow-tests` and `#[ignore]` because 4D construction can
/// take multiple minutes in debug mode.
#[cfg(feature = "slow-tests")]
#[test]
#[ignore = "4D 100-point construction can take minutes in debug mode"]
fn regression_issue_230_4d_100_orientation() {
    let seed = seed_for_case::<4>(42, 100);
    let points = generate_random_points_in_ball_seeded::<f64, 4>(100, 100.0, seed)
        .expect("point generation should succeed");
    let vertices: Vec<Vertex<f64, (), 4>> = points.into_iter().map(|p| vertex!(p)).collect();

    let kernel = RobustKernel::<f64>::new();
    let (dt, stats) =
        DelaunayTriangulation::<RobustKernel<f64>, (), (), 4>::with_options_and_statistics(
            &kernel,
            &vertices,
            TopologyGuarantee::PLManifoldStrict,
            ConstructionOptions::default(),
        )
        .expect("construction must not fail (#230 regression)");

    println!(
        "regression_issue_230: inserted={} skipped={} (duplicate={} degeneracy={}) seed=0x{seed:X}",
        stats.inserted,
        stats.total_skipped(),
        stats.skipped_duplicate,
        stats.skipped_degeneracy
    );

    assert!(
        dt.as_triangulation().validate().is_ok(),
        "Topology validation (L1-L3) must pass (#230 regression, seed=0x{seed:X})"
    );
}

#[test]
#[ignore = "large-scale debug harness (manual run)"]
fn debug_large_scale_2d() {
    let outcome = debug_large_case::<2>("2D", 40_000);
    assert!(matches!(outcome, DebugOutcome::Success), "{outcome}");
}

#[test]
#[ignore = "large-scale debug harness (manual run)"]
fn debug_large_scale_3d() {
    let outcome = debug_large_case::<3>("3D", 8_000);
    assert!(matches!(outcome, DebugOutcome::Success), "{outcome}");
}

#[test]
#[ignore = "large-scale debug harness (manual run)"]
fn debug_large_scale_4d() {
    let outcome = debug_large_case::<4>("4D", 900);
    assert!(matches!(outcome, DebugOutcome::Success), "{outcome}");
}

#[test]
#[ignore = "large-scale debug harness (manual run)"]
fn debug_large_scale_5d() {
    let outcome = debug_large_case::<5>("5D", 150);
    assert!(matches!(outcome, DebugOutcome::Success), "{outcome}");
}
