import React, { Component } from "react";
import { Card, Tree, Input, Checkbox, Icon } from "antd";
import { cloneDeep } from "lodash";
import { throttle } from 'jad-tool'
import { is, List, Map } from 'immutable';
import { RenderToolTip } from '../Tips/Tips';
import { OptionType, PropsKeyValue, transform2TreeData, transform2FlatData, initOptions, getCheckedTreeOptions, updateOption } from './utils'

const { TreeNode } = Tree;
const { Search } = Input;

type State = {
    options: OptionType[],
    optionsFlat: OptionType[],
    checkedOptions: OptionType[], // 根据 checkedKeys 从 options 过滤到的数据，用于 selected-tree 的展示
    checkedKeys: any[],
    expandedKeys: any[],
    expandedKeysChecked: any[],
    searchValue: string,
    autoExpandParent: boolean,
    autoExpandParentChecked: boolean,
    checkedAllStatus: boolean
};

type TreeSelectSearchProps = {
    options: OptionType[], // options 与 options二选一
    optionsType: 'tree' | 'flat', // 数据源的数据格式类型：tree 树状有 children；flat 二维数组，有pid需要拿到后转换成；默认 flat
    value?: any[],
    disabled?: boolean,
    keyValue?: PropsKeyValue,
    onChange?: (values: any[]) => void,
    rootPid?: string | number,
    onChangeCheckedStrategy?: 'CHILD' | 'PARENT' | 'ALL', // 默认 CHILD
    [propName: string]: any,
}
export class TreeSelectSearch extends Component<TreeSelectSearchProps>{
    state: State;
    keyValue = {
        propPValue: 'pid',
        propValue: 'id',
        propName: 'name',
    }
    initOptions = (options: OptionType[], checkedKeys: any[]) => {
        return initOptions.bind(this)(options, checkedKeys)
    }
    initPropOptions = [];
    constructor(props: TreeSelectSearchProps) {
        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 clonedOptions = cloneDeep(props['options']);
        this.initPropOptions = props['optionsType'] === 'tree' ? clonedOptions : transform2TreeData(clonedOptions, this.keyValue, props['rootPid']); // 默认 flat
        const initCheckedKeys = props.value || []; // 跟 onChangeCheckedStrategy 有关，默认是 "ALL"，接受"PARENT"/"CHILD"
        const { options, optionsFlat, checkedOptions, checkedAllStatus } = this.initOptions(cloneDeep(this.initPropOptions), initCheckedKeys);

        this.state = {
            options: options,
            optionsFlat: optionsFlat,
            checkedOptions: checkedOptions,
            checkedKeys: initCheckedKeys,
            checkedAllStatus: checkedAllStatus,
            expandedKeys: [],
            expandedKeysChecked: initCheckedKeys,
            searchValue: "",
            autoExpandParent: true,
            autoExpandParentChecked: true,
        };
        this.onSearch = this.onSearch.bind(this)
        this.onChange = this.onChange.bind(this)
    }
    componentDidMount() {
        this.updateRenderLoopTreeNodeCache(this.state.searchValue);
    }

    shouldComponentUpdate(nextProps, nextState) {
        if ((is(Map(nextProps), Map(this.props)) &&
            is(Map(nextState), Map(this.state)))) return false
        else
            return true
    }
    /**
     * 这些场景需要有效触发 UNSAFE_componentWillReceiveProps 更新
     * 1. options
     * 2. value
     */
    UNSAFE_componentWillReceiveProps(nextProps) {
        const { options: nextPropsOptions, value: nextPropsValue, optionsType, rootPid } = nextProps;
        const { options: thisPropsOptions } = this.props;
        const { checkedKeys } = this.state;
        switch (optionsType) { // 默认 flat
            case 'tree':
                if (!is(List(nextPropsOptions), List(thisPropsOptions))) { // props.options 是否一致，不一致时需要更新this.initPropOptions 及 state 中的值
                    this.initPropOptions = cloneDeep(nextPropsOptions); // 默认 flat
                    this.propsUpdateStateOptionsAndRenderLoopTreeNodeCache(cloneDeep(this.initPropOptions), nextPropsValue)
                } else if (!is(List(nextPropsValue), List(checkedKeys))) { // props.value 与 state.checkedKeys 是否一致
                    this.propsUpdateStateOptionsAndRenderLoopTreeNodeCache(cloneDeep(this.initPropOptions), nextPropsValue)
                }
                break;
            default:
                if (!is(List(nextPropsOptions), List(thisPropsOptions))) { // props.options 是否一致，不一致时需要更新this.initPropOptions 及 state 中的值
                    this.initPropOptions = transform2TreeData(cloneDeep(nextPropsOptions), this.keyValue, rootPid); // 默认 flat
                    this.propsUpdateStateOptionsAndRenderLoopTreeNodeCache(cloneDeep(this.initPropOptions), nextPropsValue)
                } else if (!is(List(nextPropsValue), List(checkedKeys))) { // props.value 与 state.checkedKeys 是否一致
                    this.propsUpdateStateOptionsAndRenderLoopTreeNodeCache(cloneDeep(this.initPropOptions), nextPropsValue)
                }
                break;
        }
    }
    updateStateOptions = (options: OptionType[], checkedKeys: any[]) => {
        const { options: newOptions, optionsFlat: newOptionsFlat, checkedOptions: newCheckedOptions, checkedAllStatus: newCheckedAllStatus } = this.initOptions(options, checkedKeys)
        this.setState({
            checkedKeys: checkedKeys,
            options: newOptions,
            optionsFlat: newOptionsFlat,
            checkedOptions: newCheckedOptions,
            checkedAllStatus: newCheckedAllStatus,
            expandedKeysChecked: checkedKeys,
            autoExpandParentChecked: true
        }, this.onChange)
    }
    propsUpdateStateOptionsAndRenderLoopTreeNodeCache = (options: OptionType[], checkedKeys: any[]) => {
        this.updateStateOptions(options, checkedKeys)
        this.updateRenderLoopTreeNodeCache(this.state.searchValue);
    }

    // onCheck 方法中需要
    // checkedKeys 与 optionsFlat过滤 checked === true 的一致（如果不一致说明前面有问题）
    onFilterCheckedKeysCheckedStrategy = (checkedKeys: any[], optionsFlat: OptionType[]) => {
        const { propValue } = this.keyValue;
        const { onChangeCheckedStrategy } = this.props;
        if (!checkedKeys || !checkedKeys.length) return []
        else {
            switch (onChangeCheckedStrategy) {
                case 'ALL':
                    return checkedKeys;
                case 'PARENT':
                    const parentCheckedKeys = optionsFlat.filter(item => item['checked'] && (!item['parent'] || (item['parent'] && !item['parent'].checked))).map(e => e[propValue])
                    return parentCheckedKeys;
                case 'CHILD':
                default:
                    const childCheckedKeys = optionsFlat.filter(item => item['checked'] && (!(item['children'] && item['children'].length))).map(e => e[propValue])
                    return childCheckedKeys;
            }
        }
    }
    // onCheckAll、onClear需要
    onFindCheckedKeysCheckedStrategy = (optionsFlat: OptionType[], isALLChecked?: boolean) => {
        const { propValue } = this.keyValue;
        const { onChangeCheckedStrategy } = this.props;
        let checkedKeys = []
        if (isALLChecked) { // 全选中
            switch (onChangeCheckedStrategy) {
                case 'ALL': // 所有节点
                    checkedKeys = optionsFlat.map(item => item[propValue]);
                    break;
                case 'PARENT': // 跟节点
                    checkedKeys = optionsFlat.filter(item => !item['parent']).map(item => item[propValue])
                    break;
                case 'CHILD': // 底层节点
                default:
                    checkedKeys = optionsFlat.filter(item => !(item['children'] && item['children'].length)).map(item => item[propValue])
                    break;
            }
        } else { // 非全选中，要过滤 checked 属性
            switch (onChangeCheckedStrategy) {
                case 'ALL': // 所有节点
                    checkedKeys = optionsFlat.filter(item => item['checked']).map(item => item[propValue]);
                    break;
                case 'PARENT': // 父节点：checked === true && (没有父节点 || (有父节点 && 但父节点 checked === false))
                    checkedKeys = optionsFlat.filter(item => item['checked'] && (!item['parent'] || (item['parent'] && !item['parent'].checked))).map(item => item[propValue]);
                    break;
                case 'CHILD': // 底层节点：checked === true && 没有子节点
                default:
                    checkedKeys = optionsFlat.filter(item => item['checked'] && !(item['children'] && item['children'].length)).map(item => item[propValue]);
                    break;
            }
        }
        return checkedKeys
    }
    onExpand = (expandedKeys) => {
        this.setState({
            expandedKeys,
            autoExpandParent: false
        });
    };
    onExpandChecked = (expandedKeys) => {
        this.setState({
            expandedKeysChecked: expandedKeys,
            autoExpandParentChecked: false
        });
    };
    // 搜索模块
    @throttle(300)
    onSearch(value) {
        const { optionsFlat } = this.state;
        const { propValue, propName } = this.keyValue;
        if (!value || !value.trim()) return this.setState({
            expandedKeys: [],
            searchValue: value,
            autoExpandParent: true
        })
        const expandedKeys = optionsFlat
            .map((item) => {
                if (item[propName].indexOf(value) > -1) {
                    return item[propValue]
                }
                return null;
            })
            .filter((item, i, self) => item && self.indexOf(item) === i);
        this.updateRenderLoopTreeNodeCache(value)
        this.setState({
            expandedKeys,
            searchValue: value,
            autoExpandParent: true
        });
    }
    onChangeSearch = (e) => {
        const { value } = e.target;
        this.onSearch(value);
    };
    @throttle(300)
    onChange() {
        const { checkedKeys } = this.state;
        const { onChange } = this.props;
        onChange(checkedKeys)
    }
    onUnCheckExpandedKeysChange = (expandedKeysChecked: any[], uncheckKey) => {
        return expandedKeysChecked.filter(key => key !== uncheckKey)
    }
    // 选中/取消选中模块
    onCheck = (checkedKeys, info) => { // checkedKeys：所有 checked === true 的
        const checkedKey = info.node.props.eventKey || undefined;
        const checkStatus = info.checked;
        if (checkedKey === undefined) return console.error(`Please check: TreeSelectSearch onCheck checkedKey which is ${checkedKey}`);

        const { options, optionsFlat, expandedKeysChecked } = this.state;
        const { propValue } = this.keyValue;
        const matchCheckedRecord = optionsFlat.find(item => item[propValue] === checkedKey) // O(N) —— ok
        updateOption(checkStatus, matchCheckedRecord) // O(f(n)) —— ok
        const newCheckedOptions = getCheckedTreeOptions(options);
        const newCheckedKeys = this.onFilterCheckedKeysCheckedStrategy(checkedKeys, optionsFlat);
        const newCheckedAllStatus = options.every(item => item['checked']);
        const newExpandedKeysChecked = checkStatus ? [...expandedKeysChecked, checkedKey] : this.onUnCheckExpandedKeysChange(expandedKeysChecked, checkedKey)
        this.setState({
            checkedKeys: newCheckedKeys,
            options: options,
            optionsFlat: optionsFlat,
            checkedOptions: newCheckedOptions,
            checkedAllStatus: newCheckedAllStatus,
            expandedKeysChecked: newExpandedKeysChecked,
            autoExpandParentChecked: true,
        }, () => {
            this.onChange()
        })
    };
    // 删除/清空模块
    onClear = (record: OptionType) => {
        const { propValue } = this.keyValue;
        const { options, optionsFlat, expandedKeysChecked } = this.state;
        const checkedKey = record[propValue];
        const checkStatus = false;
        const matchCheckedRecord = optionsFlat.find(item => item[propValue] === checkedKey);
        updateOption(checkStatus, matchCheckedRecord) // 更新了 options、optionsFlat（注意：要传 matchCheckedRecord，不传 record，因为 record !== matchCheckedRecord，修改了 record 不会改到 options）

        const newCheckedOptions = getCheckedTreeOptions(options)
        const newCheckedKeys = this.onFindCheckedKeysCheckedStrategy(optionsFlat)
        const newCheckedAllStatus = options.every(item => item['checked']);
        const newExpandedKeysChecked = checkStatus ? [...expandedKeysChecked, checkedKey] : this.onUnCheckExpandedKeysChange(expandedKeysChecked, checkedKey)
        this.setState({
            checkedKeys: newCheckedKeys,
            options: options,
            optionsFlat: optionsFlat,
            checkedOptions: newCheckedOptions,
            checkedAllStatus: newCheckedAllStatus,
            expandedKeysChecked: newExpandedKeysChecked,
            autoExpandParentChecked: true,
        }, this.onChange)
    };

    onCheckAll = (e) => {
        const { checked } = e.target;
        if (checked) {
            const { options, optionsFlat } = this.state;
            const newCheckedKeys = this.onFindCheckedKeysCheckedStrategy(optionsFlat, true)
            this.updateStateOptions(options, newCheckedKeys)
        } else {
            this.onClearAll()
        }
    };
    onClearAll = () => {
        const resetStateOptions = cloneDeep(this.initPropOptions);
        const resetStateOptionsFlat = transform2FlatData(resetStateOptions, this.keyValue, this.props['rootPid']);
        this.setState({
            checkedKeys: [],
            checkedOptions: [],
            options: resetStateOptions,
            optionsFlat: resetStateOptionsFlat,
            checkedAllStatus: false,
            expandedKeysChecked: [],
            autoExpandParentChecked: true,
        }, this.onChange)
    };
    // render 缓存(初始、search时更新)
    renderLoopTreeNodeCache;
    updateRenderLoopTreeNodeCache = (searchValue?: string) => {
        this.renderLoopTreeNodeCache = this.renderLoopTreeNode(this.initPropOptions, searchValue)
    }
    renderLoopTreeNode = (data, searchValue?: string) => {
        const { propValue, propName } = this.keyValue;

        return data.map((item) => {
            const index = item[propName].indexOf(searchValue);
            const beforeStr = item[propName].substr(0, index);
            const afterStr = item[propName].substr(index + searchValue.length);
            const title =
                index > -1 ? (
                    <span>
                        {beforeStr}
                        <span className='primary'>{searchValue}</span>
                        {afterStr}
                    </span>
                ) : (
                    <span>{RenderToolTip(item[propName], 18, {placement: 'topLeft'})}</span>
                );
            if (item.children) {
                return (
                    <TreeNode className='search-tree-node' key={item[propValue]} title={title}>
                        {this.renderLoopTreeNode(item.children, searchValue)}
                    </TreeNode>
                );
            }
            return <TreeNode key={item[propValue]} title={title} />;
        });
    };
    selectedTitleNode = (record: OptionType) => {
        const { propName } = this.keyValue;
        return <>
            <span>{RenderToolTip(record[propName], 18, {placement: 'topLeft'})}</span>
            <Icon type="delete" className='delete-icon' onClick={() => this.onClear(record)} />
        </>
    }
    renderLoopSelectedTreeNode = (data) => {
        const { propValue } = this.keyValue;
        return data.map((item) => {
            if (item.children) {
                return (
                    <TreeNode className='selected-tree-node' key={item[propValue]} title={this.selectedTitleNode(item)}>
                        {this.renderLoopSelectedTreeNode(item.children)}
                    </TreeNode>
                );
            }
            return <TreeNode key={item[propValue]} title={this.selectedTitleNode(item)} />;
        });
    };
    render() {
        const { checkedOptions, checkedKeys, checkedAllStatus, expandedKeys, expandedKeysChecked, autoExpandParent, autoExpandParentChecked } = this.state;

        return (
            <div className="jad-tree-select-search-box">
                <Card
                    size="small"
                    title="待选择"
                    extra={<Checkbox checked={checkedAllStatus} onChange={this.onCheckAll}>全选</Checkbox>}
                    className="card-style"
                >
                    <Search
                        style={{ marginBottom: 8 }}
                        placeholder="请输入关键字"
                        onChange={this.onChangeSearch}
                    />
                    <Tree
                        checkedKeys={checkedKeys}
                        onCheck={this.onCheck}
                        onExpand={this.onExpand}
                        expandedKeys={expandedKeys}
                        autoExpandParent={autoExpandParent}
                        checkable
                        blockNode
                        selectable={false}
                        className='select-search-tree'
                    >
                        {this.renderLoopTreeNodeCache}
                    </Tree>
                </Card>
                <Card
                    size="small"
                    title="已有"
                    extra={<span className='link' onClick={this.onClearAll}> 清空 </span>}
                    className="card-style"
                >
                    <Tree
                        defaultExpandAll={true}
                        autoExpandParent={autoExpandParentChecked}
                        expandedKeys={expandedKeysChecked}
                        onExpand={this.onExpandChecked}
                        blockNode
                        selectable={false}
                        className='selected-tree'
                    >
                        {this.renderLoopSelectedTreeNode(checkedOptions)}
                    </Tree>
                </Card>
            </div>
        );
    }
}

