///|
priv enum NativeFiberProtocolFailure {
  FiberWrongThread
  FiberInvalidTransition(NativeFiberPhase)
  FiberInvalidState(Int)
  FiberUnexpectedYield(Int64)
  FiberNativeFailure(Int)
}

///|
// Debug is consumed only by the white-box protocol snapshot.
#warnings("-unused_value")
priv enum NativeFiberAdvanceCode {
  FiberDidSuspend
  FiberDidReturn
  FiberAdvanceWrongThread
  FiberAdvanceInvalidTransition
  FiberAdvanceInvalidState
  FiberAdvanceNativeFailure(Int)
} derive(Debug)

///|
priv enum NativeFiberAdvanceResult {
  FiberSuspended(Int64)
  FiberReturned(Int64)
  FiberAdvanceFailed(NativeFiberProtocolFailure)
}

///|
#warnings("-unused_value")
priv enum NativeFiberCancelCode {
  FiberDidCancel
  FiberCancelWrongThread
  FiberCancelInvalidTransition
  FiberCancelNativeFailure(Int)
} derive(Debug)

///|
priv enum NativeJITAdvanceResult {
  JITHostcallSuspended
  JITReturned(Int)
  JITAdvanceFailed(NativeFiberProtocolFailure)
}

///|
fn decode_native_fiber_state_code(code : Int) -> Result[NativeFiberPhase, Int] {
  match code {
    0 => Ok(Ready)
    1 => Ok(Running)
    2 => Ok(Suspended)
    3 => Ok(Returned)
    4 => Ok(Cancelled)
    code => Err(code)
  }
}

///|
fn decode_native_fiber_advance_code(
  status_code : Int,
) -> NativeFiberAdvanceCode {
  match status_code {
    0 => FiberDidSuspend
    1 => FiberDidReturn
    -2 => FiberAdvanceWrongThread
    -3 => FiberAdvanceInvalidTransition
    -4 => FiberAdvanceInvalidState
    code => FiberAdvanceNativeFailure(code)
  }
}

///|
fn decode_native_fiber_cancel_code(status_code : Int) -> NativeFiberCancelCode {
  match status_code {
    0 => FiberDidCancel
    -2 => FiberCancelWrongThread
    -3 => FiberCancelInvalidTransition
    code => FiberCancelNativeFailure(code)
  }
}

///|
fn native_fiber_invalid_transition(
  raw : NativeFiberObject,
) -> NativeFiberProtocolFailure {
  match native_fiber_state(raw) {
    Ok(phase) => FiberInvalidTransition(phase)
    Err(code) => FiberInvalidState(code)
  }
}

///|
fn native_fiber_state(raw : NativeFiberObject) -> Result[NativeFiberPhase, Int] {
  decode_native_fiber_state_code(c_native_fiber_state_code(raw))
}

///|
fn native_fiber_is_allocated(raw : NativeFiberObject) -> Bool {
  native_fiber_state(raw) is Ok(_)
}

///|
fn native_fiber_phase(raw : NativeFiberObject) -> NativeFiberPhase {
  match native_fiber_state(raw) {
    Ok(phase) => phase
    Err(code) => abort("invalid native fiber state code: \{code}")
  }
}

///|
fn native_fiber_advance(
  raw : NativeFiberObject,
  resume_value : Int64,
) -> NativeFiberAdvanceResult {
  let status_code = c_native_fiber_continue_code(raw, resume_value)
  match decode_native_fiber_advance_code(status_code) {
    FiberDidSuspend => FiberSuspended(c_native_fiber_yielded_value(raw))
    FiberDidReturn => FiberReturned(c_native_fiber_return_value(raw))
    FiberAdvanceWrongThread => FiberAdvanceFailed(FiberWrongThread)
    FiberAdvanceInvalidTransition =>
      FiberAdvanceFailed(native_fiber_invalid_transition(raw))
    FiberAdvanceInvalidState =>
      FiberAdvanceFailed(FiberInvalidState(c_native_fiber_state_code(raw)))
    FiberAdvanceNativeFailure(code) =>
      FiberAdvanceFailed(FiberNativeFailure(code))
  }
}

///|
fn native_jit_continuation_advance(
  raw : NativeFiberObject,
  resume_value : Int64,
) -> NativeJITAdvanceResult {
  match native_fiber_advance(raw, resume_value) {
    FiberSuspended(event) =>
      if event == c_native_hostcall_suspend_event() {
        JITHostcallSuspended
      } else {
        JITAdvanceFailed(FiberUnexpectedYield(event))
      }
    FiberReturned(value) => JITReturned(value.to_int())
    FiberAdvanceFailed(failure) => JITAdvanceFailed(failure)
  }
}

///|
fn native_fiber_cancel(raw : NativeFiberObject) -> NativeFiberProtocolFailure? {
  let status_code = c_native_fiber_cancel_code(raw)
  match decode_native_fiber_cancel_code(status_code) {
    FiberDidCancel => None
    FiberCancelWrongThread => Some(FiberWrongThread)
    FiberCancelInvalidTransition => Some(native_fiber_invalid_transition(raw))
    FiberCancelNativeFailure(code) => Some(FiberNativeFailure(code))
  }
}

///|
fn NativeFiberProtocolFailure::to_error(
  self : NativeFiberProtocolFailure,
) -> NativeFiberError {
  match self {
    FiberWrongThread => WrongThread
    FiberInvalidTransition(phase) => InvalidTransition(phase)
    FiberInvalidState(code) => InvalidState(code)
    FiberUnexpectedYield(event) => UnexpectedYield(event)
    FiberNativeFailure(code) => NativeFailure(code)
  }
}