import classNames from 'classnames';
import React, { useContext, useEffect, useMemo, useState } from 'react';
import { ConfigProvider, Tree as OldTree, TreeProps } from 'antd';
import { TreeNodeProps } from 'rc-tree-select/lib/TreeNode';
import { DataNode, EventDataNode } from 'antd/lib/tree';
import './index.less';
import { Icon } from '../Icon';
import { Input } from '../Input';
import { Tooltip } from '../Tooltip';
import { AOP } from '../utils/AOP';

export declare type NodeDragEventParams<T = HTMLDivElement> = {
  event: React.DragEvent<T>;
  node: EventDataNode<DataNode>;
};
export type EditableType = {
  show: boolean;
  beforeEnterEditMode?: (node: DataNode) => Promise<boolean>;
  afterEnterEditMode?: (node: DataNode) => void;
  beforeExitEditMode?: (node: DataNode, value: string) => Promise<boolean>;
  afterExitEditMode?: (node: DataNode, value: string) => void;
};
export type DeleteableType = {
  show: boolean;
  beforeDelete?: (node: DataNode) => Promise<boolean>;
  afterDelete?: (node: DataNode) => void;
};
export type AddableType = {
  show: boolean | ((node: DataNode) => boolean);
  beforeAdd?: (parentNode: DataNode) => Promise<DataNode>;
  afterAdd?: (node: DataNode) => void;
};

const RenderTitle = (props: {
  titleRender?: (node: DataNode) => React.ReactNode;
  nodeData: DataNode;
  onTitleChange?: (data: { key: string | number; value: string }) => void;
  onNodeDelete?: (data: { key: string | number; value: string }) => void;
  onAdd?: (data: { parentKey: string | number; data: DataNode }) => void;
  editable?: boolean | EditableType;
  deleteable?: boolean | DeleteableType;
  showIcon?: boolean;
  checkedKeys?: React.Key[];
  icon?: any;
  addable?: boolean | AddableType;
}) => {
  const {
    nodeData,
    onTitleChange,
    titleRender,
    onNodeDelete,
    onAdd,
    showIcon,
    checkedKeys,
  } = props;
  const { getPrefixCls } = useContext(ConfigProvider.ConfigContext);
  const prefixCls = getPrefixCls('btri-tree');
  const [_isEdit, _setIsEdit] = useState(false);
  const [_inputValue, _setInputValue] = useState('');
  useEffect(() => {
    if (typeof nodeData.title === 'string') {
      _setInputValue(nodeData.title);
    }
  }, [nodeData.title]);

  const isEdit = useMemo(() => {
    if (typeof props.editable === 'boolean') {
      return props.editable;
    }
    return props.editable?.show;
  }, [props.editable]);

  const isDelete = useMemo(() => {
    if (typeof props.deleteable === 'boolean') {
      return props.deleteable;
    }
    return props.deleteable?.show;
  }, [props.deleteable]);

  const isAdd = useMemo(() => {
    if (typeof props.addable === 'boolean') {
      return props.addable;
    }
    if (typeof props.addable?.show === 'function') {
      return props.addable.show(nodeData);
    }
    return props.addable?.show;
  }, [props.addable]);

  const defaultIcon = useMemo(() => {
    if (!nodeData.children) {
      return 'File';
    }
    if (checkedKeys?.indexOf(nodeData.key) !== -1) {
      return 'FolderOpen';
    }
    return 'FolderClose';
  }, [nodeData, checkedKeys]);

  return (
    <div className={`${prefixCls}-title-box`}>
      {_isEdit && typeof nodeData.title === 'string' ? (
        <Input
          defaultValue={nodeData.title as string}
          onChange={(v) => {
            _setInputValue(v.target.value);
          }}
        ></Input>
      ) : (
        <div className={`${prefixCls}-title-content`}>
          <>
            {showIcon &&
              (nodeData.icon || props.icon || (
                <Icon
                  className={`${prefixCls}-title-content-icon`}
                  iconName={defaultIcon}
                ></Icon>
              ))}
            {titleRender?.(nodeData) || nodeData.title}
          </>
        </div>
      )}
      {(isEdit || isDelete || isAdd) && (
        <div className={`${prefixCls}-title-icon-box`}>
          {isAdd && (
            <Icon
              iconName="FocusAdd"
              onClick={async () => {
                let newNodeData = null;
                // 添加子级需要请求完接口才知道key值，beforeAdd请求完接口后，返回数据，然后修改treeData
                if (
                  typeof props.addable !== 'boolean' &&
                  props.addable?.beforeAdd
                ) {
                  newNodeData = await props.addable?.beforeAdd?.(nodeData);
                }
                if (newNodeData) {
                  onAdd?.({
                    parentKey: nodeData.key,
                    data: newNodeData,
                  });
                  if (typeof props.addable !== 'boolean') {
                    props.addable?.afterAdd?.(nodeData);
                  }
                }
              }}
            ></Icon>
          )}
          {isEdit && (
            <Icon
              iconName={_isEdit ? 'CheckOn' : 'Edit'}
              onClick={async () => {
                let editHandle = true;
                if (typeof props.editable !== 'boolean') {
                  if (_isEdit) {
                    if (props.editable?.beforeExitEditMode) {
                      editHandle = await props.editable?.beforeExitEditMode?.(
                        nodeData,
                        _inputValue,
                      );
                    }
                  } else {
                    if (props.editable?.beforeEnterEditMode) {
                      editHandle = await props.editable?.beforeEnterEditMode?.(
                        nodeData,
                      );
                    }
                  }
                }
                if (editHandle) {
                  onTitleChange?.({
                    key: nodeData.key,
                    value: _inputValue as string,
                  });
                  _setIsEdit((v) => !v);
                  if (typeof props.editable !== 'boolean') {
                    if (_isEdit) {
                      props.editable?.afterExitEditMode?.(
                        nodeData,
                        _inputValue,
                      );
                    } else {
                      props.editable?.afterEnterEditMode?.(nodeData);
                    }
                  }
                }
              }}
            ></Icon>
          )}
          {isDelete && (
            <Icon
              iconName="Delete"
              onClick={async () => {
                let deleteHandle = true;
                if (
                  typeof props.deleteable !== 'boolean' &&
                  props.deleteable?.beforeDelete
                ) {
                  deleteHandle = await props.deleteable?.beforeDelete?.(
                    nodeData,
                  );
                }
                if (deleteHandle) {
                  onNodeDelete?.({
                    key: nodeData.key,
                    value: _inputValue as string,
                  });
                  if (typeof props.deleteable !== 'boolean') {
                    props.deleteable?.afterDelete?.(nodeData);
                  }
                }
              }}
            ></Icon>
          )}
        </div>
      )}
    </div>
  );
};

type ExtraTreeProps = {
  onTreeDataChange?: (data: DataNode[]) => void;
  editable?: boolean | EditableType;
  deleteable?: boolean | DeleteableType;
  addable?: boolean | AddableType;
};

const Tree = (props: TreeProps & ExtraTreeProps) => {
  const { getPrefixCls } = useContext(ConfigProvider.ConfigContext);
  const prefixCls = getPrefixCls('btri-tree');
  const { treeData } = props;

  const renderSwitchIcon = (switchProps: { expanded: boolean }) => {
    return (
      <Icon
        style={{ fontSize: '16px', color: 'var(--caption-font)' }}
        iconName={switchProps.expanded ? 'Down' : 'Right'}
      ></Icon>
    );
  };

  const [_treeData, _setTreeData] = useState(treeData);
  useEffect(() => {
    _setTreeData(treeData);
  }, [treeData]);

  // 这里用中序遍历吧，改变节点
  const _changeTreeData = (
    key: string | number,
    value: string,
    oriTreeData: DataNode[],
  ) => {
    if (!oriTreeData) return;
    const resTreeData = oriTreeData;
    for (let i = 0; i < resTreeData.length; i++) {
      if (resTreeData[i].key === key) {
        resTreeData[i].title = value;
        return resTreeData;
      }
      const resChildren = _changeTreeData(key, value, oriTreeData[i].children);
      resTreeData[i].children = resChildren;
    }
    return resTreeData;
  };
  // 删除节点，基本和改变节点一样
  const _deleteTreeData = (key: string | number, oriTreeData: DataNode[]) => {
    if (!oriTreeData) return;
    const resTreeData = oriTreeData;
    for (let i = 0; i < resTreeData.length; i++) {
      if (resTreeData[i]?.key === key) {
        const res = resTreeData;
        res.splice(i, 1);
        return res;
      }
      const resChildren = _deleteTreeData(key, oriTreeData[i]?.children);
      resTreeData[i].children = resChildren;
    }
    return resTreeData;
  };

  // 添加树的子级节点
  const _addTreeData = (
    parentKey: string | number,
    data: DataNode,
    oriTreeData: DataNode[],
  ) => {
    if (!oriTreeData) return;
    const resTreeData = oriTreeData;
    for (let i = 0; i < resTreeData.length; i++) {
      if (resTreeData[i].key === parentKey) {
        if (!resTreeData[i].children) {
          resTreeData[i].children = [];
        }
        resTreeData[i].children.push(data);
        return resTreeData;
      }
      const resChildren = _addTreeData(
        parentKey,
        data,
        oriTreeData[i].children,
      );
      resTreeData[i].children = resChildren;
    }
    return resTreeData;
  };

  useEffect(() => {
    props.onTreeDataChange?.(_treeData);
  }, [_treeData]);

  const [_checkedKeys, _setCheckedKeys] = useState<React.Key[]>([]);

  const _onExpand = new AOP<
    | React.Key[]
    | {
        node: EventDataNode<DataNode>;
        expanded: boolean;
        nativeEvent: MouseEvent;
      },
    void
  >(props.onExpand)
    .after((keys) => {
      _setCheckedKeys(keys as React.Key[]);
    })
    .getFunction();

  const [_showKey, _setShowKey] = useState<React.Key | null>(null);

  return (
    <OldTree
      switcherIcon={(switchProps) => renderSwitchIcon(switchProps)}
      blockNode
      {...props}
      showIcon={false}
      virtual={false}
      treeData={_treeData}
      className={classNames(`${prefixCls}`, props.className)}
      style={{ width: '100%', ...props.style }}
      onExpand={_onExpand}
      titleRender={(nodeData) => {
        const isLeaf =
          typeof nodeData.isLeaf === 'boolean'
            ? nodeData.isLeaf
            : !nodeData.children || nodeData.children.length === 0;
        return (
          <Tooltip
            open={_showKey === nodeData.key}
            title={nodeData.title as any}
          >
            <span
              onMouseEnter={(e) => {
                if (
                  (e.target as HTMLElement).clientWidth <
                  (e.target as HTMLElement).scrollWidth
                ) {
                  _setShowKey(nodeData.key);
                } else {
                  _setShowKey(null);
                }
              }}
              onMouseLeave={(e) => {
                _setShowKey(null);
              }}
              className={`${prefixCls}-title-wrapper ${
                isLeaf && !props.checkable ? 'is-leaf' : ''
              }`}
            >
              <RenderTitle
                icon={props.icon}
                nodeData={nodeData}
                onTitleChange={(v) => {
                  _setTreeData((data) =>
                    _changeTreeData(
                      v.key,
                      v.value,
                      JSON.parse(JSON.stringify(data)),
                    ),
                  );
                }}
                onNodeDelete={(v) => {
                  _setTreeData((data) =>
                    _deleteTreeData(v.key, JSON.parse(JSON.stringify(data))),
                  );
                }}
                onAdd={(v) => {
                  _setTreeData((data) =>
                    _addTreeData(
                      v.parentKey,
                      v.data,
                      JSON.parse(JSON.stringify(data)),
                    ),
                  );
                }}
                titleRender={props.titleRender}
                editable={props.editable}
                deleteable={props.deleteable}
                addable={props.addable}
                showIcon={props.showIcon}
                checkedKeys={_checkedKeys}
              ></RenderTitle>
            </span>
          </Tooltip>
        );
      }}
    ></OldTree>
  );
};

Tree.TreeNode = (props: TreeNodeProps) => {
  const { TreeNode } = OldTree;
  const { getPrefixCls } = useContext(ConfigProvider.ConfigContext);
  const prefixCls = getPrefixCls('btri-tree-node');
  return (
    <TreeNode
      {...props}
      className={classNames(`${prefixCls}`, props.className)}
    ></TreeNode>
  );
};

export { Tree };
