import {
  createColumnHelper,
  useReactTable,
  getCoreRowModel,
  Header,
  RowSelectionState,
  SortingState,
  getSortedRowModel,
  ColumnSort,
  getExpandedRowModel,
  ExpandedState,
} from '@tanstack/react-table';
import {
  CSSProperties,
  Dispatch,
  SetStateAction,
  useEffect,
  useMemo,
  useRef,
  useState,
} from 'react';
import {DragStartEvent, DragEndEvent} from '@dnd-kit/core';
import {arrayMove} from '@dnd-kit/sortable';
import {createTableSettingContext, useContextSection} from '@hooks';
import TableCheck from './Check';
import {LDColumnsType} from '.';
import {TABLE_CELL_WIDTH} from '@utils';
import {useLatest} from 'ahooks';

function parseColumn<T extends Record<string, unknown>>(
  columns: LDColumnsType<T>[],
  enableSelect?: boolean,
  enableNo?: boolean,
) {
  const helper = createColumnHelper<Record<string, any>>();
  let hasSort = false, hasGroup = false;

  const parseColumns: LDColumnsType<T>[] = [...columns];
  if (enableNo) {
    parseColumns.unshift({
      title: '序号',
      dataIndex: 'no',
      align: 'center',
      width: TABLE_CELL_WIDTH.no,
      render(_, index, row) {
        return row.depth > 0 ? null : index + 1;
      },
    });
  }

  let leftPosition = enableSelect ? TABLE_CELL_WIDTH.checkbox : 0;
  let rightPosition = parseColumns.reduce(function(prev, next) {
    if (next.fixed !== 'right') return prev;

    return prev + next.width;
  }, 0);

  const columnList = parseColumns.map(function(val) {
    const {dataIndex, title, render, align, fixed, width, sort, children} = val;

    let fixedStyle: CSSProperties = {};

    if (fixed === 'left') {
      fixedStyle = {left: leftPosition};
      leftPosition += width;
    }

    if (fixed === 'right') {
      rightPosition -= width;
      fixedStyle = {right: rightPosition};
    }

    const meta = {
      align,
      fixed,
      fixedStyle,
      disabledSort: Boolean(fixed) || dataIndex === 'no',
      sort,
    };

    if (children && children.length) {
      if (!hasGroup) hasGroup = true;

      const {columnList: columns} = parseColumn(
        children,
        false,
        false,
      );

      const group = helper.group({
        header: title,
        columns,
        minSize: 0,
        size: width,
        meta,
      });

      return group;
    }

    if (!hasSort && sort) hasSort = true;

    if (render) {
      return helper.display({
        id: dataIndex.toString(),
        header: title.toString(),
        size: width,
        minSize: 0,
        cell({row}) {
          return render(
            row.original as T,
            row.index,
            row,
          );
        },
        meta,
      });
    }

    return helper.accessor(dataIndex.toString(), {
      header: title?.toString(),
      cell: props => props.getValue(),
      size: width,
      meta,
    });
  });

  enableSelect && columnList.unshift(
    helper.accessor('select', {
      header({table}) {
        return (
          <TableCheck
            checked={table.getIsAllRowsSelected()}
            indeterminate={table.getIsSomeRowsSelected()}
            onChange={table.getToggleAllRowsSelectedHandler()}
          />
        );
      },
      cell({row}) {
        return (
          <TableCheck
            checked={row.getIsSelected()}
            indeterminate={row.getIsSomeSelected()}
            onChange={row.getToggleSelectedHandler()}
            enabled={row.getCanSelect()}
          />
        );
      },
      size: TABLE_CELL_WIDTH.checkbox,
      meta: {
        align: 'center',
        fixed: 'left',
        disabledSort: true,
        fixedStyle: {left: 0},
      },
    }),
  );

  return {
    columnList,
    hasSort,
    hasGroup,
  };
}

export function useTable<T extends Record<string, any>>(
  columns: LDColumnsType<T>[],
  data: T[],
  options: {
    settingContext: ReturnType<typeof createTableSettingContext>;
    rowSelection?: RowSelectionState;
    setRowSelection?: Dispatch<SetStateAction<RowSelectionState>>;
    rawKey?: keyof T;
    onSortingChange?: (state: ColumnSort | null) => void;
    subRowKey?: keyof T;
  },
) {
  const {
    settingContext,
    rowSelection,
    setRowSelection,
    rawKey,
    onSortingChange,
    subRowKey,
  } = options;

  const {columnList, hasSort, hasGroup} = useMemo(
    function() {
      return parseColumn(
        columns,
        Boolean(setRowSelection),
        true,
      );
    },
    [columns, setRowSelection],
  );

  const preloadData = useContextSection(settingContext, state => state[0]);

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

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

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

    return hasGroup ? [] : ['select', 'no', ...nextList];
  });
  const [sorting, setSorting] = useState<SortingState>([]);
  const onSortingChangeFn = useLatest(onSortingChange);
  useEffect(function() {
    onSortingChangeFn.current?.(sorting.length ? sorting[0] : null);
  }, [onSortingChangeFn, sorting]);
  const [expanded, setExpanded] = useState<ExpandedState>({});

  const table = useReactTable({
    data,
    columns: columnList,
    state: {
      columnSizing,
      columnOrder,
      rowSelection,
      sorting,
      expanded,
    },
    getSortedRowModel: onSortingChange ? void 0 : getSortedRowModel(),
    getCoreRowModel: getCoreRowModel(),
    onColumnSizingChange: setColumnSizing,
    columnResizeMode: 'onChange',
    onColumnOrderChange: setColumnOrder,
    onRowSelectionChange: setRowSelection,
    getRowId: row => row[rawKey as string | undefined ?? 'id'],
    onSortingChange: setSorting,
    getSubRows: subRowKey ? row => row[subRowKey as string] : void 0,
    getExpandedRowModel: subRowKey ? getExpandedRowModel() : void 0,
    onExpandedChange: setExpanded,
  });

  type ActiveState = Header<Record<string, any>, unknown> | null;
  const [active, setActive] = useState<ActiveState>(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, hasSort, hasGroup},
    {onDragStart, onDragEnd},
  ] as const;
}

export function useTableShadow(tableSize: number) {
  const [scrollProgess, setScrollProgess] = useState<'start' | 'process' | 'end'>(
    'start',
  );
  const scrollTableRef = useRef<HTMLTableElement>(null);
  const headerTableRef = useRef<HTMLDivElement>(null);

  useEffect(
    function() {
      const el = scrollTableRef.current;

      function listener() {
        const elWidth = el?.getBoundingClientRect().width ?? 0,
              scrollWidth = el?.scrollWidth ?? 0,
              scrollLeft = el?.scrollLeft ?? 0;

        headerTableRef.current && (
          headerTableRef.current.scrollLeft = el?.scrollLeft ?? 0
        );

        setScrollProgess(function() {
          if (scrollLeft + elWidth === scrollWidth) return 'end';
          if (scrollLeft === 0) return 'start';

          return 'process';
        });
      }

      listener();

      window.addEventListener('resize', listener);
      el?.addEventListener('scroll', listener);

      return function() {
        window.addEventListener('resize', listener);
        el?.removeEventListener('scroll', listener);
      };
    },
    [tableSize],
  );

  return {scrollProgess, scrollTableRef, headerTableRef};
}

