///|
priv enum CalendarRequest {
  Choose(@headless.CalendarDate)
  ShiftMonth(Int)
}

///|
priv enum PaginationRequest {
  Target(Int)
  Step(Int)
}

///|
priv enum DataTableRequest {
  Sort(String)
  Select(String)
  Column(String)
  Query(String)
  PageStep(Int)
}

///|
fn data_toggle_key(keys : Array[String], key : String) -> Array[String] {
  if keys.contains(key) {
    keys.filter(value => value != key)
  } else {
    let next = keys.copy()
    next.push(key)
    next
  }
}

///|
fn[T : Eq] data_table_dispatch(
  rows : @minimoon.Val[Array[T]],
  state : @minimoon.Val[TableState],
  columns : Array[DataColumn[T]],
  row_key : (T) -> String,
  change : @minimoon.Emit[TableState],
  selectable : Bool,
) -> @minimoon.Emit[DataTableRequest] {
  form_dispatch(
    @minimoon.Val::map2(rows, state, (rows, state) => (rows, state)),
    (input, request : DataTableRequest) => {
      let (rows, state) = input
      let next = match request {
        Sort(key) => {
          guard columns.any(column => column.key == key && column.sortable) else {
            return @minimoon.none
          }
          {
            ..state,
            page: 0,
            sort_key: key,
            descending: key == state.sort_key && !state.descending,
          }
        }
        Select(key) => {
          guard selectable && rows.any(row => row_key(row) == key) else {
            return @minimoon.none
          }
          { ..state, selected_keys: data_toggle_key(state.selected_keys, key), }
        }
        Column(key) => {
          guard columns.any(column => column.key == key && column.hideable) else {
            return @minimoon.none
          }
          {
            ..state,
            hidden_columns: data_toggle_key(state.hidden_columns, key),
          }
        }
        Query(query) => { ..state, query, page: 0, }
        PageStep(delta) => {
          let total = rows
            .filter(row => {
              state.query == "" ||
              columns.any(column => {
                column.searchable && (column.text)(row).contains(state.query)
              })
            })
            .length()
          let count = @headless.page_count(total, state.page_size.max(1))
          let current = state.page.clamp(min=0, max=count - 1)
          { ..state, page: (current + delta).clamp(min=0, max=count - 1), }
        }
      }
      if next == state {
        @minimoon.none
      } else {
        change(next)
      }
    },
  )
}