import { CellMouseDownEvent, CellMouseOverEvent, IRowNode } from 'ag-grid-community';
import { MutableRefObject, RefObject, useCallback, useEffect, useRef, useState } from 'react';

import { useInterval } from '../../utils/useInterval';
import { GridBaseRow } from '../types';

export interface CellLocation {
  rowId: string;
  colId: string;
  clickLocation: [number, number];
}

export interface GridRanges<TData extends GridBaseRow> {
  selectedColIds: string[];
  selectedNodes: IRowNode<TData>[];
}

export const useGridRangeSelection = <TData extends GridBaseRow>({
  enableRangeSelection,
  gridDivRef,
  rangeStartRef,
  rangeEndRef,
  hasSelectedMoreThanOneCellRef,
  rangeSortedNodesRef,
}: {
  enableRangeSelection: boolean;
  gridDivRef: RefObject<HTMLDivElement | null>;
  rangeStartRef: MutableRefObject<CellLocation | null>;
  rangeEndRef: MutableRefObject<CellLocation | null>;
  hasSelectedMoreThanOneCellRef: MutableRefObject<boolean>;
  rangeSortedNodesRef: MutableRefObject<IRowNode<TData>[] | null>;
}) => {
  const [refreshIntervalEnabled, setRefreshIntervalEnabled] = useState(false);

  const ranges = useCallback((): GridRanges<TData> => {
    const gridElement = gridDivRef.current!;
    const rangeStart = rangeStartRef.current!;
    const rangeEnd = rangeEndRef.current!;
    const rangeSortedNodes = rangeSortedNodesRef.current!;
    if (!gridElement || !rangeStart || !rangeEnd || !rangeSortedNodes) {
      return {
        selectedColIds: [],
        selectedNodes: [],
      };
    }

    const elStyleLeftComparator = (el1: Element, el2: Element) => elStyleLeft(el1) - elStyleLeft(el2);
    const elStyleLeft = (el: Element): number => parseFloat((el as HTMLElement).style.left) ?? 0;

    const getSortedColIds = (): string[] => {
      //
      const leftHeaders = [...gridElement.querySelectorAll('.ag-pinned-left-header .ag-header-cell')].sort(
        elStyleLeftComparator,
      );
      const centerHeaders = [...gridElement.querySelectorAll('.ag-header-viewport .ag-header-cell')].sort(
        elStyleLeftComparator,
      );
      const rightHeaders = [...gridElement.querySelectorAll('.ag-pinned-right-header .ag-header-cell')].sort(
        elStyleLeftComparator,
      );

      return [...leftHeaders, ...centerHeaders, ...rightHeaders].map((el, i) => el.getAttribute('col-id') ?? String(i));
    };

    const sortedColIds = getSortedColIds();

    const startColIndex = sortedColIds.indexOf(rangeStart.colId);
    const endColIndex = sortedColIds.indexOf(rangeEnd.colId);
    const selectedColIds = sortedColIds.slice(
      Math.min(startColIndex, endColIndex),
      Math.max(startColIndex, endColIndex) + 1,
    );

    const startRowIndex = rangeSortedNodes.findIndex((node) => node.data!.id === rangeStart.rowId);
    const endRowIndex = rangeSortedNodes.findIndex((node) => node.data!.id === rangeEnd.rowId);
    const selectedNodes = rangeSortedNodes.slice(
      Math.min(startRowIndex, endRowIndex),
      Math.max(startRowIndex, endRowIndex) + 1,
    );

    return { selectedColIds, selectedNodes };
  }, [gridDivRef, rangeSortedNodesRef, rangeStartRef, rangeEndRef]);

  const redrawSelectedRanges = useCallback(() => {
    const gridElement = gridDivRef.current!;
    const { selectedColIds, selectedNodes } = ranges();

    selectedColIds.forEach((colId, colIndex) => {
      selectedNodes.forEach((node, rowIndex) => {
        const rowId = node.data!.id;
        const cell = gridElement.querySelector(
          `.ag-row[row-id=${JSON.stringify(String(rowId))}] .ag-cell[col-id=${JSON.stringify(colId)}`,
        );
        cell?.classList.add('rangeSelect');
        if (colIndex === 0) {
          cell?.classList.add('rangeSelectLeft');
        }
        if (colIndex === selectedColIds.length - 1) {
          cell?.classList.add('rangeSelectRight');
        }
        if (rowIndex === 0) {
          cell?.classList.add('rangeSelectTop');
        }
        if (rowIndex === selectedNodes.length - 1) {
          cell?.classList.add('rangeSelectBottom');
        }
      });
    });
  }, [ranges, gridDivRef]);

  const updateRangeSelectionCellClasses = useCallback(
    (justRefresh?: boolean) => {
      //
      // Get all grid cols, sort by pinned, then style: left
      const gridElement = gridDivRef.current;
      if (!gridElement) {
        return;
      }

      // Clear all selections
      if (!justRefresh) {
        gridElement
          .querySelectorAll('.rangeSelect,.rangeSelectLeft,.rangeSelectTop,.rangeSelectRight,.rangeSelectBottom')
          .forEach((el) => {
            el.classList.remove(
              'rangeSelect',
              'rangeSelectLeft',
              'rangeSelectTop',
              'rangeSelectRight',
              'rangeSelectBottom',
            );
          });
      }

      // if range selection multiple add .Grid-container.rangeSelectingMultiple
      const rangeStart = rangeStartRef.current;
      const rangeEnd = rangeEndRef.current;

      if (
        rangeStart !== null &&
        rangeEnd !== null &&
        (hasSelectedMoreThanOneCellRef.current ||
          rangeStart.colId !== rangeEnd.colId ||
          rangeStart.rowId !== rangeEnd.rowId)
      ) {
        gridElement.classList.add('rangeSelectingMultiple');
        gridElement.querySelectorAll('.ag-cell-focus').forEach((el) => {
          el.classList.remove('ag-cell-focus');
        });
      } else {
        gridElement.classList.remove('rangeSelectingMultiple');
        return;
      }
      const rangeSortedNodes = rangeSortedNodesRef.current;
      if (!rangeSortedNodes) {
        return;
      }

      redrawSelectedRanges();
    },
    [redrawSelectedRanges, rangeStartRef, rangeEndRef, gridDivRef, hasSelectedMoreThanOneCellRef, rangeSortedNodesRef],
  );

  // Handle updates after scroll / grid refresh
  useInterval(
    () => {
      updateRangeSelectionCellClasses(true);
    },
    refreshIntervalEnabled ? 150 : null,
  );

  const clearRangeSelection = useCallback(() => {
    hasSelectedMoreThanOneCellRef.current = false;
    setRefreshIntervalEnabled(false);

    hasSelectedMoreThanOneCellRef.current = false;
    rangeStartRef.current = null;
    rangeEndRef.current = null;

    updateRangeSelectionCellClasses();
  }, [updateRangeSelectionCellClasses, hasSelectedMoreThanOneCellRef, rangeStartRef, rangeEndRef]);

  const mouseDownRef = useRef<[number, number]>([0, 0]);
  const trackMouseDown = useCallback(
    (e: MouseEvent) => {
      mouseDownRef.current = [e.screenX, e.screenY];
    },
    [mouseDownRef],
  );

  const trackMouseUp = useCallback(
    (e: MouseEvent) => {
      if (rangeEndRef.current) {
        rangeEndRef.current.clickLocation = [e.screenX, e.screenY];
      }
    },
    [rangeEndRef],
  );

  useEffect(() => {
    document.addEventListener('mousedown', trackMouseDown, { capture: true });
    document.addEventListener('mouseup', trackMouseUp, { capture: true });
    return () => {
      document.removeEventListener('mousemove', trackMouseDown, { capture: true });
      document.removeEventListener('mouseup', trackMouseUp, { capture: true });
    };
  }, [trackMouseDown, trackMouseUp]);

  const onCellMouseOver = useCallback(
    (e: CellMouseOverEvent, mouseDown?: boolean) => {
      if (!enableRangeSelection) {
        return;
      }
      const button = (e.event as { buttons?: number }).buttons;
      if (button !== 1) {
        // TODO fix this?
        //rangeSortedNodesRef.current = null;
        return;
      }
      rangeEndRef.current = {
        rowId: e.node.data.id,
        colId: e.column.getColId(),
        clickLocation: [0, 0],
      };

      if (mouseDown) {
        const sortedNodes: IRowNode<TData>[] = [];
        e.api.forEachNodeAfterFilterAndSort((node: IRowNode<TData>) => sortedNodes.push(node));
        rangeSortedNodesRef.current = sortedNodes;
        setRefreshIntervalEnabled(true);
        rangeStartRef.current = { ...rangeEndRef.current, clickLocation: mouseDownRef.current };
      }

      if (
        rangeStartRef.current &&
        rangeEndRef.current &&
        (rangeStartRef.current.rowId !== rangeEndRef.current.rowId ||
          rangeStartRef.current.colId !== rangeEndRef.current.colId)
      ) {
        e.api.stopEditing();
        hasSelectedMoreThanOneCellRef.current = true;
        window.getSelection()?.removeAllRanges();
      }

      updateRangeSelectionCellClasses();
    },
    [
      enableRangeSelection,
      updateRangeSelectionCellClasses,
      hasSelectedMoreThanOneCellRef,
      rangeEndRef,
      rangeSortedNodesRef,
      rangeStartRef,
    ],
  );

  const onCellMouseDown = useCallback(
    (e: CellMouseDownEvent) => {
      const button = (e.event as { buttons?: number }).buttons;
      if (button === 1) {
        clearRangeSelection();
      }
      onCellMouseOver(e as unknown as CellMouseOverEvent, true);
    },
    [onCellMouseOver, clearRangeSelection],
  );

  return { clearRangeSelection, ranges, onCellMouseDown, onCellMouseOver };
};
