/**
 * Particial Methods Collection
 */
import * as React from "react";
import {
  NodeElement,
  Key,
  DataNode,
  DataEntity,
  NodeInstance
} from "../interface";
import TreeNode, { TreeNodeProps } from "../tree-node";
import { TreeProps } from "../tree";

//Constant about Drag Event
const DRAG_SIDE_RANGE = 0.25;
const DRAG_MIN_GAP = 2;

//To Array
const toArray = (children: React.ReactNode): React.ReactElement[] => {
  const arrg = [];
  React.Children.forEach(children, c => {
    arrg.push(c);
  });
  return arrg;
};

//Delete Array
const arrDel = (list: Key[], value: Key) => {
  const clone = list.slice();
  const index = clone.indexOf(value);
  if (index >= 0) {
    clone.splice(index, 1);
  }
  return clone;
};

//Add Array
const arrAdd = (list: Key[], value: Key) => {
  const clone = list.slice();
  if (clone.indexOf(value) === -1) {
    clone.push(value);
  }
  return clone;
};

//Position value to array
const posToArr = (pos: string) => {
  return pos.split("-");
};

//Get Position
const getPosition = (level: string | number, index: number) => {
  return `${level}-${index}`;
};

//Judge Tree Node
const isTreeNode = (node: NodeElement) => {
  return node && node.type && node.type.isTreeNode;
};

//Get Drag Nodes keys
const getDragNodesKeys = (
  dragNodeKey: Key,
  keyEntities: Record<Key, DataEntity>
): Key[] => {
  const dragNodesKeys = [dragNodeKey];

  const entity = keyEntities[dragNodeKey];
  const dig = (list: DataEntity[] = []) => {
    list.forEach(({ key, children }) => {
      dragNodesKeys.push(key);
      dig(children);
    });
  };
  dig(entity.children);
  return dragNodesKeys;
};

//Calc Drop Position,
//Only used when drag, not affect SSR
const calcDropPosition = (e: React.MouseEvent, treeNode: NodeInstance) => {
  const { clientY } = e;
  const { top, bottom, height } = treeNode.selectHandle.getBoundingClientRect();
  const des = Math.max(height * DRAG_SIDE_RANGE, DRAG_MIN_GAP);

  if (clientY <= top + des) {
    return -1;
  } else if (clientY >= bottom - des) {
    return 1;
  } else {
    return 0;
  }
};

/**
 * Return selectedKeys according with multiple props
 * @param selectedKeys
 * @param this.props
 * @return [string]
 */

const calcSelectedKeys = (selectedKeys: Key[], props: TreeProps) => {
  if (!selectedKeys) return undefined;

  const { multiple } = props;
  if (multiple) {
    return selectedKeys.slice();
  }
  if (selectedKeys.length) {
    return [selectedKeys[0]];
  }
  return selectedKeys;
};

//Process Internal Props -- DataNode
const internalProcessProps = (props: DataNode): Partial<TreeNodeProps> => props;

//Convert Data Node to Tree Node Structure
const convertDataToTree = (
  treeData: DataNode[],
  processor?: { processProps: (prop: DataNode) => any }
): NodeElement[] => {
  if (!treeData) return [];

  const { processProps = internalProcessProps } = processor || {};
  const list = Array.isArray(treeData) ? treeData : [treeData];
  return list.map(
    ({ children, ...props }): NodeElement => {
      const childrenNodes = convertDataToTree(children, processor);

      return <TreeNode {...processProps(props)}>{childrenNodes}</TreeNode>;
    }
  );
};

/**
 * If user use `autoExpandParent` we should get the list of parent node
 * @param keyList
 * @param keyEntities
 */

const conductExpandParent = (
  keyList: Key[],
  keyEntities: Record<Key, DataEntity>
) => {
  const expandedKeys = {};

  const conductUp = (key: Key) => {
    if (expandedKeys[key]) return;
    const entity = keyEntities[key];
    if (!entity) return;

    expandedKeys[key] = true;

    const { parent, node } = entity;

    if (node.disabled) return;

    if (parent) {
      conductUp(parent.key);
    }
  };
  (keyList || []).forEach(key => {
    conductUp(key);
  });

  return Object.keys(expandedKeys);
};

/**
 * Returns only the data- and aria- key/value paris
 */

const getDataAndAria = (props: Partial<TreeProps | TreeNodeProps>) => {
  const omitProps: Record<string, string> = {};
  Object.keys(props).forEach(key => {
    if (key.startsWith("data-") || key.startsWith("aria-")) {
      omitProps[key] = props[key];
    }
  });
  return omitProps;
};

export {
  toArray,
  arrDel,
  arrAdd,
  posToArr,
  getPosition,
  isTreeNode,
  getDragNodesKeys,
  calcDropPosition,
  calcSelectedKeys,
  internalProcessProps,
  convertDataToTree,
  conductExpandParent,
  getDataAndAria
};
