///|
#cfg(platform="windows")
fn resolve_runtime_menu(
  menu : @native.MenuBar?,
  preferences : @locale.LocalePreferences,
  application_name : String,
) -> @native.MenuBar? {
  menu.map(menu => resolve_menu(menu, preferences, application_name))
}

///|
#cfg(platform="linux")
fn resolve_runtime_menu(
  menu : @native.MenuBar?,
  preferences : @locale.LocalePreferences,
  application_name : String,
) -> @native.MenuBar? {
  menu.map(menu => resolve_menu(menu, preferences, application_name))
}

///|
#cfg(not(any(platform="windows", platform="linux")))
fn resolve_runtime_menu(
  menu : @native.MenuBar?,
  preferences : @locale.LocalePreferences,
  application_name : String,
) -> @native.MenuBar? {
  Some(
    resolve_menu(
      menu.unwrap_or(@native.MenuBar::new(menus=[])),
      preferences,
      application_name,
      macos_application_menu=true,
    ),
  )
}

///|
fn resolve_menu(
  menu_bar : @native.MenuBar,
  preferences : @locale.LocalePreferences,
  application_name : String,
  macos_application_menu? : Bool = false,
) -> @native.MenuBar {
  let catalog = framework_catalog(preferences)
  let resolved : Array[@native.Menu] = []
  let source = menu_bar.definitions()
  let definitions = if macos_application_menu {
    let normalized : Array[@native.Menu] = []
    let mut has_application_menu = false
    for menu in source {
      if menu.definition().0 is Some(@native.MenuRole::Application) {
        normalized.push(menu)
        has_application_menu = true
      }
    }
    if !has_application_menu {
      normalized.push(@native.Menu::role(@native.MenuRole::Application))
    }
    for menu in source {
      if !(menu.definition().0 is Some(@native.MenuRole::Application)) {
        normalized.push(menu)
      }
    }
    normalized
  } else {
    source
  }
  for menu in definitions {
    let (role, label, items) = menu.definition()
    match role {
      Some(role) =>
        resolved.push(
          resolve_standard_menu(role, label, items, catalog, application_name),
        )
      None => {
        let final_label = label.unwrap_or("")
        let final_items = items
          .unwrap_or([])
          .map(item => resolve_menu_item(item, catalog, application_name))
        resolved.push(@native.Menu::new(final_label, items=final_items))
      }
    }
  }
  @native.MenuBar::new(menus=resolved)
}

///|
fn resolve_standard_menu(
  role : @native.MenuRole,
  label : String?,
  items : Array[@native.MenuItem]?,
  catalog : FrameworkCatalog,
  application_name : String,
) -> @native.Menu {
  let final_label = label.unwrap_or(
    standard_menu_label(role, catalog, application_name),
  )
  let source_items = items.unwrap_or(default_menu_items(role))
  let final_items = source_items.map(item => {
    resolve_menu_item(item, catalog, application_name)
  })
  @native.Menu::role(role, label=final_label, items=final_items)
}

///|
fn resolve_menu_item(
  item : @native.MenuItem,
  catalog : FrameworkCatalog,
  application_name : String,
) -> @native.MenuItem {
  let (kind, id, label, key, role) = item.definition()
  match kind {
    "command" =>
      @native.MenuItem::command(id.unwrap_or(""), label.unwrap_or(""), key?)
    "separator" => @native.MenuItem::separator()
    "role" => {
      let role = role.unwrap()
      @native.MenuItem::role(
        role,
        label=label.unwrap_or(
          standard_item_label(role, catalog, application_name),
        ),
        key=key.unwrap_or(standard_item_key(role)),
      )
    }
    _ => abort("invalid typed menu item")
  }
}

///|
fn default_menu_items(role : @native.MenuRole) -> Array[@native.MenuItem] {
  match role {
    Application =>
      [
        @native.MenuItem::role(@native.MenuItemRole::Hide),
        @native.MenuItem::role(@native.MenuItemRole::HideOthers),
        @native.MenuItem::role(@native.MenuItemRole::ShowAll),
        @native.MenuItem::separator(),
        @native.MenuItem::role(@native.MenuItemRole::Quit),
      ]
    Edit =>
      [
        @native.MenuItem::role(@native.MenuItemRole::Undo),
        @native.MenuItem::role(@native.MenuItemRole::Redo),
        @native.MenuItem::separator(),
        @native.MenuItem::role(@native.MenuItemRole::Cut),
        @native.MenuItem::role(@native.MenuItemRole::Copy),
        @native.MenuItem::role(@native.MenuItemRole::Paste),
        @native.MenuItem::role(@native.MenuItemRole::SelectAll),
      ]
    Window =>
      [
        @native.MenuItem::role(@native.MenuItemRole::Minimize),
        @native.MenuItem::role(@native.MenuItemRole::Zoom),
        @native.MenuItem::role(@native.MenuItemRole::Close),
      ]
    File | View | Help => []
  }
}

///|
fn standard_menu_label(
  role : @native.MenuRole,
  catalog : FrameworkCatalog,
  application_name : String,
) -> String {
  match role {
    Application => application_name
    File => catalog.file_menu
    Edit => catalog.edit_menu
    View => catalog.view_menu
    Window => catalog.window_menu
    Help => catalog.help_menu
  }
}

///|
fn standard_item_label(
  role : @native.MenuItemRole,
  catalog : FrameworkCatalog,
  application_name : String,
) -> String {
  match role {
    Quit => catalog.quit_prefix + application_name
    Hide => catalog.hide_prefix + application_name
    HideOthers => catalog.hide_others
    ShowAll => catalog.show_all
    Close => catalog.close
    Minimize => catalog.minimize
    Zoom => catalog.zoom
    Undo => catalog.undo
    Redo => catalog.redo
    Cut => catalog.cut
    Copy => catalog.copy
    Paste => catalog.paste
    SelectAll => catalog.select_all
  }
}

///|
fn standard_item_key(role : @native.MenuItemRole) -> String {
  match role {
    Quit => "q"
    Hide | HideOthers => "h"
    ShowAll | Zoom => ""
    Close => "w"
    Minimize => "m"
    Undo => "z"
    Redo => "Z"
    Cut => "x"
    Copy => "c"
    Paste => "v"
    SelectAll => "a"
  }
}