import React from 'react';
import Container from '../DragAndDrop/Draggable/components/Container';
import { Draggable } from '../DragAndDrop/Draggable';
import withDNDContext from './withDNDContext';
import { st, classes } from './SortableGridBase.st.css.js';
import { dataHooks, StripPosition } from './constants';
/** component allow you to implement drag and drop in grid layout */
class SortableGridBase extends React.PureComponent {
    constructor() {
        super(...arguments);
        this.state = {
            items: this.props.items || [],
            animationShifts: {},
            draggedId: null,
            indexToInsert: null,
            originalIndex: null,
        };
        this.wrapperNodes = [];
        this.setWrapperNode = (node, index, item) => {
            this.wrapperNodes[index] = { node, index, item };
        };
        this.reSetAnimationState = (overrides = {}) => {
            this.setState({ animationShifts: {}, draggedId: null, ...overrides });
        };
        this.handleMoveOut = id => {
            this.reSetAnimationState({
                items: this.state.items.filter(it => it.id !== id),
            });
            this.wrapperNodes = this.wrapperNodes.filter(({ item }) => item.id !== id);
        };
        this.getInsertionIndex = (addedIndex, item) => {
            const { insertPosition } = this.props;
            const { items } = this.state;
            if (insertPosition === 'start') {
                return 0;
            }
            if (insertPosition === 'end') {
                return items.includes(item) ? items.length - 1 : items.length;
            }
            return addedIndex;
        };
        /**
         * Called when DragSource (list item) is hovered over DragTarget (other list item)
         * Calculates animation shifts & adds new element if it was dragged from another list
         *
         * @param {object} prop Prop
         * @param {number} prop.addedIndex Index to where dragged element should be added
         * @param {number} prop.removedIndex Index from where dragged element was removed
         * @param {number|string} prop.id item's `id`
         * @param {object} prop.item item from `items` prop that is being dragged
         * */
        this.handleHover = prop => {
            const { addedIndex, item } = prop;
            const indexToInsert = this.getInsertionIndex(addedIndex, item);
            const originalIndex = this.state.items.findIndex(({ id }) => id === item.id);
            this.setState(() => ({
                indexToInsert,
                originalIndex,
            }));
        };
        this.handleDrop = ({ payload, addedIndex, removedIndex, addedToContainerId, removedFromContainerId, }) => {
            const index = this.getInsertionIndex(addedIndex, payload);
            this.reSetAnimationState();
            this.changeItemPlace(payload, index); // put element at right place after drop
            this.props.onDrop({
                payload,
                addedIndex: index,
                removedIndex,
                addedToContainerId,
                removedFromContainerId,
            });
        };
        this.changeItemPlace = (item, index) => {
            const items = [...this.state.items];
            const originalIndex = items.indexOf(item);
            if (originalIndex !== -1) {
                items.splice(originalIndex, 1);
            }
            items.splice(index, 0, item);
            this.setState({ items });
        };
        this.handleDragStart = data => {
            this.reSetAnimationState({ draggedId: data.id });
            if (this.props.onDragStart) {
                this.props.onDragStart(data);
            }
            this.setState({ indexToInsert: null });
        };
        this.handleDragEnd = data => {
            this.reSetAnimationState();
            if (this.props.onDragEnd) {
                this.props.onDragEnd(data);
            }
            this.setState({ indexToInsert: null });
        };
        this.renderItem = args => {
            const dropped = this.context.dragDropManager.monitor.didDrop();
            const dragging = this.context.dragDropManager.monitor.isDragging();
            return this.props.renderItem({
                ...args,
                isListInDragState: dragging && !dropped,
            });
        };
    }
    UNSAFE_componentWillReceiveProps({ items }) {
        // We clear state on drag end if element was dragged from another list,
        // `onDragEnd` will be called on "source" list, not "target" one.
        this.reSetAnimationState({ items: items ? items : this.state.items });
    }
    renderPreview() {
        const { className, contentClassName, renderItem } = this.props;
        return (React.createElement("div", { className: className },
            React.createElement("div", { className: contentClassName }, this.state.items.map((item, index) => (React.createElement("div", { key: `${item.id}-${index}-${this.props.containerId}` }, renderItem({
                item,
                id: item.id,
                isPlaceholder: false,
                isPreview: false,
            })))))));
    }
    render() {
        const { dataHook, className, contentClassName, groupName, dragPreview, containerId, withHandle, usePortal, animationDuration, animationTiming, droppable, startFixedElement, endFixedElement, } = this.props;
        const { items, animationShifts, draggedId, indexToInsert, originalIndex } = this.state;
        const common = {
            groupName,
            droppable,
            containerId,
            onHover: this.handleHover,
            onMoveOut: this.handleMoveOut,
        };
        if (dragPreview) {
            return this.renderPreview();
        }
        return (React.createElement(Container, { dataHook: dataHook, className: className, onDrop: this.handleDrop, total: this.state.items.length, ...common },
            React.createElement("div", { className: st(classes.sortableGridBaseContent, contentClassName) },
                startFixedElement && (React.createElement("div", { "data-hook": dataHooks.START_FIXED_ELEMENT }, startFixedElement)),
                items.map((item, index) => {
                    const isInitialPositionToDrop = indexToInsert === originalIndex;
                    /**
                     * define the strip position: in case when item is moved before the initial index, then
                     * the strip between the grid items must be appeared on the right side of hovered item. Otherwise, strip
                     * is on the left side of hovered item
                     * */
                    const stripPosition = indexToInsert < originalIndex
                        ? StripPosition.RIGHT
                        : StripPosition.LEFT;
                    return (React.createElement(Draggable, { key: `${item.id}-${containerId}`, shift: animationShifts[index], hasDragged: !!draggedId && draggedId !== item.id, setWrapperNode: this.setWrapperNode, animationDuration: animationDuration, animationTiming: animationTiming, ...common, id: item.id, index: index, item: item, renderItem: this.renderItem, withHandle: withHandle, usePortal: usePortal, onDrop: this.handleDrop, onDragStart: this.handleDragStart, onDragEnd: this.handleDragEnd, canDrag: this.props.canDrag, delay: this.props.delay, withStrip: !isInitialPositionToDrop &&
                            index === indexToInsert &&
                            stripPosition, isInitialPositionToDrop: isInitialPositionToDrop, listOfPropsThatAffectItems: this.props.listOfPropsThatAffectItems }));
                }),
                endFixedElement && (React.createElement("div", { "data-hook": dataHooks.END_FIXED_ELEMENT }, endFixedElement)))));
    }
}
SortableGridBase.defaultProps = {
    animationDuration: 0,
    animationTiming: '',
    droppable: true,
    insertPosition: 'any',
};
SortableGridBase.displayName = 'SortableGridBase';
SortableGridBase.contextTypes = {
    dragDropManager: () => null,
};
SortableGridBase.defaultProps = {};
export default withDNDContext(SortableGridBase);
//# sourceMappingURL=SortableGridBase.js.map