// Private decision core for timer and interval dispatch. Queue inspection,
// mutation, cancellation lookup, and callback execution stay in the shell.

///|
const TIMER_SAFETY_LIMIT = 10000

///|
priv enum TimerQueueReadiness {
  TimerQueueEmpty
  TimerQueueSafetyLimit
  TimerQueueObservationRequired
}

///|
fn[T] invalid_timer_queue_policy_transition(operation : String) -> T raise {
  fail("invalid timer queue policy transition: " + operation)
}

///|
priv enum TimerQueueCorePhase {
  TimerQueueCoreReady
  TimerQueueCoreCommitRequired
  TimerQueueCoreSelected(TimerTask)
  TimerQueueCoreCallbackSucceeded(TimerTask)
  TimerQueueCoreFailed
  TimerQueueCoreCompleted
}

///|
priv enum TimerQueueCommitDecision {
  TimerQueueDiscardCommitted
  TimerQueueRunCommitted(TimerTask)
}

///|
priv enum TimerQueueCheckpointDecision {
  TimerQueueDoNotRequeue
  TimerQueueRequeue(TimerTask)
}

///|
priv struct TimerQueuePolicyCore {
  safety_limit : Int
  iterations : Int
  phase : TimerQueueCorePhase
}

///|
fn TimerQueuePolicyCore::TimerQueuePolicyCore(
  safety_limit : Int,
) -> TimerQueuePolicyCore {
  { safety_limit, iterations: 0, phase: TimerQueueCoreReady }
}

///|
fn TimerQueuePolicyCore::readiness(
  self : TimerQueuePolicyCore,
  queue_is_empty : Bool,
) -> TimerQueueReadiness raise {
  guard self.phase is TimerQueueCoreReady else {
    invalid_timer_queue_policy_transition("readiness requires Ready")
  }
  if queue_is_empty {
    TimerQueueEmpty
  } else if self.iterations >= self.safety_limit {
    TimerQueueSafetyLimit
  } else {
    TimerQueueObservationRequired
  }
}

///|
fn TimerQueuePolicyCore::accept_dispatch_observation(
  self : TimerQueuePolicyCore,
  queue_is_empty : Bool,
) -> TimerQueuePolicyCore raise {
  match self.readiness(queue_is_empty) {
    TimerQueueObservationRequired =>
      { ..self, phase: TimerQueueCoreCommitRequired }
    TimerQueueEmpty | TimerQueueSafetyLimit =>
      invalid_timer_queue_policy_transition(
        "accept_dispatch_observation requires ObservationRequired",
      )
  }
}

///|
fn TimerQueuePolicyCore::reject_dispatch_observation(
  self : TimerQueuePolicyCore,
  queue_is_empty : Bool,
) -> TimerQueuePolicyCore raise {
  match self.readiness(queue_is_empty) {
    TimerQueueObservationRequired => self
    TimerQueueEmpty | TimerQueueSafetyLimit =>
      invalid_timer_queue_policy_transition(
        "reject_dispatch_observation requires ObservationRequired",
      )
  }
}

///|
fn TimerQueuePolicyCore::commit_popped(
  self : TimerQueuePolicyCore,
  timer : TimerTask,
  cancelled : Bool,
) -> (TimerQueuePolicyCore, TimerQueueCommitDecision) raise {
  guard self.phase is TimerQueueCoreCommitRequired else {
    invalid_timer_queue_policy_transition(
      "commit_popped requires CommitRequired",
    )
  }
  let iterations = self.iterations + 1
  if cancelled {
    (
      { ..self, iterations, phase: TimerQueueCoreReady },
      TimerQueueDiscardCommitted,
    )
  } else {
    (
      { ..self, iterations, phase: TimerQueueCoreSelected(timer) },
      TimerQueueRunCommitted(timer),
    )
  }
}

///|
fn TimerQueuePolicyCore::callback_succeeded(
  self : TimerQueuePolicyCore,
) -> TimerQueuePolicyCore raise {
  match self.phase {
    TimerQueueCoreSelected(timer) =>
      { ..self, phase: TimerQueueCoreCallbackSucceeded(timer) }
    TimerQueueCoreReady
    | TimerQueueCoreCommitRequired
    | TimerQueueCoreCallbackSucceeded(_)
    | TimerQueueCoreFailed
    | TimerQueueCoreCompleted =>
      invalid_timer_queue_policy_transition(
        "callback_succeeded requires Selected",
      )
  }
}

///|
fn TimerQueuePolicyCore::callback_failed(
  self : TimerQueuePolicyCore,
) -> TimerQueuePolicyCore raise {
  match self.phase {
    TimerQueueCoreSelected(_) => { ..self, phase: TimerQueueCoreFailed }
    TimerQueueCoreReady
    | TimerQueueCoreCommitRequired
    | TimerQueueCoreCallbackSucceeded(_)
    | TimerQueueCoreFailed
    | TimerQueueCoreCompleted =>
      invalid_timer_queue_policy_transition("callback_failed requires Selected")
  }
}

///|
fn TimerQueuePolicyCore::checkpoint_succeeded(
  self : TimerQueuePolicyCore,
  cancelled : Bool,
  insertion_order : Int,
) -> (TimerQueuePolicyCore, TimerQueueCheckpointDecision) raise {
  let timer = match self.phase {
    TimerQueueCoreCallbackSucceeded(timer) => timer
    TimerQueueCoreReady
    | TimerQueueCoreCommitRequired
    | TimerQueueCoreSelected(_)
    | TimerQueueCoreFailed
    | TimerQueueCoreCompleted =>
      invalid_timer_queue_policy_transition(
        "checkpoint_succeeded requires CallbackSucceeded",
      )
  }
  let decision = if timer.is_interval && !cancelled {
    TimerQueueRequeue({
      id: timer.id,
      callback: timer.callback,
      args: timer.args,
      delay: timer.delay + timer.period,
      period: timer.period,
      is_interval: true,
      insertion_order,
    })
  } else {
    TimerQueueDoNotRequeue
  }
  ({ ..self, phase: TimerQueueCoreReady }, decision)
}

///|
fn TimerQueuePolicyCore::checkpoint_failed(
  self : TimerQueuePolicyCore,
) -> TimerQueuePolicyCore raise {
  match self.phase {
    TimerQueueCoreCallbackSucceeded(_) =>
      { ..self, phase: TimerQueueCoreFailed }
    TimerQueueCoreReady
    | TimerQueueCoreCommitRequired
    | TimerQueueCoreSelected(_)
    | TimerQueueCoreFailed
    | TimerQueueCoreCompleted =>
      invalid_timer_queue_policy_transition(
        "checkpoint_failed requires CallbackSucceeded",
      )
  }
}

///|
fn TimerQueuePolicyCore::complete_run(
  self : TimerQueuePolicyCore,
  queue_is_empty : Bool,
) -> (TimerQueuePolicyCore, Bool) raise {
  guard self.phase is TimerQueueCoreReady else {
    invalid_timer_queue_policy_transition("complete_run requires Ready")
  }
  ({ ..self, phase: TimerQueueCoreCompleted }, queue_is_empty)
}