///|
/// JavaScript Atomics API
///
/// The Atomics namespace object contains static methods for carrying out atomic operations.
/// They are used with SharedArrayBuffer and ArrayBuffer objects.
///
/// MDN Reference: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics

///| Core Types

///|
/// JavaScript Atomics namespace object
#external
pub type Atomics

///|
/// Wait result returned by wait()
pub enum AtomicsWaitResult {
  Ok
  NotEqual
  TimedOut
} derive(Eq)

///|
pub impl Show for AtomicsWaitResult with fn output(self, logger) {
  logger.write_string(
    match self {
      Ok => "Ok"
      NotEqual => "NotEqual"
      TimedOut => "TimedOut"
    },
  )
}

///| Atomic Operations

///|
/// Atomically adds a value to the value at the given position in the array.
/// Returns the old value at that index.
///
/// # Example
///
/// ```moonbit no-check
/// let sab = SharedArrayBuffer::new(1024)
/// let ta = Uint8Array::new(sab)
/// Atomics::add(ta, 0, 12)  // returns 0 (the old value)
/// Atomics::load(ta, 0)     // 12 (the new value)
/// ```
///
/// MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics/add
pub fn Atomics::add(typedArray : @core.Any, index : Int, value : Int) -> Int {
  ffi_atomics_add(typedArray, index, value).cast()
}

///|
/// Atomically computes a bitwise AND with a value at the given position.
/// Returns the old value at that index.
///
/// MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics/and
pub fn Atomics::and_(typedArray : @core.Any, index : Int, value : Int) -> Int {
  ffi_atomics_and(typedArray, index, value).cast()
}

///|
/// Atomically stores a value at the given position, if it equals the expected value.
/// Returns the old value.
///
/// # Example
///
/// ```moonbit no-check
/// let ta = Int32Array::new(sab)
/// Atomics::store(ta, 0, 5)
/// Atomics::compareExchange(ta, 0, 5, 12)  // returns 5
/// Atomics::load(ta, 0)                     // 12
/// ```
///
/// MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics/compareExchange
pub fn Atomics::compareExchange(
  typedArray : @core.Any,
  index : Int,
  expectedValue : Int,
  replacementValue : Int,
) -> Int {
  ffi_atomics_compare_exchange(
    typedArray, index, expectedValue, replacementValue,
  ).cast()
}

///|
/// Atomically stores a value at the given position in the array.
/// Returns the old value.
///
/// MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics/exchange
pub fn Atomics::exchange(
  typedArray : @core.Any,
  index : Int,
  value : Int,
) -> Int {
  ffi_atomics_exchange(typedArray, index, value).cast()
}

///|
/// Returns true if an atomic operation on arrays of the given element size
/// will be implemented using a hardware atomic operation (as opposed to a lock).
///
/// # Example
///
/// ```moonbit no-check
/// Atomics::isLockFree(1)  // true
/// Atomics::isLockFree(2)  // true
/// Atomics::isLockFree(4)  // true
/// Atomics::isLockFree(8)  // true (on 64-bit systems)
/// ```
///
/// MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics/isLockFree
pub fn Atomics::isLockFree(size : Int) -> Bool {
  ffi_atomics_is_lock_free(size)
}

///|
/// Atomically returns the value at the given position in the array.
///
/// MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics/load
pub fn Atomics::load(typedArray : @core.Any, index : Int) -> Int {
  ffi_atomics_load(typedArray, index).cast()
}

///|
/// Notifies agents waiting on the given index of the array.
/// Returns the number of agents that were notified.
///
/// Note: Also known as Atomics.wake() in older specifications.
///
/// # Example
///
/// ```moonbit no-check
/// Atomics::notify(ia, 0, count=1)  // Notify 1 agent waiting on index 0
/// ```
///
/// MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics/notify
pub fn Atomics::notify(
  typedArray : @core.Any,
  index : Int,
  count? : Int,
) -> Int {
  match count {
    Some(c) => ffi_atomics_notify(typedArray, index, c).cast()
    None => ffi_atomics_notify_2(typedArray, index).cast()
  }
}

///|
/// Atomically computes a bitwise OR with a value at the given position.
/// Returns the old value at that index.
///
/// MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics/or
pub fn Atomics::or_(typedArray : @core.Any, index : Int, value : Int) -> Int {
  ffi_atomics_or(typedArray, index, value).cast()
}

///|
/// Atomically stores a value at the given position in the array.
/// Returns the value that was stored.
///
/// MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics/store
pub fn Atomics::store(typedArray : @core.Any, index : Int, value : Int) -> Int {
  ffi_atomics_store(typedArray, index, value).cast()
}

///|
/// Atomically subtracts a value from the value at the given position.
/// Returns the old value at that index.
///
/// MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics/sub
pub fn Atomics::sub(typedArray : @core.Any, index : Int, value : Int) -> Int {
  ffi_atomics_sub(typedArray, index, value).cast()
}

///|
/// Verifies that the given position in the array still contains a value
/// and sleeps awaiting or times out.
/// Returns Ok, NotEqual, or TimedOut.
///
/// Note: This operation is only allowed in workers, not on the main thread.
///
/// # Example
///
/// ```moonbit no-check
/// let result = Atomics::wait(ia, 0, 0, timeout=1000)
/// match result {
///   Ok => println("Woken up")
///   NotEqual => println("Value changed")
///   TimedOut => println("Timed out")
/// }
/// ```
///
/// MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics/wait
pub fn Atomics::wait(
  typedArray : @core.Any,
  index : Int,
  value : Int,
  timeout? : Int,
) -> AtomicsWaitResult {
  let result = match timeout {
    Some(t) => ffi_atomics_wait(typedArray, index, value, t)
    None => ffi_atomics_wait_3(typedArray, index, value)
  }
  let result_str : String = @core.identity(result)
  match result_str {
    "ok" => AtomicsWaitResult::Ok
    "not-equal" => AtomicsWaitResult::NotEqual
    "timed-out" => AtomicsWaitResult::TimedOut
    _ => AtomicsWaitResult::Ok // fallback
  }
}

///|
/// Atomically computes a bitwise XOR with a value at the given position.
/// Returns the old value at that index.
///
/// MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics/xor
pub fn Atomics::xor(typedArray : @core.Any, index : Int, value : Int) -> Int {
  ffi_atomics_xor(typedArray, index, value).cast()
}

///| FFI Bindings

///|
extern "js" fn ffi_atomics_add(
  typedArray : @core.Any,
  index : Int,
  value : Int,
) -> @core.Any =
  #| (typedArray, index, value) => Atomics.add(typedArray, index, value)

///|
extern "js" fn ffi_atomics_and(
  typedArray : @core.Any,
  index : Int,
  value : Int,
) -> @core.Any =
  #| (typedArray, index, value) => Atomics.and(typedArray, index, value)

///|
extern "js" fn ffi_atomics_compare_exchange(
  typedArray : @core.Any,
  index : Int,
  expectedValue : Int,
  replacementValue : Int,
) -> @core.Any =
  #| (typedArray, index, expectedValue, replacementValue) => Atomics.compareExchange(typedArray, index, expectedValue, replacementValue)

///|
extern "js" fn ffi_atomics_exchange(
  typedArray : @core.Any,
  index : Int,
  value : Int,
) -> @core.Any =
  #| (typedArray, index, value) => Atomics.exchange(typedArray, index, value)

///|
extern "js" fn ffi_atomics_is_lock_free(size : Int) -> Bool =
  #| (size) => Atomics.isLockFree(size)

///|
extern "js" fn ffi_atomics_load(
  typedArray : @core.Any,
  index : Int,
) -> @core.Any =
  #| (typedArray, index) => Atomics.load(typedArray, index)

///|
extern "js" fn ffi_atomics_notify(
  typedArray : @core.Any,
  index : Int,
  count : Int,
) -> @core.Any =
  #| (typedArray, index, count) => Atomics.notify(typedArray, index, count)

///|
extern "js" fn ffi_atomics_notify_2(
  typedArray : @core.Any,
  index : Int,
) -> @core.Any =
  #| (typedArray, index) => Atomics.notify(typedArray, index)

///|
extern "js" fn ffi_atomics_or(
  typedArray : @core.Any,
  index : Int,
  value : Int,
) -> @core.Any =
  #| (typedArray, index, value) => Atomics.or(typedArray, index, value)

///|
extern "js" fn ffi_atomics_store(
  typedArray : @core.Any,
  index : Int,
  value : Int,
) -> @core.Any =
  #| (typedArray, index, value) => Atomics.store(typedArray, index, value)

///|
extern "js" fn ffi_atomics_sub(
  typedArray : @core.Any,
  index : Int,
  value : Int,
) -> @core.Any =
  #| (typedArray, index, value) => Atomics.sub(typedArray, index, value)

///|
extern "js" fn ffi_atomics_wait(
  typedArray : @core.Any,
  index : Int,
  value : Int,
  timeout : Int,
) -> @core.Any =
  #| (typedArray, index, value, timeout) => Atomics.wait(typedArray, index, value, timeout)

///|
extern "js" fn ffi_atomics_wait_3(
  typedArray : @core.Any,
  index : Int,
  value : Int,
) -> @core.Any =
  #| (typedArray, index, value) => Atomics.wait(typedArray, index, value)

///|
extern "js" fn ffi_atomics_xor(
  typedArray : @core.Any,
  index : Int,
  value : Int,
) -> @core.Any =
  #| (typedArray, index, value) => Atomics.xor(typedArray, index, value)