///|
/// Deep-clones a JSON value so extension options do not alias the source tree.
fn clone_json(json : Json) -> Json {
  match json {
    Array(items) => Json::array(items.map(item => clone_json(item)))
    Object(object) => Json::object(clone_json_object(object))
    _ => json
  }
}

///|
/// Deep-clones a JSON object map entry by entry.
fn clone_json_object(object : Map[String, Json]) -> Map[String, Json] {
  let cloned : Map[String, Json] = Map([])
  for key, value in object {
    cloned.set(key, clone_json(value))
  }
  cloned
}

///|
/// Converts one normalized extension setting back into its JSON representation.
fn extension_setting_to_json(setting : @manifest.ExtensionSetting) -> Json {
  match setting {
    Disabled => false
    Enabled => true
  }
}

///|
/// Converts one manifest window into its internal runtime JSON object form.
fn window_manifest_to_json(window : @manifest.WindowManifest) -> Json {
  let object : Map[String, Json] = {
    "title": window.title,
    "width": window.width,
    "height": window.height,
  }
  match window.size_hint {
    @manifest.WindowSizeHint::None => ()
    @manifest.WindowSizeHint::Fixed => object["size_hint"] = Json("fixed")
    @manifest.WindowSizeHint::Min => object["size_hint"] = Json("min")
    @manifest.WindowSizeHint::Max => object["size_hint"] = Json("max")
  }
  match window.titlebar_style {
    @manifest.TitlebarStyle::Default => ()
    @manifest.TitlebarStyle::Overlay =>
      object["titlebar_style"] = Json("overlay")
  }
  Json::object(object)
}

///|
/// Converts one manifest entry into its internal runtime JSON object form.
fn app_entry_to_json(entry : @manifest.AppEntry) -> Json {
  match entry {
    Html(value) => { "kind": "html", "value": value }
    Url(value) => { "kind": "url", "value": value }
    File(value) => { "kind": "file", "value": value }
    Asset(value) => { "kind": "asset", "value": value }
  }
}

///|
/// Converts one secondary window manifest into internal runtime JSON.
fn app_window_manifest_to_json(window : @manifest.AppWindowManifest) -> Json {
  {
    "id": window.id,
    "window": window_manifest_to_json(window.window),
    "entry": app_entry_to_json(window.entry),
    "open_on_start": window.open_on_start,
  }
}

///|
fn permission_grant_to_json(grant : @manifest.PermissionGrant) -> Json {
  {
    "window": grant.window(),
    "origin": match grant.origin() {
      App => "app"
      Entry => "entry"
    },
    "extension": grant.extension(),
    "scope": grant.scope(),
  }
}

///|
/// Converts a typed runtime manifest into JSON for native runtime handoff.
pub fn app_manifest_to_json(manifest : @manifest.AppManifest) -> Json {
  let object : Map[String, Json] = {
    "window": window_manifest_to_json(manifest.window),
    "entry": app_entry_to_json(manifest.entry),
    "debug": ToJson::to_json(manifest.debug),
  }
  if manifest.windows.length() > 0 {
    object["windows"] = ToJson::to_json(
      manifest.windows.map(fn(window) { app_window_manifest_to_json(window) }),
    )
  }
  if manifest.extensions.length() > 0 {
    let extensions : Map[String, Json] = Map([])
    for name, setting in manifest.extensions {
      extensions[name] = extension_setting_to_json(setting)
    }
    object["extensions"] = Json::object(extensions)
  }
  if manifest.permissions.length() > 0 {
    object["permissions"] = Json::array(
      manifest.permissions.map(permission_grant_to_json),
    )
  }
  Json::object(object)
}