import type { JSXBase } from '../../stencil-public-runtime';
import type { VNode, FunctionalComponent } from '../../stencil-public-runtime';

export declare namespace TableTypes {
  /** The column property */
  type ColumnProp = string | number;

  /** Possible positions. Horizontally means l/r. Vertically means top/bottom */
  type Position = 'start' | 'end' | 'top' | 'bottom';

  /** A function to assess whether a rendered row will be pinned to the top or bottom */
  type PinFunc = (props?: RowDataSchemaModel) => Position;

  /** Function called, via intersection observer, when an element's pinned status changes */
  type PinnedCb = (
    positions: { [key in Position]?: boolean },
    target: Element
  ) => void;

  /** Potential column ordering options */
  type Order = 'asc' | 'desc' | undefined;

  /** Column filtering */
  interface Filter {
    prop: TableTypes.Prop;
    filter: string | boolean | string[];
  }

  /** Column sorting */
  interface Sort {
    prop: TableTypes.Prop;
    order: TableTypes.Order;
  }

  /** Potential html properties which can be passed to a cell or column */
  type CellProps = JSXBase.ThHTMLAttributes<HTMLTableCellElement>;

  /** Potential html properties which can be passed to a row */
  type RowProps = JSXBase.HTMLAttributes<HTMLTableRowElement>;

  /** data model passed to cell rendering functions */
  interface RenderModel<T = RowGeneric> {
    /** The column property being iterated over */
    prop: keyof T;
    /** The data for this cell */
    cellModel: any;
    /** The column config object */
    column: ColumnConfig<T>;
    /** The row index currently being iterated over */
    rowIndex: number;
    /** The row model currently being iterated over  */
    rowModel: RowData<T>;
  }

  /** data model passed to row rendering functions */
  interface RowDataSchemaModel<T = RowGeneric> {
    /** the current data row model being iterated over */
    rowModel?: RowData<T>;
    /** the current data row index  */
    rowIndex?: number;
    /** The row being rendered. You *must* nest this in your render function otherwise it will not be shown */
    renderedRow?: VNode;
  }

  /** A JSX `createElement` function passed to rendering functions.
   * You can use this to create markup outside of JSX environments
   * e.g. https://reactjs.org/docs/react-without-jsx.html */
  interface HFunc<T> {
    (tag: any): T;
  }
  interface HFunc<T> {
    (tag: any, data: any): T;
  }
  interface HFunc<T> {
    (tag: any, text: string): T;
  }
  interface HFunc<T> {
    (sel: any, children: Array<T | undefined | null>): T;
  }
  interface HFunc<T> {
    (sel: any, data: any, text: string): T;
  }
  interface HFunc<T> {
    (sel: any, data: any, children: Array<T | undefined | null>): T;
  }
  interface HFunc<T> {
    (sel: any, data: any, children: T): T;
  }

  /**
   * Properties that can be used with the row template `TableCell` jsx partial
   */
  type TableRowCellHelperProps = {
    header?: boolean;
    wrap?: boolean;
    cellProps?: CellProps;
    wrapperProps?: JSXBase.HTMLAttributes<HTMLDivElement>;
  };

  /**
   * A row render function, called during a any row's render to the dom.
   * It can be used to add extra markup around any given table row
   **/
  type RowTemplateFunc<T, P> = (
    /** use this to create markup outside of JSX environments
     * e.g. https://reactjs.org/docs/react-without-jsx.html */
    createElement: HFunc<T>,
    /** data passed to each template */
    props: RowDataSchemaModel<P>,
    /** a helper template to minimise boilerplate.
     * A shortcut for `<td><div class="nano-tbl__cell-content"></div></td>` */
    TableCell: FunctionalComponent<TableRowCellHelperProps>
  ) => T | undefined;

  /**
   * A cell render function, called during any cell's render to the dom.
   * It can be used to add any markup within a table cell.
   */
  type CellTemplateFunc<T, P> = (
    createElement: HFunc<T>,
    props: RenderModel<P>
  ) => TplResult;

  /**
   * A table header render function, called during any column's render to the dom.
   * It can be used to add any markup within a table header `th`.
   */
  type ColumnTemplateFunc<T, P> = (
    createElement: HFunc<T>,
    col: ColumnConfig<P>
  ) => TplResult;

  /** A function used to assess the order of any given column.
   * e.g. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort
   */
  type ColSortComparator = (
    prop: keyof RowData,
    order: Order
  ) => (a: RowData, b: RowData) => number;

  /** A function which can be used to apply any html attribute/s onto a table cell `td` */
  type PropertiesFunc = (props: RenderModel) => CellProps | void | undefined;

  /** A function which can be used to apply any html attribute/s onto a table head `th` */
  type ColPropertiesFunc<T> = (
    props: ColumnConfig<T>
  ) => CellProps | void | undefined;

  /** A function which can be used to apply any html attribute/s onto a table row `tr` */
  type RowPropertiesFunc<T> = (
    props: RowDataSchemaModel<T>
  ) => RowProps | void | undefined;

  type RowGeneric = { [key: string | number]: any };

  /** An individual table cell's data */
  type RowData<T = RowGeneric> = T & {
    __uuid?: string | number;
    __index?: number;
  };

  /** A data property */
  type Prop = keyof RowData;

  /** Valid column data types */
  type ColumnType =
    | 'text'
    | 'string'
    | 'number'
    | 'date'
    | 'boolean'
    | 'unknown';

  type LibJSX = { [key: string]: any };
  type JSXRender = VNode | VNode[] | LibJSX | LibJSX[];
  type TplReturn = JSXRender | string | Element | undefined;
  type TplResult = TplReturn | Promise<string>;

  /**
   * A table column's configuration
   */
  interface ColumnConfig<T = RowData> {
    /** The title of the column. Displayed in the table header `th` */
    title?: string;
    /** The row's data property to retrieve per cell render */
    prop?: keyof T;
    /** The type of data. Will affect how it is sorted and the available
     * filter operations ('text' uses browser locale to compare).
     * If none supplied, it will be derived by default using the first row of data retrieved. */
    type?: ColumnType;
    /** Whether this column is horizontally pinned to viewport */
    pinned?: Position;
    /** By default cell content too wide for the cell will not wrap,
     * but rather be trimmed with '...'.
     * *Note* 'wrap' is mutually-exclusive from `autoTooltip` */
    wrap?: boolean;
    /** Setting this to true, will automatically watch content that
     * doesn't fit and duplicate it to show in a tooltip.
     * *Note* 'autoTooltip' is mutually-exclusive from `wrap` */
    autoTooltip?: boolean;
    /** Whether this column is a row header; the cell will render a `th` not a `td` */
    rowHeader?: boolean;
    /** Whether this column can be sorted. Defaults to true */
    sortable?: boolean;
    /** Current order of this column */
    order?: Order;
    /** Whether to include this column when doing a general search. Defaults to true */
    searchable?: boolean;
    /** Current filters applied to this column  */
    filter?: string | boolean | string[];
    /** Whether this column is rendered. Defaults to false */
    hidden?: boolean;
    /**
     * A cell render function, called during any cell's render to the dom.
     * It can be used to add any markup within a table cell.
     */
    cellTemplate?: CellTemplateFunc<JSXRender, T>;
    /** A function which can be used to apply any html attribute/s onto a table cell `td` */
    cellProperties?: PropertiesFunc;
    /**
     * A table header render function, called during any column's render to the dom.
     * It can be used to add any markup within a table header `th`.
     */
    columnTemplate?: ColumnTemplateFunc<JSXRender, T>;
    /** A function which can be used to apply any html attribute/s onto a table head `th` */
    columnProperties?: ColPropertiesFunc<T>;
    /** A custom sorting comparator function - assess the order of any given column.
     *  e.g. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort */
    sortCompareFn?: ColSortComparator | string;
  }

  /**
   * Row renderer property. Called during the rendering of any table row
   * controlling it's visual state.
   */
  interface RowRenderer<T = RowData> {
    /** Function to decide whether the row is vertically pinned. */
    pinned?: PinFunc;
    /**
     * A row render function, called during a any row's render to the dom.
     * It can be used to add extra markup around any given table row
     **/
    template?: RowTemplateFunc<VNode | VNode[], T>;
    /** A function which can be used to apply any html attribute/s onto a table row `tr` */
    rowProperties?: RowPropertiesFunc<T>;
  }

  /**
   * Row renderer property. Called during the rendering of a tables head and foot
   * controlling their visual state.
   */
  interface HeadFootRenderer<T = RowData> {
    /** Position prop. Defaults to `top` for the header and `bottom` the footer */
    pinned: Position;
    /**
     * A row render function, called during a any row's render to the dom.
     * It can be used to add extra markup around any given table row.
     * If used, and you want to see the rendered row, you *must* nest it within your
     * jsx function e.g. `(h, props) => { return [props.renderedRow, <tr><td>Custom thing</td></tr>]}`
     **/
    template?: RowTemplateFunc<VNode | VNode[], T>;
    /** A function which can be used to apply any html attribute/s onto a table row `tr` */
    rowProperties?: () => RowProps | void | undefined;
  }

  type TBody = {
    rows: TableTypes.NanoTable['rows'];
    __uuid: string | number;
  };

  class NanoTable<T = RowGeneric> {
    rows: T[];
    columns: ColumnConfig<T>[];
    rowRender: RowRenderer<T>;
    headRender: HeadFootRenderer<T>;
    footRender: HeadFootRenderer<T>;
  }

  type Falsy = false | 0 | '' | null | undefined;
}
