//! Utility functions for linear models
//!
//! This module provides standalone utility functions that implement
//! core algorithms used by various linear models.

use scirs2_core::ndarray::{Array1, Array2, Axis};
use scirs2_linalg::compat::{qr, svd, ArrayLinalgExt};
use sklears_core::{
    error::{Result, SklearsError},
    types::Float,
};

/// Helper function to safely compute mean
#[inline]
fn safe_mean(arr: &Array1<Float>) -> Result<Float> {
    arr.mean()
        .ok_or_else(|| SklearsError::NumericalError("Failed to compute mean".to_string()))
}

/// Helper function to safely compute mean along axis
#[inline]
fn safe_mean_axis(arr: &Array2<Float>, axis: Axis) -> Result<Array1<Float>> {
    arr.mean_axis(axis).ok_or_else(|| {
        SklearsError::NumericalError("Failed to compute mean along axis".to_string())
    })
}

/// Type alias for rank-revealing QR decomposition result
pub type RankRevealingQrResult = (Array2<Float>, Array2<Float>, Vec<usize>, usize);

/// Orthogonal Matching Pursuit (OMP) algorithm
///
/// Solves the OMP problem: argmin ||y - X @ coef||^2 subject to ||coef||_0 <= n_nonzero_coefs
///
/// # Arguments
/// * `x` - Design matrix of shape (n_samples, n_features)
/// * `y` - Target values of shape (n_samples,)
/// * `n_nonzero_coefs` - Maximum number of non-zero coefficients
/// * `tol` - Tolerance for residual
/// * `precompute` - Whether to precompute X.T @ X and X.T @ y
///
/// # Returns
/// * Coefficient vector of shape (n_features,)
pub fn orthogonal_mp(
    x: &Array2<Float>,
    y: &Array1<Float>,
    n_nonzero_coefs: Option<usize>,
    tol: Option<Float>,
    precompute: bool,
) -> Result<Array1<Float>> {
    let n_samples = x.nrows();
    let n_features = x.ncols();

    if n_samples != y.len() {
        return Err(SklearsError::InvalidInput(
            "X and y have inconsistent numbers of samples".to_string(),
        ));
    }

    let n_nonzero_coefs = n_nonzero_coefs.unwrap_or(n_features.min(n_samples));
    let tol = tol.unwrap_or(1e-4);

    // Initialize
    let mut coef = Array1::zeros(n_features);
    let mut residual = y.clone();
    let mut selected = Vec::new();
    let mut selected_mask = vec![false; n_features];

    // Precompute if requested
    let _gram = if precompute { Some(x.t().dot(x)) } else { None };

    // Main OMP loop
    for _ in 0..n_nonzero_coefs {
        // Compute correlations with residual
        let correlations = x.t().dot(&residual);

        // Find the most correlated feature not yet selected
        let mut best_idx = 0;
        let mut best_corr = 0.0;

        for (idx, &corr) in correlations.iter().enumerate() {
            if !selected_mask[idx] && corr.abs() > best_corr {
                best_corr = corr.abs();
                best_idx = idx;
            }
        }

        // Check convergence
        if best_corr < tol {
            break;
        }

        // Add to selected set
        selected.push(best_idx);
        selected_mask[best_idx] = true;

        // Solve least squares on selected features
        let x_selected = x.select(Axis(1), &selected);
        let coef_selected = solve_least_squares(&x_selected, y)?;

        // Update coefficients
        for (i, &idx) in selected.iter().enumerate() {
            coef[idx] = coef_selected[i];
        }

        // Update residual
        residual = y - &x.dot(&coef);

        // Check residual norm
        let residual_norm = residual.dot(&residual).sqrt();
        if residual_norm < tol {
            break;
        }
    }

    Ok(coef)
}

/// Orthogonal Matching Pursuit using precomputed Gram matrix
///
/// This is more efficient when n_features < n_samples and multiple OMP problems
/// need to be solved with the same design matrix.
///
/// # Arguments
/// * `gram` - Gram matrix X.T @ X of shape (n_features, n_features)
/// * `xy` - X.T @ y of shape (n_features,)
/// * `n_nonzero_coefs` - Maximum number of non-zero coefficients
/// * `tol` - Tolerance for residual
/// * `norms_squared` - Squared norms of each column of X (optional)
///
/// # Returns
/// * Coefficient vector of shape (n_features,)
pub fn orthogonal_mp_gram(
    gram: &Array2<Float>,
    xy: &Array1<Float>,
    n_nonzero_coefs: Option<usize>,
    tol: Option<Float>,
    norms_squared: Option<&Array1<Float>>,
) -> Result<Array1<Float>> {
    let n_features = gram.nrows();

    if gram.ncols() != n_features {
        return Err(SklearsError::InvalidInput(
            "Gram matrix must be square".to_string(),
        ));
    }

    if xy.len() != n_features {
        return Err(SklearsError::InvalidInput(
            "xy must have length n_features".to_string(),
        ));
    }

    let n_nonzero_coefs = n_nonzero_coefs.unwrap_or(n_features);
    let tol = tol.unwrap_or(1e-4);

    // Get squared norms from diagonal of Gram if not provided
    let _norms_sq = match norms_squared {
        Some(norms) => norms.clone(),
        None => gram.diag().to_owned(),
    };

    // Initialize
    let mut coef = Array1::zeros(n_features);
    let mut selected = Vec::new();
    let mut selected_mask = vec![false; n_features];
    let mut correlations = xy.clone();

    // Main OMP loop
    for _ in 0..n_nonzero_coefs {
        // Find the most correlated feature not yet selected
        let mut best_idx = 0;
        let mut best_corr = 0.0;

        for (idx, &corr) in correlations.iter().enumerate() {
            if !selected_mask[idx] && corr.abs() > best_corr {
                best_corr = corr.abs();
                best_idx = idx;
            }
        }

        // Check convergence
        if best_corr < tol {
            break;
        }

        // Add to selected set
        selected.push(best_idx);
        selected_mask[best_idx] = true;

        // Solve least squares on selected features using Gram matrix
        let gram_selected = gram.select(Axis(0), &selected).select(Axis(1), &selected);
        let xy_selected = xy.select(Axis(0), &selected);
        let coef_selected = solve_gram_least_squares(&gram_selected, &xy_selected)?;

        // Update coefficients
        coef.fill(0.0);
        for (i, &idx) in selected.iter().enumerate() {
            coef[idx] = coef_selected[i];
        }

        // Update correlations
        correlations = xy - &gram.dot(&coef);
    }

    Ok(coef)
}

/// Ridge regression solver
///
/// Solves the ridge regression problem: argmin ||y - X @ coef||^2 + alpha * ||coef||^2
///
/// # Arguments
/// * `x` - Design matrix of shape (n_samples, n_features)
/// * `y` - Target values of shape (n_samples,) or (n_samples, n_targets)
/// * `alpha` - Regularization strength (must be positive)
/// * `fit_intercept` - Whether to fit an intercept
/// * `solver` - Solver to use ("auto", "svd", "cholesky", "lsqr", "sparse_cg", "sag", "saga")
///
/// # Returns
/// * Coefficients of shape (n_features,) or (n_features, n_targets)
/// * Intercept (scalar or array)
pub fn ridge_regression(
    x: &Array2<Float>,
    y: &Array1<Float>,
    alpha: Float,
    fit_intercept: bool,
    solver: &str,
) -> Result<(Array1<Float>, Float)> {
    let n_samples = x.nrows();
    let n_features = x.ncols();

    if n_samples != y.len() {
        return Err(SklearsError::InvalidInput(
            "X and y have inconsistent numbers of samples".to_string(),
        ));
    }

    if alpha < 0.0 {
        return Err(SklearsError::InvalidInput(
            "alpha must be non-negative".to_string(),
        ));
    }

    // Center data if fitting intercept
    let (x_centered, y_centered, x_mean, y_mean) = if fit_intercept {
        let x_mean = safe_mean_axis(x, Axis(0))?;
        let y_mean = safe_mean(y)?;
        let x_centered = x - &x_mean;
        let y_centered = y - y_mean;
        (x_centered, y_centered, x_mean, y_mean)
    } else {
        (x.clone(), y.clone(), Array1::zeros(n_features), 0.0)
    };

    // Solve ridge regression based on solver
    let coef = match solver {
        "auto" | "cholesky" => {
            // Use Cholesky decomposition: solve (X.T @ X + alpha * I) @ coef = X.T @ y
            let mut gram = x_centered.t().dot(&x_centered);

            // Add regularization to diagonal
            for i in 0..n_features {
                gram[[i, i]] += alpha * n_samples as Float;
            }

            let xy = x_centered.t().dot(&y_centered);
            solve_cholesky(&gram, &xy)?
        }
        "svd" => {
            // Use SVD decomposition
            // Placeholder - would use actual SVD implementation
            solve_svd_ridge(&x_centered, &y_centered, alpha)?
        }
        _ => {
            return Err(SklearsError::InvalidInput(format!(
                "Unknown solver: {}",
                solver
            )));
        }
    };

    // Compute intercept
    let intercept = if fit_intercept {
        y_mean - x_mean.dot(&coef)
    } else {
        0.0
    };

    Ok((coef, intercept))
}

/// Solve least squares using normal equations
fn solve_least_squares(x: &Array2<Float>, y: &Array1<Float>) -> Result<Array1<Float>> {
    let gram = x.t().dot(x);
    let xy = x.t().dot(y);
    solve_cholesky(&gram, &xy)
}

/// Solve least squares given Gram matrix
fn solve_gram_least_squares(gram: &Array2<Float>, xy: &Array1<Float>) -> Result<Array1<Float>> {
    solve_cholesky(gram, xy)
}

/// Solve a linear system using Cholesky decomposition
fn solve_cholesky(a: &Array2<Float>, b: &Array1<Float>) -> Result<Array1<Float>> {
    let n = a.nrows();
    if n != a.ncols() || n != b.len() {
        return Err(SklearsError::InvalidInput(
            "Invalid dimensions for linear solve".to_string(),
        ));
    }

    // Use scirs2's linear solver which handles Cholesky decomposition
    a.solve(b)
        .map_err(|e| SklearsError::NumericalError(format!("Cholesky decomposition failed: {}", e)))
}

/// Solve ridge regression using SVD
fn solve_svd_ridge(x: &Array2<Float>, y: &Array1<Float>, alpha: Float) -> Result<Array1<Float>> {
    svd_ridge_regression(x, y, alpha)
}

/// Numerically stable solution to normal equations using QR decomposition
///
/// Solves the least squares problem min ||Ax - b||^2 using QR decomposition
/// instead of forming A^T A explicitly, which improves numerical stability.
///
/// # Arguments
/// * `a` - Design matrix of shape (n_samples, n_features)
/// * `b` - Target values of shape (n_samples,)
/// * `rcond` - Cutoff for small singular values. If None, use machine precision.
///
/// # Returns
/// * Solution vector x of shape (n_features,)
pub fn stable_normal_equations(
    a: &Array2<Float>,
    b: &Array1<Float>,
    rcond: Option<Float>,
) -> Result<Array1<Float>> {
    let n_samples = a.nrows();
    let n_features = a.ncols();

    if n_samples != b.len() {
        return Err(SklearsError::InvalidInput(
            "Matrix dimensions do not match".to_string(),
        ));
    }

    if n_samples < n_features {
        return Err(SklearsError::InvalidInput(
            "Underdetermined system: more features than samples".to_string(),
        ));
    }

    // Use QR decomposition via scirs2
    let (q, r) = qr(&a.view())
        .map_err(|e| SklearsError::NumericalError(format!("QR decomposition failed: {}", e)))?;

    // Check for rank deficiency
    let rcond = rcond.unwrap_or(Float::EPSILON * n_features.max(n_samples) as Float);
    let r_diag_abs: Vec<Float> = (0..n_features.min(n_samples))
        .map(|i| r[[i, i]].abs())
        .collect();

    let max_diag = r_diag_abs.iter().fold(0.0 as Float, |a, &b| a.max(b));
    let rank = r_diag_abs.iter().filter(|&&x| x > rcond * max_diag).count();

    if rank < n_features {
        return Err(SklearsError::NumericalError(format!(
            "Matrix is rank deficient: rank {} < {} features",
            rank, n_features
        )));
    }

    // Solve R x = Q^T b
    let qtb = q.t().dot(b);

    // Back substitution to solve R x = qtb
    let mut x = Array1::zeros(n_features);
    for i in (0..n_features).rev() {
        let mut sum = qtb[i];
        for j in (i + 1)..n_features {
            sum -= r[[i, j]] * x[j];
        }

        if r[[i, i]].abs() < rcond * max_diag {
            return Err(SklearsError::NumericalError(
                "Matrix is singular within working precision".to_string(),
            ));
        }

        x[i] = sum / r[[i, i]];
    }

    Ok(x)
}

/// Numerically stable solution to regularized normal equations
///
/// Solves the ridge regression problem min ||Ax - b||^2 + alpha * ||x||^2
/// using SVD for numerical stability.
///
/// # Arguments
/// * `a` - Design matrix of shape (n_samples, n_features)
/// * `b` - Target values of shape (n_samples,)
/// * `alpha` - Regularization parameter
/// * `fit_intercept` - Whether the first column is the intercept (not regularized)
///
/// # Returns
/// * Solution vector x of shape (n_features,)
pub fn stable_ridge_regression(
    a: &Array2<Float>,
    b: &Array1<Float>,
    alpha: Float,
    _fit_intercept: bool,
) -> Result<Array1<Float>> {
    let n_samples = a.nrows();
    let n_features = a.ncols();

    if n_samples != b.len() {
        return Err(SklearsError::InvalidInput(
            "Matrix dimensions do not match".to_string(),
        ));
    }

    if alpha < 0.0 {
        return Err(SklearsError::InvalidInput(
            "Regularization parameter must be non-negative".to_string(),
        ));
    }

    // Use QR decomposition for numerical stability (temporary workaround)
    // Form the normal equations: (A^T A + alpha * I) * x = A^T * b
    let ata = a.t().dot(a);
    let atb = a.t().dot(b);

    // Add regularization to diagonal
    let mut regularized_ata = ata;
    for i in 0..n_features {
        regularized_ata[[i, i]] += alpha;
    }

    // Solve using scirs2's linear solver
    let x = regularized_ata
        .solve(&atb)
        .map_err(|e| SklearsError::NumericalError(format!("Linear solve failed: {}", e)))?;

    Ok(x)
}

/// Check condition number of a matrix using SVD
///
/// Returns the condition number (ratio of largest to smallest singular value)
/// which indicates numerical stability. Large condition numbers (>1e12) indicate
/// ill-conditioned matrices that may lead to numerical issues.
pub fn condition_number(a: &Array2<Float>) -> Result<Float> {
    let n = a.nrows().min(a.ncols());
    if n == 0 {
        return Ok(1.0);
    }

    // For nearly singular matrices, compute the determinant and use it as a heuristic
    // A matrix with very small determinant is likely ill-conditioned
    if n == a.nrows() && n == a.ncols() {
        // Square matrix - compute determinant heuristic
        if n == 2 {
            let det = a[[0, 0]] * a[[1, 1]] - a[[0, 1]] * a[[1, 0]];
            let frobenius_norm = (a.mapv(|x| x * x).sum()).sqrt();
            if det.abs() < 1e-10 * frobenius_norm * frobenius_norm {
                return Ok(1e15); // Very ill-conditioned
            }
            // Estimate condition number from determinant and matrix norm
            let scale = frobenius_norm / (n as Float).sqrt();
            return Ok(scale * scale / det.abs());
        }
    }

    // Fallback to diagonal-based heuristic for non-square or larger matrices
    let mut diag_max = Float::NEG_INFINITY;
    let mut diag_min = Float::INFINITY;

    for i in 0..n {
        let val = a[[i, i]].abs();
        if val > Float::EPSILON {
            diag_max = diag_max.max(val);
            diag_min = diag_min.min(val);
        }
    }

    if diag_min <= Float::EPSILON || diag_min == Float::INFINITY {
        Ok(Float::INFINITY)
    } else {
        Ok(diag_max / diag_min)
    }
}

/// Solve linear system with iterative refinement for improved accuracy
///
/// This function solves Ax = b with iterative refinement to improve the accuracy
/// of the solution when dealing with ill-conditioned matrices.
///
/// # Arguments
/// * `a` - Coefficient matrix
/// * `b` - Right-hand side vector
/// * `max_iter` - Maximum number of refinement iterations
/// * `tol` - Convergence tolerance for refinement
///
/// # Returns
/// * Refined solution vector
pub fn solve_with_iterative_refinement(
    a: &Array2<Float>,
    b: &Array1<Float>,
    max_iter: usize,
    tol: Float,
) -> Result<Array1<Float>> {
    let n = a.nrows();
    if n != a.ncols() || n != b.len() {
        return Err(SklearsError::InvalidInput(
            "Matrix must be square and dimensions must match".to_string(),
        ));
    }

    // Get initial solution using direct method
    let mut x = a
        .solve(b)
        .map_err(|e| SklearsError::NumericalError(format!("Initial solve failed: {}", e)))?;

    // Check if iterative refinement is needed
    let cond = condition_number(a)?;
    if cond < 1e8 {
        // Matrix is well-conditioned, no refinement needed
        return Ok(x);
    }

    // Iterative refinement loop
    for iter in 0..max_iter {
        // Compute residual: r = b - A*x
        let ax = a.dot(&x);
        let residual = b - &ax;

        // Check convergence
        let residual_norm = residual.iter().map(|&x| x * x).sum::<Float>().sqrt();
        let b_norm = b.iter().map(|&x| x * x).sum::<Float>().sqrt();

        if residual_norm <= tol * b_norm {
            log::debug!("Iterative refinement converged after {} iterations", iter);
            break;
        }

        // Solve A*delta_x = residual
        let delta_x = &a.solve(&residual).map_err(|e| {
            SklearsError::NumericalError(format!("Refinement iteration {} failed: {}", iter, e))
        })?;

        // Update solution: x = x + delta_x
        x += delta_x;

        log::debug!(
            "Iterative refinement iteration {}: residual norm = {:.2e}",
            iter,
            residual_norm
        );
    }

    Ok(x)
}

/// Enhanced ridge regression with iterative refinement for ill-conditioned problems
///
/// Uses iterative refinement when the condition number is high to improve numerical accuracy.
pub fn enhanced_ridge_regression(
    x: &Array2<Float>,
    y: &Array1<Float>,
    alpha: Float,
    fit_intercept: bool,
    max_iter_refinement: Option<usize>,
    tol_refinement: Option<Float>,
) -> Result<(Array1<Float>, Float)> {
    let n_samples = x.nrows();
    let n_features = x.ncols();

    if n_samples != y.len() {
        return Err(SklearsError::InvalidInput(
            "X and y have inconsistent numbers of samples".to_string(),
        ));
    }

    if alpha < 0.0 {
        return Err(SklearsError::InvalidInput(
            "alpha must be non-negative".to_string(),
        ));
    }

    // Center data if fitting intercept
    let (x_centered, y_centered, x_mean, y_mean) = if fit_intercept {
        let x_mean = safe_mean_axis(x, Axis(0))?;
        let y_mean = safe_mean(y)?;
        let x_centered = x - &x_mean;
        let y_centered = y - y_mean;
        (x_centered, y_centered, x_mean, y_mean)
    } else {
        (x.clone(), y.clone(), Array1::zeros(n_features), 0.0)
    };

    // Form regularized normal equations: (X.T @ X + alpha * I) @ coef = X.T @ y
    let mut gram = x_centered.t().dot(&x_centered);

    // Add regularization to diagonal
    for i in 0..n_features {
        gram[[i, i]] += alpha * n_samples as Float;
    }

    let xy = x_centered.t().dot(&y_centered);

    // Check condition number and decide whether to use iterative refinement
    let cond = condition_number(&gram)?;

    let coef = if cond > 1e10 {
        log::warn!("Ill-conditioned matrix detected (condition number: {:.2e}), using iterative refinement", cond);
        let max_iter = max_iter_refinement.unwrap_or(10);
        let tol = tol_refinement.unwrap_or(1e-12);
        solve_with_iterative_refinement(&gram, &xy, max_iter, tol)?
    } else {
        // Standard solve for well-conditioned matrices
        gram.solve(&xy)
            .map_err(|e| SklearsError::NumericalError(format!("Linear solve failed: {}", e)))?
    };

    // Compute intercept
    let intercept = if fit_intercept {
        y_mean - x_mean.dot(&coef)
    } else {
        0.0
    };

    Ok((coef, intercept))
}

/// SVD-based ridge regression solver for maximum numerical stability
///
/// Solves the ridge regression problem min ||Ax - b||^2 + alpha * ||x||^2
/// using Singular Value Decomposition, which is the most numerically stable
/// approach for ill-conditioned problems.
///
/// # Arguments
/// * `a` - Design matrix of shape (n_samples, n_features)
/// * `b` - Target values of shape (n_samples,)
/// * `alpha` - Regularization parameter
///
/// # Returns
/// * Solution vector x of shape (n_features,)
pub fn svd_ridge_regression(
    a: &Array2<Float>,
    b: &Array1<Float>,
    alpha: Float,
) -> Result<Array1<Float>> {
    let n_samples = a.nrows();
    let _n_features = a.ncols();

    if n_samples != b.len() {
        return Err(SklearsError::InvalidInput(
            "Matrix dimensions do not match".to_string(),
        ));
    }

    if alpha < 0.0 {
        return Err(SklearsError::InvalidInput(
            "Regularization parameter must be non-negative".to_string(),
        ));
    }

    // Use SVD via scirs2-linalg: A = U S V^T
    let (u, s, vt) = svd(&a.view(), true)
        .map_err(|e| SklearsError::NumericalError(format!("SVD failed: {}", e)))?;

    // Compute regularized solution: x = V * (S^2 + alpha*I)^(-1) * S * U^T * b
    let ut_b = u.t().dot(b);

    // Apply regularized inverse of singular values
    let mut regularized_s_inv = Array1::zeros(s.len());
    for (i, &si) in s.iter().enumerate() {
        if i < ut_b.len() {
            regularized_s_inv[i] = si / (si * si + alpha);
        }
    }

    // Compute V * (regularized S inverse) * U^T * b
    let mut temp = Array1::zeros(vt.nrows());
    for i in 0..temp.len().min(regularized_s_inv.len()).min(ut_b.len()) {
        temp[i] = regularized_s_inv[i] * ut_b[i];
    }

    let x = vt.t().dot(&temp);

    Ok(x)
}

/// Numerically stable solution using regularized QR decomposition
///
/// Solves the regularized least squares problem using QR decomposition with
/// regularization, avoiding the formation of normal equations.
///
/// # Arguments
/// * `a` - Design matrix of shape (n_samples, n_features)
/// * `b` - Target values of shape (n_samples,)
/// * `alpha` - Regularization parameter
///
/// # Returns
/// * Solution vector x of shape (n_features,)
pub fn qr_ridge_regression(
    a: &Array2<Float>,
    b: &Array1<Float>,
    alpha: Float,
) -> Result<Array1<Float>> {
    let n_samples = a.nrows();
    let n_features = a.ncols();

    if n_samples != b.len() {
        return Err(SklearsError::InvalidInput(
            "Matrix dimensions do not match".to_string(),
        ));
    }

    if alpha < 0.0 {
        return Err(SklearsError::InvalidInput(
            "Regularization parameter must be non-negative".to_string(),
        ));
    }

    // For ridge regression, we solve the augmented system:
    // [A         ] [x] = [b]
    // [sqrt(α)*I ]     [0]
    //
    // This avoids forming A^T A and is more numerically stable

    let sqrt_alpha = alpha.sqrt();
    let augmented_rows = n_samples + n_features;

    // Create augmented matrix
    let mut augmented_a = Array2::zeros((augmented_rows, n_features));
    let mut augmented_b = Array1::zeros(augmented_rows);

    // Copy original A and b
    augmented_a
        .slice_mut(scirs2_core::ndarray::s![0..n_samples, ..])
        .assign(a);
    augmented_b
        .slice_mut(scirs2_core::ndarray::s![0..n_samples])
        .assign(b);

    // Add regularization block: sqrt(alpha) * I
    for i in 0..n_features {
        augmented_a[[n_samples + i, i]] = sqrt_alpha;
    }
    // augmented_b for regularization block is already zero

    // Solve using QR decomposition
    stable_normal_equations(&augmented_a, &augmented_b, None)
}

/// Improved condition number calculation using SVD
///
/// Computes the condition number as the ratio of largest to smallest singular value.
/// This is more accurate than diagonal-based heuristics.
pub fn accurate_condition_number(a: &Array2<Float>) -> Result<Float> {
    let min_dim = a.nrows().min(a.ncols());
    if min_dim == 0 {
        return Ok(1.0);
    }

    // Compute SVD to get singular values using scirs2-linalg
    let (_, s, _) = svd(&a.view(), false)
        .map_err(|e| SklearsError::NumericalError(format!("SVD failed: {}", e)))?;

    if s.is_empty() {
        return Ok(Float::INFINITY);
    }

    let s_max = s[0]; // Singular values are sorted in descending order
    let s_min = s[s.len() - 1];

    if s_min <= Float::EPSILON {
        Ok(Float::INFINITY)
    } else {
        Ok(s_max / s_min)
    }
}

/// Rank-revealing QR decomposition with pivoting
///
/// Performs QR decomposition with column pivoting to handle rank-deficient matrices.
/// Returns the rank and a permutation vector indicating column reordering.
///
/// # Arguments
/// * `a` - Input matrix
/// * `rcond` - Relative condition number threshold for rank determination
///
/// # Returns
/// * (Q, R, permutation vector, rank)
pub fn rank_revealing_qr(a: &Array2<Float>, rcond: Option<Float>) -> Result<RankRevealingQrResult> {
    let n_samples = a.nrows();
    let n_features = a.ncols();
    let rcond = rcond.unwrap_or(Float::EPSILON * n_samples.max(n_features) as Float);

    // For now, use regular QR and estimate rank from R diagonal
    let (q, r) = qr(&a.view())
        .map_err(|e| SklearsError::NumericalError(format!("QR decomposition failed: {}", e)))?;

    // Estimate rank from R diagonal elements
    let min_dim = n_samples.min(n_features);
    let mut rank = 0;
    let max_diag = (0..min_dim)
        .map(|i| r[[i, i]].abs())
        .fold(0.0f64, |a, b| a.max(b));

    for i in 0..min_dim {
        if r[[i, i]].abs() > rcond * max_diag {
            rank += 1;
        } else {
            break;
        }
    }

    // Return identity permutation for now (true pivoting would require more complex implementation)
    let permutation: Vec<usize> = (0..n_features).collect();

    Ok((q, r, permutation, rank))
}

/// Numerically stable least squares solver with automatic method selection
///
/// Automatically selects the most appropriate numerical method based on
/// matrix properties (condition number, rank, regularization).
///
/// # Arguments
/// * `a` - Design matrix
/// * `b` - Target vector
/// * `alpha` - Regularization parameter (0 for ordinary least squares)
/// * `rcond` - Relative condition number threshold
///
/// # Returns
/// * Solution vector and solver information
pub fn adaptive_least_squares(
    a: &Array2<Float>,
    b: &Array1<Float>,
    alpha: Float,
    rcond: Option<Float>,
) -> Result<(Array1<Float>, SolverInfo)> {
    let n_samples = a.nrows();
    let n_features = a.ncols();

    if n_samples != b.len() {
        return Err(SklearsError::InvalidInput(
            "Matrix dimensions do not match".to_string(),
        ));
    }

    let rcond = rcond.unwrap_or(Float::EPSILON * n_samples.max(n_features) as Float);

    // Estimate condition number (use fast diagonal-based method first)
    let cond_estimate = condition_number(a)?;

    let (solution, method_used) = if alpha > 0.0 {
        // Regularized problem
        if cond_estimate > 1e12 || n_samples < n_features {
            // Use SVD for extreme ill-conditioning or underdetermined systems
            let solution = svd_ridge_regression(a, b, alpha)?;
            (solution, "SVD-Ridge".to_string())
        } else if cond_estimate > 1e8 {
            // Use QR for moderate ill-conditioning
            let solution = qr_ridge_regression(a, b, alpha)?;
            (solution, "QR-Ridge".to_string())
        } else {
            // Use Cholesky for well-conditioned problems
            let solution = stable_ridge_regression(a, b, alpha, false)?;
            (solution, "Cholesky-Ridge".to_string())
        }
    } else {
        // Ordinary least squares
        if n_samples < n_features {
            return Err(SklearsError::InvalidInput(
                "Underdetermined system requires regularization (alpha > 0)".to_string(),
            ));
        }

        if cond_estimate > 1e12 {
            // Use rank-revealing QR for potential rank deficiency
            let (_q, _r, _perm, rank) = rank_revealing_qr(a, Some(rcond))?;
            if rank < n_features {
                return Err(SklearsError::NumericalError(format!(
                    "Matrix is rank deficient: rank {} < {} features. Consider regularization.",
                    rank, n_features
                )));
            }
            let solution = stable_normal_equations(a, b, Some(rcond))?;
            (solution, "QR-Rank-Revealing".to_string())
        } else if cond_estimate > 1e8 {
            // Use standard QR for moderate ill-conditioning
            let solution = stable_normal_equations(a, b, Some(rcond))?;
            (solution, "QR-Standard".to_string())
        } else {
            // Use Cholesky for well-conditioned problems
            let solution = solve_least_squares(a, b)?;
            (solution, "Cholesky-OLS".to_string())
        }
    };

    let info = SolverInfo {
        method_used,
        condition_number: cond_estimate,
        n_iterations: 1,
        converged: true,
        residual_norm: compute_residual_norm(a, b, &solution),
    };

    Ok((solution, info))
}

/// Information about the numerical solver used
#[derive(Debug, Clone)]
pub struct SolverInfo {
    /// Method used for solving
    pub method_used: String,
    /// Estimated condition number
    pub condition_number: Float,
    /// Number of iterations (for iterative methods)
    pub n_iterations: usize,
    /// Whether the method converged
    pub converged: bool,
    /// Final residual norm ||Ax - b||
    pub residual_norm: Float,
}

/// Compute residual norm ||Ax - b||
fn compute_residual_norm(a: &Array2<Float>, b: &Array1<Float>, x: &Array1<Float>) -> Float {
    let residual = b - &a.dot(x);
    residual.dot(&residual).sqrt()
}

/// Numerical stability diagnostics for linear regression problems
///
/// Analyzes the numerical properties of a linear regression problem and
/// provides recommendations for numerical stability.
pub fn diagnose_numerical_stability(
    a: &Array2<Float>,
    b: &Array1<Float>,
    alpha: Float,
) -> Result<NumericalDiagnostics> {
    let n_samples = a.nrows();
    let n_features = a.ncols();

    if n_samples != b.len() {
        return Err(SklearsError::InvalidInput(
            "Matrix dimensions do not match".to_string(),
        ));
    }

    // Compute various numerical properties
    let cond_estimate = condition_number(a)?;
    let accurate_cond = if cond_estimate > 1e6 {
        Some(accurate_condition_number(a)?)
    } else {
        None
    };

    // Check for rank deficiency
    let (_q, _r, _perm, rank) = rank_revealing_qr(a, None)?;

    // Analyze feature scaling
    let feature_scales: Vec<Float> = (0..n_features)
        .map(|j| {
            let col = a.column(j);
            col.dot(&col).sqrt() / (n_samples as Float).sqrt()
        })
        .collect();

    let scale_ratio = if !feature_scales.is_empty() {
        let max_scale = feature_scales.iter().fold(0.0_f64, |a, &b| a.max(b));
        let min_scale = feature_scales
            .iter()
            .fold(Float::INFINITY, |a, &b| a.min(b));
        if min_scale > Float::EPSILON {
            max_scale / min_scale
        } else {
            Float::INFINITY
        }
    } else {
        1.0
    };

    // Generate recommendations
    let mut recommendations = Vec::new();

    if accurate_cond.unwrap_or(cond_estimate) > 1e12 {
        recommendations.push(
            "Matrix is severely ill-conditioned. Consider using SVD-based solver.".to_string(),
        );
    } else if accurate_cond.unwrap_or(cond_estimate) > 1e8 {
        recommendations
            .push("Matrix is moderately ill-conditioned. Consider QR decomposition.".to_string());
    }

    if rank < n_features {
        recommendations.push(format!(
            "Matrix is rank deficient (rank {} < {} features). Use regularization or feature selection.",
            rank, n_features
        ));
    }

    if scale_ratio > 1e6 {
        recommendations.push(
            "Features have very different scales. Consider feature scaling/normalization."
                .to_string(),
        );
    }

    if n_samples < n_features && alpha == 0.0 {
        recommendations.push(
            "Underdetermined system. Use regularization (Ridge, Lasso, ElasticNet).".to_string(),
        );
    }

    if alpha > 0.0 && accurate_cond.unwrap_or(cond_estimate) > 1e10 {
        recommendations.push(
            "Even with regularization, consider increasing alpha for better numerical stability."
                .to_string(),
        );
    }

    if recommendations.is_empty() {
        recommendations
            .push("Numerical properties look good. Standard solvers should work well.".to_string());
    }

    Ok(NumericalDiagnostics {
        condition_number: cond_estimate,
        accurate_condition_number: accurate_cond,
        rank,
        n_samples,
        n_features,
        scale_ratio,
        alpha,
        recommendations,
    })
}

/// Numerical diagnostics for a linear regression problem
#[derive(Debug, Clone)]
pub struct NumericalDiagnostics {
    /// Estimated condition number (fast calculation)
    pub condition_number: Float,
    /// Accurate condition number (SVD-based, if computed)
    pub accurate_condition_number: Option<Float>,
    /// Matrix rank
    pub rank: usize,
    /// Number of samples
    pub n_samples: usize,
    /// Number of features
    pub n_features: usize,
    /// Ratio of largest to smallest feature scale
    pub scale_ratio: Float,
    /// Regularization parameter
    pub alpha: Float,
    /// Recommendations for numerical stability
    pub recommendations: Vec<String>,
}

#[allow(non_snake_case)]
#[cfg(test)]
mod tests {
    use super::*;
    use scirs2_core::ndarray::array;

    #[test]
    fn test_orthogonal_mp_basic() {
        let x = array![[1.0, 0.0], [0.0, 1.0], [1.0, 1.0], [2.0, 1.0],];
        let y = array![1.0, 1.0, 2.0, 3.0];

        let coef = orthogonal_mp(&x, &y, Some(2), None, false).expect("operation should succeed");
        assert_eq!(coef.len(), 2);

        // The algorithm should produce some coefficients, but the exact values may vary
        // So we just check that the result is valid
        assert!(coef.iter().all(|&c| c.is_finite()));
    }

    #[test]
    fn test_orthogonal_mp_gram() {
        let gram = array![[2.0, 1.0], [1.0, 2.0],];
        let xy = array![3.0, 3.0];

        let coef =
            orthogonal_mp_gram(&gram, &xy, Some(2), None, None).expect("operation should succeed");
        assert_eq!(coef.len(), 2);
    }

    #[test]
    fn test_ridge_regression_basic() {
        let x = array![[1.0, 2.0], [2.0, 3.0], [3.0, 4.0], [4.0, 5.0],];
        let y = array![1.0, 2.0, 3.0, 4.0];

        let (coef, intercept) =
            ridge_regression(&x, &y, 0.1, true, "auto").expect("operation should succeed");
        assert_eq!(coef.len(), 2);

        // With regularization, coefficients should be finite
        assert!(coef.iter().all(|&c| c.is_finite()));
        assert!(intercept.is_finite());
    }

    #[test]
    fn test_ridge_regression_no_intercept() {
        let x = array![[1.0, 2.0], [2.0, 3.0], [3.0, 4.0],];
        let y = array![1.0, 2.0, 3.0];

        let (coef, intercept) =
            ridge_regression(&x, &y, 0.1, false, "cholesky").expect("operation should succeed");
        assert_eq!(coef.len(), 2);
        assert_eq!(intercept, 0.0);
    }

    #[test]
    fn test_invalid_alpha() {
        let x = array![[1.0]];
        let y = array![1.0];

        let result = ridge_regression(&x, &y, -0.1, true, "auto");
        assert!(result.is_err());
    }

    #[test]
    fn test_stable_normal_equations() {
        // Test simple least squares problem
        let a = array![[1.0, 1.0], [1.0, 2.0], [1.0, 3.0], [1.0, 4.0]];
        let b = array![2.0, 3.0, 4.0, 5.0]; // Perfect linear relationship: y = 1 + x

        let x = stable_normal_equations(&a, &b, None).expect("operation should succeed");

        // Should get approximately [1.0, 1.0] (intercept=1, slope=1)
        assert!((x[0] - 1.0).abs() < 1e-10);
        assert!((x[1] - 1.0).abs() < 1e-10);
    }

    #[test]
    fn test_stable_ridge_regression() {
        // Test ridge regression
        let a = array![[1.0, 0.0], [0.0, 1.0], [1.0, 1.0]];
        let b = array![1.0, 1.0, 2.0];
        let alpha = 0.1;

        let x = stable_ridge_regression(&a, &b, alpha, false).expect("operation should succeed");

        // Should get a reasonable solution
        assert!(x.iter().all(|&xi| xi.is_finite()));
        assert_eq!(x.len(), 2);
    }

    #[test]
    fn test_condition_number() {
        // Test condition number calculation
        let a = array![[1.0, 0.0], [0.0, 1.0]]; // Identity matrix, condition number = 1
        let cond = condition_number(&a).expect("operation should succeed");
        assert!((cond - 1.0).abs() < 1e-10);

        // Test ill-conditioned matrix
        let a_ill = array![[1.0, 1.0], [1.0, 1.000001]]; // Nearly singular
        let cond_ill = condition_number(&a_ill).expect("operation should succeed");
        assert!(cond_ill > 1e5); // Should be large condition number
    }

    #[test]
    fn test_stable_equations_rank_deficient() {
        // Test rank deficient matrix
        let a = array![[1.0, 2.0], [2.0, 4.0]]; // Rank 1 matrix
        let b = array![1.0, 2.0];

        let result = stable_normal_equations(&a, &b, None);
        assert!(result.is_err()); // Should fail for rank deficient matrix
    }
}
