///|
/// Page-owned UI lifetime; created only by `root` inside a Page builder.
struct UiContext {
  page : @minimoon.PageContext
  visible : @minimoon.Val[Bool]
  toasts : ToastQueue
}

///|
pub fn UiContext::visible(self : UiContext) -> @minimoon.Val[Bool] {
  self.visible
}

///|
pub fn root(
  page : @minimoon.PageContext,
  build : (UiContext) -> @minimoon.Val[@minimoon.Node],
  theme? : @minimoon.Val[@theme.Theme] = @minimoon.Val::constant(@theme.theme()),
  toast_limit? : Int = 5,
) -> @minimoon.Val[@minimoon.Node] {
  guard toast_limit > 0 && toast_limit <= 100 else {
    abort("toast limit must be between 1 and 100")
  }
  let (visible, _) = @minimoon.create_state(
    true,
    update=(_, next, _) => @minimoon.no_cmd(next),
    subscriptions=(_, emit) => {
      @minimoon.Sub::batch([page.on_hide(emit(false)), page.on_show(emit(true))])
    },
  )
  let context = UiContext::{
    page,
    visible,
    toasts: create_toast_queue(page, visible, toast_limit),
  }
  @minimoon.Val::view2(theme, build(context), (theme, content) => {
    @minimoon.layer_root(class=theme.class_name(), style=theme.style(), [
      content,
      @minimoon.div(
        id="mmui-viewport",
        style="position:fixed;left:0;top:0;width:100vw;height:100vh;pointer-events:none;",
        semantics=@minimoon.semantics(hidden=true),
        @minimoon.nothing(),
      ),
    ])
  })
}

///|
/// Combine independently owned components without allocating state in a view.
pub fn stack(
  children : Array[@minimoon.Val[@minimoon.Node]],
) -> @minimoon.Val[@minimoon.Node] {
  let mut result = @minimoon.Val::constant(@minimoon.nothing())
  for child in children {
    result = @minimoon.Val::view2(result, child, (left, right) => {
      @minimoon.fragment([left, right])
    })
  }
  result
}