/* eslint-disable react-hooks/exhaustive-deps */
import { useRef, isValidElement } from 'react';
import PropTypes from 'prop-types';
import MUIDataTable, { TableFilterList, TableFooter } from 'mui-datatables';
import { css } from '@emotion/react';
import isObject from 'lodash/isObject';
import cloneDeep from 'lodash/cloneDeep';
import get from 'lodash/get';
import clsx from 'clsx';
import { useCreation, useDeepCompareEffect } from 'ahooks';
import { CircularProgress } from '@mui/material';

import Empty from '../Empty';
import CustomToolbar from './CustomToolbar';
import { DatatableProvider as DataTableProvider, useDatatableContext } from './DatatableContext';
import { styled } from '../Theme';

/**
 * @typedef {{
 *  icon: import('react').ReactElement,
 *  title: import('react').ReactElement | string,
 *  onClick?: () => void
 * }} DataTableCustomButton
 */

/**
 * @typedef {{
 *  name: string,
 *  width?: number,
 *  align?: 'left' | 'center' | 'right',
 * } & import('mui-datatables').MUIDataTableColumnDef}  DataTableColumn
 */

/**
 * @typedef {Pick<import('mui-datatables').MUIDataTableState, 'count' | 'page' | 'rowsPerPage' | 'searchText' | 'sortOrder' | 'filterList'>} DataTableState
 */

/**
 * @typedef {{
 *  title?: import('react').ReactElement | string, // The title of the table (optional)
 * } & Omit<import('mui-datatables').MUIDataTableProps, 'title'>} ModifiedMUIDataTableProps
 */

/**
 * @typedef {{
 *  data: Array<object | number[] | string[]>,
 *  columns: Array<DataTableColumn>,
 *  locale?: import('../type').Locale,
 *  options?: { searchDebounceTime?: number } & import('mui-datatables').MUIDataTableOptions,
 *  style?: import('react').CSSProperties,
 *  customButtons?: Array<DataTableCustomButton>,
 *  customPreButtons?: Array<DataTableCustomButton>,
 *  onChange?: (state: DataTableState, action: string) => void | Promise<void>,
 *  loading?: false | true,
 *  disabled?: false | true,
 *  stripped?: false | true,
 *  verticalKeyWidth?: number | string,
 *  hideTableHeader?: false | true,
 *  components?: ModifiedMUIDataTableProps['components'],
 *  emptyNode?: import('react').ReactNode,
 *  durable?: string,
 *  durableKeys?: 'page' | 'rowsPerPage' | 'searchText' | 'sortOrder',
 *  bgColor?: string,
 *  hoverColor?: string,
 *  stripColor?: string
 * } & ModifiedMUIDataTableProps} DataTableProps
 */

const FilterLine = styled('div')`
  display: flex;
  align-items: center;
  .toolbar-filter-content {
    margin-bottom: 8px;
  }
  .toolbar-filter-title {
    font-weight: 700;
    font-size: 14px;
  }
`;

function WrapFilterList({ ...props }) {
  const { filterLabel } = useDatatableContext();
  const hasFilter = !!(props.filterList ?? []).filter((e) => e.length).length;
  if (hasFilter) {
    return (
      <FilterLine>
        {hasFilter && <div className="toolbar-filter-title">{filterLabel}</div>}
        <div className="toolbar-filter-content">
          <TableFilterList {...props} />
        </div>
      </FilterLine>
    );
  }
  return '';
}

WrapFilterList.propTypes = {
  filterList: PropTypes.array,
};

function WrapTableFooter(props) {
  const { loading, disabled } = useDatatableContext();

  return (
    <FooterContainer>
      <div className={`datatable-footer ${loading || disabled ? 'datatable-footer-disabled' : ''}`}>
        <TableFooter {...props} />
      </div>
    </FooterContainer>
  );
}

export const getDurableData = (key) => {
  const durableKey = `datatable-durable-${key}`;
  let localData = localStorage[durableKey];
  if (localData) {
    try {
      localData = JSON.parse(localData);
    } catch (err) {
      console.error(`parse durable data error (key:${key}) => `, err);
    }
  }
  return localData || {};
};

/**
 * @description
 * @export
 * @param {DataTableProps} props
 * @return {import('react').ReactComponentElement}
 */
export default function DataTable(props) {
  return (
    <DataTableProvider>
      <ReDataTable {...props} />
    </DataTableProvider>
  );
}

const fixCellProp = (tempObj, cellProps) => {
  const cellStyle = {};

  if (tempObj.align) {
    cellProps.className = clsx(cellProps.className, `pc-align-${tempObj.align}`);
  }

  if (tempObj.verticalKeyAlign) {
    cellProps.className = clsx(cellProps.className, `vertical-align-${tempObj.verticalKeyAlign}`);
  }

  if (tempObj.minWidth) {
    cellStyle.minWidth = tempObj.minWidth;
  }

  cellProps.style = Object.assign({}, cellProps.style, cellStyle);
};

/**
 * @param {Object} props.options The options of mui-datatable，detail see https://github.com/gregnb/mui-datatables/tree/b8d2eee6af4589d254b40918e5d7e70b1ee4baca
 * @param {Array} props.customButtons Custom buttons for toolbar
 * @param {Function} props.onChange When onChange is present, serverSide mode is activated by default https://github.com/gregnb/mui-datatables/tree/b8d2eee6af4589d254b40918e5d7e70b1ee4baca#remote-data
 * @param {Boolean} props.loading For dynamic data, usually used with onChange
 * @returns
 */
function ReDataTable({
  data: originData,
  columns: originColumns,
  locale = 'en',
  options = {}, // The options object is usually a property supported by mui-datatable
  style = {},
  customPreButtons = [],
  customButtons = [],
  onChange = '',
  loading = false,
  disabled = false,
  stripped = false,
  verticalKeyWidth = '',
  hideTableHeader = false,
  components = {},
  emptyNode = '',
  durable = '',
  durableKeys = ['page', 'rowsPerPage', 'searchText', 'sortOrder'],
  bgColor = 'transparent',
  hoverColor = '',
  stripColor = '',
  ...rest
}) {
  const oldState = useRef(null);
  const { setCustomPreButtons, setCustomButtons, setFilterLabel, setLoading, setDisabled } = useDatatableContext();

  const disabledCellStyle = {
    cursor: 'not-allowed',
    pointerEvents: 'none',
  };

  const keys = [];

  // Convert Columns fields to object sets to support the width function
  const columns = originColumns.map((e) => {
    let tempObj;

    if (!isObject(e)) {
      tempObj = {
        label: e,
        name: e,
      };
    } else {
      tempObj = cloneDeep(e);
    }
    keys.push(tempObj.name);

    if (!tempObj.align) {
      // There must be an align default for setCellRender to take effect, this is a bug in mui-datatable
      tempObj.align = 'left';
    }

    if (!tempObj.options) {
      tempObj.options = {};
    }

    const { setCellHeaderProps, setCellProps } = tempObj.options;

    tempObj.options.setCellHeaderProps = (...args) => {
      let cellProps = {
        className: '',
      };

      // Complementing width while inheriting old setCellHeaderProps
      if (setCellHeaderProps && !setCellHeaderProps.__innerFunc) {
        cellProps = {
          ...cellProps,
          ...(setCellHeaderProps(...args) || {}),
        };
      }

      if (loading || disabled) {
        cellProps = { ...cellProps, style: disabledCellStyle };
      }

      if (tempObj.width) {
        cellProps.width = tempObj.width;
      }

      fixCellProp(tempObj, cellProps);

      return cellProps;
    };

    // Prevent memory xie caused by recursive forwarding of setCellHeaderProps functions
    tempObj.options.setCellHeaderProps.__innerFunc = 1;

    if (tempObj.align || tempObj.style) {
      tempObj.options.setCellProps = (...args) => {
        let cellProps = {
          className: '',
        };

        // Complementing width while inheriting old setCellHeaderProps
        if (setCellProps && !setCellProps.__innerFunc) {
          cellProps = {
            ...cellProps,
            ...(setCellProps(...args) || {}),
          };
        }

        fixCellProp(tempObj, cellProps);

        return cellProps;
      };
      tempObj.options.setCellProps.__innerFunc = 1;
    }

    return tempObj;
  });

  const data = useCreation(() => {
    return originData.map((e) => {
      if (!Array.isArray(e) && isObject(e)) {
        return keys.map((key) => get(e, key));
      }
      return e;
    });
  }, [originData, keys]);

  useDeepCompareEffect(() => setCustomButtons(customButtons || []), [customButtons]);
  useDeepCompareEffect(() => setCustomPreButtons(customPreButtons || []), [customPreButtons]);
  useDeepCompareEffect(() => setLoading(loading), [loading]);
  useDeepCompareEffect(() => setDisabled(disabled), [disabled]);

  let emptyEl;
  if (loading) {
    emptyEl = <CircularProgress color="primary" />;
  } else if (isValidElement(emptyNode)) {
    emptyEl = emptyNode;
  } else if (locale === 'zh') {
    emptyEl = <Empty>{emptyNode || '没有找到匹配的记录'}</Empty>;
  } else {
    emptyEl = <Empty>{emptyNode || 'No matching records found'}</Empty>;
  }

  let textLabels = {
    body: {
      noMatch: emptyEl,
      toolTip: 'Sort',
    },
    pagination: {
      next: 'Next Page',
      previous: 'Previous Page',
      rowsPerPage: 'Per page:',
      displayRows: 'of',
      jumpToPage: 'Jump to Page:',
    },
    toolbar: {
      search: 'Search',
      downloadCsv: 'Download CSV',
      print: 'Print',
      viewColumns: 'View Columns',
      filterTable: 'Filter Table',
    },
    filter: { all: 'All', title: 'FILTERS', reset: 'RESET' },
    viewColumns: { title: 'Show Columns', titleAria: 'Show/Hide Table Columns' },
    selectedRows: { text: 'row(s) selected', delete: 'Delete', deleteAria: 'Delete Selected Rows' },
  };

  if (locale === 'zh') {
    textLabels = {
      body: { noMatch: emptyEl, toolTip: '排序' },
      pagination: {
        next: '下一页',
        previous: '上一页',
        rowsPerPage: '每页行数',
        displayRows: '/',
        jumpToPage: '跳转到页面：',
      },
      toolbar: {
        search: '搜索',
        downloadCsv: '下载CSV',
        print: '打印',
        viewColumns: '查看列',
        filterTable: '筛选表格',
      },
      filter: { all: '全部', title: '筛选器', reset: '重置' },
      viewColumns: { title: '显示的列', titleAria: '显示/隐藏 表格的列' },
      selectedRows: { text: '个已选项目', delete: '删除', deleteAria: '删除所选项目' },
    };
  }

  useDeepCompareEffect(() => setFilterLabel(textLabels.filter.title), [textLabels.filter.title]);

  const durableData = getDurableData(durable);

  const opts = {
    selectableRows: 'none',
    textLabels,
    rowsPerPage: 10,
    rowsPerPageOptions: [10, 20, 50],
    ...durableData,
    ...options,
    // Wrap the more friendly onChange callback by listening to onTableChange,
    // which will only be triggered when the table key state changes
    onTableChange: (action, tableState) => {
      if (action === 'propsUpdate') {
        return;
      }
      const state = {
        count: tableState.count,
        page: tableState.page,
        rowsPerPage: tableState.rowsPerPage,
        searchText: tableState.searchText,
        sortOrder: tableState.sortOrder, //
        filterList: tableState.filterList,
      };

      if (durable) {
        const needSaveState = {};

        durableKeys.forEach((key) => {
          needSaveState[key] = state[key];
        });

        localStorage.setItem(`datatable-durable-${durable}`, JSON.stringify(needSaveState));
      }

      const stateStr = JSON.stringify(state);
      if (stateStr === oldState.current) {
        return;
      }
      oldState.current = stateStr;
      if (onChange) {
        onChange(state, action);
      }
    },
  };

  if (onChange) {
    opts.serverSide = true;
  }

  const props = {
    options: opts,
    ...rest,
    components: {
      TableToolbar: CustomToolbar,
      TableFooter: WrapTableFooter,
      TableFilterList: WrapFilterList,
      ...components,
    },
  };

  return (
    <TableContainer
      verticalKeyWidth={verticalKeyWidth}
      bgColor={bgColor}
      hoverColor={hoverColor}
      stripColor={stripColor}
      className={clsx({
        'datatable-stripped': stripped,
        'datatable-hide-header': hideTableHeader,
      })}
      style={style}>
      <MUIDataTable data={data} columns={columns} {...props} />
    </TableContainer>
  );
}

ReDataTable.propTypes = {
  data: PropTypes.array.isRequired,
  columns: PropTypes.array.isRequired,
  options: PropTypes.object,
  style: PropTypes.object,
  locale: PropTypes.string,
  loading: PropTypes.bool,
  disabled: PropTypes.bool,
  customPreButtons: PropTypes.array,
  customButtons: PropTypes.array,
  onChange: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),
  stripped: PropTypes.bool,
  verticalKeyWidth: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
  hideTableHeader: PropTypes.bool,
  components: PropTypes.object,
  emptyNode: PropTypes.node,
  durable: PropTypes.string,
  durableKeys: PropTypes.array,
  bgColor: PropTypes.string,
  hoverColor: PropTypes.string,
  stripColor: PropTypes.string,
};

const alignCss = css`
  .MuiTableCell-head {
    [class*='MUIDataTableHeadCell-toolButton'] {
      width: 100%;
      > [class*='MUIDataTableHeadCell-sortAction'] {
        width: 100%;
      }
    }
    &.pc-align-center {
      text-align: center;
      [class*='MUIDataTableHeadCell-toolButton'] > [class*='MUIDataTableHeadCell-sortAction'] {
        justify-content: center;
      }
    }
    &.pc-align-right {
      text-align: right;
      [class*='MUIDataTableHeadCell-toolButton'] {
        margin-right: 0;
        padding-right: 0;
        & > [class*='MUIDataTableHeadCell-sortAction'] {
          justify-content: flex-end;
        }
      }
    }
  }
  .MuiTableCell-body {
    &.pc-align-center {
      text-align: center;
    }
    &.pc-align-right {
      text-align: right;
    }
  }
`;

const TableContainer = styled('div', {
  shouldForwardProp: (prop) => !['verticalKeyWidth', 'bgColor', 'hoverColor', 'stripColor'].includes(prop),
})(({ theme, verticalKeyWidth, bgColor, hoverColor, stripColor }) => {
  const primaryTextColor = theme.palette.text.primary;
  const defaultHoverColor = theme.palette.action.hover;
  const defaultStripColor = theme.palette.grey[50];

  const verticalKeyWidthStyle = verticalKeyWidth
    ? `
      ${theme.breakpoints.down('md')} {
        [class*='MUIDataTable-responsiveBase'] {
          tr:not([class*='responsiveSimple']) {
            td.MuiTableCell-body {
              > div {
                &:first-of-type {
                  width: ${verticalKeyWidth + (!/\D/.test(verticalKeyWidth) ? 'px' : '')};
                }
              }
            }
          }
        }
      }
    `
    : '';

  return css`
    &.datatable-hide-header {
      thead.MuiTableHead-root {
        display: none;
      }
    }

    td {
      &.vertical-align-top {
        align-items: flex-start;
      }
      &.vertical-align-center {
        align-items: center;
      }
      &.vertical-align-bottom {
        align-items: flex-end;
      }
    }

    height: 100%;
    > .MuiPaper-root {
      display: flex;
      flex-direction: column;
      height: 100%;
      box-shadow: none;
      background: none;
      background-color: ${bgColor};
    }
    ${theme.breakpoints.down('md')} {
      td.MuiTableCell-body {
        padding-right: 0;
      }
      [class*='MUIDataTable-responsiveBase'] {
        tr:not([class*='responsiveSimple']) {
          td.MuiTableCell-body {
            display: flex;
            > div {
              width: auto;
              flex: 1;
              &:first-of-type {
                font-weight: bold;
                font-size: 14px;
                width: auto;
                flex: 0 0 auto;
                padding-right: 16px;
                &:empty {
                  padding-right: 0;
                }
              }
            }
          }
        }
      }
    }
    ${theme.breakpoints.up('md')} {
      ${alignCss}
    }
    ${theme.breakpoints.up('sm')} {
      [class*='responsiveSimple'] {
        ${alignCss}
      }
    }

    ${verticalKeyWidthStyle}

    .MuiTableCell-head {
      color: ${primaryTextColor};
      background-color: ${bgColor};
    }

    .MuiTableCell-root {
      color: ${primaryTextColor};
    }

    .MuiTableRow-root {
      background-color: ${bgColor};
      &:hover {
        background-color: ${hoverColor || defaultHoverColor};
      }
      &.MuiTableRow-footer {
        background-color: ${bgColor};
      }
    }

    &.datatable-stripped {
      .MuiTableRow-root:nth-of-type(odd) {
        background-color: ${stripColor || defaultStripColor};
        &:hover {
          background-color: ${hoverColor || defaultHoverColor};
        }
        &.MuiTableRow-head {
          background-color: ${bgColor};
        }
        &.MuiTableRow-footer {
          background-color: ${bgColor};
        }
      }
    }
  `;
});

const FooterContainer = styled('div')`
  display: flex;
  align-items: center;
  .datatable-footer {
    position: relative;
    margin-left: auto;
    &.datatable-footer-disabled {
      position: relative;
      .MuiTablePagination-root {
        opacity: 0.6;
      }
      &:after {
        position: absolute;
        display: block;
        z-index: 2;
        width: 100%;
        height: 100%;
        left: 0;
        top: 0;
        content: '';
        cursor: not-allowed;
      }
    }
  }

  ${(props) => props.theme.breakpoints.down('sm')} {
    .MuiTableCell-footer,
    .MuiToolbar-gutters {
      padding: 0;
    }

    div[variant='standard'] {
      margin-left: 4px;
      margin-right: 16px;
    }

    .MuiTablePagination-actions {
      margin-left: 10px;
    }
  }
`;

export { TableFilterList, TableFooter };
