/* *
 *
 *  (c) 2010-2026 Highsoft AS
 *  Author: Torstein Hønsi
 *
 *  Integration of this software requires a license.
 *  - For commercial use, see www.highcharts.com/license
 *  - For non-commercial, see www.highcharts.com/license-eula
 *
 *
 * */
'use strict';
import TimeBase from '../Shared/TimeBase.js';
import { defined, extend } from '../Shared/Utilities.js';
import { timeUnits } from './Utilities.js';
/* *
 *
 *  Constants
 *
 * */
class Time extends TimeBase {
    getBoundaryTicks(tickPositions, unitRange, visibleMin, visibleMax) {
        const boundaryTicks = {};
        // No boundary ticks for year range.
        if (unitRange === timeUnits.year) {
            return boundaryTicks;
        }
        const hasVisibleRange = defined(visibleMin) && defined(visibleMax), needsHourBoundary = unitRange < timeUnits.hour, needsDayBoundary = unitRange < timeUnits.day, needsMonthBoundary = unitRange < timeUnits.month, tickAlignment = (unitRange >= timeUnits.hour ?
            900000 : unitRange >= timeUnits.minute ?
            60000 : unitRange >= timeUnits.second ?
            1000 : false);
        // Handle boundary ticks. Use a reasonable dropout threshold
        // to prevent looping over dense data grouping (#6156).
        if (tickPositions.length < 10000) {
            let isFirstVisibleTick = true;
            for (let i = 0; i < tickPositions.length; i++) {
                const t = tickPositions[i];
                // Loop only over visible ticks.
                if (hasVisibleRange) {
                    if (t < visibleMin) {
                        continue;
                    }
                    if (t > visibleMax) {
                        break;
                    }
                }
                // Mark first visible tick as boundary, if timeUnit is month or
                // hour.
                if (isFirstVisibleTick) {
                    if (unitRange === timeUnits.month) {
                        boundaryTicks[t] = 'year';
                        isFirstVisibleTick = false;
                        continue;
                    }
                    if (unitRange === timeUnits.hour) {
                        boundaryTicks[t] = 'day';
                        isFirstVisibleTick = false;
                        continue;
                    }
                }
                // Skip misaligned ticks to save performance.
                if (tickAlignment && t % tickAlignment !== 0) {
                    isFirstVisibleTick = false;
                    continue;
                }
                const [, // Unused 'year' var
                month, day, hours, minutes, seconds, milliseconds] = this.toParts(t);
                const isMidnight = !hours && !minutes && !seconds && !milliseconds;
                if (needsHourBoundary && minutes === 0) {
                    boundaryTicks[t] = 'hour';
                }
                if (needsDayBoundary && isMidnight) {
                    boundaryTicks[t] = 'day';
                }
                if (needsMonthBoundary && day === 1 && isMidnight) {
                    boundaryTicks[t] = 'month';
                }
                if (month === 0 && day === 1 && isMidnight) {
                    boundaryTicks[t] = 'year';
                }
                isFirstVisibleTick = false;
            }
        }
        return boundaryTicks;
    }
    /**
     * Return an array with time positions distributed on round time values
     * right and right after min and max. Used in datetime axes as well as for
     * grouping data on a datetime axis.
     *
     * @function Highcharts.Time#getTimeTicks
     *
     * @param {Highcharts.TimeNormalizedObject} normalizedInterval
     *        The interval in axis values (ms) and the count
     *
     * @param {number} [min]
     *        The minimum in axis values
     *
     * @param {number} [max]
     *        The maximum in axis values
     *
     * @param {number} [startOfWeek=1]
     *
     * @return {Highcharts.AxisTickPositionsArray}
     * Time positions
     */
    getTimeTicks(normalizedInterval, min, max, startOfWeek) {
        const time = this, tickPositions = [], { count = 1, unitRange } = normalizedInterval, visibleMin = min, visibleMax = max;
        let [year, month, dayOfMonth, hours, minutes, seconds] = time.toParts(min), milliseconds = (min || 0) % 1000, variableDayLength;
        startOfWeek ?? (startOfWeek = 1);
        if (defined(min)) { // #1300
            milliseconds = unitRange >= timeUnits.second ?
                0 : // #3935
                count * Math.floor(milliseconds / count);
            if (unitRange >= timeUnits.second) { // Second
                seconds = unitRange >= timeUnits.minute ?
                    0 : // #3935
                    count * Math.floor(seconds / count);
            }
            if (unitRange >= timeUnits.minute) { // Minute
                minutes = unitRange >= timeUnits.hour ?
                    0 :
                    count * Math.floor(minutes / count);
            }
            if (unitRange >= timeUnits.hour) { // Hour
                hours = unitRange >= timeUnits.day ?
                    0 :
                    count * Math.floor(hours / count);
            }
            if (unitRange >= timeUnits.day) { // Day
                dayOfMonth = unitRange >= timeUnits.month ?
                    1 :
                    Math.max(1, count * Math.floor(dayOfMonth / count));
            }
            if (unitRange >= timeUnits.month) { // Month
                month = unitRange >= timeUnits.year ? 0 :
                    count * Math.floor(month / count);
            }
            if (unitRange >= timeUnits.year) { // Year
                year -= year % count;
            }
            // Week is a special case that runs outside the hierarchy
            if (unitRange === timeUnits.week) {
                if (count) {
                    min = time.makeTime(year, month, dayOfMonth, hours, minutes, seconds, milliseconds);
                }
                // Get start of current week, independent of count
                const weekday = this.dateTimeFormat({
                    timeZone: this.timezone,
                    weekday: 'narrow'
                }, min, 'es'), 
                // Spanish weekday index
                weekdayNo = 'DLMXJVS'.indexOf(weekday);
                dayOfMonth += -weekdayNo + startOfWeek +
                    // We don't want to skip days that are before
                    // startOfWeek (#7051)
                    (weekdayNo < startOfWeek ? -7 : 0);
            }
            min = time.makeTime(year, month, dayOfMonth, hours, minutes, seconds, milliseconds);
            // Handle local timezone offset
            if (time.variableTimezone && defined(max)) {
                // Detect whether we need to take the DST crossover into
                // consideration. If we're crossing over DST, the day length may
                // be 23h or 25h and we need to compute the exact clock time for
                // each tick instead of just adding hours. This comes at a cost,
                // so first we find out if it is needed (#4951).
                variableDayLength = (
                // Long range, assume we're crossing over.
                max - min > 4 * timeUnits.month ||
                    // Short range, check if min and max are in different time
                    // zones.
                    time.getTimezoneOffset(min) !==
                        time.getTimezoneOffset(max));
            }
            // Iterate and add tick positions at appropriate values
            let t = min, i = 1;
            while (t < max) {
                tickPositions.push(t);
                // Increase the years
                if (unitRange === timeUnits.year) {
                    t = time.makeTime(year + i * count, 0);
                    // Increase the months
                }
                else if (unitRange === timeUnits.month) {
                    t = time.makeTime(year, month + i * count);
                    // If we're using local time, the interval is not fixed as it
                    // jumps one hour at the DST crossover
                }
                else if (variableDayLength && (unitRange === timeUnits.day ||
                    unitRange === timeUnits.week)) {
                    t = time.makeTime(year, month, dayOfMonth +
                        i * count * (unitRange === timeUnits.day ? 1 : 7));
                }
                else if (variableDayLength &&
                    unitRange === timeUnits.hour &&
                    count > 1) {
                    // Make sure boundary ticks are preserved across DST (#6797,
                    // #7621)
                    t = time.makeTime(year, month, dayOfMonth, hours + i * count);
                    // Else, the interval is fixed and we use simple addition
                }
                else {
                    t += unitRange * count;
                }
                i++;
            }
            // Push the last time
            tickPositions.push(t);
        }
        tickPositions.info = extend(normalizedInterval, {
            boundaryTicks: this.getBoundaryTicks(tickPositions, unitRange, visibleMin, visibleMax),
            totalRange: unitRange * count
        });
        return tickPositions;
    }
}
/* *
 *
 * Default export
 *
 * */
export default Time;
