/* *
 *
 *  (c) 2023 Torstein Honsi, Askel Eirik Johansson
 *
 *  License: www.highcharts.com/license
 *
 *  !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
 *
 * */
'use strict';
import U from '../../Core/Utilities.js';
const { addEvent, isObject, pick, defined, merge, isNumber } = U;
import NBU from '../Annotations/NavigationBindingsUtilities.js';
const { getAssignedAxis } = NBU;
/* *
 *
 *  Constants
 *
 * */
const composedClasses = [], defaultOptions = {
    enabled: true,
    sensitivity: 1.1
};
/* *
 *
 *  Functions
 *
 * */
/**
 * @private
 */
const optionsToObject = (options) => {
    if (!isObject(options)) {
        return merge(defaultOptions, { enabled: defined(options) ? options : true });
    }
    return merge(defaultOptions, options);
};
/**
 * Fit a segment inside a range.
 * @private
 * @param {number} outerStart
 * Beginning of the range.
 * @param {number} outerWidth
 * Width of the range.
 * @param {number} innerStart
 * Beginning of the segment.
 * @param {number} innerWidth
 * Width of the segment.
 * @return {Object}
 * Object containing rangeStart and rangeWidth.
 */
const fitToRange = (outerStart, outerWidth, innerStart, innerWidth) => {
    if (innerStart + innerWidth > outerStart + outerWidth) {
        if (innerWidth > outerWidth) {
            innerWidth = outerWidth;
            innerStart = outerStart;
        }
        else {
            innerStart = outerStart + outerWidth - innerWidth;
        }
    }
    if (innerWidth > outerWidth) {
        innerWidth = outerWidth;
    }
    if (innerStart < outerStart) {
        innerStart = outerStart;
    }
    return {
        rangeStart: innerStart,
        rangeWidth: innerWidth
    };
};
let wheelTimer, startOnTick, endOnTick;
/**
 * Temporarly disable `axis.startOnTick` and `axis.endOnTick` to allow zooming
 * for small values.
 * @private
*/
const waitForAutomaticExtremes = function (axis) {
    const axisOptions = axis.options;
    // Options interfering with yAxis zoom by setExtremes() returning
    // integers by default.
    if (defined(wheelTimer)) {
        clearTimeout(wheelTimer);
    }
    if (!defined(startOnTick)) {
        startOnTick = axisOptions.startOnTick;
        endOnTick = axisOptions.endOnTick;
    }
    // Temporarily disable start and end on tick, because they would
    // prevent small increments of zooming.
    if (startOnTick || endOnTick) {
        axisOptions.startOnTick = false;
        axisOptions.endOnTick = false;
    }
    wheelTimer = setTimeout(() => {
        if (defined(startOnTick) && defined(endOnTick)) {
            // Repeat merge after the wheel zoom is finished, #19178
            axisOptions.startOnTick = startOnTick;
            axisOptions.endOnTick = endOnTick;
            // Set the extremes to the same as they already are, but now
            // with the original startOnTick and endOnTick. We need
            // `forceRedraw` otherwise it will detect that the values
            // haven't changed. We do not use a simple yAxis.update()
            // because it will destroy the ticks and prevent animation.
            const { min, max } = axis.getExtremes();
            axis.forceRedraw = true;
            axis.setExtremes(min, max);
            startOnTick = endOnTick = void 0;
        }
    }, 400);
};
/**
* Calulate the ratio of mouse position on the axis to it's length. If mousePos
* doesn't exist, returns 0.5;
* @private
*/
const getMouseAxisRatio = function (chart, axis, mousePos) {
    if (!defined(mousePos)) {
        return 0.5;
    }
    const mouseAxisRatio = (mousePos - axis.pos) / axis.len, isXAxis = axis.isXAxis;
    if (isXAxis && (!axis.reversed !== !chart.inverted) ||
        !isXAxis && axis.reversed) {
        // We are taking into account that xAxis automatically gets
        // reversed when chart.inverted
        return 1 - mouseAxisRatio;
    }
    return mouseAxisRatio;
};
/**
* Perform zooming on the passed axis.
* @private
* @param {Highcharts.Chart} chart
* Chart object.
* @param {Highcharts.Axis} axis
* Axis to zoom.
* @param {number} mousePos
* Mouse position on the plot.
* @param {number} howMuch
* Amount of zoom to apply.
* @param {number} centerArg
* Mouse position in axis units.
* @return {boolean}
* True if axis extremes were changed.
*/
const zoomOnDirection = function (chart, axis, mousePos, howMuch, centerArg) {
    const isXAxis = axis.isXAxis;
    let hasZoomed = false;
    if (defined(axis.max) && defined(axis.min) &&
        defined(axis.dataMax) && defined(axis.dataMin)) {
        if (!isXAxis) {
            waitForAutomaticExtremes(axis);
        }
        const range = axis.max - axis.min, center = isNumber(centerArg) ? centerArg :
            axis.min + range / 2, mouseAxisRatio = getMouseAxisRatio(chart, axis, mousePos), newRange = range * howMuch, newMin = center - newRange * mouseAxisRatio, dataRange = pick(axis.options.max, axis.dataMax) -
            pick(axis.options.min, axis.dataMin), minPaddingOffset = axis.options.min ? 0 :
            dataRange * axis.options.minPadding, maxPaddingOffset = axis.options.max ? 0 :
            dataRange * axis.options.maxPadding, outerMin = pick(axis.options.min, axis.dataMin) - minPaddingOffset, outerRange = dataRange + maxPaddingOffset + minPaddingOffset, newExt = fitToRange(outerMin, outerRange, newMin, newRange), zoomOut = (newExt.rangeStart < pick(axis.options.min, outerMin) ||
            newExt.rangeStart === axis.min &&
                (newExt.rangeWidth > outerRange &&
                    newExt.rangeStart + newExt.rangeWidth <
                        pick(axis.options.max, Number.MIN_VALUE)) ||
            newExt.rangeWidth === axis.max - axis.min);
        if (defined(howMuch) && !zoomOut) { // Zoom
            axis.setExtremes(newExt.rangeStart, newExt.rangeStart + newExt.rangeWidth, false);
            hasZoomed = true;
        }
        else { // Reset zoom
            axis.setExtremes(void 0, void 0, false);
        }
    }
    return hasZoomed;
};
/**
 * @private
 */
const zoomBy = function (chart, howMuch, xAxis, yAxis, mouseX, mouseY, options) {
    const type = pick(options.type, chart.zooming.type, ''), zoomX = /x/.test(type), zoomY = /y/.test(type);
    let centerXArg = xAxis.toValue(mouseX), centerYArg = yAxis.toValue(mouseY);
    if (chart.inverted) {
        const emulateRoof = yAxis.pos + yAxis.len;
        // Get the correct values
        centerXArg = xAxis.toValue(mouseY);
        centerYArg = yAxis.toValue(mouseX);
        // Swapping x and y for simplicity when chart is inverted.
        const tmp = mouseX;
        mouseX = mouseY;
        mouseY = emulateRoof - tmp + yAxis.pos;
    }
    const hasZoomedX = zoomX && zoomOnDirection(chart, xAxis, mouseX, howMuch, centerXArg), hasZoomedY = zoomY && zoomOnDirection(chart, yAxis, mouseY, howMuch, centerYArg), hasZoomed = hasZoomedX || hasZoomedY;
    if (hasZoomed) {
        chart.redraw(false);
    }
    return hasZoomed;
};
/**
 * @private
 */
function onAfterGetContainer() {
    const chart = this, wheelZoomOptions = optionsToObject(chart.zooming.mouseWheel);
    if (wheelZoomOptions.enabled) {
        addEvent(this.container, 'wheel', (e) => {
            e = this.pointer.normalize(e);
            const allowZoom = !chart.pointer.inClass(e.target, 'highcharts-no-mousewheel');
            // Firefox uses e.detail, WebKit and IE uses deltaX, deltaY, deltaZ.
            if (chart.isInsidePlot(e.chartX - chart.plotLeft, e.chartY - chart.plotTop) && allowZoom) {
                const wheelSensitivity = wheelZoomOptions.sensitivity || 1.1, delta = e.detail || ((e.deltaY || 0) / 120), xAxisCoords = getAssignedAxis(this.pointer.getCoordinates(e).xAxis), yAxisCoords = getAssignedAxis(this.pointer.getCoordinates(e).yAxis);
                const hasZoomed = zoomBy(chart, Math.pow(wheelSensitivity, delta), xAxisCoords ? xAxisCoords.axis : chart.xAxis[0], yAxisCoords ? yAxisCoords.axis : chart.yAxis[0], e.chartX, e.chartY, wheelZoomOptions);
                // Prevent page scroll
                if (hasZoomed && e.preventDefault) {
                    e.preventDefault();
                }
            }
        });
    }
}
/**
 * @private
 */
function compose(ChartClass) {
    if (composedClasses.indexOf(ChartClass) === -1) {
        composedClasses.push(ChartClass);
        addEvent(ChartClass, 'afterGetContainer', onAfterGetContainer);
    }
}
/* *
 *
 *  Default Export
 *
 * */
const MouseWheelZoomComposition = {
    compose
};
export default MouseWheelZoomComposition;
/* *
 *
 *  API Options
 *
 * */
/**
 * The mouse wheel zoom is a feature included in Highcharts Stock, but is also
 * available for Highcharts Core as a module. Zooming with the mouse wheel is
 * enabled by default in Highcharts Stock. In Highcharts Core it is enabled if
 * [chart.zooming.type](chart.zooming.type) is set. It can be disabled by
 * setting this option to `false`.
 *
 * @type      {boolean|object}
 * @since 11.1.0
 * @requires  modules/mouse-wheel-zoom
 * @sample    {highcharts} highcharts/mouse-wheel-zoom/enabled
 *            Enable or disable
 * @sample    {highstock} stock/mouse-wheel-zoom/enabled
 *            Enable or disable
 * @apioption chart.zooming.mouseWheel
 */
/**
 * Zooming with the mouse wheel can be disabled by setting this option to
 * `false`.
 *
 * @type      {boolean}
 * @default   true
 * @since 11.1.0
 * @requires  modules/mouse-wheel-zoom
 * @apioption chart.zooming.mouseWheel.enabled
 */
/**
 * Adjust the sensitivity of the zoom. Sensitivity of mouse wheel or trackpad
 * scrolling. `1` is no sensitivity, while with `2`, one mouse wheel delta will
 * zoom in `50%`.
 *
 * @type      {number}
 * @default   1.1
 * @since 11.1.0
 * @requires  modules/mouse-wheel-zoom
 * @sample    {highcharts} highcharts/mouse-wheel-zoom/sensitivity
 *            Change mouse wheel zoom sensitivity
 * @sample    {highstock} stock/mouse-wheel-zoom/sensitivity
 *            Change mouse wheel zoom sensitivity
 * @apioption chart.zooming.mouseWheel.sensitivity
 */
/**
 * Decides in what dimensions the user can zoom scrolling the wheel. Can be one
 * of `x`, `y` or `xy`. In Highcharts Core, if not specified here, it will
 * inherit the type from [chart.zooming.type](chart.zooming.type). In Highcharts
 * Stock, it defaults to `x`.
 *
 * Note that particularly with mouse wheel in the y direction, the zoom is
 * affected by the default [yAxis.startOnTick](#yAxis.startOnTick) and
 * [endOnTick]((#yAxis.endOnTick)) settings. In order to respect these settings,
 * the zoom level will adjust after the user has stopped zooming. To prevent
 * this, consider setting `startOnTick` and `endOnTick` to `false`.
 *
 * @type      {string}
 * @default   {highcharts} undefined
 * @default   {highstock} x
 * @validvalue ["x", "y", "xy"]
 * @since 11.1.0
 * @requires  modules/mouse-wheel-zoom
 * @apioption chart.zooming.mouseWheel.type
 */
(''); // Keeps doclets above in JS file
