import { windowScrollPosition } from '@slickgrid-universal/utils';
/***
 * Interactions, add basic behaviors to any element.
 * All the packages are written in pure vanilla JS and supports both mouse & touch events.
 * @module Interactions
 * @namespace Slick
 */
/**
 * Draggable Class, enables dragging functionality for any element for example cell & row selections.
 * Note that mouse/touch start is on the specified container element but all other events are on the document body.
 * code refs:
 *   https://betterprogramming.pub/perfecting-drag-and-drop-in-pure-vanilla-javascript-a761184b797a
 * available optional options:
 *   - containerElement: container DOM element, defaults to "document"
 *   - dragFromClassDetectArr: array of tags and query selectors/ids to match on dragstart, used to determine
 *     drag source element. eg:  [ { tag: 'B', id: 'myElement' }, { tag: 'A', cssSelector: 'div.myClass' } ]
 *   - allowDragFrom: when defined, only allow dragging from an element that matches a specific query selector
 *   - allowDragFromClosest: when defined, only allow dragging from an element or its parent matching a specific .closest() query selector
 *   - onDragInit: drag initialized callback
 *   - onDragStart: drag started callback
 *   - onDrag: drag callback
 *   - onDragEnd: drag ended callback
 * @param {Object} options
 * @returns - Draggable instance which includes destroy method
 * @class Draggable
 */
export function Draggable(options) {
    let { containerElement } = options;
    const { onDragInit, onDragStart, onDrag, onDragEnd, preventDragFromKeys } = options;
    let element;
    let startX;
    let startY;
    let deltaX;
    let deltaY;
    let dragStarted;
    let matchClassTag;
    if (!containerElement) {
        containerElement = document.body;
    }
    let originaldd = {
        dragSource: containerElement,
        dragHandle: null,
    };
    function init() {
        if (containerElement) {
            containerElement.addEventListener('mousedown', userPressed);
            containerElement.addEventListener('touchstart', userPressed);
        }
    }
    function executeDragCallbackWhenDefined(callback, evt, dd) {
        if (typeof callback === 'function') {
            return callback(evt, dd);
        }
    }
    function destroy() {
        if (containerElement) {
            containerElement.removeEventListener('mousedown', userPressed);
            containerElement.removeEventListener('touchstart', userPressed);
        }
    }
    /** Do we want to prevent Drag events from happening (for example prevent onDrag when Ctrl key is pressed while dragging) */
    function preventDrag(event) {
        let eventPrevented = false;
        if (preventDragFromKeys) {
            preventDragFromKeys.forEach((key) => {
                if (event[key]) {
                    eventPrevented = true;
                }
            });
        }
        return eventPrevented;
    }
    function userPressed(event) {
        element = event.target;
        if (!preventDrag(event)) {
            const targetEvent = event?.touches?.[0] ?? event;
            const { target } = targetEvent;
            if (!options.allowDragFrom ||
                (options.allowDragFrom && element.matches(options.allowDragFrom)) ||
                (options.allowDragFromClosest && element.closest(options.allowDragFromClosest))) {
                originaldd.dragHandle = element;
                matchClassTag = '';
                if (options.dragFromClassDetectArr) {
                    for (let o, i = 0; i < options.dragFromClassDetectArr.length; i++) {
                        o = options.dragFromClassDetectArr[i];
                        if ((o.id && element.id === o.id) || (o.cssSelector && element.matches(o.cssSelector))) {
                            matchClassTag = o.tag;
                            break;
                        }
                    }
                }
                const winScrollPos = windowScrollPosition();
                startX = winScrollPos.left + targetEvent.clientX;
                startY = winScrollPos.top + targetEvent.clientY;
                deltaX = targetEvent.clientX - targetEvent.clientX;
                deltaY = targetEvent.clientY - targetEvent.clientY;
                originaldd = Object.assign(originaldd, { deltaX, deltaY, startX, startY, target, matchClassTag });
                const result = executeDragCallbackWhenDefined(onDragInit, event, originaldd);
                if (result !== false) {
                    document.body.addEventListener('mousemove', userMoved);
                    document.body.addEventListener('touchmove', userMoved);
                    // register mouseup/... events on the window object so that we can catch them even if the user moves the mouse outside the container element
                    window.addEventListener('mouseup', userReleased);
                    window.addEventListener('touchend', userReleased);
                    window.addEventListener('touchcancel', userReleased);
                }
            }
        }
    }
    function userMoved(event) {
        const targetEvent = event?.touches?.[0] ?? event;
        if (!preventDrag(event)) {
            deltaX = targetEvent.clientX - startX;
            deltaY = targetEvent.clientY - startY;
            const { target } = targetEvent;
            if (!dragStarted) {
                originaldd = Object.assign(originaldd, { deltaX, deltaY, startX, startY, target, matchClassTag });
                executeDragCallbackWhenDefined(onDragStart, event, originaldd);
                dragStarted = true;
            }
            originaldd = Object.assign(originaldd, { deltaX, deltaY, startX, startY, target, matchClassTag });
            executeDragCallbackWhenDefined(onDrag, event, originaldd);
        }
    }
    function userReleased(event) {
        document.body.removeEventListener('mousemove', userMoved);
        document.body.removeEventListener('touchmove', userMoved);
        window.removeEventListener('mouseup', userReleased);
        window.removeEventListener('touchend', userReleased);
        window.removeEventListener('touchcancel', userReleased);
        // trigger a dragEnd event only after dragging started and stopped
        if (dragStarted) {
            const { target } = event;
            originaldd = Object.assign(originaldd, { target });
            executeDragCallbackWhenDefined(onDragEnd, event, originaldd);
            dragStarted = false;
        }
    }
    // initialize Draggable service by attaching mouse/touch events
    init();
    // public API
    return { destroy };
}
/**
 * MouseWheel Class, add mousewheel listeners and calculate delta values and return them in the callback function.
 * available optional options:
 *   - element: optional DOM element to attach mousewheel values, if undefined we'll attach it to the "window" object
 *   - onMouseWheel: mousewheel callback
 * @param {Object} options
 * @returns - MouseWheel instance which includes destroy method
 * @class MouseWheel
 */
export function MouseWheel(options) {
    const { element, onMouseWheel } = options;
    function destroy() {
        element.removeEventListener('wheel', wheelHandler);
        element.removeEventListener('mousewheel', wheelHandler);
    }
    function init() {
        element.addEventListener('wheel', wheelHandler);
        element.addEventListener('mousewheel', wheelHandler);
    }
    // copy over the same event handler code used in jquery.mousewheel
    function wheelHandler(event) {
        const orgEvent = event || window.event;
        let delta = 0;
        let deltaX = 0;
        let deltaY = 0;
        // Old school scrollwheel delta
        if (orgEvent.wheelDelta) {
            delta = orgEvent.wheelDelta / 120;
        }
        if (orgEvent.detail) {
            delta = -orgEvent.detail / 3;
        }
        // New school multidimensional scroll (touchpads) deltas
        deltaY = delta;
        // Gecko
        if (orgEvent.axis !== undefined && orgEvent.axis === orgEvent.HORIZONTAL_AXIS) {
            deltaY = 0;
            deltaX = -1 * delta;
        }
        // WebKit
        if (orgEvent.wheelDeltaY !== undefined) {
            deltaY = orgEvent.wheelDeltaY / 120;
        }
        if (orgEvent.wheelDeltaX !== undefined) {
            deltaX = (-1 * orgEvent.wheelDeltaX) / 120;
        }
        if (typeof onMouseWheel === 'function') {
            onMouseWheel(event, delta, deltaX, deltaY);
        }
    }
    // initialize MouseWheel service by attaching mousewheel events
    init();
    // public API
    return { destroy };
}
/**
 * Resizable Class, enables resize functionality for any element
 * Code mostly comes from these 2 resources:
 *   https://spin.atomicobject.com/2019/11/21/creating-a-resizable-html-element/
 *   https://htmldom.dev/make-a-resizable-element/
 * available optional options:
 *   - resizeableElement: resizable DOM element
 *   - resizeableHandleElement: resizable DOM element
 *   - onResizeStart: resize start callback
 *   - onResize: resizing callback
 *   - onResizeEnd: resize ended callback
 * @param {Object} options
 * @returns - Resizable instance which includes destroy method
 * @class Resizable
 */
export function Resizable(options) {
    const { resizeableElement, resizeableHandleElement, onResizeStart, onResize, onResizeEnd } = options;
    const supportsPointerEvents = typeof window !== 'undefined' && 'PointerEvent' in window;
    let activePointerId;
    if (!resizeableHandleElement || typeof resizeableHandleElement.addEventListener !== 'function') {
        throw new Error('[SlickResizable] You did not provide a valid html element that will be used for the handle to resize.');
    }
    function init() {
        if (supportsPointerEvents) {
            resizeableHandleElement.addEventListener('pointerdown', pointerResizeStartHandler);
        }
        // Keep mouse/touch listeners for backward compatibility and test environments
        // where interactions are simulated with mousedown/mousemove/mouseup.
        resizeableHandleElement.addEventListener('mousedown', resizeStartHandler);
        resizeableHandleElement.addEventListener('touchstart', resizeStartHandler);
    }
    function destroy() {
        if (typeof resizeableHandleElement?.removeEventListener === 'function') {
            if (supportsPointerEvents) {
                resizeableHandleElement.removeEventListener('pointerdown', pointerResizeStartHandler);
                removePointerListeners();
            }
            resizeableHandleElement.removeEventListener('mousedown', resizeStartHandler);
            resizeableHandleElement.removeEventListener('touchstart', resizeStartHandler);
        }
    }
    function addPointerListeners() {
        resizeableHandleElement.addEventListener('pointermove', pointerResizingHandler);
        resizeableHandleElement.addEventListener('pointerup', pointerResizeEndHandler);
        resizeableHandleElement.addEventListener('pointercancel', pointerResizeEndHandler);
        resizeableHandleElement.addEventListener('lostpointercapture', pointerResizeEndHandler);
    }
    function removePointerListeners() {
        resizeableHandleElement.removeEventListener('pointermove', pointerResizingHandler);
        resizeableHandleElement.removeEventListener('pointerup', pointerResizeEndHandler);
        resizeableHandleElement.removeEventListener('pointercancel', pointerResizeEndHandler);
        resizeableHandleElement.removeEventListener('lostpointercapture', pointerResizeEndHandler);
    }
    function executeResizeCallbackWhenDefined(callback, e) {
        if (typeof callback === 'function') {
            return callback(e, { resizeableElement, resizeableHandleElement });
        }
    }
    function resizeStartHandler(e) {
        if (supportsPointerEvents && activePointerId !== undefined) {
            return;
        }
        e.preventDefault();
        const event = e.touches ? e.changedTouches[0] : e;
        const result = executeResizeCallbackWhenDefined(onResizeStart, event);
        if (result !== false) {
            document.body.addEventListener('mousemove', resizingHandler);
            document.body.addEventListener('mouseup', resizeEndHandler);
            document.body.addEventListener('touchmove', resizingHandler);
            document.body.addEventListener('touchend', resizeEndHandler);
        }
    }
    function pointerResizeStartHandler(e) {
        if (e.pointerType === 'mouse' && e.button !== 0) {
            return;
        }
        e.preventDefault();
        activePointerId = e.pointerId;
        if (typeof resizeableHandleElement.setPointerCapture === 'function') {
            resizeableHandleElement.setPointerCapture(e.pointerId);
        }
        const result = executeResizeCallbackWhenDefined(onResizeStart, e);
        if (result !== false) {
            addPointerListeners();
        }
        else if (typeof resizeableHandleElement.releasePointerCapture === 'function') {
            resizeableHandleElement.releasePointerCapture(e.pointerId);
            activePointerId = undefined;
        }
    }
    function resizingHandler(e) {
        if (e.preventDefault && e.type !== 'touchmove') {
            e.preventDefault();
        }
        const event = (e.touches ? e.changedTouches[0] : e);
        if (typeof onResize === 'function') {
            onResize(event, { resizeableElement, resizeableHandleElement });
        }
    }
    function pointerResizingHandler(e) {
        if (activePointerId !== undefined && e.pointerId !== activePointerId) {
            return;
        }
        if (e.preventDefault && e.pointerType !== 'touch') {
            e.preventDefault();
        }
        if (typeof onResize === 'function') {
            onResize(e, { resizeableElement, resizeableHandleElement });
        }
    }
    /** Remove all mouse/touch handlers */
    function resizeEndHandler(e) {
        const event = e.touches ? e.changedTouches[0] : e;
        executeResizeCallbackWhenDefined(onResizeEnd, event);
        document.body.removeEventListener('mousemove', resizingHandler);
        document.body.removeEventListener('mouseup', resizeEndHandler);
        document.body.removeEventListener('touchmove', resizingHandler);
        document.body.removeEventListener('touchend', resizeEndHandler);
    }
    function pointerResizeEndHandler(e) {
        if (activePointerId !== undefined && e.pointerId !== activePointerId) {
            return;
        }
        executeResizeCallbackWhenDefined(onResizeEnd, e);
        removePointerListeners();
        if (activePointerId !== undefined &&
            typeof resizeableHandleElement.hasPointerCapture === 'function' &&
            resizeableHandleElement.hasPointerCapture(activePointerId) &&
            typeof resizeableHandleElement.releasePointerCapture === 'function') {
            resizeableHandleElement.releasePointerCapture(activePointerId);
        }
        activePointerId = undefined;
    }
    // initialize Resizable service by attaching mouse/touch events
    init();
    // public API
    return { destroy };
}
//# sourceMappingURL=slickInteractions.js.map