var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
function onMouseDown(dragEvent) {
    return __awaiter(this, void 0, void 0, function* () {
        if (dragEvent.button !== 0)
            return;
        const element = $(this);
        const root = element.is(':visible') ? element : element.find(':visible').first();
        const startPos = new Point(dragEvent).subtract(new Point(root.offset()));
        let args;
        let ghost;
        let dragStarted = false;
        let dragEnded = false;
        const mouseMoveThrottled = throttleRaf(mousemove);
        document.addEventListener('pointermove', mouseMoveThrottled);
        document.addEventListener('pointerup', mouseup);
        function startDrag(e) {
            var _a;
            if (dragStarted)
                return false;
            const diff = new Point(e).subtract(new Point(dragEvent));
            if (diff.length < 10)
                return false;
            dragStarted = true;
            const target = $(dragEvent.originalEvent.target);
            const elements = target.parentsUntil(element.parent()).add(target);
            const bounds = root[0].getBoundingClientRect();
            args = {
                source: element[0],
                sourceSize: {
                    x: bounds.left,
                    y: bounds.top,
                    width: bounds.width,
                    height: bounds.height
                },
                originalEvent: dragEvent.originalEvent,
                event: dragEvent.originalEvent,
                tags: getDraggableTags(elements),
                data: getData(elements)
            };
            // Duplicate element as ghost
            const ghostElement = root[0].cloneNode(true);
            cloneComputedStyle(root[0], ghostElement);
            // Create ghost root element 
            ghost = $('<div class="drag_ghost-root"></div>');
            ghost.append(ghostElement);
            ghost.css({ 'mask': `radial-gradient(at ${dragEvent.offsetX}px ${dragEvent.offsetY}px, white, transparent)` });
            $('body').append(ghost);
            (_a = getSelection()) === null || _a === void 0 ? void 0 : _a.removeAllRanges();
            return true;
        }
        function endDrag(e) {
            return __awaiter(this, void 0, void 0, function* () {
                if (!dragStarted)
                    return;
                dragEnded = true;
                yield ZoneHelper.handleDragEnd(Object.assign(Object.assign({}, args), { event: e }));
                ghost.remove();
                element.css({ 'display': '' });
                element.removeClass('dragging');
            });
        }
        function mousemove(e) {
            return __awaiter(this, void 0, void 0, function* () {
                if (dragEnded)
                    return;
                const justStarted = startDrag(e);
                if (!dragStarted)
                    return;
                const pos = new Point(e);
                const offset = pos.subtract(startPos);
                ghost.css({ left: `${offset.x}px`, top: `${offset.y}px` });
                yield ZoneHelper.handleMouseMove(Object.assign(Object.assign({}, args), { event: e }));
                // Change late to prevent ugly timing jitter
                if (justStarted) {
                    // Change element styling with dragging class
                    element.addClass('dragging');
                }
            });
        }
        function mouseup(e) {
            return __awaiter(this, void 0, void 0, function* () {
                document.removeEventListener('pointermove', mouseMoveThrottled);
                document.removeEventListener('pointerup', mouseup);
                yield endDrag(e);
            });
        }
    });
}
function throttleRaf(callback) {
    let handle = undefined;
    return (...args) => {
        if (handle)
            return;
        handle = requestAnimationFrame(() => __awaiter(this, void 0, void 0, function* () {
            yield callback(...args);
            handle = undefined;
        }));
    };
}
function getTags(raw) {
    if (raw === '' || raw == null)
        return null;
    if (typeof raw === 'string')
        return new Set(raw.split(','));
    if (Array.isArray(raw))
        return new Set(raw);
    return null;
}
function getDraggableTags(elements) {
    const result = new Set();
    elements.each(function () {
        var _a, _b;
        (_a = getTags(this.getAttribute('data-draggable'))) === null || _a === void 0 ? void 0 : _a.forEach(x => result.add(x));
        (_b = getTags(this.getAttribute('data-droptags'))) === null || _b === void 0 ? void 0 : _b.forEach(x => result.add(x));
    });
    return result.size === 0 ? null : result;
}
function getData(elements) {
    const data = {};
    let any = false;
    elements.each(function () {
        for (let i = 0; i < this.attributes.length; i++) {
            const attr = this.attributes.item(i);
            if (attr.name === 'data-dropvalue') {
                data[''] = parse(attr.value);
                any = true;
            }
            else if (attr.name.startsWith('data-dropvalue-')) {
                const key = attr.name.substring(15);
                data[key] = parse(attr.value);
                any = true;
            }
        }
    });
    return any ? data : null;
}
function parse(s) {
    try {
        return JSON.parse(s);
    }
    catch (e) {
        return s;
    }
}
// https://stackoverflow.com/a/1848489
function cloneComputedStyle(src, dest) {
    const s = document.defaultView.getComputedStyle(src);
    // ReSharper disable once MissingHasOwnPropertyInForeach
    for (const i in s) {
        if (i === 'pointer-events')
            continue;
        // Do not use `hasOwnProperty`, nothing will get copied
        // ReSharper disable once SuspiciousTypeofCheck
        if (typeof s[i] === 'string' && s[i] && i !== 'cssText' && !/\d/.test(i)) {
            // The try is for setter only properties
            try {
                dest.style[i] = s[i];
                // `fontSize` comes before `font` If `font` is empty, `fontSize` gets
                // overwritten.  So make sure to reset this property. (hackyhackhack)
                // Other properties may need similar treatment
                if (i === 'font') {
                    dest.style.fontSize = s.fontSize;
                }
            }
            catch (e) { }
        }
    }
}
class ZoneHelper {
    static handleMouseMove(e) {
        return __awaiter(this, void 0, void 0, function* () {
            let zone;
            let skip = 0;
            while (true) {
                zone = this.getZone(e.event, skip++);
                if (!zone)
                    break;
                if (this.tagsMatch(zone, e.tags)) {
                    yield this.notifyDragOver(zone, e);
                }
                else {
                    this.wasRefused = true;
                    this.wasIgnored = true;
                }
                if (!this.wasIgnored)
                    break;
            }
            if (this.wasRefused) {
                e.source.removeAttribute('data-drop_accept');
            }
            // notify dragLeave if drop target changed
            if (this.previousZone && this.previousZone.target !== (zone === null || zone === void 0 ? void 0 : zone.target)) {
                yield this.notifyDragLeave(this.previousZone, e);
            }
            this.previousZone = zone;
        });
    }
    static handleDragEnd(e) {
        return __awaiter(this, void 0, void 0, function* () {
            if (this.previousZone == null)
                return;
            yield this.notifyDragEnd(this.previousZone, e);
        });
    }
    static tagsMatch(zone, tags) {
        const zoneTags = getTags(zone.target.getAttribute('data-droparea'));
        if (zoneTags == null && tags == null)
            return true;
        if (zoneTags == null || tags == null)
            return false;
        for (const tag of tags) {
            if (zoneTags.has(tag))
                return true;
        }
        return false;
    }
    static notifyDragOver(zone, e) {
        return __awaiter(this, void 0, void 0, function* () {
            var _a;
            const accept = zone.dotnet
                ? (_a = yield zone.dotnet.invokeMethodAsync('NotifyDragOver', this.getNotificationArgs(e))) !== null && _a !== void 0 ? _a : null
                : true;
            if (accept == null)
                zone.target.removeAttribute('data-drop_accept');
            else
                zone.target.setAttribute('data-drop_accept', accept.toString());
            if (accept == null)
                e.source.removeAttribute('data-drop_accept');
            else
                e.source.setAttribute('data-drop_accept', accept.toString());
            this.wasRefused = accept !== true;
            this.wasIgnored = accept == null;
        });
    }
    static notifyDragLeave(zone, e) {
        return __awaiter(this, void 0, void 0, function* () {
            var _a;
            yield ((_a = zone.dotnet) === null || _a === void 0 ? void 0 : _a.invokeMethodAsync('NotifyDragLeave', this.getNotificationArgs(e)));
            zone.target.removeAttribute('data-drop_accept');
        });
    }
    static notifyDragEnd(zone, e) {
        return __awaiter(this, void 0, void 0, function* () {
            var _a;
            if (!this.wasRefused)
                yield ((_a = zone.dotnet) === null || _a === void 0 ? void 0 : _a.invokeMethodAsync('NotifyDragEnd', this.getNotificationArgs(e)));
            zone.target.removeAttribute('data-drop_accept');
            e.source.removeAttribute('data-drop_accept');
            this.previousZone = undefined;
            this.wasRefused = true;
            this.wasIgnored = false;
        });
    }
    static getNotificationArgs(e) {
        return {
            clientX: e.event.clientX,
            clientY: e.event.clientY,
            offsetX: e.event.offsetX,
            offsetY: e.event.offsetY,
            ctrlKey: e.event.ctrlKey,
            shiftKey: e.event.shiftKey,
            altKey: e.event.altKey,
            metaKey: e.event.metaKey,
            button: e.event.button,
            buttons: e.event.buttons,
            draggedSize: e.sourceSize,
            data: e.data
        };
    }
    static getZone(e, skip = 0) {
        const target = $(e.target);
        const areas = target.parents().add(target).filter('[data-droparea]');
        const droparea = areas.get(areas.length - skip - 1);
        if (!droparea)
            return undefined;
        return {
            target: droparea,
            dotnet: this.zones.get(droparea)
        };
    }
}
ZoneHelper.zones = new Map();
ZoneHelper.wasRefused = true;
ZoneHelper.wasIgnored = false;
export function registerDropZone(element, dotnet) {
    ZoneHelper.zones.set(element, dotnet);
}
export function unregisterDropZone(element) {
    ZoneHelper.zones.delete(element);
}
class Point {
    constructor(xOrEvent, y) {
        if (typeof xOrEvent === 'number') {
            this.x = xOrEvent;
            this.y = y !== null && y !== void 0 ? y : xOrEvent;
        }
        else if ('left' in xOrEvent) {
            this.x = xOrEvent.left;
            this.y = xOrEvent.top;
        }
        else {
            this.x = xOrEvent.clientX;
            this.y = xOrEvent.clientY;
        }
    }
    get length() {
        return Math.sqrt(this.x * this.x + this.y * this.y);
    }
    get magnitude() {
        return Math.max(Math.abs(this.x), Math.abs(this.y));
    }
    negate() {
        return new Point(-this.x, -this.y);
    }
    normalize() {
        return this.divide(this.length);
    }
    add(b) {
        return new Point(this.x + b.x, this.y + b.y);
    }
    subtract(b) {
        return new Point(this.x - b.x, this.y - b.y);
    }
    multiply(b) {
        if (typeof b === 'number')
            return new Point(this.x * b, this.y * b);
        return new Point(this.x * b.x, this.y * b.y);
    }
    divide(b) {
        if (typeof b === 'number')
            return new Point(this.x / b, this.y / b);
        return new Point(this.x / b.x, this.y / b.y);
    }
}
Point.zero = new Point(0);
Point.one = new Point(1);
Point.up = new Point(0, 1);
Point.right = new Point(1, 0);
Point.down = new Point(0, -1);
Point.left = new Point(-1, 0);
// Register global drag events
$('body').on('pointerdown', '[data-draggable]', onMouseDown);
//# sourceMappingURL=dragDrop.js.map