import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';

import { Select } from '@alifd/next';

import { AddressStore, defaultAddressStoreCNUser } from './AddressStore';
import { SelectionChain, NodeChain, NodeIDStrict, AddressNode } from './Types';

import {
  nodeChainFromNode,
  selectionToId,
  getLabelIntl,
  lastOr,
  getNodeLevelInTree,
} from './Common';

import AddressIntlCascader from './AddressIntlCascader';

function getDataSource(node: AddressNode | null) {
  if (!node || node.isLeaf || !node.children || !node.children.length) {
    return [];
  }
  return node.children.map(i => ({ label: getLabelIntl(i), value: i.value }));
}

function normalizeToId(
  value: SelectionChain | NodeIDStrict | undefined
): NodeIDStrict | undefined {
  if (Array.isArray(value)) {
    return lastOr(value, undefined);
  }
  return value;
}

interface AddressIntlProps {
  className: string;
  style: object;
  value?: SelectionChain | NodeIDStrict;
  defaultValue?: SelectionChain | NodeIDStrict;
  onChange?: (value: SelectionChain, nodeChain: NodeChain) => any;
  size?: 'small' | 'medium' | 'large';
  placeholder: string;
  popupContainer: React.ReactNode | Function;
  kind: 'cascader' | 'select';
  level?: number;

  addressStore: AddressStore;
}

interface AddressIntlState {
  value: SelectionChain;
}

export default class AddressIntl extends React.Component<
  AddressIntlProps,
  AddressIntlState
> {
  static propTypes = {
    className: PropTypes.string,
    style: PropTypes.object,
    value: PropTypes.oneOfType([
      PropTypes.string,
      PropTypes.arrayOf(PropTypes.string),
    ]),
    defaultValue: PropTypes.oneOfType([
      PropTypes.string,
      PropTypes.arrayOf(PropTypes.string),
    ]),
    onChange: PropTypes.func,
    size: PropTypes.oneOf(['small', 'medium', 'large']),
    placeholder: PropTypes.string,
    popupContainer: PropTypes.oneOfType([PropTypes.func, PropTypes.node]),
    kind: PropTypes.oneOf(['cascader', 'select']),
    level: PropTypes.number,

    addressStore: PropTypes.object,
  };

  static defaultProps = {
    addressStore: defaultAddressStoreCNUser,
  };

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

    this.state = {
      value: [],
    };

    this.handleUserSelect = this.handleUserSelect.bind(this);
    this.getOverlayContainer = this.getOverlayContainer.bind(this);
  }

  componentDidMount() {
    let value = this.props.value || this.props.defaultValue;
    const valueId = normalizeToId(value) || '0';
    this.handleSelect(valueId);
  }

  async componentDidUpdate(
    prevProps: AddressIntlProps,
    prevState: AddressIntlState
  ) {
    const lastValue = normalizeToId(prevProps.value);
    const thisValue = normalizeToId(this.props.value);
    if (lastValue != thisValue) {
      if (typeof thisValue == 'undefined') {
        this.handleSelect('0');
      } else if (typeof thisValue == 'string') {
        this.handleSelect(thisValue);
      } else if (Array.isArray(thisValue)) {
        this.setState({ value: thisValue });
      }
    }

    const valueNow = selectionToId(this.state.value);
    if (valueNow != selectionToId(prevState.value)) {
      this.handleSelect(valueNow);
    }
  }

  setStateAsync(updater: any): Promise<void> {
    return new Promise((resolve, reject) => this.setState(updater, resolve));
  }

  getOverlayContainer() {
    const { popupContainer } = this.props;
    if (this.props.popupContainer) {
      if (typeof popupContainer == 'function') {
        return popupContainer();
      }
      return popupContainer;
    }
    return this.refs.gatewayWrapper;
  }

  async handleSelect(value: NodeIDStrict): Promise<NodeChain | null> {
    const nodePre = this.props.addressStore.impl.getNodeByIdSync(value);
    if (!nodePre || (!nodePre.isLeaf && !nodePre.children)) {
      await this.props.addressStore.impl.getNodeById(value, true);
    }
    const node = this.props.addressStore.impl.getNodeByIdSync(value)!;
    const nodeChain = nodeChainFromNode(node);
    const idChain = nodeChain.map(i => i.id).slice(1);
    await this.setStateAsync({ value: idChain });
    return nodeChain;
  }

  async handleUserSelect(value: NodeIDStrict) {
    const nodeChain = nodeChainFromNode(
      this.props.addressStore.impl.getNodeByIdSync(value)!
    );
    const idChain = nodeChain.map(i => i.id).slice(1);
    typeof this.props.onChange == 'function' &&
      this.props.onChange(idChain, nodeChain);
    await this.handleSelect(value);
  }

  renderSelectBox(value: NodeIDStrict | undefined, parentID: NodeIDStrict) {
    const SelectComp = Select as any;
    const parentNode = this.props.addressStore.impl.getNodeByIdSync(parentID);
    if (!parentNode) {
      throw new Error(`no such node ${parentID}`);
    }
    if (
      parentNode.isLeaf ||
      !parentNode.children ||
      !parentNode.children.length
    ) {
      return null;
    }
    // TODO: complete logic for level in case of SelectBox mode
    if (
      this.props.level &&
      getNodeLevelInTree(parentNode) >= this.props.level
    ) {
      return null;
    }
    return (
      <SelectComp
        key={parentID}
        className="next-cn-address-iu-list"
        value={value}
        onChange={this.handleUserSelect}
        size={this.props.size}
        dataSource={getDataSource(parentNode)}
        showSearch={true}
        autoWidth={false}
        placeholder={this.props.placeholder}
        popupContainer={this.getOverlayContainer}
      />
    );
  }

  renderSelectBoxes() {
    const box = (value: NodeIDStrict, index: number) =>
      this.renderSelectBox(value, index ? this.state.value[index - 1] : '0');

    return [
      this.state.value.map(box),
      this.renderSelectBox(undefined, selectionToId(this.state.value)),
    ];
  }

  renderCascader() {
    return (
      <div className="next-cn-address-multi-overlay">
        <AddressIntlCascader
          value={this.state.value}
          onChange={this.handleUserSelect}
          rootNodeId="0"
          addressStore={this.props.addressStore}
          maxLevel={this.props.level}
        />
      </div>
    );
  }

  renderCascaderBox() {
    const SelectComp = Select as any;
    const displayValue =
      this.state.value
        .map(this.props.addressStore.impl.getNodeByIdSync)
        .map(i => getLabelIntl(i!))
        .join(' / ') || undefined;
    return (
      <SelectComp
        size={this.props.size}
        placeholder={this.props.placeholder}
        overlay={this.renderCascader()}
        popupContainer={this.getOverlayContainer}
        autoWidth={false}
        value={displayValue}
      />
    );
  }

  render() {
    return (
      <span
        className={classNames(
          'next-cn-address-iu',
          this.props.size || 'medium',
          this.props.className
        )}
        style={this.props.style}
      >
        {/* {this.renderSelectBoxes()} */}
        {/* {this.renderCascaderBox()} */}
        {this.props.kind == 'cascader'
          ? this.renderCascaderBox()
          : this.renderSelectBoxes()}
        <span ref="gatewayWrapper" />
      </span>
    );
  }
}
