import React from "react";
import PropTypes from "prop-types";
import classNames from "classnames";
import { Checkbox, Icon } from "@alifd/next";
import CascaderMenu from "./CascaderMenu";
import {
  AddressTree,
  AddressNodeSet,
  AddressNode,
  NodeIDStrict,
} from "./Types";
import { nodeChainFromNode, getParentSetOf, isDescendantOfOne } from "./Common";

enum CheckboxState {
  Checked,
  Indeterminate,
  Unchecked,
}

interface AddressEntryProps {
  node: AddressNode;
  value: CheckboxState;
  onChangeCheckbox: (checked: boolean, node: AddressNode) => void;
  onMenuClick: (checkedNow: boolean, node: AddressNode) => void;
  active: boolean;
  level: number;
}

class AddressEntry extends React.Component<AddressEntryProps, {}> {
  static propTypes = {
    node: PropTypes.object,
    value: PropTypes.number,
    onChangeCheckbox: PropTypes.func,
    onMenuClick: PropTypes.func,
    active: PropTypes.bool,
    level: PropTypes.number,
  };

  constructor(props: AddressEntryProps) {
    super(props);

    this.handleChangeCheckbox = this.handleChangeCheckbox.bind(this);
    this.handleMenuClick = this.handleMenuClick.bind(this);
    this.handleChangeHotarea = this.handleChangeHotarea.bind(this);
  }

  isChecked(props = this.props) {
    return props.value == CheckboxState.Checked;
  }

  handleChangeCheckbox(checked: boolean) {
    this.props.onChangeCheckbox &&
      this.props.onChangeCheckbox(checked, this.props.node);
  }

  handleChangeHotarea() {
    return this.handleChangeCheckbox(this.isChecked());
  }

  handleMenuClick() {
    this.props.onMenuClick &&
      this.props.onMenuClick(this.isChecked(), this.props.node);
  }

  showArrow(props = this.props) {
    if (props.node.isLeaf) {
      return false;
    }
    if (
      props.node.children &&
      props.node.children.length &&
      props.node.children[0].level > props.level
    ) {
      return false;
    }
    if (
      props.level < 4 &&
      (!props.node.children || !props.node.children.length)
    ) {
      return false;
    }
    return true;
  }

  render() {
    const {
      node,
      value,
      onChangeCheckbox,
      onMenuClick,
      active,
      level,
      ...rest
    } = this.props;
    return (
      <CascaderMenu.Item
        key={node.id.toString()}
        className={classNames("cn-addr-casitem", {
          "cn-addr-casactive": this.props.active,
        })}
        onClick={this.handleMenuClick}
        {...rest}
      >
        {/* <div
                    className="cn-addr-casitem-hot"
                    onClick={this.handleChangeHotarea}
                /> */}
        <Checkbox
          checked={this.isChecked()}
          indeterminate={value == CheckboxState.Indeterminate}
          onChange={this.handleChangeCheckbox}
        />
        <span className="cn-addr-castext">{node.nameZh}</span>
        {!this.showArrow() ? null : (
          <Icon className="cn-addr-casexpand" type="arrow-right" size="xs" />
        )}
      </CascaderMenu.Item>
    );
  }
}

interface AddressCascaderProps {
  addressTree: AddressTree;
  allNodesSet: AddressNodeSet;
  expandedValue: NodeIDStrict;
  onExpand: (expandedValue: NodeIDStrict) => void;
  value: NodeIDStrict[];
  onChange: (value: NodeIDStrict[]) => void;
  rootNodeId: NodeIDStrict;
  level: number;
  hiddenMap: { [id: string]: any };
}

interface AddressCascaderState {}

export default class AddressCascader extends React.Component<
  AddressCascaderProps,
  AddressCascaderState
> {
  static propTypes = {
    addressTree: PropTypes.object,
    allNodesSet: PropTypes.object,
    expandedValue: PropTypes.string,
    onExpand: PropTypes.func,
    onLoadTown: PropTypes.func,
    value: PropTypes.arrayOf(PropTypes.string),
    onChange: PropTypes.func,
    rootNodeId: PropTypes.string,
    level: PropTypes.number,
    hiddenMap: PropTypes.object,
  };

  static defaultProps = {
    // @ts-ignore
    value: [],
  };

  valueSet: Set<NodeIDStrict> = new Set();
  allParentSet: Set<NodeIDStrict> = new Set();
  activeChainSet: Set<NodeIDStrict> = new Set();

  constructor(props: AddressCascaderProps) {
    super(props);

    this.renderMenu = this.renderMenu.bind(this);
    this.handleMenuClick = this.handleMenuClick.bind(this);
    this.handleChangeCheckbox = this.handleChangeCheckbox.bind(this);
  }

  handleMenuClick(checkedNode: boolean, node: AddressNode) {
    this.props.onExpand(node.id);
  }

  handleChangeCheckbox(checked: boolean, node: AddressNode) {
    let changed = false;
    if (!checked && this.valueSet.has(node.id)) {
      this.valueSet.delete(node.id);
      changed = true;
    } else if (checked && !this.valueSet.has(node.id)) {
      this.valueSet.add(node.id);
      changed = true;
    } else if (!checked && !this.valueSet.has(node.id)) {
      let nodeToDestruct: AddressNode =
        this.props.allNodesSet[node.id.toString()];
      while (true) {
        if (this.valueSet.has(nodeToDestruct.id)) {
          break;
        }
        if (nodeToDestruct.parentNode) {
          nodeToDestruct = nodeToDestruct.parentNode;
        } else {
          return;
        }
      }

      const parentSet = getParentSetOf(node);
      this.valueSet.delete(nodeToDestruct.id);
      if (nodeToDestruct.children && node.parentNode) {
        const filterAndAdd = (child: AddressNode) => {
          if (!parentSet.has(child.id)) {
            this.valueSet.add(child.id);
          }
        };

        let nodeChain = nodeChainFromNode(node);
        nodeChain = nodeChain.slice(nodeChain.indexOf(nodeToDestruct));
        nodeChain.pop();
        for (const currentNode of nodeChain) {
          currentNode.children!.forEach(filterAndAdd);
        }

        changed = true;
      }
    }
    changed &&
      this.props.onChange &&
      this.props.onChange(Array.from(this.valueSet));
  }

  renderMenu(
    node: AddressNode,
    zIndex: number,
    valueSet: Set<NodeIDStrict>,
    allParentSet: Set<NodeIDStrict>,
    activeChainSet: Set<NodeIDStrict>
  ) {
    if (node.isLeaf) {
      return null;
    }
    return (
      <CascaderMenu
        className="cn-addr-caslist"
        key={node.id.toString()}
        style={{ zIndex }}
      >
        {node.children!.map((child) => {
          if (this.props.hiddenMap[child.id.toString()]) {
            return null;
          }
          let c = CheckboxState.Unchecked;
          if (
            valueSet.has(child.id) ||
            isDescendantOfOne(
              this.props.allNodesSet,
              child.id,
              this.props.value
            )
          ) {
            c = CheckboxState.Checked;
          } else if (allParentSet.has(child.id)) {
            c = CheckboxState.Indeterminate;
          }
          return (
            <AddressEntry
              key={child.id.toString()}
              node={child}
              onMenuClick={this.handleMenuClick}
              value={c}
              onChangeCheckbox={this.handleChangeCheckbox}
              active={activeChainSet.has(child.id)}
              level={this.props.level}
            />
          );
        })}
      </CascaderMenu>
    );
  }

  render() {
    let nodeChain = nodeChainFromNode(
      this.props.allNodesSet[this.props.expandedValue.toString()]
    );
    const rootPos = nodeChain.findIndex((n) => n.id == this.props.rootNodeId);
    if (rootPos != -1) {
      nodeChain = nodeChain.slice(rootPos);
    }

    this.valueSet = new Set();
    this.props.value.forEach((v) => this.valueSet.add(v));

    this.allParentSet = new Set();
    this.props.value.forEach((v) => {
      const parentNodeChain = nodeChainFromNode(
        this.props.allNodesSet[v.toString()]
      );
      parentNodeChain.forEach((node) => this.allParentSet.add(node.id));
    });

    this.activeChainSet = new Set();
    nodeChain.forEach((v) => this.activeChainSet.add(v.id));

    const menus = nodeChain
      .filter(
        (node) =>
          node.level < this.props.level &&
          !(
            this.props.level < 4 &&
            (node.isLeaf || !node.children || !node.children.length)
          )
      )
      .map((node, idx) =>
        this.renderMenu(
          node,
          nodeChain.length - idx + 12,
          this.valueSet,
          this.allParentSet,
          this.activeChainSet
        )
      );

    return <span className="cn-addr-casmenu">{menus}</span>;
  }
}
