import React, {
  useCallback,
  useReducer,
  useMemo,
  useEffect,
  useImperativeHandle,
  ForwardRefRenderFunction,
  useState,
  useRef,
} from 'react';
import Table from 'antd/es/table';
import 'antd/es/table/style/index';
import Pagination from 'antd/es/pagination';
import 'antd/es/pagination/style/index';
import Spin from 'antd/es/spin';
import 'antd/es/spin/style/index';
import message from 'antd/es/message';
import 'antd/es/message/style/index';
import 'antd/es/checkbox/style/index';
import Loading3QuartersOutlined from '@ant-design/icons/Loading3QuartersOutlined';
import dayjs from 'dayjs';
import classNames from 'classnames';
import { getRoleKeyFromUrl } from '@jy-fe/business';
import { pagination, tableScroll, checkType, resizableColumns } from '@jy-fe/utils/es/joyowoUmi';
import XuiHeadLine from '../xui-head-line';
import XuiOperationList from '../xui-operation-list';
import XuiSearchHead from '../xui-search-head';
import XuiSearchBar from '../xui-search-bar';
import XuiSearchFrame from '../xui-search-frame';
import XuiResizableTh from '../xui-resizable-th';
import XuiResizableTd from '../xui-resizable-td';
import {
  SearchHeadHandles,
  NodeType as SearchHeadNodeType,
} from '../xui-search-head/xui-search-head.d';
import {
  SearchBarHandles,
  NodeType as SearchBarNodeType,
} from '../xui-search-bar/xui-search-bar.d';
import {
  SearchFrameHandles,
  NodeType as SearchFrameNodeType,
} from '../xui-search-frame/xui-search-frame.d';
import {
  GetTableDataType,
  InitialStateType,
  XuiTablePageProps,
  XuiTablePageHandles,
} from './xui-table-page.d';
import tableNoDataPNG from '../assets/table-no-data.png';

Spin.setDefaultIndicator(<Loading3QuartersOutlined />);

const initialState: InitialStateType = {
  searchState: {},
  loading: false,
  current: 1,
  pageSize: 30,
  total: 0,
  dataSource: [],
  selectedRowKeys: [],
  selectedRows: [],
  defaultValues: {},
  disabledRowKeys: [],
  mandatoryRowKeys: [],
};

const reducer = (
  state: InitialStateType,
  action: {
    type: string;
    payload?: { [key: string]: any };
  },
) => {
  switch (action.type) {
    case 'update':
      return { ...state, ...action.payload };
    default:
      throw new Error();
  }
};

const XuiTablePage: ForwardRefRenderFunction<XuiTablePageHandles, XuiTablePageProps> = (
  {
    aid,
    aidMap,
    className: wrapperClassName,
    closePagination,
    columns = [],
    customNoData,
    defaultSearchValues = {},
    extraContent,
    extraData = {},
    extraHead,
    extraOptions = {},
    id,
    isSearchCache = true,
    keepDefaultValuesOnReset,
    listType = 'table',
    maxWidth,
    openLocalPagination,
    operationColumnsConfig,
    paginationConfig,
    propDataSource,
    propLoading,
    renderBeforeTable,
    roleId,
    rowKey,
    rowSelection,
    searchComponentType = 'searchHead',
    searchNode = [],
    tableHeight = document.body.offsetHeight - 324,
    title,
    beforeInitRequest,
    disableSelection,
    mandatorySelection,
    onDisableSelection,
    onMandatorySelection,
    onSelectionChange,
    operationColumns,
    paginationCallback,
    paginationChange,
    processResponseData,
    processValues,
    request,
  },
  ref,
) => {
  const className = 'xui-ant__table-page';

  const searchHeadRef = useRef<SearchHeadHandles>(null);
  const searchBarRef = useRef<SearchBarHandles>(null);
  const searchFrameRef = useRef<SearchFrameHandles>(null);

  const curSearchComponentRaf = useMemo(() => {
    if (searchComponentType === 'searchBar') {
      return searchBarRef;
    }
    if (searchComponentType === 'searchHead') {
      return searchHeadRef;
    }
    return searchFrameRef;
  }, [searchComponentType]);

  const [stateColumns, setStateColumns] = useState<any[]>([]);

  /** sessionStorage唯一标识符 */
  const sessionStorageKey = useMemo(() => `${location.pathname}${id ? `:${id}` : ''}`, [id]);

  /** 需时间格式处理的字段关键字 */
  const momentKeyword = useMemo(() => ['Time', 'Date', 'Month'], []);

  const [state, hookDispatch] = useReducer(reducer, {
    ...initialState,
    pageSize: paginationConfig?.pageSizeOptions?.[0]
      ? Number(paginationConfig?.pageSizeOptions?.[0])
      : initialState.pageSize,
  });

  const {
    searchState,
    loading,
    current,
    pageSize,
    total,
    dataSource,
    selectedRowKeys,
    selectedRows,
    defaultValues,
    disabledRowKeys,
    mandatoryRowKeys,
  } = state;

  const triggerDispatch = useCallback((payload: Partial<InitialStateType>) => {
    hookDispatch({
      type: 'update',
      payload,
    });
  }, []);

  /** 更新分页数据 */
  useEffect(() => {
    const currentPageConfig = {
      current: paginationConfig?.current ?? current,
      pageSize: paginationConfig?.pageSize ?? pageSize,
      total: openLocalPagination ? propDataSource?.length : paginationConfig?.total ?? total,
    };

    triggerDispatch(currentPageConfig);
  }, [
    openLocalPagination,
    paginationConfig,
    propDataSource,
    current,
    pageSize,
    total,
    triggerDispatch,
  ]);

  /** 处理列表接口返回数据 */
  const discopeResponseData = (data: any) => {
    const { pageNum, pageSize, list, total } = data || {};
    return {
      list,
      pageNum,
      pageSize,
      total,
    };
  };

  /**
   * 获取列表数据
   * params 接口调用额外传参
   * holdSelected 保持勾选状态
   */
  const getTableData = useCallback<GetTableDataType>(
    async extraPropsParams => {
      const { params = {}, isSearch = false, holdSelected = false, callback } =
        extraPropsParams || {};
      if (!request) {
        return false;
      }
      triggerDispatch({
        loading: true,
      });
      let requestParams = {};
      if (isSearch) {
        requestParams = {
          ...params,
        };
      } else {
        requestParams = {
          ...searchState,
          ...params,
        };
      }
      const extraParams = {
        ...extraData,
      };
      Object.keys(extraParams).forEach(key => {
        const valueInRequestParams = requestParams[key];
        let cover = false;
        if (checkType(valueInRequestParams) === 'Array' && valueInRequestParams.length > 0) {
          cover = true;
        } else if (
          checkType(valueInRequestParams) === 'Object' &&
          Object.keys(valueInRequestParams).length > 0
        ) {
          cover = true;
        } else if (
          checkType(valueInRequestParams) !== 'Array' &&
          (valueInRequestParams || valueInRequestParams === 0)
        ) {
          cover = true;
        }
        if (cover) {
          extraParams[key] = valueInRequestParams;
        }
      });

      const aidVal = (() => {
        if (aid) return aid;
        if (aidMap) return aidMap[getRoleKeyFromUrl()];
        return '';
      })();

      const res: {
        status: number;
        data: {
          pageNum: number;
          pageSize: number;
          total: number;
          list: any[];
        };
        msg: string;
      } = await request(
        {
          pageNum: current,
          pageSize,
          ...requestParams,
          ...extraParams,
        },
        {
          header: {
            rid: roleId || '',
            aid: aidVal,
          },
          ...extraOptions,
        },
      );
      const { status, data, msg } = res;
      if (!status) {
        const {
          pageNum: newPageNum,
          pageSize: newPageSize,
          list = [],
          total: newTotal,
        } = processResponseData ? processResponseData(data) : discopeResponseData(data);
        const newList = list.map((item, index) => ({
          ...item,
          serialNumer: (newPageNum - 1) * newPageSize + index + 1,
        }));
        triggerDispatch({
          current: newPageNum,
          pageSize: newPageSize,
          dataSource: newList,
          total: newTotal,
        });
        if (!holdSelected) {
          triggerDispatch({
            selectedRowKeys: [],
            selectedRows: [],
          });
        }
        if (disableSelection) {
          const currentDisabledRowKeys = newList
            .filter(l => disableSelection(l))
            .map(item => item[rowKey]);
          triggerDispatch({
            disabledRowKeys: currentDisabledRowKeys,
          });
          callback &&
            callback({
              selectedRowKeys: !holdSelected ? [] : selectedRowKeys,
              selectedRows: !holdSelected ? [] : selectedRows,
              disabledRowKeys: currentDisabledRowKeys,
              mandatoryRowKeys,
              dataSource: newList || [],
            });
          onSelectionChange &&
            onSelectionChange(
              !holdSelected ? [] : selectedRowKeys,
              !holdSelected ? [] : selectedRows,
            );
        } else if (mandatorySelection) {
          const newMandatoryRowKeys: React.ReactText[] = [];
          const newMandatoryRows: { [key: string]: any }[] = [];
          newList.forEach(l => {
            if (mandatorySelection(l)) {
              newMandatoryRowKeys.push(l[rowKey]);
              newMandatoryRows.push(l);
            }
          });
          triggerDispatch({
            mandatoryRowKeys: newMandatoryRowKeys,
            selectedRowKeys: newMandatoryRowKeys,
            selectedRows: newMandatoryRows,
          });
          callback &&
            callback({
              selectedRowKeys: !holdSelected ? [] : newMandatoryRowKeys,
              selectedRows: !holdSelected ? [] : newMandatoryRows,
              disabledRowKeys,
              mandatoryRowKeys: newMandatoryRowKeys,
              dataSource: newList || [],
            });
          onSelectionChange &&
            onSelectionChange(
              !holdSelected ? [] : newMandatoryRowKeys,
              !holdSelected ? [] : newMandatoryRows,
            );
        } else {
          callback &&
            callback({
              selectedRowKeys: !holdSelected ? [] : selectedRowKeys,
              selectedRows: !holdSelected ? [] : selectedRows,
              disabledRowKeys,
              mandatoryRowKeys,
              dataSource: newList || [],
            });
          onSelectionChange &&
            onSelectionChange(
              !holdSelected ? [] : selectedRowKeys,
              !holdSelected ? [] : selectedRows,
            );
        }
        if (isSearchCache) {
          const cacheJSONStr = sessionStorage.getItem(sessionStorageKey);
          const cache = {
            ...(cacheJSONStr ? JSON.parse(cacheJSONStr) : {}),
            pageNum: newPageNum,
            pageSize: newPageSize,
          };
          sessionStorage.setItem(sessionStorageKey, JSON.stringify(cache));
        }
      } else if (msg) {
        message.warn(msg);
      }
      triggerDispatch({
        loading: false,
      });
      return true;
    },
    [
      aid,
      aidMap,
      extraData,
      extraOptions,
      isSearchCache,
      roleId,
      rowKey,
      current,
      disabledRowKeys,
      mandatoryRowKeys,
      pageSize,
      searchState,
      selectedRowKeys,
      selectedRows,
      sessionStorageKey,
      disableSelection,
      mandatorySelection,
      onSelectionChange,
      processResponseData,
      request,
      triggerDispatch,
    ],
  );

  /** 操作table的分页器 */
  const changeTablePagination = useCallback(
    (newCurrent, newPageSize) => {
      if (paginationChange) {
        paginationChange(newCurrent, newPageSize);
      } else if (paginationConfig?.onChange) {
        paginationConfig.onChange(newCurrent, newPageSize);
      } else {
        if (openLocalPagination) {
          triggerDispatch({
            current: newCurrent,
            pageSize: newPageSize,
          });
        } else {
          getTableData({
            params: {
              pageNum: newCurrent,
              pageSize: newPageSize,
            },
            isSearch: false,
            holdSelected: true,
          });
        }
        if (paginationCallback) {
          paginationCallback(newCurrent, newPageSize);
        }
      }
    },
    [
      openLocalPagination,
      paginationConfig,
      getTableData,
      paginationCallback,
      paginationChange,
      triggerDispatch,
    ],
  );

  /** 更新表格数据 */
  const updateState = useCallback(
    (newState: Partial<InitialStateType>) => {
      triggerDispatch({
        ...state,
        ...newState,
      });
    },
    [state, triggerDispatch],
  );

  /** 筛选 */
  const search = useCallback(
    (values: { [key: string]: any }, type: 'search' | 'reset') => {
      let searchValues = {
        ...values,
      };
      if (type === 'reset' && keepDefaultValuesOnReset) {
        triggerDispatch({
          defaultValues: { ...defaultSearchValues },
        });
        searchValues = {
          ...searchValues,
          ...defaultSearchValues,
        };
      }
      if (processValues && checkType(processValues) === 'Function') {
        searchValues = processValues({ ...searchValues });
      }
      triggerDispatch({
        searchState: searchValues,
      });
      getTableData({
        params: {
          pageNum: 1,
          ...searchValues,
        },
        isSearch: true,
      });
      if (isSearchCache) {
        /** 组装sessionStorage */
        sessionStorage.setItem(
          sessionStorageKey,
          JSON.stringify({
            ...values,
          }),
        );
      }
    },
    [
      defaultSearchValues,
      isSearchCache,
      keepDefaultValuesOnReset,
      sessionStorageKey,
      getTableData,
      processValues,
      triggerDispatch,
    ],
  );

  const init = async (isAuto = false) => {
    /** 获取sessionStorage里的参数 */
    const cacheJSONStr = sessionStorage.getItem(sessionStorageKey);
    const cacheParams = cacheJSONStr ? JSON.parse(cacheJSONStr) : {};
    const entries = isSearchCache ? Object.entries(cacheParams) : [];
    /** 常规参数，value type is string */
    let params = {
      ...(processValues && checkType(processValues) === 'Function'
        ? processValues({ ...defaultSearchValues })
        : defaultSearchValues),
    };
    /** _search, value type is object */
    const originalSearchParams = { ...defaultSearchValues };
    let searchParams = {};

    if (entries.length > 0) {
      entries.forEach(searchParseEntry => {
        const [searchParseKey, searchParseValue] = searchParseEntry;
        let isDate = false;
        momentKeyword.forEach(element => {
          if (searchParseKey.includes(element)) {
            isDate = true;
          }
        });
        if (isDate) {
          if (checkType(searchParseValue) === 'Array') {
            originalSearchParams[searchParseKey] = (searchParseValue as string[]).map(child =>
              child ? dayjs(child) : undefined,
            );
          } else {
            originalSearchParams[searchParseKey] = dayjs(searchParseValue as string);
          }
        } else {
          originalSearchParams[searchParseKey] = searchParseValue;
        }
      });
      if (processValues && checkType(processValues) === 'Function') {
        /** 自定义处理数据 */
        searchParams = processValues({ ...originalSearchParams });
      } else {
        searchParams = { ...originalSearchParams };
      }
    }
    triggerDispatch({
      searchState:
        processValues && checkType(processValues) === 'Function'
          ? processValues({ ...originalSearchParams })
          : originalSearchParams,
      defaultValues: originalSearchParams,
    });

    let beforeInitRequestParams: any = {};
    let ableInitRequest: any = true;
    if (beforeInitRequest) {
      const resultParams = await beforeInitRequest();
      if (checkType(resultParams) === 'Object') {
        beforeInitRequestParams = resultParams;
      }
      if (checkType(resultParams) === 'Boolean') {
        ableInitRequest = resultParams;
      }
    }
    // 判断初始化是否调用接口
    if ((isAuto && ableInitRequest) || !isAuto) {
      params = {
        ...params,
        ...searchParams,
        ...beforeInitRequestParams,
      };
      /** 获取表格数据 */
      await getTableData({ params });
    }
  };

  const clearSelectedRows = () => {
    triggerDispatch({
      selectedRowKeys: [],
      selectedRows: [],
    });
  };

  const tableDataSource = useMemo(() => {
    if (propDataSource) {
      if (openLocalPagination) {
        return propDataSource.slice().splice((current - 1) * pageSize, pageSize);
      }
      return propDataSource;
    }
    return dataSource;
  }, [openLocalPagination, dataSource, propDataSource, current, pageSize]);

  useImperativeHandle(ref, () => ({
    selectedRowKeys,
    selectedRows,
    searchState,
    dataSource,
    pageNum: current,
    pageSize,
    total,
    init,
    clearSelectedRows,
    updateState,
    getTableData,
    clearSearchParams: () => {
      if (curSearchComponentRaf.current) {
        curSearchComponentRaf.current.clear();
      }
    },
    resetSearchParams: (paramNameList: string[]) => {
      if (curSearchComponentRaf.current) {
        curSearchComponentRaf.current.resetState(paramNameList);
      }
    },
  }));

  useEffect(() => {
    if (!propDataSource) {
      init(true);
    }
  }, []);

  useEffect(() => {
    const currentColumns = [...columns];
    if (operationColumns) {
      const operateWidth = XuiOperationList.getWidth(
        () => operationColumns(searchState),
        searchState,
        operationColumnsConfig,
      );
      if (operateWidth) {
        currentColumns.push({
          title: '操作',
          dataIndex: '操作',
          fixed: 'right',
          width: operateWidth,
          render: (_, record: any) => (
            <XuiOperationList getNodes={() => operationColumns(record)} data={record} />
          ),
        });
      }
    }
    setStateColumns(currentColumns);
  }, [columns, operationColumnsConfig, searchState, operationColumns]);

  const { columns: newColumns, scroll } = useMemo(() => {
    const props = {
      oldColumns: resizableColumns(stateColumns, nextColumns => {
        setStateColumns(nextColumns);
      }),
      dataSource: propDataSource || dataSource,
      rowSelectionWidth: rowSelection ? 40 : 0,
      maxHeight: tableHeight,
      maxWidth,
    };
    if (listType === 'drawer900') {
      props.maxWidth = 900 - 24 * 2;
    }
    if (listType === 'drawer600') {
      props.maxWidth = 600 - 24 * 2;
    }
    return tableScroll(props);
  }, [listType, maxWidth, propDataSource, rowSelection, tableHeight, dataSource, stateColumns]);

  const tableProps: { [key: string]: any } = {
    pagination: false,
    onRow: record => {
      if (mandatoryRowKeys.includes(record[rowKey])) {
        return { className: `${className}--tr-mandatory` };
      }
      if (disabledRowKeys.includes(record[rowKey])) {
        return { className: `${className}--tr-disabled` };
      }
      return {};
    },
  };

  if (rowSelection) {
    tableProps.rowSelection = {
      columnWidth: 40,
      selectedRowKeys,
      fixed: true,
      hideSelectAll: disabledRowKeys.length > 0,
      onSelect: (record: any, selected: boolean, selectedRows: any) => {
        if (disabledRowKeys.includes(record[rowKey])) {
          onDisableSelection && onDisableSelection(record);
          return;
        }
        if (mandatoryRowKeys.includes(record[rowKey])) {
          onMandatorySelection && onMandatorySelection(record);
          return;
        }
        let newSelectedRowKeys: React.ReactText[] = [];
        let newSelectedRows: { [key: string]: any }[] = [];

        if (rowSelection?.type === 'radio') {
          newSelectedRowKeys = selectedRows.map(item => item[rowKey]).slice();
          newSelectedRows = selectedRows.slice();
        } else if (selected) {
          newSelectedRowKeys = selectedRowKeys.slice();
          newSelectedRows = selectedRows.slice();
          newSelectedRowKeys.push(record[rowKey]);
          newSelectedRows.push(record);

          triggerDispatch({
            selectedRowKeys: newSelectedRowKeys,
            selectedRows: newSelectedRows,
          });
        } else {
          selectedRows.forEach(row => {
            if (row[rowKey] !== record[rowKey]) {
              newSelectedRowKeys.push(row[rowKey]);
              newSelectedRows.push(row);
            }
          });
        }

        triggerDispatch({
          selectedRowKeys: newSelectedRowKeys,
          selectedRows: newSelectedRows,
        });
        onSelectionChange && onSelectionChange(newSelectedRowKeys, newSelectedRows);
      },
      onSelectAll: (selected: boolean, _: any, changeRows: { [key: string]: any }[]) => {
        const newSelectedRowKeys: React.ReactText[] = [];
        const newSelectedRows: { [key: string]: any }[] = [];
        if (selected) {
          const mergeRows = selectedRows.concat(changeRows);
          const mergeRowKeys = Array.from(new Set(mergeRows.map(row => row[rowKey])));
          mergeRows.forEach(row => {
            const currentKey = row[rowKey];
            if (mergeRowKeys.includes(currentKey) && !disabledRowKeys.includes(currentKey)) {
              newSelectedRowKeys.push(currentKey);
              newSelectedRows.push(row);
              mergeRowKeys.splice(mergeRowKeys.indexOf(currentKey), 1);
            }
          });
        } else {
          const removeRowKeys = changeRows.map(row => row[rowKey]);
          selectedRows.forEach(row => {
            if (!removeRowKeys.includes(row[rowKey]) || mandatoryRowKeys.includes(row[rowKey])) {
              newSelectedRowKeys.push(row[rowKey]);
              newSelectedRows.push(row);
            }
          });
        }
        triggerDispatch({
          selectedRowKeys: newSelectedRowKeys,
          selectedRows: newSelectedRows,
        });
        onSelectionChange && onSelectionChange(newSelectedRowKeys, newSelectedRows);
      },
      ...(typeof rowSelection === 'boolean' ? {} : rowSelection),
    };
  }

  const renderSearchComponent = useCallback(() => {
    const hasSearchNode = searchNode && searchNode.length > 0;
    const component = (() => {
      if (searchComponentType === 'searchBar') {
        return (
          <XuiSearchBar
            ref={curSearchComponentRaf}
            searchNode={searchNode as SearchBarNodeType[]}
            defaultValues={defaultValues}
            onSearch={search}
          />
        );
      }
      if (searchComponentType === 'searchHead') {
        return (
          <XuiSearchHead
            ref={curSearchComponentRaf}
            searchNode={searchNode as SearchHeadNodeType[]}
            defaultValues={defaultValues}
            onSearch={search}
          />
        );
      }

      return (
        <XuiSearchFrame
          ref={curSearchComponentRaf}
          searchNode={searchNode as SearchFrameNodeType[]}
          defaultValues={defaultValues}
          onSearch={search}
        />
      );
    })();

    return hasSearchNode ? component : null;
  }, [curSearchComponentRaf, defaultValues, searchComponentType, searchNode, search]);

  const isCustomNoData = useMemo(() => customNoData && tableDataSource.length === 0, [
    customNoData,
    tableDataSource,
  ]);

  return (
    <div
      className={classNames({
        [className]: true,
        [`${className}--custom`]: isCustomNoData,
        [wrapperClassName || '']: wrapperClassName,
      })}
    >
      {title && <XuiHeadLine title={title} extra={extraContent} />}
      {extraHead}
      {renderSearchComponent()}
      <div className={`${className}--renderBeforeTable`}>{renderBeforeTable}</div>
      <div className={`${className}--table`}>
        {(loading || propLoading) && (
          <div className={`${className}--table-loading`}>
            <Spin spinning />
          </div>
        )}
        <Table
          rowKey={(record: any) => record[rowKey]}
          bordered
          loading={false}
          columns={newColumns}
          dataSource={tableDataSource}
          scroll={scroll}
          {...tableProps}
          components={{
            header: {
              cell: XuiResizableTh,
            },
            body: {
              cell: XuiResizableTd,
            },
          }}
        />
        <div className={`${className}--table-pagination`}>
          {!closePagination && !!total && (
            <Pagination
              {...pagination({
                ...paginationConfig,
                current,
                pageSize,
                total,
                onChange: changeTablePagination,
              })}
            />
          )}
        </div>
      </div>
      {isCustomNoData && (
        <div className={`${className}--customNoData`}>
          <div>
            {(customNoData as { img?: JSX.Element }).img || <img src={tableNoDataPNG} alt='' />}
          </div>
          <div className={`${className}--customNoData-tip`}>
            {(customNoData as { tip?: string }).tip || '暂无数据'}
          </div>
        </div>
      )}
    </div>
  );
};

export default React.forwardRef(XuiTablePage);
