/**
 * Handle virtual list of the TreeNodes.
 */

import * as React from "react";
import VirtualList from "rc-virtual-list";
import { FlattenNode, Key, DataEntity, DataNode, ScrollTo } from "./interface";
import { RefMotionTreeNode as MotionTreeNode } from "./motion-tree-node";
import { findExpandedKeys, getExpandRange } from "./utils/diff-util";
import { getTreeNodeProps, getKey } from "./utils/tree-util";

//Hidden Styles
const HIDDEN_STYLE = {
  width: 0,
  height: 0,
  display: "flex",
  overflow: "hidden",
  opacity: 0,
  border: 0,
  padding: 0,
  margin: 0,
};

//foo-bar func
const noop = () => {};

//
const MOTION_KEY = `CO_TREE_MOTION_${Math.random()}`;

const MotionNode: DataNode = {
  key: MOTION_KEY,
};

//Basic setting default value
//
const MotionEntity: DataEntity = {
  key: MOTION_KEY,
  level: 0,
  index: 0,
  pos: "0",
  node: MotionNode,
};

const MotionFlattenData: FlattenNode = {
  parent: null,
  children: [],
  pos: MotionEntity.pos,
  data: MotionNode,
  /** Hold empty list here since we do not use it */
  isStart: [],
  isEnd: [],
};

//node已挂载列表
//
interface NodeListRef {
  scrollTo: ScrollTo;
}

interface NodeListProps {
  prefixCls: string;
  style: React.CSSProperties;
  data: FlattenNode[];
  motion: any;
  focusable?: boolean;
  activeItem: FlattenNode;
  focused?: boolean;
  tabIndex: number;
  selectable?: boolean;
  disabled?: boolean;

  expandedKeys: Key[];
  selectedKeys?: Key[];
  loadedKeys: Key[];
  loadingKeys: Key[];
  keyEntities: Record<Key, DataEntity>;

  dragging: boolean;
  dragOverNodeKey: Key;
  dropPosition: number;

  // Virtual list
  height: number;
  itemHeight: number;
  virtual?: boolean;

  onKeyDown?: React.KeyboardEventHandler<HTMLDivElement>;
  onFocus?: React.FocusEventHandler<HTMLDivElement>;
  onBlur?: React.FocusEventHandler<HTMLDivElement>;
  onActiveChange: (key: Key) => void;

  onListChangeStart: () => void;
  onListChangeEnd: () => void;
}

/**
 * We only need get visible content items to play the animation.
 */
//
const getMinimumRangeTransitionRange = (
  list: FlattenNode[],
  height: number,
  itemHeight: number
) => {
  if (!height) {
    return list;
  }

  return list.slice(0, Math.ceil(height / itemHeight) + 1);
};

//Item Key
//
const itemKey = (item: FlattenNode) => {
  const {
    data: { key },
    pos,
  } = item;
  return getKey(key, pos);
};

// Access Path
const getAccessibilityPath = (item: FlattenNode): string => {
  let path = String(item.data.key);
  let current = item;

  while (current.parent) {
    current = current.parent;
    path = `${current.data.key} > ${path}`;
  }
  return path;
};

//Declaration of RefNodeList with the old type NodeListRef and NodeListProps
const RefNodeList: React.RefForwardingComponent<NodeListRef, NodeListProps> = (
  props,
  ref
) => {
  const {
    prefixCls,
    data,
    selectable,
    expandedKeys,
    selectedKeys,
    loadedKeys,
    loadingKeys,
    keyEntities,
    disabled,

    dragging,
    dragOverNodeKey,
    dropPosition,
    motion,

    height,
    itemHeight,
    virtual,

    focusable,
    activeItem,
    focused,
    tabIndex,

    onKeyDown,
    onFocus,
    onBlur,
    onActiveChange,

    onListChangeStart,
    onListChangeEnd,

    ...domProps
  } = props;

  // =============================== Ref ================================
  const listRef = React.useRef<VirtualList<FlattenNode>>(null);
  React.useImperativeHandle(ref, () => ({
    scrollTo: (scroll) => {
      listRef.current.scrollTo(scroll);
    },
  }));

  // ============================== Motion ==============================
  const [disableVirtual, setDisableVirtual] = React.useState(false);
  const [prevExpandedKeys, setPrevExpandedKeys] = React.useState(expandedKeys);
  const [prevData, setPrevData] = React.useState(data);
  const [transitionData, setTransitionData] = React.useState(data);
  const [transitionRange, setTransitionRange] = React.useState([]);
  const [motionType, setMotionType] = React.useState<"show" | "hide" | null>(
    null
  );

  const onMotionEnd = () => {
    setPrevData(data);
    setTransitionData(data);
    setTransitionRange([]);
    setMotionType(null);
    setDisableVirtual(false);

    onListChangeEnd();
  };

  // Do animation if expanded keys changed
  React.useEffect(() => {
    setPrevExpandedKeys(expandedKeys);

    const diffExpanded = findExpandedKeys(prevExpandedKeys, expandedKeys);

    if (diffExpanded.key !== null) {
      if (diffExpanded.add) {
        const keyIndex = prevData.findIndex(
          ({ data: { key } }) => key === diffExpanded.key
        );

        if (motion) setDisableVirtual(true);
        const rangeNodes = getMinimumRangeTransitionRange(
          getExpandRange(prevData, data, diffExpanded.key),
          height,
          itemHeight
        );

        const newTransitionData: FlattenNode[] = prevData.slice();
        newTransitionData.splice(keyIndex + 1, 0, MotionFlattenData);

        setTransitionData(newTransitionData);
        setTransitionRange(rangeNodes);
        setMotionType("show");
      } else {
        const keyIndex = data.findIndex(
          ({ data: { key } }) => key === diffExpanded.key
        );

        if (motion) setDisableVirtual(true);
        const rangeNodes = getMinimumRangeTransitionRange(
          getExpandRange(data, prevData, diffExpanded.key),
          height,
          itemHeight
        );

        const newTransitionData: FlattenNode[] = data.slice();
        newTransitionData.splice(keyIndex + 1, 0, MotionFlattenData);

        setTransitionData(newTransitionData);
        setTransitionRange(rangeNodes);
        setMotionType("hide");
      }

      // Trigger when `motion` provided
      if (motion) {
        onListChangeStart();
      }
    } else if (prevData !== data) {
      // If whole data changed, we just refresh the list
      setPrevData(data);
      setTransitionData(data);
    }
  }, [expandedKeys, data]);

  // We should clean up motion if is changed by dragging
  React.useEffect(() => {
    if (!dragging) {
      onMotionEnd();
    }
  }, [dragging]);

  const mergedData = motion ? transitionData : data;

  const treeNodeRequiredProps = {
    expandedKeys,
    selectedKeys,
    loadedKeys,
    loadingKeys,
    dragOverNodeKey,
    dropPosition,
    keyEntities,
  };

  return (
    <div>
      {focused && activeItem && (
        <span style={HIDDEN_STYLE} aria-live="assertive">
          {getAccessibilityPath(activeItem)}
        </span>
      )}

      <div role="tree">
        <input
          style={HIDDEN_STYLE}
          disabled={focusable === false || disabled}
          tabIndex={focusable !== false ? tabIndex : null}
          onKeyDown={onKeyDown}
          onFocus={onFocus}
          onBlur={onBlur}
          value=""
          onChange={noop}
        />
      </div>

      <VirtualList<FlattenNode>
        {...domProps}
        disabled={disableVirtual}
        data={mergedData}
        itemKey={itemKey}
        height={height}
        fullHeight={false}
        virtual={virtual}
        itemHeight={itemHeight}
        onSkipRender={onMotionEnd}
        prefixCls={`${prefixCls}-list`}
        ref={listRef}
      >
        {(treeNode: FlattenNode) => {
          const {
            pos,
            data: { key, ...restProps },
            isStart,
            isEnd,
          } = treeNode;
          const mergedKey = getKey(key, pos);
          delete restProps.children;

          const treeNodeProps = getTreeNodeProps(
            mergedKey,
            treeNodeRequiredProps
          );

          return (
            <MotionTreeNode
              {...restProps}
              {...treeNodeProps}
              active={activeItem && key === activeItem.data.key}
              pos={pos}
              data={treeNode.data}
              isStart={isStart}
              isEnd={isEnd}
              motion={motion}
              motionNodes={key === MOTION_KEY ? transitionRange : null}
              motionType={motionType}
              onMotionEnd={onMotionEnd}
              treeNodeRequiredProps={treeNodeRequiredProps}
              onMouseMove={() => {
                onActiveChange(null);
              }}
            />
          );
        }}
      </VirtualList>
    </div>
  );
};

const NodeList = React.forwardRef(RefNodeList);
NodeList.displayName = "NodeList";

export {
  NodeList,
  itemKey,
  getMinimumRangeTransitionRange,
  NodeListRef,
  MotionEntity,
  MOTION_KEY,
};
