import React from 'react';
import { DragSource } from 'react-dnd';
import noop from 'lodash/noop';
import DragLayer from './DragLayer';
import { ItemTypes } from '../types';
import { dataAttributes } from '../constants';
import { getEmptyImage } from '../DragUtils';
const source = {
    beginDrag: ({ id, index, containerId, groupName, item, onMoveOut, onDragStart, onDrop, }) => {
        if (onDragStart) {
            onDragStart({
                id,
                index,
                containerId,
                groupName,
                item,
            });
        }
        /** we setup monitor.getItem() snapshot, so we will be always able to get info about item that we drag */
        return {
            id,
            index,
            containerId,
            groupName,
            originalIndex: index, // as index is mutable during d&d, we need another storage for original index
            originalItem: item,
            onMoveOut,
            realTime: {
                onMoveOut,
                onDrop,
                containerId,
            },
        };
    },
    endDrag: ({ id, index, containerId, groupName, item, onDragEnd }, monitor) => {
        /** if drop was called, on drop target and drag is end, then we notify parent about this */
        const { onDrop } = monitor.getItem().realTime;
        if (onDragEnd) {
            onDragEnd({
                id,
                index,
                containerId,
                groupName,
                item,
            });
        }
        if (monitor.getDropResult()) {
            const isSameGroup = groupName &&
                monitor.getItem().groupName &&
                groupName === monitor.getDropResult().groupName;
            const isSameContainer = containerId === monitor.getDropResult().containerId;
            if (isSameGroup || isSameContainer) {
                onDrop({
                    payload: monitor.getItem().originalItem, // original item
                    removedIndex: monitor.getItem().originalIndex, // original item index
                    addedIndex: monitor.getItem().index, // new item index
                    addedToContainerId: monitor.getDropResult().containerId, // new container for item
                    removedFromContainerId: containerId, // original item container
                });
            }
        }
    },
    canDrag: ({ id, index, containerId, groupName, item, canDrag }) => {
        return canDrag
            ? canDrag({
                id,
                index,
                containerId,
                groupName,
                item,
            })
            : true;
    },
    isDragging: ({ id, containerId, groupName }, monitor) => {
        const item = monitor.getItem();
        const isSameGroup = groupName && item.groupName && groupName === item.groupName;
        const isSameContainer = containerId === item.containerId;
        return (isSameGroup || isSameContainer) && item.id === id;
    },
};
const collect = (connect, monitor) => ({
    connectDragSource: connect.dragSource(),
    connectDragPreview: connect.dragPreview(),
    isDragging: monitor.isDragging(),
});
class DraggableSource extends React.Component {
    constructor() {
        super(...arguments);
        this.state = {
            offsetOfHandle: { x: 0, y: 0 },
            itemWidth: null,
        };
        this.rootNode = null;
        this.updateItemWidth = () => {
            if (this.rootNode) {
                this.setState({ itemWidth: this.rootNode.getBoundingClientRect().width });
            }
        };
        this._setRootNode = node => {
            // Don't need to reset the values if node remains the same
            if (node && this.rootNode !== node) {
                this.rootNode = node;
            }
        };
        this._renderPreview = ({ previewStyles }) => {
            const { renderItem, id, item, delayed } = this.props;
            return renderItem({
                id,
                item,
                previewStyles: { width: this.state.itemWidth, ...previewStyles },
                isPreview: true,
                connectHandle: noop,
                delayed,
            });
        };
    }
    componentDidMount() {
        if (this.props.connectDragPreview) {
            this.props.connectDragPreview(getEmptyImage(), {
                captureDraggingState: true,
            });
        }
        this.updateDiff();
        this.updateItemWidth();
    }
    componentDidUpdate(prevProps) {
        if (prevProps.id !== this.props.id ||
            prevProps.containerId !== this.props.containerId) {
            this.updateDiff();
        }
        if (prevProps.isDragging !== this.props.isDragging) {
            this.updateItemWidth();
        }
    }
    updateDiff() {
        /* in case if we have handle, the drag will start in wrong position and we need to fix this */
        if (this.props.withHandle && this.handleNode) {
            this.setState({
                offsetOfHandle: {
                    x: this.handleNode.getBoundingClientRect().x -
                        this.rootNode.getBoundingClientRect().x,
                    y: this.handleNode.getBoundingClientRect().y -
                        this.rootNode.getBoundingClientRect().y,
                },
            });
        }
    }
    _getWrapperStyles() {
        const { shift, ignoreMouseEvents, animationDuration, animationTiming } = this.props;
        const [xShift, yShift] = shift || [0, 0];
        const hasShift = xShift || yShift;
        const transition = ignoreMouseEvents
            ? `transform ${animationDuration}ms ${animationTiming}`
            : undefined;
        const transform = hasShift
            ? `translate(${xShift}px, ${yShift}px)`
            : undefined;
        const willChange = hasShift ? 'transform' : undefined;
        const pointerEvents = ignoreMouseEvents || hasShift ? 'none' : undefined;
        return {
            willChange,
            transition,
            transform,
            pointerEvents,
        };
    }
    _renderDraggableItem() {
        const { isDragging, connectDragSource, withHandle, renderItem, id, item, delayed, withStrip, isInitialPositionToDrop, } = this.props;
        const content = React.cloneElement(withHandle
            ? renderItem({
                id,
                item,
                isPlaceholder: isDragging,
                connectHandle: handle => {
                    const handleWithRef = React.cloneElement(handle, {
                        ref: node => (this.handleNode = node),
                    });
                    return connectDragSource(handleWithRef);
                },
                delayed,
                withStrip,
                isInitialPositionToDrop,
            })
            : connectDragSource(renderItem({
                id,
                item,
                isPlaceholder: isDragging,
                connectHandle: noop,
                delayed,
                withStrip,
                isInitialPositionToDrop,
            })), { [dataAttributes.draggableSource]: true, [dataAttributes.id]: id });
        return React.createElement("div", { style: this._getWrapperStyles() }, content);
    }
    _renderPreviewItem() {
        const { id, usePortal } = this.props;
        return (React.createElement(DragLayer, { offsetOfHandle: this.state.offsetOfHandle, usePortal: usePortal, renderPreview: this._renderPreview, id: id, 
            // pass 'width' this prop to rerender element with changed width from state
            width: this.state.itemWidth, draggedType: ItemTypes.DRAGGABLE }));
    }
    render() {
        const { connectDragSource } = this.props;
        return connectDragSource ? (React.createElement("div", { ref: this._setRootNode },
            this._renderDraggableItem(),
            this._renderPreviewItem())) : null;
    }
}
export default DragSource(ItemTypes.DRAGGABLE, source, collect)(DraggableSource);
//# sourceMappingURL=DraggableSource.js.map