///|
/// Creates an inline HTML application.
pub fn html(
  title : String,
  html : String,
  width~ : Int32 = 900,
  height~ : Int32 = 700,
  debug~ : Bool = false,
  resizable~ : Bool = true,
) -> App {
  App(
    WindowConfig(
      title,
      @manifest.AppEntry::Html(html),
      width~,
      height~,
      size_hint=if resizable { Unconstrained } else { Fixed },
    ),
  )..debug(enabled=debug)
}

///|
/// Creates an inline URL application.
pub fn url(
  title : String,
  url : String,
  width~ : Int32 = 900,
  height~ : Int32 = 700,
  debug~ : Bool = false,
  resizable~ : Bool = true,
) -> App {
  App(
    WindowConfig(
      title,
      @manifest.AppEntry::Url(url),
      width~,
      height~,
      size_hint=if resizable { Unconstrained } else { Fixed },
    ),
  )..debug(enabled=debug)
}

///|
/// Creates an inline file application.
pub fn file(
  title : String,
  path : String,
  width~ : Int32 = 900,
  height~ : Int32 = 700,
  debug~ : Bool = false,
  resizable~ : Bool = true,
) -> App {
  App(
    WindowConfig(
      title,
      @manifest.AppEntry::File(path),
      width~,
      height~,
      size_hint=if resizable { Unconstrained } else { Fixed },
    ),
  )..debug(enabled=debug)
}

///|
/// Creates an inline asset application.
pub fn asset(
  title : String,
  path : String,
  width~ : Int32 = 900,
  height~ : Int32 = 700,
  debug~ : Bool = false,
  resizable~ : Bool = true,
) -> App {
  App(
    WindowConfig(
      title,
      @manifest.AppEntry::Asset(path),
      width~,
      height~,
      size_hint=if resizable { Unconstrained } else { Fixed },
    ),
  )..debug(enabled=debug)
}

///|
/// Creates a web contents view configuration for `WindowHandle::add_view`,
/// following the Electron `new WebContentsView(options)` model: the view
/// renders `url` inside its owning window's content area at explicit
/// top-left bounds, stacked above the window's main page.
pub fn view(
  url : String,
  width~ : Int32,
  height~ : Int32,
  x~ : Int32 = 0,
  y~ : Int32 = 0,
  visible~ : Bool = true,
  z_order~ : Int32 = 0,
  background_color? : String,
) -> ViewConfig {
  ViewConfig(
    width~,
    height~,
    x~,
    y~,
    visible~,
    z_order~,
    initial_url=url,
    background_color?,
  )
}

///|
/// Enables or disables runtime debug mode.
pub fn App::debug(self : App, enabled~ : Bool = true) -> Unit {
  self.debug_level(debug_level_from_bool(enabled))
}

///|
/// Sets the runtime debug level.
pub fn App::debug_level(self : App, debug : Int32) -> Unit {
  self.debug_level_value = debug
}

///|
/// Enables or disables off-screen headless rendering for the application.
///
/// Headless mode does not create a native top-level window, so native dialogs,
/// menus, title bars, and other window-bound UI are unavailable. Set
/// `PROTON_HEADLESS=1` to force this mode for automated test runs.
pub fn App::headless(self : App, enabled~ : Bool = true) -> Unit {
  self.headless_value = enabled
}

///|
/// Controls when Proton enables Chromium's accessibility tree.
///
/// `Automatic` follows assistive-technology activity where the platform can
/// report it. `AlwaysEnabled` keeps the tree available for the whole runtime.
pub fn App::accessibility(self : App, mode : AccessibilityMode) -> Unit {
  self.accessibility_mode_value = mode
}

///|
/// Selects the immutable locale used by this application runtime.
pub fn App::locale(self : App, locale : @locale.Locale) -> Unit {
  self.explicit_locale = Some(locale)
}

///|
/// Ensures only one operating-system process owns this application identity.
/// Later processes forward their URL, document, or reopen activation and exit
/// after the primary application loop accepts it. Forwarding has a five-second
/// deadline and raises `AppRunError` if confirmation is unavailable or the
/// primary is stopping. Success does not wait for asynchronous launch handlers.
/// A timeout never starts another owner or terminates the existing process.
pub fn App::single_instance(self : App, enabled~ : Bool = true) -> Unit {
  self.single_instance_enabled = enabled
}

///|
/// Uses an isolated persistent browser profile below the application's
/// sessionData directory.
pub fn App::session_partition(self : App, partition : String) -> Unit {
  self.session_partition_value = Some(partition.trim().to_owned())
}

///|
/// Configures a startup-wide Chromium proxy. This is read when the native
/// runtime starts and cannot be changed while the app is running.
pub fn App::proxy(self : App, server : String, bypass? : String) -> Unit {
  self.proxy_server_value = Some(server.trim().to_owned())
  self.proxy_bypass_value = bypass.map(value => value.trim().to_owned())
}

///|
/// Sets the maximum time allowed for the native bridge to become ready.
pub fn App::bridge_startup_timeout_ms(self : App, timeout_ms : Int32) -> Unit {
  self.bridge_startup_timeout_value_ms = timeout_ms
}

///|
/// Sets the app-level native menu bar.
pub fn App::menu(self : App, menu : MenuBar) -> Unit {
  self.menu = Some(menu)
}

///|
/// Declares an application URL scheme for launch forwarding and protocol-client
/// controls. Package configuration must declare the same scheme so distributed
/// applications contain the required platform metadata.
pub fn App::url_scheme(self : App, scheme : String) -> Unit {
  let scheme = scheme.trim().to_owned().to_lower()
  if !app_url_scheme_is_valid(scheme) {
    self.validation_errors.push(
      InvalidSetting(name="url_scheme", message="must be a scheme name"),
    )
  } else if !self.url_schemes.contains(scheme) {
    self.url_schemes.push(scheme)
  }
}

///|
/// Cancels browser requests whose URL starts with `url_prefix`.
///
/// Matching is case-sensitive and applies to every browser session created
/// by this application, including secondary windows and web contents views.
pub fn App::web_request_cancel_prefix(self : App, url_prefix : String) -> Unit {
  let url_prefix = url_prefix.trim().to_owned()
  if url_prefix == "" {
    self.validation_errors.push(
      InvalidSetting(
        name="web_request_cancel_prefix",
        message="must not be empty",
      ),
    )
  } else if self.web_request_cancel_prefixes.contains(url_prefix) {
    self.validation_errors.push(
      InvalidSetting(name="web_request_cancel_prefix", message="is duplicated"),
    )
  } else if self.web_request_cancel_prefixes.length() >= 128 {
    self.validation_errors.push(
      InvalidSetting(name="web_request_cancel_prefix", message="limit exceeded"),
    )
  } else {
    self.web_request_cancel_prefixes.push(url_prefix)
  }
}

///|
/// Redirects matching browser requests to `target_url` before loading.
pub fn App::web_request_redirect_prefix(
  self : App,
  url_prefix : String,
  target_url : String,
) -> Unit {
  let url_prefix = url_prefix.trim().to_owned()
  let target_url = target_url.trim().to_owned()
  if url_prefix == "" || target_url == "" {
    self.validation_errors.push(
      InvalidSetting(
        name="web_request_redirect_prefix",
        message="prefix and target URL must not be empty",
      ),
    )
  } else if self.web_request_redirect_prefixes.any(item => item.0 == url_prefix) {
    self.validation_errors.push(
      InvalidSetting(
        name="web_request_redirect_prefix",
        message="is duplicated",
      ),
    )
  } else if self.web_request_redirect_prefixes.length() >= 128 {
    self.validation_errors.push(
      InvalidSetting(
        name="web_request_redirect_prefix",
        message="limit exceeded",
      ),
    )
  } else {
    self.web_request_redirect_prefixes.push((url_prefix, target_url))
  }
}

///|
/// Overrides a request header for URLs matching `url_prefix`.
pub fn App::web_request_header_prefix(
  self : App,
  url_prefix : String,
  header_name : String,
  header_value : String,
) -> Unit {
  let url_prefix = url_prefix.trim().to_owned()
  let header_name = header_name.trim().to_owned()
  if url_prefix == "" || header_name == "" {
    self.validation_errors.push(
      InvalidSetting(
        name="web_request_header_prefix",
        message="prefix and header name must not be empty",
      ),
    )
  } else if self.web_request_header_prefixes.any(item => {
      item.0 == url_prefix && item.1.equal_ignore_ascii_case(header_name)
    }) {
    self.validation_errors.push(
      InvalidSetting(name="web_request_header_prefix", message="is duplicated"),
    )
  } else {
    self.web_request_header_prefixes.push(
      (url_prefix, header_name, header_value),
    )
  }
}

///|
fn app_url_scheme_is_valid(scheme : String) -> Bool {
  guard scheme.length() > 0 else { return false }
  for index, char in scheme {
    let valid = if index == 0 {
      char.is_ascii_alphabetic()
    } else {
      char.is_ascii_alphabetic() ||
      char.is_ascii_digit() ||
      char == '+' ||
      char == '-' ||
      char == '.'
    }
    guard valid else { return false }
  }
  true
}

///|
/// Registers a document extension for single-instance launch forwarding.
/// OS registration belongs to the package configuration.
pub fn App::document_extension(self : App, extension : String) -> Unit {
  let extension = extension.trim().to_owned().to_lower()
  let extension = if extension.has_prefix(".") {
    extension.unsafe_substring(start=1, end=extension.length()).to_string()
  } else {
    extension
  }
  if extension == "" || extension.contains("/") || extension.contains("\\") {
    self.validation_errors.push(
      InvalidSetting(
        name="document_extension",
        message="must be a file extension",
      ),
    )
  } else if !self.document_extensions.contains(extension) {
    self.document_extensions.push(extension)
  }
}

///|
/// Configures the signed update channel used by this application.
/// `public_keys` contains PEM-encoded RSA public-key text, not file paths.
/// Keep private keys outside the application.
pub fn App::update_channel(
  self : App,
  endpoint : String,
  public_keys : Array[String],
  check_on_launch~ : Bool = true,
  freshness_days~ : Int32 = 30,
) -> Unit {
  let endpoint = endpoint.trim().to_owned()
  if !endpoint.has_prefix("https://") {
    self.validation_errors.push(
      InvalidSetting(
        name="update_channel.endpoint",
        message="must be an https URL",
      ),
    )
  } else if public_keys.length() == 0 {
    self.validation_errors.push(
      InvalidSetting(
        name="update_channel.public_keys",
        message="must contain at least one trusted key",
      ),
    )
  } else if freshness_days <= 0 {
    self.validation_errors.push(
      InvalidSetting(
        name="update_channel.freshness_days",
        message="must be positive",
      ),
    )
  } else {
    self.update_channel = Some(ResolvedUpdateChannel::{
      endpoint,
      public_keys: public_keys.copy(),
      check_on_launch,
      freshness_days,
    })
  }
}

///|
/// Registers an application-level handler for URL, file, and reopen inputs.
/// Registers a handler for an update the channel offers.
///
/// Registering one is what turns the automatic check on: an application that
/// has not said what to do when an update exists is not asked to contact a
/// server on launch. The handler runs after the application is up, on the
/// application task group, and never on the startup path.
///
/// It is called with an update that has already been authenticated and found
/// newer. Nothing has been downloaded and nothing will be until the handler
/// asks for it.
pub fn App::on_update_available(
  self : App,
  handler : async (PendingUpdate) -> Unit noraise,
) -> Unit {
  self.update_handlers.push(handler)
}

///|
pub fn App::on_launch_input(
  self : App,
  handler : async (ApplicationContext, RuntimeLaunchInput) -> Unit noraise,
) -> Unit {
  self.launch_input_handlers.push(handler)
}

///|
/// Registers a handler for changes to system appearance. The handler receives
/// the new snapshot on the application task group. Registration does not call
/// the handler; read `system_appearance()` explicitly for the initial state.
/// Window theme changes do not trigger this event.
pub fn App::on_system_appearance_change(
  self : App,
  handler : async (SystemAppearance) -> Unit noraise,
) -> Unit {
  self.system_appearance_handlers.push(handler)
}

///|
/// Registers a handler for taskbar thumbnail-toolbar clicks.
///
/// The handler runs on the application task group with the window that owns
/// the toolbar and the `id` of the button that was clicked. Electron attaches
/// one callback per button; Proton reports the identifier instead so rebuilding
/// the toolbar cannot silently retarget a click. Windows raises the event;
/// other platforms never do, because the thumbnail toolbar is Windows only.
pub fn App::on_thumbar_button_click(
  self : App,
  handler : async (WindowHandle, String) -> Unit noraise,
) -> Unit {
  self.thumbar_button_handlers.push(handler)
}

///|
/// Chooses whether closing the last window exits the application or leaves it
/// running so a later launch input can open another window.
pub fn App::last_window_closed_policy(
  self : App,
  policy : LastWindowClosedPolicy,
) -> Unit {
  self.last_window_closed_policy_value = policy
}

///|
/// Appends an async observer of coalesced native state changes for all windows.
/// Observers do not delay the source operation and may overlap while suspended.
pub fn App::on_window_event(
  self : App,
  handler : async (WindowHandle, WindowEvent) -> Unit noraise,
) -> Unit {
  self.window_event_handlers.push(handler)
}

///|
/// Observes every window Proton creates, Electron's `browser-window-created`.
///
/// Runs synchronously in registration order after the instance is registered,
/// including windows opened later through `WindowManager::open`. It observes
/// creation, not page readiness; configure policy before creating the window.
pub fn App::on_window_created(
  self : App,
  handler : (WindowHandle) -> Unit noraise,
) -> Unit {
  self.window_created_handlers.push(handler)
}

///|
/// Observes every web contents Proton creates, Electron's
/// `web-contents-created`.
///
/// Both a window's main browser page and each web contents view report here,
/// including views declared in `WindowConfig` and views added later
/// through `WindowHandle::add_view`. Runs synchronously in registration order;
/// it is not a setup hook before initial navigation or a page-ready barrier.
pub fn App::on_web_contents_created(
  self : App,
  handler : (WebContentsHandle) -> Unit noraise,
) -> Unit {
  self.web_contents_created_handlers.push(handler)
}

///|
/// Observes renderer processes that disappear, Electron's
/// `render-process-gone`.
///
/// The handler runs for every affected web contents and reports Electron's
/// reason vocabulary with the platform exit code. Proton does not reload the
/// page automatically; `WebContentsHandle::reload` decides
/// what happens next.
pub fn App::on_render_process_gone(
  self : App,
  handler : async (WebContentsHandle, RenderProcessGoneDetails) -> Unit noraise,
) -> Unit {
  self.render_process_gone_handlers.push(handler)
}

///|
/// Observes the application session Proton created for this run, Electron's
/// `session-created`. Runs synchronously in registration order before
/// application startup hooks. Use `setup` for asynchronous setup.
///
/// Proton runs one session per application, so the handler runs once during
/// startup, before the `setup` start hooks.
pub fn App::on_session_created(
  self : App,
  handler : (ApplicationSession) -> Unit noraise,
) -> Unit {
  self.session_created_handlers.push(handler)
}

///|
/// Observes events for main pages and child views: loading changes, main-frame
/// navigations, title updates, load failures, and page-search results.
pub fn App::on_web_contents_event(
  self : App,
  handler : async (WebContentsHandle, WebContentsEvent) -> Unit noraise,
) -> Unit {
  self.contents_event_handlers.push(handler)
}

///|
/// Reviews downloads before CEF chooses a destination.
/// Replaces the previously configured handler.
pub fn App::set_download_handler(
  self : App,
  handler : async (WebContentsHandle, DownloadRequest) -> DownloadDecision noraise,
) -> Unit {
  self.download_handler = Some(handler)
}

///|
/// Reviews invalid TLS certificates. The default is denial.
/// Replaces the previously configured handler.
pub fn App::set_certificate_error_handler(
  self : App,
  handler : async (WebContentsHandle, CertificateError) -> BrowserPermissionDecision noraise,
) -> Unit {
  self.certificate_handler = Some(handler)
}

///|
/// Reviews camera, microphone, and display-capture requests. The default is
/// denial.
/// Replaces the previously configured handler.
pub fn App::set_media_permission_handler(
  self : App,
  handler : async (WebContentsHandle, MediaPermissionRequest) -> BrowserPermissionDecision noraise,
) -> Unit {
  self.media_handler = Some(handler)
}

///|
/// Observes download progress and terminal states.
pub fn App::on_download_event(
  self : App,
  handler : async (WebContentsHandle, DownloadEvent) -> Unit noraise,
) -> Unit {
  self.download_event_handlers.push(handler)
}

///|
/// Runs before an accepted application quit starts closing windows.
///
/// This is Electron's cancelable `before-quit` step. `Prevent` aborts the quit
/// request, keeps every window open, and leaves the application running; a
/// later quit request runs the handler again.
/// Replaces the previously configured handler.
pub fn App::set_before_quit_handler(
  self : App,
  handler : async (ApplicationContext) -> ApplicationQuitDecision noraise,
) -> Unit {
  self.before_quit_handler = Some(handler)
}

///|
/// Runs after every application window has closed and before Proton
/// terminates.
///
/// This is Electron's cancelable `will-quit` step. `Prevent` aborts the
/// termination and leaves the application running without windows; use
/// `LastWindowClosedPolicy::KeepRunning` semantics to open a window again.
/// Replaces the previously configured handler.
pub fn App::set_will_quit_handler(
  self : App,
  handler : async (ApplicationContext) -> ApplicationQuitDecision noraise,
) -> Unit {
  self.will_quit_handler = Some(handler)
}

///|
/// Observes the final `quit` step with the process exit code.
///
/// Runs synchronously in registration order after exit is committed and before
/// lifecycle shutdown and native cleanup. Register async cleanup with
/// `context.add_cleanup` during `setup`.
/// `exit_code` is requested by `ApplicationContext::quit` or
/// `ApplicationContext::exit`.
pub fn App::on_quit(
  self : App,
  handler : (ApplicationContext, Int32) -> Unit noraise,
) -> Unit {
  self.quit_handlers.push(handler)
}

///|
/// Adds a secondary window owned by the standard application lifecycle.
pub fn App::add_window(
  self : App,
  id : String,
  config : WindowConfig,
  open_on_start~ : Bool = true,
) -> Unit {
  let normalized_id = id.trim().to_owned()
  if normalized_id == "" || normalized_id == "main" {
    self.validation_errors.push(
      InvalidSetting(
        name="window.id",
        message=if normalized_id == "main" {
          "\"main\" is reserved for the primary window"
        } else {
          "must not be empty"
        },
      ),
    )
  } else if self.windows.any(window => window.0 == normalized_id) {
    self.validation_errors.push(
      InvalidSetting(
        name="window.id",
        message="duplicate secondary window id: " + normalized_id,
      ),
    )
  } else {
    self.windows.push((normalized_id, config.snapshot(), open_on_start))
  }
}

///|
fn manifest_window_size_hint(hint : WindowSizeHint) -> @manifest.WindowSizeHint {
  match hint {
    WindowSizeHint::Unconstrained => @manifest.WindowSizeHint::None
    WindowSizeHint::Fixed => @manifest.WindowSizeHint::Fixed
    WindowSizeHint::Min => @manifest.WindowSizeHint::Min
    WindowSizeHint::Max => @manifest.WindowSizeHint::Max
  }
}

///|
fn manifest_titlebar_style(style : TitlebarStyle) -> @manifest.TitlebarStyle {
  match style {
    TitlebarStyle::Default => @manifest.TitlebarStyle::Default
    TitlebarStyle::Overlay => @manifest.TitlebarStyle::Overlay
  }
}

///|
fn manifest_window_theme(
  theme : WindowThemePreference,
) -> @manifest.WindowThemePreference {
  match theme {
    WindowThemePreference::System => @manifest.WindowThemePreference::System
    WindowThemePreference::Light => @manifest.WindowThemePreference::Light
    WindowThemePreference::Dark => @manifest.WindowThemePreference::Dark
  }
}

///|
/// Adds one package registrar for typed application commands.
///
/// Registration runs before any window is created and is sealed before the
/// renderer bridge starts accepting requests. Only the operations registered
/// by this callback are granted to its targets; the primary configured entry is
/// the default target.
pub fn App::commands(
  self : App,
  register : (@proton_command.CommandRegistrar) -> Unit raise,
  targets? : Array[RendererTarget],
) -> Unit {
  self.command_capabilities.push(AppCommandCapability::{
    register,
    targets: renderer_capability_targets(targets),
  })
}

///|
/// Appends application setup, awaited in registration order before automatic
/// startup windows open. Unlike observers, setup can fail startup. Register
/// acquired resources immediately with `context.add_cleanup`.
pub fn App::setup(
  self : App,
  setup : async (ApplicationContext) -> Unit,
) -> Unit {
  self.application_setup.push(setup)
}

///|
/// Appends setup awaited in registration order for each window after page
/// readiness, including reopened windows.
/// Register instance-owned resources with `context.add_cleanup`.
pub fn App::setup_window(
  self : App,
  setup : async (WindowContext) -> Unit,
) -> Unit {
  self.window_setup.push(setup)
}

///|
/// Grants one typed extension capability to its renderer targets.
///
/// The default target is the configured entry of the primary window. The
/// capability registers its backend handlers and exposes only its validated
/// scope to those renderers.
pub fn App::capability(
  self : App,
  capability : @proton_extension.RendererCapability,
  targets? : Array[RendererTarget],
) -> Unit {
  let id = capability.id()
  if id.trim().to_owned() == "" {
    self.validation_errors.push(
      InvalidSetting(name="capability.id", message="must not be empty"),
    )
  } else {
    let command_spec = Some(capability.command_spec()) catch {
      error => {
        self.validation_errors.push(
          ExtensionAdaptationFailed(extension_id=id, error~),
        )
        None
      }
    }
    match command_spec {
      Some(spec) =>
        self.extension_capabilities.push(ExtensionCapabilityBinding::{
          definition_id: capability.definition_id(),
          spec,
          event_source: capability.event_source(),
          scope: capability.scope(),
          targets: renderer_capability_targets(targets),
        })
      None => ()
    }
  }
}

///|
/// Selects a window's configured entry as a renderer capability target.
pub fn RendererTarget::entry(window~ : String = "main") -> RendererTarget {
  RendererTarget::{ window, bundled: false, }
}

///|
/// Selects Proton's bundled application origin in one window as a renderer
/// capability target.
pub fn RendererTarget::bundled(window~ : String = "main") -> RendererTarget {
  RendererTarget::{ window, bundled: true, }
}

///|
fn renderer_capability_targets(
  configured : Array[RendererTarget]?,
) -> Array[RendererTarget] {
  match configured {
    Some(targets) => targets.copy()
    None => [RendererTarget::entry()]
  }
}