///|
let app_main : Ref[(async () -> Unit)?] = Ref(None)

///|
priv enum AppRunnerLifecycleState {
  RunnerIdle = 0
  RunnerInstalled = 1
  RunnerRunning = 2
  RunnerStopping = 3
} derive(Eq)

///|
let app_runner_lifecycle : Ref[AppRunnerLifecycleState] = Ref(RunnerIdle)

///|
fn app_runner_is_active() -> Bool {
  app_runner_lifecycle.val != RunnerIdle
}

///|
fn app_thread_entry() -> Unit {
  match (app_runner_lifecycle.val, app_main.val) {
    (RunnerInstalled, Some(main)) => {
      app_runner_lifecycle.val = RunnerRunning
      @async.run_async_main(main)
      app_runner_lifecycle.val = RunnerStopping
    }
    _ => abort("Proton application entry is not installed")
  }
}

///|
/// Runs a Proton application with the platform UI loop on the process main
/// thread and MoonBit async work on Proton's application thread.
///
/// Call this once from a synchronous `main`.
pub fn run(main : async () -> Unit) -> Unit {
  guard app_runner_lifecycle.val == RunnerIdle && app_main.val is None else {
    abort("Proton application runner is already active")
  }
  app_main.val = Some(main)
  app_runner_lifecycle.val = RunnerInstalled
  let entry : FuncRef[() -> Unit] = fn() { app_thread_entry() }
  @native.run_app(entry) catch {
    error => {
      app_runner_lifecycle.val = RunnerIdle
      app_main.val = None
      abort(
        "run Proton application failed: native error " +
        error.status().to_string() +
        ": " +
        error.message(),
      )
    }
  }
  app_runner_lifecycle.val = RunnerIdle
  app_main.val = None
}