// Deterministic state machine for the exact admitted Array.prototype.map
// callback slice. Runtime effects are acknowledged through MapEvent; this
// core owns no evaluator closure or host-language callback.

///|
priv suberror InvalidMapTransition {
  InvalidMapTransition(String)
}

///|
#warnings("-unused_constructor")
priv enum MapPhase {
  MapReady
  MapAwaitingHasProperty
  MapAwaitingGet
  MapAwaitingCallback
  MapAwaitingDefine
  MapCompleted
  MapFailed
}

///|
priv struct MapState {
  length : Int64
  index : Int64
  receiver : Value
  result : Value
  callback : Value
  this_arg : Value
  pending_result_consumer : DispatchContinuation
  phase : MapPhase
}

///|
#warnings("-unused_value")
fn MapState::MapState(
  length~ : Int64,
  index? : Int64 = 0L,
  receiver~ : Value,
  result~ : Value,
  callback~ : Value,
  this_arg~ : Value,
  pending_result_consumer~ : DispatchContinuation,
  phase? : MapPhase = MapReady,
) -> MapState {
  {
    length,
    index,
    receiver,
    result,
    callback,
    this_arg,
    pending_result_consumer,
    phase,
  }
}

///|
#warnings("-unused_constructor")
priv enum MapEvent {
  MapStart
  MapHasProperty(Bool)
  MapGotValue(Value)
  MapCallbackReturned(Value)
  MapPropertyDefined
  MapAbrupt(Error)
}

///|
#warnings("-unused_constructor-unused_field")
priv enum MapDecision {
  MapRequestHasProperty(Int64)
  MapRequestGet(Int64)
  MapRequestCallback(Value, Value, Int64, Value, Value)
  MapRequestDefine(Int64, Value)
  MapComplete(Value, DispatchContinuation)
  MapAbort(Error)
}

///|
fn[T] invalid_map_transition(message : String) -> T raise InvalidMapTransition {
  raise InvalidMapTransition(message)
}

///|
fn map_state_is_terminal(state : MapState) -> Bool {
  match state.phase {
    MapCompleted | MapFailed => true
    _ => false
  }
}

///|
fn map_next_decision(
  state : MapState,
) -> (MapState, MapDecision) raise InvalidMapTransition {
  guard state.phase is MapReady else {
    invalid_map_transition("map next decision requires Ready")
  }
  if state.index >= state.length {
    let completed = { ..state, phase: MapCompleted }
    (
      completed,
      MapDecision::MapComplete(state.result, state.pending_result_consumer),
    )
  } else {
    let awaiting = { ..state, phase: MapAwaitingHasProperty }
    (awaiting, MapDecision::MapRequestHasProperty(state.index))
  }
}

///|
#warnings("-unused_value")
fn reduce_array_map(
  state : MapState,
  event : MapEvent,
) -> (MapState, MapDecision) raise InvalidMapTransition {
  guard !map_state_is_terminal(state) else {
    invalid_map_transition("map received an event after terminal completion")
  }
  match (state.phase, event) {
    (MapReady, MapStart) => map_next_decision(state)
    (MapAwaitingHasProperty, MapHasProperty(true)) =>
      (
        { ..state, phase: MapAwaitingGet },
        MapDecision::MapRequestGet(state.index),
      )
    (MapAwaitingHasProperty, MapHasProperty(false)) => {
      let next = { ..state, index: state.index + 1L, phase: MapReady }
      map_next_decision(next)
    }
    (MapAwaitingGet, MapGotValue(value)) =>
      (
        { ..state, phase: MapAwaitingCallback },
        MapDecision::MapRequestCallback(
          state.callback,
          state.this_arg,
          state.index,
          state.receiver,
          value,
        ),
      )
    (MapAwaitingCallback, MapCallbackReturned(value)) =>
      (
        { ..state, phase: MapAwaitingDefine },
        MapDecision::MapRequestDefine(state.index, value),
      )
    (MapAwaitingDefine, MapPropertyDefined) => {
      let next = { ..state, index: state.index + 1L, phase: MapReady }
      map_next_decision(next)
    }
    (_, MapAbrupt(error)) =>
      ({ ..state, phase: MapFailed }, MapDecision::MapAbort(error))
    _ => invalid_map_transition("map event arrived outside its semantic order")
  }
}