Frame

use "collections"

actor Frame is CompositeWidget
  """
  A single-child container that draws a box border around its content.
  Optionally displays a title in the top border:

  ```
  ┌─ Title ──────────────────┐
  │                          │
  └──────────────────────────┘
  ```

  Frame always expands to the maximum size allowed by its parent.
  The child receives the interior dimensions (width - 2, height - 2).
  """
  let _state: WidgetState
  var _title: String val
  var _border_color: Color
  var _title_color: Color
  var _child: (Widget tag | None)

  new create(
    p: WidgetParent tag,
    title: String val = "",
    border_color: Color = White,
    title_color: Color = BrightWhite)
  =>
    _state = WidgetState(p)
    _title = title
    _border_color = border_color
    _title_color = title_color
    _child = None

  // -- Widget + CompositeWidget required helpers --

  fun ref state(): WidgetState => _state

  fun ref render_background(): Grid =>
    """
    Draw the border with optional title.
    """
    let w = _state.width
    let h = _state.height
    let title = _title
    let border_color = _border_color
    let title_color = _title_color

    let empty = _state.empty_cell()
    let cells = recover val
      let size = w * h
      let arr = Array[Cell](size)
      for i in Range(0, size) do
        arr.push(empty)
      end

      if (w >= 2) and (h >= 2) then
        DrawingPrimitives.draw_box(arr, w, h, w, h, border_color)

        // Draw title in top border: ┌─ Title ─────┐
        if title.size() > 0 then
          // 6 = 3 chars before title (┌─ ) + 3 chars after ( ─┐)
          let max_title = if w > 6 then w - 6 else 0 end
          let title_len = title.size().min(max_title)
          if (title_len > 0) and (w > 5) then
            try arr(2)? = Cell(' ', 1, border_color, Default, 0) end
            for ti in Range(0, title_len) do
              try
                arr(3 + ti)? = Cell(title(ti)?.u32(), 1, title_color, Default, 0)
              end
            end
            try arr(3 + title_len)? = Cell(' ', 1, border_color, Default, 0) end
          end
        end
      end

      arr
    end

    GridFactory(w, h, cells)

  // -- Override render: blit child into interior with 1-cell inset --

  fun ref render(): Grid =>
    """
    Draw border background, then blit the child grid into the interior.
    """
    let bg = render_background()
    let w = _state.width
    let h = _state.height

    if (w < 3) or (h < 3) or (_state.child_grids.size() == 0) then
      return bg
    end

    // Get the child's grid (first entry)
    let child_grid = try
      (_, let g) = _state.child_grids(0)?
      g
    else
      return bg
    end

    let empty = _state.empty_cell()
    let cells: Array[Cell] iso = recover iso
      let size = w * h
      let arr = Array[Cell](size)
      // Copy background
      for row in Range(0, h) do
        for col in Range(0, w) do
          match bg(col, row)
          | let c: Cell => arr.push(c)
          | GridCellOutOfBounds => arr.push(empty)
          end
        end
      end
      arr
    end

    // Blit child into interior (inset by 1)
    let inner_w = w - 2
    let inner_h = h - 2
    for crow in Range(0, child_grid.height.min(inner_h)) do
      for ccol in Range(0, child_grid.width.min(inner_w)) do
        match child_grid(ccol, crow)
        | let c: Cell =>
          try cells(((crow + 1) * w) + ccol + 1)? = c end
        end
      end
    end

    let cells_val: Array[Cell] val = consume cells
    GridFactory(w, h, cells_val)

  // -- Override resize: resize child to interior dimensions --

  be resize(w: USize, h: USize) =>
    """
    Update frame size and resize the child to the new interior dimensions.
    """
    _state.width = w
    _state.height = h
    match _child
    | let c: Widget tag =>
      if (w >= 2) and (h >= 2) then
        c.resize(w - 2, h - 2)
      end
    end
    render_and_send()

  // -- Container-specific --

  be set_child(widget: Widget tag) =>
    """
    Set the single child widget. If the frame already has a size,
    resizes the child to the interior dimensions immediately.
    """
    _child = widget
    _state.child_grids.push((widget, Grid.filled(0, 0, Cell.empty())))
    let w = _state.width
    let h = _state.height
    if (w >= 2) and (h >= 2) then
      widget.resize(w - 2, h - 2)
    end

  be set_title(title: String val) =>
    """
    Update the frame title and re-render.
    """
    _title = title
    render_and_send()

  be set_border_color(color: Color) =>
    """
    Update the border color and re-render.
    """
    _border_color = color
    render_and_send()