use std::panic::{UnwindSafe, catch_unwind, resume_unwind};

/// A macro to duplicate code for each SIMD type.
///
/// The inner code has access to type aliases and constants that can be used to
/// write tests.
///
/// ```ignore
/// // Has access to:
/// // - type T
/// // - const N: usize
/// // - type Simd
/// for_simd_types!(|T, N| ...);
///
/// // Has access to:
/// // - type T
/// // - const N: usize
/// // - type Simd
/// // - type Signed
/// // - type SimdSigned
/// // - fn pow_simd (because its name differs for each type)
/// for_simd_types!(|T: Float, N| ...);
///
/// // Has access to:
/// // - type T
/// // - const N: usize
/// // - type Simd
/// for_simd_types!(|T: Integer, N| ...);
///
/// // Has access to:
/// // - type T
/// // - const N: usize
/// // - type Simd
/// // - type Unsigned
/// // - type SimdUnsigned
/// for_simd_types!(|T: Signed, N| ...);
///
/// // Has access to:
/// // - type T
/// // - const N: usize
/// // - type Simd
/// for_simd_types!(|T: Unsigned, N| ...);
/// ```
macro_rules! for_simd_types {
  (|T, N| $expr:expr) => {
    for_simd_types!(|T: Float, N| $expr);
    for_simd_types!(|T: Integer, N| $expr);
  };
  (|T: Float, N| $expr:expr) => {
    for_simd_types!(float!(f32, 4, f32x4, i32, i32x4, pow_f32x4, $expr));
    for_simd_types!(float!(f32, 8, f32x8, i32, i32x8, pow_f32x8, $expr));
    for_simd_types!(float!(f32, 16, f32x16, i32, i32x16, pow_f32x16, $expr));
    for_simd_types!(float!(f64, 2, f64x2, i64, i64x2, pow_f64x2, $expr));
    for_simd_types!(float!(f64, 4, f64x4, i64, i64x4, pow_f64x4, $expr));
    for_simd_types!(float!(f64, 8, f64x8, i64, i64x8, pow_f64x8, $expr));
  };
  (|T: Integer, N| $expr:expr) => {
    for_simd_types!(|T: Signed, N| $expr);
    for_simd_types!(|T: Unsigned, N| $expr);
  };
  (|T: Signed, N| $expr:expr) => {
    for_simd_types!(signed!(i8, 16, i8x16, u8, u8x16, $expr));
    for_simd_types!(signed!(i8, 32, i8x32, u8, u8x32, $expr));
    for_simd_types!(signed!(i16, 8, i16x8, u16, u16x8, $expr));
    for_simd_types!(signed!(i16, 16, i16x16, u16, u16x16, $expr));
    for_simd_types!(signed!(i16, 32, i16x32, u16, u16x32, $expr));
    for_simd_types!(signed!(i32, 4, i32x4, u32, u32x4, $expr));
    for_simd_types!(signed!(i32, 8, i32x8, u32, u32x8, $expr));
    for_simd_types!(signed!(i32, 16, i32x16, u32, u32x16, $expr));
    for_simd_types!(signed!(i64, 2, i64x2, u64, u64x2, $expr));
    for_simd_types!(signed!(i64, 4, i64x4, u64, u64x4, $expr));
    for_simd_types!(signed!(i64, 8, i64x8, u64, u64x8, $expr));
  };
  (|T: Unsigned, N| $expr:expr) => {
    for_simd_types!(unsigned!(u8, 16, u8x16, $expr));
    for_simd_types!(unsigned!(u8, 32, u8x32, $expr));
    for_simd_types!(unsigned!(u16, 8, u16x8, $expr));
    for_simd_types!(unsigned!(u16, 16, u16x16, $expr));
    for_simd_types!(unsigned!(u16, 32, u16x32, $expr));
    for_simd_types!(unsigned!(u32, 4, u32x4, $expr));
    for_simd_types!(unsigned!(u32, 8, u32x8, $expr));
    for_simd_types!(unsigned!(u32, 16, u32x16, $expr));
    for_simd_types!(unsigned!(u64, 2, u64x2, $expr));
    for_simd_types!(unsigned!(u64, 4, u64x4, $expr));
    for_simd_types!(unsigned!(u64, 8, u64x8, $expr));
  };
  (float!($T:ident, $N:literal, $Simd:ident, $Signed:ident, $SimdSigned:ident, $pow_simd:ident, $expr:expr)) => {{
    type Simd = wide::$Simd;
    #[allow(dead_code)]
    type T = $T;
    #[allow(dead_code)]
    const N: usize = $N;
    #[allow(dead_code)]
    type Signed = $Signed;
    #[allow(dead_code)]
    type SimdSigned = wide::$SimdSigned;
    #[allow(dead_code)]
    #[expect(non_upper_case_globals)]
    /// Points to `Simd::pow_{Simd}` which has a different name for each type.
    const pow_simd: fn(Simd, Simd) -> Simd = Simd::$pow_simd;
    $crate::utils::for_simd_types_helper(|| $expr, stringify!($T), $N);
  }};
  (signed!($T:ident, $N:literal, $Simd:ident, $Unsigned:ident, $SimdUnsigned:ident, $expr:expr)) => {{
    type Simd = wide::$Simd;
    #[allow(dead_code)]
    type T = $T;
    #[allow(dead_code)]
    const N: usize = $N;
    #[allow(dead_code)]
    type Unsigned = $Unsigned;
    #[allow(dead_code)]
    type SimdUnsigned = wide::$SimdUnsigned;
    $crate::utils::for_simd_types_helper(|| $expr, stringify!($T), $N);
  }};
  (unsigned!($T:ident, $N:literal, $Simd:ident, $expr:expr)) => {{
    type Simd = wide::$Simd;
    #[allow(dead_code)]
    type T = $T;
    #[allow(dead_code)]
    const N: usize = $N;
    $crate::utils::for_simd_types_helper(|| $expr, stringify!($T), $N);
  }};
}
pub(crate) use for_simd_types;

/// Improves error messages by specifying which `T` and `N` failed.
#[doc(hidden)]
pub fn for_simd_types_helper(f: impl FnOnce() + UnwindSafe, t: &str, n: usize) {
  // For WASM, `catch/resume_unwind` leads to:
  // "wasm trap: wasm `unreachable` instruction executed"
  // which hides the actual panic message.
  if cfg!(target_family = "wasm") {
    f();
  } else {
    match catch_unwind(f) {
      Ok(_) => {}
      Err(payload) => {
        println!();
        println!("T: {t}");
        println!("N: {n}");
        resume_unwind(payload);
      }
    }
  }
}
