/* *
 *
 *  (c) 2009-2026 Highsoft AS
 *  Author: Highsoft, Black Label
 *
 *  A commercial license may be required depending on use.
 *  See www.highcharts.com/license
 *
 *
 * */
'use strict';
import H from '../../Core/Globals.js';
const { doc, isTouchDevice } = H;
import { addEvent, fireEvent, objectEach, pick, removeEvent } from '../../Shared/Utilities.js';
/* *
 *
 *  Class
 *
 * */
// Internal class, but made public because Annotation extends it.
class EventEmitter {
    /* *
     *
     *  Functions
     *
     * */
    /**
     * Add emitter events.
     * @internal
     */
    addEvents() {
        const emitter = this, addMouseDownEvent = function (element) {
            addEvent(element, isTouchDevice ? 'touchstart' : 'mousedown', (e) => {
                emitter.onMouseDown(e);
            }, { passive: false });
        };
        addMouseDownEvent(this.graphic.element);
        (emitter.labels || []).forEach((label) => {
            if (label.options.useHTML &&
                label.graphic.text &&
                !label.graphic.text.foreignObject) {
                // Mousedown event bound to HTML element (#13070).
                addMouseDownEvent(label.graphic.text.element);
            }
        });
        objectEach(emitter.options.events, (event, type) => {
            const eventHandler = function (e) {
                if (type !== 'click' || !emitter.cancelClick) {
                    event.call(emitter, emitter.chart.pointer?.normalize(e), emitter.target, emitter);
                }
            };
            if ((emitter.nonDOMEvents || []).indexOf(type) === -1) {
                addEvent(emitter.graphic.element, type, eventHandler, { passive: false });
                if (emitter.graphic.div) {
                    addEvent(emitter.graphic.div, type, eventHandler, { passive: false });
                }
            }
            else {
                addEvent(emitter, type, eventHandler, { passive: false });
            }
        });
        if (emitter.options.draggable) {
            addEvent(emitter, 'drag', emitter.onDrag);
            if (!emitter.graphic.renderer.styledMode) {
                const cssPointer = {
                    cursor: {
                        x: 'ew-resize',
                        y: 'ns-resize',
                        xy: 'move'
                    }[emitter.options.draggable]
                };
                emitter.graphic.css(cssPointer);
                (emitter.labels || []).forEach((label) => {
                    if (label.options.useHTML &&
                        label.graphic.text &&
                        !label.graphic.text.foreignObject) {
                        label.graphic.text.css(cssPointer);
                    }
                });
            }
        }
        if (!emitter.isUpdating) {
            fireEvent(emitter, 'add');
        }
    }
    /**
     * Destroy the event emitter.
     * @internal
     */
    destroy() {
        this.removeDocEvents();
        removeEvent(this);
        this.hcEvents = null;
    }
    /**
     * Map mouse move event to the radians.
     * @internal
     */
    mouseMoveToRadians(e, cx, cy) {
        let prevDy = e.prevChartY - cy, prevDx = e.prevChartX - cx, dy = e.chartY - cy, dx = e.chartX - cx, temp;
        if (this.chart.inverted) {
            temp = prevDx;
            prevDx = prevDy;
            prevDy = temp;
            temp = dx;
            dx = dy;
            dy = temp;
        }
        return Math.atan2(dy, dx) - Math.atan2(prevDy, prevDx);
    }
    /**
     * Map mouse move to the scale factors.
     * @internal
     */
    mouseMoveToScale(e, cx, cy) {
        const prevDx = e.prevChartX - cx, prevDy = e.prevChartY - cy, dx = e.chartX - cx, dy = e.chartY - cy;
        let sx = (dx || 1) / (prevDx || 1), sy = (dy || 1) / (prevDy || 1);
        if (this.chart.inverted) {
            const temp = sy;
            sy = sx;
            sx = temp;
        }
        return {
            x: sx,
            y: sy
        };
    }
    /**
     * Map mouse move event to the distance between two following events.
     * @internal
     */
    mouseMoveToTranslation(e) {
        let dx = e.chartX - e.prevChartX, dy = e.chartY - e.prevChartY, temp;
        if (this.chart.inverted) {
            temp = dy;
            dy = dx;
            dx = temp;
        }
        return {
            x: dx,
            y: dy
        };
    }
    /**
     * Drag and drop event. All basic annotations should share this
     * capability as well as the extended ones.
     * @internal
     */
    onDrag(e) {
        if (this.chart.isInsidePlot(e.chartX - this.chart.plotLeft, e.chartY - this.chart.plotTop, {
            visiblePlotOnly: true
        })) {
            const translation = this.mouseMoveToTranslation(e);
            if (this.options.draggable === 'x') {
                translation.y = 0;
            }
            if (this.options.draggable === 'y') {
                translation.x = 0;
            }
            const emitter = this;
            if (emitter.points.length) {
                emitter.translate(translation.x, translation.y);
            }
            else {
                emitter.shapes.forEach((shape) => shape.translate(translation.x, translation.y));
                emitter.labels.forEach((label) => label.translate(translation.x, translation.y));
            }
            this.redraw(false);
        }
    }
    /**
     * Mouse down handler.
     * @internal
     */
    onMouseDown(e) {
        if (e.preventDefault) {
            e.preventDefault();
        }
        // On right click, do nothing:
        if (e.button === 2) {
            return;
        }
        const emitter = this, pointer = emitter.chart.pointer, 
        // Using experimental property on event object to check if event was
        // created by touch on screen on hybrid device (#18122)
        firesTouchEvents = (e?.sourceCapabilities?.firesTouchEvents) || false;
        e = pointer?.normalize(e) || e;
        let prevChartX = e.chartX, prevChartY = e.chartY;
        emitter.cancelClick = false;
        emitter.chart.hasDraggedAnnotation = true;
        emitter.removeDrag = addEvent(doc, isTouchDevice || firesTouchEvents ? 'touchmove' : 'mousemove', function (e) {
            emitter.hasDragged = true;
            e = pointer?.normalize(e) || e;
            e.prevChartX = prevChartX;
            e.prevChartY = prevChartY;
            fireEvent(emitter, 'drag', e);
            prevChartX = e.chartX;
            prevChartY = e.chartY;
        }, isTouchDevice || firesTouchEvents ? { passive: false } : void 0);
        emitter.removeMouseUp = addEvent(doc, isTouchDevice || firesTouchEvents ? 'touchend' : 'mouseup', function () {
            // Sometimes the target is the annotation and sometimes its the
            // controllable
            const annotation = pick(emitter.target && emitter.target.annotation, emitter.target);
            if (annotation) {
                // Keep annotation selected after dragging control point
                annotation.cancelClick = emitter.hasDragged;
            }
            emitter.cancelClick = emitter.hasDragged;
            emitter.chart.hasDraggedAnnotation = false;
            if (emitter.hasDragged) {
                // ControlPoints vs Annotation:
                fireEvent(pick(annotation, // #15952
                emitter), 'afterUpdate');
            }
            emitter.hasDragged = false;
            emitter.onMouseUp();
        }, isTouchDevice || firesTouchEvents ? { passive: false } : void 0);
    }
    /**
     * Mouse up handler.
     * @internal
     */
    onMouseUp() {
        this.removeDocEvents();
    }
    /**
     * Remove emitter document events.
     * @internal
     */
    removeDocEvents() {
        if (this.removeDrag) {
            this.removeDrag = this.removeDrag();
        }
        if (this.removeMouseUp) {
            this.removeMouseUp = this.removeMouseUp();
        }
    }
}
/* *
 *
 *  Default Export
 *
 * */
export default EventEmitter;
