//! Tests for the #[magetypes] function-level macro.
//!
//! Tests verify:
//! - Function variants are generated with correct suffixes
//! - Token type substitution works
//! - Functions compile on the current platform
//! - Integration with incant! for dispatch
//! - Multiple arguments and return types

// incant! generates _v3/_v4 for x86_64, _neon for aarch64, _wasm128 for wasm32.
// On i686 (target_arch = "x86") none of these exist.
#![cfg(any(
    target_arch = "x86_64",
    target_arch = "aarch64",
    target_arch = "wasm32"
))]

use archmage::{ScalarToken, SimdToken};

// =============================================================================
// Basic function generation
// =============================================================================

mod basic_generation {
    use super::*;
    use archmage::magetypes;

    /// A simple function that should generate _v3, _v4, _neon, _wasm128, _scalar variants
    #[magetypes]
    pub fn add_one(token: Token, x: f32) -> f32 {
        let _ = token;
        x + 1.0
    }

    #[test]
    fn scalar_variant_exists() {
        let result = add_one_scalar(ScalarToken, 41.0);
        assert_eq!(result, 42.0);
    }

    #[cfg(target_arch = "x86_64")]
    #[test]
    fn v3_variant_exists() {
        if let Some(token) = archmage::X64V3Token::summon() {
            let result = add_one_v3(token, 41.0);
            assert_eq!(result, 42.0);
        }
    }

    #[cfg(all(target_arch = "x86_64", feature = "avx512"))]
    #[test]
    fn v4_variant_exists() {
        if let Some(token) = archmage::X64V4Token::summon() {
            let result = add_one_v4(token, 41.0);
            assert_eq!(result, 42.0);
        }
    }
}

// =============================================================================
// Multiple arguments
// =============================================================================

mod multi_args {
    use super::*;
    use archmage::magetypes;

    #[magetypes]
    pub fn multiply(token: Token, a: f32, b: f32) -> f32 {
        let _ = token;
        a * b
    }

    #[test]
    fn scalar_multiply() {
        let result = multiply_scalar(ScalarToken, 6.0, 7.0);
        assert_eq!(result, 42.0);
    }

    #[cfg(target_arch = "x86_64")]
    #[test]
    fn v3_multiply() {
        if let Some(token) = archmage::X64V3Token::summon() {
            let result = multiply_v3(token, 6.0, 7.0);
            assert_eq!(result, 42.0);
        }
    }
}

// =============================================================================
// Slice arguments
// =============================================================================

mod slice_args {
    use super::*;
    use archmage::magetypes;

    #[magetypes]
    pub fn sum_slice(token: Token, data: &[f32]) -> f32 {
        let _ = token;
        data.iter().sum()
    }

    #[test]
    fn scalar_sum() {
        let data = [1.0f32, 2.0, 3.0, 4.0];
        let result = sum_slice_scalar(ScalarToken, &data);
        assert_eq!(result, 10.0);
    }

    #[cfg(target_arch = "x86_64")]
    #[test]
    fn v3_sum() {
        if let Some(token) = archmage::X64V3Token::summon() {
            let data = [1.0f32, 2.0, 3.0, 4.0];
            let result = sum_slice_v3(token, &data);
            assert_eq!(result, 10.0);
        }
    }
}

// =============================================================================
// Integration with incant!
// =============================================================================

mod incant_integration {
    use archmage::{incant, magetypes};

    #[magetypes]
    pub fn double(token: Token, x: f32) -> f32 {
        let _ = token;
        x * 2.0
    }

    /// Public API that dispatches via incant!
    pub fn double_api(x: f32) -> f32 {
        incant!(double(x))
    }

    #[test]
    fn incant_dispatches_to_variant() {
        let result = double_api(21.0);
        assert_eq!(result, 42.0);
    }

    #[test]
    fn incant_with_zero() {
        let result = double_api(0.0);
        assert_eq!(result, 0.0);
    }

    #[test]
    fn incant_with_negative() {
        let result = double_api(-5.0);
        assert_eq!(result, -10.0);
    }
}

// =============================================================================
// Mutable reference arguments
// =============================================================================

mod mut_ref_args {
    use super::*;
    use archmage::magetypes;

    #[magetypes]
    pub fn fill_array(token: Token, data: &mut [f32], val: f32) {
        let _ = token;
        for x in data.iter_mut() {
            *x = val;
        }
    }

    #[test]
    fn scalar_fill() {
        let mut data = [0.0f32; 4];
        fill_array_scalar(ScalarToken, &mut data, 3.125);
        assert_eq!(data, [3.125, 3.125, 3.125, 3.125]);
    }

    #[cfg(target_arch = "x86_64")]
    #[test]
    fn v3_fill() {
        if let Some(token) = archmage::X64V3Token::summon() {
            let mut data = [0.0f32; 4];
            fill_array_v3(token, &mut data, 2.71);
            assert_eq!(data, [2.71, 2.71, 2.71, 2.71]);
        }
    }
}

// =============================================================================
// Return types other than f32
// =============================================================================

mod return_types {
    use super::*;
    use archmage::magetypes;

    #[magetypes]
    pub fn count(token: Token, data: &[f32]) -> usize {
        let _ = token;
        data.len()
    }

    #[test]
    fn scalar_count() {
        let data = [1.0f32, 2.0, 3.0];
        assert_eq!(count_scalar(ScalarToken, &data), 3);
    }

    #[magetypes]
    pub fn is_empty(token: Token, data: &[f32]) -> bool {
        let _ = token;
        data.is_empty()
    }

    #[test]
    fn scalar_is_empty() {
        assert!(is_empty_scalar(ScalarToken, &[]));
        assert!(!is_empty_scalar(ScalarToken, &[1.0]));
    }
}

// =============================================================================
// Visibility preservation
// =============================================================================

mod visibility {
    use super::*;
    use archmage::magetypes;

    #[magetypes]
    pub fn public_fn(token: Token, x: f32) -> f32 {
        let _ = token;
        x
    }

    #[magetypes]
    fn private_fn(token: Token, x: f32) -> f32 {
        let _ = token;
        x
    }

    #[test]
    fn public_variant_accessible() {
        let _ = public_fn_scalar(ScalarToken, 1.0);
    }

    #[test]
    fn private_variant_accessible_from_same_module() {
        let _ = private_fn_scalar(ScalarToken, 1.0);
    }
}

// =============================================================================
// Passthrough with incant! and IntoConcreteToken
// =============================================================================

mod passthrough_integration {
    use super::*;
    use archmage::{IntoConcreteToken, incant, magetypes};

    #[magetypes]
    pub fn inner(token: Token, x: f32) -> f32 {
        let _ = token;
        x * 3.0
    }

    fn dispatch_inner<T: IntoConcreteToken>(token: T, x: f32) -> f32 {
        incant!(inner(x) with token)
    }

    #[test]
    fn passthrough_scalar() {
        let result = dispatch_inner(ScalarToken, 14.0);
        assert_eq!(result, 42.0);
    }

    #[cfg(target_arch = "x86_64")]
    #[test]
    fn passthrough_v3() {
        if let Some(token) = archmage::X64V3Token::summon() {
            let result = dispatch_inner(token, 14.0);
            assert_eq!(result, 42.0);
        }
    }
}

// =============================================================================
// Token replacement must be ident-level, not substring
// =============================================================================

mod token_not_substring {
    use super::*;
    use archmage::magetypes;

    /// Regression: the old string-level replacement would corrupt identifiers
    /// that contain "Token" as a substring (e.g., `ScalarToken`, `MyTokenizer`).
    /// Token-level replacement treats each identifier as an atomic unit.
    #[magetypes]
    pub fn uses_token_substring(token: Token, x: f32) -> f32 {
        // These identifiers contain "Token" as a substring.
        // String-level replace("Token", "archmage::X64V3Token") would mangle them.
        let _scalar_token_ref: &str = "ScalarToken";
        let _token_izer: f32 = x; // variable named with Token substring
        let _ = token;
        _token_izer + 1.0
    }

    #[test]
    fn scalar_variant_with_token_substrings() {
        let result = uses_token_substring_scalar(ScalarToken, 41.0);
        assert_eq!(result, 42.0);
    }

    #[cfg(target_arch = "x86_64")]
    #[test]
    fn v3_variant_with_token_substrings() {
        if let Some(token) = archmage::X64V3Token::summon() {
            let result = uses_token_substring_v3(token, 41.0);
            assert_eq!(result, 42.0);
        }
    }
}

// =============================================================================
// Generic magetypes function + incant! dispatch (manual variants)
// =============================================================================

#[cfg(not(target_arch = "wasm32"))] // magetypes is non-wasm dev-dep
mod generic_magetypes_dispatch {
    use archmage::{ScalarToken, arcane, incant};
    use magetypes::simd::backends::F32x8Backend;
    use magetypes::simd::generic::f32x8;

    // NOTE: #[magetypes] + f32x8::<Token> does NOT work because #[magetypes]
    // generates variants for ALL tokens (v3, v4, neon, wasm128, scalar) but
    // F32x8Backend is only implemented for specific base tokens (v3, neon,
    // wasm128, scalar). The v4 variant would fail to compile.
    //
    // Instead: write a generic function and wire up incant! manually.

    fn sum_generic<T: F32x8Backend>(token: T, data: &[f32; 8]) -> f32 {
        f32x8::<T>::from_array(token, *data).reduce_add()
    }

    #[cfg(target_arch = "x86_64")]
    #[arcane]
    fn sum_impl_v3(token: archmage::X64V3Token, data: &[f32; 8]) -> f32 {
        sum_generic(token, data)
    }

    fn sum_impl_scalar(token: ScalarToken, data: &[f32; 8]) -> f32 {
        sum_generic(token, data)
    }

    pub fn sum(data: &[f32; 8]) -> f32 {
        incant!(sum_impl(data), [v3, scalar])
    }

    #[test]
    fn generic_dispatch_works() {
        let data = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0];
        let result = sum(&data);
        assert!((result - 36.0).abs() < 0.01);
    }
}
