#![deny(rustdoc::broken_intra_doc_links)]

use crate::{
    FpScalar, RealScalar,
    functions::FunctionErrors,
    kernels::RawComplexTrait,
    kernels::{RawRealTrait, RawScalarTrait},
    validation::{StrictFinitePolicy, capture_backtrace},
};
use num::{Complex, Zero};
use std::backtrace::Backtrace;
use thiserror::Error;
use try_create::{IntoInner, ValidationPolicy};

/// Trait for constructing complex scalar types from their raw components.
///
/// This trait provides methods for creating complex numbers from raw real and imaginary
/// parts, as well as specialized constructors for purely real or purely imaginary numbers.
///
/// # Examples
///
/// ```rust
/// use num_valid::ComplexNative64StrictFinite;
/// use num_valid::functions::ComplexScalarConstructors;
///
/// // Create a complex number from raw f64 values
/// let z = ComplexNative64StrictFinite::try_new_complex(3.0, 4.0).unwrap();
///
/// // Create a purely real number (imaginary part is zero)
/// let real_only = ComplexNative64StrictFinite::try_new_pure_real(5.0).unwrap();
///
/// // Create a purely imaginary number (real part is zero)
/// let imag_only = ComplexNative64StrictFinite::try_new_pure_imaginary(7.0).unwrap();
/// ```
pub trait ComplexScalarConstructors: FpScalar<InnerType = Self::RawComplex> {
    /// The raw underlying complex type (e.g., `num::Complex<f64>` or `rug::Complex`).
    type RawComplex: RawComplexTrait<RawReal = <Self::RealType as RealScalar>::RawReal>;

    /// Tries to create a new complex scalar from its raw real and imaginary parts.
    ///
    /// Returns an error if either component fails validation (e.g., NaN, Infinity).
    fn try_new_complex(
        real: <Self::RawComplex as RawComplexTrait>::RawReal,
        imag: <Self::RawComplex as RawComplexTrait>::RawReal,
    ) -> Result<Self, <Self::RawComplex as RawScalarTrait>::ValidationErrors>;

    /// Creates a new complex scalar from its (validated) real and imaginary parts.
    fn new_complex(real: Self::RealType, imag: Self::RealType) -> Self {
        Self::try_new_complex(real.into_inner(), imag.into_inner()).unwrap()
    }

    /// Tries to create a new complex scalar from a raw real part, with a zero imaginary part.
    fn try_new_pure_real(
        real_part: <Self::RawComplex as RawComplexTrait>::RawReal,
    ) -> Result<Self, <Self::RawComplex as RawScalarTrait>::ValidationErrors> {
        let zero = <Self::RawComplex as RawComplexTrait>::RawReal::raw_zero(real_part.precision());
        Self::try_new_complex(real_part, zero)
    }

    /// Tries to create a new complex scalar from a raw imaginary part, with a zero real part.
    fn try_new_pure_imaginary(
        imag_part: <Self::RawComplex as RawComplexTrait>::RawReal,
    ) -> Result<Self, <Self::RawComplex as RawScalarTrait>::ValidationErrors> {
        let zero = <Self::RawComplex as RawComplexTrait>::RawReal::raw_zero(imag_part.precision());
        Self::try_new_complex(zero, imag_part)
    }

    fn new_pure_real(real_part: Self::RealType) -> Self {
        Self::try_new_pure_real(real_part.into_inner()).unwrap()
    }

    fn new_pure_imaginary(imag_part: Self::RealType) -> Self {
        Self::try_new_pure_imaginary(imag_part.into_inner()).unwrap()
    }
}

/// Trait for accessing the real and imaginary components of a complex scalar.
///
/// This trait provides methods to extract and query the components of a complex number.
///
/// # Examples
///
/// ```rust
/// use num_valid::ComplexNative64StrictFinite;
/// use num_valid::functions::{ComplexScalarConstructors, ComplexScalarGetParts};
///
/// let z = ComplexNative64StrictFinite::try_new_complex(3.0, 4.0).unwrap();
///
/// // Access components
/// assert_eq!(*z.raw_real_part(), 3.0);
/// assert_eq!(*z.raw_imag_part(), 4.0);
///
/// // Check if purely real or imaginary
/// assert!(!z.is_pure_real());
/// assert!(!z.is_pure_imaginary());
///
/// let real_only = ComplexNative64StrictFinite::try_new_pure_real(5.0).unwrap();
/// assert!(real_only.is_pure_real());
/// ```
pub trait ComplexScalarGetParts: ComplexScalarConstructors {
    /// Get the real part of the complex number.
    fn real_part(&self) -> Self::RealType;

    /// Get the imaginary part of the complex number.
    fn imag_part(&self) -> Self::RealType;

    /// Returns a reference to the raw real part of the complex number.
    fn raw_real_part(&self) -> &<Self::RealType as RealScalar>::RawReal;

    /// Returns a reference to the raw imaginary part of the complex number.
    fn raw_imag_part(&self) -> &<Self::RealType as RealScalar>::RawReal;

    /// Returns `true` if the complex number is purely real (i.e., has no imaginary part).
    ///
    /// Note: the complex number zero is considered purely real.
    fn is_pure_real(&self) -> bool {
        self.raw_imag_part().is_zero()
    }

    /// Returns `true` if the complex number is purely imaginary (i.e., has zero real part and non-zero imaginary part).
    ///
    /// Note: the complex number zero is considered purely real.
    fn is_pure_imaginary(&self) -> bool {
        self.raw_real_part().is_zero() && !self.raw_imag_part().is_zero()
    }
}

/// Trait for setting the real and imaginary components of a complex scalar.
///
/// This trait provides methods to modify the components of a complex number,
/// either by mutation or by creating a new value with modified components.
///
/// # Examples
///
/// ```rust
/// use num_valid::{ComplexNative64StrictFinite, RealNative64StrictFinite};
/// use num_valid::functions::{ComplexScalarConstructors, ComplexScalarSetParts, ComplexScalarGetParts};
/// use try_create::TryNew;
///
/// let z = ComplexNative64StrictFinite::try_new_complex(3.0, 4.0).unwrap();
/// let new_real = RealNative64StrictFinite::try_new(10.0).unwrap();
///
/// // Create a new complex number with modified real part
/// let z_modified = z.with_real_part(new_real);
/// assert_eq!(*z_modified.raw_real_part(), 10.0);
/// assert_eq!(*z_modified.raw_imag_part(), 4.0);
/// ```
pub trait ComplexScalarSetParts: ComplexScalarGetParts {
    /// Set the real part of the complex number.
    fn set_real_part(&mut self, real_part: Self::RealType);

    /// Set the imaginary part of the complex number.
    fn set_imaginary_part(&mut self, imag_part: Self::RealType);

    /// Returns a new complex number with the real part set to the given value and the imaginary part from `self`.
    fn with_real_part(mut self, real_part: Self::RealType) -> Self {
        self.set_real_part(real_part);
        self
    }

    /// Returns a new complex number with the imaginary part set to the given value and the real part from `self`.
    fn with_imaginary_part(mut self, imag_part: Self::RealType) -> Self {
        self.set_imaginary_part(imag_part);
        self
    }
}

/// Provides methods for in-place mutation of the components of a complex scalar.
///
/// This trait offers efficient, in-place operations to modify the real and imaginary
/// parts of a complex number individually using a real scalar value. These methods
/// are particularly useful in numerical algorithms that require adjusting specific
/// components of a complex vector or matrix without allocating new complex numbers.
///
/// # Examples
///
/// ```
/// use num_valid::{
///     ComplexNative64StrictFinite, RealNative64StrictFinite,
///     functions::{ComplexScalarGetParts, ComplexScalarMutateParts, ComplexScalarConstructors},
/// };
/// use try_create::TryNew;
///
/// let mut c = ComplexNative64StrictFinite::try_new_complex(3.0, 5.0).unwrap();
/// let real_addend = RealNative64StrictFinite::try_new(2.0).unwrap();
/// let real_multiplier = RealNative64StrictFinite::try_new(10.0).unwrap();
///
/// // Add 2.0 to the real part
/// c.add_to_real_part(&real_addend);
/// assert_eq!(c.real_part(), 5.0);
///
/// // Multiply the imaginary part by 10.0
/// c.multiply_imaginary_part(&real_multiplier);
/// assert_eq!(c.imag_part(), 50.0);
///
/// assert_eq!(c, ComplexNative64StrictFinite::try_new_complex(5.0, 50.0).unwrap());
/// ```
pub trait ComplexScalarMutateParts: ComplexScalarSetParts {
    /// Add the value of the the real coefficient `c` to real part of `self`.
    fn add_to_real_part(&mut self, c: &Self::RealType);

    /// Add the value of the the real coefficient `c` to imaginary part of `self`.
    fn add_to_imaginary_part(&mut self, c: &Self::RealType);

    /// Multiply the value of the real part by the real coefficient `c`.
    fn multiply_real_part(&mut self, c: &Self::RealType);

    /// Multiply the value of the imaginary part by the real coefficient `c`.
    fn multiply_imaginary_part(&mut self, c: &Self::RealType);
}

//--------------------------------------------------------------------------------------------------
/// Trait for computing the complex conjugate of a number.
///
/// The complex conjugate of a complex number is obtained by changing the sign of its imaginary part.
///
/// # Example
///
/// ```rust
/// use num_valid::functions::Conjugate;
/// use num::Complex;
/// use try_create::TryNew;
///
/// // Example with Complex<f64>
/// let z = Complex::new(1.0, -2.0);
/// let z_conjugate = z.conjugate();
/// println!("Conjugate: {}", z_conjugate); // Output: Conjugate: 1+2i
/// assert_eq!(z_conjugate, Complex::new(1.0, 2.0));
///
/// // Example with ComplexRugStrictFinite<53> (when the `rug` feature is enabled)
/// #[cfg(feature = "rug")]
/// {
///     use rug::Float;
///     use num_valid::ComplexRugStrictFinite;
///
///     const PRECISION: u32 = 53;
///
///     let z = ComplexRugStrictFinite::<PRECISION>::try_new(
///         rug::Complex::with_val(PRECISION,
///             (Float::with_val(PRECISION, 1.0),Float::with_val(PRECISION, -2.0)),
///         )).unwrap();
///     let z_conjugate = z.conjugate();
///     println!("Conjugate: {}", z_conjugate);
///     assert_eq!(
///         z_conjugate,
///         ComplexRugStrictFinite::<PRECISION>::try_new(
///         rug::Complex::with_val(PRECISION,
///             (Float::with_val(PRECISION, 1.0),Float::with_val(PRECISION, 2.0)),
///         )).unwrap()
///     );
/// }
/// ```
pub trait Conjugate: Sized {
    /// Returns the *complex conjugate* of `self`.
    fn conjugate(self) -> Self;
}

impl Conjugate for Complex<f64> {
    #[inline(always)]
    fn conjugate(self) -> Self {
        self.conj()
    }
}

//--------------------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------------------
/// Errors that can occur specifically during the input validation phase or due to
/// special input values when attempting to compute the argument (principal value)
/// of a complex number.
///
/// This enum is used as a source for the [`ArgErrors::Input`] variant and helps categorize
/// why an input was deemed unsuitable *before* or *during* the core argument calculation.
///
/// # Generic Parameters
///
/// - `RawComplex`: A type that implements [`RawComplexTrait`].
///   This defines the raw error type for the input complex number via `<RawComplex as RawScalarTrait>::ValidationErrors`.
#[derive(Debug, Error)]
pub enum ArgInputErrors<RawComplex: RawComplexTrait> {
    /// The input complex number failed basic validation checks.
    ///
    /// This error occurs if the input complex number itself is considered invalid
    /// according to the validation policy (e.g., [`StrictFinitePolicy`]),
    /// such as containing NaN or Infinity components, before the argument calculation
    /// is attempted.
    #[error("the input complex number is invalid according to validation policy")]
    ValidationError {
        /// The underlying validation error from the complex number type.
        ///
        /// This provides more specific details about why the complex number's
        /// components (real or imaginary parts) were considered invalid.
        #[source]
        #[backtrace]
        source: <RawComplex as RawScalarTrait>::ValidationErrors,
    },

    /// The input complex number is zero.
    ///
    /// The argument of zero is undefined. This error indicates that the input
    /// value was `0 + 0i`.
    #[error("the input value is zero!")]
    Zero {
        /// A captured backtrace for debugging purposes.
        backtrace: Backtrace,
    },
}

/// A type alias for [`FunctionErrors`], specialized for errors that can occur during
/// the computation of the argument (principal value) of a complex number.
///
/// This type represents the possible failures when calling [`Arg::try_arg()`].
///
/// # Generic Parameters
///
/// - `RawComplex`: A type that implements [`RawComplexTrait`]. This defines:
///   - The raw error type for the input complex number via `<RawComplex as RawScalarTrait>::ValidationErrors`.
///   - The raw error type for the output real number (the argument) via
///     `<<RawComplex as RawComplexTrait>::RawReal as RawScalarTrait>::ValidationErrors`.
///
/// # Variants
///
/// This type alias wraps [`FunctionErrors`], which has the following variants in this context:
///
/// - `Input { source: ArgInputErrors<RawComplex> }`:
///   Indicates that the input complex number was invalid for argument computation.
///   This could be due to failing initial validation (e.g., containing NaN or Infinity)
///   or because the input was zero (for which the argument is undefined).
///   The `source` field provides more specific details via [`ArgInputErrors`].
///
/// - `Output { source: <<RawComplex as RawComplexTrait>::RawReal as RawScalarTrait>::ValidationErrors }`:
///   Indicates that the computed argument (a real number) failed validation.
///   This typically means the result of the `atan2` or equivalent operation yielded
///   a non-finite value (NaN or Infinity), which is unexpected if the input was valid
///   and non-zero. The `source` field provides the raw validation error for the output real number.
pub type ArgErrors<RawComplex> = FunctionErrors<
    ArgInputErrors<RawComplex>,
    <<RawComplex as RawComplexTrait>::RawReal as RawScalarTrait>::ValidationErrors,
>;

/// Trait for computing the argument (principal value) of a complex number.
///
/// The argument of a complex number `z = x + iy` is the angle `φ` (phi)
/// in polar coordinates `z = r(cos φ + i sin φ)`. It is typically computed
/// using `atan2(y, x)` and lies in the interval `(-π, π]`.
///
/// This trait provides both a fallible version (`try_arg`) that performs validation
/// and an infallible version (`arg`) that may panic in debug builds if validation fails.
pub trait Arg: Sized {
    /// The return type of the *principal value* (or *argument*) function. It is always a real number.
    // TODO: Consider using the trait bound `Output: RealScalar` instead of `Output: Sized`.
    type Output: Sized;

    /// The error type that can be returned by the [`Arg::try_arg()`] method.
    ///
    /// This is typically an instantiation of [`ArgErrors`].
    type Error: std::error::Error;

    /// Attempts to compute the argument of `self`, returning a `Result`.
    fn try_arg(self) -> Result<Self::Output, Self::Error>;

    /// Returns the argument of `self`.
    fn arg(self) -> Self::Output;
}

impl Arg for Complex<f64> {
    type Output = f64;

    type Error = ArgErrors<Complex<f64>>;

    /// Attempts to compute the argument of `self`, returning a `Result`.
    ///
    /// This method first validates the input `self` using [`StrictFinitePolicy`] and
    /// also checks if it is zero. If the input is valid (finite components, non-zero),
    /// it computes the argument and then validates the resulting real number
    /// using the same policy.
    ///
    /// # Returns
    ///
    /// - `Ok(Self::Output)`: If the input complex number is valid (finite, non-zero)
    ///   and the computed argument is a valid (finite) real number.
    /// - `Err(Self::Error)`: If the input is invalid (e.g., NaN or Infinity components, zero)
    ///   or if the computed argument is invalid (e.g., NaN, Infinity).
    ///
    /// # Examples
    ///
    /// ```rust
    /// use num_valid::functions::Arg;
    /// use num::Complex;
    /// use std::f64::consts::PI;
    ///
    /// let z = Complex::new(1.0, 1.0); // Represents 1 + i
    /// match z.try_arg() {
    ///     Ok(angle) => println!("Arg(1+i) = {}", angle), // Arg(1+i) = 0.785... (π/4)
    ///     Err(e) => println!("Error: {:?}", e),
    /// }
    ///
    /// let zero = Complex::new(0.0, 0.0);
    /// assert!(zero.try_arg().is_err());
    ///
    /// let nan_val = Complex::new(f64::NAN, 1.0);
    /// assert!(nan_val.try_arg().is_err());
    /// ```
    #[inline(always)]
    fn try_arg(self) -> Result<Self::Output, Self::Error> {
        StrictFinitePolicy::<Complex<f64>, 53>::validate(self)
            .map_err(|e| Self::Error::Input {
                source: ArgInputErrors::ValidationError { source: e },
            })
            .and_then(|v: Complex<f64>| {
                if <Complex<f64> as Zero>::is_zero(&v) {
                    Err(Self::Error::Input {
                        source: ArgInputErrors::Zero {
                            backtrace: capture_backtrace(),
                        },
                    })
                } else {
                    StrictFinitePolicy::<f64, 53>::validate(Complex::<f64>::arg(v))
                        .map_err(|e| Self::Error::Output { source: e })
                }
            })
    }

    /// Returns the argument of `self`.
    ///
    /// # Behavior
    ///
    /// - **Debug Builds (`#[cfg(debug_assertions)]`)**: This method internally calls `try_arg().unwrap()`.
    ///   It will panic if the input `self` is invalid (e.g., NaN/Infinity components, zero)
    ///   or if the computed argument is invalid.
    /// - **Release Builds (`#[cfg(not(debug_assertions))]`)**: This method calls the underlying
    ///   argument function directly (e.g., `num::Complex::arg`).
    ///   The behavior for non-finite inputs or zero (like NaN propagation or specific
    ///   return values like `0.0` for `arg(0)`) depends on the underlying implementation.
    ///
    /// # Panics
    ///
    /// In debug builds, this method will panic if `try_arg()` would return an `Err`.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use num_valid::functions::Arg;
    /// use num::Complex;
    /// use std::f64::consts::PI;
    ///
    /// let z = Complex::new(-1.0, 0.0); // Represents -1
    /// println!("Arg(-1) = {}", z.arg()); // Arg(-1) = 3.141... (π)
    ///
    /// let z_imag = Complex::new(0.0, 1.0); // Represents i
    /// println!("Arg(i) = {}", z_imag.arg()); // Arg(i) = 1.570... (π/2)
    /// ```
    #[inline(always)]
    fn arg(self) -> f64 {
        #[cfg(debug_assertions)]
        {
            self.try_arg()
                .expect("Error calling Arg::try_arg() inside Arg::arg() debug mode.")
        }
        #[cfg(not(debug_assertions))]
        {
            Complex::<f64>::arg(self)
        }
    }
}

//--------------------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------------------
#[cfg(test)]
mod tests {
    use super::*;
    use crate::validation::ErrorsValidationRawComplex;
    use num::Complex;

    mod native64 {
        use super::*;
        use std::f64::consts::*;

        #[test]
        fn conjugate() {
            let z = Complex::new(1.0, -2.0);
            let expected_conjugate = Complex::new(1.0, 2.0);
            assert_eq!(z.conjugate(), expected_conjugate);

            let z = Complex::new(-3.0, 4.0);
            let expected_conjugate = Complex::new(-3.0, -4.0);
            assert_eq!(z.conjugate(), expected_conjugate);

            // Special cases
            // Zero
            let z_zero = Complex::new(0.0, 0.0);
            assert_eq!(z_zero.conjugate(), Complex::new(0.0, 0.0));
            // Purely real
            let z_real = Complex::new(5.0, 0.0);
            assert_eq!(z_real.conjugate(), Complex::new(5.0, 0.0));
            // Purely imaginary
            let z_imag = Complex::new(0.0, 5.0);
            assert_eq!(z_imag.conjugate(), Complex::new(0.0, -5.0));
            let z_neg_imag = Complex::new(0.0, -5.0);
            assert_eq!(z_neg_imag.conjugate(), Complex::new(0.0, 5.0));
        }

        #[test]
        fn arg() {
            let z = Complex::new(1.0, 1.0);
            let expected_arg = std::f64::consts::FRAC_PI_4; //0.7853981633974483; // pi/4
            assert_eq!(z.arg(), expected_arg);

            // Axes
            let z_pos_real = Complex::new(1.0, 0.0);
            assert_eq!(z_pos_real.arg(), 0.0);
            let z_neg_real = Complex::new(-1.0, 0.0);
            assert_eq!(z_neg_real.arg(), PI);
            let z_pos_imag = Complex::new(0.0, 1.0);
            assert_eq!(z_pos_imag.arg(), FRAC_PI_2);
            let z_neg_imag = Complex::new(0.0, -1.0);
            assert_eq!(z_neg_imag.arg(), -FRAC_PI_2);

            // Quadrants
            let z2 = Complex::new(-1.0, 1.0); // Q2
            assert_eq!(z2.arg(), 3.0 * FRAC_PI_4);
            let z3 = Complex::new(-1.0, -1.0); // Q3
            assert_eq!(z3.arg(), -3.0 * FRAC_PI_4);
            let z4 = Complex::new(1.0, -1.0); // Q4
            assert_eq!(z4.arg(), -FRAC_PI_4);

            let zero = Complex::new(0.0, 0.0);
            assert!(matches!(
                zero.try_arg(),
                Err(ArgErrors::<Complex<f64>>::Input {
                    source: ArgInputErrors::Zero { .. }
                })
            ));
        }

        #[test]
        fn try_arg_invalid() {
            // NaN cases
            let z_nan_re = Complex::new(f64::NAN, 1.0);
            assert!(matches!(
                z_nan_re.try_arg(),
                Err(ArgErrors::<Complex<f64>>::Input {
                    source: ArgInputErrors::ValidationError {
                        source: ErrorsValidationRawComplex::InvalidRealPart { .. }
                    }
                })
            ));
            let z_nan_im = Complex::new(1.0, f64::NAN);
            assert!(matches!(
                z_nan_im.try_arg(),
                Err(ArgErrors::<Complex<f64>>::Input {
                    source: ArgInputErrors::ValidationError {
                        source: ErrorsValidationRawComplex::InvalidImaginaryPart { .. }
                    }
                })
            ));

            // Infinity cases
            let z_inf_re = Complex::new(f64::INFINITY, 1.0);
            assert!(matches!(
                z_inf_re.try_arg(),
                Err(ArgErrors::<Complex<f64>>::Input {
                    source: ArgInputErrors::ValidationError {
                        source: ErrorsValidationRawComplex::InvalidRealPart { .. }
                    }
                })
            ));
            let z_inf_im = Complex::new(1.0, f64::INFINITY);
            assert!(matches!(
                z_inf_im.try_arg(),
                Err(ArgErrors::<Complex<f64>>::Input {
                    source: ArgInputErrors::ValidationError {
                        source: ErrorsValidationRawComplex::InvalidImaginaryPart { .. }
                    }
                })
            ));

            // Case where num::Complex::arg might produce NaN, caught by output validation
            // e.g. if inputs were valid but atan2 produced NaN (though hard with f64 if inputs are finite)
            // This test assumes that if Complex::arg itself returns NaN, f64::try_new catches it.
            // Example: Complex::new(f64::NEG_INFINITY, f64::NEG_INFINITY).arg() is -3*PI/4
            // Example: Complex::new(f64::NAN, f64::NEG_INFINITY).arg() is NaN
            // The validate() step should catch component-wise NaN/Inf first.
            // If validate() passed but Complex::arg() produced NaN, it would be an Output error.
            // For instance, if we had a type where components are valid but arg calculation leads to NaN.
            // For Complex<f64>, validate() is quite comprehensive.
            // Let's consider a case that passes validate() but whose arg() is NaN (hypothetical for f64, more for other types)
            // For f64, if re or im is NaN, validate() fails. If both are Inf, validate() fails.
            // If one is Inf and other is finite, arg() is well-defined.
            // So, for Complex<f64>, the Output error due to NaN is less likely if validate() passes.
        }

        #[test]
        #[cfg(debug_assertions)]
        #[should_panic]
        fn arg_panics_on_zero_debug() {
            let zero = Complex::new(0.0, 0.0);
            let _ = Arg::arg(zero);
        }

        #[test]
        #[cfg(debug_assertions)]
        #[should_panic]
        fn arg_panics_on_nan_debug() {
            let nan = Complex::new(f64::NAN, 1.0);
            let _ = Arg::arg(nan);
        }

        #[test]
        #[cfg(debug_assertions)]
        #[should_panic]
        fn arg_panics_on_inf_debug() {
            let inf = Complex::new(f64::INFINITY, 1.0);
            let _ = Arg::arg(inf);
        }

        #[test]
        #[cfg(not(debug_assertions))]
        fn arg_behavior_release() {
            // Zero input in release returns 0.0 from num::Complex::arg
            let zero = Complex::new(0.0, 0.0);
            assert_eq!(Arg::arg(zero), 0.0);

            // NaN/Inf inputs in release mode will lead to NaN from num::Complex::arg
            let nan_re = Complex::new(f64::NAN, 1.0);
            assert!(Arg::arg(nan_re).is_nan());
            let inf_re = Complex::new(f64::INFINITY, 0.0); // arg is 0
            assert_eq!(Arg::arg(inf_re), 0.0);
            let inf_im = Complex::new(0.0, f64::INFINITY); // arg is PI/2
            assert_eq!(Arg::arg(inf_im), FRAC_PI_2);
            let inf_both = Complex::new(f64::INFINITY, f64::INFINITY); // arg is PI/4
            assert_eq!(Arg::arg(inf_both), FRAC_PI_4);
        }

        #[test]
        fn test_is_pure_real() {
            use crate::ComplexNative64StrictFinite;

            // Purely real number
            let z_real = ComplexNative64StrictFinite::try_new_complex(5.0, 0.0).unwrap();
            assert!(z_real.is_pure_real());
            assert!(!z_real.is_pure_imaginary());

            // Zero is considered purely real
            let z_zero = ComplexNative64StrictFinite::try_new_complex(0.0, 0.0).unwrap();
            assert!(z_zero.is_pure_real());
            assert!(!z_zero.is_pure_imaginary());

            // Complex with both parts non-zero
            let z_complex = ComplexNative64StrictFinite::try_new_complex(3.0, 4.0).unwrap();
            assert!(!z_complex.is_pure_real());
            assert!(!z_complex.is_pure_imaginary());

            // Purely imaginary
            let z_imag = ComplexNative64StrictFinite::try_new_complex(0.0, 5.0).unwrap();
            assert!(!z_imag.is_pure_real());
            assert!(z_imag.is_pure_imaginary());
        }

        #[test]
        fn test_is_pure_imaginary() {
            use crate::ComplexNative64StrictFinite;

            // Purely imaginary number
            let z_imag = ComplexNative64StrictFinite::try_new_complex(0.0, 7.0).unwrap();
            assert!(z_imag.is_pure_imaginary());
            assert!(!z_imag.is_pure_real());

            // Negative imaginary
            let z_neg_imag = ComplexNative64StrictFinite::try_new_complex(0.0, -3.0).unwrap();
            assert!(z_neg_imag.is_pure_imaginary());

            // Zero is NOT considered purely imaginary
            let z_zero = ComplexNative64StrictFinite::try_new_complex(0.0, 0.0).unwrap();
            assert!(!z_zero.is_pure_imaginary());
            assert!(z_zero.is_pure_real());
        }

        #[test]
        fn test_with_real_part() {
            use crate::{ComplexNative64StrictFinite, RealNative64StrictFinite};
            use try_create::TryNew;

            let z = ComplexNative64StrictFinite::try_new_complex(3.0, 4.0).unwrap();
            let new_real = RealNative64StrictFinite::try_new(10.0).unwrap();

            let z_modified = z.with_real_part(new_real);
            assert_eq!(*z_modified.raw_real_part(), 10.0);
            assert_eq!(*z_modified.raw_imag_part(), 4.0);
        }

        #[test]
        fn test_with_imaginary_part() {
            use crate::{ComplexNative64StrictFinite, RealNative64StrictFinite};
            use try_create::TryNew;

            let z = ComplexNative64StrictFinite::try_new_complex(3.0, 4.0).unwrap();
            let new_imag = RealNative64StrictFinite::try_new(20.0).unwrap();

            let z_modified = z.with_imaginary_part(new_imag);
            assert_eq!(*z_modified.raw_real_part(), 3.0);
            assert_eq!(*z_modified.raw_imag_part(), 20.0);
        }

        #[test]
        fn test_new_pure_real() {
            use crate::{ComplexNative64StrictFinite, RealNative64StrictFinite};
            use try_create::TryNew;

            let real_part = RealNative64StrictFinite::try_new(7.0).unwrap();
            let z = ComplexNative64StrictFinite::new_pure_real(real_part);

            assert_eq!(*z.raw_real_part(), 7.0);
            assert_eq!(*z.raw_imag_part(), 0.0);
            assert!(z.is_pure_real());
        }

        #[test]
        fn test_new_pure_imaginary() {
            use crate::{ComplexNative64StrictFinite, RealNative64StrictFinite};
            use try_create::TryNew;

            let imag_part = RealNative64StrictFinite::try_new(9.0).unwrap();
            let z = ComplexNative64StrictFinite::new_pure_imaginary(imag_part);

            assert_eq!(*z.raw_real_part(), 0.0);
            assert_eq!(*z.raw_imag_part(), 9.0);
            assert!(z.is_pure_imaginary());
        }

        #[test]
        fn test_try_new_pure_real_valid() {
            use crate::ComplexNative64StrictFinite;

            let z = ComplexNative64StrictFinite::try_new_pure_real(5.0).unwrap();
            assert_eq!(*z.raw_real_part(), 5.0);
            assert_eq!(*z.raw_imag_part(), 0.0);
        }

        #[test]
        fn test_try_new_pure_real_invalid() {
            use crate::ComplexNative64StrictFinite;

            // NaN should fail
            let result = ComplexNative64StrictFinite::try_new_pure_real(f64::NAN);
            assert!(result.is_err());

            // Infinity should fail
            let result = ComplexNative64StrictFinite::try_new_pure_real(f64::INFINITY);
            assert!(result.is_err());
        }

        #[test]
        fn test_try_new_pure_imaginary_valid() {
            use crate::ComplexNative64StrictFinite;

            let z = ComplexNative64StrictFinite::try_new_pure_imaginary(8.0).unwrap();
            assert_eq!(*z.raw_real_part(), 0.0);
            assert_eq!(*z.raw_imag_part(), 8.0);
        }

        #[test]
        fn test_try_new_pure_imaginary_invalid() {
            use crate::ComplexNative64StrictFinite;

            // NaN should fail
            let result = ComplexNative64StrictFinite::try_new_pure_imaginary(f64::NAN);
            assert!(result.is_err());

            // Infinity should fail
            let result = ComplexNative64StrictFinite::try_new_pure_imaginary(f64::NEG_INFINITY);
            assert!(result.is_err());
        }

        #[test]
        fn test_into_parts_basic() {
            use crate::{ComplexNative64StrictFinite, ComplexScalar};

            let z = ComplexNative64StrictFinite::try_new_complex(3.0, 4.0).unwrap();

            // Consume z and extract its parts
            let (real, imag) = z.into_parts();

            assert_eq!(*real.as_ref(), 3.0);
            assert_eq!(*imag.as_ref(), 4.0);
        }

        #[test]
        fn test_into_parts_zero() {
            use crate::{ComplexNative64StrictFinite, ComplexScalar};

            let z = ComplexNative64StrictFinite::try_new_complex(0.0, 0.0).unwrap();
            let (real, imag) = z.into_parts();

            assert_eq!(*real.as_ref(), 0.0);
            assert_eq!(*imag.as_ref(), 0.0);
        }

        #[test]
        fn test_into_parts_pure_real() {
            use crate::{ComplexNative64StrictFinite, ComplexScalar};

            let z = ComplexNative64StrictFinite::try_new_complex(7.5, 0.0).unwrap();
            let (real, imag) = z.into_parts();

            assert_eq!(*real.as_ref(), 7.5);
            assert_eq!(*imag.as_ref(), 0.0);
        }

        #[test]
        fn test_into_parts_pure_imaginary() {
            use crate::{ComplexNative64StrictFinite, ComplexScalar};

            let z = ComplexNative64StrictFinite::try_new_complex(0.0, -9.2).unwrap();
            let (real, imag) = z.into_parts();

            assert_eq!(*real.as_ref(), 0.0);
            assert_eq!(*imag.as_ref(), -9.2);
        }

        #[test]
        fn test_into_parts_negative() {
            use crate::{ComplexNative64StrictFinite, ComplexScalar};

            let z = ComplexNative64StrictFinite::try_new_complex(-5.5, -3.3).unwrap();
            let (real, imag) = z.into_parts();

            assert_eq!(*real.as_ref(), -5.5);
            assert_eq!(*imag.as_ref(), -3.3);
        }

        #[test]
        fn test_into_parts_parts_are_independent() {
            use crate::{ComplexNative64StrictFinite, ComplexScalar, RealNative64StrictFinite};
            use try_create::TryNew;

            let z = ComplexNative64StrictFinite::try_new_complex(10.0, 20.0).unwrap();
            let (real, imag) = z.into_parts();

            // Parts should be usable independently
            let two = RealNative64StrictFinite::try_new(2.0).unwrap();
            let real_doubled = real * two;
            let imag_halved = imag / two;

            assert_eq!(*real_doubled.as_ref(), 20.0);
            assert_eq!(*imag_halved.as_ref(), 10.0);
        }
    }

    #[cfg(feature = "rug")]
    mod rug53 {
        use super::*;
        use crate::kernels::rug::{ComplexRugStrictFinite, RealRugStrictFinite};
        use rug::Float;
        use std::f64::consts::*;
        use try_create::{New, TryNew};

        const PRECISION: u32 = 53;

        fn rug_complex(re: f64, im: f64) -> rug::Complex {
            rug::Complex::with_val(
                PRECISION,
                (
                    Float::with_val(PRECISION, re),
                    Float::with_val(PRECISION, im),
                ),
            )
        }

        fn new_complex_rug(re: f64, im: f64) -> ComplexRugStrictFinite<PRECISION> {
            ComplexRugStrictFinite::<PRECISION>::new(rug_complex(re, im))
        }

        #[allow(clippy::result_large_err)]
        fn try_new_complex_rug(
            re: f64,
            im: f64,
        ) -> Result<
            ComplexRugStrictFinite<PRECISION>,
            <rug::Complex as RawScalarTrait>::ValidationErrors,
        > {
            ComplexRugStrictFinite::<PRECISION>::try_new(rug_complex(re, im))
        }

        fn real_rug(val: f64) -> RealRugStrictFinite<PRECISION> {
            RealRugStrictFinite::<PRECISION>::new(Float::with_val(PRECISION, val))
        }

        #[test]
        fn conjugate() {
            let z = new_complex_rug(1.0, -2.0);
            let expected_conjugate = new_complex_rug(1.0, 2.0);
            assert_eq!(z.conjugate(), expected_conjugate);

            let z = new_complex_rug(-3.0, 4.0);
            let expected_conjugate = new_complex_rug(-3.0, -4.0);
            assert_eq!(z.conjugate(), expected_conjugate);

            // Special cases
            // Zero
            let z_zero = new_complex_rug(0.0, 0.0);
            assert_eq!(z_zero.conjugate(), new_complex_rug(0.0, 0.0));
            // Purely real
            let z_real = new_complex_rug(5.0, 0.0);
            assert_eq!(z_real.conjugate(), new_complex_rug(5.0, 0.0));
            // Purely imaginary
            let z_imag = new_complex_rug(0.0, 5.0);
            assert_eq!(z_imag.conjugate(), new_complex_rug(0.0, -5.0));
            let z_neg_imag = new_complex_rug(0.0, -5.0);
            assert_eq!(z_neg_imag.conjugate(), new_complex_rug(0.0, 5.0));
        }

        #[test]
        fn arg() {
            // Existing test (Q1)
            let z1 = new_complex_rug(1.0, 1.0);
            assert_eq!(z1.arg(), real_rug(FRAC_PI_4));

            // Axes
            let z_pos_real = new_complex_rug(1.0, 0.0);
            assert_eq!(z_pos_real.arg(), real_rug(0.0));
            let z_neg_real = new_complex_rug(-1.0, 0.0);
            assert_eq!(z_neg_real.arg(), real_rug(PI));
            let z_pos_imag = new_complex_rug(0.0, 1.0);
            assert_eq!(z_pos_imag.arg(), real_rug(FRAC_PI_2));
            let z_neg_imag = new_complex_rug(0.0, -1.0);
            assert_eq!(z_neg_imag.arg(), real_rug(-FRAC_PI_2));

            // Quadrants
            let z2 = new_complex_rug(-1.0, 1.0); // Q2
            assert_eq!(z2.arg(), real_rug(3.0 * FRAC_PI_4));
            let z3 = new_complex_rug(-1.0, -1.0); // Q3
            assert_eq!(z3.arg(), real_rug(-3.0 * FRAC_PI_4));
            let z4 = new_complex_rug(1.0, -1.0); // Q4
            assert_eq!(z4.arg(), real_rug(-FRAC_PI_4));

            // Test try_arg for zero (already in existing tests)
            let zero = new_complex_rug(0.0, 0.0);
            assert!(matches!(
                zero.try_arg(),
                Err(ArgErrors::Input {
                    source: ArgInputErrors::Zero { .. }
                })
            ));
        }

        #[test]
        #[cfg(not(debug_assertions))]
        fn try_arg_zero_invalid() {
            // zero
            let z_zero = new_complex_rug(0.0, 0.0);
            assert!(matches!(
                z_zero.try_arg(),
                Err(ArgErrors::Input {
                    source: ArgInputErrors::Zero { .. }
                })
            ));
        }

        #[test]
        #[should_panic(
            expected = "Error calling ComplexValidated::try_arg() inside ComplexValidated::arg()"
        )]
        fn arg_panics_on_zero() {
            let zero = new_complex_rug(0.0, 0.0);
            let _ = zero.arg();
        }

        #[test]
        fn err_on_try_new_real_part_infinity_debug() {
            let err = try_new_complex_rug(f64::INFINITY, 1.0);
            assert!(matches!(
                err,
                Err(ErrorsValidationRawComplex::InvalidRealPart { .. })
            ));
        }

        #[test]
        fn err_on_try_new_real_part_nan_debug() {
            let err = try_new_complex_rug(f64::NAN, 1.0);
            assert!(matches!(
                err,
                Err(ErrorsValidationRawComplex::InvalidRealPart { .. })
            ));
        }

        #[test]
        fn err_on_try_new_imaginary_part_infinity_debug() {
            let err = try_new_complex_rug(1.0, f64::INFINITY);
            assert!(matches!(
                err,
                Err(ErrorsValidationRawComplex::InvalidImaginaryPart { .. })
            ));
        }

        #[test]
        fn err_on_try_new_imaginary_part_nan_debug() {
            let err = try_new_complex_rug(1.0, f64::NAN);
            assert!(matches!(
                err,
                Err(ErrorsValidationRawComplex::InvalidImaginaryPart { .. })
            ));
        }

        #[test]
        #[cfg(debug_assertions)]
        #[should_panic(expected = "Error calling try_new_validated() inside new() in debug mode")]
        fn panics_on_new_real_part_nan_debug() {
            let _nan = new_complex_rug(f64::NAN, 1.0);
        }

        #[test]
        #[cfg(debug_assertions)]
        #[should_panic(expected = "Error calling try_new_validated() inside new() in debug mode")]
        fn panics_on_new_real_part_infinity_debug() {
            let _inf = new_complex_rug(f64::INFINITY, 1.0);
        }

        #[test]
        #[cfg(debug_assertions)]
        #[should_panic(expected = "Error calling try_new_validated() inside new() in debug mode")]
        fn panics_on_new_imaginary_part_nan_debug() {
            let _nan = new_complex_rug(1.0, f64::NAN);
        }

        #[test]
        #[cfg(debug_assertions)]
        #[should_panic(expected = "Error calling try_new_validated() inside new() in debug mode")]
        fn panics_on_new_imaginary_part_infinity_debug() {
            let _inf = new_complex_rug(1.0, f64::INFINITY);
        }

        #[test]
        #[cfg(not(debug_assertions))]
        fn arg_behavior_release() {
            // NaN/Inf inputs for rug
            // rug::Float::NAN.arg() behavior might differ or how it's handled by rug::Complex::arg
            // rug::Float from f64::NAN is NaN. rug::Complex with NaN component.
            // rug::Complex::arg() on NaN components: real(NaN).arg() is NaN.
            //let nan_re = new_complex_rug(f64::NAN, 1.0);
            //assert!(nan_re.arg().is_nan()); // RealRugStrictFinite::is_nan()

            // rug::Float from f64::INFINITY is Inf.
            let inf_re = new_complex_rug(f64::INFINITY, 0.0); // arg is 0
            assert_eq!(inf_re.arg(), real_rug(0.0));
            let inf_im = new_complex_rug(0.0, f64::INFINITY); // arg is PI/2
            assert_eq!(inf_im.arg(), real_rug(FRAC_PI_2));
            let inf_both = new_complex_rug(f64::INFINITY, f64::INFINITY); // arg is PI/4
            assert_eq!(inf_both.arg(), real_rug(FRAC_PI_4));
        }

        #[test]
        fn test_into_parts_basic() {
            use crate::ComplexScalar;

            let z = new_complex_rug(3.0, 4.0);
            let (real, imag) = z.into_parts();

            assert_eq!(*real.as_ref(), 3.0);
            assert_eq!(*imag.as_ref(), 4.0);
        }

        #[test]
        fn test_into_parts_zero() {
            use crate::ComplexScalar;

            let z = new_complex_rug(0.0, 0.0);
            let (real, imag) = z.into_parts();

            assert_eq!(*real.as_ref(), 0.0);
            assert_eq!(*imag.as_ref(), 0.0);
        }

        #[test]
        fn test_into_parts_pure_real() {
            use crate::ComplexScalar;

            let z = new_complex_rug(7.5, 0.0);
            let (real, imag) = z.into_parts();

            assert_eq!(*real.as_ref(), 7.5);
            assert_eq!(*imag.as_ref(), 0.0);
        }

        #[test]
        fn test_into_parts_pure_imaginary() {
            use crate::ComplexScalar;

            let z = new_complex_rug(0.0, -9.2);
            let (real, imag) = z.into_parts();

            assert_eq!(*real.as_ref(), 0.0);
            assert_eq!(*imag.as_ref(), -9.2);
        }

        #[test]
        fn test_into_parts_negative() {
            use crate::ComplexScalar;

            let z = new_complex_rug(-5.5, -3.3);
            let (real, imag) = z.into_parts();

            assert_eq!(*real.as_ref(), -5.5);
            assert_eq!(*imag.as_ref(), -3.3);
        }

        #[test]
        fn test_into_parts_parts_are_independent() {
            use crate::ComplexScalar;

            let z = new_complex_rug(10.0, 20.0);
            let (real, imag) = z.into_parts();

            // Parts should be usable independently
            let two = real_rug(2.0);
            let real_doubled = real * two;
            let imag_halved = imag / real_rug(2.0);

            assert_eq!(*real_doubled.as_ref(), 20.0);
            assert_eq!(*imag_halved.as_ref(), 10.0);
        }
    }
}
//--------------------------------------------------------------------------------------------------
