import {
  createColumnHelper,
  useReactTable,
  getCoreRowModel,
  Header,
} from '@tanstack/react-table';
import {ColumnType} from 'antd/es/table';
import {useEffect, useMemo, useState} from 'react';
import {DragStartEvent, DragEndEvent} from '@dnd-kit/core';
import {arrayMove} from '@dnd-kit/sortable';
import {
  createSearchContext,
  usePreloadSettingData,
  useTableSearchDispatch,
} from '@hooks';

function parseColumn<T>(columns: ColumnType<T>[]) {
  const hepler = createColumnHelper<Record<string, any>>();

  const tableColumns = columns.map(function (val) {
    const {dataIndex, title, render, align, fixed} = val;
    const meta = {
      align,
      fixed: Boolean(fixed),
      disabledSort: Boolean(fixed) || dataIndex === 'no',
    };

    if (render) {
      return hepler.display({
        id: dataIndex!.toString(),
        header: title?.toString(),
        cell(props) {
          return render(
            props.getValue(),
            props.row.original as T,
            props.row.index,
          );
        },
        meta,
      });
    }

    return hepler.accessor(dataIndex!.toString(), {
      header: title?.toString(),
      cell: props => props.getValue(),
      minSize: 0,
      meta,
    });
  });

  tableColumns.unshift(
    hepler.display({
      header: '序号',
      id: 'no',
      cell({row}) {
        return row.index + 1;
      },
      meta: {
        align: 'center',
        fixed: false,
        disabledSort: true,
      },
    }),
  );

  return tableColumns;
}

export function useTable<T extends Record<string, any>>(
  columns: ColumnType<T>[],
  data: T[],
  options: {
    pageSize: number;
    preloadKey?: string;
    searchContext: ReturnType<typeof createSearchContext>;
  },
) {
  const columnList = useMemo(
    function () {
      return parseColumn(columns);
    },
    [columns],
  );
  const preload = usePreloadSettingData(options.preloadKey);

  const [columnSizing, setColumnSizing] = useState(function () {
    if (preload?.tableWidth)
      return JSON.parse(preload.tableWidth) as Record<string, number>;

    const obj: Record<string, number> = {no: 64};

    columns.forEach(function ({dataIndex, width}) {
      if (dataIndex && width) {
        obj[dataIndex.toString()] = Number(width);
      }
    });

    return obj;
  });

  const [columnOrder, setColumnOrder] = useState(function () {
    if (preload?.tableOrder) return JSON.parse(preload?.tableOrder) as string[];

    const nextList = columns.map(val => val.dataIndex!.toString());

    return ['no', ...nextList];
  });

  const dispatch = useTableSearchDispatch(options.searchContext);
  useEffect(
    function () {
      dispatch({
        type: 'CHANGE_VALUE',
        payload: {key: 'tableWidth', value: JSON.stringify(columnSizing)},
      });
      dispatch({
        type: 'CHANGE_VALUE',
        payload: {key: 'tableOrder', value: JSON.stringify(columnOrder)},
      });
    },
    [columnOrder, columnSizing, dispatch],
  );

  const table = useReactTable({
    data,
    columns: columnList,
    state: {
      columnSizing,
      columnOrder,
    },
    getCoreRowModel: getCoreRowModel(),
    onColumnSizingChange: setColumnSizing,
    columnResizeMode: 'onChange',
    onColumnOrderChange: setColumnOrder,
  });

  const [active, setActive] = useState<Header<
    Record<string, any>,
    unknown
  > | null>(null);

  function onDragStart(event: DragStartEvent) {
    setActive(event.active.data.current?.header);
  }

  function onDragEnd({active, over}: DragEndEvent) {
    setActive(null);

    if (!over) return;

    const {id: activeId} = active,
      {id: overId} = over;
    const {disabledSort} = (over.data.current as any).header.column.columnDef
      .meta;

    if (disabledSort) return;

    if (activeId === overId) return;
    const {setColumnOrder} = table;

    setColumnOrder(function (prev) {
      const fromIdx = prev.findIndex(val => val === activeId),
        toIdx = prev.findIndex(val => val === overId);

      return arrayMove(prev, fromIdx, toIdx);
    });
  }

  return [
    {...table, active},
    {onDragStart, onDragEnd},
  ] as const;
}
