//! AVX2 parallel point arithmetic for Ed25519.
//!
//! Extended Edwards point operations using HWCD'08 parallel formulas that
//! compute all 4 coordinates (X, Y, Z, T) simultaneously via vectorized
//! field arithmetic.
//!
//! # Hamburg scaling trick
//!
//! The cached point format absorbs the curve constant `d = -d1/d2` (where
//! `d1 = 121665`, `d2 = 121666`) into the precomputed values. This
//! eliminates a separate multiply by `2d` during addition, at the cost of
//! scaling all output coordinates by `d2²` — which cancels in projective
//! coordinates.

#[cfg(target_arch = "x86_64")]
use core::arch::x86_64::_mm256_loadu_si256;

#[cfg(target_arch = "x86_64")]
use super::{
  field::FieldElement,
  field_avx2::{FieldElement2625x4, Lanes, Shuffle},
  field_ifma::FieldElement51x4,
  point::{CachedPoint, ExtendedPoint},
  scalar,
};
#[cfg(target_arch = "x86_64")]
#[path = "basepoint_table_ifma.rs"]
mod basepoint_table_ifma;

/// Hamburg constants for the curve `d = -d1/d2`.
const D1: u64 = 121_665;
const D2: u64 = 121_666;

/// Extended Edwards point in AVX2 vectorized form.
///
/// Lanes: `(X, Y, Z, T)` as `(A, B, C, D)`.
#[derive(Clone, Copy)]
#[cfg(target_arch = "x86_64")]
pub(crate) struct ExtendedPointAvx2(pub(crate) FieldElement2625x4);

/// Cached point for efficient addition (Hamburg-scaled projective format).
///
/// Lanes: `(d2·(Y−X), d2·(Y+X), 2·d2·Z, −2·d1·T)` as `(A, B, C, D)`.
///
/// The `d2` scaling factor cancels in projective coordinates after the
/// uniform multiply.
#[derive(Clone, Copy)]
#[cfg(target_arch = "x86_64")]
pub(crate) struct CachedPointAvx2(pub(crate) FieldElement2625x4);

#[cfg(target_arch = "x86_64")]
impl ExtendedPointAvx2 {
  /// Pack a scalar extended point into AVX2 vectorized form.
  ///
  /// # Safety
  ///
  /// Caller must ensure AVX2 is available.
  #[inline]
  #[target_feature(enable = "avx2")]
  #[allow(unsafe_op_in_unsafe_fn)]
  pub(crate) unsafe fn from_extended(p: &ExtendedPoint) -> Self {
    let (x, y, z, t) = p.components();
    Self(FieldElement2625x4::new(x, y, z, t))
  }

  /// Unpack back to a scalar extended point.
  ///
  /// # Safety
  ///
  /// Caller must ensure AVX2 is available.
  #[inline]
  #[target_feature(enable = "avx2")]
  #[allow(unsafe_op_in_unsafe_fn)]
  pub(crate) unsafe fn to_extended(self) -> ExtendedPoint {
    let [x, y, z, t] = self.0.split();
    ExtendedPoint::from_raw(x, y, z, t)
  }

  /// Convert to Hamburg-scaled cached format for addition.
  ///
  /// Computes: `(d2·(Y−X), d2·(Y+X), 2·d2·Z, −2·d1·T)`.
  ///
  /// # Safety
  ///
  /// Caller must ensure AVX2 is available.
  #[inline]
  #[target_feature(enable = "avx2")]
  #[allow(unsafe_op_in_unsafe_fn)]
  pub(crate) unsafe fn to_cached(self) -> CachedPointAvx2 {
    // Step 1: Compute (Y-X, Y+X) in lanes A,B; keep Z,T in C,D.
    let ds = self.0.diff_sum(); // (Y-X, Y+X, T-Z, Z+T)
    let prepared = self.0.blend(&ds, Lanes::AB); // (Y-X, Y+X, Z, T)

    // Step 2: Multiply by Hamburg constants (d2, d2, 2·d2, 2·d1).
    let constants = hamburg_constants();
    let scaled = prepared.mul(&constants);

    // Step 3: Negate lane D → −2·d1·T.
    let negated = scaled.negate_lazy();
    CachedPointAvx2(scaled.blend(&negated, Lanes::D))
  }

  /// Add a cached point to this extended point.
  ///
  /// Uses HWCD'08 parallel addition: 2 uniform vectorized multiplies.
  ///
  /// # Safety
  ///
  /// Caller must ensure AVX2 is available.
  #[inline]
  #[target_feature(enable = "avx2")]
  #[allow(unsafe_op_in_unsafe_fn)]
  pub(crate) unsafe fn add_cached(&self, other: &CachedPointAvx2) -> Self {
    // Step 1: Prepare self as (Y-X, Y+X, Z, T) by blending diff_sum into A,B.
    let ds = self.0.diff_sum();
    let tmp = self.0.blend(&ds, Lanes::AB); // (Y1-X1, Y1+X1, Z1, T1)

    // Step 2: Uniform multiply with cached point.
    // (Y1-X1)·d2·(Y2-X2), (Y1+X1)·d2·(Y2+X2), Z1·2·d2·Z2, T1·(−2·d1·T2)
    let product = tmp.mul(&other.0);

    // Step 3: Swap C↔D to align for diff_sum.
    // After swap: lane C has the T-product, lane D has the Z-product.
    let swapped = product.shuffle(Shuffle::ABDC);

    // Step 4: diff_sum computes (e, h, f, g) (up to Hamburg scaling).
    let ehfg = swapped.diff_sum();

    // Step 5: Shuffle into final multiply operands.
    let t0 = ehfg.shuffle(Shuffle::ADDA); // (e, g, g, e)
    let t1 = ehfg.shuffle(Shuffle::CBCB); // (f, h, f, h)

    // Step 6: Uniform multiply → (e·f, g·h, g·f, e·h) = (X3, Y3, Z3, T3).
    Self(t0.mul(&t1))
  }

  /// Double this point using dedicated HWCD'08 parallel doubling.
  ///
  /// Uses 1 uniform vectorized square + 1 uniform vectorized multiply.
  ///
  /// # Safety
  ///
  /// Caller must ensure AVX2 is available.
  #[inline]
  #[target_feature(enable = "avx2")]
  #[allow(unsafe_op_in_unsafe_fn)]
  pub(crate) unsafe fn double(&self) -> Self {
    // Step 1: Build (X, Y, Z, X+Y) for squaring.
    let ab = self.0.shuffle(Shuffle::ABAB); // (X, Y, X, Y)
    let ba = ab.shuffle(Shuffle::BADC); // (Y, X, Y, X)
    let xy_sum = ab.add(&ba); // (X+Y, Y+X, X+Y, Y+X)
    let prepared = self.0.blend(&xy_sum, Lanes::D); // (X, Y, Z, X+Y)

    // Step 2: Square with D-lane negation.
    // Result: (X², Y², Z², −(X+Y)²) = (S1, S2, S3, −S4).
    let sq = prepared.square_and_negate_d();

    // Step 3: Compute (S5, S6, S8, S9) via non-uniform adds.
    //   S5 = S1 + S2          (= −h)
    //   S6 = S1 − S2          (= −g)
    //   S8 = S1 − S2 + 2·S3  (= −f)
    //   S9 = S1 + S2 − S4    (= −e)
    // Double-negations cancel in the final multiply.

    let zero = FieldElement2625x4::zero();
    let s1 = sq.shuffle(Shuffle::AAAA); // (S1, S1, S1, S1)
    let s2 = sq.shuffle(Shuffle::BBBB); // (S2, S2, S2, S2)

    // Build the target vector incrementally:
    let sq_doubled = sq.add(&sq); // (2S1, 2S2, 2S3, −2S4)
    let mut tmp = zero.blend(&sq_doubled, Lanes::C); // (0, 0, 2S3, 0)
    tmp = tmp.blend(&sq, Lanes::D); // (0, 0, 2S3, −S4)
    tmp = tmp.add(&s1); // (S1, S1, S1+2S3, S1−S4)

    let s2_in_ad = zero.blend(&s2, Lanes::AD); // (S2, 0, 0, S2)
    tmp = tmp.add(&s2_in_ad); // (S1+S2, S1, S1+2S3, S1+S2−S4)

    let neg_s2 = s2.negate_lazy();
    let neg_s2_in_bc = zero.blend(&neg_s2, Lanes::BC); // (0, −S2, −S2, 0)
    // No reduce needed: square_and_negate_d() negates D in the u64 accumulator
    // domain (p×2^37 bias), so −S4 emerges fully reduced (b < 0.007).
    // S9 = S1 + S2 + (−S4) is a pure 3-way add with b < 1.6 — safely under
    // the 1.75 threshold for vpmuludq's ×19 pre-multiply.
    // Bound check: t1 = (S9, S6, S6, S9), all b < 1.6 < 1.75 (rhs safe).
    //              t0 = (S8, S5, S8, S5), S8 b < 2.33 < 2.5 (self safe).
    let tmp = tmp.add(&neg_s2_in_bc); // (S5, S6, S8, S9)

    // Step 4: Shuffle into final multiply operands.
    let t0 = tmp.shuffle(Shuffle::CACA); // (S8, S5, S8, S5)
    let t1 = tmp.shuffle(Shuffle::DBBD); // (S9, S6, S6, S9)

    // Step 5: Uniform multiply → (S8·S9, S5·S6, S8·S6, S5·S9) = (X3, Y3, Z3, T3).
    Self(t0.mul(&t1))
  }
}

#[cfg(target_arch = "x86_64")]
impl CachedPointAvx2 {
  /// Negate the cached point (for subtraction).
  ///
  /// Swaps `(Y−X)` and `(Y+X)` and negates `T`, implementing curve
  /// negation `(X:Y:Z:T) → (−X:Y:Z:−T)`.
  ///
  /// # Safety
  ///
  /// Caller must ensure AVX2 is available.
  #[inline]
  #[target_feature(enable = "avx2")]
  #[allow(unsafe_op_in_unsafe_fn)]
  pub(crate) unsafe fn neg(&self) -> Self {
    let swapped = self.0.shuffle(Shuffle::BACD); // swap A↔B, keep C,D
    let negated = swapped.negate_lazy();
    Self(swapped.blend(&negated, Lanes::D)) // negate D only
  }
}

#[inline(always)]
#[must_use]
fn ct_eq_mask_u8(lhs: u8, rhs: u8) -> u64 {
  let diff = u64::from(lhs ^ rhs);
  let is_zero = ((diff | diff.wrapping_neg()) >> 63) ^ 1;
  0u64.wrapping_sub(is_zero)
}

#[inline(always)]
#[must_use]
fn ct_negative_mask_i8(value: i8) -> u64 {
  let bit = ((i16::from(value) >> 15) & 1) as u64;
  0u64.wrapping_sub(bit)
}

#[inline(always)]
#[must_use]
fn ct_abs_i8(value: i8) -> u8 {
  let value = i16::from(value);
  let sign = value >> 15;
  ((value ^ sign) - sign) as u8
}

/// Constant-time cached-point selection for AVX2 fixed-base tables.
///
/// # Safety
///
/// Caller must ensure AVX2 is available.
#[inline]
#[target_feature(enable = "avx2")]
#[allow(unsafe_op_in_unsafe_fn)]
unsafe fn select_cached_avx2(lhs: &CachedPointAvx2, rhs: &CachedPointAvx2, mask: u64) -> CachedPointAvx2 {
  CachedPointAvx2(lhs.0.select_mask(&rhs.0, mask))
}

/// Hamburg scaling constants for projective cached: `(d2, d2, 2·d2, 2·d1)`.
///
/// # Safety
///
/// Caller must ensure AVX2 is available.
#[inline]
#[target_feature(enable = "avx2")]
#[allow(unsafe_op_in_unsafe_fn)]
unsafe fn hamburg_constants() -> FieldElement2625x4 {
  let d2_fe = FieldElement::from_small(D2);
  let d2_fe_2 = FieldElement::from_small(D2.wrapping_mul(2));
  let d1_fe_2 = FieldElement::from_small(D1.wrapping_mul(2));
  FieldElement2625x4::new(&d2_fe, &d2_fe, &d2_fe_2, &d1_fe_2)
}

/// Hamburg scaling constants for affine cached: `(d2, d2, 2·d2, d2)`.
///
/// The D-lane uses `d2` (not `2·d1`) because the affine table's `t2d`
/// field already encodes the `d` constant, so `d2 · t2d = −2·d1·T`.
///
/// # Safety
///
/// Caller must ensure AVX2 is available.
#[inline]
#[target_feature(enable = "avx2")]
#[allow(unsafe_op_in_unsafe_fn)]
unsafe fn hamburg_affine_constants() -> FieldElement2625x4 {
  let d2_fe = FieldElement::from_small(D2);
  let d2_fe_2 = FieldElement::from_small(D2.wrapping_mul(2));
  FieldElement2625x4::new(&d2_fe, &d2_fe, &d2_fe_2, &d2_fe)
}

// Scalar multiplication

/// Convert an affine `CachedPoint` (from the basepoint table) to
/// Hamburg-scaled `CachedPointAvx2` format.
///
/// The affine table stores `(Y+X, Y−X, t2d)` where `t2d = 2d·T` and
/// `Z = 1`. Since `d = −d1/d2`, we have `t2d = (−2·d1/d2)·T`, so
/// `d2·t2d = −2·d1·T` — exactly the D-lane value the Hamburg format needs.
///
/// We pack `(Y−X, Y+X, 1, t2d)` and multiply by `(d2, d2, 2·d2, d2)`:
/// - A = d2·(Y−X), B = d2·(Y+X), C = 2·d2, D = −2·d1·T.
///
/// # Safety
///
/// Caller must ensure AVX2 is available.
#[inline]
#[target_feature(enable = "avx2")]
#[allow(unsafe_op_in_unsafe_fn)]
unsafe fn cached_from_affine(cp: &CachedPoint, constants: &FieldElement2625x4) -> CachedPointAvx2 {
  let (y_plus_x, y_minus_x, t2d) = cp.components();
  let packed = FieldElement2625x4::new(y_minus_x, y_plus_x, &FieldElement::ONE, t2d);
  CachedPointAvx2(packed.mul(constants))
}

/// Select a signed digit from an affine cached basepoint table.
///
/// # Safety
///
/// Caller must ensure AVX2 is available.
#[inline]
#[target_feature(enable = "avx2")]
#[allow(unsafe_op_in_unsafe_fn)]
unsafe fn select_signed_cached_avx2(
  table: &[CachedPoint; 8],
  digit: i8,
  affine_k: &FieldElement2625x4,
  identity: &CachedPointAvx2,
) -> CachedPointAvx2 {
  let abs = core::hint::black_box(ct_abs_i8(digit));
  let mut selected = *identity;
  for (i, candidate) in table.iter().enumerate() {
    let candidate = core::ptr::read_volatile(candidate);
    let cached = cached_from_affine(&candidate, affine_k);
    let mask = core::hint::black_box(ct_eq_mask_u8(abs, (i as u8).wrapping_add(1)));
    selected = select_cached_avx2(&selected, &cached, mask);
  }

  let neg = selected.neg();
  select_cached_avx2(&selected, &neg, core::hint::black_box(ct_negative_mask_i8(digit)))
}

/// Add a signed wNAF digit from an affine cached odd-multiples table.
///
/// # Safety
///
/// Caller must ensure AVX2 is available.
#[inline]
#[target_feature(enable = "avx2")]
#[allow(unsafe_op_in_unsafe_fn)]
unsafe fn add_wnaf_digit_cached_avx2(
  acc: ExtendedPointAvx2,
  table: &[CachedPoint; 8],
  digit: i8,
  affine_k: &FieldElement2625x4,
) -> ExtendedPointAvx2 {
  let index = usize::from((digit.unsigned_abs().wrapping_sub(1)) / 2);
  let Some(point) = table.get(index) else {
    return acc;
  };

  let cached = cached_from_affine(point, affine_k);
  if digit > 0 {
    acc.add_cached(&cached)
  } else {
    acc.add_cached(&cached.neg())
  }
}

/// Add a signed digit from a runtime CachedPointAvx2 table.
///
/// # Safety
///
/// Caller must ensure AVX2 is available.
#[inline]
#[target_feature(enable = "avx2")]
#[allow(unsafe_op_in_unsafe_fn)]
unsafe fn add_signed_runtime_cached_avx2(
  acc: ExtendedPointAvx2,
  table: &[CachedPointAvx2; 8],
  digit: i8,
) -> ExtendedPointAvx2 {
  let index = usize::from(digit.unsigned_abs()).wrapping_sub(1);
  let Some(point) = table.get(index) else {
    return acc;
  };

  if digit > 0 {
    acc.add_cached(point)
  } else {
    acc.add_cached(&point.neg())
  }
}

/// Build a runtime table of `[1P, 2P, ..., 8P]` in Hamburg-scaled cached format.
///
/// # Safety
///
/// Caller must ensure AVX2 is available.
#[target_feature(enable = "avx2")]
#[allow(unsafe_op_in_unsafe_fn)]
unsafe fn cached_multiples_avx2(point: &ExtendedPointAvx2) -> [CachedPointAvx2; 8] {
  let mut acc = *point;
  let point_cached = point.to_cached();
  let first = acc.to_cached();

  // Build [1P, 2P, ..., 8P] — initialize from first entry then accumulate.
  let mut out = [first; 8];
  for entry in out.iter_mut().skip(1) {
    acc = acc.add_cached(&point_cached);
    *entry = acc.to_cached();
  }

  out
}

/// Variable-base signed radix-16 scalar multiplication using AVX2 point ops.
///
/// Builds a runtime cached table and scans the scalar in signed radix-16 digits,
/// using AVX2 parallel doubling and addition throughout.
///
/// # Safety
///
/// Caller must ensure AVX2 is available.
#[target_feature(enable = "avx2")]
#[allow(unsafe_op_in_unsafe_fn)]
pub(crate) unsafe fn scalar_mul_vartime_avx2(point: &ExtendedPoint, scalar_bytes: &[u8; 32]) -> ExtendedPoint {
  let digits = scalar::as_radix_16(scalar_bytes);
  let avx_point = ExtendedPointAvx2::from_extended(point);
  let table = cached_multiples_avx2(&avx_point);

  let mut acc = ExtendedPointAvx2::from_extended(&ExtendedPoint::identity());

  for digit in digits.iter().rev().copied() {
    acc = acc.double().double().double().double();
    if digit != 0 {
      acc = add_signed_runtime_cached_avx2(acc, &table, digit);
    }
  }

  acc.to_extended()
}

/// Fixed-base scalar multiplication for the Ed25519 basepoint using AVX2.
///
/// Uses the static radix-16 precomputed table, converting each affine
/// cached entry to Hamburg format on the fly.
///
/// # Safety
///
/// Caller must ensure AVX2 is available.
#[target_feature(enable = "avx2")]
#[allow(unsafe_op_in_unsafe_fn)]
pub(crate) unsafe fn scalar_mul_basepoint_avx2(scalar_bytes: &[u8; 32]) -> ExtendedPoint {
  use super::point::BASEPOINT_RADIX16_TABLE;

  let digits = scalar::as_radix_16(scalar_bytes);
  let affine_k = hamburg_affine_constants();
  let mut acc = ExtendedPointAvx2::from_extended(&ExtendedPoint::identity());
  let identity = acc.to_cached();

  for (digit, table) in digits.iter().copied().zip(BASEPOINT_RADIX16_TABLE.iter()) {
    let point = select_signed_cached_avx2(table, digit, &affine_k, &identity);
    acc = acc.add_cached(&point);
  }

  acc.to_extended()
}

/// Select one signed fixed-base table digit through the production AVX2 selector.
///
/// # Safety
///
/// Caller must ensure AVX2 is available.
#[cfg(feature = "diag")]
#[inline]
#[target_feature(enable = "avx2")]
#[allow(unsafe_op_in_unsafe_fn)]
pub unsafe fn diag_select_basepoint_cached_avx2_limb_digest(digit: i8) -> [u64; 20] {
  use super::point::BASEPOINT_RADIX16_TABLE;

  let affine_k = hamburg_affine_constants();
  let identity = ExtendedPointAvx2::from_extended(&ExtendedPoint::identity()).to_cached();
  let selected = select_signed_cached_avx2(&BASEPOINT_RADIX16_TABLE[0], digit, &affine_k, &identity);
  let fields = selected.0.split();
  let mut out = [0u64; 20];
  for (chunk, field) in out.chunks_exact_mut(5).zip(fields.iter()) {
    chunk.copy_from_slice(field.limbs());
  }
  out
}

/// Build a runtime table of odd multiples `[1P, 3P, 5P, ..., (2n-1)P]` in
/// Hamburg-scaled cached AVX2 format.
///
/// For wNAF(w), the table has `2^(w-2)` entries. wNAF(5) -> 8 entries.
///
/// # Safety
///
/// Caller must ensure AVX2 is available.
#[target_feature(enable = "avx2")]
#[allow(unsafe_op_in_unsafe_fn)]
unsafe fn odd_multiples_avx2<const N: usize>(point: &ExtendedPointAvx2) -> [CachedPointAvx2; N] {
  let p2 = point.double();
  let p2_cached = p2.to_cached();

  let first = point.to_cached();
  let mut out = [first; N];
  let mut acc = *point;
  for dst in out.iter_mut().skip(1) {
    acc = acc.add_cached(&p2_cached);
    *dst = acc.to_cached();
  }
  out
}

/// Add a signed wNAF digit from an odd-multiples AVX2 cached table.
///
/// # Safety
///
/// Caller must ensure AVX2 is available.
#[inline]
#[target_feature(enable = "avx2")]
#[allow(unsafe_op_in_unsafe_fn)]
unsafe fn add_wnaf_digit_avx2(acc: ExtendedPointAvx2, table: &[CachedPointAvx2], digit: i8) -> ExtendedPointAvx2 {
  let index = usize::from((digit.unsigned_abs().wrapping_sub(1)) / 2);
  let Some(point) = table.get(index) else {
    return acc;
  };

  if digit > 0 {
    acc.add_cached(point)
  } else {
    acc.add_cached(&point.neg())
  }
}

/// wNAF-based Straus/Shamir: `[s]B + [h]A` using AVX2 point operations.
///
/// Uses wNAF(5) for both scalars. This reduces additions versus the older
/// radix-16 AVX2 Straus fallback while keeping setup costs modest: two 8-entry
/// odd-multiples tables, with the basepoint table reused from static storage.
///
/// Variable-time: branches on scalar digit values. Safe for verification
/// where both `s` and `h` are public.
///
/// # Safety
///
/// Caller must ensure AVX2 is available.
#[target_feature(enable = "avx2")]
#[allow(unsafe_op_in_unsafe_fn)]
#[allow(clippy::indexing_slicing)] // i bounded by top < 256, naf arrays are [i8; 256]
pub(crate) unsafe fn straus_wnaf_vartime_avx2(s: &[u8; 32], h: &[u8; 32], a: &ExtendedPoint) -> ExtendedPoint {
  use super::point::BASEPOINT_WNAF5_TABLE;

  let s_naf = scalar::non_adjacent_form(s, 5);
  let h_naf = scalar::non_adjacent_form(h, 5);

  let avx_a = ExtendedPointAvx2::from_extended(a);
  let a_table: [CachedPointAvx2; 8] = odd_multiples_avx2(&avx_a);
  let affine_k = hamburg_affine_constants();

  let top = s_naf
    .iter()
    .enumerate()
    .rev()
    .find(|&(_, &d)| d != 0)
    .map_or(0, |(i, _)| i)
    .max(
      h_naf
        .iter()
        .enumerate()
        .rev()
        .find(|&(_, &d)| d != 0)
        .map_or(0, |(i, _)| i),
    );

  let mut acc = ExtendedPointAvx2::from_extended(&ExtendedPoint::identity());

  for i in (0..=top).rev() {
    acc = acc.double();

    if s_naf[i] != 0 {
      acc = add_wnaf_digit_cached_avx2(acc, &BASEPOINT_WNAF5_TABLE, s_naf[i], &affine_k);
    }
    if h_naf[i] != 0 {
      acc = add_wnaf_digit_avx2(acc, &a_table, h_naf[i]);
    }
  }

  acc.to_extended()
}

// ===========================================================================
// AVX-512 IFMA point operations (radix-2^51 via FieldElement51x4)
// ===========================================================================

/// Extended Edwards point in IFMA vectorized form.
///
/// Lanes: `(X, Y, Z, T)` as `(A, B, C, D)`.
#[derive(Clone, Copy)]
#[cfg(target_arch = "x86_64")]
pub(crate) struct ExtendedPointIfma(pub(crate) FieldElement51x4);

/// Cached point for efficient addition (Hamburg-scaled, IFMA format).
#[derive(Clone, Copy)]
#[cfg(target_arch = "x86_64")]
pub(crate) struct CachedPointIfma(pub(crate) FieldElement51x4);

#[cfg(target_arch = "x86_64")]
impl ExtendedPointIfma {
  /// Pack a scalar extended point into IFMA vectorized form.
  ///
  /// # Safety
  ///
  /// Caller must ensure AVX2 is available.
  #[inline]
  #[target_feature(enable = "avx2")]
  #[allow(unsafe_op_in_unsafe_fn)]
  pub(crate) unsafe fn from_extended(p: &ExtendedPoint) -> Self {
    let (x, y, z, t) = p.components();
    Self(FieldElement51x4::new(x, y, z, t))
  }

  /// Unpack back to a scalar extended point.
  ///
  /// # Safety
  ///
  /// Caller must ensure AVX2 is available.
  #[inline]
  #[target_feature(enable = "avx2")]
  #[allow(unsafe_op_in_unsafe_fn)]
  pub(crate) unsafe fn to_extended(self) -> ExtendedPoint {
    let [x, y, z, t] = self.0.split();
    ExtendedPoint::from_raw(x, y, z, t)
  }

  /// Convert to Hamburg-scaled cached format for addition.
  ///
  /// # Safety
  ///
  /// Caller must ensure AVX-512 IFMA + VL are available.
  #[inline]
  #[target_feature(enable = "avx2,avx512ifma,avx512vl")]
  #[allow(unsafe_op_in_unsafe_fn)]
  pub(crate) unsafe fn to_cached(self) -> CachedPointIfma {
    let ds = self.0.diff_sum();
    let prepared = self.0.blend(&ds, Lanes::AB);
    let constants = hamburg_constants_ifma();
    let scaled = prepared.reduce().mul_small(&constants);
    let negated = scaled.negate_lazy();
    CachedPointIfma(scaled.blend(&negated, Lanes::D).reduce())
  }

  /// Add a cached point to this extended point.
  ///
  /// # Safety
  ///
  /// Caller must ensure AVX-512 IFMA + VL are available.
  #[inline]
  #[target_feature(enable = "avx2,avx512ifma,avx512vl")]
  #[allow(unsafe_op_in_unsafe_fn)]
  pub(crate) unsafe fn add_cached(&self, other: &CachedPointIfma) -> Self {
    let ds = self.0.diff_sum();
    let tmp = self.0.blend(&ds, Lanes::AB);
    let product = tmp.reduce().mul(&other.0);
    let swapped = product.shuffle(Shuffle::ABDC);
    let ehfg = swapped.diff_sum();
    let reduced = ehfg.reduce();
    let t0 = reduced.shuffle(Shuffle::ADDA);
    let t1 = reduced.shuffle(Shuffle::CBCB);
    Self(t0.mul(&t1))
  }

  /// Double this point using HWCD'08 parallel doubling.
  ///
  /// Uses the reduced-input `square()` + `mul()` path: reduces before each
  /// multiply to eliminate overflow corrections (~40% fewer ops per double).
  ///
  /// # Safety
  ///
  /// Caller must ensure AVX-512 IFMA + VL are available.
  #[inline]
  #[target_feature(enable = "avx2,avx512ifma,avx512vl")]
  #[allow(unsafe_op_in_unsafe_fn)]
  pub(crate) unsafe fn double(&self) -> Self {
    // Prepare (X, Y, Z, X+Y) for squaring.
    let tmp0 = self.0.shuffle(Shuffle::BADC); // (Y, X, _, _)
    let tmp1 = self.0.add(&tmp0).shuffle(Shuffle::ABAB); // (X+Y, X+Y, X+Y, X+Y)
    let prepared = self.0.blend(&tmp1, Lanes::D); // (X, Y, Z, X+Y)

    // Square reduced inputs → (S1, S2, S3, S4) = (X², Y², Z², (X+Y)²)
    let sq = prepared.reduce().square();
    // sq is reduced (≤51 bits per limb).

    // Compute (S5, S6, S8, S9) where:
    //   S5 = S1+S2, S6 = S1-S2, S8 = S1-S2+2S3, S9 = S1+S2-S4
    let zero = FieldElement51x4::zero();
    let s1 = sq.shuffle(Shuffle::AAAA);
    let s2 = sq.shuffle(Shuffle::BBBB);

    // (-S2, -S2, -S2, -S4): negate S2 in A,B,C and S4 in D.
    let s2_s2_s2_s4 = s2.blend(&sq, Lanes::D).negate_lazy();

    let mut tmp0 = s1.add(&zero.blend(&sq.add(&sq), Lanes::C));
    // = (S1, S1, S1+2S3, S1)
    tmp0 = tmp0.add(&zero.blend(&s2, Lanes::AD));
    // = (S1+S2, S1, S1+2S3, S1+S2)
    tmp0 = tmp0.add(&zero.blend(&s2_s2_s2_s4, Lanes::BCD));
    // = (S1+S2, S1-S2, S1+2S3-S2, S1+S2-S4) = (S5, S6, S8, S9)

    // Reduce before final multiply.
    let reduced = tmp0.reduce();
    let t0 = reduced.shuffle(Shuffle::CACA); // (S8, S5, S8, S5)
    let t1 = reduced.shuffle(Shuffle::DBBD); // (S9, S6, S6, S9)

    // (S8·S9, S5·S6, S8·S6, S5·S9) = (X3, Y3, Z3, T3)
    Self(t0.mul(&t1))
  }
}

#[cfg(target_arch = "x86_64")]
impl CachedPointIfma {
  /// Negate the cached point (for subtraction).
  ///
  /// # Safety
  ///
  /// Caller must ensure AVX2 is available.
  #[inline]
  #[target_feature(enable = "avx2")]
  #[allow(unsafe_op_in_unsafe_fn)]
  pub(crate) unsafe fn neg(&self) -> Self {
    let swapped = self.0.shuffle(Shuffle::BACD);
    let negated = swapped.negate_lazy();
    Self(swapped.blend(&negated, Lanes::D))
  }
}

/// Constant-time cached-point selection for IFMA fixed-base tables.
///
/// # Safety
///
/// Caller must ensure AVX2 is available.
#[inline]
#[target_feature(enable = "avx2")]
#[allow(unsafe_op_in_unsafe_fn)]
unsafe fn select_cached_ifma(lhs: &CachedPointIfma, rhs: &CachedPointIfma, mask: u64) -> CachedPointIfma {
  CachedPointIfma(lhs.0.select_mask(&rhs.0, mask))
}

/// Hamburg scaling constants for projective cached (IFMA): `(d2, d2, 2·d2, 2·d1)`.
///
/// # Safety
///
/// Caller must ensure AVX2 is available.
#[inline]
#[target_feature(enable = "avx2")]
#[allow(unsafe_op_in_unsafe_fn)]
unsafe fn hamburg_constants_ifma() -> FieldElement51x4 {
  let d2_fe = FieldElement::from_small(D2);
  let d2_fe_2 = FieldElement::from_small(D2.wrapping_mul(2));
  let d1_fe_2 = FieldElement::from_small(D1.wrapping_mul(2));
  FieldElement51x4::new(&d2_fe, &d2_fe, &d2_fe_2, &d1_fe_2)
}

/// Hamburg scaling constants for affine cached (IFMA): `(d2, d2, 2·d2, d2)`.
///
/// # Safety
///
/// Caller must ensure AVX2 is available.
#[inline]
#[target_feature(enable = "avx2")]
#[allow(unsafe_op_in_unsafe_fn)]
unsafe fn hamburg_affine_constants_ifma() -> FieldElement51x4 {
  let d2_fe = FieldElement::from_small(D2);
  let d2_fe_2 = FieldElement::from_small(D2.wrapping_mul(2));
  FieldElement51x4::new(&d2_fe, &d2_fe, &d2_fe_2, &d2_fe)
}

/// Convert an affine `CachedPoint` to Hamburg-scaled IFMA cached format.
///
/// # Safety
///
/// Caller must ensure AVX-512 IFMA + VL are available.
#[inline]
#[target_feature(enable = "avx2,avx512ifma,avx512vl")]
#[allow(unsafe_op_in_unsafe_fn)]
unsafe fn cached_from_affine_ifma(cp: &CachedPoint, constants: &FieldElement51x4) -> CachedPointIfma {
  let (y_plus_x, y_minus_x, t2d) = cp.components();
  let packed = FieldElement51x4::new(y_minus_x, y_plus_x, &FieldElement::ONE, t2d);
  // Affine table entries are already reduced (≤51-bit limbs from static data).
  CachedPointIfma(packed.mul_small(constants).reduce())
}

/// Add a signed digit from an affine cached table (IFMA).
/// Select a signed digit from an affine cached basepoint table.
///
/// # Safety
///
/// Caller must ensure AVX-512 IFMA + VL are available.
#[inline]
#[target_feature(enable = "avx2,avx512ifma,avx512vl")]
#[allow(unsafe_op_in_unsafe_fn)]
unsafe fn select_signed_cached_ifma(
  table: &[CachedPoint; 8],
  digit: i8,
  affine_k: &FieldElement51x4,
  identity: &CachedPointIfma,
) -> CachedPointIfma {
  let abs = core::hint::black_box(ct_abs_i8(digit));
  let mut selected = *identity;
  for (i, candidate) in table.iter().enumerate() {
    let candidate = core::ptr::read_volatile(candidate);
    let cached = cached_from_affine_ifma(&candidate, affine_k);
    let mask = core::hint::black_box(ct_eq_mask_u8(abs, (i as u8).wrapping_add(1)));
    selected = select_cached_ifma(&selected, &cached, mask);
  }

  let neg = selected.neg();
  select_cached_ifma(&selected, &neg, core::hint::black_box(ct_negative_mask_i8(digit)))
}

/// Add a signed digit from a runtime CachedPointIfma table.
///
/// # Safety
///
/// Caller must ensure AVX-512 IFMA + VL are available.
#[inline]
#[target_feature(enable = "avx2,avx512ifma,avx512vl")]
#[allow(unsafe_op_in_unsafe_fn)]
unsafe fn add_signed_runtime_cached_ifma(
  acc: ExtendedPointIfma,
  table: &[CachedPointIfma; 8],
  digit: i8,
) -> ExtendedPointIfma {
  let index = usize::from(digit.unsigned_abs()).wrapping_sub(1);
  let Some(point) = table.get(index) else {
    return acc;
  };
  if digit > 0 {
    acc.add_cached(point)
  } else {
    acc.add_cached(&point.neg())
  }
}

/// Build a runtime table of `[1P, 2P, ..., 8P]` in IFMA Hamburg-scaled cached format.
///
/// # Safety
///
/// Caller must ensure AVX-512 IFMA + VL are available.
#[target_feature(enable = "avx2,avx512ifma,avx512vl")]
#[allow(unsafe_op_in_unsafe_fn)]
unsafe fn cached_multiples_ifma(point: &ExtendedPointIfma) -> [CachedPointIfma; 8] {
  let mut acc = *point;
  let point_cached = point.to_cached();
  let first = acc.to_cached();

  let mut out = [first; 8];
  for entry in out.iter_mut().skip(1) {
    acc = acc.add_cached(&point_cached);
    *entry = acc.to_cached();
  }
  out
}

/// Variable-base scalar multiplication using AVX-512 IFMA.
///
/// # Safety
///
/// Caller must ensure AVX-512 IFMA + VL are available.
#[target_feature(enable = "avx2,avx512ifma,avx512vl")]
#[allow(unsafe_op_in_unsafe_fn)]
pub(crate) unsafe fn scalar_mul_vartime_ifma(point: &ExtendedPoint, scalar_bytes: &[u8; 32]) -> ExtendedPoint {
  let digits = scalar::as_radix_16(scalar_bytes);
  let ifma_point = ExtendedPointIfma::from_extended(point);
  let table = cached_multiples_ifma(&ifma_point);
  let mut acc = ExtendedPointIfma::from_extended(&ExtendedPoint::identity());
  for digit in digits.iter().rev().copied() {
    acc = acc.double().double().double().double();
    if digit != 0 {
      acc = add_signed_runtime_cached_ifma(acc, &table, digit);
    }
  }
  acc.to_extended()
}

/// Fixed-base scalar multiplication for the Ed25519 basepoint using IFMA.
///
/// # Safety
///
/// Caller must ensure AVX-512 IFMA + VL are available.
#[target_feature(enable = "avx2,avx512ifma,avx512vl")]
#[allow(unsafe_op_in_unsafe_fn)]
pub(crate) unsafe fn scalar_mul_basepoint_ifma(scalar_bytes: &[u8; 32]) -> ExtendedPoint {
  use super::point::BASEPOINT_RADIX16_TABLE;

  let digits = scalar::as_radix_16(scalar_bytes);
  let affine_k = hamburg_affine_constants_ifma();
  let mut acc = ExtendedPointIfma::from_extended(&ExtendedPoint::identity());
  let identity = acc.to_cached();

  for (digit, table) in digits.iter().copied().zip(BASEPOINT_RADIX16_TABLE.iter()) {
    let point = select_signed_cached_ifma(table, digit, &affine_k, &identity);
    acc = acc.add_cached(&point);
  }

  acc.to_extended()
}

/// Select one signed fixed-base table digit through the production IFMA selector.
///
/// # Safety
///
/// Caller must ensure AVX2, AVX-512 IFMA, and AVX-512 VL are available.
#[cfg(feature = "diag")]
#[inline]
#[target_feature(enable = "avx2,avx512ifma,avx512vl")]
#[allow(unsafe_op_in_unsafe_fn)]
pub unsafe fn diag_select_basepoint_cached_ifma_limb_digest(digit: i8) -> [u64; 20] {
  use super::point::BASEPOINT_RADIX16_TABLE;

  let affine_k = hamburg_affine_constants_ifma();
  let identity = ExtendedPointIfma::from_extended(&ExtendedPoint::identity()).to_cached();
  let selected = select_signed_cached_ifma(&BASEPOINT_RADIX16_TABLE[0], digit, &affine_k, &identity);
  let fields = selected.0.split();
  let mut out = [0u64; 20];
  for (chunk, field) in out.chunks_exact_mut(5).zip(fields.iter()) {
    chunk.copy_from_slice(field.limbs());
  }
  out
}

// Radix-16 IFMA Straus removed — superseded by straus_wnaf_vartime_ifma.

// ===========================================================================
// wNAF-based Straus verify (IFMA)
// ===========================================================================

/// Build a runtime table of odd multiples `[1P, 3P, 5P, ..., (2n-1)P]` in
/// IFMA Hamburg-scaled cached format.
///
/// For wNAF(w), the table has `2^(w-1)` entries. wNAF(5) → 8 entries,
/// wNAF(8) → 64 entries. Digit `d` indexes as `(|d| - 1) / 2`.
///
/// # Safety
///
/// Caller must ensure AVX-512 IFMA + VL are available.
#[target_feature(enable = "avx2,avx512ifma,avx512vl")]
#[allow(unsafe_op_in_unsafe_fn)]
unsafe fn odd_multiples_ifma<const N: usize>(point: &ExtendedPointIfma) -> [CachedPointIfma; N] {
  let p2 = point.double();
  let p2_cached = p2.to_cached();

  let first = point.to_cached();
  let mut out = [first; N];
  let mut acc = *point;
  for dst in out.iter_mut().skip(1) {
    acc = acc.add_cached(&p2_cached);
    *dst = acc.to_cached();
  }
  out
}

/// Add a signed wNAF digit from an odd-multiples table.
///
/// The digit is odd; the table index is `(|digit| - 1) / 2`. Works for
/// any table size (8 entries for wNAF-5, 64 entries for wNAF-8).
///
/// # Safety
///
/// Caller must ensure AVX-512 IFMA + VL are available.
#[inline]
#[target_feature(enable = "avx2,avx512ifma,avx512vl")]
#[allow(unsafe_op_in_unsafe_fn)]
unsafe fn add_wnaf_digit_ifma(acc: ExtendedPointIfma, table: &[CachedPointIfma], digit: i8) -> ExtendedPointIfma {
  let index = usize::from((digit.unsigned_abs().wrapping_sub(1)) / 2);
  let Some(point) = table.get(index) else {
    return acc;
  };

  if digit > 0 {
    acc.add_cached(point)
  } else {
    acc.add_cached(&point.neg())
  }
}

/// Load a `CachedPointIfma` from the static raw `i64` table.
///
/// # Safety
///
/// Caller must ensure AVX2 is available.
#[inline]
#[target_feature(enable = "avx2")]
#[allow(unsafe_op_in_unsafe_fn)]
unsafe fn load_cached_ifma_raw(entry: &[[i64; 4]; 5]) -> CachedPointIfma {
  CachedPointIfma(FieldElement51x4([
    _mm256_loadu_si256(entry[0].as_ptr().cast()),
    _mm256_loadu_si256(entry[1].as_ptr().cast()),
    _mm256_loadu_si256(entry[2].as_ptr().cast()),
    _mm256_loadu_si256(entry[3].as_ptr().cast()),
    _mm256_loadu_si256(entry[4].as_ptr().cast()),
  ]))
}

/// Add a signed wNAF digit from the static raw basepoint table.
///
/// # Safety
///
/// Caller must ensure AVX-512 IFMA + VL are available.
#[inline]
#[target_feature(enable = "avx2,avx512ifma,avx512vl")]
#[allow(unsafe_op_in_unsafe_fn)]
unsafe fn add_wnaf_digit_ifma_raw(acc: ExtendedPointIfma, table: &[[[i64; 4]; 5]], digit: i8) -> ExtendedPointIfma {
  let index = usize::from((digit.unsigned_abs().wrapping_sub(1)) / 2);
  let Some(entry) = table.get(index) else {
    return acc;
  };
  let point = load_cached_ifma_raw(entry);
  if digit > 0 {
    acc.add_cached(&point)
  } else {
    acc.add_cached(&point.neg())
  }
}

/// wNAF-based Straus/Shamir: `[s]B + [h]A`.
///
/// Uses wNAF(8) for the basepoint scalar `s` (64-entry odd-multiples table,
/// ~28 additions) and wNAF(5) for the public-key scalar `h` (8-entry
/// odd-multiples table, ~43 additions). Total ~71 additions vs ~128 with
/// the radix-16 approach — a ~45% reduction in point additions.
///
/// The loop scans 256 bit positions with 1 double per bit (same 256 total
/// doublings), but additions are much sparser due to the wNAF non-adjacency
/// property.
///
/// Variable-time: branches on scalar digit values. Safe for verification
/// where both `s` and `h` are public (derived from the message/signature).
///
/// # Safety
///
/// Caller must ensure AVX-512 IFMA + VL are available.
#[target_feature(enable = "avx2,avx512ifma,avx512vl")]
#[allow(unsafe_op_in_unsafe_fn)]
#[allow(clippy::indexing_slicing)] // i bounded by top < 256, naf arrays are [i8; 256]
pub(crate) unsafe fn straus_wnaf_vartime_ifma(s: &[u8; 32], h: &[u8; 32], a: &ExtendedPoint) -> ExtendedPoint {
  let s_naf = scalar::non_adjacent_form(s, 8);
  let h_naf = scalar::non_adjacent_form(h, 5);

  // wNAF(8) basepoint table: [B, 3B, 5B, ..., 127B] (64 entries).
  // Static compile-time data in .rodata — zero build cost, zero copy.
  // Entries loaded on demand via _mm256_loadu_si256 (~5 loads per access,
  // only ~28 entries touched per verify due to wNAF sparsity).
  let base_table = &basepoint_table_ifma::BASEPOINT_WNAF8_IFMA_RAW;

  // Build wNAF(5) public-key table: [A, 3A, 5A, ..., 15A] (8 entries).
  let ifma_a = ExtendedPointIfma::from_extended(a);
  let a_table: [CachedPointIfma; 8] = odd_multiples_ifma(&ifma_a);

  // Find the highest non-zero digit position (skip leading zeros).
  let top = s_naf
    .iter()
    .enumerate()
    .rev()
    .find(|&(_, &d)| d != 0)
    .map_or(0, |(i, _)| i)
    .max(
      h_naf
        .iter()
        .enumerate()
        .rev()
        .find(|&(_, &d)| d != 0)
        .map_or(0, |(i, _)| i),
    );

  let mut acc = ExtendedPointIfma::from_extended(&ExtendedPoint::identity());

  // Main loop: 1 double per bit, sparse additions from wNAF digits.
  for i in (0..=top).rev() {
    acc = acc.double();

    if s_naf[i] != 0 {
      acc = add_wnaf_digit_ifma_raw(acc, base_table, s_naf[i]);
    }
    if h_naf[i] != 0 {
      acc = add_wnaf_digit_ifma(acc, &a_table, h_naf[i]);
    }
  }

  acc.to_extended()
}

// Tests

#[cfg(test)]
#[cfg(target_arch = "x86_64")]
mod tests {
  use super::{ExtendedPoint, *};

  fn avx512ifma_available_for_tests() -> bool {
    !cfg!(miri) && std::arch::is_x86_feature_detected!("avx512ifma")
  }

  fn basepoint() -> ExtendedPoint {
    ExtendedPoint::basepoint()
  }

  fn decode_hex_32(hex: &str) -> [u8; 32] {
    let bytes = hex.as_bytes();
    let mut out = [0u8; 32];
    for (dst, chunk) in out.iter_mut().zip(bytes.chunks_exact(2)) {
      *dst = hex_val(chunk[0]) << 4 | hex_val(chunk[1]);
    }
    out
  }

  fn hex_val(b: u8) -> u8 {
    match b {
      b'0'..=b'9' => b - b'0',
      b'a'..=b'f' => b - b'a' + 10,
      _ => panic!("invalid hex"),
    }
  }

  #[test]
  fn pack_unpack_roundtrip() {
    if !std::arch::is_x86_feature_detected!("avx2") {
      return;
    }
    let bp = basepoint();

    // SAFETY: AVX2 availability checked by the runtime guard above.
    unsafe {
      let avx = ExtendedPointAvx2::from_extended(&bp);
      let back = avx.to_extended();

      assert!(
        bp.equals_projective(&back),
        "pack/unpack roundtrip should preserve point"
      );
    }
  }

  #[test]
  fn double_matches_scalar() {
    if !std::arch::is_x86_feature_detected!("avx2") {
      return;
    }
    let bp = basepoint();
    let scalar_doubled = bp.double();

    // SAFETY: AVX2 availability checked by the runtime guard above.
    unsafe {
      let avx = ExtendedPointAvx2::from_extended(&bp);
      let avx_doubled = avx.double();
      let result = avx_doubled.to_extended();

      assert!(
        scalar_doubled.equals_projective(&result),
        "AVX2 double should match scalar double"
      );
    }
  }

  #[test]
  fn double_chain_matches_scalar() {
    if !std::arch::is_x86_feature_detected!("avx2") {
      return;
    }
    let bp = basepoint();
    // 8B = cofactor mul
    let scalar_8b = bp.double().double().double();

    // SAFETY: AVX2 availability checked by the runtime guard above.
    unsafe {
      let avx = ExtendedPointAvx2::from_extended(&bp);
      let avx_8b = avx.double().double().double();
      let result = avx_8b.to_extended();

      assert!(
        scalar_8b.equals_projective(&result),
        "AVX2 triple-double (8B) should match scalar"
      );
    }
  }

  #[test]
  fn add_cached_matches_scalar_add() {
    if !std::arch::is_x86_feature_detected!("avx2") {
      return;
    }
    let bp = basepoint();
    let bp2 = bp.double();
    // Scalar: B + 2B = 3B
    let scalar_3b = bp.add(&bp2);

    // SAFETY: AVX2 availability checked by the runtime guard above.
    unsafe {
      let avx_bp = ExtendedPointAvx2::from_extended(&bp);
      let avx_bp2 = ExtendedPointAvx2::from_extended(&bp2);
      let cached_bp2 = avx_bp2.to_cached();
      let avx_3b = avx_bp.add_cached(&cached_bp2);
      let result = avx_3b.to_extended();

      assert!(
        scalar_3b.equals_projective(&result),
        "AVX2 add should match scalar add (B + 2B = 3B)"
      );
    }
  }

  #[test]
  fn add_cached_neg_is_subtraction() {
    if !std::arch::is_x86_feature_detected!("avx2") {
      return;
    }
    let bp = basepoint();
    let bp2 = bp.double();

    // SAFETY: AVX2 availability checked by the runtime guard above.
    unsafe {
      // 2B + (−B) should equal B
      let avx_bp2 = ExtendedPointAvx2::from_extended(&bp2);
      let avx_bp = ExtendedPointAvx2::from_extended(&bp);
      let cached_bp_neg = avx_bp.to_cached().neg();
      let result = avx_bp2.add_cached(&cached_bp_neg).to_extended();

      assert!(bp.equals_projective(&result), "2B + (−B) should equal B");
    }
  }

  #[test]
  fn add_then_double_matches_scalar() {
    if !std::arch::is_x86_feature_detected!("avx2") {
      return;
    }
    let bp = basepoint();
    // Scalar: 2*(B + B) = 2*(2B) = 4B
    let scalar_4b = bp.add(&bp).double();

    // SAFETY: AVX2 availability checked by the runtime guard above.
    unsafe {
      let avx_bp = ExtendedPointAvx2::from_extended(&bp);
      let cached_bp = avx_bp.to_cached();
      let avx_2b = avx_bp.add_cached(&cached_bp);
      let avx_4b = avx_2b.double();
      let result = avx_4b.to_extended();

      assert!(
        scalar_4b.equals_projective(&result),
        "AVX2 add+double should match scalar (4B)"
      );
    }
  }

  #[test]
  fn identity_addition() {
    if !std::arch::is_x86_feature_detected!("avx2") {
      return;
    }
    let bp = basepoint();
    let identity = ExtendedPoint::identity();

    // SAFETY: AVX2 availability checked by the runtime guard above.
    unsafe {
      let avx_bp = ExtendedPointAvx2::from_extended(&bp);
      let avx_id = ExtendedPointAvx2::from_extended(&identity);
      let cached_id = avx_id.to_cached();
      let result = avx_bp.add_cached(&cached_id).to_extended();

      assert!(bp.equals_projective(&result), "B + identity should equal B");
    }
  }

  #[test]
  fn scalar_mul_vartime_matches_scalar() {
    if !std::arch::is_x86_feature_detected!("avx2") {
      return;
    }
    let bp = basepoint();
    let mut scalar = [0u8; 32];
    scalar[0] = 42;

    let scalar_result = bp.scalar_mul_vartime(&scalar);

    // SAFETY: AVX2 availability checked by the runtime guard above.
    unsafe {
      let avx_result = scalar_mul_vartime_avx2(&bp, &scalar);
      assert!(
        scalar_result.equals_projective(&avx_result),
        "AVX2 vartime scalar mul should match scalar"
      );
    }
  }

  #[test]
  fn scalar_mul_basepoint_matches_scalar() {
    if !std::arch::is_x86_feature_detected!("avx2") {
      return;
    }
    let mut scalar = [0u8; 32];
    scalar[0] = 1;
    let scalar_result = ExtendedPoint::scalar_mul_basepoint(&scalar);

    // SAFETY: AVX2 availability checked by the runtime guard above.
    unsafe {
      let avx_result = scalar_mul_basepoint_avx2(&scalar);
      assert!(
        scalar_result.equals_projective(&avx_result),
        "AVX2 basepoint mul [1]B should match scalar"
      );
    }
  }

  #[cfg(feature = "ed25519")]
  #[test]
  fn scalar_mul_basepoint_rfc8032_vector1() {
    if !std::arch::is_x86_feature_detected!("avx2") {
      return;
    }
    use crate::auth::ed25519::{Ed25519SecretKey, hash::ExpandedSecret};

    let secret = Ed25519SecretKey::from_bytes(decode_hex_32(
      "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60",
    ));
    let expanded = ExpandedSecret::from_secret_key(&secret);
    let expected = decode_hex_32("d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a");

    // SAFETY: AVX2 availability checked by the runtime guard above.
    unsafe {
      let avx_pub = scalar_mul_basepoint_avx2(expanded.scalar_bytes());
      assert_eq!(
        avx_pub.to_bytes(),
        Some(expected),
        "AVX2 basepoint mul should match RFC 8032 vector 1"
      );
    }
  }

  #[cfg(feature = "ed25519")]
  #[test]
  fn straus_matches_scalar() {
    if !std::arch::is_x86_feature_detected!("avx2") {
      return;
    }
    let bp = basepoint();
    let a = bp.double().double(); // 4B as the variable-base point

    let mut s = [0u8; 32];
    s[0] = 7;
    let mut h = [0u8; 32];
    h[0] = 13;

    let scalar_result = super::super::point::straus_wnaf_basepoint_vartime(&s, &h, &a);

    // SAFETY: AVX2 availability checked by the runtime guard above.
    unsafe {
      let avx_result = straus_wnaf_vartime_avx2(&s, &h, &a);
      assert!(
        scalar_result.equals_projective(&avx_result),
        "AVX2 wNAF Straus should match scalar"
      );
    }
  }

  #[cfg(feature = "ed25519")]
  #[test]
  fn straus_matches_scalar_large_scalars() {
    if !std::arch::is_x86_feature_detected!("avx2") {
      return;
    }
    // Reproduce the actual verify path: full 256-bit scalars, realistic point.
    use crate::{
      auth::ed25519::{Ed25519Keypair, Ed25519SecretKey},
      hashes::crypto::Sha512,
      traits::Digest,
    };

    let secret = Ed25519SecretKey::from_bytes([13u8; 32]);
    let keypair = Ed25519Keypair::from_secret_key(secret);
    let public = keypair.public_key();
    let sig = keypair.sign(b"test message for straus");

    // Extract R, s, compute challenge h — same as verify()
    let sig_bytes = sig.as_bytes();
    let mut r_bytes = [0u8; 32];
    let mut s_bytes = [0u8; 32];
    r_bytes.copy_from_slice(&sig_bytes[..32]);
    s_bytes.copy_from_slice(&sig_bytes[32..]);

    let r_point = ExtendedPoint::from_bytes(&r_bytes).unwrap();
    let a_point = ExtendedPoint::from_bytes(public.as_bytes()).unwrap();
    let s_scalar = super::super::scalar::from_canonical_bytes(&s_bytes).unwrap();

    let mut hasher = Sha512::new();
    hasher.update(&r_bytes);
    hasher.update(public.as_bytes());
    hasher.update(b"test message for straus");
    let challenge = super::super::scalar::reduce_bytes_mod_order(&hasher.finalize());
    let neg_challenge = super::super::scalar::negate_mod(&challenge);
    let neg_challenge_bytes = super::super::scalar::to_bytes(&neg_challenge);
    let s_canonical = super::super::scalar::to_bytes(&s_scalar);

    let scalar_result =
      super::super::point::straus_wnaf_basepoint_vartime(&s_canonical, &neg_challenge_bytes, &a_point);

    // SAFETY: AVX2 availability checked by the runtime guard above.
    unsafe {
      let avx_result = straus_wnaf_vartime_avx2(&s_canonical, &neg_challenge_bytes, &a_point);
      assert!(
        scalar_result.equals_projective(&avx_result),
        "AVX2 wNAF Straus with large scalars should match scalar"
      );

      assert!(
        !a_point.is_small_order(),
        "public key used in verify should not be weak"
      );
      assert!(!r_point.is_small_order(), "R used in verify should not be low order");

      assert!(
        avx_result.equals_projective(&r_point),
        "AVX2 wNAF Straus strict verify equation should hold"
      );
    }
  }

  #[test]
  fn double_of_identity_is_identity() {
    if !std::arch::is_x86_feature_detected!("avx2") {
      return;
    }
    let identity = ExtendedPoint::identity();

    // SAFETY: AVX2 availability checked by the runtime guard above.
    unsafe {
      let avx_id = ExtendedPointAvx2::from_extended(&identity);
      let result = avx_id.double().to_extended();

      assert!(
        identity.equals_projective(&result),
        "double(identity) should be identity"
      );
    }
  }

  // IFMA point operation tests

  #[test]
  fn ifma_pack_unpack_roundtrip() {
    if !avx512ifma_available_for_tests() {
      return;
    }
    let bp = basepoint();

    // SAFETY: AVX-512 IFMA availability checked above.
    unsafe {
      let ifma = ExtendedPointIfma::from_extended(&bp);
      let back = ifma.to_extended();
      assert!(bp.equals_projective(&back), "IFMA pack/unpack roundtrip");
    }
  }

  #[test]
  fn ifma_double_matches_scalar() {
    if !avx512ifma_available_for_tests() {
      return;
    }
    let bp = basepoint();
    let scalar_doubled = bp.double();

    // SAFETY: AVX-512 IFMA availability checked above.
    unsafe {
      let ifma = ExtendedPointIfma::from_extended(&bp);
      let result = ifma.double().to_extended();
      assert!(
        scalar_doubled.equals_projective(&result),
        "IFMA double should match scalar"
      );
    }
  }

  #[test]
  fn ifma_double_chain_matches_scalar() {
    if !avx512ifma_available_for_tests() {
      return;
    }
    let bp = basepoint();
    let scalar_8b = bp.double().double().double();

    // SAFETY: AVX-512 IFMA availability checked above.
    unsafe {
      let ifma = ExtendedPointIfma::from_extended(&bp);
      let result = ifma.double().double().double().to_extended();
      assert!(
        scalar_8b.equals_projective(&result),
        "IFMA triple-double (8B) should match scalar"
      );
    }
  }

  #[test]
  fn ifma_add_cached_matches_scalar() {
    if !avx512ifma_available_for_tests() {
      return;
    }
    let bp = basepoint();
    let bp2 = bp.double();
    let scalar_3b = bp.add(&bp2);

    // SAFETY: AVX-512 IFMA availability checked above.
    unsafe {
      let ifma_bp = ExtendedPointIfma::from_extended(&bp);
      let ifma_bp2 = ExtendedPointIfma::from_extended(&bp2);
      let cached = ifma_bp2.to_cached();
      let result = ifma_bp.add_cached(&cached).to_extended();
      assert!(
        scalar_3b.equals_projective(&result),
        "IFMA add should match scalar (B + 2B = 3B)"
      );
    }
  }

  #[test]
  fn ifma_add_then_double_matches_scalar() {
    if !avx512ifma_available_for_tests() {
      return;
    }
    let bp = basepoint();
    let scalar_4b = bp.add(&bp).double();

    // SAFETY: AVX-512 IFMA availability checked above.
    unsafe {
      let ifma_bp = ExtendedPointIfma::from_extended(&bp);
      let cached = ifma_bp.to_cached();
      let ifma_2b = ifma_bp.add_cached(&cached);
      let result = ifma_2b.double().to_extended();
      assert!(
        scalar_4b.equals_projective(&result),
        "IFMA add+double should match scalar (4B)"
      );
    }
  }

  #[test]
  fn ifma_neg_cached_is_subtraction() {
    if !avx512ifma_available_for_tests() {
      return;
    }
    let bp = basepoint();
    let bp2 = bp.double();

    // SAFETY: AVX-512 IFMA availability checked above.
    unsafe {
      let ifma_bp2 = ExtendedPointIfma::from_extended(&bp2);
      let ifma_bp = ExtendedPointIfma::from_extended(&bp);
      let neg_cached = ifma_bp.to_cached().neg();
      let result = ifma_bp2.add_cached(&neg_cached).to_extended();
      assert!(bp.equals_projective(&result), "IFMA 2B + (-B) should equal B");
    }
  }

  #[test]
  fn ifma_scalar_mul_vartime_matches_scalar() {
    if !avx512ifma_available_for_tests() {
      return;
    }
    let bp = basepoint();
    let mut scalar = [0u8; 32];
    scalar[0] = 42;
    let scalar_result = bp.scalar_mul_vartime(&scalar);

    // SAFETY: AVX-512 IFMA availability checked above.
    unsafe {
      let result = scalar_mul_vartime_ifma(&bp, &scalar);
      assert!(
        scalar_result.equals_projective(&result),
        "IFMA vartime scalar mul should match scalar"
      );
    }
  }

  #[test]
  fn ifma_scalar_mul_basepoint_matches_scalar() {
    if !avx512ifma_available_for_tests() {
      return;
    }
    let mut scalar = [0u8; 32];
    scalar[0] = 1;
    let scalar_result = ExtendedPoint::scalar_mul_basepoint(&scalar);

    // SAFETY: AVX-512 IFMA availability checked above.
    unsafe {
      let result = scalar_mul_basepoint_ifma(&scalar);
      assert!(
        scalar_result.equals_projective(&result),
        "IFMA basepoint mul [1]B should match scalar"
      );
    }
  }
}
