///|
/// A thread-safe sender for byte messages delivered to App::proxy_message.
///
/// The native queue copies every message, so callers may release the source
/// bytes immediately. It accepts at most 1 MiB per message and 8 MiB in total.
pub struct EventLoopProxy {
  priv backend : NativeBackend
  priv generation : UInt64
} derive(Debug)

///|
/// A rejected event-loop proxy submission.
pub(all) enum ProxyError {
  Closed
  MessageTooLarge
  QueueFull
} derive(Eq, Debug)

///|
/// Returns a sender that can be moved to a worker thread.
///
/// Only byte messages cross the thread boundary. Their handlers still run on
/// Orby's UI thread through App::proxy_message.
pub fn EventLoop::proxy(self : EventLoop) -> EventLoopProxy {
  { backend: self.backend, generation: self.proxy_generation }
}

///|
/// Returns a sender associated with this running application.
pub fn ActiveApp::proxy(self : ActiveApp) -> EventLoopProxy {
  { backend: self.backend, generation: self.proxy_generation }
}

///|
/// Returns whether this event-loop proxy can no longer accept messages.
pub fn EventLoopProxy::is_closed(self : EventLoopProxy) -> Bool {
  !native_proxy_is_open(self.backend, self.generation)
}

///|
/// Copies a byte message into the native queue and wakes the UI event loop.
///
/// A successful submission is delivered in FIFO order. Delivery stops when the
/// event loop exits; callers must handle Closed as a normal shutdown result.
pub fn EventLoopProxy::post(
  self : EventLoopProxy,
  message : Bytes,
) -> Result[Unit, ProxyError] {
  if self.is_closed() {
    return Err(Closed)
  }
  proxy_post_result(
    native_post_proxy_message(self.backend, self.generation, message),
  )
}

///|
fn proxy_post_result(status : Int) -> Result[Unit, ProxyError] {
  match status {
    1 => Ok(())
    2 => Err(MessageTooLarge)
    3 => Err(QueueFull)
    _ => Err(Closed)
  }
}

///|
fn take_proxy_message(backend : NativeBackend) -> Bytes? {
  let length : Ref[Int] = Ref(-1)
  let message = native_take_proxy_message(backend, length)
  if length.val < 0 {
    None
  } else {
    Some(message)
  }
}

///|
fn[A : App] dispatch_proxy_messages(app : A, active : ActiveApp) -> Unit {
  let mut delivered = 0
  while delivered < 256 {
    match take_proxy_message(active.backend) {
      None => break
      Some(message) => {
        app.proxy_message(active, message)
        delivered = delivered + 1
      }
    }
  }
}