// @ts-nocheck
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';

import { Grid, Button, Select, Tab, Menu } from '@alifd/next';

import AddressLabel from './AddressLabel';

import MenuWrap from './MenuWrap';

import {
  PANE_LEVELS,
  PANE_ATTRIBUTES,
  PANE_LEVEL_KEYS,
  loadAddressData,
  foldNodeChainToSelection,
  nodeChainFromNode,
  selectionToChain,
  loadTownData,
  traceTownParents,
  defaultTextSerializer,
  addressTreeOfSet,
  delay,
  id,
} from './util';

import { PROVINCE_LETTERS, provinceLetterParititon } from './Common';

import { doSearch } from './search-util';

import { last, zipObject, dropRight } from './lodash-alt';

const { Row, Col } = Grid;

const KEY_SELECT_VALUE = 'KEY_SELECT_VALUE';

function selectNodeFromChain(node, chain) {
  const ret = [node];
  return !chain.length
    ? ret
    : ret.concat(
        selectNodeFromChain(
          node.children.find((n) => n.id === chain[0]),
          chain.slice(1, chain.length),
        ),
      );
}

function anyValueToSelectionId(val) {
  switch (typeof val) {
    case 'string':
    case 'number':
      return val.toString();
    case 'object':
      return last(selectionToChain(val));
    default:
      throw new Error(`invalid address anyValue ${val}`);
  }
}

function addressEqual(lhs, rhs) {
  return anyValueToSelectionId(lhs) === anyValueToSelectionId(rhs);
}

async function loadAndMergeTownList(params, node, allNodesSet) {
  const townList = await loadTownData(params, node);
  /* eslint-disable */
  node.children = townList;
  /* eslint-enable */
  Object.assign(
    allNodesSet,
    zipObject(
      townList.map((t) => t.id),
      townList,
    ),
  );
  return townList;
}

function selectedNodeChain(addressTree, value) {
  const chain = selectionToChain(value);
  return selectNodeFromChain(addressTree, chain);
}

function getShownTabs(normalizedValue, allNodesSet, maxLevel) {
  const nodeChain = selectedNodeChain(addressTreeOfSet(allNodesSet), normalizedValue);
  return nodeChain
    .filter((node) => node.children && node.children.length)
    .slice(1)
    .filter((node) => PANE_ATTRIBUTES[node.children[0].levelKey].index <= maxLevel);
}

const selectionLevelCanCommit = async (node, minLevel) => {
  if (minLevel < 4) {
    const isCurLevelEnough = node.level >= minLevel;
    const curLevelTooSmallButNoAvailableChild =
      node.isLeaf || node.children.length === 0 || node.children[0].level !== node.level + 1;
    return isCurLevelEnough || curLevelTooSmallButNoAvailableChild;
  }
  const isCurLevelEnough = node.level >= minLevel;
  const curLevelNoAvailableChild = node.isLeaf || node.children.length === 0; // 不存在子节点
  return isCurLevelEnough || curLevelNoAvailableChild;
};

function displayValueFromNodeChain(nodeChain) {
  return nodeChain
    .slice(2)
    .map((node) => node.nameZh)
    .join(' / ');
}

export default class Address extends React.Component {
  static propTypes = {
    className: PropTypes.string,
    style: PropTypes.object,
    value: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.string]),
    defaultValue: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.string]),
    onChange: PropTypes.func,
    size: PropTypes.oneOf(['small', 'medium', 'large']),
    overseas: PropTypes.bool,
    level: PropTypes.number,
    addressSelectPlaceholder: PropTypes.string,
    countrySelectPlaceholder: PropTypes.string,
    disabled: PropTypes.bool,
    readOnly: PropTypes.bool,
    // TODO: can do better - componentSerializer
    textOnly: PropTypes.bool,
    textSerializer: PropTypes.func,
    requestAddressUrl: PropTypes.string,
    requestTownUrl: PropTypes.string,
    requestAddressLevelUrl: PropTypes.string,
    fixedWidth: PropTypes.bool,
    animation: PropTypes.bool,
    hasClear: PropTypes.bool,
    popupContainer: PropTypes.func,
    ignored: PropTypes.arrayOf(PropTypes.string),
    dataOverride: PropTypes.objectOf(PropTypes.arrayOf(PropTypes.string)),
    preprocessor: PropTypes.func,
    hiddenData: PropTypes.arrayOf(PropTypes.string),
    hasToSelectToLastLevel: PropTypes.oneOfType([PropTypes.bool, PropTypes.number]),
    partialSelectionOverlayClosable: PropTypes.bool,
    normalizeInitValue: PropTypes.bool,
    provincePartition: PropTypes.oneOf(['letter']),
    showSearch: PropTypes.bool,
    onLabelUpdate: PropTypes.func,
    popupProps: PropTypes.object,
    popupClassName: PropTypes.string,
    countryPopupProps: PropTypes.object,
    countryPopupClassName: PropTypes.string,
    _IS_CN_ADDRESS_: PropTypes.bool,
    container: PropTypes.any,
  };

  static defaultProps = {
    defaultValue: {
      country: '1',
      province: undefined,
      city: undefined,
      area: undefined,
      town: undefined,
    },
    level: 4,
    addressSelectPlaceholder: '请选择地址',
    countrySelectPlaceholder: '请选择国家',
    disabled: false,
    readOnly: false,
    textOnly: false,
    textSerializer: defaultTextSerializer,
    requestAddressUrl: '//division-data.alicdn.com/simple/addr_4_1111_1_0.js',
    requestTownUrl: 'https://lsp.wuliu.taobao.com/locationservice/addr/output_address_town.do',
    requestAddressLevelUrl: 'https://lsp.wuliu.taobao.com/locationservice/addr/outputParentDivisons.do',
    fixedWidth: true,
    animation: true,
    hasClear: false,
    ignored: [],
    dataOverride: {},
    preprocessor: id,
    hiddenData: [],
    hasToSelectToLastLevel: false,
    partialSelectionOverlayClosable: true,
    normalizeInitValue: false,
    showSearch: false,
    _IS_CN_ADDRESS_: true,
  };

  constructor(props) {
    super(props);
    this.state = {
      addressTree: {},
      countryList: [],
      paneRoot: null,
      uiOverlayVisible: false,
      selectionCommitable: false,
      uiActiveTab: 'province',
      uiActiveTabPrev: 'province',
      value: {},
      overlayValue: {},
      inSearch: false,
      searchResult: null,
      searchQuery: '',
    };

    this.selectRef = null;

    this.updateHiddenMap(props.hiddenData);

    this.handleSelect = this.handleSelect.bind(this);
    this.handleSelectCountry = this.handleSelectCountry.bind(this);
    this.handleSelectChange = this.handleSelectChange.bind(this);
    this.handleClear = this.handleClear.bind(this);
    this.handleOverlayVisibleChange = this.handleOverlayVisibleChange.bind(this);
    this.handleActiveTabChanged = this.handleActiveTabChanged.bind(this);
    this.getOverlayContainer = this.getOverlayContainer.bind(this);
    this.handleSearch = this.handleSearch.bind(this);
    this.handleSearchFocus = this.handleSearchFocus.bind(this);
    this.handleSelectSearchItem = this.handleSelectSearchItem.bind(this);

    const { value, defaultValue } = this.props;

    // TODO: lift address data store into a larger scope
    (async () => {
      const initialValue = props.value || props.defaultValue;
      const { nodeChain, address } = await this.normalizeValueReturningAddressAndNodeChain(initialValue);
      // TODO: investigate the sequence problem of valuesetting
      if ((value || defaultValue) === initialValue) {
        this.triggerLabelUpdate(nodeChain);
        await this.setStateAsync({
          value: address,
          searchQuery: this.displayValueFromSelection(address),
          uiActiveTab: this.getNewActiveTab(address),
        });
      }
    })();
  }

  updateHiddenMap(hiddenList) {
    const ret = zipObject(hiddenList, new Array(hiddenList.length).fill(true));
    this.hiddenMap = ret;
    return ret;
  }

  getMinSelectLevel(props = this.props) {
    const v = props.hasToSelectToLastLevel;
    switch (typeof v) {
      case 'number':
        return v;
      case 'boolean':
        return v ? props.level : 0;
      default:
        return 0;
    }
  }

  async componentWillReceiveProps(nextProps) {
    // TODO: this is still a concurrency bug
    const { value } = this.state;
    const { hiddenData, defaultValue, requestAddressUrl } = this.props;

    if ('requestAddressUrl' in nextProps && requestAddressUrl !== nextProps.requestAddressUrl) {
      this.setState({
        value: await this.normalizeValue(value),
      });
    }

    if ('hiddenData' in nextProps && hiddenData !== nextProps.hiddenData) {
      this.updateHiddenMap(nextProps.hiddenData);
    }

    const nextValue = (() => {
      if (typeof nextProps.value === 'undefined' || nextProps.value === null) {
        // reseting the field should result in ALL value cleared
        //  instead of fallback to defaultValue
        //  but that would not be appropriate for our Address
        //  (when used without overseas)
        //  use { } to state an EMPTY value explicitly
        // return { }
        return /* nextProps.defaultValue || */ defaultValue;
      }
      return nextProps.value;
    })();
    const { address: normalizedValue, nodeChain } = await this.normalizeValueReturningAddressAndNodeChain(nextValue);

    // TODO: Address + Field + setValue ... need more work on this later
    if ('value' in nextProps /* && this.state.value == valueNow */) {
      this.triggerLabelUpdate(nodeChain);
      if (!addressEqual(nextValue, value)) {
        this.setState({
          value: normalizedValue,
          searchQuery: this.displayValueFromSelection(normalizedValue),
          // 05/12/2017: we need to update activeTab too
          //  or the overlay would be empty
          // in fact, the active tab may be better implemented
          //  as a view constraint, rather than view state
          uiActiveTab: this.getNewActiveTab(normalizedValue),
        });
      }
    }
  }

  /**
   * 设置 defaultValue 包含街道时触发街道列表查询
   * @param {array} chain 节点 id（[counry, province, city, area, town]）
   */
  async getDefaultValToTownList(chain) {
    const { level } = this.props;
    const { addressTree } = this.state;
    if (level >= 4 && chain && chain.length) {
      const selectionToChains = selectNodeFromChain(addressTree, chain) || [];
      const selectionLen = (selectionToChains && selectionToChains.length) || 0;
      const townNode = selectionToChains[selectionLen - 1];
      const node = selectionToChains[selectionLen - 2];
      if (!townNode && node.levelKey !== 'town' && node.levelKey !== 'country' && !node.children.length) {
        await loadAndMergeTownList(this.getRequestParams(), node, this.allNodesSet);
      }
    }
  }

  async normalizeValueReturningNodeChain(val, params = this.getRequestParams()) {
    const traceTownParentsAndUpdateIfDataNotChanged = async (town, { addressTree, allNodesSet }) => {
      const chain = await traceTownParents(params, town);
      await this.getDefaultValToTownList(chain);
      const nodeChain = selectNodeFromChain(addressTree, dropRight(1, chain));
      if (this.allNodesSet === allNodesSet) {
        this.setState({ addressTree });
      }
      return [...nodeChain, allNodesSet[town]];
    };

    if (typeof val === 'number' || typeof val === 'string') {
      const dataset = await this.dataset(params);
      const { allNodesSet } = dataset;
      if (allNodesSet[val]) {
        return nodeChainFromNode(allNodesSet[val]);
      }
      /* eslint-disable */
      return await traceTownParentsAndUpdateIfDataNotChanged(val, dataset);
    }
    if (typeof val === 'object') {
      const dataset = await this.dataset(params);
      const { allNodesSet } = dataset;
      if (val.town && !this.allNodesSet[val.town]) {
        return await traceTownParentsAndUpdateIfDataNotChanged(val.town, dataset);
        /* eslint-disable */
      }
      const selectionChain = selectionToChain(val);
      if (!selectionChain.length) {
        return [];
      }
      return nodeChainFromNode(allNodesSet[last(selectionChain)]);
    }
    throw new Error(`Address normalizeValue(): unknown value of ${typeof val} - ${val}`);
  }

  async normalizeValueReturningAddressAndNodeChain(val, params) {
    const nodeChain = await this.normalizeValueReturningNodeChain(val, params);
    const address = foldNodeChainToSelection(nodeChain);
    return { nodeChain, address };
  }

  async normalizeValue(val, params) {
    const nodeChain = await this.normalizeValueReturningNodeChain(val, params);
    const address = foldNodeChainToSelection(nodeChain);
    return address;
  }

  setStateAsync(updater) {
    return new Promise((resolve) => {
      this.setState(updater, () => resolve());
    });
  }

  async loadAndStoreDataset(params = this.getRequestParams()) {
    const { countryList, allNodesSet, addressTree } = await loadAddressData(params);
    this.allNodesSet = allNodesSet;
    await this.setStateAsync({
      countryList,
      addressTree,
      paneRoot: allNodesSet['1'],
    });
    return {
      addressTree,
      allNodesSet,
    };
  }

  // TODO: this impl. is not exactly right ...
  async dataset(params) {
    const { paneRoot, addressTree } = this.state;
    if (paneRoot) {
      return {
        addressTree,
        allNodesSet: this.allNodesSet,
      };
    }
    // concurrency issue ...
    if (!this.futureDatasetTem) {
      this.futureDatasetTem = this.loadAndStoreDataset(params);
    }
    return await this.futureDatasetTem;
  }

  getNewActiveTab(newValue) {
    const { level } = this.props;
    const tabs = getShownTabs(newValue, this.allNodesSet, level);
    if (tabs.length) {
      return last(tabs).children[0].levelKey;
    }

    return 'province';
  }

  async handleSelect(node) {
    const newNodeChain = nodeChainFromNode(node);
    const newValue = foldNodeChainToSelection(newNodeChain);
    const { level, onChange } = this.props;

    const { addressTree, uiOverlayVisible, uiActiveTab } = this.state;

    // when the user selected an item in the last level, by common sense,
    //      his/her work on the pane is done, specifically:
    //  * if we has selected the value of last level specified in props
    //  * or we are not in the last level, but we got no child
    // -> close the pane overlay
    if (node.levelKey === PANE_LEVEL_KEYS[level] || (level < 4 && !node.children.length)) {
      this.setState({ uiOverlayVisible: false });
      if (this.selectRef && this.selectRef.hide && typeof this.selectRef.hide === 'function') {
        this.selectRef && this.selectRef?.hide(); // 无线影藏selectDrawer弹窗
      }
    }

    const activeTab = this.getNewActiveTab(newValue);

    this.setState({ overlayValue: newValue, selectionCommitable: false });
    const stateMutation = { uiActiveTab: activeTab };

    // do not commit if hasToSelectToLastLevel isn't meet
    //   OR we are clearing (i.e. selecting a country node)
    //      (you're not able to select a country node in tab panes, except clear)
    if (node.level <= 0 || (await selectionLevelCanCommit(node, this.getMinSelectLevel()))) {
      stateMutation.value = newValue;
      stateMutation.selectionCommitable = true;
      this.setState(stateMutation);
      if (onChange) {
        onChange(newValue, newNodeChain, newNodeChain);
      }
    } else {
      stateMutation.selectionCommitable = false;
      this.setState(stateMutation);
    }

    // don't fetch 'town' children, if at least one of the following conditions holds:
    //  * the user does not need 'town' level
    //  * if we are already in the 'town' level
    //  * if the current node already has children
    //      it either does not need to fetch, or already has been fetched beforehand
    //  * if we are in the country level
    //      (or the server would just return a whole bunch of garbage,
    //          THIS SEEMS TO BE A SERVER BUG, and we have to use a hack)

    const { overlayValue } = this.state;

    if (level >= 4 && node.levelKey !== 'town' && node.levelKey !== 'country' && !node.children.length) {
      const townList = await loadAndMergeTownList(this.getRequestParams(), node, this.allNodesSet);
      await this.setStateAsync({ addressTree });
      const nodeChain = this.selectedNodeChain(overlayValue);
      if (uiOverlayVisible && last(nodeChain) === node) {
        this.setState({
          uiActiveTab: townList.length ? 'town' : uiActiveTab,
          uiOverlayVisible: !!townList.length,
        });
      }
    }

    this.exitSearch(newValue);
  }

  async handleSelectCountry(key) {
    let newValue;
    let newNodeChain = [];
    const { onChange } = this.props;
    const { addressTree } = this.state;
    if (key) {
      newValue = await this.normalizeValue({ country: key });
      newNodeChain = selectNodeFromChain(addressTree, selectionToChain(newValue));
    }
    await this.setStateAsync({
      value: newValue || {},
      uiActiveTab: 'province',
    });
    if (onChange) {
      onChange(newValue || {}, newNodeChain, newNodeChain);
    }
  }

  // this is invoked only for builtin clear of <Select />
  handleSelectChange(value) {
    if (value === null || value === undefined) {
      this.handleClear();
    }
  }

  async handleClear() {
    const valueOverride = {};
    const { value, addressTree } = this.state;
    PANE_LEVELS.forEach((level) => {
      valueOverride[level] = undefined;
    });
    const newValue = {
      ...value,
      ...valueOverride,
    };
    this.handleSelect(last(selectNodeFromChain(addressTree, selectionToChain(newValue))));
    this.exitSearch();
    this.setState({ uiOverlayVisible: false });
  }

  handleActiveTabChanged(key) {
    this.setState({ uiActiveTab: key });
  }

  async handleOverlayVisibleChange(val) {
    const arg = { uiOverlayVisible: val };
    const { value, uiActiveTab, uiActiveTabPrev, selectionCommitable, partialSelectionOverlayClosable } = this.state;
    if (val) {
      arg.overlayValue = value;
      arg.selectionCommitable = await selectionLevelCanCommit(
        last(await this.normalizeValueReturningNodeChain(value)),
        this.getMinSelectLevel(),
      );
      arg.uiActiveTabPrev = uiActiveTab;
      this.setState(arg);
    } else {
      this.exitSearch(value);
      if (!partialSelectionOverlayClosable && !selectionCommitable) {
        return;
      }
      this.setState({ uiOverlayVisible: false });
      if (!selectionCommitable) {
        // we are discarding all selection states
        //  including UI state
        arg.uiActiveTab = uiActiveTabPrev;
      }
      this.setState(arg);
    }
  }

  exitSearch(selection) {
    const searchQuery = selection ? this.displayValueFromSelection(selection) : '';
    this.setState({ inSearch: false, searchQuery, searchResult: null });
  }

  handleSearch(v) {
    const val = v.trim();
    const { value } = this.state;
    this.setState({ searchQuery: val });
    if (!val) {
      this.exitSearch();
      return;
    }
    this.setState({ inSearch: true });
    const queryArgs = {
      ofCountry: value.country,
    };
    const result = doSearch(val, queryArgs, this);
    this.setState({ searchResult: result.length ? result : null });
    this.handleOverlayVisibleChange(true);
  }

  handleSearchFocus(e, clickByUser) {
    if (clickByUser) {
      this.setState({ searchQuery: '' });
      this.handleOverlayVisibleChange(true);
    }
  }

  async handleSelectSearchItem(nid) {
    this.handleOverlayVisibleChange(false);
    await delay(5);
    await this.handleSelect(this.allNodesSet[nid]);
  }

  selectedNodeChain(value) {
    const chain = selectionToChain(value);
    const { addressTree } = this.state;
    return selectNodeFromChain(addressTree, chain);
  }

  getDisabled() {
    const { disabled, readOnly } = this.props;
    return disabled || readOnly;
  }

  safeToRenderForValue(value) {
    const { paneRoot } = this.state;
    const { level } = this.props;
    return !!paneRoot && (level < 4 || !value.town || this.allNodesSet[value.town]);
  }

  safeToRender() {
    const { value } = this.state;
    return this.safeToRenderForValue(value);
  }

  getRequestParams(props = this.props) {
    return {
      addressURL: props.requestAddressUrl,
      townURL: props.requestTownUrl,
      parentURL: props.requestAddressLevelUrl,
      ignored: props.ignored,
      dataOverride: props.dataOverride,
      preprocessor: props.preprocessor,
    };
  }

  getOverlayContainer() {
    const { container } = this.props;
    if (container) {
      if (typeof container === 'function') {
        return container();
      }
      return container;
    }
    return this.outerWrapperRef;
  }

  displayValueFromSelection(selection) {
    return displayValueFromNodeChain(this.selectedNodeChain(selection));
  }

  triggerLabelUpdate(nodeChain) {
    const { onLabelUpdate, overseas } = this.props;
    if (typeof onLabelUpdate === 'function') {
      const nodes = nodeChain.filter((n) => !!n.parentNode);
      const label = !overseas && nodes.length <= 1 ? '' : nodes.map((n) => n.nameZh).join(' / ');
      onLabelUpdate.call(null, label, nodeChain);
    }
  }

  renderTab(pane, node) {
    const { overlayValue } = this.state;
    const { provincePartition } = this.props;
    const renderTag = (n) =>
      this.hiddenMap[node.id] ? null : (
        <AddressLabel key={n.id} node={n} checked={n.id === overlayValue[pane.field]} onClick={this.handleSelect} />
      );
    if (provincePartition === 'letter' && node.id === '1') {
      const parts = Array.from(Array(PROVINCE_LETTERS.length)).map(() => []);
      node.children.forEach((n) => parts[provinceLetterParititon(n.namePy)].push(n));
      return (
        <Tab.Item className="next-cn-address-tabpane" title={pane.title} key={pane.field}>
          {PROVINCE_LETTERS.map((title, idx) => {
            if (!parts[idx].length) {
              return null;
            }
            return (
              <Row key={idx} type="no-padding">
                <Col className="next-cn-address-provletter" fixedSpan={2}>
                  {title}
                </Col>
                <Col>{parts[idx].map(renderTag)}</Col>
              </Row>
            );
          })}
        </Tab.Item>
      );
    }
    return (
      <Tab.Item title={pane.title} key={pane.field}>
        <div className="next-cn-address-tabpane">
          {node.children.map((n) =>
            this.hiddenMap[n.id] ? null : (
              <AddressLabel
                key={n.id}
                node={n}
                checked={n.id === this.state.overlayValue[pane.field]}
                onClick={this.handleSelect}
              />
            ),
          )}
        </div>
      </Tab.Item>
    );
  }

  renderTabs() {
    const { overlayValue } = this.state;
    const { level } = this.props;
    if (this.safeToRender()) {
      return getShownTabs(overlayValue, this.allNodesSet, level).map((node) =>
        this.renderTab(PANE_ATTRIBUTES[node.children[0].levelKey], node),
      );
    }
    return null;
  }

  renderOverlayToolbar() {
    const { hasClear } = this.props;
    if (hasClear) {
      return (
        <div className="next-cn-address-overlay-toolbar" key="overlayToolbar">
          <Button size="small" onClick={this.handleClear}>
            清空
          </Button>
        </div>
      );
    }
    return null;
  }

  renderOverlayInnerSearch() {
    const { searchResult } = this.state;
    return {
      className: 'next-cn-address-search-wrap',
      inner: searchResult ? (
        // TODO: this is a horrible hack for 1.x compatibility
        <Menu onClick={this.handleSelectSearchItem} onItemClick={this.handleSelectSearchItem}>
          {searchResult.map((nid) => (
            <Menu.Item key={nid}>{this.allNodesSet[nid].nameZh}</Menu.Item>
          ))}
        </Menu>
      ) : (
        <Menu>
          <Menu.Item disabled>无搜索结果</Menu.Item>
        </Menu>
      ),
    };
  }

  renderOverlayInnerTabs() {
    const { fixedWidth, animation } = this.props;
    const { uiActiveTab } = this.state;
    return {
      className: {
        'next-cn-address-overlay': true,
        'next-cn-address-fixed-width': fixedWidth,
      },
      inner: [
        <Tab activeKey={uiActiveTab} onChange={this.handleActiveTabChanged} animation={animation} key="overlayTabs">
          {this.renderTabs()}
        </Tab>,
        this.renderOverlayToolbar(),
      ],
    };
  }

  renderOverlay() {
    const { inSearch } = this.state;
    const { className: className1 } = this.props;
    const { className, inner } = inSearch ? this.renderOverlayInnerSearch() : this.renderOverlayInnerTabs();
    // we have some problems for now:
    //  * Requirement: we want 2 modes for the same overlay:
    //     one for a 'standard' search result menu, another for customized address select with tabs;
    //    we also want to utilize arrow keys for search mode (by calling Menu.onKeyNavNodeKeyDown(e))
    //
    //  * <Overlay> breaks if the type of Overlay Root element changed during animation
    //      => means: the elements for the 2 modes must be wrapped in some kind of (stable) root node
    //
    //  * <Overlay> can only pick up ref of the Root element, the children just lost and since we have
    //     to wrap the content, it's difficult to get ref to the wrapped element (to do any operation)
    //
    // solution:
    //  * make a <MenuWrap> element, which always renders to a <div>
    //     (this also helps keep style backward compatible)
    //  * use callback ref to get ref of <Menu> inside <MenuWrap>
    //  * <MenuWrap> will proxy onKeyNavNodeKeyDown(), which will be called by <Select>
    return (
      <MenuWrap
        className={classNames(className, className1)}
        ref={() => {
          return 'menu';
        }}
      >
        {inner}
      </MenuWrap>
    );
  }

  render() {
    const {
      className,
      style,
      value,
      size,
      textOnly,
      textSerializer,
      level,
      addressSelectPlaceholder,
      countrySelectPlaceholder,
      requestAddressUrl,
      requestTownUrl,
      requestAddressLevelUrl,
      fixedWidth,
      overseas,
      animation,
      hasClear,
      container,
      ignored,
      dataOverride,
      preprocessor,
      hiddenData,
      hasToSelectToLastLevel,
      partialSelectionOverlayClosable,
      normalizeInitValue,
      provincePartition,
      showSearch,
      onLabelUpdate,
      popupProps,
      countryPopupProps,
      popupClassName,
      countryPopupClassName,
      _IS_CN_ADDRESS_,
      ...rest
    } = this.props;
    const elements = [];

    const { value: val, countryList, uiOverlayVisible, overlayValue, searchQuery } = this.state;

    // if we are loading address data -> show a 'loading' status
    // if the user has enabled 'overseas' -> show a country select
    // if the current country has more fine-grained address data,
    //      and the user does not forbid it explicitly -> show address select pane
    //  (just a fancy FizzBuzz ...
    if (!this.safeToRender()) {
      elements.push(
        <span
          key="dataLoading"
          className={classNames('next-cn-address-loading', className)}
          style={style}
          // { ... rest }
        >
          加载数据中 …
        </span>,
      );
    } else {
      const nodeChain = this.selectedNodeChain(val);
      if (textOnly) {
        let text = '';
        if (nodeChain.length < 2) {
          text = textSerializer([]);
        } else {
          text = textSerializer(nodeChain.slice(1));
        }
        return (
          <span className={classNames('next-cn-address-text', className)} style={style} {...rest}>
            {text}
          </span>
        );
      }

      if (overseas) {
        elements.push(
          <Select
            key="countrySelect"
            className="next-cn-address-country"
            value={val.country}
            size={size}
            placeholder={countrySelectPlaceholder}
            dataSource={countryList}
            onChange={this.handleSelectCountry}
            showSearch
            disabled={this.getDisabled()}
            hasClear={hasClear}
            popupContainer={this.getOverlayContainer}
            popupProps={countryPopupProps}
            popupClassName={countryPopupClassName}
          />,
        );
      }

      const countryHasChild = nodeChain.length >= 2 && nodeChain[1].children && nodeChain[1].children.length;
      // TODO: customize domestic select visibility on disabled
      if (
        countryHasChild &&
        level >= 1 &&
        ((this.getDisabled() && nodeChain.length > 2) || !this.getDisabled() || !overseas)
      ) {
        const props = {
          key: 'domesticSelect',
          className: 'next-cn-address-domestic',
          size,
          placeholder: addressSelectPlaceholder,
          popupContent: this.renderOverlay(),
          visible: uiOverlayVisible,
          onVisibleChange: this.handleOverlayVisibleChange,
          disabled: this.getDisabled(),
          onChange: this.handleSelectChange,
          hasClear,
          popupContainer: this.getOverlayContainer,
          hasArrow: true,
          popupProps,
          popupClassName,
        };
        const displayValue = uiOverlayVisible
          ? this.displayValueFromSelection(overlayValue)
          : displayValueFromNodeChain(nodeChain);
        const label = !showSearch ? displayValue : searchQuery;
        let searchValue = { label, value: KEY_SELECT_VALUE };
        if (!label) {
          searchValue = undefined;
        }
        props.dataSource = searchValue ? [searchValue] : [];
        props.value = searchValue && KEY_SELECT_VALUE;
        if (showSearch) {
          props.value = (searchValue && searchValue.label) || '';
          props.onInputUpdate = this.handleSearch;
          props.onInputFocus = this.handleSearchFocus;
        }
        const SelectComp = showSearch ? Select.Combobox : Select;
        elements.push(
          <SelectComp
            {...props}
            // TODO: next@1.x label可直接设置在里面
            label={rest.label}
            ref={(ele) => (this.selectRef = ele)}
          />,
        );
      }
    }

    return (
      <span
        className={classNames('next-cn-address', className)}
        style={style}
        ref={(el) => {
          this.outerWrapperRef = el;
        }}
      >
        {elements}
      </span>
    );
  }
}
