// XXH64 — a faithful MoonBit port of the reference C (Cyan4973/xxHash).
//
// Unlike rapidhash/wyhash/xxh3, XXH64 never needs the high 64 bits of a
// 64×64 product: every multiply is 64×64 → low 64 (a single `mul`), so it maps
// directly onto MoonBit's `UInt64::*` with no `umulh` / `__int128` dependency
// and no portable-schoolbook penalty. 64-bit output, little-endian reads.
// Validated against reference-generated known-answer vectors (see the tests).

///|
const P1 : UInt64 = 0x9E3779B185EBCA87UL

///|
const P2 : UInt64 = 0xC2B2AE3D27D4EB4FUL

///|
const P3 : UInt64 = 0x165667B19E3779F9UL

///|
const P4 : UInt64 = 0x85EBCA77C2B2AE63UL

///|
const P5 : UInt64 = 0x27D4EB2F165667C5UL

///|
/// Rotate-left by `r` (1..63).
fn rotl(x : UInt64, r : Int) -> UInt64 {
  (x << r) | (x >> (64 - r))
}

///|
/// `XXH64_round`: fold one 64-bit lane into an accumulator.
fn round(acc : UInt64, input : UInt64) -> UInt64 {
  rotl(acc + input * P2, 31) * P1
}

///|
/// `XXH64_mergeRound`: combine a finished lane into the running hash.
fn merge_round(acc : UInt64, val : UInt64) -> UInt64 {
  (acc ^ round(0, val)) * P1 + P4
}

///|
/// `XXH64_avalanche`: final bit-mixing.
fn avalanche(h : UInt64) -> UInt64 {
  let h = h ^ (h >> 33)
  let h = h * P2
  let h = h ^ (h >> 29)
  let h = h * P3
  h ^ (h >> 32)
}

///|
fn read64(data : Bytes, p : Int) -> UInt64 {
  data.unsafe_read_uint64_le(p)
}

///|
fn read32(data : Bytes, p : Int) -> UInt64 {
  data.unsafe_read_uint32_le(p).to_uint64()
}

///|
/// XXH64 of `data` with an optional `seed` (default 0). 64-bit output.
///
/// ```mbt nocheck
/// inspect(@xxh64.to_hex(@xxh64.xxh64(b"abc")), content="44bc2cf5ad770999")
/// ```
pub fn xxh64(data : Bytes, seed? : UInt64 = 0) -> UInt64 {
  let len = data.length()
  let mut p = 0
  let mut h = if len >= 32 {
    // Four parallel accumulator lanes over 32-byte stripes.
    let mut a1 = seed + P1 + P2
    let mut a2 = seed + P2
    let mut a3 = seed
    let mut a4 = seed - P1
    let limit = len - 32
    while p <= limit {
      a1 = round(a1, read64(data, p))
      a2 = round(a2, read64(data, p + 8))
      a3 = round(a3, read64(data, p + 16))
      a4 = round(a4, read64(data, p + 24))
      p = p + 32
    }
    let acc = rotl(a1, 1) + rotl(a2, 7) + rotl(a3, 12) + rotl(a4, 18)
    merge_round(merge_round(merge_round(merge_round(acc, a1), a2), a3), a4)
  } else {
    seed + P5
  }
  h = h + len.to_uint64()
  // Finalize the trailing 0..31 bytes: 8-byte, then 4-byte, then 1-byte chunks.
  let mut rem = len - p
  while rem >= 8 {
    h = h ^ round(0, read64(data, p))
    h = rotl(h, 27) * P1 + P4
    p = p + 8
    rem = rem - 8
  }
  if rem >= 4 {
    h = h ^ (read32(data, p) * P1)
    h = rotl(h, 23) * P2 + P3
    p = p + 4
    rem = rem - 4
  }
  while rem > 0 {
    h = h ^ (data.unsafe_get(p).to_uint64() * P5)
    h = rotl(h, 11) * P1
    p = p + 1
    rem = rem - 1
  }
  avalanche(h)
}

///|
/// `xxh64` formatted as 16 lowercase hex chars.
///
/// ```mbt nocheck
/// inspect(@xxh64.to_hex(@xxh64.xxh64(b"abc")), content="44bc2cf5ad770999")
/// ```
pub fn to_hex(hash : UInt64) -> String {
  let hexchars = "0123456789abcdef".to_array()
  let buf = StringBuilder::new()
  for k in 0..<16 {
    buf.write_char(hexchars[((hash >> ((15 - k) * 4)) & 0xF).to_int()])
  }
  buf.to_string()
}