import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { DynamicListColumn } from '../../components';
import { Column } from '../../components/List/List.model';
import { Data } from '../../types';
import { ResizeIndicator } from './ResizeIndicator';

/**
 * Handles the resizing logic of the columns
 * @param columns The list of column definitions
 * @param onColumnSizesChanged Callback that will be called when the column sizes change
 * @returns an object containing the refs to the columns (add these to all column header elements),
 *          the mouseDown handler (add this to the element that should be used to resize the column),
 *          and the ResizeIndicator element (render this to show the resize indicator)
 */
export const useResize = <T extends Data>(
  columns: Column<T>[] | DynamicListColumn<T>[],
  onColumnSizesChanged: (columnSizes: string) => void,
): {
  cols: {
    ref: React.RefObject<HTMLTableCellElement>;
    orgSize: string | undefined;
  }[];
  mouseDown: (index: number) => void;
  ResizeIndicator: JSX.Element;
} => {
  const currentGridColumns = React.useRef<string | undefined>(undefined);

  const minCellWidth = 50;

  const cols = useMemo(
    () =>
      [
        ...columns,
        { size: 'auto' }, // Last column for action buttons and checkbox, etc
      ].map((col) => ({
        ref: React.createRef<HTMLTableCellElement>(),
        orgSize: col.size,
      })),
    [columns],
  );

  const [activeIndex, setActiveIndex] = useState<number | undefined>(undefined);
  const [resizePosition, setResizePosition] = useState<number | undefined>(
    undefined,
  );
  const resizeStart = React.useRef<
    { mouse: number; width: number } | undefined
  >(undefined);

  const mouseMove = useCallback(
    (e) => {
      if (activeIndex !== undefined) {
        const elem = cols[activeIndex].ref.current;
        if (!elem) {
          return;
        }

        if (!resizeStart.current) {
          resizeStart.current = {
            mouse: e.clientX,
            width: elem.offsetWidth,
          };
        }

        const start = resizeStart.current;
        if (!start) {
          return;
        }

        // Calculate position (clientX gives viewport position for fixed positioning)

        setResizePosition(e.clientX);

        const gridColumns = cols.map((col, i) => {
          if (!col.ref.current) {
            return col.orgSize;
          }

          if (i === activeIndex) {
            const width = start.width + e.clientX - start.mouse;
            if (width >= minCellWidth) {
              return `${width}px`;
            } else {
              return `${minCellWidth}px`;
            }
          }
          return `${col.ref.current.offsetWidth}px`;
        });

        currentGridColumns.current = gridColumns.join(' ');
      }
    },
    [activeIndex, cols],
  );

  const mouseDown = (index: number): void => {
    setActiveIndex(index);
  };

  const removeListeners = useCallback(() => {
    window.removeEventListener('mousemove', mouseMove);
    window.removeEventListener('mouseup', removeListeners);
  }, [mouseMove]);

  const mouseUp = useCallback(() => {
    // Apply the new column sizes only if a resize actually happened
    if (resizeStart.current !== undefined) {
      onColumnSizesChanged(
        currentGridColumns.current || cols.map((col) => col.orgSize).join(' '),
      );
    }

    setActiveIndex(undefined);
    setResizePosition(undefined);
    resizeStart.current = undefined;
    currentGridColumns.current = undefined;
    removeListeners();
  }, [removeListeners, onColumnSizesChanged, cols]);

  useEffect(() => {
    if (activeIndex !== undefined) {
      window.addEventListener('mousemove', mouseMove);
      window.addEventListener('mouseup', mouseUp);
    }

    return () => {
      removeListeners();
    };
  }, [activeIndex, mouseMove, mouseUp, removeListeners]);

  return {
    cols,
    mouseDown,
    ResizeIndicator: (
      <ResizeIndicator
        resizePosition={resizePosition}
        isResizing={activeIndex !== undefined}
      />
    ),
  };
};
