import { LayoutNode, Width, Alignment, BorderStyle } from "std::ui/layout"

/** @module
  @summary Build tables for terminal output.
  Build tables with aligned headers, rows, and footers. Pass the cells as
  arrays, then call `render` from `std::ui/layout` to display the table.
  You can also place a table inside a `box`, `row`, or `column` layout.

  ```ts
  import { table } from "std::ui/table"
  import { render } from "std::ui/layout"

  const t = table(
    title: "Employees",
    header: ["ID", "Name", "Balance"],
    body: [
      ["1", "Dave",  "100"],
      ["2", "Alice", "-50"],
    ],
    footer: [["", "Total", "50"]],
  )
  print(render(t))
  ```

  Use a trailing block to add rows as you build the table:

  ```ts
  const t = table(title: "Employees") as builder {
    builder.header("ID", "Name", "Balance")
    builder.row("1", "Dave", "100")
    builder.row("2", "Alice", "-50")
    builder.footer("", "Total", "50")
  }
  ```
*/

/**
  A table cell. Use a string for plain text or a layout node for styled
  content, such as `text("-50", fgColor: "red")` from `std::ui/layout`.
*/
export type Cell = string | LayoutNode

// The parser does not accept `Cell[][]` when `Cell` is a union, so the row
// type gets its own alias.
/** One row of cells in a table's body or footer. */
export type CellRow = Cell[]

/**
  Configure each column's alignment, width, and text color. Columns without
  a specification use start alignment and size themselves to their content.

  - `align`: horizontal alignment within the column.
  - `minWidth`: minimum content width in terminal cells, excluding padding.
  - `width`: content width in terminal cells, or a percentage of the space
    left after borders, padding, separators, and columns sized by a number
    or their content. `"full"` means `"100%"`. Percentage columns need a
    width set on the table or inherited from a sized box or column layout.
  - `fgColor`: default color for plain strings and uncolored `text` cells.

  Text wraps to the column width. Content that cannot wrap can widen the
  column beyond its requested width.
*/
export type ColumnSpec = {
  align?: Alignment;
  minWidth?: number;
  width?: Width;
  fgColor?: string
}

/**
  Methods available inside a `table` trailing block:

  - `columns(specs)`: replace the column specifications.
  - `caption(text)`: replace the caption.
  - `header(...cells)`: replace the header row.
  - `row(...cells)`: append a body row.
  - `footer(...cells)`: append a footer row.

  These methods also work with data passed in the `table` arguments.
*/
export type TableBuilder = {
  columns: any;
  caption: any;
  header: any;
  row: any;
  footer: any
}

def _setTableColumns(state: any, specs: ColumnSpec[]): any {
  state.columns = specs
  return null
}

def _setTableCaption(state: any, text: string): any {
  state.caption = text
  return null
}

def _setTableHeader(state: any, ...cells: Cell[]): any {
  state.header = cells
  return null
}

def _addTableRow(state: any, ...cells: Cell[]): any {
  state.body.push(cells)
  return null
}

def _addTableFooter(state: any, ...cells: Cell[]): any {
  state.footer.push(cells)
  return null
}

def _makeTableBuilder(state: any): TableBuilder {
  return {
    columns: _setTableColumns.partial(state: state),
    caption: _setTableCaption.partial(state: state),
    header: _setTableHeader.partial(state: state),
    row: _addTableRow.partial(state: state),
    footer: _addTableFooter.partial(state: state)
  }
}

/**
  Use the array arguments for existing data, or a trailing block to build
  rows in a loop. The block can replace the header, columns, and caption,
  and append to the supplied body and footer rows.

  With no `width`, the table sizes itself to its content and wraps its
  widest columns to fit the terminal or enclosing layout. With no terminal,
  the available width defaults to 80 cells. Columns with an explicit width
  keep that width. Raw cells do not wrap, so a table containing them can
  exceed the available width.

  Set `borderStyle: "none"` for a table without an outer frame. The divider
  flags still apply. To leave blank space between columns, set
  `columnDividers: false` and choose a `columnGap`.
*/
export def table(
  title: string = "",
  titleColor: string = "",
  borderStyle: BorderStyle = "rounded",
  borderColor: string = "",
  caption: string = "",
  cellPadding: number = 1,
  width: Width = null,
  columns: ColumnSpec[] = null,
  header: Cell[] = null,
  body: CellRow[] = null,
  footer: CellRow[] = null,
  headerDivider: boolean = true,
  footerDivider: boolean = true,
  rowDividers: boolean = false,
  columnDividers: boolean = true,
  columnGap: number = 0,
  block: (TableBuilder) -> void = null,
): LayoutNode {
  """
  Build a terminal table. Every row must have the same number of cells.
  Call `render` from `std::ui/layout` to display it.

  @param title - Title above the rows, placed in the top border when it fits
  @param borderStyle - Outer frame style; "none" leaves the divider flags in effect
  @param caption - Caption shown beneath the table
  @param cellPadding - Blank terminal cells on each side of a cell's content
  @param width - Total width in terminal cells, "full", or "N%" of the available width
  @param columns - One specification per column; percentage widths need a table width set or inherited
  @param rowDividers - Draw a divider between body rows
  @param columnGap - Blank terminal cells between columns when columnDividers is false
  """
  const bodyArr: any[] = []
  if (body != null) {
    for (r in body) {
      bodyArr.push(r)
    }
  }
  const footerArr: any[] = []
  if (footer != null) {
    for (r in footer) {
      footerArr.push(r)
    }
  }
  const state: any = {
    header: header,
    body: bodyArr,
    footer: footerArr,
    columns: columns,
    caption: caption
  }
  if (block != null) {
    block(_makeTableBuilder(state))
  }
  const attrs: any = {
    title: title,
    titleColor: titleColor,
    borderStyle: borderStyle,
    borderColor: borderColor,
    caption: state.caption,
    cellPadding: cellPadding,
    columns: state.columns,
    header: state.header,
    body: state.body,
    footer: state.footer,
    headerDivider: headerDivider,
    footerDivider: footerDivider,
    rowDividers: rowDividers,
    columnDividers: columnDividers,
    columnGap: columnGap
  }
  if (width != null) {
    attrs.width = width
  }
  return {
    type: "table",
    attrs: attrs,
    children: []
  }
}
