import React, { Fragment, useState, useContext } from "react";
import classNames from "classnames";
import { TableAddon } from "../../TableProps";
import { TableContext } from "../../TableContext";
import { TableDraggableContext } from "../../TableDraggableContext";
import { Icon } from "../../../icon";
import { noop } from "../../../_util/noop";
import { mergeRefs } from "../../../_util/merge-refs";
import { rowsDraggable, RowsDraggableOptions } from "./rowsDraggable";
import { injectPropsIfTargetNotExisted } from "../../util/inject-props-if-target-not-existed";

/**
 * `draggable` 插件用于支持拖拽排序操作。
 */
export interface DraggableOptions<Record = any> {
  /**
   * 当前支持 `react-beautiful-dnd`
   */
  module?: any;

  /**
   * 拖拽完成回调
   */
  onDragEnd?: (records: Record[], context: any) => void;

  /**
   * 拖拽开始时回调
   */
  onDragStart?: (context: any) => void;

  /**
   * 拖拽手柄列宽度
   */
  width?: string | number;

  /**
   * 拖拽手柄图标
   *
   * *参考 Icon 组件[图标类型](/component/icon)*
   *
   * @default "drop"
   */
  icon?: string;

  /**
   * 隐藏拖拽手柄图标
   *
   * *默认值：*
   *
   * - *内置拖拽 `"unhover"`*
   * - *第三方库拖拽 `false`*
   *
   * @since 2.4.2
   */
  hideHandler?: boolean | "unhover";

  /**
   * 是否整行可拖拽
   *
   * **内置拖拽当前不支持改变该行为**
   *
   * @since 2.4.2
   */
  full?: boolean;

  /**
   * 提供一个列的 `key`，将手柄组件插入到一个目标列
   *
   * *默认在最前新建一列插入*
   *
   * @since 2.6.7
   */
  targetColumnKey?: string;
}

const fallbackColumnKey = "__draggable_addon__";

const cellPaddingWidth = 20;

const reorder = (list, startIndex, endIndex) => {
  const result = Array.from(list);
  if (typeof endIndex === "undefined") {
    return result;
  }
  const [removed] = result.splice(startIndex, 1);
  result.splice(endIndex, 0, removed);

  return result;
};

export function draggable({
  module,
  width = 26,
  full,
  icon = "drop",
  hideHandler,
  targetColumnKey,
  onDragEnd = noop,
  onDragStart = noop,
  draggableRowKeys = "all",
  childrenColName = "childrenList",
  dragType = "row",
  relations,
  onExpandedKeysChange,
  expandedKeys,
  indent = 20,
}: DraggableOptions & RowsDraggableOptions): TableAddon {
  // 支持新的行拖拽，轻量版本
  if (!module) {
    return rowsDraggable({
      onDragEnd,
      onDragStart,
      draggableRowKeys,
      childrenColName,
      dragType,
      relations,
      onExpandedKeysChange,
      expandedKeys,
      indent,
      icon,
      hideHandler,
      width,
      targetColumnKey,
    });
  }

  const { Draggable } = module;
  let total = null;
  return {
    getInfo: () => ({ name: "draggable" }),
    onInjectProps: props => {
      const { records } = props;
      let columns = [...props.columns];
      if (hideHandler !== true) {
        columns = injectPropsIfTargetNotExisted(
          props.columns,
          targetColumnKey,
          {
            key: fallbackColumnKey,
            width,
            header: null,
            render: () => null,
          }
        );
      }
      total = records.length;
      return { ...props, columns, draggable: true };
    },
    onInjectColumn: render => (
      record,
      rowKey,
      recordIndex,
      column,
      columnIndex
    ) => {
      const result = render(record, rowKey, recordIndex, column, columnIndex);
      // 非表头
      if (columnIndex === -1) {
        return result;
      }
      if (
        recordIndex !== -1 &&
        (column.key === fallbackColumnKey || column.key === targetColumnKey)
      ) {
        result.children = (
          <DraggableTableRowIcon
            index={columnIndex}
            icon={icon}
            hideHandler={hideHandler}
          />
        );
      }
      return {
        props: result.props,
        children: (
          <DraggableTableCell index={columnIndex}>
            {result.children}
          </DraggableTableCell>
        ),
      };
    },
    onInjectRow: render => (record, rowKey, recordIndex, ...args) => {
      const result = render(record, rowKey, recordIndex, ...args);
      const row = (
        <Draggable key={rowKey} draggableId={rowKey} index={recordIndex}>
          {(provided, snapshot) => (
            <DraggableTableRowContext.Provider value={{ provided }}>
              {React.cloneElement(result.row, {
                ...provided.draggableProps,
                ...(full ? provided.dragHandleProps : {}),
                style: {
                  ...(result.row.props.style || {}),
                  ...provided.draggableProps.style,
                  ...(full ? provided.dragHandleProps.style : {}),
                },
                ref: provided.innerRef,
                className: classNames(result.row.props.className, {
                  "is-dragging": snapshot.isDragging,
                }),
              })}
            </DraggableTableRowContext.Provider>
          )}
        </Draggable>
      );
      return {
        ...result,
        row:
          recordIndex === total - 1 ? (
            <Fragment key={rowKey}>
              {row}
              <DraggableTablePlaceHolder />
            </Fragment>
          ) : (
            row
          ),
      };
    },
    onInjectBody: render => (...args) => {
      return (
        <DraggableTableBody
          module={module}
          onDragStart={onDragStart}
          onDragEnd={context => {
            const records = args[0];
            onDragEnd(
              reorder(
                records,
                context.source?.index,
                context.destination?.index
              ),
              context
            );
          }}
        >
          {render(...args)}
        </DraggableTableBody>
      );
    },
  };
}

export interface DraggableTableRowContextValue {
  provided?: any;
}

export const DraggableTableRowContext = React.createContext<
  DraggableTableRowContextValue
>({});

interface DraggableTableBodyProps extends React.HTMLAttributes<HTMLDivElement> {
  module: DraggableOptions["module"];
  onDragEnd: (result: any) => void;
  onDragStart?: (context: any) => void;
  children: JSX.Element;
}

const DraggableTableBody = React.forwardRef(
  (
    {
      module,
      onDragEnd,
      onDragStart,
      children,
      ...props
    }: DraggableTableBodyProps,
    ref
  ) => {
    const { DragDropContext, Droppable } = module;
    const [isDragging, setIsDragging] = useState<boolean>(false);

    return (
      <DragDropContext
        onBeforeDragStart={() => setIsDragging(true)}
        onDragStart={context => onDragStart(context)}
        onDragEnd={result => {
          setIsDragging(false);
          onDragEnd(result);
        }}
      >
        <Droppable droppableId="droppable">
          {provided => {
            return (
              <TableDraggableContext.Provider
                value={{
                  isDragging,
                  placeholder: provided.placeholder,
                }}
              >
                {React.cloneElement(children, {
                  ...props,
                  ref: mergeRefs(provided.innerRef, ref),
                  className: classNames(
                    children.props.className,
                    props.className,
                    {
                      "is-dragging": isDragging,
                    }
                  ),
                })}
              </TableDraggableContext.Provider>
            );
          }}
        </Droppable>
      </DragDropContext>
    );
  }
);

function DraggableTableRowIcon({
  icon,
  index,
  hideHandler,
}: {
  icon: string;
  index: number;
  hideHandler: DraggableOptions["hideHandler"];
}) {
  const { columnsWidths } = useContext(TableContext);
  const { isDragging } = useContext(TableDraggableContext);
  const { provided } = useContext(DraggableTableRowContext);
  if (!provided) {
    return null;
  }
  return (
    <div
      style={
        isDragging
          ? { width: columnsWidths[index] - cellPaddingWidth }
          : undefined
      }
      className={classNames({
        "hover-icon": hideHandler === "unhover" && !isDragging,
      })}
    >
      <Icon type={icon} {...provided.dragHandleProps} />
    </div>
  );
}

function DraggableTablePlaceHolder() {
  const { placeholder } = useContext(TableDraggableContext);
  return <>{placeholder}</>;
}

function DraggableTableCell({ index, children }) {
  const { columnsWidths } = useContext(TableContext);
  const { isDragging } = useContext(TableDraggableContext);

  return (
    <div
      style={{
        width: isDragging ? columnsWidths[index] - cellPaddingWidth : undefined,
      }}
    >
      {children}
    </div>
  );
}
