/* eslint-disable @typescript-eslint/no-explicit-any */
import { Component, DefineProps } from 'vue';
import { MenuOption } from '../Menu/Menu.vue.d';
import { ClassComponent } from '../ts-helpers';

type Data = Record<string, any>;

export type QueryParams = Partial<Record<string, unknown>>;

export type FetchResponse<T = Data> = {
  data: T[];
  totalRecords: number;
};

export type TableFetchResponse<T = Data> = {
  message: string;
  data: FetchResponse<T>;
};

export type ShortFetchResponse<T = Data> = {
  message: string;
  data: T;
};

export type TableCellComponent = {
  component: string | Component;
  props?: object;
  model?: any;
  events?: any;
  onChange?: (itemData: any) => void;
  disabled?: boolean;
};

/**
 * Defines valid properties in VirtualScroller component.
 */
export interface VirtualScrollerProps {
  /**
   * Unique identifier of the element.
   */
  id?: string | undefined;
  /**
   * Inline style of the component.
   */
  style?: any;
  /**
   * Style class of the component.
   */
  class?: any;
  /**
   * An array of objects to display.
   */
  items?: any[] | any[][] | undefined | null;
  /**
   * The height/width of item according to orientation.
   */
  itemSize?: number | number[] | undefined;
  /**
   * Height of the scroll viewport.
   */
  scrollHeight?: string | undefined;
  /**
   * Width of the scroll viewport.
   */
  scrollWidth?: string | undefined;
  /**
   * The orientation of scrollbar.
   * @defaultValue vertical
   */
  orientation?: 'vertical' | 'horizontal' | 'both' | undefined;
  /**
   * Determines how many additional elements to add to the DOM outside of the view.
   * According to the scrolls made up and down, extra items are added in a certain algorithm in the form of multiples of this number.
   * @defaultValue half the number of items shown in the view.
   */
  numToleratedItems?: number | undefined;
  /**
   * Delay in scroll before new data is loaded.
   * @defaultValue 0
   */
  delay?: number | undefined;
  /**
   * Delay after window's resize finishes.
   * @defaultValue 10
   */
  resizeDelay?: number | undefined;
  /**
   * Defines if data is loaded and interacted with in lazy manner.
   * @defaultValue false
   */
  lazy?: boolean | undefined;
  /**
   * If disabled, the VirtualScroller feature is eliminated and the content is displayed directly.
   * @defaultValue false
   */
  disabled?: boolean | undefined;
  /**
   * Used to implement a custom loader instead of using the loader feature in the VirtualScroller.
   * @defaultValue false
   */
  loaderDisabled?: boolean | undefined;
  /**
   * Whether to show loader.
   * @defaultValue false
   */
  showLoader?: boolean | undefined;
  /**
   * Used to implement a custom spacer instead of using the spacer feature in the VirtualScroller.
   * @defaultValue true
   */
  showSpacer?: boolean | undefined;
  /**
   * Whether to load items.
   * @defaultValue false
   */
  loading?: boolean | undefined;
  /**
   * Callback to invoke in lazy mode to load new data.
   * @param {VirtualScrollerLazyEvent} event - Custom lazy event.
   */
  onLazyLoad?(event: VirtualScrollerLazyEvent): void;
  /**
   * Index of the element in tabbing order.
   * @defaultValue 0
   */
  tabindex?: number | string | undefined;
  /**
   * When enabled, positions the content as inline.
   * @defaultValue false
   */
  inline?: boolean | undefined;
  /**
   * Used to specify how many items to load in each load method in lazy mode.
   * @defaultValue 0
   */
  step?: number | undefined;
  /**
   * Used to append each loaded item to top without removing any items from the DOM. Using very large data may cause the browser to crash.
   * @defaultValue false
   */
  appendOnly?: boolean | undefined;
  /**
   * Whether to dynamically change the height or width of scrollable container.
   * @defaultValue false
   */
  autoSize?: boolean | undefined;
}

export type TableColumn = {
  header?: string;
  field: string;
  sortable?: boolean;
  /**
   * Wether the column can be re-ordered by column visibilty.
   * If set to false, the column will not appear on column visibility.
   */
  reorderable?: boolean;
  /**
   * Make the column cannot reordered by disabled dragability.
   * Unlike `reorderable`, the column will still appear in the column visibility
   * overlay if this is set to false.
   */
  dragable?: boolean;
  /**
   * Whether the column is pinned to the left of the table.
   */
  pinned?: boolean;
  fixed?: boolean;
  visible?: boolean;
  /**
   * Whether the column is checked by default
   * @default true
   */
  checkedByDefault?: boolean;
  /**
   * The column width in pixel.
   */
  width?: number | string;
  /**
   * Exclude column from download/export excel.
   */
  excluded?: boolean;
  /**
   * Set the field for Export Excel, when you need to export different field of column field.
   *
   * @default undefined - the export field using column 'field'
   */
  exportField?: string;
  exportHeader?: string; // Value of header that will be exported
  dateValue?: boolean; // To format the date value with formatDate util
  /**
   * Specify the property or field which value in the array should be used for exporting to Excel.
   *
   * @example 'field.name.fullName'
   */
  arrayValueField?: string;
  /**
   * Download Excel Config
   *
   * When needs to export only the property has Truthy value, set this to true,
   * The excel result will only get the property object which the value is truthy.
   *
   * @example
   * Example Object:
   * "roles": {
   *    roleA: true,
   *    roleB: true,
   *    roleC: false,
   *    roleD: null, // falsy
   *    roleE: '', // falsy
   *    roleF: 'role', // truthy
   * }
   *
   * The result will be: "roleA, roleB, roleF" - Only the properties with truthy values will be included.
   */
  includeTruthyProperties?: boolean;
  /**
   * Convert boolean into text Yes/No
   */
  booleanValue?: boolean;
  bodyTemplate?: (data: any) => string | undefined;
  bodyComponent?: (data: any) => TableCellComponent;
  headerTemplate?: () => string;
  headerComponent?: () => {
    component: string | Component;
    props?: DefineProps;
    model?: any;
    events?: any;
    onChange?: (data: any) => void;
    disabled?: boolean;
  };
  /**
   * Inline style of header, body and footer cells.
   */
  style?: string | (() => string);
  /**
   * Style class of header, body and footer cells.
   */
  class?: string | string[] | (() => string | string[]);
  /**
   * Inline style of the column header.
   */
  headerStyle?: string | (() => string);
  /**
   * Style class of the column header.
   */
  headerClass?: string | string[] | (() => string | string[]);
  /**
   * Inline style of the column body.
   */
  bodyStyle?: string | (() => string);
  /**
   * Style class of the column body.
   */
  bodyClass?: string | string[] | ((data?: any) => string | string[]);
};

export type TableOption = {
  label?: string;
  command?: (data?: any) => any;
  icon?: string;
  items?: any[];
  class?: string;
  disabled?: boolean;
  separator?: boolean;
  danger?: boolean;
  visible?: boolean;
};

/**
 * Custom row click event.
 * @see {@link DataTableEmits['row-click']}
 */
export interface DataTableRowClickEvent {
  /**
   * Browser event.
   */
  originalEvent?: Event;
  /**
   * Selected row data.
   */
  data: any;
  /**
   * Row index.
   */
  index?: number;
}

/**
 * Custom row select all change event.
 * @see {@link DataTableEmits['select-all-change']}
 */
export interface DataTableSelectAllChangeEvent {
  /**
   * Browser event
   */
  originalEvent: Event;
  /**
   * Whether all data is selected.
   */
  checked: boolean;
}

export interface TSDataTableProps {
  /**
   * Index of the first row to be displayed.
   * @defaultValue 0
   */
  first?: number;
  /**
   * Optional property to set a unique name for the table. This name will be used as part of the unique table ID.
   *
   * @optional
   */
  tableName?: string;
  /**
   * An array of table columns to display.
   */
  columns: TableColumn[];
  /**
   * Use this when you need single action to be disabled
   * Default value is `false` even if row is disabled due to in general use this single action still used even if row is disabled
   *
   * @default false
   */
  disableOption?: boolean;
  /**
   * Wether show single action option.
   * @default true;
   */
  useOption?: boolean;
  /**
   * An array of table columns for children table.
   */
  childrenColumns?: TableColumn[];
  /**
   * Whether show single action option in children table.
   * @default true;
   */
  childrenUseOption?: boolean;
  /**
   * The options that will be visible for each row data in children table.
   *
   * __Provide at least one option to display an Options Menu for each row.__
   *
   * The options menu will be displayed on the right side of the children table row.
   */
  childrenOptions?: MenuOption[];
  /**
   * The key of the data object to use as a unique identifier for chlidren table.
   * @default '_id'
   */
  childrenDataKey?: string;
  childrenFilters?: DataTableFilterMeta;
  /**
   * An array of objects to display in the table.
   */
  data?: Data[];
  /**
   * The key of the data object to use as a unique identifier.
   */
  dataKey: string;
  /**
   * The key of the data object to determine wether the row data should be disabled.
   */
  disableKey?: string;
  /**
   * Boolean to specify wether all rows should be disabled.
   */
  disableAllRows?: boolean;
  /**
   * Boolean to determine whether all checkbox should be disabled.
   */
  disableAllCheckboxes?: boolean;
  /**
   * Boolean to determine whether paginator should be disabled.
   */
  disablePaginator?: boolean;
  /**
   * Custom message when data table is empty
   */
  emptyTableMessage?: string;
  /**
   * Class for custom message when data table is empty
   */
  emptyTableMessageClass?: string;
  /**
   * The query search from ButtonSearch component.
   */
  search?: string;
  /**
   * Tag query for search by scan
   */
  tag?: string;
  /**
   * The default query params that must be included every fetch request.
   */
  defaultQueryParams?: Readonly<Record<string, any>>;
  /**
   * The function to fetch data on DataTable mounted and on queryParams dependencies updated.
   *
   * @param params this is required
   */
  fetchFunction?: (params: QueryParams) => Promise<FetchResponse | undefined>;
  /**
   * The number of rows to display per page.
   * @defaultValue 5
   */
  rows?: number;
  /**
   * Whether all rows are selected or not.
   */
  isSelectedAll?: boolean;
  filters?: DataTableFilterMeta;
  /**
   * Determine the type of DataTable.
   *
   * Sets to lazy if you need to dynamicaly shows data.
   */
  lazy?: boolean;
  loading?: boolean;
  /**
   * Whether to enable 'checkbox' | 'single' selection.
   *
   * @default 'checkbox'
   */
  selectionType?: 'single' | 'checkbox' | 'none';
  /**
   * Whether to enable checkbox multi-selection.
   *
   * @deprecated use 'selection-type="checkbox"'
   */
  useSelection?: boolean;
  /**
   * An array of selected objects from `data`.
   */
  selectedData?: Data[];
  /**
   * An array of selected objects from `data`.
   */
  expandedRows?: { [key: string]: boolean };
  /**
   * Wether display pagination under the table or not.
   */
  usePaginator?: boolean;
  /**
   * The options that will be visible for each row data.
   *
   * __Provide at least one option to display an Options Menu for each row.__
   *
   * The options menu will be displayed on the right side of the table row.
   */
  options?: MenuOption[];
  /**
   * Whether to enable single selection or not.
   * If enabled, clicking a row will trigger the `rowSelect` event with the corresponding data object passed as a parameter.
   *
   * @deprecated use 'selection-type="single"'
   * @note Do not combine `singleSelect` with `useSelection`, as it may lead to unexpected behavior.
   */
  singleSelect?: boolean;
  /**
   * Wether the column is customizable or not.
   *
   * @default true - the table is able to reorder and toggle visibility column;
   */
  customColumn?: boolean;
  /**
   * Set the scrollHeight in px
   */
  scrollHeight?: string;
  /**
   * Whether to use the virtualScroller feature. The properties of VirtualScroller component can be used like an object in it.
   * Note: Currently only vertical orientation mode is supported.
   */
  virtualScrollerOptions?: VirtualScrollerProps;
  /**
   * An array of fields as string to use in global filtering.
   */
  globalFilterFields?: string[];
  /**
   * Total disabled rows in table (used for synchronizing with bulk action button)
   */
  totalDisabledRows?: number;
  /**
   * A function that takes the row data as a parameter and returns a string to apply a particular class for the row.
   * The return value is added to the row's :classes array (see Vue.js class bindings).
   */
  rowClass?: (data: any) => string | object | undefined;
  /**
   * An array of arrays to display in the exported excel file before the main data.
   */
  preContentData?: (string | number)[][];
}

export type TSDataTableEmits = {
  /**
   * Emits when option menu button clicked.
   */
  'toggleOption': [data: Data];
  /**
   * Emits when option menu button in children table was clicked.
   */
  'toggleChildrenOption': [data: Data];
  /**
   * @deprecated use 'v-model:selected-data' instead.
   */
  'selectData': [data: Data[]];
  'rowSelect': [data: DataTableRowClickEvent];
  /**
   * Event emitted when the `isSelectedAll` property is updated.
   *
   * @event update:isSelectedAll
   * @param {boolean} data - The updated value of `isSelectedAll`.
   *
   * @example
   * <TSDataTable v-model:is-selected-all="isSelectedAll" />
   *
   * This will update the `isSelectedAll` value when one of checkbox is unchecked.
   *
   * @deprecated;
   */
  // 'update:isSelectedAll': [data: boolean];
  /**
   * Event emitted when the page changes in the data table.
   *
   * @event page
   * @param {DataTablePageEvent} data - The event data containing information about the new page.
   *
   * @example
   * <TSDataTable @page="handlePageChange" />
   *
   * This will call the `handlePageChange` method whenever the page changes in the data table.
   */
  'page': [data: DataTablePageEvent];

  /**
   * Event emitted when the sort order changes in the data table.
   *
   * @event sort
   * @param {DataTableSortEvent} data - The event data containing information about the new sort order.
   *
   * @example
   * <TSDataTable @sort="handleSortChange" />
   *
   * This will call the `handleSortChange` method whenever the sort order changes in the data table.
   */
  'sort': [data: DataTableSortEvent];
  /**
   * Event emitted when the `selectedData` property is updated.
   *
   * @event update:selectedData
   * @param {Data[]} datas - The updated array of selected data objects.
   *
   * @example
   * <TSDataTable v-model:selected-data="selectedData" />
   *
   * This will update the `selectedData` value whenever a row is selected or deselected.
   */
  'update:selectedData': [datas: Data[]];
  /**
   * Emitted when the rows changes.
   * @param {number} value - New value.
   */
  'update:rows': [value: number];
  /**
   * Emitted when the first changes.
   * @param {number} value - New value.
   */
  'update:first': [value: number];
};

declare class DataTable extends ClassComponent<
  TSDataTableProps,
  Record<string, unknown>,
  TSDataTableEmits
> {}

export default DataTable;
