/**
 * empty comment line Ivan Marshalkin
 *
 * TODO: navigation using the keyboard
 *
 * @author: Ivan Marshalkin
 * @date: 2019-05-27
 */

import * as React from 'react';
import keyBy from 'lodash/keyBy';
import {IFlatListItem, ITreeColumn, ITreeNode} from './TreeType';
import {IStringKeyMap} from '../../type';
import {safeInvoke} from '../../utils/safeInvoke';
import {Button, INTENT} from '../../index';
import {ActionItem, ConditionFunc, IDropConfig, Node as NodeComponent} from './Node';

import * as styles from './tree.m.scss';
import {TreeContainer, ScrollEvent} from './TreeContainer';

export interface ITreeTableProps<T> {
    nodes: Array<ITreeNode<T>>;
    flatNodes?: T[];
    isLoading?: boolean;
    readOnly?: boolean;
    columns: Array<ITreeColumn<T>>;
    onNodeSelect?: (node: ITreeNode<T>) => void;
    onNodeExpand?: (node: ITreeNode<T>) => void;
    onNodeDelete?: (node: ITreeNode<T>) => void;
    onBeforeNodeSelect?: (node: ITreeNode<T>) => boolean;
    selectedNodes: string[];
    disabledNodes?: string[];
    canSelectDisabled?: boolean;
    colorScheme?: ColorScheme;
    isStriped?: boolean;
    isAutoHeightFill?: boolean;
    maxRowsBeforeOverflow?: number;
    isDeletable?: boolean | ConditionFunc<T>;
    filter?: (node: ITreeNode<T>) => boolean;
    canAdd?: boolean;
    addText?: React.ReactNode;
    isAddButtonDisabled?: boolean;
    onAddNode?: () => void;
    isEditable?: boolean | ConditionFunc<T>;
    onEdit?: (node: ITreeNode<T>, newValue: string, cellId: string) => void;

    // Need to disable draggable mode when use Tree filter
    // You can use filter nodes outside the Tree component if you want to use drag&drop for filtered tree
    isDraggable?: boolean;
    onDragStart?: (event: React.DragEvent<HTMLDivElement>, node: ITreeNode<T>) => void;
    onDragEnd?: (event: React.DragEvent<HTMLDivElement>) => void;
    onDrop?: (event: React.DragEvent<HTMLDivElement>, node: ITreeNode<T>) => void;
    onMoveNode?: (node: ITreeNode<T>, parentNode?: ITreeNode<T>, previousNode?: ITreeNode<T>) => void;
    allowDragOver?: (event: React.DragEvent<HTMLDivElement>, dropToNode: ITreeNode<T>, draggedNode?: ITreeNode<T>) => boolean;
    canHasChildren?: boolean | ConditionFunc<T>;

    actions?: Array<ActionItem<T>>;
    isActionsEnabled?: (node: ITreeNode) => boolean;
    rightExtraItem?: (nodeData: T) => React.ReactNode;
}

interface IState<T> {
    selectedItems: IStringKeyMap;
    disabledItems: IStringKeyMap;
    draggedNode: ITreeNode<T> | undefined;
}

export enum ColorScheme {
    light = 'theme-light',
    dark = 'theme-dark'
}

const defaultProps = {
    colorScheme: ColorScheme.light,
    disabled: [],
    isStriped: true,
    isAutoHeightFill: true,
    maxRowsBeforeOverflow: 20,
    canSelectDisabled: false,
    addText: 'add'
};

export {
    ITreeNode,
    NodeComponent as Node,
    ITreeColumn,
    IFlatListItem,
    ActionItem,
    TreeContainer,
    ScrollEvent
};

export {TreeUtil} from './TreeUtil';

export class Tree<T = {[key: string]: any}> extends React.PureComponent<ITreeTableProps<T>, IState<T>> {
    static defaultProps = defaultProps;

    constructor (props: ITreeTableProps<T>) {
        super(props);

        this.state = {
            selectedItems: keyBy(this.props.selectedNodes),
            disabledItems: keyBy(this.props.disabledNodes),
            draggedNode: undefined
        };
    }

    addNode = () => {
        safeInvoke(this.props.onAddNode);
    };

    onDragStart = (event: React.DragEvent<HTMLDivElement>, node: ITreeNode<T>) => {
        safeInvoke(this.props.onDragStart, event, node);
        this.setState({draggedNode: node});
    }

    onDragEnd = (event: React.DragEvent<HTMLDivElement>) => {
        safeInvoke(this.props.onDragEnd, event);
        this.setState({draggedNode: undefined});
    }

    onDrop = (
        zeroLevelNode: ITreeNode<T>,
        zeroLevelNodeIndex: number
    ) => (
        event: React.DragEvent<HTMLDivElement>,
        node: ITreeNode<T>,
        dropConfig: IDropConfig<T>
    ) => {
        const draggedNode = this.state.draggedNode;

        if (!draggedNode) {
            return;
        }

        if (!dropConfig.configFilled && dropConfig.previousNode === undefined) {
            dropConfig.previousNode = zeroLevelNodeIndex === 0 ? undefined : this.props.nodes[zeroLevelNodeIndex - 1];
        }

        safeInvoke(this.props.onDrop, event, node);
        safeInvoke(this.props.onMoveNode, draggedNode, dropConfig.parentNode, dropConfig.previousNode);
        this.setState({draggedNode: undefined});
    }

    override componentDidUpdate (prevProps: Readonly<ITreeTableProps<T>>, prevState: Readonly<IState<T>>, snapshot?: any): void {
        if (prevProps.selectedNodes !== this.props.selectedNodes) {
            this.setState({selectedItems: keyBy(this.props.selectedNodes)});
        }

        if (prevProps.disabledNodes !== this.props.disabledNodes) {
            this.setState({disabledItems: keyBy(this.props.disabledNodes)});
        }
    }

    override render () {
        const {
            isLoading,
            nodes,
            colorScheme,
            isDeletable,
            columns,
            onNodeSelect,
            onNodeExpand,
            onNodeDelete,
            onBeforeNodeSelect,
            filter,
            isEditable,
            onEdit,
            isStriped,
            isAutoHeightFill,
            canSelectDisabled,
            flatNodes,
            maxRowsBeforeOverflow = Infinity,
            isDraggable,
            actions = [],
            isActionsEnabled,
            canHasChildren,
            rightExtraItem
        } = this.props;

        const nodesCount = flatNodes?.length;
        const draggedNode = this.state.draggedNode;

        return (
            <TreeContainer
                nodesCount={nodesCount || 0}
                colorScheme={colorScheme}
                isAutoHeightFill={isAutoHeightFill === true}
                isLoading={Boolean(isLoading)}
                isStriped={isStriped === true}
                maxRowsBeforeOverflow={maxRowsBeforeOverflow}
                additionalContent={(this.props.canAdd === true) ? () => (
                    <div className={styles.addDeleteButtonsContainer}>
                        <Button
                            isMinimal={true}
                            intent={INTENT.PRIMARY}
                            leftIcon={'plus-circle'}
                            onClick={this.addNode}
                            isDisabled={this.props.isAddButtonDisabled}>
                            {this.props.addText}
                        </Button>
                    </div>
                ) : undefined}>
                {nodes && nodes.map((node, index) => (
                    <NodeComponent<T>
                        key={node.key}
                        readOnly={node.readOnly}
                        columns={columns}
                        onNodeSelect={onNodeSelect}
                        onNodeExpand={onNodeExpand}
                        onBeforeNodeSelect={onBeforeNodeSelect}
                        selectedItems={this.state.selectedItems}
                        disabledItems={this.state.disabledItems}
                        canSelectDisabled={Boolean(canSelectDisabled)}
                        level={0}
                        node={node}
                        isLastChild={false}
                        isDeletable={isDeletable}
                        onNodeDelete={onNodeDelete}
                        filter={filter}
                        isEditable={isEditable}
                        onEdit={onEdit}
                        isDraggable={isDraggable}
                        draggedNode={draggedNode}
                        lastChildStreak={0}
                        onDragStart={this.onDragStart}
                        onDragEnd={this.onDragEnd}
                        onBackwardDrop={this.onDrop(node, index)}
                        allowDragOver={this.props.allowDragOver}
                        canHasChildren={canHasChildren}
                        actions={actions}
                        isActionsEnabled={isActionsEnabled}
                        rightExtraItem={rightExtraItem}
                        isExpanded={node.expanded}
                    />
                ))}
            </TreeContainer>
        );
    }
}
