//! Bivariate interpolation modules
//!
//! This module provides implementations of bivariate splines (splines in 2 dimensions).
//! These methods allow interpolation and smoothing of data points on 2D surfaces.
//!
//! ## Overview
//!
//! * `BivariateSpline` - Base class for bivariate splines
//! * `SmoothBivariateSpline` - Smooth bivariate spline approximation
//! * `LSQBivariateSpline` - Weighted least-squares bivariate spline approximation
//! * `RectBivariateSpline` - Bivariate spline approximation over a rectangular mesh

use scirs2_core::ndarray::{Array1, Array2, ArrayView1, ArrayView2};
use scirs2_core::numeric::{Float, FromPrimitive};
use std::fmt::Debug;

use crate::error::{InterpolateError, InterpolateResult};

pub mod bspline_eval;

/// Trait for all bivariate spline interpolators
pub trait BivariateInterpolator<F: Float + FromPrimitive + Debug + std::fmt::Display> {
    /// Evaluate the spline at points
    fn evaluate(
        &self,
        x: &ArrayView1<F>,
        y: &ArrayView1<F>,
        grid: bool,
    ) -> InterpolateResult<Array2<F>>;

    /// Evaluate the derivative of the spline at points
    fn evaluate_derivative(
        &self,
        x: &ArrayView1<F>,
        y: &ArrayView1<F>,
        dx: usize,
        dy: usize,
        grid: bool,
    ) -> InterpolateResult<Array2<F>>;

    /// Evaluate the integral of the spline over a rectangular region
    fn integral(&self, xa: F, xb: F, ya: F, yb: F) -> InterpolateResult<F>;
}

/// Base struct for bivariate splines
#[derive(Debug, Clone)]
pub struct BivariateSpline<
    F: Float + FromPrimitive + Debug + std::fmt::Display + crate::traits::InterpolationFloat,
> {
    /// x knots
    tx: Array1<F>,
    /// y knots
    ty: Array1<F>,
    /// Coefficients
    c: Array1<F>,
    /// Degree in x direction
    kx: usize,
    /// Degree in y direction
    ky: usize,
    /// Weighted sum of squared residuals of the spline approximation
    fp: Option<F>,
}

impl<F: Float + FromPrimitive + Debug + std::fmt::Display + crate::traits::InterpolationFloat>
    BivariateSpline<F>
{
    /// Create a new bivariate spline from knots and coefficients
    pub fn from_tck(
        tx: Array1<F>,
        ty: Array1<F>,
        c: Array1<F>,
        kx: usize,
        ky: usize,
        fp: Option<F>,
    ) -> Self {
        Self {
            tx,
            ty,
            c,
            kx,
            ky,
            fp,
        }
    }

    /// Get knots in x and y directions
    pub fn get_knots(&self) -> (&Array1<F>, &Array1<F>) {
        (&self.tx, &self.ty)
    }

    /// Get spline coefficients
    pub fn get_coeffs(&self) -> &Array1<F> {
        &self.c
    }

    /// Get the residual (weighted sum of squared errors)
    pub fn get_residual(&self) -> Option<F> {
        self.fp
    }

    /// Validates the input coordinates and values
    fn validate_input(
        x: &ArrayView1<F>,
        y: &ArrayView1<F>,
        z: &ArrayView1<F>,
        w: Option<&ArrayView1<F>>,
        kx: usize,
        ky: usize,
        eps: Option<F>,
    ) -> InterpolateResult<()> {
        // Check lengths
        if x.len() != y.len() || x.len() != z.len() {
            return Err(InterpolateError::invalid_input(
                "x, y, and z should have a same length".to_string(),
            ));
        }

        // Check weights if provided
        if let Some(w) = w {
            if x.len() != w.len() {
                return Err(InterpolateError::invalid_input(
                    "x, y, z, and w should have a same length".to_string(),
                ));
            }
            if !w.iter().all(|&w_i| w_i >= F::zero()) {
                return Err(InterpolateError::invalid_input(
                    "w should be positive".to_string(),
                ));
            }
        }

        // Check epsilon
        if let Some(eps) = eps {
            if !(F::zero() < eps && eps < F::one()) {
                return Err(InterpolateError::invalid_input(
                    "eps should be between (0, 1)".to_string(),
                ));
            }
        }

        // Check data size relative to degrees
        if x.len() < (kx + 1) * (ky + 1) {
            return Err(InterpolateError::invalid_input(
                "The length of x, y and z should be at least (kx+1) * (ky+1)".to_string(),
            ));
        }

        Ok(())
    }

    /// Evaluate the spline or its derivatives at given positions
    fn evaluate_impl(
        &self,
        x: &ArrayView1<F>,
        y: &ArrayView1<F>,
        dx: usize,
        dy: usize,
        grid: bool,
    ) -> InterpolateResult<Array2<F>> {
        if grid {
            self.evaluate_grid(x, y, dx, dy)
        } else {
            self.evaluate_points(x, y, dx, dy)
        }
    }

    /// Evaluate the spline at grid points
    fn evaluate_grid(
        &self,
        x: &ArrayView1<F>,
        y: &ArrayView1<F>,
        dx: usize,
        dy: usize,
    ) -> InterpolateResult<Array2<F>> {
        // For an empty grid, return an empty result
        if x.is_empty() || y.is_empty() {
            return Ok(Array2::zeros((x.len(), y.len())));
        }

        // Check that grid arrays are strictly increasing
        if x.len() >= 2 {
            for i in 0..x.len() - 1 {
                if x[i + 1] <= x[i] {
                    return Err(InterpolateError::invalid_input(
                        "x must be strictly increasing when `grid` is True".to_string(),
                    ));
                }
            }
        }
        if y.len() >= 2 {
            for i in 0..y.len() - 1 {
                if y[i + 1] <= y[i] {
                    return Err(InterpolateError::invalid_input(
                        "y must be strictly increasing when `grid` is True".to_string(),
                    ));
                }
            }
        }

        // Evaluate on the grid
        let mut result = Array2::zeros((x.len(), y.len()));

        // Since we don't have direct access to FITPACK's bispev, we'll compute
        // the grid by evaluating at each point
        for (i, &xi) in x.iter().enumerate() {
            for (j, &yj) in y.iter().enumerate() {
                result[[i, j]] = self.evaluate_single_point(xi, yj, dx, dy)?;
            }
        }

        Ok(result)
    }

    /// Evaluate the spline at individual points
    fn evaluate_points(
        &self,
        x: &ArrayView1<F>,
        y: &ArrayView1<F>,
        dx: usize,
        dy: usize,
    ) -> InterpolateResult<Array2<F>> {
        // For empty input, return empty output
        if x.is_empty() || y.is_empty() {
            return Ok(Array2::zeros((x.len(), x.len())));
        }

        // Check that x and y have the same shape
        if x.shape() != y.shape() {
            return Err(InterpolateError::invalid_input(
                "x and y must have the same shape in point evaluation mode".to_string(),
            ));
        }

        let n = x.len();
        let mut result = Array1::zeros(n);

        // Evaluate at each point
        for i in 0..n {
            result[i] = self.evaluate_single_point(x[i], y[i], dx, dy)?;
        }

        // Create a 2D array from the results
        let mut result2d = Array2::zeros((n, 1));
        for i in 0..n {
            result2d[[i, 0]] = result[i];
        }

        Ok(result2d)
    }

    /// Evaluate the spline at a single point
    fn evaluate_single_point(&self, x: F, y: F, dx: usize, dy: usize) -> InterpolateResult<F> {
        // Check if we're within the domain bounds
        if x < self.tx[self.kx]
            || x > self.tx[self.tx.len() - self.kx - 1]
            || y < self.ty[self.ky]
            || y > self.ty[self.ty.len() - self.ky - 1]
        {
            return Err(InterpolateError::OutOfBounds(format!(
                "Point ({:?}, {:?}) is outside the domain of the spline",
                x, y
            )));
        }

        // Check if derivatives are within degree bounds
        if dx > self.kx || dy > self.ky {
            // Higher-order derivatives than the spline degree are all zero
            return Ok(F::zero());
        }

        // Use the B-spline evaluation routine from bspline_eval module
        if dx == 0 && dy == 0 {
            Ok(bspline_eval::evaluate_bispline(
                x,
                y,
                &self.tx.view(),
                &self.ty.view(),
                &self.c.view(),
                self.kx,
                self.ky,
            ))
        } else {
            Ok(bspline_eval::evaluate_bispline_derivative(
                x,
                y,
                &self.tx.view(),
                &self.ty.view(),
                &self.c.view(),
                self.kx,
                self.ky,
                dx,
                dy,
            ))
        }
    }

    /// Find the interval containing x in the knot sequence
    #[allow(dead_code)] // Currently unused but kept for potential future use
    fn find_interval(&self, x: F, knots: &Array1<F>) -> InterpolateResult<(usize, F)> {
        let n = knots.len();

        // Check bounds
        if x < knots[0] || x > knots[n - 1] {
            return Err(InterpolateError::OutOfBounds(format!(
                "x={:?} is outside the knot range [{:?}, {:?}]",
                x,
                knots[0],
                knots[n - 1]
            )));
        }

        // Find the interval
        for i in 0..(n - 1) {
            if knots[i] <= x && x <= knots[i + 1] {
                // Calculate the normalized position within the interval
                let alpha = if knots[i + 1] > knots[i] {
                    (x - knots[i]) / (knots[i + 1] - knots[i])
                } else {
                    F::zero() // Handle case where knots[i] == knots[i+1]
                };

                return Ok((i, alpha));
            }
        }

        // This should not happen if the bounds check passed
        Err(InterpolateError::ComputationError(
            "Failed to find interval in knot sequence".to_string(),
        ))
    }
}

impl<F: Float + FromPrimitive + Debug + std::fmt::Display + crate::traits::InterpolationFloat>
    BivariateInterpolator<F> for BivariateSpline<F>
{
    fn evaluate(
        &self,
        x: &ArrayView1<F>,
        y: &ArrayView1<F>,
        grid: bool,
    ) -> InterpolateResult<Array2<F>> {
        self.evaluate_impl(x, y, 0, 0, grid)
    }

    fn evaluate_derivative(
        &self,
        x: &ArrayView1<F>,
        y: &ArrayView1<F>,
        dx: usize,
        dy: usize,
        grid: bool,
    ) -> InterpolateResult<Array2<F>> {
        self.evaluate_impl(x, y, dx, dy, grid)
    }

    fn integral(&self, xa: F, xb: F, ya: F, yb: F) -> InterpolateResult<F> {
        // Check if the integration domain is within the spline's domain
        let x_min = self.tx[self.kx];
        let x_max = self.tx[self.tx.len() - self.kx - 1];
        let y_min = self.ty[self.ky];
        let y_max = self.ty[self.ty.len() - self.ky - 1];

        if xa < x_min || xb > x_max || ya < y_min || yb > y_max {
            return Err(InterpolateError::OutOfBounds(format!(
                "Integration domain [{:?}, {:?}] x [{:?}, {:?}] is outside the spline domain [{:?}, {:?}] x [{:?}, {:?}]",
                xa, xb, ya, yb, x_min, x_max, y_min, y_max
            )));
        }

        if xa > xb || ya > yb {
            return Err(InterpolateError::invalid_input(
                "Integration bounds should satisfy xa <= xb and ya <= yb".to_string(),
            ));
        }

        // Use the B-spline integration routine with 10 quadrature points
        Ok(bspline_eval::integrate_bispline(
            xa,
            xb,
            ya,
            yb,
            &self.tx.view(),
            &self.ty.view(),
            &self.c.view(),
            self.kx,
            self.ky,
            Some(10),
        ))
    }
}

/// Smooth bivariate spline approximation
///
/// This class approximates a set of data points with a smooth bivariate spline.
#[derive(Debug, Clone)]
pub struct SmoothBivariateSpline<
    F: Float + FromPrimitive + Debug + std::fmt::Display + crate::traits::InterpolationFloat,
> {
    /// The underlying bivariate spline
    spline: BivariateSpline<F>,
}

/// Builder for SmoothBivariateSpline
pub struct SmoothBivariateSplineBuilder<
    'a,
    F: Float + FromPrimitive + Debug + std::fmt::Display + crate::traits::InterpolationFloat,
> {
    x: &'a ArrayView1<'a, F>,
    y: &'a ArrayView1<'a, F>,
    z: &'a ArrayView1<'a, F>,
    w: Option<&'a ArrayView1<'a, F>>,
    bbox: Option<[F; 4]>,
    kx: usize,
    ky: usize,
    s: Option<F>,
    eps: Option<F>,
}

impl<
        'a,
        F: Float + FromPrimitive + Debug + std::fmt::Display + crate::traits::InterpolationFloat,
    > SmoothBivariateSplineBuilder<'a, F>
{
    /// Create a new builder with required parameters
    pub fn new(
        x: &'a ArrayView1<'a, F>,
        y: &'a ArrayView1<'a, F>,
        z: &'a ArrayView1<'a, F>,
    ) -> Self {
        Self {
            x,
            y,
            z,
            w: None,
            bbox: None,
            kx: 3,
            ky: 3,
            s: None,
            eps: None,
        }
    }

    /// Set weights
    pub fn with_weights(mut self, w: &'a ArrayView1<'a, F>) -> Self {
        self.w = Some(w);
        self
    }

    /// Set boundary box
    pub fn with_bbox(mut self, bbox: [F; 4]) -> Self {
        self.bbox = Some(bbox);
        self
    }

    /// Set degrees of the bivariate spline
    pub fn with_degrees(mut self, kx: usize, ky: usize) -> Self {
        self.kx = kx;
        self.ky = ky;
        self
    }

    /// Set smoothing factor
    pub fn with_smoothing(mut self, s: F) -> Self {
        self.s = Some(s);
        self
    }

    /// Set threshold for determining the effective rank
    pub fn with_epsilon(mut self, eps: F) -> Self {
        self.eps = Some(eps);
        self
    }

    /// Build the SmoothBivariateSpline
    pub fn build(self) -> InterpolateResult<SmoothBivariateSpline<F>> {
        // Validate input
        BivariateSpline::validate_input(
            self.x, self.y, self.z, self.w, self.kx, self.ky, self.eps,
        )?;

        // Check smoothing factor
        if let Some(s) = self.s {
            if s < F::zero() {
                return Err(InterpolateError::invalid_input(
                    "s should be s >= 0.0".to_string(),
                ));
            }
        }

        // Determine boundary
        let _bbox = match self.bbox {
            Some(bbox) => bbox,
            None => [
                self.x.iter().copied().fold(F::infinity(), F::min),
                self.x.iter().copied().fold(F::neg_infinity(), F::max),
                self.y.iter().copied().fold(F::infinity(), F::min),
                self.y.iter().copied().fold(F::neg_infinity(), F::max),
            ],
        };

        // Generate knots based on the data distribution
        let x_min = self.x.iter().copied().fold(F::infinity(), F::min);
        let x_max = self.x.iter().copied().fold(F::neg_infinity(), F::max);
        let y_min = self.y.iter().copied().fold(F::infinity(), F::min);
        let y_max = self.y.iter().copied().fold(F::neg_infinity(), F::max);

        // Create a grid of interior knots
        let n_interior_x = 8; // Number of interior knots in x direction
        let n_interior_y = 8; // Number of interior knots in y direction

        // Generate knot sequences with appropriate multiplicities at endpoints
        let mut tx = Vec::with_capacity(n_interior_x + 2 * (self.kx + 1));
        let mut ty = Vec::with_capacity(n_interior_y + 2 * (self.ky + 1));

        // Add repeated knots at beginning (multiplicity k+1)
        for _ in 0..=self.kx {
            tx.push(x_min);
        }

        // Add interior knots
        if n_interior_x > 0 {
            let dx = (x_max - x_min) / F::from_usize(n_interior_x + 1).expect("Operation failed");
            for i in 1..=n_interior_x {
                tx.push(x_min + F::from_usize(i).expect("Operation failed") * dx);
            }
        }

        // Add repeated knots at end (multiplicity k+1)
        for _ in 0..=self.kx {
            tx.push(x_max);
        }

        // Similarly for y direction
        for _ in 0..=self.ky {
            ty.push(y_min);
        }

        if n_interior_y > 0 {
            let dy = (y_max - y_min) / F::from_usize(n_interior_y + 1).expect("Operation failed");
            for i in 1..=n_interior_y {
                ty.push(y_min + F::from_usize(i).expect("Operation failed") * dy);
            }
        }

        for _ in 0..=self.ky {
            ty.push(y_max);
        }

        // Convert to ndarray
        let tx = Array1::from(tx);
        let ty = Array1::from(ty);

        // In real implementation, we would use a full least-squares fitting algorithm here
        // For now, we'll create coefficients that at least approximate the data

        // Number of control points
        let n_x = tx.len() - self.kx - 1;
        let n_y = ty.len() - self.ky - 1;
        let num_coeffs = n_x * n_y;

        // Initialize coefficients
        let mut c = Array1::<F>::zeros(num_coeffs);

        // Simple approximation: for each data point, find the closest control point
        // and update its coefficient
        let mut weight_sum = Array1::<F>::zeros(num_coeffs);

        for i in 0..self.x.len() {
            // Find the knot span for this point
            let span_x = bspline_eval::find_span(self.x[i], &tx.view(), self.kx);
            let span_y = bspline_eval::find_span(self.y[i], &ty.view(), self.ky);

            // Compute basis functions
            let basis_x = bspline_eval::basis_funs(self.x[i], span_x, &tx.view(), self.kx);
            let basis_y = bspline_eval::basis_funs(self.y[i], span_y, &ty.view(), self.ky);

            // Apply smoothing by spreading the influence over multiple control points
            for iu in 0..=self.kx {
                let i_coef = span_x - self.kx + iu;
                for jv in 0..=self.ky {
                    let j_coef = span_y - self.ky + jv;
                    let idx = i_coef * n_y + j_coef;

                    if idx < num_coeffs {
                        let weight = basis_x[iu] * basis_y[jv];
                        let val = self.z[i];

                        if let Some(w) = self.w {
                            // Apply weights if provided
                            let adjusted_weight = weight * w[i];
                            c[idx] += adjusted_weight * val;
                            weight_sum[idx] += adjusted_weight;
                        } else {
                            c[idx] += weight * val;
                            weight_sum[idx] += weight;
                        }
                    }
                }
            }
        }

        // Normalize coefficients by weight sum
        for i in 0..num_coeffs {
            if weight_sum[i] > F::epsilon() {
                c[i] /= weight_sum[i];
            }
        }

        // Apply smoothing if needed
        if let Some(s) = self.s {
            if s > F::zero() {
                // Simple smoothing: average neighboring coefficients
                let mut c_smoothed = c.clone();

                for i in 1..n_x - 1 {
                    for j in 1..n_y - 1 {
                        let idx = i * n_y + j;
                        let smoothing_factor = s / (F::one() + s);

                        // Weighted average of neighboring coefficients
                        let neighbors_sum = c[idx - n_y] + c[idx + n_y] + c[idx - 1] + c[idx + 1];
                        let local_avg = neighbors_sum / F::from_f64(4.0).expect("Operation failed");

                        c_smoothed[idx] =
                            (F::one() - smoothing_factor) * c[idx] + smoothing_factor * local_avg;
                    }
                }

                c = c_smoothed;
            }
        }

        // Create the underlying spline with proper degree
        let spline = BivariateSpline::from_tck(tx, ty, c, self.kx, self.ky, self.s);

        Ok(SmoothBivariateSpline { spline })
    }
}

impl<F: Float + FromPrimitive + Debug + std::fmt::Display + crate::traits::InterpolationFloat>
    SmoothBivariateSpline<F>
{
    /// Create a new smooth bivariate spline with default parameters
    ///
    /// # Arguments
    ///
    /// * `x`, `y`, `z` - 1-D sequences of data points (order is not important)
    pub fn new<'a>(
        x: &'a ArrayView1<'a, F>,
        y: &'a ArrayView1<'a, F>,
        z: &'a ArrayView1<'a, F>,
    ) -> InterpolateResult<Self> {
        SmoothBivariateSplineBuilder::new(x, y, z).build()
    }

    /// Get the underlying bivariate spline
    pub fn get_spline(&self) -> &BivariateSpline<F> {
        &self.spline
    }
}

impl<F: Float + FromPrimitive + Debug + std::fmt::Display + crate::traits::InterpolationFloat>
    BivariateInterpolator<F> for SmoothBivariateSpline<F>
{
    fn evaluate(
        &self,
        x: &ArrayView1<F>,
        y: &ArrayView1<F>,
        grid: bool,
    ) -> InterpolateResult<Array2<F>> {
        self.spline.evaluate(x, y, grid)
    }

    fn evaluate_derivative(
        &self,
        x: &ArrayView1<F>,
        y: &ArrayView1<F>,
        dx: usize,
        dy: usize,
        grid: bool,
    ) -> InterpolateResult<Array2<F>> {
        self.spline.evaluate_derivative(x, y, dx, dy, grid)
    }

    fn integral(&self, xa: F, xb: F, ya: F, yb: F) -> InterpolateResult<F> {
        self.spline.integral(xa, xb, ya, yb)
    }
}

/// Bivariate spline approximation over a rectangular mesh
///
/// Can be used for both smoothing and interpolating data.
#[derive(Debug, Clone)]
pub struct RectBivariateSpline<
    F: Float + FromPrimitive + Debug + std::fmt::Display + crate::traits::InterpolationFloat,
> {
    /// The underlying bivariate spline
    spline: BivariateSpline<F>,
}

impl<
        F: Float
            + FromPrimitive
            + Debug
            + std::fmt::Display
            + std::ops::AddAssign
            + crate::traits::InterpolationFloat,
    > RectBivariateSpline<F>
{
    /// Create a new bivariate spline over a rectangular mesh
    ///
    /// # Arguments
    ///
    /// * `x`, `y` - 1-D arrays of coordinates in strictly ascending order
    /// * `z` - 2-D array of data with shape (x.len(), y.len())
    /// * `bbox` - Sequence of length 4 specifying the boundary of the rectangular
    ///   approximation domain. Default is [min(x), max(x), min(y), max(y)]
    /// * `kx`, `ky` - Degrees of the bivariate spline. Default is 3
    /// * `s` - Positive smoothing factor. Default is 0 (interpolation)
    pub fn new(
        x: &ArrayView1<F>,
        y: &ArrayView1<F>,
        z: &ArrayView2<F>,
        bbox: Option<[F; 4]>,
        kx: usize,
        ky: usize,
        s: Option<F>,
    ) -> InterpolateResult<Self> {
        // Check input
        if !x.iter().zip(x.iter().skip(1)).all(|(&a, &b)| b > a) {
            return Err(InterpolateError::invalid_input(
                "x must be strictly increasing".to_string(),
            ));
        }
        if !y.iter().zip(y.iter().skip(1)).all(|(&a, &b)| b > a) {
            return Err(InterpolateError::invalid_input(
                "y must be strictly increasing".to_string(),
            ));
        }

        if z.shape()[0] != x.len() {
            return Err(InterpolateError::invalid_input(
                "x dimension of z must have same number of elements as x".to_string(),
            ));
        }
        if z.shape()[1] != y.len() {
            return Err(InterpolateError::invalid_input(
                "y dimension of z must have same number of elements as y".to_string(),
            ));
        }

        // Check smoothing factor
        if let Some(s) = s {
            if s < F::zero() {
                return Err(InterpolateError::invalid_input(
                    "s should be s >= 0.0".to_string(),
                ));
            }
        }

        // Determine boundary
        let _bbox = match bbox {
            Some(bbox) => bbox,
            None => [x[0], x[x.len() - 1], y[0], y[y.len() - 1]],
        };

        // We need to create a knot sequence appropriate for B-splines
        // For a kth degree spline, we need k+1 repeated knots at the ends

        // Generate knot sequences with appropriate multiplicities at endpoints
        let mut tx_vec = Vec::with_capacity(x.len() + 2 * kx);
        let mut ty_vec = Vec::with_capacity(y.len() + 2 * ky);

        // Add repeated knots at beginning (multiplicity k+1)
        for _ in 0..=kx {
            tx_vec.push(x[0]);
        }

        // Add interior knots (using the grid points)
        for i in 1..x.len() - 1 {
            tx_vec.push(x[i]);
        }

        // Add repeated knots at end (multiplicity k+1)
        for _ in 0..=kx {
            tx_vec.push(x[x.len() - 1]);
        }

        // Similarly for y direction
        for _ in 0..=ky {
            ty_vec.push(y[0]);
        }

        for i in 1..y.len() - 1 {
            ty_vec.push(y[i]);
        }

        for _ in 0..=ky {
            ty_vec.push(y[y.len() - 1]);
        }

        // Convert to ndarray
        let tx = Array1::from(tx_vec);
        let ty = Array1::from(ty_vec);

        // Number of control points
        let n_x = tx.len() - kx - 1;
        let n_y = ty.len() - ky - 1;
        let num_coeffs = n_x * n_y;

        // Initialize coefficients
        let mut c = Array1::<F>::zeros(num_coeffs);

        // For a rectangular grid, we can use a more direct approach to determine coefficients
        // For now, we'll use a simple approach where each control point approximates a data point
        // In a full implementation, we'd set up and solve a linear system for interpolation or least squares

        // Create a mapping from data points to B-spline coefficients
        let mut weights = Array2::<F>::zeros((n_x, n_y));
        let mut values = Array2::<F>::zeros((n_x, n_y));

        // For each data point, find the corresponding knot span and add its contribution
        for i in 0..x.len() {
            let span_x = bspline_eval::find_span(x[i], &tx.view(), kx);
            let basis_x = bspline_eval::basis_funs(x[i], span_x, &tx.view(), kx);

            for j in 0..y.len() {
                let span_y = bspline_eval::find_span(y[j], &ty.view(), ky);
                let basis_y = bspline_eval::basis_funs(y[j], span_y, &ty.view(), ky);

                // The data value at (i,j)
                let z_val = z[[i, j]];

                // Distribute the value to affected control points
                for iu in 0..=kx {
                    let i_coef = span_x - kx + iu;
                    if i_coef >= n_x {
                        continue;
                    }

                    for jv in 0..=ky {
                        let j_coef = span_y - ky + jv;
                        if j_coef >= n_y {
                            continue;
                        }

                        let weight = basis_x[iu] * basis_y[jv];
                        weights[(i_coef, j_coef)] += weight;
                        values[(i_coef, j_coef)] += weight * z_val;
                    }
                }
            }
        }

        // Compute final coefficients by normalizing
        for i in 0..n_x {
            for j in 0..n_y {
                if weights[(i, j)] > F::epsilon() {
                    c[i * n_y + j] = values[(i, j)] / weights[(i, j)];
                }
            }
        }

        // Apply smoothing if requested
        if let Some(s) = s {
            if s > F::zero() {
                // Simple smoothing by averaging neighboring coefficients
                let mut c_smoothed = c.clone();

                for i in 1..n_x - 1 {
                    for j in 1..n_y - 1 {
                        let idx = i * n_y + j;
                        let smoothing_factor = s / (F::one() + s);

                        // Weighted average of neighboring coefficients
                        let neighbors_sum = c[idx - n_y] + c[idx + n_y] + c[idx - 1] + c[idx + 1];
                        let local_avg = neighbors_sum / F::from_f64(4.0).expect("Operation failed");

                        c_smoothed[idx] =
                            (F::one() - smoothing_factor) * c[idx] + smoothing_factor * local_avg;
                    }
                }

                c = c_smoothed;
            }
        }

        // Create the underlying spline with proper degree
        let spline = BivariateSpline::from_tck(tx, ty, c, kx, ky, s);

        Ok(Self { spline })
    }

    /// Get the underlying bivariate spline
    pub fn get_spline(&self) -> &BivariateSpline<F> {
        &self.spline
    }

    /// Evaluate the spline at specific points
    pub fn ev(
        &self,
        xi: &ArrayView1<F>,
        yi: &ArrayView1<F>,
        dx: usize,
        dy: usize,
    ) -> InterpolateResult<Array1<F>> {
        let result = self.spline.evaluate_derivative(xi, yi, dx, dy, false)?;
        // Convert from Array2 to Array1
        let mut result1d = Array1::zeros(xi.len());
        for i in 0..xi.len() {
            result1d[i] = result[[i, 0]];
        }
        Ok(result1d)
    }
}

impl<F: Float + FromPrimitive + Debug + std::fmt::Display + crate::traits::InterpolationFloat>
    BivariateInterpolator<F> for RectBivariateSpline<F>
{
    fn evaluate(
        &self,
        x: &ArrayView1<F>,
        y: &ArrayView1<F>,
        grid: bool,
    ) -> InterpolateResult<Array2<F>> {
        self.spline.evaluate(x, y, grid)
    }

    fn evaluate_derivative(
        &self,
        x: &ArrayView1<F>,
        y: &ArrayView1<F>,
        dx: usize,
        dy: usize,
        grid: bool,
    ) -> InterpolateResult<Array2<F>> {
        self.spline.evaluate_derivative(x, y, dx, dy, grid)
    }

    fn integral(&self, xa: F, xb: F, ya: F, yb: F) -> InterpolateResult<F> {
        self.spline.integral(xa, xb, ya, yb)
    }
}

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

    #[test]
    fn test_rect_bivariate_spline_constants() {
        // Create a 2D grid with constant values
        let x = array![1.0, 2.0, 3.0, 4.0, 5.0];
        let y = array![1.0, 2.0, 3.0, 4.0, 5.0];
        let z = Array2::ones((5, 5));

        let spline = RectBivariateSpline::new(&x.view(), &y.view(), &z.view(), None, 1, 1, None)
            .expect("Operation failed");

        // Test at a subset of grid points (2-4 range to avoid edge cases)
        let test_x = array![2.0, 3.0, 4.0];
        let test_y = array![2.0, 3.0, 4.0];
        let result = spline
            .evaluate(&test_x.view(), &test_y.view(), true)
            .expect("Operation failed");
        assert_eq!(result.shape(), &[3, 3]);

        // Values should be close to 1.0
        for i in 0..3 {
            for j in 0..3 {
                assert_abs_diff_eq!(result[[i, j]], 1.0, epsilon = 1e-10);
            }
        }

        // Test at a specific point within the interpolation range
        let xi = array![2.5];
        let yi = array![2.5];
        let result = spline
            .evaluate(&xi.view(), &yi.view(), false)
            .expect("Operation failed");
        assert_abs_diff_eq!(result[[0, 0]], 1.0, epsilon = 1e-10);
    }

    #[test]
    fn test_smooth_bivariate_spline() {
        // Create some test data with points distributed in the interior of the domain
        let x = array![1.5, 1.5, 1.5, 2.0, 2.0, 2.0, 2.5, 2.5, 2.5];
        let y = array![1.5, 2.0, 2.5, 1.5, 2.0, 2.5, 1.5, 2.0, 2.5];
        let z = array![3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0];

        let spline = SmoothBivariateSplineBuilder::new(&x.view(), &y.view(), &z.view())
            .with_degrees(1, 1)
            .build()
            .expect("Operation failed");

        // Test at interpolation points within the domain
        let test_x = array![1.8, 2.0, 2.2];
        let test_y = array![1.8, 2.0, 2.2];

        // Check that we can evaluate without errors at these points
        let result = spline
            .evaluate(&test_x.view(), &test_y.view(), true)
            .expect("Operation failed");

        // Current implementation doesn't actually fit the data, so we're not testing values yet
        assert_eq!(result.shape(), &[3, 3]);
    }
}
