///|
const ContextMenuContainerStyle : String = "position:fixed;left:var(--sumi-ctx-x,0px);top:var(--sumi-ctx-y,0px);z-index:50;display:flex;flex-direction:column;gap:2px;min-width:200px;padding:4px;border:1px solid var(--sumi-stroke-secondary,rgba(255,255,255,0.04));border-radius:var(--sumi-radius-lg,12px);background:var(--sumi-menu,#262626);box-shadow:var(--sumi-shadow-menu,0px 8px 56px 0px rgba(0,0,0,0.24))"

///|
/// A right-click menu wrapping any target. Fully controlled: the caller owns
/// `open`; right-clicking the target emits `on_open_change(true)` while the
/// theme's static event layer suppresses the native menu and records the
/// pointer position into the `--sumi-ctx-x/y` variables the panel reads.
/// Closes on outside click, Escape, and selection; arrow keys move focus.
///
/// ```moonbit nocheck
/// context_menu(
///   entries=[
///     Item(MenuItem::new("rename", "Rename", shortcut="R")),
///     Separator,
///     Item(MenuItem::new("delete", "Delete", shortcut="⌫")),
///   ],
///   open=ctx_open,
///   on_open_change=set_ctx_open.map(v => _ => v),
///   on_select=set_action.map(v => _ => v),
///   [canvas_card],
/// )
/// ```
pub fn[C : @html.IsChildren] context_menu(
  entries~ : Array[MenuEntry],
  open~ : Bool,
  on_open_change? : @internal.Emit[Bool],
  selected? : String,
  on_select? : @internal.Emit[String],
  id? : String,
  class? : String,
  attrs? : @html.Attrs,
  style? : Array[String] = [],
  menu_style? : Array[String] = [],
  children : C,
) -> @html.Html {
  let close : @cmd.Cmd? = on_open_change.map(emit => emit(false))
  let request_open : @cmd.Cmd? = on_open_change.map(emit => emit(true))
  let element_attrs = @internal.sumi_attrs(attrs).data_set(
    "slot", "context-menu-root",
  )
  // The event payload is unused: the guard script has already recorded the
  // pointer position and suppressed the native menu by the time this runs.
  if request_open is Some(cmd) {
    ignore(element_attrs.on_contextmenu(_ => cmd))
  }
  let click_for = fn(item : MenuItem) -> @cmd.Cmd? {
    if item.disabled {
      None
    } else {
      let select_cmd = on_select.map(emit => emit(item.value))
      match (close, select_cmd) {
        (Some(c), Some(s)) => Some(@cmd.batch([c, s]))
        (Some(c), None) => Some(c)
        (None, Some(s)) => Some(s)
        (None, None) => None
      }
    }
  }
  let overlay : @html.Html = match close {
    Some(cmd) =>
      @html.div(
        style=[MenuOverlayStyle],
        hidden=!open,
        on_click=cmd,
        attrs=@html.Attrs::build()
          .data_set("slot", "menu-overlay")
          .aria_hidden("true"),
        "",
      )
    None => @html.nothing
  }
  @html.div(
    style=@internal.sumi_styles(
      [@internal.SumiBoxSizing, "display:contents"],
      style,
    ),
    id?,
    class?,
    attrs=element_attrs,
    [
      @internal.sumi_slot(children),
      @html.fragment([
        overlay,
        @html.div(
          style=@internal.sumi_styles(
            [
              @internal.SumiBoxSizing,
              @internal.SumiFontSans,
              @internal.SumiTextRendering,
              ContextMenuContainerStyle,
            ],
            menu_style,
          ),
          hidden=!open,
          attrs=@html.Attrs::build().data_set("slot", "menu").role("menu"),
          entries.map(entry => menu_entry_view(entry, selected, click_for)),
        ),
      ]),
    ],
  )
}