import React, { Component } from 'react';
import { is, List, Map } from 'immutable';
import * as Tools from 'jad-tool';
import { Select, Empty, Checkbox, Icon } from 'antd';
import classNames from 'classnames';
import { throttle } from 'jad-tool';
import { initOptions, onCheckLow2High, onCheckHigh2Low, transform2FlatData, transform2TreeData } from './utils';

type OptionType = {
    value: string | number,
    name?: string,
    fullName?: (string)[],
    leaf: number, // 从 0 开始
    parent?: OptionType,
    children?: OptionType[],
    checked: boolean,
    indeterminate: boolean,
};

type State = {
    options: OptionType[],
    optionsFlat: OptionType[],
    selectedValue: (string | number)[],
    selectedMap: OptionType[],
    clickedShowMap: { [stateName: string]: string | number },
    searchValue: string,
};

type PropsKeyValue = {
    propPValue: string,
    propValue: string,
    propName: string,
}
type SelectProps = {
    options?: OptionType[], // options 与 optionsFlat二选一
    optionsFlat?: OptionType[], // 最简单的二维数组：需要[propPValue]/[propValue]/[propName]
    value?: string[] | number[],
    disabled?: boolean,
    keyValue?: PropsKeyValue,
    onChange?: (values: string[] | number[]) => void,
    showCheckedStrategy?: 'SHOW_PARENT' | 'SHOW_CHILD' | 'SHOW_ALL', // 默认 SHOW_PARENT
    showCheckedFormat?: 'ALL' | 'SELF', // 默认 ALL —— 放在 select 里面或者展示里面，去 state.selected Map中的propName
    showSearchFormat?: 'ALL' | 'SELF', // 默认 ALL —— 放在 select  search 面板 里面或者展示里面，去 state.selectedMap 中的propName
    selectProps?: { [propsName: string]: any } // select 中除了排除掉上述的其他属性，以及labelInValue
}
export class MultiCascaderSelect extends Component<SelectProps>{
    state: State;
    keyValue = {
        propPValue: 'pValue',
        propValue: 'value',
        propName: 'name',
    }
    multiCasacaderSelectRef = null;
    constructor(props: SelectProps) {
        super(props);

        if (props['keyValue'])
            this.keyValue = {
                propPValue: props['keyValue']['propPValue'] || this.keyValue.propPValue,
                propValue: props['keyValue']['propValue'] || this.keyValue.propValue,
                propName: props['keyValue']['propName'] || this.keyValue.propName,
            }
        const initValueOption = props['options'] ? props['options'] : (props['optionsFlat'] ? transform2TreeData(props['optionsFlat'], this.keyValue) : []);
        const initValueOptionFlat = transform2FlatData(initValueOption, this.keyValue.propValue);
        this.state = {
            optionsFlat: initValueOptionFlat,
            options: initValueOption,
            selectedValue: props.value,
            selectedMap: [],
            clickedShowMap: {}, // 点击
            searchValue: '',
        }
    }
    componentDidMount() {
        const { value } = this.props;
        const { options } = this.state;
        if (!Tools.isEmptyArray(options)) {
            this.initOptions(value, options);
        }
    }

    UNSAFE_componentWillReceiveProps(nextProps) {
        const { options, optionsFlat, value } = nextProps;
        if (!Tools.isEmptyArray(options) && !is(List(options), List(this.props.options))) {
            this.initOptions(value, options)
        } else if (!Tools.isEmptyArray(optionsFlat) && !is(List(optionsFlat), List(this.props.optionsFlat))) {
            this.initOptions(value, transform2TreeData(optionsFlat, this.keyValue))
        } else if (!is(List(value), List(this.props.value))) {
            if (!Tools.isEmptyArray(options))
                this.initOptions(value, options)
            else if (!Tools.isEmptyArray(optionsFlat))
                this.initOptions(value, transform2TreeData(optionsFlat, this.keyValue))
        }
    }
    shouldComponentUpdate(nextProps, nextState) {
        if ((is(Map(nextProps), Map(this.props)) &&
            is(Map(nextState), Map(this.state)))) {
            return false
        }
        return true
    }
    initOptions = (initValue: (string | number)[], initValueOptions: OptionType[]) => {
        const { selectedMap, options, optionsFlat, selectedValue } = initOptions.bind(this)(initValue, initValueOptions);
        const { onChange } = this.props;
        this.setState({
            selectedMap,
            options,
            optionsFlat,
            selectedValue,
        })
        onChange && onChange(selectedValue)
    }

    /* ------------------------------下拉选择面板------------------------------ */
    // 找到根结点 
    findRoot = (record: OptionType) => {
        const { propValue } = this.keyValue;
        function walk(r) {
            if (r && r['parent'] && r['parent'][propValue]) {
                return walk(r['parent'])
            } else if (r && !r['parent']) {
                return r
            }
        }
        return walk(record)
    }
    /**
     * @param options 最新操作过后的 options
     * 1. getSelectedMap 获取选中的项，此处可以根据 showCheckedStrategy 定制回填方式
     * 2. 取值： 
     *   2.1 SHOW_ALL：所有选中的节点都展示
     *   2.2 SHOW_PARENT：当父节点的子节点都选中时，展示父节点(父节点：cheked：true ) —— 此为默认
     *   2.3 SHOW_CHILD：展示最底层节点
     */
    getSelectedMap = (options: OptionType[]) => {
        const { showCheckedStrategy } = this.props;
        return options.reduce((prev, current) => {
            if (showCheckedStrategy === 'SHOW_CHILD') {
                if (current.checked && !current.children) {
                    return prev.concat(current)
                } else if ((current.checked || current.indeterminate) && current.children) {
                    return prev.concat(this.getSelectedMap(current.children))
                }
            } else if (showCheckedStrategy === 'SHOW_ALL') {
                if (current.checked) {
                    if (current.children)
                        return prev.concat(current).concat(this.getSelectedMap(current.children))
                    else return prev.concat(current)
                } else if (current.indeterminate) {
                    //后代部分选中
                    return prev.concat(this.getSelectedMap(current.children))
                }
            } else {
                if (current.checked) {
                    return prev.concat(current)
                } else if (current.indeterminate && current.children) {
                    //后代部分选中
                    return prev.concat(this.getSelectedMap(current.children))
                }
            }
            return prev
        }, [])
    }
    // 获取 selectedMap 和 替换 原来的 options中新操作过的 rootRecord
    onCollect = (record: OptionType, options: OptionType[]) => {
        const { propValue } = this.keyValue;
        const rootRecord = this.findRoot(record);
        options.find((item, index) => {
            if (item[propValue] === rootRecord[propValue]) {
                options[index] = rootRecord; // 注意：这里修改了 options
                return true
            };
        })
        const selectedMap = this.getSelectedMap(options);
        return { selectedMap: selectedMap, options: options }
    }
    // 选中
    onCheck = (checked: boolean, record: OptionType, e?) => {
        window.event ? window.event.cancelBubble = true : e.stopPropagation();
        let { options, optionsFlat, searchValue } = this.state;
        let { onChange } = this.props;
        const { propValue, propName } = this.keyValue;
        !searchValue && this.onShow(record); // 在checkbox 选择面板时才需要触发
        let newRecord = record;
        let newOptions = options;
        onCheckLow2High(checked, newRecord);
        onCheckHigh2Low(checked, newRecord);
        const collect = this.onCollect(newRecord, newOptions);
        const newValues = collect.selectedMap.map(item => item[propValue])
        const newOptionsFlat = collect.options ? transform2FlatData(collect.options, propName) : optionsFlat
        this.setState({
            selectedMap: collect.selectedMap,
            options: collect.options,
            optionsFlat: newOptionsFlat,
            selectedValue: newValues,
        })
        onChange && onChange(newValues)
    }
    // 打开子面板
    onShow = (record: OptionType) => {
        if(!record) return console.error('MultiCascaderSelect onShow  receive OptionType is undefined')
        const { propValue } = this.keyValue;
        const newClickedShowMap = {};

        function walkParent(r: OptionType) {
            newClickedShowMap[r['leaf']] = r[propValue]
            if (r && r['parent'] && r['parent'][propValue]) {
                walkParent(r['parent'])
            }
        }
        walkParent(record)

        this.setState({
            clickedShowMap: {
                ...newClickedShowMap,
                [record['leaf'] + 1]: undefined
            }
        })
    }
    renderCheckedDropdown = () => {
        const { disabled } = this.props;
        const { propValue, propName } = this.keyValue;
        const { options, clickedShowMap } = this.state;

        const columns = [];
        function mapColumn(options, clickedShowMap) {
            if (!options || !options.length) return
            const column = (<ul className={'jad-cascader-panel-column'} key={options[0][propValue]}>
                {options.map((item, index) => {
                    const active = !!(clickedShowMap[item.leaf] && clickedShowMap[item['leaf']] === item[propValue])
                    if (item['children'] && item['children'].length && active) {
                        mapColumn.bind(this)(item['children'], clickedShowMap)
                    }
                    return (
                        <li key={item[propValue]} className={classNames(active ? 'active' : '')}>
                            <Checkbox
                                checked={item['checked']}
                                indeterminate={item['indeterminate']}
                                onClick={e => this.onCheck(!item['checked'], item, e)}
                                disabled={!!disabled || item['disabled']}
                            >{item[propName]}</Checkbox>
                            {
                                !Tools.isEmptyArray(item['children']) ?
                                    <span className={'icon-right-box'} onClick={(e) => this.onShow(item)}>
                                        <Icon className={'icon-right'} type="right" />
                                    </span>
                                    : null
                            }
                        </li>
                    )
                })
                }
            </ul>)
            columns.unshift(column);
        }
        mapColumn.bind(this)(options, clickedShowMap);
        return <div className={'jad-multi-cascader-panel-wrap'} onMouseDown={e => e.preventDefault()}>
            {columns}
        </div>
    }
    /* ------------------------------下拉查询面板------------------------------ */
    onSearchSelect = (record: OptionType) => {
        const { selectedValue } = this.state;
        const { propValue } = this.keyValue;
        if (!selectedValue.includes(record[propValue])) {
            this.onCheck(true, record)
        }
    }

    getSearchFiteredOptions(optionsFlat: OptionType[]) {
        const { showSearchFormat } = this.props;
        const { searchValue } = this.state;
        const { propName } = this.keyValue;
        if (showSearchFormat === 'ALL') {
            return optionsFlat.filter(item => (searchValue && item['fullName'].join('').includes(searchValue)))
        } else {
            return optionsFlat.filter(item => (searchValue && item[propName].includes(searchValue)))
        }
    }

    renderSearchDropdown() {
        const { showSearchFormat } = this.props;
        const { propValue, propName } = this.keyValue;
        const { optionsFlat, searchValue } = this.state;
        const filteredOptions = this.getSearchFiteredOptions(optionsFlat) || [];
        if (!searchValue) return
        if (Tools.isEmptyArray(filteredOptions)) return <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />

        if (showSearchFormat === 'ALL') {
            const Node = filteredOptions.map(obj => {
                const selected = obj['checked'];
                return <div
                    key={obj[propValue]}
                    onClick={selected ? () => false : (e) => this.onSearchSelect(obj)}
                    className={classNames('jad-multi-cascader-select-search-item', selected ? 'item-selected' : '')}
                >
                    <span>
                        {obj['fullName'].map((f, index) => {
                            return (<span key={f} className={'text-ellipsis'}>
                                {index ? ` / ` : null}
                                {!selected ? f.split(searchValue).map((e, i) => {
                                    return <span key={i}>
                                        {i ? <span className={'matched'}>{searchValue}</span> : null}
                                        {e}
                                    </span>
                                }) : <span>{f}</span>}
                            </span>)
                        })}
                    </span>
                    <Icon type="check" className={classNames('icon-right')} />
                </div>
            })
            return <div onMouseDown={e => e.preventDefault()} className={'jad-multi-cascader-select-search-drop'}>{Node}</div>
        } else {
            const Node = filteredOptions.map(obj => {
                const selected = obj['checked'];
                return <div
                    key={obj[propValue]}
                    onClick={selected ? () => false : (e) => this.onSearchSelect(obj)}
                    className={classNames('jad-multi-cascader-select-search-item', selected ? 'item-selected' : '')}
                >
                    <span>
                        {!selected ? obj[propName].split(searchValue).map((f, index) => {
                            return (<span key={index} className={'text-ellipsis'}>
                                {index ? <span className={'matched'}>{searchValue}</span> : null}
                                {f}
                            </span>)
                        }) : obj[propName]}
                    </span>
                    <Icon type="check" className={classNames('icon-right')} />
                </div>
            })
            return <div onMouseDown={e => e.preventDefault()} className={'jad-multi-cascader-select-search-drop'}>{Node}</div>
        }
    }

    renderDropdown = (menu) => {
        const { searchValue } = this.state;
        if (!searchValue) return this.renderCheckedDropdown()
        else return this.renderSearchDropdown.bind(this)()
    }
    /* ------------------------------select 选中展示框------------------------------ */
    onSelectChange = (values, option) => {
        const { options, optionsFlat, onChange } = this.props;
        const { propName } = this.keyValue;
        if (Tools.isEmptyArray(values)) { // 清空，恢复初始props
            this.setState({
                selectedValue: [],
                selectedMap: [],
                options: options ? options : transform2TreeData(optionsFlat, this.keyValue),
                optionsFlat: optionsFlat ? optionsFlat : transform2FlatData(options, propName),
            })
            onChange && onChange([])
        }
    }
    onDeselect = (value: { key: string | number, label: string }) => {
        if (!value || !value['key']) return console.warn('MultiCascaderSelect onDeselect value has some problems')
        const { selectedMap } = this.state;
        const { propValue } = this.keyValue;
        const record = selectedMap.find(item => item[propValue] === value['key'])
        this.onCheck(false, record)
    }
    @throttle(300)
    onSearch(value) {
        this.setState({ searchValue: value })
    }
    onBlur = () => {
        this.setState({ searchValue: '' })
    }

    getCheckedValueFormat = () => {
        const { showCheckedFormat } = this.props;
        const { selectedMap } = this.state;
        const { propValue, propName } = this.keyValue;
        if (showCheckedFormat === 'ALL') {
            return selectedMap.map(item => ({ key: item && item[propValue], label: item && item['fullName'] && item['fullName'].join('/') || item[propName] }))
        } else {
            return selectedMap.map(item => ({ key: item && item[propValue], label: item && item[propName] }))
        }
    }
    render() {
        const { disabled, selectProps } = this.props;
        return <Select
            mode="tags"
            allowClear
            labelInValue
            filterOption={false}
            disabled={disabled}
            value={this.getCheckedValueFormat()}
            onChange={this.onSelectChange}
            onDeselect={this.onDeselect}
            onSearch={this.onSearch.bind(this)}
            onBlur={this.onBlur}
            dropdownRender={this.renderDropdown}
            dropdownMatchSelectWidth={false}
            {...selectProps}
        // onDropdownVisibleChange={(open) => !open && this.setState({ clickedShowMap: {} })}
        />
    }
}
