import React, { forwardRef, Fragment, useRef, useContext } from "react";
import classNames from "classnames";
import { Text } from "../text";
import { TableBox } from "./TableBox";
import { TableProps, TableColumn } from "./TableProps";
import { getRowKeyFromRecordKey } from "./util/get-row-key-from-record-key";
import { useMiddleware } from "./util/use-middleware";
import { SlideTransition } from "../transition";
import { useConfig } from "../_util/config-context";
import {
  TableVirtualizedBody,
  TableVirtualizedBodyProps,
} from "./TableVirtualizedBody";
import { useResizeObserver } from "../_util/use-resize-observer";
import { DomRef } from "../domref";
import { noop } from "../_util/noop";
import { TableContext } from "./TableContext";
import { TableDraggableContext } from "./TableDraggableContext";
import { useIsomorphicLayoutEffect } from "../_util/use-isomorphic-layout-effect";
import { DragContext } from "./addons/draggable/DraggableTable";
import { getValueByPathKey } from "./util/get-value-by-path-key";

export function CellResizeObserver({
  children,
  onResize,
}: {
  children: React.ReactNode;
  onResize(width: number): void;
}) {
  const ref = useRef<HTMLTableCellElement>(null);
  const { width } = useResizeObserver(ref);
  // 拖拽中不重置宽度
  const { isDragging } = useContext(TableDraggableContext) || {};

  useIsomorphicLayoutEffect(() => {
    let didCancel = false;
    if (!didCancel && ref.current && width && !isDragging) {
      onResize(width);
    }
    return () => {
      didCancel = true;
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [width]);
  return <DomRef ref={ref}>{children}</DomRef>;
}

// 计算固定列偏移
export function getFixedOffset(
  columns: TableProps["columns"],
  index: number,
  columnsWidths: number[],
  fixed: TableColumn["fixed"]
): number {
  let offset = 0;
  if (fixed === "left") {
    for (let i = 0; i < index; ++i) {
      if (columns[i].fixed === "left" && columnsWidths[i]) {
        offset += columnsWidths[i];
      }
    }
  } else {
    for (let i = columns.length - 1; i > index; --i) {
      if (columns[i].fixed === "right" && columnsWidths[i]) {
        offset += columnsWidths[i];
      }
    }
  }
  return offset;
}

// 获取固定列最后一列以显示阴影
export function getFixedEndColumn(columns: TableProps["columns"]) {
  let left = 0;
  let right = columns.length - 1;
  for (let i = 0; i < right; ++i) {
    if (columns[i].fixed === "left") {
      left = i;
    }
  }
  for (let i = right; i >= 0; --i) {
    if (columns[i].fixed === "right") {
      right = i;
    }
  }
  return { left, right };
}

// 表格组件核心实现
export const TableBody = forwardRef(function TableBody(
  {
    columns,
    records,
    addons,
    recordKey,
    rowClassName,
    rowDisabled,
    topTip,
    bottomTip,
    tableBoxStyle,
    disableHoverHighlight,
    disableTextOverflow,
    onScrollCapture = noop,
    draggable,
    columnsDraggable,
    virtualizedOptions,
    onResize,
    recordsExpandable,
  }: TableProps & {
    tableBoxStyle?: React.CSSProperties;
    onScrollCapture?: (event: React.UIEvent<HTMLDivElement>) => void;
    // `scrollable` 开启虚拟滚动时注入该 props
    virtualizedOptions?: TableVirtualizedBodyProps;
    // `expandable` 开启且包含可展开行
    recordsExpandable?: boolean;
    // `draggable` 开启时注入该 props
    draggable?: boolean;
    // `columnsDraggable` 开启时注入该 props
    columnsDraggable?: boolean;
    // 固定列计算宽度
    onResize?: React.Dispatch<React.SetStateAction<number[]>>;
  },
  ref: React.Ref<HTMLDivElement>
) {
  const { classPrefix } = useConfig();
  const { scrollStatus, columnsWidths } = useContext(TableContext);
  const hasFixedColumn = columns.find(column => !!column.fixed);
  const { columnClassNames } = useContext(DragContext);
  // 键值的获取方式
  const getRowKey = getRowKeyFromRecordKey(recordKey);

  // 列渲染：(record, column) => { props, children }
  const renderColumn = useMiddleware(
    addons,
    "onInjectColumn"
  )((record, rowKey, recordIndex, column, columnIndex) => {
    let children = null;
    if (isFunction(column.render)) {
      children = column.render(
        record,
        rowKey,
        recordIndex,
        column,
        columnIndex
      );
    } else if (typeof record === "object" && record) {
      children = getValueByPathKey(record, column.key);
    }
    if (typeof children === "undefined") {
      // never render undefined
      children = null;
    }
    if (typeof children !== "object") {
      children = (
        <Text
          overflow={!disableTextOverflow}
          title={disableTextOverflow ? undefined : String(children)}
        >
          {children}
        </Text>
      );
    }
    return {
      props: {},
      children,
    };
  });

  // 行渲染：(columns, record) => { prepends, row, appends }
  const renderRow = useMiddleware(
    addons,
    "onInjectRow"
  )((record, rowKey, recordIndex, columns) => {
    const className = classNames(
      isFunction(rowClassName) ? rowClassName(record) : null,
      isFunction(rowDisabled) ? { "is-disabled": rowDisabled(record) } : null,
      { "no-hover": !!disableHoverHighlight }
    );

    return {
      prepends: [],
      row: (
        <tr key={rowKey} className={className || null}>
          {columns.map((column, index) => {
            const cKey = column.key;
            const { props, children } = renderColumn(
              record,
              rowKey,
              recordIndex,
              column,
              index
            );
            if (props.colSpan === 0 || props.rowSpan === 0) {
              return null;
            }
            // 开启虚拟滚动
            if (virtualizedOptions) {
              props.style = {
                ...(props.style || {}),
                display: "inline-block",
                overflow: "hidden",
                boxSizing: "border-box",
                flex: column.width ? undefined : 1,
                flexBasis: column.width || 1,
              };
            }

            // 固定列
            if (
              (column.fixed === "left" || column.fixed === "right") &&
              column.fixed !== scrollStatus &&
              scrollStatus !== "no-scroll"
            ) {
              const end = getFixedEndColumn(columns);
              props.className = classNames(
                props.className,
                `${classPrefix}-table__td--fixed`,
                {
                  [`${classPrefix}-table__td--fixed-${column.fixed}`]:
                    end[column.fixed] === index,
                }
              );
              props.style = {
                ...(props.style || {}),
                position: "sticky",
                [column.fixed]: getFixedOffset(
                  columns,
                  index,
                  columnsWidths,
                  column.fixed
                ),
              };
            }

            // 拖拽排序
            if (columnsDraggable) {
              props["data-column"] = cKey;
              if (
                columnClassNames[cKey] &&
                Array.isArray(columnClassNames[cKey])
              ) {
                props.className = classNames(
                  props.className,
                  ...columnClassNames[cKey]
                );
              }
            }
            const cell = (
              <Text key={cKey} parent="td" {...props} align={column.align}>
                <div>{children}</div>
              </Text>
            );

            // 需要获取列宽
            if (recordIndex === 0 && (hasFixedColumn || draggable)) {
              return (
                <CellResizeObserver
                  key={`${cKey}${index}`}
                  onResize={width => {
                    onResize(widths => {
                      const columnsWidths = [...widths];
                      columnsWidths[index] = width;
                      return columnsWidths;
                    });
                  }}
                >
                  {cell}
                </CellResizeObserver>
              );
            }

            return cell;
          })}
        </tr>
      ),
      appends: [],
    };
  });

  // 记录渲染：(record) => <tr>[]
  const renderRecord = (record: any, recordIndex: number) => {
    // 生成 rowKey
    const rowKey = getRowKey(record, recordIndex);
    const { prepends, row, appends } = renderRow(
      record,
      rowKey,
      recordIndex,
      columns
    );
    return [...prepends, row, ...appends].filter(Boolean);
  };

  const renderBody = useMiddleware(
    addons,
    "onInjectBody"
  )((records, columns, topTip, bottomTip) =>
    // 开启虚拟滚动
    virtualizedOptions ? (
      <TableVirtualizedBody
        ref={ref}
        tableBoxStyle={tableBoxStyle}
        onScrollCapture={onScrollCapture}
        records={records}
        renderRecord={renderRecord}
        getRowKey={getRowKey}
        expandable={recordsExpandable}
        topTip={
          topTip && (
            <tr className={`${classPrefix}-table__tr--blank-new`}>
              <td
                colSpan={columns.length}
                style={{
                  overflow: "hidden",
                  flex: 1,
                  display: "inline-block",
                  width: "100%",
                }}
              >
                {topTip}
              </td>
            </tr>
          )
        }
        bottomTip={
          bottomTip && (
            <tr className={`${classPrefix}-table__tr--blank-new`}>
              <td
                colSpan={columns.length}
                style={{
                  overflow: "hidden",
                  flex: 1,
                  display: "inline-block",
                  width: "100%",
                }}
              >
                {bottomTip}
              </td>
            </tr>
          )
        }
        {...virtualizedOptions}
      />
    ) : (
      <div
        className={`${classPrefix}-table__body`}
        ref={ref}
        onScrollCapture={onScrollCapture}
      >
        <TableBox
          columns={columns}
          style={tableBoxStyle}
          classPrefix={classPrefix}
        >
          <tbody>
            <SlideTransition in={Boolean(topTip)} exit={false}>
              <tr className={`${classPrefix}-table__tr--blank-new`}>
                <td colSpan={columns.length}>{topTip}</td>
              </tr>
            </SlideTransition>
            {(records || []).map((record, index) => (
              <Fragment key={getRowKey(record, index)}>
                {renderRecord(record, index)}
              </Fragment>
            ))}
            <SlideTransition in={Boolean(bottomTip)} exit={false}>
              <tr className={`${classPrefix}-table__tr--blank-new`}>
                <td colSpan={columns.length}>{bottomTip}</td>
              </tr>
            </SlideTransition>
          </tbody>
        </TableBox>
      </div>
    )
  );

  return renderBody(records, columns, topTip, bottomTip);
});

TableBody.displayName = "TableBody";

function isFunction(target: any): target is Function {
  return typeof target === "function";
}
