define(["require", "exports", "../../Utilities"], function (require, exports, Utilities_1) {
    "use strict";
    Object.defineProperty(exports, "__esModule", { value: true });
    var MOUSEDOWN_PRIMARY_BUTTON = 0; // for mouse down event we are using ev.button property, 0 means left button
    var MOUSEMOVE_PRIMARY_BUTTON = 1; // for mouse move event we are using ev.buttons property, 1 means left button
    var DragDropHelper = /** @class */ (function () {
        function DragDropHelper(params) {
            this._selection = params.selection;
            this._dragEnterCounts = {};
            this._activeTargets = {};
            this._lastId = 0;
            // To make this class cheap to create, which allows simplifying some logic elsewhere,
            // only initialize the event group and global event handlers as needed.
            this._initialized = false;
        }
        DragDropHelper.prototype.dispose = function () {
            if (this._events) {
                this._events.dispose();
            }
        };
        DragDropHelper.prototype.subscribe = function (root, events, dragDropOptions) {
            var _this = this;
            if (!this._initialized) {
                this._events = new Utilities_1.EventGroup(this);
                var doc = Utilities_1.getDocument();
                // clear drag data when mouse up, use capture event to ensure it will be run
                if (doc) {
                    this._events.on(doc.body, 'mouseup', this._onMouseUp.bind(this), true);
                    this._events.on(doc, 'mouseup', this._onDocumentMouseUp.bind(this), true);
                }
                this._initialized = true;
            }
            var _a = dragDropOptions.key, key = _a === void 0 ? "" + ++this._lastId : _a;
            var handlers = [];
            var onDragStart;
            var onDragLeave;
            var onDragEnter;
            var onDragEnd;
            var onDrop;
            var onDragOver;
            var onMouseDown;
            var isDraggable;
            var isDroppable;
            var activeTarget;
            if (dragDropOptions && root) {
                var eventMap = dragDropOptions.eventMap, context = dragDropOptions.context, updateDropState_1 = dragDropOptions.updateDropState;
                var dragDropTarget = {
                    root: root,
                    options: dragDropOptions,
                    key: key,
                };
                isDraggable = this._isDraggable(dragDropTarget);
                isDroppable = this._isDroppable(dragDropTarget);
                if (isDraggable || isDroppable) {
                    if (eventMap) {
                        for (var _i = 0, eventMap_1 = eventMap; _i < eventMap_1.length; _i++) {
                            var event_1 = eventMap_1[_i];
                            var handler = {
                                callback: event_1.callback.bind(null, context),
                                eventName: event_1.eventName,
                            };
                            handlers.push(handler);
                            this._events.on(root, handler.eventName, handler.callback);
                        }
                    }
                }
                if (isDroppable) {
                    // If the target is droppable, wire up global event listeners to track drop-related events.
                    onDragLeave = function (event) {
                        if (!event.isHandled) {
                            event.isHandled = true;
                            _this._dragEnterCounts[key]--;
                            if (_this._dragEnterCounts[key] === 0) {
                                updateDropState_1(false /* isDropping */, event);
                            }
                        }
                    };
                    onDragEnter = function (event) {
                        event.preventDefault(); // needed for IE
                        if (!event.isHandled) {
                            event.isHandled = true;
                            _this._dragEnterCounts[key]++;
                            if (_this._dragEnterCounts[key] === 1) {
                                updateDropState_1(true /* isDropping */, event);
                            }
                        }
                    };
                    onDragEnd = function (event) {
                        _this._dragEnterCounts[key] = 0;
                        updateDropState_1(false /* isDropping */, event);
                    };
                    onDrop = function (event) {
                        _this._dragEnterCounts[key] = 0;
                        updateDropState_1(false /* isDropping */, event);
                        if (dragDropOptions.onDrop) {
                            dragDropOptions.onDrop(dragDropOptions.context.data, event);
                        }
                    };
                    onDragOver = function (event) {
                        event.preventDefault();
                        if (dragDropOptions.onDragOver) {
                            dragDropOptions.onDragOver(dragDropOptions.context.data, event);
                        }
                    };
                    this._dragEnterCounts[key] = 0;
                    // dragenter and dragleave will be fired when hover to the child element
                    // but we only want to change state when enter or leave the current element
                    // use the count to ensure it.
                    events.on(root, 'dragenter', onDragEnter);
                    events.on(root, 'dragleave', onDragLeave);
                    events.on(root, 'dragend', onDragEnd);
                    events.on(root, 'drop', onDrop);
                    events.on(root, 'dragover', onDragOver);
                }
                if (isDraggable) {
                    // If the target is draggable, wire up local event listeners for mouse events.
                    onMouseDown = this._onMouseDown.bind(this, dragDropTarget);
                    onDragEnd = this._onDragEnd.bind(this, dragDropTarget);
                    // We need to add in data so that on Firefox we show the ghost element when dragging
                    onDragStart = function (event) {
                        var options = dragDropOptions;
                        if (options && options.onDragStart) {
                            options.onDragStart(options.context.data, options.context.index, _this._selection.getSelection(), event);
                        }
                        _this._isDragging = true;
                        if (event.dataTransfer) {
                            event.dataTransfer.setData('id', root.id);
                        }
                    };
                    events.on(root, 'dragstart', onDragStart);
                    events.on(root, 'mousedown', onMouseDown);
                    events.on(root, 'dragend', onDragEnd);
                }
                activeTarget = {
                    target: dragDropTarget,
                    dispose: function () {
                        if (_this._activeTargets[key] === activeTarget) {
                            delete _this._activeTargets[key];
                        }
                        if (root) {
                            for (var _i = 0, handlers_1 = handlers; _i < handlers_1.length; _i++) {
                                var handler = handlers_1[_i];
                                _this._events.off(root, handler.eventName, handler.callback);
                            }
                            if (isDroppable) {
                                events.off(root, 'dragenter', onDragEnter);
                                events.off(root, 'dragleave', onDragLeave);
                                events.off(root, 'dragend', onDragEnd);
                                events.off(root, 'dragover', onDragOver);
                                events.off(root, 'drop', onDrop);
                            }
                            if (isDraggable) {
                                events.off(root, 'dragstart', onDragStart);
                                events.off(root, 'mousedown', onMouseDown);
                                events.off(root, 'dragend', onDragEnd);
                            }
                        }
                    },
                };
                this._activeTargets[key] = activeTarget;
            }
            return {
                key: key,
                dispose: function () {
                    if (activeTarget) {
                        activeTarget.dispose();
                    }
                },
            };
        };
        DragDropHelper.prototype.unsubscribe = function (root, key) {
            var activeTarget = this._activeTargets[key];
            if (activeTarget) {
                activeTarget.dispose();
            }
        };
        DragDropHelper.prototype._onDragEnd = function (target, event) {
            var options = target.options;
            if (options.onDragEnd) {
                options.onDragEnd(options.context.data, event);
            }
        };
        /**
         * clear drag data when mouse up on body
         */
        DragDropHelper.prototype._onMouseUp = function (event) {
            this._isDragging = false;
            if (this._dragData) {
                for (var _i = 0, _a = Object.keys(this._activeTargets); _i < _a.length; _i++) {
                    var key = _a[_i];
                    var activeTarget = this._activeTargets[key];
                    if (activeTarget.target.root) {
                        this._events.off(activeTarget.target.root, 'mousemove');
                        this._events.off(activeTarget.target.root, 'mouseleave');
                    }
                }
                if (this._dragData.dropTarget) {
                    // raise dragleave event to let dropTarget know it need to remove dropping style
                    Utilities_1.EventGroup.raise(this._dragData.dropTarget.root, 'dragleave');
                    Utilities_1.EventGroup.raise(this._dragData.dropTarget.root, 'drop');
                }
            }
            this._dragData = null;
        };
        /**
         * clear drag data when mouse up outside of the document
         */
        DragDropHelper.prototype._onDocumentMouseUp = function (event) {
            var doc = Utilities_1.getDocument();
            if (doc && event.target === doc.documentElement) {
                this._onMouseUp(event);
            }
        };
        /**
         * when mouse move over a new drop target while dragging some items,
         * fire dragleave on the old target and fire dragenter to the new target
         * The target will handle style change on dragenter and dragleave events.
         */
        DragDropHelper.prototype._onMouseMove = function (target, event) {
            var 
            // use buttons property here since ev.button in some edge case is not updating well during the move.
            // but firefox doesn't support it, so we set the default value when it is not defined.
            _a = event.buttons, 
            // use buttons property here since ev.button in some edge case is not updating well during the move.
            // but firefox doesn't support it, so we set the default value when it is not defined.
            buttons = _a === void 0 ? MOUSEMOVE_PRIMARY_BUTTON : _a;
            if (this._dragData && buttons !== MOUSEMOVE_PRIMARY_BUTTON) {
                // cancel mouse down event and return early when the primary button is not pressed
                this._onMouseUp(event);
                return;
            }
            var root = target.root, key = target.key;
            if (this._isDragging) {
                if (this._isDroppable(target)) {
                    // we can have nested drop targets in the DOM, like a folder inside a group. In that case, when we drag into
                    // the inner target (folder), we first set dropTarget to the inner element. But the same event is bubbled to the
                    // outer target too, and we need to prevent the outer one from taking over.
                    // So, check if the last dropTarget is not a child of the current.
                    if (this._dragData) {
                        if (this._dragData.dropTarget &&
                            this._dragData.dropTarget.key !== key &&
                            !this._isChild(root, this._dragData.dropTarget.root)) {
                            if (this._dragEnterCounts[this._dragData.dropTarget.key] > 0) {
                                Utilities_1.EventGroup.raise(this._dragData.dropTarget.root, 'dragleave');
                                Utilities_1.EventGroup.raise(root, 'dragenter');
                                this._dragData.dropTarget = target;
                            }
                        }
                    }
                }
            }
        };
        /**
         * when mouse leave a target while dragging some items, fire dragleave to the target
         */
        DragDropHelper.prototype._onMouseLeave = function (target, event) {
            if (this._isDragging) {
                if (this._dragData && this._dragData.dropTarget && this._dragData.dropTarget.key === target.key) {
                    Utilities_1.EventGroup.raise(target.root, 'dragleave');
                    this._dragData.dropTarget = undefined;
                }
            }
        };
        /**
         * when mouse down on a draggable item, we start to track dragdata.
         */
        DragDropHelper.prototype._onMouseDown = function (target, event) {
            if (event.button !== MOUSEDOWN_PRIMARY_BUTTON) {
                // Ignore anything except the primary button.
                return;
            }
            if (this._isDraggable(target)) {
                this._dragData = {
                    clientX: event.clientX,
                    clientY: event.clientY,
                    eventTarget: event.target,
                    dragTarget: target,
                };
                for (var _i = 0, _a = Object.keys(this._activeTargets); _i < _a.length; _i++) {
                    var key = _a[_i];
                    var activeTarget = this._activeTargets[key];
                    if (activeTarget.target.root) {
                        this._events.on(activeTarget.target.root, 'mousemove', this._onMouseMove.bind(this, activeTarget.target));
                        this._events.on(activeTarget.target.root, 'mouseleave', this._onMouseLeave.bind(this, activeTarget.target));
                    }
                }
            }
            else {
                this._dragData = null;
            }
        };
        /**
         * determine whether the child target is a descendant of the parent
         */
        DragDropHelper.prototype._isChild = function (parentElement, childElement) {
            while (childElement && childElement.parentElement) {
                if (childElement.parentElement === parentElement) {
                    return true;
                }
                childElement = childElement.parentElement;
            }
            return false;
        };
        DragDropHelper.prototype._isDraggable = function (target) {
            var options = target.options;
            return !!(options.canDrag && options.canDrag(options.context.data));
        };
        DragDropHelper.prototype._isDroppable = function (target) {
            // TODO: take the drag item into consideration to prevent dragging an item into the same group
            var options = target.options;
            var dragContext = this._dragData && this._dragData.dragTarget ? this._dragData.dragTarget.options.context : undefined;
            return !!(options.canDrop && options.canDrop(options.context, dragContext));
        };
        return DragDropHelper;
    }());
    exports.DragDropHelper = DragDropHelper;
});
//# sourceMappingURL=DragDropHelper.js.map