///|
/// The renderer/backend composition request handed to a platform entry.
pub struct AppLaunchRequest {
  title : String
  runtime : AppRuntime
  renderer_providers : Array[@render.RendererProvider]
  window_requests : @backend_host.WindowRequests
}

///|
pub fn AppLaunchRequest::title(self : AppLaunchRequest) -> String {
  self.title
}

///|
pub fn AppLaunchRequest::runtime(self : AppLaunchRequest) -> AppRuntime {
  self.runtime
}

///|
pub fn AppLaunchRequest::renderer_providers(
  self : AppLaunchRequest,
) -> Array[@render.RendererProvider] {
  self.renderer_providers.copy()
}

///|
pub fn AppLaunchRequest::window_requests(
  self : AppLaunchRequest,
) -> @backend_host.WindowRequests {
  self.window_requests
}

///|
/// A platform launch closure. The platform package captures its concrete
/// window options and erases the window type at this boundary.
pub struct PlatformEntry {
  id : String
  launch_fn : (AppLaunchRequest) -> Unit
  launch_async_fn : (async (AppLaunchRequest, Int) -> Unit)?
}

///|
pub fn PlatformEntry::new(
  id~ : String,
  launch~ : (AppLaunchRequest) -> Unit,
  launch_async? : (async (AppLaunchRequest, Int) -> Unit)? = None,
) -> PlatformEntry {
  { id, launch_fn: launch, launch_async_fn: launch_async, }
}

///|
pub fn PlatformEntry::id(self : PlatformEntry) -> String {
  self.id
}

///|
pub fn PlatformEntry::launch(
  self : PlatformEntry,
  request : AppLaunchRequest,
) -> Unit {
  (self.launch_fn)(request)
}

///|
pub async fn PlatformEntry::launch_async_pump(
  self : PlatformEntry,
  request : AppLaunchRequest,
  frame_interval_ms : Int,
) -> Unit {
  match self.launch_async_fn {
    Some(launch) => launch(request, frame_interval_ms)
    None => self.launch(request)
  }
}

///|
pub struct AppBuilder {
  title : String
  runtime : AppRuntime
  renderer_providers : Array[@render.RendererProvider]
  backend : PlatformEntry?
  window_requests : @backend_host.WindowRequests
}

///|
pub fn run_app(title : String, runtime : AppRuntime) -> AppBuilder {
  {
    title,
    runtime,
    renderer_providers: [],
    backend: None,
    window_requests: @backend_host.WindowRequests::unavailable(),
  }
}

///|
pub fn AppBuilder::render(
  self : AppBuilder,
  provider : @render.RendererProvider,
) -> AppBuilder {
  let providers = self.renderer_providers.copy()
  providers.push(provider)
  { ..self, renderer_providers: providers, }
}

///|
pub fn AppBuilder::render_all(
  self : AppBuilder,
  providers : Array[@render.RendererProvider],
) -> AppBuilder {
  let next = self.renderer_providers.copy()
  for provider in providers {
    next.push(provider)
  }
  { ..self, renderer_providers: next, }
}

///|
pub fn AppBuilder::backend(
  self : AppBuilder,
  backend : PlatformEntry,
) -> AppBuilder {
  { ..self, backend: Some(backend), }
}

///|
pub fn AppBuilder::window_requests(
  self : AppBuilder,
  window_requests : @backend_host.WindowRequests,
) -> AppBuilder {
  { ..self, window_requests, }
}

///|
pub fn AppBuilder::run(self : AppBuilder) -> Unit {
  if self.renderer_providers.is_empty() {
    abort("runtime.run_app: at least one renderer provider is required")
  }
  let backend = match self.backend {
    Some(backend) => backend
    None => abort("runtime.run_app: a platform backend is required")
  }
  backend.launch({
    title: self.title,
    runtime: self.runtime,
    renderer_providers: self.renderer_providers,
    window_requests: self.window_requests,
  })
}

///|
/// Run through a platform's cooperative async event pump. Platforms without
/// a dedicated pump fall back to their normal synchronous launch closure.
pub async fn AppBuilder::run_async_pump(
  self : AppBuilder,
  frame_interval_ms? : Int = 8,
) -> Unit {
  if self.renderer_providers.is_empty() {
    abort("runtime.run_app: at least one renderer provider is required")
  }
  let backend = match self.backend {
    Some(backend) => backend
    None => abort("runtime.run_app: a platform backend is required")
  }
  backend.launch_async_pump(
    {
      title: self.title,
      runtime: self.runtime,
      renderer_providers: self.renderer_providers,
      window_requests: self.window_requests,
    },
    frame_interval_ms,
  )
}