///|
/// Permission categories are intentionally platform-neutral. A host may map
/// them to native prompts, browser permissions, or an unavailable result.
pub(all) enum PermissionKind {
  Notifications
  Camera
  Microphone
  Location
  Photos
  Files
} derive(Eq, Hash, Debug, ToJson)

///|
pub extend PermissionKind with Debug::{to_repr}

///|
pub extend PermissionKind with Eq::{not_equal, equal}

///|
pub extend PermissionKind with Hash::{hash, hash_combine}

///|
pub extend PermissionKind with ToJson::{to_json}

///|
pub(all) enum PermissionStatus {
  Granted
  Denied
  Restricted
} derive(Eq, Debug, ToJson)

///|
pub extend PermissionStatus with Debug::{to_repr}

///|
pub extend PermissionStatus with Eq::{not_equal, equal}

///|
pub extend PermissionStatus with ToJson::{to_json}

///|
/// Host lifecycle events describe whether the application is visible to the
/// user. Window focus remains a window/input concern and is not represented
/// here.
pub(all) enum AppLifecycleEvent {
  Foreground
  Background
} derive(Eq, Debug, ToJson)

///|
pub extend AppLifecycleEvent with Debug::{to_repr}

///|
pub extend AppLifecycleEvent with Eq::{not_equal, equal}

///|
pub extend AppLifecycleEvent with ToJson::{to_json}

///|
priv struct AppLifecycleHandler {
  token : Int
  handle : (AppLifecycleEvent) -> Unit
}

///|
/// Push source populated by a platform composition root. An absent source on
/// `PlatformServices` means the host does not expose app lifecycle events.
pub struct AppLifecycleSource {
  priv mut next_token : Int
  priv mut handlers : Array[AppLifecycleHandler]
}

///|
pub fn AppLifecycleSource::new() -> AppLifecycleSource {
  { next_token: 1, handlers: [], }
}

///|
pub fn AppLifecycleSource::publish(
  self : AppLifecycleSource,
  event : AppLifecycleEvent,
) -> Int {
  let handlers = self.handlers.copy()
  for handler in handlers {
    (handler.handle)(event)
  }
  handlers.length()
}

///|
pub fn AppLifecycleSource::active_handler_count(
  self : AppLifecycleSource,
) -> Int {
  self.handlers.length()
}

///|
pub fn[Msg] AppLifecycleSource::subscription(
  self : AppLifecycleSource,
  map~ : (AppLifecycleEvent) -> Msg,
  key? : String = "app:lifecycle",
  label? : String = "App lifecycle",
) -> @core.Subscription[Msg] {
  @core.Subscription::listen(key~, label~, kind="app-lifecycle", start=dispatch => {
    let token = self.next_token
    self.next_token = self.next_token + 1
    self.handlers.push({ token, handle: event => dispatch(map(event)), })
    Some(() => {
      self.handlers = self.handlers.filter(handler => handler.token != token)
    })
  })
}

///|
pub fn NotificationRequest::new(
  id~ : String,
  title~ : String,
  body~ : String,
) -> NotificationRequest {
  { id, title, body, }
}

///|
pub fn TrayItem::new(
  id~ : String,
  label~ : String,
  tooltip? : String = "",
) -> TrayItem {
  { id, label, tooltip, }
}

///|
pub fn ShareRequest::new(
  title~ : String,
  text~ : String,
  url? : String? = None,
) -> ShareRequest {
  { title, text, url, }
}

///|
pub fn PrintRequest::new(
  title~ : String,
  text~ : String,
  mime_type? : String = "text/plain",
) -> PrintRequest {
  { title, text, mime_type, }
}

///|
pub fn ProtocolRegistration::new(
  scheme~ : String,
  handler~ : String,
) -> ProtocolRegistration {
  { scheme, handler, }
}

///|
pub fn FileAssociation::new(
  extension~ : String,
  mime_type~ : String,
  handler~ : String,
) -> FileAssociation {
  { extension, mime_type, handler, }
}

///|
pub(all) struct NotificationRequest {
  id : String
  title : String
  body : String
} derive(Eq, Debug, ToJson)

///|
pub extend NotificationRequest with Debug::{to_repr}

///|
pub extend NotificationRequest with Eq::{not_equal, equal}

///|
pub extend NotificationRequest with ToJson::{to_json}

///|
pub(all) struct TrayItem {
  id : String
  label : String
  tooltip : String
} derive(Eq, Debug, ToJson)

///|
pub extend TrayItem with Debug::{to_repr}

///|
pub extend TrayItem with Eq::{not_equal, equal}

///|
pub extend TrayItem with ToJson::{to_json}

///|
pub(all) struct ShareRequest {
  title : String
  text : String
  url : String?
} derive(Eq, Debug, ToJson)

///|
pub extend ShareRequest with Debug::{to_repr}

///|
pub extend ShareRequest with Eq::{not_equal, equal}

///|
pub extend ShareRequest with ToJson::{to_json}

///|
pub(all) struct PrintRequest {
  title : String
  text : String
  mime_type : String
} derive(Eq, Debug, ToJson)

///|
pub extend PrintRequest with Debug::{to_repr}

///|
pub extend PrintRequest with Eq::{not_equal, equal}

///|
pub extend PrintRequest with ToJson::{to_json}

///|
pub(all) struct ProtocolRegistration {
  scheme : String
  handler : String
} derive(Eq, Debug, ToJson)

///|
pub extend ProtocolRegistration with Debug::{to_repr}

///|
pub extend ProtocolRegistration with Eq::{not_equal, equal}

///|
pub extend ProtocolRegistration with ToJson::{to_json}

///|
pub(all) struct FileAssociation {
  extension : String
  mime_type : String
  handler : String
} derive(Eq, Debug, ToJson)

///|
pub extend FileAssociation with Debug::{to_repr}

///|
pub extend FileAssociation with Eq::{not_equal, equal}

///|
pub extend FileAssociation with ToJson::{to_json}

///|
pub struct NotificationServices {
  notify_fn : (NotificationRequest) -> ServiceTask[Unit]
  cancel_fn : (String) -> ServiceTask[Unit]
}

///|
pub fn NotificationServices::new(
  notify? : (NotificationRequest) -> ServiceTask[Unit] = _request => {
    unavailable_task("notification:send", "notifications")
  },
  cancel? : (String) -> ServiceTask[Unit] = _id => {
    unavailable_task("notification:cancel", "notifications")
  },
) -> NotificationServices {
  { notify_fn: notify, cancel_fn: cancel, }
}

///|
pub fn NotificationServices::notify(
  self : NotificationServices,
  request : NotificationRequest,
) -> ServiceTask[Unit] {
  (self.notify_fn)(request)
}

///|
pub fn NotificationServices::cancel(
  self : NotificationServices,
  id : String,
) -> ServiceTask[Unit] {
  (self.cancel_fn)(id)
}

///|
pub struct TrayServices {
  set_fn : (TrayItem) -> ServiceTask[Unit]
  remove_fn : (String) -> ServiceTask[Unit]
}

///|
pub fn TrayServices::new(
  set? : (TrayItem) -> ServiceTask[Unit] = _item => {
    unavailable_task("tray:set", "system tray")
  },
  remove? : (String) -> ServiceTask[Unit] = _id => {
    unavailable_task("tray:remove", "system tray")
  },
) -> TrayServices {
  { set_fn: set, remove_fn: remove, }
}

///|
pub fn TrayServices::set(
  self : TrayServices,
  item : TrayItem,
) -> ServiceTask[Unit] {
  (self.set_fn)(item)
}

///|
pub fn TrayServices::remove(
  self : TrayServices,
  id : String,
) -> ServiceTask[Unit] {
  (self.remove_fn)(id)
}

///|
pub struct PermissionServices {
  request_fn : (PermissionKind) -> ServiceTask[PermissionStatus]
}

///|
pub fn PermissionServices::new(
  request? : (PermissionKind) -> ServiceTask[PermissionStatus] = _kind => {
    unavailable_task("permission:request", "permission prompts")
  },
) -> PermissionServices {
  { request_fn: request, }
}

///|
pub fn PermissionServices::request(
  self : PermissionServices,
  kind : PermissionKind,
) -> ServiceTask[PermissionStatus] {
  (self.request_fn)(kind)
}

///|
pub struct ShareServices {
  share_fn : (ShareRequest) -> ServiceTask[Unit]
}

///|
pub fn ShareServices::new(
  share? : (ShareRequest) -> ServiceTask[Unit] = _request => {
    unavailable_task("share:open", "share sheet")
  },
) -> ShareServices {
  { share_fn: share, }
}

///|
pub fn ShareServices::share(
  self : ShareServices,
  request : ShareRequest,
) -> ServiceTask[Unit] {
  (self.share_fn)(request)
}

///|
pub struct PrintServices {
  print_fn : (PrintRequest) -> ServiceTask[Unit]
}

///|
pub fn PrintServices::new(
  print? : (PrintRequest) -> ServiceTask[Unit] = _request => {
    unavailable_task("print:document", "printing")
  },
) -> PrintServices {
  { print_fn: print, }
}

///|
pub fn PrintServices::print(
  self : PrintServices,
  request : PrintRequest,
) -> ServiceTask[Unit] {
  (self.print_fn)(request)
}

///|
pub struct AssociationServices {
  register_protocol_fn : (ProtocolRegistration) -> ServiceTask[Unit]
  register_file_fn : (FileAssociation) -> ServiceTask[Unit]
  unregister_fn : (String) -> ServiceTask[Unit]
}

///|
pub fn AssociationServices::new(
  register_protocol? : (ProtocolRegistration) -> ServiceTask[Unit] = _registration => {
    unavailable_task("association:protocol", "protocol associations")
  },
  register_file? : (FileAssociation) -> ServiceTask[Unit] = _association => {
    unavailable_task("association:file", "file associations")
  },
  unregister? : (String) -> ServiceTask[Unit] = _value => {
    unavailable_task("association:remove", "file/protocol associations")
  },
) -> AssociationServices {
  {
    register_protocol_fn: register_protocol,
    register_file_fn: register_file,
    unregister_fn: unregister,
  }
}

///|
pub fn AssociationServices::register_protocol(
  self : AssociationServices,
  registration : ProtocolRegistration,
) -> ServiceTask[Unit] {
  (self.register_protocol_fn)(registration)
}

///|
pub fn AssociationServices::register_file(
  self : AssociationServices,
  association : FileAssociation,
) -> ServiceTask[Unit] {
  (self.register_file_fn)(association)
}

///|
pub fn AssociationServices::unregister(
  self : AssociationServices,
  value : String,
) -> ServiceTask[Unit] {
  (self.unregister_fn)(value)
}

///|
pub struct WindowStateServices {
  read_fn : (String) -> ServiceTask[String?]
  write_fn : (String, String) -> ServiceTask[Unit]
  remove_fn : (String) -> ServiceTask[Unit]
}

///|
pub fn WindowStateServices::new(
  read? : (String) -> ServiceTask[String?] = _window_id => {
    unavailable_task("window-state:read", "window state persistence")
  },
  write? : (String, String) -> ServiceTask[Unit] = (_window_id, _snapshot) => {
    unavailable_task("window-state:write", "window state persistence")
  },
  remove? : (String) -> ServiceTask[Unit] = _window_id => {
    unavailable_task("window-state:remove", "window state persistence")
  },
) -> WindowStateServices {
  { read_fn: read, write_fn: write, remove_fn: remove, }
}

///|
pub fn WindowStateServices::read(
  self : WindowStateServices,
  window_id : String,
) -> ServiceTask[String?] {
  (self.read_fn)(window_id)
}

///|
pub fn WindowStateServices::write(
  self : WindowStateServices,
  window_id : String,
  snapshot : String,
) -> ServiceTask[Unit] {
  (self.write_fn)(window_id, snapshot)
}

///|
pub fn WindowStateServices::remove(
  self : WindowStateServices,
  window_id : String,
) -> ServiceTask[Unit] {
  (self.remove_fn)(window_id)
}

///|
pub struct PlatformServices {
  notifications_value : NotificationServices
  tray_value : TrayServices
  permissions_value : PermissionServices
  sharing_value : ShareServices
  printing_value : PrintServices
  associations_value : AssociationServices
  window_state_value : WindowStateServices
  lifecycle_value : AppLifecycleSource?
  /// Non-optional so app code stays unconditional: hosts without native
  /// window dragging keep the unwired default, whose declarations are simply
  /// never applied.
  window_drag_value : WindowDragRegionSource
}

///|
pub fn PlatformServices::new(
  notifications? : NotificationServices = NotificationServices::new(),
  tray? : TrayServices = TrayServices::new(),
  permissions? : PermissionServices = PermissionServices::new(),
  sharing? : ShareServices = ShareServices::new(),
  printing? : PrintServices = PrintServices::new(),
  associations? : AssociationServices = AssociationServices::new(),
  window_state? : WindowStateServices = WindowStateServices::new(),
  lifecycle? : AppLifecycleSource? = None,
  window_drag? : WindowDragRegionSource = WindowDragRegionSource::new(),
) -> PlatformServices {
  {
    notifications_value: notifications,
    tray_value: tray,
    permissions_value: permissions,
    sharing_value: sharing,
    printing_value: printing,
    associations_value: associations,
    window_state_value: window_state,
    lifecycle_value: lifecycle,
    window_drag_value: window_drag,
  }
}

///|
pub fn PlatformServices::unavailable() -> PlatformServices {
  PlatformServices::new()
}

///|
pub fn PlatformServices::notifications(
  self : PlatformServices,
) -> NotificationServices {
  self.notifications_value
}

///|
pub fn PlatformServices::tray(self : PlatformServices) -> TrayServices {
  self.tray_value
}

///|
pub fn PlatformServices::permissions(
  self : PlatformServices,
) -> PermissionServices {
  self.permissions_value
}

///|
pub fn PlatformServices::sharing(self : PlatformServices) -> ShareServices {
  self.sharing_value
}

///|
pub fn PlatformServices::printing(self : PlatformServices) -> PrintServices {
  self.printing_value
}

///|
pub fn PlatformServices::associations(
  self : PlatformServices,
) -> AssociationServices {
  self.associations_value
}

///|
pub fn PlatformServices::window_state(
  self : PlatformServices,
) -> WindowStateServices {
  self.window_state_value
}

///|
pub fn PlatformServices::lifecycle(
  self : PlatformServices,
) -> AppLifecycleSource? {
  self.lifecycle_value
}

///|
/// Declare a native window-drag band for self-drawn window chrome. Hosts
/// without native dragging ignore declarations.
pub fn PlatformServices::window_drag(
  self : PlatformServices,
) -> WindowDragRegionSource {
  self.window_drag_value
}