import * as React from "react";
import classNames from "classnames";
// @ts-ignore

import { TreeContext, TreeContextProps } from "./context-types";
import { getDataAndAria } from "./utils/common-util";
import { IconType, Key, DataNode } from "./interface";
import { Indent } from "./indent";
import { convertNodePropsToEventData } from "./utils/tree-util";

const ICON_OPEN = "open";
const ICON_CLOSE = "close";

const defaultTitle = "---";

interface TreeNodeProps {
  eventKey?: Key; // Pass by parent `cloneElement`
  prefixCls?: string;
  className?: string;
  style?: React.CSSProperties;

  // By parent
  expanded?: boolean;
  selected?: boolean;
  loaded?: boolean;
  loading?: boolean;
  title?: React.ReactNode | ((data: DataNode) => React.ReactNode);
  dragOver?: boolean;
  dragOverGapTop?: boolean;
  dragOverGapBottom?: boolean;
  pos?: string;
  domRef?: React.Ref<HTMLDivElement>;
  /** New added in Tree for easy data access */
  data?: DataNode;
  isStart?: boolean[];
  isEnd?: boolean[];
  active: boolean;
  onMouseMove?: React.MouseEventHandler<HTMLDivElement>;

  // By user
  isLeaf?: boolean;
  selectable?: boolean;
  disabled?: boolean;
  icon?: IconType;
  switcherIcon?: IconType;
  children?: React.ReactNode;

  context?: TreeContextProps;
}

interface TreeNodeState {
  dragNodeHighlight: boolean;
}

class TreeNode extends React.Component<TreeNodeProps, TreeNodeState> {
  public state = {
    dragNodeHighlight: false
  };

  public selectHandle: HTMLSpanElement;

  // Isomorphic needn't load data in server side
  componentDidMount() {
    this.syncLoadData(this.props);
  }

  componentDidUpdate() {
    this.syncLoadData(this.props);
  }

  onSelectorClick = e => {
    // Click trigger before select operation
    const {
      context: { onNodeClick },
      selected
    } = this.props;
    onNodeClick(e, convertNodePropsToEventData(this.props));

    if (this.isSelectable() && !selected) {
      this.onSelect(e);
    } else {
      return;
    }
  };

  onSelectorDoubleClick = e => {
    const {
      context: { onNodeDoubleClick }
    } = this.props;
    onNodeDoubleClick(e, convertNodePropsToEventData(this.props));
  };

  onSelect = e => {
    if (this.isDisabled()) return;

    const {
      context: { onNodeSelect }
    } = this.props;
    e.preventDefault();
    onNodeSelect(e, convertNodePropsToEventData(this.props));
  };

  onMouseEnter = e => {
    const {
      context: { onNodeMouseEnter }
    } = this.props;
    onNodeMouseEnter(e, convertNodePropsToEventData(this.props));
  };

  onMouseLeave = e => {
    const {
      context: { onNodeMouseLeave }
    } = this.props;
    onNodeMouseLeave(e, convertNodePropsToEventData(this.props));
  };

  onContextMenu = e => {
    const {
      context: { onNodeContextMenu }
    } = this.props;
    onNodeContextMenu(e, convertNodePropsToEventData(this.props));
  };

  onDragStart = e => {
    const {
      context: { onNodeDragStart }
    } = this.props;

    e.stopPropagation();
    this.setState({
      dragNodeHighlight: false
    });
    onNodeDragStart(e, this);

    try {
      // ie throw error
      // firefox-need-it
      e.dataTransfer.setData("text/plain", "");
    } catch (error) {
      // empty
    }
  };

  onDragEnter = e => {
    const {
      context: { onNodeDragEnter }
    } = this.props;

    e.preventDefault();
    e.stopPropagation();
    onNodeDragEnter(e, this);
  };

  onDragOver = e => {
    const {
      context: { onNodeDragOver }
    } = this.props;

    e.preventDefault();
    e.stopPropagation();
    onNodeDragOver(e, this);
  };

  onDragLeave = e => {
    const {
      context: { onNodeDragLeave }
    } = this.props;

    e.stopPropagation();
    onNodeDragLeave(e, this);
  };

  onDragEnd = e => {
    const {
      context: { onNodeDragEnd }
    } = this.props;

    e.stopPropagation();
    this.setState({
      dragNodeHighlight: false
    });
    onNodeDragEnd(e, this);
  };

  onDrop = e => {
    const {
      context: { onNodeDrop }
    } = this.props;

    e.preventDefault();
    e.stopPropagation();
    this.setState({
      dragNodeHighlight: false
    });
    onNodeDrop(e, this);
  };

  // Disabled item still can be switch
  onExpand: React.MouseEventHandler<HTMLDivElement> = e => {
    const {
      context: { onNodeExpand }
    } = this.props;
    onNodeExpand(e, convertNodePropsToEventData(this.props));
  };

  // Drag usage
  setSelectHandle = node => {
    this.selectHandle = node;
  };

  getNodeState = () => {
    const { expanded } = this.props;

    if (this.isChild()) {
      return null;
    }

    return expanded ? ICON_OPEN : ICON_CLOSE;
  };

  hasChildren = () => {
    const { eventKey } = this.props;
    const {
      context: { keyEntities }
    } = this.props;
    const { children } = keyEntities[eventKey] || {};

    return !!(children || []).length;
  };

  isChild = () => {
    const { isLeaf, loaded } = this.props;
    const {
      context: { loadData }
    } = this.props;

    const hasChildren = this.hasChildren();

    if (isLeaf === false) {
      return false;
    }

    return (
      isLeaf ||
      (!loadData && !hasChildren) ||
      (loadData && loaded && !hasChildren)
    );
  };

  isDisabled = () => {
    const { disabled } = this.props;
    const {
      context: { disabled: treeDisabled }
    } = this.props;

    return !!(treeDisabled || disabled);
  };

  // Load data to avoid default expanded tree without data
  syncLoadData = props => {
    const { expanded, loading, loaded } = props;
    const {
      context: { loadData, onNodeLoad }
    } = this.props;

    if (loading) return;

    // read from state to avoid loadData at same time
    if (loadData && expanded && !this.isChild()) {
      // We needn't reload data when has children in sync logic
      // It's only needed in node expanded
      if (!this.hasChildren() && !loaded) {
        onNodeLoad(convertNodePropsToEventData(this.props));
      }
    }
  };

  isSelectable() {
    const { selectable } = this.props;
    const {
      context: { selectable: treeSelectable }
    } = this.props;

    // Ignore when selectable is undefined or null
    if (typeof selectable === "boolean") {
      return selectable;
    }

    return treeSelectable;
  }

  // Switcher
  renderSwitcher = () => {
    const { expanded, switcherIcon: switcherIconFromProps } = this.props;
    const {
      context: { prefixCls, switcherIcon: switcherIconFromCtx }
    } = this.props;

    const switcherIcon = switcherIconFromProps || switcherIconFromCtx;

    if (this.isChild()) {
      return (
        <div
          className={classNames(
            `${prefixCls}-switcher`,
            `${prefixCls}-switcher-noop`
          )}
        >
          {typeof switcherIcon === "function"
            ? switcherIcon({ ...this.props, isLeaf: true })
            : switcherIcon}
        </div>
      );
    }

    const switcherCls = classNames(
      `${prefixCls}-switcher`,
      `${prefixCls}-switcher_${expanded ? ICON_OPEN : ICON_CLOSE}`
    );
    if (switcherIcon) {
      return (
        <div onClick={this.onExpand} className={switcherCls}>
          {typeof switcherIcon === "function"
            ? switcherIcon({ ...this.props, isLeaf: false })
            : switcherIcon}
        </div>
      );
    } else {
      return (
        //icon
        <div onClick={this.onExpand} className={switcherCls}>
          <span>👉</span>
          {typeof switcherIcon === "function"
            ? switcherIcon({ ...this.props, isLeaf: false })
            : switcherIcon}
        </div>
      );
    }
  };

  renderIcon = () => {
    const { loading } = this.props;
    const {
      context: { prefixCls }
    } = this.props;

    if (this.getNodeState()) {
      return <span className={classNames(`${prefixCls}-icon`)}>📕</span>;
    }
    if (loading) {
      return <span className={classNames(`${prefixCls}-icon_loading`)}></span>;
    }
    return <span className={classNames(`${prefixCls}-icon_docu`)}>📗</span>;
  };

  // Icon + Title
  renderSelector = () => {
    const { dragNodeHighlight } = this.state;
    const { title, selected, icon, loading, data } = this.props;
    const {
      context: { prefixCls, showIcon, icon: treeIcon, draggable, loadData }
    } = this.props;
    const disabled = this.isDisabled();

    const wrapClass = `${prefixCls}-node-content-wrapper`;

    // Icon - Still show loading icon when loading without showIcon
    let $icon;

    if (showIcon) {
      const currentIcon = icon || treeIcon;

      $icon = currentIcon ? (
        <span
          className={classNames(
            `${prefixCls}-iconEle`,
            `${prefixCls}-icon__customize`
          )}
        >
          {typeof currentIcon === "function"
            ? currentIcon(this.props)
            : currentIcon}
        </span>
      ) : (
        this.renderIcon()
      );
    } else if (loadData && loading) {
      $icon = this.renderIcon();
    }

    // Title
    const $title = (
      <span className={`${prefixCls}-title`}>
        {typeof title === "function" ? title(data) : title}
      </span>
    );

    return (
      <span
        ref={this.setSelectHandle}
        title={typeof title === "string" ? title : ""}
        className={classNames(
          `${wrapClass}`,
          `${wrapClass}-${this.getNodeState() || "normal"}`,
          !disabled &&
            (selected || dragNodeHighlight) &&
            `${prefixCls}-node-selected`,
          !disabled && draggable && "draggable"
        )}
        aria-grabbed={(!disabled && draggable) || undefined}
        onMouseEnter={this.onMouseEnter}
        onMouseLeave={this.onMouseLeave}
        onContextMenu={this.onContextMenu}
        onClick={this.onSelectorClick}
        onDoubleClick={this.onSelectorDoubleClick}
      >
        {$icon}
        {$title}
      </span>
    );
  };

  render() {
    const {
      eventKey,
      className,
      style,
      dragOver,
      dragOverGapTop,
      dragOverGapBottom,
      isLeaf,
      isStart,
      isEnd,
      expanded,
      selected,
      loading,
      domRef,
      active,
      onMouseMove,
      ...otherProps
    } = this.props;
    const {
      context: { prefixCls, filterTreeNode, draggable, keyEntities }
    } = this.props;
    const disabled = this.isDisabled();
    const dataOrAriaAttributeProps = getDataAndAria(otherProps);
    const { level } = keyEntities[eventKey] || {};

    return (
      <div
        ref={domRef}
        className={classNames(className, `${prefixCls}-treenode`, {
          [`${prefixCls}-treenode-disabled`]: disabled,
          [`${prefixCls}-treenode-switcher-${
            expanded ? "open" : "close"
          }`]: !isLeaf,
          [`${prefixCls}-treenode-selected`]: selected,
          [`${prefixCls}-treenode-loading`]: loading,
          [`${prefixCls}-treenode-active`]: active,

          "drag-over": !disabled && dragOver,
          "drag-over-gap-top": !disabled && dragOverGapTop,
          "drag-over-gap-bottom": !disabled && dragOverGapBottom,
          "filter-node":
            filterTreeNode &&
            filterTreeNode(convertNodePropsToEventData(this.props))
        })}
        style={style}
        draggable={(!disabled && draggable) || undefined}
        onDragStart={draggable ? this.onDragStart : undefined}
        onDragEnter={draggable ? this.onDragEnter : undefined}
        onDragOver={draggable ? this.onDragOver : undefined}
        onDragLeave={draggable ? this.onDragLeave : undefined}
        onDrop={draggable ? this.onDrop : undefined}
        onDragEnd={draggable ? this.onDragEnd : undefined}
        onContextMenu={this.onContextMenu}
        onDoubleClick={this.onSelectorDoubleClick}
        onMouseMove={onMouseMove}
        {...dataOrAriaAttributeProps}
      >
        <Indent
          prefixCls={prefixCls}
          level={level}
          isStart={isStart}
          isEnd={isEnd}
        />
        {this.renderSwitcher()}
        {this.renderSelector()}
      </div>
    );
  }
}

const ContextTreeNode: React.FC<TreeNodeProps> | any = props => {
  const context = React.useContext(TreeContext);
  return <TreeNode {...props} context={context} />;
};

ContextTreeNode.displayName = "TreeNode";

ContextTreeNode.defaultProps = {
  title: defaultTitle
};

(ContextTreeNode as any).isTreeNode = 1;

export { TreeNodeProps, TreeNode, TreeNodeState };

export default ContextTreeNode;
