///|
/// Touch-native compound sidebar adapted from RUI b1291945 (MIT).
pub(all) enum SidebarSide {
  SidebarLeft
  SidebarRight
} derive(Eq, Debug)

///|
pub(all) enum SidebarVariant {
  SidebarDefault
  SidebarFloating
  SidebarInset
} derive(Eq, Debug)

///|
pub(all) enum SidebarCollapsible {
  SidebarOffcanvas
  SidebarIcon
  SidebarFixed
} derive(Eq, Debug)

///|
pub(all) enum SidebarMenuButtonSize {
  SidebarMenuSm
  SidebarMenuDefault
  SidebarMenuLg
} derive(Eq, Debug)

///|
pub(all) enum SidebarMenuButtonVariant {
  SidebarMenuButtonDefault
  SidebarMenuButtonOutline
} derive(Eq, Debug)

///|
pub(all) struct SidebarState {
  collapsed : Bool
  mobile_open : Bool
  width : Double
} derive(Eq, Debug)

///|
pub fn sidebar_state(
  collapsed? : Bool = false,
  mobile_open? : Bool = false,
  width? : Double = 256.0,
) -> SidebarState {
  guard width > 0.0 && width - width == 0.0 else {
    abort("sidebar width must be finite and positive")
  }
  { collapsed, mobile_open, width, }
}

///|
priv struct SidebarRailDrag {
  pointer : Int
  start : Double
  initial : Double
  minimum : Double
  maximum : Double
  side : SidebarSide
  moved : Bool
} derive(Eq)

///|
priv struct SidebarTooltip {
  id : String
  side : Side
  align : Alignment
  gap : Double
  offset : Double
  bounds : @headless.Bounds?
} derive(Eq)

///|
priv struct SidebarTransient {
  token : Int
  drag : SidebarRailDrag?
  suppress_tap : Bool
  tooltip : SidebarTooltip?
} derive(Eq)

///|
priv enum SidebarMsg {
  SetCollapsed(Bool)
  SetMobile(Bool)
  ToggleCollapsed
  ToggleMobile
  SetWidth(Double)
  RailTap
  RailStart(SidebarSide, Double, Double, @minimoon.TouchDetail)
  RailMove(@minimoon.TouchDetail)
  RailEnd(@minimoon.TouchDetail)
  RailCancel(@minimoon.TouchDetail)
  Hide
  ClearTransient
  ShowTooltip(SidebarTooltip)
  CloseTooltip
  TooltipMeasured(Int, Result[Array[@minimoon.NodeRect?], @minimoon.HostError])
}

///|
struct SidebarScope {
  id : String
  state : SidebarState
  visible : Bool
  transient : SidebarTransient
  send : @minimoon.Emit[SidebarMsg]
}

///|
/// Explicit rendering context replaces browser ancestor lookup and CSS selectors.
struct SidebarSurfaceScope {
  owner : SidebarScope
  side : SidebarSide
  variant : SidebarVariant
  collapsible : SidebarCollapsible
  mobile : Bool
}

///|
pub fn sidebar_is_collapsed(scope : SidebarScope) -> Bool {
  scope.state.collapsed
}

///|
pub fn sidebar_is_mobile_open(scope : SidebarScope) -> Bool {
  scope.state.mobile_open
}

///|
pub fn sidebar_width(scope : SidebarScope) -> Double {
  scope.state.width
}

///|
pub fn sidebar_expand(scope : SidebarScope) -> @minimoon.Cmd {
  (scope.send)(SetCollapsed(false))
}

///|
pub fn sidebar_collapse(scope : SidebarScope) -> @minimoon.Cmd {
  (scope.send)(SetCollapsed(true))
}

///|
pub fn sidebar_open_mobile(scope : SidebarScope) -> @minimoon.Cmd {
  (scope.send)(SetMobile(true))
}

///|
pub fn sidebar_close_mobile(scope : SidebarScope) -> @minimoon.Cmd {
  (scope.send)(SetMobile(false))
}

///|
pub fn sidebar_set_width(scope : SidebarScope, width : Double) -> @minimoon.Cmd {
  guard width > 0.0 && width - width == 0.0 else {
    abort("sidebar width must be finite and positive")
  }
  (scope.send)(SetWidth(width))
}

///|
fn sidebar_tooltip_bounds(
  tip : SidebarTooltip,
  anchor : @minimoon.NodeRect,
  surface : @minimoon.NodeRect,
  window : @minimoon.NodeRect,
) -> @headless.Bounds {
  let horizontal = tip.side == Left || tip.side == Right
  let mut left = match tip.side {
    Left => anchor.left - surface.width - tip.gap
    Right => anchor.right + tip.gap
    _ => anchor.left
  }
  let mut top = match tip.side {
    Top => anchor.top - surface.height - tip.gap
    Bottom => anchor.bottom + tip.gap
    _ => anchor.top
  }
  if horizontal {
    if left < window.left {
      left = anchor.right + tip.gap
    } else if left + surface.width > window.right {
      left = anchor.left - surface.width - tip.gap
    }
    top = (match tip.align {
        Start => anchor.top
        Center => anchor.top + (anchor.height - surface.height) / 2.0
        End => anchor.bottom - surface.height
      }) +
      tip.offset
  } else {
    if top < window.top {
      top = anchor.bottom + tip.gap
    } else if top + surface.height > window.bottom {
      top = anchor.top - surface.height - tip.gap
    }
    left = (match tip.align {
        Start => anchor.left
        Center => anchor.left + (anchor.width - surface.width) / 2.0
        End => anchor.right - surface.width
      }) +
      tip.offset
  }
  {
    left: left
    .max(window.left)
    .min((window.right - surface.width).max(window.left)),
    top: top
    .max(window.top)
    .min((window.bottom - surface.height).max(window.top)),
    width: surface.width.min(window.width),
    height: surface.height.min(window.height),
  }
}