//! Input validation for LOWESS configuration and data.
//!
//! ## Purpose
//!
//! This module provides comprehensive validation functions for LOWESS
//! configuration parameters and input data. It ensures that all inputs
//! meet the requirements for successful smoothing before any computation
//! begins, providing clear error messages when validation fails.
//!
//! ## Design notes
//!
//! * All validation is performed upfront before smoothing begins.
//! * Validation is fail-fast: returns on first error encountered.
//! * Error messages include specific values and context for debugging.
//! * Validation is generic over `Float` types to support f32 and f64.
//! * Checks are ordered from cheap to expensive for efficiency.
//! * Uses cache-friendly combined loops where possible.
//!
//! ## Validated parameters
//!
//! * **Input data**: Non-empty, matching lengths, sufficient points, all finite
//! * **Fraction**: In (0, 1] and finite
//! * **Delta**: Non-negative and finite
//! * **Interval level**: In (0, 1) and finite
//! * **CV fractions**: Non-empty, all in (0, 1], all finite
//! * **Auto-convergence tolerance**: Positive and finite
//! * **Chunk size**: Meets minimum requirements (streaming)
//! * **Overlap**: Less than chunk size (streaming)
//! * **Window capacity**: Meets minimum requirements (online)
//! * **Min points**: At least 2 and at most window capacity (online)
//!
//! ## Key concepts
//!
//! ### Fail-Fast Validation
//! Validation stops at the first error encountered, returning immediately
//! with a descriptive error. This avoids unnecessary computation and provides
//! quick feedback to users.
//!
//! ### Finite Value Checks
//! All floating-point values must be finite (not NaN or infinity). This
//! prevents numerical issues during smoothing and ensures meaningful results.
//!
//! ### Minimum Point Requirements
//! LOWESS requires at least 2 points for linear regression. Additional
//! constraints apply for specific adapters (streaming, online).
//!
//! ### Fraction Bounds
//! The fraction parameter must be in (0, 1] to ensure meaningful window
//! sizes. A fraction of 0 would result in empty windows, while values > 1
//! are undefined.
//!
//! ## Invariants
//!
//! * All validated inputs satisfy their respective constraints.
//! * Validation is deterministic (same inputs always produce same result).
//! * Error messages are descriptive and include problematic values.
//! * Validation does not modify input data.
//!
//! ## Non-goals
//!
//! * This module does not sort or transform input data.
//! * This module does not perform the smoothing itself.
//! * This module does not provide automatic correction of invalid inputs.
//! * This module does not validate output data.
//!
//! ## Visibility
//!
//! This module is an internal implementation detail used by the LOWESS
//! builder and adapters. It is not part of the public API and may change
//! without notice.

#[cfg(not(feature = "std"))]
use alloc::format;

use crate::primitives::errors::LowessError;
use num_traits::Float;

// ============================================================================
// Validator
// ============================================================================

/// Validation utility for LOWESS configuration and input data.
///
/// Provides static methods for validating various LOWESS parameters and
/// input data. All methods return `Result<(), LowessError>` and fail fast
/// on the first validation error.
pub struct Validator;

impl Validator {
    // ========================================================================
    // Core Input Validation
    // ========================================================================

    /// Validate input arrays for LOWESS smoothing.
    ///
    /// Performs validation of input data arrays.
    ///
    /// # Parameters
    ///
    /// * `x` - Independent variable values
    /// * `y` - Dependent variable values
    ///
    /// # Returns
    ///
    /// `Ok(())` if all checks pass, or the first error encountered.
    ///
    /// # Validation checks
    ///
    /// 1. **Non-empty arrays**: Both x and y must have at least one element
    /// 2. **Matching lengths**: x and y must have the same length
    /// 3. **Sufficient points**: At least 2 points required for regression
    /// 4. **All values finite**: No NaN or infinity in x or y
    ///
    /// # Performance
    ///
    /// Checks are ordered from cheap to expensive. The finite value check
    /// uses a combined loop for cache locality.
    pub fn validate_inputs<T: Float>(x: &[T], y: &[T]) -> Result<(), LowessError> {
        // Check 1: Non-empty arrays
        if x.is_empty() || y.is_empty() {
            return Err(LowessError::EmptyInput);
        }

        // Check 2: Matching lengths
        let n = x.len();
        if n != y.len() {
            return Err(LowessError::MismatchedInputs {
                x_len: n,
                y_len: y.len(),
            });
        }

        // Check 3: Sufficient points for regression
        if n < 2 {
            return Err(LowessError::TooFewPoints { got: n, min: 2 });
        }

        // Check 4: All values finite (combined loop for cache locality)
        for i in 0..n {
            if !x[i].is_finite() {
                return Err(LowessError::InvalidNumericValue(format!(
                    "x[{}]={}",
                    i,
                    x[i].to_f64().unwrap_or(f64::NAN)
                )));
            }
            if !y[i].is_finite() {
                return Err(LowessError::InvalidNumericValue(format!(
                    "y[{}]={}",
                    i,
                    y[i].to_f64().unwrap_or(f64::NAN)
                )));
            }
        }

        Ok(())
    }

    // ========================================================================
    // Parameter Validation
    // ========================================================================

    /// Validate delta parameter for interpolation optimization.
    ///
    /// Delta controls the distance threshold for point skipping during
    /// fitting. It must be non-negative and finite.
    ///
    /// # Parameters
    ///
    /// * `delta` - Delta value to validate
    ///
    /// # Returns
    ///
    /// `Ok(())` if delta is valid, `Err(LowessError::InvalidDelta)` otherwise.
    ///
    /// # Valid range
    ///
    /// delta ≥ 0 and finite (not NaN or infinity)
    pub fn validate_delta<T: Float>(delta: T) -> Result<(), LowessError> {
        if !delta.is_finite() || delta < T::zero() {
            return Err(LowessError::InvalidDelta(
                delta.to_f64().unwrap_or(f64::NAN),
            ));
        }
        Ok(())
    }

    /// Validate smoothing fraction parameter.
    ///
    /// The fraction controls the proportion of data points used in each
    /// local regression. It must be in (0, 1] and finite.
    ///
    /// # Parameters
    ///
    /// * `fraction` - Smoothing fraction to validate
    ///
    /// # Returns
    ///
    /// `Ok(())` if fraction is valid, `Err(LowessError::InvalidFraction)` otherwise.
    ///
    /// # Valid range
    ///
    /// 0 < fraction ≤ 1 and finite (not NaN or infinity)
    pub fn validate_fraction<T: Float>(fraction: T) -> Result<(), LowessError> {
        if !fraction.is_finite() || fraction <= T::zero() || fraction > T::one() {
            return Err(LowessError::InvalidFraction(
                fraction.to_f64().unwrap_or(f64::NAN),
            ));
        }
        Ok(())
    }

    /// Validate confidence/prediction interval level.
    ///
    /// The interval level represents the desired coverage probability
    /// (e.g., 0.95 for 95% intervals). It must be in (0, 1) and finite.
    ///
    /// # Parameters
    ///
    /// * `level` - Interval level to validate
    ///
    /// # Returns
    ///
    /// `Ok(())` if level is valid, `Err(LowessError::InvalidIntervals)` otherwise.
    ///
    /// # Valid range
    ///
    /// 0 < level < 1 and finite (not NaN or infinity)
    pub fn validate_interval_level<T: Float>(level: T) -> Result<(), LowessError> {
        if !level.is_finite() || level <= T::zero() || level >= T::one() {
            return Err(LowessError::InvalidIntervals(
                level.to_f64().unwrap_or(f64::NAN),
            ));
        }
        Ok(())
    }

    /// Validate cross-validation fractions.
    ///
    /// CV fractions are the candidate smoothing fractions to test during
    /// cross-validation. All must be in (0, 1] and finite.
    ///
    /// # Parameters
    ///
    /// * `fracs` - Array of fractions to validate
    ///
    /// # Returns
    ///
    /// `Ok(())` if all fractions are valid, or the first error encountered.
    ///
    /// # Valid range
    ///
    /// * Array must be non-empty
    /// * Each fraction must be in (0, 1] and finite
    pub fn validate_cv_fractions<T: Float>(fracs: &[T]) -> Result<(), LowessError> {
        if fracs.is_empty() {
            return Err(LowessError::InvalidFraction(0.0));
        }

        for &f in fracs {
            if !f.is_finite() || f <= T::zero() || f > T::one() {
                return Err(LowessError::InvalidFraction(f.to_f64().unwrap_or(0.0)));
            }
        }

        Ok(())
    }

    /// Validate auto-convergence tolerance.
    ///
    /// The tolerance controls when iterative robustness refinement stops.
    /// It must be positive and finite.
    ///
    /// # Parameters
    ///
    /// * `tol` - Tolerance value to validate
    ///
    /// # Returns
    ///
    /// `Ok(())` if tolerance is valid, `Err(LowessError::InvalidTolerance)` otherwise.
    ///
    /// # Valid range
    ///
    /// tol > 0 and finite (not NaN or infinity)
    pub fn validate_tolerance<T: Float>(tol: T) -> Result<(), LowessError> {
        if !tol.is_finite() || tol <= T::zero() {
            return Err(LowessError::InvalidTolerance(
                tol.to_f64().unwrap_or(f64::NAN),
            ));
        }
        Ok(())
    }

    // ========================================================================
    // Adapter-Specific Validation
    // ========================================================================

    /// Validate chunk size for streaming adapter.
    ///
    /// The chunk size determines how many points are processed at once in
    /// streaming mode. It must meet a minimum threshold for meaningful
    /// local regression.
    ///
    /// # Parameters
    ///
    /// * `chunk_size` - Chunk size to validate
    /// * `min` - Minimum required chunk size
    ///
    /// # Returns
    ///
    /// `Ok(())` if chunk size is valid, `Err(LowessError::InvalidChunkSize)` otherwise.
    ///
    /// # Valid range
    ///
    /// chunk_size ≥ min
    pub fn validate_chunk_size(chunk_size: usize, min: usize) -> Result<(), LowessError> {
        if chunk_size < min {
            return Err(LowessError::InvalidChunkSize {
                got: chunk_size,
                min,
            });
        }
        Ok(())
    }

    /// Validate overlap for streaming adapter.
    ///
    /// The overlap determines how many points are shared between consecutive
    /// chunks. It must be less than the chunk size to ensure progress.
    ///
    /// # Parameters
    ///
    /// * `overlap` - Overlap size to validate
    /// * `chunk_size` - Chunk size for context
    ///
    /// # Returns
    ///
    /// `Ok(())` if overlap is valid, `Err(LowessError::InvalidOverlap)` otherwise.
    ///
    /// # Valid range
    ///
    /// overlap < chunk_size
    pub fn validate_overlap(overlap: usize, chunk_size: usize) -> Result<(), LowessError> {
        if overlap >= chunk_size {
            return Err(LowessError::InvalidOverlap {
                overlap,
                chunk_size,
            });
        }
        Ok(())
    }

    /// Validate window capacity for online adapter.
    ///
    /// The window capacity determines the maximum number of points retained
    /// in the sliding window. It must meet a minimum threshold for meaningful
    /// local regression.
    ///
    /// # Parameters
    ///
    /// * `window_capacity` - Window capacity to validate
    /// * `min` - Minimum required capacity
    ///
    /// # Returns
    ///
    /// `Ok(())` if capacity is valid, `Err(LowessError::InvalidWindowCapacity)` otherwise.
    ///
    /// # Valid range
    ///
    /// window_capacity ≥ min
    pub fn validate_window_capacity(window_capacity: usize, min: usize) -> Result<(), LowessError> {
        if window_capacity < min {
            return Err(LowessError::InvalidWindowCapacity {
                got: window_capacity,
                min,
            });
        }
        Ok(())
    }

    /// Validate min_points for online adapter.
    ///
    /// The min_points parameter determines when smoothing begins in online
    /// mode. It must be at least 2 (for regression) and at most the window
    /// capacity.
    ///
    /// # Parameters
    ///
    /// * `min_points` - Minimum points to validate
    /// * `window_capacity` - Window capacity for context
    ///
    /// # Returns
    ///
    /// `Ok(())` if min_points is valid, `Err(LowessError::InvalidMinPoints)` otherwise.
    ///
    /// # Valid range
    ///
    /// 2 ≤ min_points ≤ window_capacity
    pub fn validate_min_points(
        min_points: usize,
        window_capacity: usize,
    ) -> Result<(), LowessError> {
        if min_points < 2 || min_points > window_capacity {
            return Err(LowessError::InvalidMinPoints {
                got: min_points,
                window_capacity,
            });
        }
        Ok(())
    }
}
