///|
/// Failures at the MoonBit/native runtime boundary.
pub(all) suberror NativeError {
  /// A negative status returned by the public Proton C ABI.
  Status(status~ : Int, message~ : String)
  /// An invalid argument rejected before entering the C ABI.
  InvalidArgument(message~ : String)
  /// Malformed data returned by the native runtime.
  InvalidPayload(context~ : String, message~ : String)
} derive(Debug, Eq)

///|
pub fn NativeError::status(self : NativeError) -> Int {
  match self {
    Status(status~, ..) => status
    InvalidArgument(..) | InvalidPayload(..) => -1
  }
}

///|
pub fn NativeError::message(self : NativeError) -> String {
  match self {
    Status(message~, ..) | InvalidArgument(message~) => message
    InvalidPayload(context~, message~) =>
      "failed to decode " + context + ": " + message
  }
}

///|
/// Reports whether a bridge response lost its renderer request while an
/// asynchronous handler was still completing.
pub fn NativeError::is_stale_bridge_response(self : NativeError) -> Bool {
  self.status() == proton_err_stale_bridge_response
}

///|
pub fn NativeError::is_stale_window_request(self : NativeError) -> Bool {
  self.status() == proton_err_stale_window_request
}

///|
pub fn NativeError::is_stale_browser_request(self : NativeError) -> Bool {
  self.status() == proton_err_stale_browser_request
}

///|
pub fn NativeError::is_update_busy(self : NativeError) -> Bool {
  self.status() == proton_err_update_busy
}

///|
pub fn NativeError::is_update_rollback(self : NativeError) -> Bool {
  self.status() == proton_err_update_rollback
}

///|
pub fn NativeError::is_update_revision_mismatch(self : NativeError) -> Bool {
  self.status() == proton_err_update_revision_mismatch
}

///|
impl Show for NativeError with fn output(self, logger) {
  logger.write_string(self.message())
}

///|
pub(all) enum ProcessResult {
  MainProcess
  SubprocessHandled(Int)
} derive(Debug, Eq)

///|
pub(all) struct RuntimeInfo {
  abi_version : Int
  runtime_available : Bool
  build_mode : String
  platform : String
  /// The prebuilt identifier this library was built as, such as
  /// `darwin-arm64`. Matches the platform keys in an update manifest.
  platform_id : String
  features : Array[String]
} derive(Debug, Eq, FromJson)

///|
pub(all) struct RuntimeWaitReady {
  mask : Int
} derive(Debug, Eq)

///|
/// Inputs forwarded by a second operating-system application instance.
pub(all) struct AppActivation {
  abi_version : Int
  urls : Array[String]
  files : Array[String]
  reopen : Bool
} derive(Debug, Eq, ToJson)

///|
/// Result of claiming an operating-system application identity.
pub enum AppInstanceAcquire {
  Primary(AppInstance)
  Forwarded
}

///|
struct AppInstance {
  mut handle : Int64
  mut destroyed : Bool
}

///|
struct RuntimeEvent {
  event_type : String
  window : Int64?
  view : Int64?
  title : String?
  is_loading : Bool?
  state : WindowState?
  request_id : Int64?
  url : String?
  http_method : String?
  user_gesture : Bool?
  redirect : Bool?
  disposition : Int?
  download_id : Int?
  suggested_name : String?
  download_state : String?
  received_bytes : Int64?
  total_bytes : Int64?
  percent : Int?
  error_code : Int?
  permissions : Int?
  menu_command_id : String?
  revision : Int64?
  items : Array[String]
  ok : Bool?
  message : String?
} derive(Debug, Eq)

///|
/// A macOS application activation delivered by Launch Services or the Dock.
pub(all) enum RuntimeLaunchInput {
  OpenUrls(Array[String])
  OpenFiles(Array[String])
  Reopen
} derive(Debug, Eq)

///|
pub(all) enum NativeNotificationResult {
  Delivered
  Failed(String)
} derive(Debug, Eq)

///|
pub(all) enum NativeBrowserRequest {
  Navigation(
    request_id~ : Int64,
    url~ : String,
    http_method~ : String,
    user_gesture~ : Bool,
    redirect~ : Bool
  )
  Popup(
    request_id~ : Int64,
    url~ : String,
    disposition~ : Int,
    user_gesture~ : Bool
  )
  Download(
    request_id~ : Int64,
    download_id~ : Int,
    url~ : String,
    suggested_name~ : String
  )
  Certificate(request_id~ : Int64, url~ : String, error_code~ : Int)
  Media(request_id~ : Int64, origin~ : String, permissions~ : Int)
} derive(Debug, Eq)

///|
pub(all) struct NativeDownloadUpdate {
  download_id : Int
  state : String
  received_bytes : Int64
  total_bytes : Int64
  percent : Int
} derive(Debug, Eq)

///|
pub(all) struct BridgeLifecycleState {
  abi_version : Int
  revision : String
  outcome : String
  page_instance : String
  url : String
  failure_pending : Bool
} derive(Debug, Eq, FromJson)

///|
pub(all) struct BridgeDiagnostic {
  abi_version : Int
  stage : String
  code : String
  message : String
  page_instance : String
  url : String
  owner : String?
  source_url : String?
  source_line : String?
  line : Int?
  column : Int?
  stack : String?
  additional_failure_count : Int?
  details_truncated : Bool
} derive(Debug, Eq, FromJson)

///|
pub(all) struct BridgeRequest {
  request_id : Int64
  window : Int64
  op : String
  payload : Json
  page_instance : String?
  source_origin : String
} derive(Debug)

///|
pub(all) enum BridgeResponse {
  Ok(request_id~ : Int64, payload~ : Json)
  Err(request_id~ : Int64, code~ : String, message~ : String)
} derive(Debug)

///|
pub(all) enum DialogLevel {
  Info = 0
  Warning = 1
  Error = 2
} derive(Debug, Eq)

///|
pub(all) enum DialogPollResult {
  Pending
  Ready(String)
} derive(Debug, Eq)

///|
/// Geometry and scaling information for the monitor containing a window.
pub(all) struct WindowMonitor {
  x : Int
  y : Int
  width : Int
  height : Int
  work_x : Int
  work_y : Int
  work_width : Int
  work_height : Int
  scale_factor_percent : Int
} derive(Debug, Eq, FromJson)

///|
/// A point-in-time native window state snapshot.
pub(all) struct WindowState {
  x : Int
  y : Int
  width : Int
  height : Int
  monitor : WindowMonitor
  zoom_percent : Int
  visible : Bool
  focused : Bool
  minimized : Bool
  maximized : Bool
  fullscreen : Bool
  always_on_top : Bool
  theme : String
} derive(Debug, Eq, FromJson)

///|
/// A native notification activation, optionally carrying its application
/// payload.
pub(all) struct NativeNotificationClick {
  payload : String?
} derive(Debug, Eq)

///|
/// An application-level native menu bar.
struct MenuBar {
  raw_json : String?
  menus : Array[Menu]
} derive(Debug, Eq)

///|
/// A top-level native menu and its items.
struct Menu {
  label : String
  items : Array[MenuItem]
} derive(Debug, Eq)

///|
/// A command, separator, or platform role in a native menu.
struct MenuItem {
  kind : String
  id : String?
  label : String?
  key : String?
  role : String?
} derive(Debug, Eq)

///|
pub(all) struct BridgeConfig {
  raw_json : String?
  max_payload_bytes : Int
  grants : Array[BridgeGrantConfig]
} derive(Debug, Eq)

///|
/// Renderer capabilities granted to one canonical source in one window.
pub(all) struct BridgeGrantConfig {
  source_origin : String
  ops : Array[String]
  extensions : Array[BridgeExtensionConfig]
  initialization_units : Array[BridgeInitializationUnit]
} derive(Debug, Eq)

///|
/// One JavaScript namespace installed by the renderer bootstrap.
pub(all) struct BridgeExtensionConfig {
  js_namespace : String
  apis : Array[String]
} derive(Debug, Eq)

///|
/// One ordered JavaScript initialization unit owned by an extension.
pub(all) struct BridgeInitializationUnit {
  owner : String
  name : String
  source : String
} derive(Debug, Eq)

///|
pub fn RuntimeEvent::event_type(self : RuntimeEvent) -> String {
  self.event_type
}

///|
pub fn RuntimeEvent::has_window(self : RuntimeEvent) -> Bool {
  self.window is Some(_)
}

///|
/// Returns the window associated with this event when one exists. For an app
/// menu command this is the focused window at click time.
pub fn RuntimeEvent::window_id(self : RuntimeEvent) -> Int64? {
  self.window
}

///|
/// Returns the web contents view associated with this event when one exists.
pub fn RuntimeEvent::view_id(self : RuntimeEvent) -> Int64? {
  self.view
}

///|
/// The payload of a web contents view lifecycle event.
pub(all) struct ViewEventInfo {
  url : String?
  title : String?
  is_loading : Bool?
  error_code : Int?
  error_text : String?
} derive(Debug, Eq)

///|
/// Returns the view event payload when this event carries one.
pub fn RuntimeEvent::view_event(self : RuntimeEvent) -> ViewEventInfo? {
  if !self.event_type.has_prefix("view_") {
    return None
  }
  Some({
    url: self.url,
    title: self.title,
    is_loading: self.is_loading,
    error_code: self.error_code,
    error_text: self.message,
  })
}

///|
pub fn RuntimeEvent::is_window_created(self : RuntimeEvent) -> Bool {
  self.event_type == "window_created"
}

///|
pub fn RuntimeEvent::is_window_closed(self : RuntimeEvent) -> Bool {
  self.event_type == "window_closed"
}

///|
pub fn RuntimeEvent::window_state_change(self : RuntimeEvent) -> WindowState? {
  if self.event_type == "window_state_changed" {
    self.state
  } else {
    None
  }
}

///|
pub fn RuntimeEvent::window_close_request(self : RuntimeEvent) -> Int64? {
  if self.event_type == "window_close_requested" {
    self.request_id
  } else {
    None
  }
}

///|
pub fn RuntimeEvent::browser_request(
  self : RuntimeEvent,
) -> NativeBrowserRequest? {
  let request_id = self.request_id
  let url = self.url
  match (self.event_type, request_id, url) {
    ("browser_navigation_requested", Some(request_id), Some(url)) =>
      Some(
        Navigation(
          request_id~,
          url~,
          http_method=self.http_method.unwrap_or("GET"),
          user_gesture=self.user_gesture.unwrap_or(false),
          redirect=self.redirect.unwrap_or(false),
        ),
      )
    ("browser_popup_requested", Some(request_id), Some(url)) =>
      Some(
        Popup(
          request_id~,
          url~,
          disposition=self.disposition.unwrap_or(0),
          user_gesture=self.user_gesture.unwrap_or(false),
        ),
      )
    ("browser_download_requested", Some(request_id), Some(url)) =>
      Some(
        Download(
          request_id~,
          download_id=self.download_id.unwrap_or(0),
          url~,
          suggested_name=self.suggested_name.unwrap_or(""),
        ),
      )
    ("browser_certificate_error", Some(request_id), Some(url)) =>
      Some(
        Certificate(request_id~, url~, error_code=self.error_code.unwrap_or(0)),
      )
    ("browser_media_permission_requested", Some(request_id), Some(origin)) =>
      Some(
        Media(request_id~, origin~, permissions=self.permissions.unwrap_or(0)),
      )
    _ => None
  }
}

///|
pub fn RuntimeEvent::browser_download_update(
  self : RuntimeEvent,
) -> NativeDownloadUpdate? {
  guard self.event_type == "browser_download_updated" else { return None }
  match
    (
      self.download_id,
      self.download_state,
      self.received_bytes,
      self.total_bytes,
      self.percent,
    ) {
    (
      Some(download_id),
      Some(state),
      Some(received_bytes),
      Some(total_bytes),
      Some(percent),
    ) => Some({ download_id, state, received_bytes, total_bytes, percent })
    _ => None
  }
}

///|
pub fn RuntimeEvent::is_bridge_lifecycle_changed(self : RuntimeEvent) -> Bool {
  self.event_type == "bridge_lifecycle_changed"
}

///|
pub fn RuntimeEvent::bridge_request_cancellation(self : RuntimeEvent) -> Int64? {
  if self.event_type == "bridge_request_cancelled" {
    self.request_id
  } else {
    None
  }
}

///|
/// The command id for an app-menu command item click, or `None` for every
/// other event kind.
pub fn RuntimeEvent::menu_command_id(self : RuntimeEvent) -> String? {
  if self.event_type == "menu_command" {
    self.menu_command_id
  } else {
    None
  }
}

///|
/// Returns the typed application launch input carried by this event.
pub fn RuntimeEvent::launch_input(self : RuntimeEvent) -> RuntimeLaunchInput? {
  match self.event_type {
    "open_urls" => Some(RuntimeLaunchInput::OpenUrls(self.items.copy()))
    "open_files" => Some(RuntimeLaunchInput::OpenFiles(self.items.copy()))
    "reopen" => Some(RuntimeLaunchInput::Reopen)
    _ => None
  }
}

///|
pub fn RuntimeEvent::notification_result(
  self : RuntimeEvent,
) -> NativeNotificationResult? {
  guard self.event_type == "notification_result" else { return None }
  match self.ok {
    Some(true) => Some(NativeNotificationResult::Delivered)
    Some(false) =>
      Some(
        NativeNotificationResult::Failed(
          self.message.unwrap_or("notification delivery failed"),
        ),
      )
    None => None
  }
}

///|
pub fn RuntimeWaitReady::mask(self : RuntimeWaitReady) -> Int {
  self.mask
}

///|
pub fn RuntimeWaitReady::has_event(self : RuntimeWaitReady) -> Bool {
  self.mask.land(runtime_wait_event) != 0
}

///|
pub fn RuntimeWaitReady::has_bridge(self : RuntimeWaitReady) -> Bool {
  self.mask.land(runtime_wait_bridge) != 0
}

///|
pub fn RuntimeWaitReady::has_platform(self : RuntimeWaitReady) -> Bool {
  self.mask.land(runtime_wait_platform) != 0
}

///|
pub fn RuntimeWaitReady::is_timeout(self : RuntimeWaitReady) -> Bool {
  self.mask == runtime_wait_none
}

///|
pub fn BridgeRequest::request_id(self : BridgeRequest) -> Int64 {
  self.request_id
}

///|
pub fn BridgeRequest::window(self : BridgeRequest) -> Int64 {
  self.window
}

///|
pub fn BridgeRequest::op(self : BridgeRequest) -> String {
  self.op
}

///|
pub fn BridgeRequest::payload(self : BridgeRequest) -> Json {
  self.payload
}

///|
pub fn BridgeRequest::page_instance(self : BridgeRequest) -> String? {
  self.page_instance
}

///|
pub fn BridgeRequest::source_origin(self : BridgeRequest) -> String {
  self.source_origin
}

///|
priv enum RuntimeLifecycleState {
  RuntimeActive = 0
  RuntimeDestroying = 1
  RuntimeDestroyed = 2
}

///|
priv enum WindowLifecycleState {
  WindowLive = 0
  WindowCloseRequested = 1
  WindowDestroying = 2
  WindowDestroyed = 3
}

///|
struct Runtime {
  mut handle : Int64
  mut lifecycle : RuntimeLifecycleState
}

///|
struct Window {
  mut handle : Int64
  mut lifecycle : WindowLifecycleState
}

///|
struct WindowRef {
  handle : Int64
} derive(Debug, Eq)

///|
pub(all) enum TitlebarStyle {
  Default
  Overlay
} derive(Debug, Eq)

///|
/// Process-wide cache for `runtime_info`: the loaded runtime's identity and
/// feature set cannot change within a process, so feature probes reuse the
/// first successful result. Failures are not cached — callers keep the
/// previous retry-on-failure behavior. A concurrent double-fill is benign
/// (the query is pure and idempotent). The cached `features` array is shared
/// with callers; all current callers only read it.
let runtime_info_cache : Ref[RuntimeInfo?] = { val: None }

///|
fn cached_runtime_info() -> RuntimeInfo raise NativeError {
  match runtime_info_cache.val {
    Some(info) => info
    None => {
      let info = runtime_info()
      runtime_info_cache.val = Some(info)
      info
    }
  }
}

///|
fn titlebar_overlay_config_supported() -> Bool {
  let info = cached_runtime_info() catch { _ => return false }
  info.features.contains("titlebar_overlay")
}

///|
/// Controls how the configured width and height constrain native resizing.
pub(all) enum WindowSizeHint {
  Unconstrained
  Fixed
  Min
  Max
} derive(Debug, Eq)

///|
pub(all) enum BrowserPolicyMode {
  Allow
  Deny
  Ask
} derive(Debug, Eq)

///|
pub(all) struct BrowserPolicy {
  navigation : BrowserPolicyMode
  popup : BrowserPolicyMode
  download : BrowserPolicyMode
  certificate : BrowserPolicyMode
  media : BrowserPolicyMode
  devtools : Bool
} derive(Debug, Eq)

///|
pub fn BrowserPolicy::new(
  navigation? : BrowserPolicyMode = BrowserPolicyMode::Allow,
  popup? : BrowserPolicyMode = BrowserPolicyMode::Deny,
  download? : BrowserPolicyMode = BrowserPolicyMode::Deny,
  certificate? : BrowserPolicyMode = BrowserPolicyMode::Deny,
  media? : BrowserPolicyMode = BrowserPolicyMode::Deny,
  devtools? : Bool = false,
) -> BrowserPolicy {
  { navigation, popup, download, certificate, media, devtools }
}

///|
fn window_size_hint_config_supported() -> Bool {
  let info = cached_runtime_info() catch { _ => return false }
  info.features.contains("window_size_hints")
}

///|
pub fn bridge_permission_grants_supported() -> Bool {
  let info = cached_runtime_info() catch { _ => return false }
  info.features.contains("bridge_permission_grants")
}

///|
struct RuntimeConfig {
  raw_json : String?
  use_bundled : Bool
  headless : Bool
  persist_session_cookies : Bool
  runtime_root : String?
  helper_path : String?
  resources_dir : String?
  locales_dir : String?
  cache_dir : String?
  remote_debugging_port : Int
} derive(Debug, Eq)

///|
struct WindowConfig {
  raw_json : String?
  title : String
  width : Int
  height : Int
  initial_url : String
  size_hint : WindowSizeHint
  titlebar_style : TitlebarStyle
  browser : BrowserPolicy
  bridge : BridgeConfig?
} derive(Debug, Eq)

///|
/// Builds an explicit native runtime configuration.
///
/// Set `headless` to enable CEF off-screen rendering for every window created
/// by this runtime. Remote debugging remains independently configurable. Omit
/// `cache_dir` for an isolated temporary browser profile, or provide an
/// absolute, process-exclusive path for persistent browser state.
pub fn RuntimeConfig::new(
  runtime_root? : String,
  helper_path? : String,
  resources_dir? : String,
  locales_dir? : String,
  cache_dir? : String,
  remote_debugging_port? : Int = 0,
  headless? : Bool = false,
  persist_session_cookies? : Bool = true,
) -> RuntimeConfig {
  {
    raw_json: None,
    use_bundled: false,
    headless,
    persist_session_cookies,
    runtime_root,
    helper_path,
    resources_dir,
    locales_dir,
    cache_dir,
    remote_debugging_port,
  }
}

///|
pub fn RuntimeConfig::unsafe_from_json(raw_json : String) -> RuntimeConfig {
  {
    raw_json: Some(raw_json),
    use_bundled: false,
    headless: false,
    persist_session_cookies: true,
    runtime_root: None,
    helper_path: None,
    resources_dir: None,
    locales_dir: None,
    cache_dir: None,
    remote_debugging_port: 0,
  }
}

///|
/// `helper_path`, when set, overrides the subprocess executable the bundled
/// runtime discovers. Packaged macOS applications automatically use their
/// nested base Helper app. Set `headless` to enable CEF off-screen rendering
/// for every window. Omit `cache_dir` for an isolated temporary browser
/// profile, or provide an absolute, process-exclusive path for persistence.
pub fn RuntimeConfig::bundled(
  helper_path? : String,
  cache_dir? : String,
  remote_debugging_port? : Int = 0,
  headless? : Bool = false,
  persist_session_cookies? : Bool = true,
) -> RuntimeConfig {
  {
    raw_json: None,
    use_bundled: true,
    headless,
    persist_session_cookies,
    runtime_root: None,
    helper_path,
    resources_dir: None,
    locales_dir: None,
    cache_dir,
    remote_debugging_port,
  }
}

///|
pub fn WindowConfig::new(
  title? : String = "Proton",
  width? : Int = 800,
  height? : Int = 600,
  initial_url? : String = "about:blank",
  size_hint? : WindowSizeHint = WindowSizeHint::Unconstrained,
  titlebar_style? : TitlebarStyle = TitlebarStyle::Default,
  browser? : BrowserPolicy = BrowserPolicy::new(),
  bridge? : BridgeConfig,
) -> WindowConfig {
  {
    raw_json: None,
    title,
    width,
    height,
    initial_url,
    size_hint,
    titlebar_style,
    browser,
    bridge,
  }
}

///|
pub fn WindowConfig::unsafe_from_json(raw_json : String) -> WindowConfig {
  {
    raw_json: Some(raw_json),
    title: "Proton",
    width: 800,
    height: 600,
    initial_url: "about:blank",
    size_hint: WindowSizeHint::Unconstrained,
    titlebar_style: TitlebarStyle::Default,
    browser: BrowserPolicy::new(),
    bridge: None,
  }
}

///|
/// Creates an application-level menu bar.
pub fn MenuBar::new(menus~ : Array[Menu]) -> MenuBar {
  { raw_json: None, menus }
}

///|
/// Creates a menu bar from unchecked native ABI JSON.
pub fn MenuBar::unsafe_from_json(raw_json : String) -> MenuBar {
  { raw_json: Some(raw_json), menus: [] }
}

///|
/// Creates a top-level menu.
pub fn Menu::new(label : String, items~ : Array[MenuItem]) -> Menu {
  { label, items }
}

///|
/// Creates an app command item. Activating it emits `menu_command`.
pub fn MenuItem::command(
  id : String,
  label : String,
  key? : String,
) -> MenuItem {
  { kind: "command", id: Some(id), label: Some(label), key, role: None }
}

///|
/// Creates a menu separator.
pub fn MenuItem::separator() -> MenuItem {
  { kind: "separator", id: None, label: None, key: None, role: None }
}

///|
/// Creates an item backed by a platform menu role such as `close` or `quit`.
pub fn MenuItem::role(
  role : String,
  label? : String,
  key? : String,
) -> MenuItem {
  { kind: "role", id: None, label, key, role: Some(role) }
}

///|
pub fn WindowRef::unsafe_from_handle(handle : Int64) -> WindowRef {
  WindowRef::{ handle, }
}

///|
pub fn BridgeConfig::new(
  grants~ : Array[BridgeGrantConfig],
  max_payload_bytes? : Int = 1048576,
) -> BridgeConfig {
  {
    raw_json: None,
    max_payload_bytes,
    grants: grants.map(fn(grant) { grant.copy() }),
  }
}

///|
pub fn BridgeConfig::unsafe_from_json(raw_json : String) -> BridgeConfig {
  { raw_json: Some(raw_json), max_payload_bytes: 1048576, grants: [] }
}

///|
pub fn BridgeGrantConfig::new(
  source_origin : String,
  ops~ : Array[String],
  extensions? : Array[BridgeExtensionConfig] = [],
  initialization_units? : Array[BridgeInitializationUnit] = [],
) -> BridgeGrantConfig {
  {
    source_origin,
    ops: copy_strings(ops),
    extensions: extensions.map(fn(extension) { extension.copy() }),
    initialization_units: initialization_units.map(fn(unit) { unit.copy() }),
  }
}

///|
pub fn BridgeExtensionConfig::new(
  js_namespace : String,
  apis~ : Array[String],
) -> BridgeExtensionConfig {
  { js_namespace, apis: copy_strings(apis) }
}

///|
pub fn BridgeInitializationUnit::new(
  owner : String,
  name : String,
  source : String,
) -> BridgeInitializationUnit {
  { owner, name, source }
}

///|
fn BridgeInitializationUnit::copy(
  self : BridgeInitializationUnit,
) -> BridgeInitializationUnit {
  { owner: self.owner, name: self.name, source: self.source }
}

///|
fn BridgeExtensionConfig::copy(
  self : BridgeExtensionConfig,
) -> BridgeExtensionConfig {
  { js_namespace: self.js_namespace, apis: copy_strings(self.apis) }
}

///|
fn BridgeGrantConfig::copy(self : BridgeGrantConfig) -> BridgeGrantConfig {
  BridgeGrantConfig::new(
    self.source_origin,
    ops=self.ops,
    extensions=self.extensions,
    initialization_units=self.initialization_units,
  )
}

///|
fn copy_strings(values : Array[String]) -> Array[String] {
  values.map(fn(value) { value })
}

///|
fn set_optional_string_field(
  fields : Map[String, Json],
  name : String,
  value : String?,
) -> Unit {
  match value {
    Some(text) => fields.set(name, Json(text))
    None => ()
  }
}

///|
pub fn RuntimeConfig::to_json_string(self : RuntimeConfig) -> String {
  match self.raw_json {
    Some(raw) => raw
    None => {
      let fields : Map[String, Json] = {
        "abi_version": Json::number(1.0, repr="1"),
        "remote_debugging_port": Json::number(
          self.remote_debugging_port.to_double(),
          repr=self.remote_debugging_port.to_string(),
        ),
      }
      if self.use_bundled {
        fields.set("use_bundled", Json(true))
      }
      if self.headless {
        fields.set("headless", Json(true))
      }
      fields.set(
        "persist_session_cookies",
        Json::boolean(self.persist_session_cookies),
      )
      set_optional_string_field(fields, "runtime_root", self.runtime_root)
      set_optional_string_field(fields, "helper_path", self.helper_path)
      set_optional_string_field(fields, "resources_dir", self.resources_dir)
      set_optional_string_field(fields, "locales_dir", self.locales_dir)
      set_optional_string_field(fields, "cache_dir", self.cache_dir)
      Json::object(fields).stringify()
    }
  }
}

///|
fn BrowserPolicyMode::to_json_string(self : BrowserPolicyMode) -> String {
  match self {
    Allow => "allow"
    Deny => "deny"
    Ask => "ask"
  }
}

///|
fn BrowserPolicy::to_json(self : BrowserPolicy) -> Json {
  {
    "navigation": self.navigation.to_json_string(),
    "popup": self.popup.to_json_string(),
    "download": self.download.to_json_string(),
    "certificate": self.certificate.to_json_string(),
    "media": self.media.to_json_string(),
    "devtools": self.devtools,
  }
}

///|
pub fn WindowConfig::to_json_string(self : WindowConfig) -> String {
  match self.raw_json {
    Some(raw) => raw
    None => {
      let fields : Map[String, Json] = {
        "abi_version": Json::number(1.0, repr="1"),
        "title": self.title,
        "width": Json::number(
          self.width.to_double(),
          repr=self.width.to_string(),
        ),
        "height": Json::number(
          self.height.to_double(),
          repr=self.height.to_string(),
        ),
        "initial_url": self.initial_url,
      }
      match self.titlebar_style {
        TitlebarStyle::Default => ()
        TitlebarStyle::Overlay =>
          if titlebar_overlay_config_supported() {
            fields["titlebar_style"] = Json("overlay")
          }
      }
      if window_size_hint_config_supported() {
        match self.size_hint {
          WindowSizeHint::Unconstrained => ()
          WindowSizeHint::Fixed => fields["size_hint"] = Json("fixed")
          WindowSizeHint::Min => fields["size_hint"] = Json("min")
          WindowSizeHint::Max => fields["size_hint"] = Json("max")
        }
      }
      match self.bridge {
        Some(bridge) => fields.set("bridge", bridge.to_json())
        None => ()
      }
      fields.set("browser", self.browser.to_json())
      Json::object(fields).stringify()
    }
  }
}

///|
fn MenuItem::to_json(self : MenuItem) -> Json {
  let fields : Map[String, Json] = { "kind": self.kind }
  set_optional_string_field(fields, "id", self.id)
  set_optional_string_field(fields, "label", self.label)
  set_optional_string_field(fields, "key", self.key)
  set_optional_string_field(fields, "role", self.role)
  Json::object(fields)
}

///|
fn Menu::to_json(self : Menu) -> Json {
  { "label": self.label, "items": self.items.map(fn(item) { item.to_json() }) }
}

///|
/// Encodes this menu bar for the native ABI.
pub fn MenuBar::to_json_string(self : MenuBar) -> String {
  match self.raw_json {
    Some(raw) => raw
    None => {
      let json : Json = {
        "abi_version": Json::number(1.0, repr="1"),
        "menus": self.menus.map(fn(menu) { menu.to_json() }),
      }
      json.stringify()
    }
  }
}

///|
fn BridgeConfig::to_json(self : BridgeConfig) -> Json {
  match self.raw_json {
    Some(raw) => @json.parse(raw) catch { _ => null }
    None => {
      let fields : Map[String, Json] = {
        "abi_version": Json::number(2.0, repr="2"),
        "namespace": "__MoonBit__",
        "grants": self.grants.map(fn(grant) -> Json {
          {
            "source_origin": grant.source_origin,
            "ops": grant.ops.map(fn(name) -> Json { { "name": name } }),
            "extensions": grant.extensions.map(fn(extension) -> Json {
              { "namespace": extension.js_namespace, "apis": extension.apis }
            }),
            "initialization_units": grant.initialization_units.map(fn(
              unit,
            ) -> Json {
              { "owner": unit.owner, "name": unit.name, "source": unit.source }
            }),
          }
        }),
        "max_payload_bytes": Json::number(
          self.max_payload_bytes.to_double(),
          repr=self.max_payload_bytes.to_string(),
        ),
      }
      Json::object(fields)
    }
  }
}

///|
pub fn BridgeConfig::to_json_string(self : BridgeConfig) -> String {
  self.to_json().stringify()
}

///|
pub fn BridgeResponse::to_json_string(self : BridgeResponse) -> String {
  match self {
    Ok(request_id~, payload~) => {
      let response : Json = {
        "abi_version": Json::number(1.0, repr="1"),
        "request_id": Json::number(
          request_id.to_double(),
          repr=request_id.to_string(),
        ),
        "ok": true,
        "payload": payload,
      }
      response.stringify()
    }
    Err(request_id~, code~, message~) => {
      let response : Json = {
        "abi_version": Json::number(1.0, repr="1"),
        "request_id": Json::number(
          request_id.to_double(),
          repr=request_id.to_string(),
        ),
        "ok": false,
        "error": { "code": code, "message": message },
      }
      response.stringify()
    }
  }
}

///|
priv enum ViewLifecycleState {
  ViewLive = 0
  ViewDestroying = 1
  ViewDestroyed = 2
}

///|
/// An owned native web contents view hosted inside a window's content area.
struct View {
  mut handle : Int64
  mut lifecycle : ViewLifecycleState
}

///|
/// A borrowed view handle for APIs that must not destroy the view.
struct ViewRef {
  handle : Int64
} derive(Debug, Eq)

///|
/// A point-in-time web contents view state snapshot. Bounds use a top-left
/// origin in the owning window's content coordinate space.
pub(all) struct ViewState {
  x : Int
  y : Int
  width : Int
  height : Int
  visible : Bool
  z_order : Int
} derive(Debug, Eq, FromJson)

///|
/// Native configuration for one web contents view. `width` and `height` are
/// required because the native browser needs an initial rectangle; `x`/`y`
/// default to `0`, `visible` to `true`, `z_order` to `0`, and `initial_url`
/// to `about:blank`. Views stack above the window's main browser, ordered by
/// ascending `z_order`.
struct ViewConfig {
  raw_json : String?
  x : Int
  y : Int
  width : Int
  height : Int
  visible : Bool
  z_order : Int
  initial_url : String
  background_color : String?
} derive(Debug, Eq)

///|
pub fn ViewConfig::new(
  width~ : Int,
  height~ : Int,
  x? : Int = 0,
  y? : Int = 0,
  visible? : Bool = true,
  z_order? : Int = 0,
  initial_url? : String = "about:blank",
  background_color? : String,
) -> ViewConfig {
  {
    raw_json: None,
    x,
    y,
    width,
    height,
    visible,
    z_order,
    initial_url,
    background_color,
  }
}

///|
/// Creates a view config from unchecked native ABI JSON.
pub fn ViewConfig::unsafe_from_json(raw_json : String) -> ViewConfig {
  {
    raw_json: Some(raw_json),
    x: 0,
    y: 0,
    width: 0,
    height: 0,
    visible: true,
    z_order: 0,
    initial_url: "about:blank",
    background_color: None,
  }
}

///|
pub fn ViewConfig::to_json_string(self : ViewConfig) -> String {
  match self.raw_json {
    Some(raw) => raw
    None => {
      let fields : Map[String, Json] = {
        "abi_version": Json::number(1.0, repr="1"),
        "x": Json::number(self.x.to_double(), repr=self.x.to_string()),
        "y": Json::number(self.y.to_double(), repr=self.y.to_string()),
        "width": Json::number(
          self.width.to_double(),
          repr=self.width.to_string(),
        ),
        "height": Json::number(
          self.height.to_double(),
          repr=self.height.to_string(),
        ),
        "visible": self.visible,
        "z_order": Json::number(
          self.z_order.to_double(),
          repr=self.z_order.to_string(),
        ),
        "initial_url": self.initial_url,
      }
      match self.background_color {
        Some(color) => fields.set("background_color", Json(color))
        None => ()
      }
      Json::object(fields).stringify()
    }
  }
}

///|
/// Returns true when the loaded native runtime supports web contents views.
pub fn web_contents_view_supported() -> Bool {
  let info = cached_runtime_info() catch { _ => return false }
  info.features.contains("web_contents_view")
}

///|
pub fn ViewRef::unsafe_from_handle(handle : Int64) -> ViewRef {
  ViewRef::{ handle, }
}