import React, { Fragment, useContext } from "react";
import classNames from "classnames";
import { TableAddon, TableColumn } from "../../TableProps";
import { passiveEventSupported } from "../../../_util/get-passive-event-support";
import { getElementOffset } from "../../../_util/get-element-offset";
import {
  ResizableTable,
  ResizeContext,
  ResizableTableHead,
} from "./ResizableTable";
import { TableContext } from "../../TableContext";

/**
 * `columnsResizable` 插件用于支持表格列宽度调整。
 */
export interface ColumnsResizableOptions<Column = any> {
  /**
   * 列可调整到的最小宽度
   */
  minWidth?: number;

  /**
   * 列可调整到的最大宽度
   */
  maxWidth?: number;

  /**
   * 调整完成回调
   */
  onResizeEnd: (columns: Column[]) => void;

  /**
   * 指定哪些列支持列宽度调整，可传入键值
   * @since 2.7.4
   */
  columns?: string[];
}

export function columnsResizable({
  minWidth,
  maxWidth,
  onResizeEnd,
  columns: resizeableColumns,
}: ColumnsResizableOptions): TableAddon {
  return {
    getInfo: () => ({ name: "columnsResizable" }),
    onInjectProps: props => {
      return { ...props, columnsResizable: true };
    },
    onInjectTable: render => props => {
      const table = render(props);
      const { columns } = props;
      return <ResizableTable table={table} columns={columns} />;
    },
    onInjectHead: render => (...args) => {
      const head = render(...args);

      return <ResizableTableHead head={head} />;
    },
    onInjectColumn: render => (
      record,
      rowKey,
      recordIndex,
      column,
      columnIndex
    ) => {
      const result = render(record, rowKey, recordIndex, column, columnIndex);
      if (recordIndex === -1) {
        result.props = Object.assign({}, result.props, {
          column: column.key,
        });
      }

      /**
       * 仅表头注入拖拽元素
       * 传入 resizeableColumns 后，只对需要的列注入拖拽元素
       */
      if (
        recordIndex !== -1 ||
        "ignore" in column ||
        (resizeableColumns && !resizeableColumns.includes(column.key))
      ) {
        return { props: result.props, children: result.children };
      }

      const children = (
        <ResizableTableCell
          columnIndex={columnIndex}
          minWidth={minWidth}
          maxWidth={maxWidth}
          onResizeEnd={onResizeEnd}
          childrenCell={result.children}
        />
      );

      return { props: result.props, children };
    },
    onInjectRow: renderRow => (record, rowKey, recordIndex, columns) => {
      const result = renderRow(record, rowKey, recordIndex, columns);

      result.row = (
        <ResizableRowCell childrenRowCell={result.row} key={rowKey} />
      );

      return result;
    },
  };
}
interface ResizableRowCellProps {
  childrenRowCell: JSX.Element;
  className?: string;
}
function ResizableRowCell({
  childrenRowCell,
  ...props
}: ResizableRowCellProps) {
  const { isResizing } = useContext(ResizeContext);
  const className = classNames(
    childrenRowCell.props.className,
    {
      "no-hover": !!isResizing,
    },
    props.className
  );
  return React.cloneElement(childrenRowCell, { ...props, className });
}

interface ResizableTableCellProps extends ColumnsResizableOptions {
  childrenCell: React.ReactNode;
  columnIndex: number;
}
function ResizableTableCell({
  minWidth,
  maxWidth,
  onResizeEnd,
  childrenCell,
  columnIndex,
  ...props
}: ResizableTableCellProps) {
  const {
    wrapperRef,
    lineRef,
    columns,
    classPrefix,
    headRef,
    setIsResizing,
  } = useContext(ResizeContext);
  const { columnsWidths } = useContext(TableContext);
  type ResizeEvent = React.MouseEvent & React.TouchEvent;
  let startX = 0;
  const fitColumnWidths = (columns: TableColumn[]): TableColumn[] => {
    const tableWidth = headRef.current?.clientWidth;
    let totalWidth = 0;
    columns.forEach(column => {
      if (column.width) {
        totalWidth +=
          typeof column.width === "string"
            ? parseInt(column.width, 10)
            : column.width;
      }
    });

    if (totalWidth < tableWidth) {
      const lastIndex = columns.length - 1;
      if (typeof columns[lastIndex].width === "undefined") {
        return [...columns];
      }

      if (typeof columns[lastIndex].width === "string") {
        columns[lastIndex].width =
          parseInt(columns[lastIndex].width as string, 10) +
          (tableWidth - totalWidth);
      } else {
        columns[lastIndex].width =
          (columns[lastIndex].width as number) + (tableWidth - totalWidth);
      }
    }

    return [...columns];
  };
  const onResizeStart = (event: ResizeEvent): void => {
    setIsResizing(true);
    let isTouchEvent = false;
    const filterColumns = columns.filter(item => item.width);
    const hasReset = filterColumns.length === columnsWidths.length;
    const trNodes: HTMLElement[] = Array.prototype.slice.call(
      headRef.current.getElementsByTagName("tr"),
      0
    );
    const tdNodes: HTMLElement[] =
      trNodes && trNodes.length > 0
        ? Array.prototype.slice.call(trNodes[0].childNodes, 0)
        : [];
    if (tdNodes && tdNodes.length === columns.length && !hasReset) {
      tdNodes.forEach((tdNode, index) => {
        columns[index].width = tdNode.clientWidth;
      });
      const newColumns = columns.filter(column => !("ignore" in column));
      onResizeEnd(newColumns);
    }

    if (event.type === "touchstart") {
      if (event.touches && event.touches.length > 1) {
        return;
      }
      isTouchEvent = true;
    }
    startX = isTouchEvent
      ? Math.round(event.touches[0].clientX)
      : event.clientX;
    const left = startX - getElementOffset(wrapperRef.current).left;
    requestAnimationFrame(() => {
      lineRef.current.style.transform = `translate3d(${left}px, 0px, 0px)`;
      lineRef.current.style.display = "block";
    });
    const eventTarget = event.currentTarget;
    const thNode: any =
      eventTarget.parentNode && eventTarget.parentNode.parentNode;
    const moveHandler = (clientX: number) => {
      const resizeWidth = clientX - startX + thNode.clientWidth;
      let left = clientX - getElementOffset(wrapperRef.current).left;
      const baseLeftPosition =
        getElementOffset(thNode).left -
        getElementOffset(wrapperRef.current).left;
      if (resizeWidth <= 0) {
        left = baseLeftPosition + minWidth;
      } else if (maxWidth && resizeWidth > maxWidth) {
        left = baseLeftPosition + maxWidth;
      } else if (minWidth && resizeWidth < minWidth) {
        left = baseLeftPosition + minWidth;
      }
      requestAnimationFrame(() => {
        lineRef.current.style.transform = `translate3d(${left}px, 0px, 0px)`;
      });
    };
    const handlersAndEvents = {
      mouse: {
        moveEvent: "mousemove",
        moveHandler: (event: ResizeEvent) => {
          if (event.cancelable) {
            event.preventDefault();
            event.stopPropagation();
          }
          moveHandler(event.clientX);

          return false;
        },
        upEvent: "mouseup",
        upHandler: (event: ResizeEvent) => {
          document.removeEventListener(
            "mousemove",
            handlersAndEvents.mouse.moveHandler as any
          );
          document.removeEventListener(
            "mouseup",
            handlersAndEvents.mouse.upHandler as any
          );

          if (lineRef.current && lineRef.current.style) {
            lineRef.current.style.display = "none";
          }

          let resizeWidth = event.clientX - startX + thNode.clientWidth;
          if (resizeWidth <= 0) {
            resizeWidth = minWidth;
          }

          if (maxWidth && resizeWidth > maxWidth) {
            resizeWidth = maxWidth;
          }

          if (minWidth && resizeWidth < minWidth) {
            resizeWidth = minWidth;
          }

          columns[columnIndex] = Object.assign({}, columns[columnIndex], {
            width: resizeWidth,
          });
          const newColumns = fitColumnWidths(columns);
          setIsResizing(false);
          onResizeEnd(newColumns.filter(column => !("ignore" in column)));
        },
      },
      touch: {
        moveEvent: "touchmove",
        moveHandler: (event: ResizeEvent) => {
          if (event.cancelable) {
            event.preventDefault();
            event.stopPropagation();
          }
          moveHandler(event.touches[0].clientX);
          return false;
        },
        upEvent: "touchend",
        upHandler: (event: ResizeEvent) => {
          document.removeEventListener(
            handlersAndEvents.touch.moveEvent,
            handlersAndEvents.touch.moveHandler as any
          );
          document.removeEventListener(
            handlersAndEvents.touch.upEvent,
            handlersAndEvents.touch.moveHandler as any
          );
          if (lineRef.current && lineRef.current.style) {
            lineRef.current.style.display = "none";
          }
          let resizeWidth = event.clientX - startX + thNode.clientWidth;
          if (resizeWidth <= 0) {
            resizeWidth = minWidth;
          }

          if (maxWidth && resizeWidth > maxWidth) {
            resizeWidth = maxWidth;
          }

          if (minWidth && resizeWidth < minWidth) {
            resizeWidth = minWidth;
          }
          columns[columnIndex] = Object.assign({}, columns[columnIndex], {
            width: resizeWidth,
          });
          const newColumns = fitColumnWidths(columns);
          setIsResizing(false);
          onResizeEnd(newColumns.filter(column => !("ignore" in column)));
        },
      },
    };

    const events = isTouchEvent
      ? handlersAndEvents.touch
      : handlersAndEvents.mouse;
    const passiveIfSupported = passiveEventSupported()
      ? { passive: false }
      : false;
    document.addEventListener(
      events.moveEvent,
      events.moveHandler as any,
      passiveIfSupported
    );
    document.addEventListener(
      events.upEvent,
      events.upHandler as any,
      passiveIfSupported
    );
  };

  return (
    <Fragment>
      {childrenCell}
      <span
        draggable={false}
        className={`${classPrefix}-table__resize-line`}
        onMouseDown={onResizeStart as any}
        onTouchStart={onResizeStart as any}
      ></span>
    </Fragment>
  );
}
