import React from "react";
import classNames from "classnames";
import { Icon } from "../../icon";
import { TableAddon, RowRenderContext, TableProps } from "../TableProps";
import { getRowKeyFromRecordKey } from "../util/get-row-key-from-record-key";
import { useMiddleware } from "../util/use-middleware";
import { injectPropsIfTargetNotExisted } from "../util/inject-props-if-target-not-existed";
import { useConfig } from "../../_util/config-context";

/**
 * `expandable` 插件用于支持表格行展开。
 */
export interface ExpandableAddonOptions<Record = any> {
  /**
   * 展开的行如何渲染
   */
  render?(record: Record): React.ReactNode;

  /**
   * 将当前记录展开成更多的记录
   */
  expand?(record: Record): Record[];

  /**
   * 展开行的键值
   */
  expandedKeys?: string[];

  /**
   * 用户进行展开/收起操作的时候，知会最新的键值
   */
  onExpandedKeysChange?: (
    value: string[],
    context: {
      event: React.SyntheticEvent;
      operateType: "expand" | "collapse";
      operateKey: string;
      operateRecord: Record;
    }
  ) => void;

  /**
   * 判断记录是否允许展开，如果不提供该判断，则认为所有记录均可展开
   */
  shouldRecordExpandable?: (record: Record) => boolean;

  /**
   * 展开行内容渲染之前，插入多少个间隙单元格
   */
  gapCell?: number;

  /**
   * 是否整行点击可展开
   * @default false
   * @since 2.2.0
   */
  rowExpand?: boolean;

  /**
   * 可以在插入的间隙单元格中，渲染指定的内容（需要配置 gapCell > 0）
   */
  gapCellRender?: (record: Record) => React.ReactNode;

  /**
   * 提供一个列的 `key`，将展开组件插入到一个目标列
   *
   * 默认在最前新建一列插入
   */
  targetColumnKey?: string;

  /**
   * 列宽度，可以指定 CSS 属性或数字 (单位：px)
   * @default 26
   */
  width?: string | number;

  /**
   * **高级用法**
   * 更改该插件的在每行的渲染内容，`element` 为默认渲染内容，`context` 中包含该行数据相关信息
   * @default x => x
   */
  iconRender?: (
    element: JSX.Element,
    context: RowRenderContext<Record>
  ) => React.ReactNode;
}

const fallbackColumnKey = "__expandable_addon__";

let rowDisabled: TableProps["rowDisabled"] = null;

export function expandable<Record = any>(
  options: ExpandableAddonOptions<Record>
): TableAddon<Record> {
  const {
    render,
    expand,
    shouldRecordExpandable,
    expandedKeys,
    onExpandedKeysChange,
    targetColumnKey,
    gapCell,
    gapCellRender,
    width = 26,
    iconRender = x => x,
    rowExpand,
  } = options;

  const expandedKeySet = new Set(expandedKeys || []);

  let getRowKey: ReturnType<typeof getRowKeyFromRecordKey>;
  let addons: TableAddon[] = [];

  const extraProps = {
    recordsExpandable: !!expand,
  };

  return {
    getInfo: () => ({ name: "expandable" }),
    onInjectProps: props => {
      const { recordKey } = props;
      getRowKey = getRowKeyFromRecordKey(recordKey);

      /* eslint-disable prefer-destructuring */
      rowDisabled = props.rowDisabled || (() => false);
      addons = props.addons;

      const columns = injectPropsIfTargetNotExisted(
        props.columns,
        targetColumnKey,
        {
          key: fallbackColumnKey,
          width,
          header: null,
          render: () => null,
        }
      );
      /* eslint-enable prefer-destructuring */

      return { ...props, columns, ...extraProps };
    },
    onInjectColumn: previous => (
      record,
      rowKey,
      recordIndex,
      column,
      columnIndex
    ) => {
      // 不是目标列
      if (column.key !== targetColumnKey && column.key !== fallbackColumnKey) {
        return previous(record, rowKey, recordIndex, column, columnIndex);
      }

      const { children: preChildren, props, ...result } = previous(
        record,
        rowKey,
        recordIndex,
        column,
        columnIndex
      );

      let children = preChildren;

      // 非表头
      if (recordIndex !== -1) {
        const key = getRowKey(record, recordIndex);
        const isExpandable =
          !shouldRecordExpandable || shouldRecordExpandable(record);
        const isExpanded = expandedKeySet.has(key);
        if (isExpandable) {
          const toggle = (event: React.MouseEvent) => {
            if (isExpanded) {
              const nextSet = new Set(expandedKeySet);
              nextSet.delete(key);
              onExpandedKeysChange(Array.from(nextSet.keys()), {
                operateType: "collapse",
                operateKey: key,
                operateRecord: record,
                event,
              });
            } else {
              onExpandedKeysChange(expandedKeys.concat(key), {
                operateType: "expand",
                operateKey: key,
                operateRecord: record,
                event,
              });
            }
          };
          const element = (
            <div>
              <IconWrap onClick={toggle}>
                <Icon type={isExpanded ? "arrowdown" : "arrowright"} />
              </IconWrap>
              {children}
            </div>
          );
          children = iconRender(element, {
            children: preChildren,
            record,
            rowKey,
            recordIndex,
            disabled: rowDisabled(record),
          });
        }
      }
      return { ...result, props, children };
    },
    onInjectRow: renderRow => (record, rowKey, recordIndex, columns) => {
      const key = getRowKey(record, recordIndex);
      const isExpandable =
        !shouldRecordExpandable || shouldRecordExpandable(record);
      let onClick = e => e;

      if (rowExpand && isExpandable) {
        const isExpanded = expandedKeySet.has(key);
        onClick = (event: React.MouseEvent) => {
          if (isExpanded) {
            const nextSet = new Set(expandedKeySet);
            nextSet.delete(key);
            onExpandedKeysChange(Array.from(nextSet.keys()), {
              operateType: "collapse",
              operateKey: key,
              operateRecord: record,
              event,
            });
          } else {
            onExpandedKeysChange(expandedKeys.concat(key), {
              operateType: "expand",
              operateKey: key,
              operateRecord: record,
              event,
            });
          }
        };
      }

      // 不是展开的行，原样渲染即可
      if (!expandedKeySet.has(rowKey)) {
        const { prepends, row, appends } = renderRow(
          record,
          rowKey,
          recordIndex,
          columns
        );
        return {
          prepends,
          row: React.cloneElement(row, {
            onClick: (event: React.MouseEvent<HTMLTableRowElement>) => {
              if (typeof row.props.onClick === "function") {
                row.props.onClick(event);
              }
              onClick(event);
            },
          }),
          appends,
        };
      }

      // 原始行的渲染内容
      const { prepends, row, appends } = renderRow(
        record,
        rowKey,
        recordIndex,
        columns
      );

      // 展开的内容
      const expands: JSX.Element[] = [];

      // 支持两种展开方式，提供了 render 方法的话，渲染一个行给 render() 作为渲染容器
      if (typeof render === "function") {
        expands.push(
          <tr className="tr__detailrow" key={`${rowKey}__expanded-row`}>
            {gapCell > 0 && (
              <td colSpan={gapCell}>
                {gapCellRender && <div>{gapCellRender(record)}</div>}
              </td>
            )}
            <td colSpan={columns.length - (gapCell > 0 ? gapCell : 0)}>
              <div>{render(record)}</div>
            </td>
          </tr>
        );
      }
      // 如果提供了 expand 方法，则意图为展开当前数据为更多的数据
      // 更多的数据走原来表格的渲染逻辑
      else if (typeof expand === "function") {
        // 展开的数据
        const expandedRecords = expand(record) || [];

        // 展开数据的渲染和追加
        expandedRecords.forEach((expandedRecord, expandedRecordIndex) => {
          const expandedRowKey = getRowKey(expandedRecord, expandedRecordIndex);
          // 这里如果直接使用 renderRow，则展开的数据无法经过插件的处理，所以调用了 useMiddleware
          const expandedInfo = useMiddleware(addons, "onInjectRow")(renderRow)(
            expandedRecord,
            expandedRowKey,
            recordIndex,
            columns
          );
          expands.push(...expandedInfo.prepends);
          expands.push(expandedInfo.row);
          expands.push(...expandedInfo.appends);
        });
      }

      return {
        prepends,
        row: React.cloneElement(row, {
          className: classNames(row.props.className, "tr__masterrow"),
          onClick: (event: React.MouseEvent<HTMLTableRowElement>) => {
            if (typeof row.props.onClick === "function") {
              row.props.onClick(event);
            }
            onClick(event);
          },
        }),
        appends: [...appends, ...expands],
      };
    },
  };
}

function IconWrap({ onClick, children }) {
  const { classPrefix } = useConfig();
  return (
    <a className={`${classPrefix}-table__icon-wrap`} onClick={onClick}>
      {children}
    </a>
  );
}
