import React from 'react';
import { addToTree, generateUniqueGroupId, getDropParent, removeFromTree, } from './utils';
import CustomDragLayer from './DragLayer';
import Container from './Container';
import AutoScroller from './AutoScroller';
import { NestableListBaseContext } from './NestableListBaseContext';
import withDNDContext from './withDNDContext';
import { ZIndex } from '../common/ZIndex';
function replaceNegativeIndex(items, nextPosition, childrenProperty) {
    let currItems = items;
    return nextPosition.map(nextIndex => {
        if (nextIndex !== -1) {
            currItems = currItems[nextIndex][childrenProperty] || [];
            return nextIndex;
        }
        return currItems.length;
    });
}
// fixing a bug where the new path is increased in nesting
// and the first position jumps too far by 2, and that's why it decreased by 1
// need to check if the jump can become more severe
function getRealNextPosition(prev, next) {
    // moving up a level
    if (prev.length < next.length) {
        return next.map((nextIndex, i) => {
            if (typeof prev[i] !== 'number') {
                return nextIndex;
            }
            return nextIndex > prev[i] ? nextIndex - 1 : nextIndex;
        });
    }
    return next;
}
class NestableListBase extends React.PureComponent {
    constructor() {
        super(...arguments);
        this.state = {
            items: this.props.items,
            dragging: false,
        };
        this.containerRef = React.createRef();
        // drag and drop between multiple nestable lists is not supported
        // according to prevent it for now was created groupName - unique value for every nestable list
        // somethings similar exists in SortableListBase component.
        this.groupName = generateUniqueGroupId();
        // FIXME: reference to latest `state.items`. This is a workaround for issue
        // with `moveItem` where it updates `state.items` and if the update occurs
        // multiple times before the component re-renders, the `state.items` will be
        // be updated based on the previous state, not the latest one, which in some
        // cases leads to item duplication or removal.
        // This should be solved by not referring to current state by `this.state` and
        // instead providing callback to `setState` that receives the latest state.
        // However, due to architectural decisions this is not possible without
        // rewriting the whole component as `moveItem` must return new position of the
        // dragged item which is then used by the item itself to update its state.
        this._items = this.props.items;
        this.moveItem = ({ dragItem, prevPosition, nextPosition }) => {
            const { childrenProperty, preventChangeDepth, preventChangeParent, enforcePinnedOrder, items, } = this.props;
            if (dragItem.preventItemChangeDepth &&
                nextPosition.length !== prevPosition.length) {
                return prevPosition;
            }
            if (preventChangeDepth && nextPosition.length > 1) {
                const parent = getDropParent(items, nextPosition, childrenProperty);
                if (!parent) {
                    return prevPosition;
                }
            }
            if (preventChangeParent) {
                const prevParentPath = prevPosition.slice(0, -1);
                const nextParentPath = nextPosition.slice(0, -1);
                const prevParent = getDropParent(items, prevParentPath, childrenProperty);
                const nextParent = getDropParent(items, nextParentPath, childrenProperty);
                if (prevParent?.id !== nextParent?.id) {
                    return prevPosition;
                }
            }
            let newItems = this._items;
            // the remove action might affect the next position,
            // so update next coordinates accordingly
            let realNextPosition = getRealNextPosition(prevPosition, nextPosition);
            if (enforcePinnedOrder) {
                const shouldProhibitPinMoveDown = !!dragItem.isPinned && !realNextPosition.isPinned;
                const shouldProhibitNonPinMoveUp = !dragItem.isPinned && !!realNextPosition.isPinned;
                if (shouldProhibitPinMoveDown && shouldProhibitNonPinMoveUp) {
                    return prevPosition;
                }
            }
            if (realNextPosition[realNextPosition.length - 1] === -1) {
                realNextPosition = replaceNegativeIndex(newItems, realNextPosition, childrenProperty);
            }
            newItems = removeFromTree(newItems, prevPosition, childrenProperty);
            newItems = addToTree(newItems, dragItem, realNextPosition, childrenProperty);
            this._items = newItems;
            this.setState({ items: newItems });
            return realNextPosition;
        };
        this.dropItem = item => {
            this.props.onUpdate?.({ items: this.state.items, item });
        };
        this.onDragStart = itemProps => {
            this.setState({ dragging: true });
            this.props.onDragStart?.(itemProps);
        };
        this.onDragEnd = itemProps => {
            this.setState({ dragging: false });
            this.props.onDragEnd?.(itemProps);
        };
    }
    // tried to use getDerivedStateFromProps but encounter an issue where the state was
    // updated internally but props items stayed the same and it caused the new state to be
    // overridden with the old state
    // can be done if component is controlled but requires refactor
    UNSAFE_componentWillReceiveProps(newProps) {
        if (newProps.items !== this.state.items && !this.state.dragging) {
            this.setState({ items: newProps.items });
            this._items = newProps.items;
        }
    }
    render() {
        const { items } = this.state;
        const { renderItem, readOnly, childrenProperty, childrenStyle, renderAction, isRenderDraggingChildren, useDragHandle, maxDepth, preventChangeDepth, preventChangeParent, enforcePinnedOrder, threshold, theme, renderPrefix, dragLayerZIndex, zIndex, } = this.props;
        return (React.createElement(NestableListBaseContext.Provider, { value: {
                groupName: this.groupName,
                useDragHandle,
                maxDepth,
                preventChangeDepth,
                preventChangeParent,
                enforcePinnedOrder,
                threshold,
                readOnly,
                renderItem,
                renderPrefix,
                moveItem: this.moveItem,
                dropItem: this.dropItem,
                onDragStart: this.onDragStart,
                onDragEnd: this.onDragEnd,
            } },
            React.createElement("div", { ref: this.containerRef },
                React.createElement(Container, { treeDepth: 1, items: items, renderAction: renderAction, parentPosition: [], childrenProperty: childrenProperty, childrenStyle: childrenStyle, isRenderDraggingChildren: isRenderDraggingChildren, topLevel: true, theme: theme }),
                this.state.dragging && (React.createElement(React.Fragment, null,
                    React.createElement(CustomDragLayer, { siblings: items, isRenderDraggingChildren: isRenderDraggingChildren, renderItem: renderItem, childrenProperty: childrenProperty, childrenStyle: childrenStyle, theme: theme, dragLayerZIndex: zIndex ?? dragLayerZIndex }),
                    React.createElement(AutoScroller, { containerRef: this.containerRef }))))));
    }
}
NestableListBase.displayName = 'NestableListBase';
NestableListBase.defaultProps = {
    items: [],
    isRenderDraggingChildren: false,
    childrenProperty: 'children',
    childrenStyle: {},
    renderPrefix: () => null,
    renderAction: () => null,
    onUpdate: () => { },
    useDragHandle: false,
    preventChangeDepth: false,
    preventChangeParent: false,
    enforcePinnedOrder: false,
    maxDepth: Infinity,
    threshold: 30,
    dragLayerZIndex: ZIndex.nestableListDragLayer,
};
export default withDNDContext(NestableListBase);
//# sourceMappingURL=NestableListBase.js.map