// Copyright 2025 International Digital Economy Academy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

///|
/// `moonbitlang/async` need an event loop to perform asynchronous IO.
/// However, some programs, such as GUI programs, have their own event loop,
/// and that event loop sometimes must be run in the main thread.
/// In this case, `moonbitlang/async` supports running its event loop
/// in a dedicated thread internally and integrate with the user-provided loop.
///
/// `ExternalEventLoop` is an abstraction for such user-provided event loop.
/// An implementation of `ExternalEventLoop` represent an event loop instance.
/// The required methods for the user-provided event loop are:
///
/// - `evloop.poll(timeout?)`: the main wait function of the user-provided loop.
///   `poll` should block the program until any interested event occur or timeout expired.
///   After waking up, `poll` should process main loop specific events and return,
///   `moonbitlang/async` will handle its own events and invoke `poll` again for the next loop.
///
///   When handling main loop specific events, `poll` SHOULD NOT perform heavy work directly.
///   Instead, it is recommended to send the event back to `async` code via `@async.CondVar`
///   or `@async.Queue`. This way, main loop events can integrate with `moonbitlang/async`
///   code better.
///
///   `poll` must support three timeout modes:
///
///   + if `timeout` is `Some(0)`, `poll` MUST NOT enter waiting state.
///     Instead, `poll` should simply check for immediately available events
///     and return immediately
///
///   + otherwise, if `timeout` is `Some(t)` (in this case it is guaranteed that `t > 0`),
///     `poll` should wait for no more than `t` milliseconds.
///     If some event occur before `t`, `poll` should return immediately.
///     Slight delay is allowed on timeout, but the delay will affect timer precision
///     of `moonbitlang/async`
///
///   + if `timeout` is `None`, `poll` should wait indefinitely for events
///
/// - `evloop.get_wakeup_callback_for_foreign_thread()` should return a callback function
///   for waking up the user-provided loop. `moonbitlang/async` will use this callback
///   to wake up the main loop when `moonbitlang/async` specific events occur.
///
///   When the callback returned by `evloop.get_wakeup_callback_for_foreign_thread()`
///   is invoked, it MUST wake up the blocked `poll` wait, if `poll` is currently running.
///   If `poll` is not running when the callback is invoked,
///   the callback should make the next `poll` should return immediately.
///   MISSED WAKEUP MAY RESULT IN PROGRAM DEAD LOCK.
///
///   If the wakeup callback event is implemented via some form of special event,
///   that event itself can be ignored in `poll`. `moonbitlang/async` will
///   check for its own events unconditionally after `poll` returns.
///
///   IMPORTANT CORRECTNESS NOTE: the returned wakeup callback will be invoked
///   in a dedicated thread, so the callback MUST NOT do anything other than
///   invoking a C FFI function directly, and that invoked C FFI function MUST NOT
///   perform any MoonBit reference counting operation.
///
/// - `terminate` will be invoked when the main loop exit.
///   Cleanup code for the user-provided event loop should be placed here.
///   Notice that `moonbitlang/async` need to perform some additional cleanup
///   after `async fn main` returns. So event loop cleanup should not be performed
///   inside `async fn main`.
pub(open) trait ExternalEventLoop {
  fn poll(Self, timeout? : Int) -> Unit raise
  fn get_wakeup_callback_for_foreign_thread(Self) -> FuncRef[() -> Unit]
  fn terminate(Self) -> Unit raise
}