// =============================================================================
// Layout Dispatcher
// =============================================================================

///|
/// Default dispatch for top module
fn default_dispatch() -> @node.DispatchFn {
  @node.DispatchFn(fn(node, ctx, dispatch) {
    @dispatch.compute(node, ctx, dispatch)
  })
}

///|
/// Compute layout for a node tree with given viewport
/// Dispatches to the appropriate layout algorithm based on display type
pub fn compute_layout(
  node : @node.Node,
  viewport : @types.Size[Double],
) -> @types.Layout {
  let ctx : @types.LayoutContext = {
    available_width: viewport.width,
    available_height: Some(viewport.height),
    sizing_mode: @types.MaxContent,
    viewport_width: viewport.width,
    viewport_height: viewport.height,
    stretch_width: false,
    stretch_height: false,
  }
  dispatch_compute(node, ctx)
}

///|
/// Compute layout with warnings
pub fn compute_layout_with_warnings(
  node : @node.Node,
  viewport : @types.Size[Double],
) -> @types.LayoutResult {
  let ctx : @types.LayoutContext = {
    available_width: viewport.width,
    available_height: Some(viewport.height),
    sizing_mode: @types.MaxContent,
    viewport_width: viewport.width,
    viewport_height: viewport.height,
    stretch_width: false,
    stretch_height: false,
  }
  dispatch_compute_with_warnings(node, ctx)
}

///|
/// Dispatch to appropriate layout algorithm based on display type
fn dispatch_compute(
  node : @node.Node,
  ctx : @types.LayoutContext,
) -> @types.Layout {
  let dispatch = default_dispatch()
  match node.style.display {
    @types.Flex | @types.InlineFlex => @flex.compute(node, ctx, dispatch)
    @types.Block | @types.Inline | @types.InlineBlock | @types.FlowRoot =>
      @block.compute(node, ctx, dispatch)
    @types.Grid | @types.InlineGrid =>
      @grid.compute_grid_layout(
        node,
        ctx.available_width,
        ctx.available_height.unwrap_or(0.0),
        dispatch,
      )
    @types.Table | @types.InlineTable => @table.compute(node, ctx, dispatch)
    @types.Display::None | @types.Contents =>
      {
        // display: none/contents - return zero-sized layout
        id: node.id,
        x: 0.0,
        y: 0.0,
        width: 0.0,
        height: 0.0,
        margin: @types.Rect::zero(),
        padding: @types.Rect::zero(),
        border: @types.Rect::zero(),
        overflow_x: @types.Visible,
        overflow_y: @types.Visible,
        scroll_width: 0.0,
        scroll_height: 0.0,
        children: [],
        text: node.text,
      }
    // Table sub-elements - handled as blocks when encountered outside table context
    @types.TableRow
    | @types.TableCell
    | @types.TableCaption
    | @types.TableRowGroup
    | @types.TableHeaderGroup
    | @types.TableFooterGroup
    | @types.TableColumn
    | @types.TableColumnGroup => @block.compute(node, ctx, dispatch)
  }
}

///|
/// Dispatch with warnings
fn dispatch_compute_with_warnings(
  node : @node.Node,
  ctx : @types.LayoutContext,
) -> @types.LayoutResult {
  let dispatch = default_dispatch()
  match node.style.display {
    @types.Flex | @types.InlineFlex =>
      @flex.compute_with_warnings(node, ctx, dispatch)
    @types.Block | @types.Inline | @types.InlineBlock | @types.FlowRoot =>
      @block.compute_with_warnings(node, ctx, dispatch)
    @types.Grid | @types.InlineGrid => {
      let layout = @grid.compute_grid_layout(
        node,
        ctx.available_width,
        ctx.available_height.unwrap_or(0.0),
        dispatch,
      )
      { layout, warnings: [] }
    }
    @types.Table | @types.InlineTable => {
      let layout = @table.compute(node, ctx, dispatch)
      { layout, warnings: [] }
    }
    @types.Display::None | @types.Contents =>
      {
        layout: {
          id: node.id,
          x: 0.0,
          y: 0.0,
          width: 0.0,
          height: 0.0,
          margin: @types.Rect::zero(),
          padding: @types.Rect::zero(),
          border: @types.Rect::zero(),
          overflow_x: @types.Visible,
          overflow_y: @types.Visible,
          scroll_width: 0.0,
          scroll_height: 0.0,
          children: [],
          text: node.text,
        },
        warnings: [],
      }
    // Table sub-elements - handled as blocks when encountered outside table context
    @types.TableRow
    | @types.TableCell
    | @types.TableCaption
    | @types.TableRowGroup
    | @types.TableHeaderGroup
    | @types.TableFooterGroup
    | @types.TableColumn
    | @types.TableColumnGroup =>
      @block.compute_with_warnings(node, ctx, dispatch)
  }
}

///|
/// Compute block layout directly
pub fn compute_block_layout(
  node : @node.Node,
  ctx : @types.LayoutContext,
) -> @types.Layout {
  @block.compute(node, ctx, default_dispatch())
}

///|
/// Compute flex layout directly
pub fn compute_flex_layout(
  node : @node.Node,
  ctx : @types.LayoutContext,
) -> @types.Layout {
  @flex.compute(node, ctx, default_dispatch())
}

///|
/// Compute element shift (CLS)
pub fn compute_element_shift(
  before : @types.BoundingRect,
  after : @types.BoundingRect,
  viewport : @types.Size[Double],
) -> @webvitals.LayoutShift {
  @webvitals.compute_element_shift(before, after, viewport)
}

///|
/// Compute total CLS score
pub fn compute_total_cls(
  before : Array[@types.BoundingRect],
  after : Array[@types.BoundingRect],
  viewport : @types.Size[Double],
) -> Double {
  @webvitals.compute_total(before, after, viewport)
}