import { NAN_STRING } from '../../constants';
import { formatISONumber } from '../../../utils';
/* -------------------------------------------------------------------------- */
/*                                  constants                                 */
/* -------------------------------------------------------------------------- */
export const maxWidthTooltip = { maxWidth: '20rem' };
/* -------------------------------------------------------------------------- */
/*                               utils functions                              */
/* -------------------------------------------------------------------------- */
/**
 * Get the appropriate rounding increment based on value magnitude.
 * - For values < 5 * magnitude: use half magnitude (finer granularity)
 * - For values >= 5 * magnitude: use full magnitude
 *
 * Examples:
 * - 150 → increment 50 (150 < 500, so use 100/2)
 * - 550 → increment 100 (550 >= 500, so use 100)
 * - 1500 → increment 500 (1500 < 5000, so use 1000/2)
 * - 5500 → increment 1000 (5500 >= 5000, so use 1000)
 */
const getIncrement = (value) => {
    if (value < 10)
        return 1;
    const magnitude = Math.pow(10, Math.floor(Math.log10(value)));
    // If value is in lower half of magnitude range, use half magnitude
    if (value < 5 * magnitude) {
        return magnitude / 2;
    }
    // Otherwise use full magnitude
    return magnitude;
};
/**
 * Round a value to a nice number for chart display
 * Used by Barchart and LineTimeSerieChart for Y-axis scaling
 */
export const getRoundReferenceValue = (value) => {
    if (value <= 0)
        return 1; // Default for zero or negative values
    // Buffer the value by 10% to avoid being too close to the edge of the chart
    const bufferedValue = value * 1.1;
    if (value >= 10) {
        const increment = getIncrement(value);
        const remainder = value % increment;
        const roundedDown = value - remainder;
        const roundedUp = roundedDown + increment;
        // If remainder is less than half the increment, round down
        if (remainder < increment / 2) {
            return roundedDown;
        }
        // If rounding up would exceed the buffered max, round down
        if (roundedUp > bufferedValue) {
            return roundedDown;
        }
        // Otherwise, round up
        return roundedUp;
    }
    // For values < 10, use the magnitude-based approach
    const magnitude = Math.pow(10, Math.floor(Math.log10(value)));
    const remainder = bufferedValue % magnitude;
    return remainder === 0 ? bufferedValue : bufferedValue - remainder;
};
/**
 * Generate tick values for Y-axis
 * Used by Barchart and LineTimeSerieChart
 */
export const getTicks = (topValue, isSymmetrical) => {
    const possibleTickNumbers = [4, 3];
    const numberOfTicks = possibleTickNumbers.find((number) => topValue % (number - 1) === 0) || 3; // Default to 3 ticks if no match
    const tickInterval = topValue / (numberOfTicks - 1);
    const ticks = Array.from({ length: numberOfTicks }, (_, index) => index * tickInterval);
    if (isSymmetrical) {
        // Create negative ticks in order without 0
        const negativeTicks = Array.from({ length: numberOfTicks - 1 }, (_, index) => (index - numberOfTicks + 1) * tickInterval);
        ticks.unshift(...negativeTicks);
    }
    return ticks;
};
/**
 * Return the unit label based on the current dataset, and the valueBase which is used to convert the data
 * Used by LineTimeSerieChart
 * @param unitRange - Array of threshold and label pairs
 * @param maxValue - The maximum value among the data set
 * @returns Object with valueBase and unitLabel
 */
export function getUnitLabel(unitRange, maxValue) {
    if (!unitRange || unitRange.length === 0) {
        return {
            valueBase: 1,
            unitLabel: undefined,
        };
    }
    // first sort the unitRange
    unitRange.sort((unitA, unitB) => {
        return unitA.threshold - unitB.threshold;
    });
    let index = unitRange.findIndex((range) => range.threshold > maxValue);
    // last unit
    if (index === -1) {
        index = unitRange.length;
    }
    if (index === 0) {
        return {
            valueBase: unitRange[index].threshold || 1,
            unitLabel: unitRange[index].label,
        };
    }
    return {
        // if the threshold is 0, we use 1 as the value base to avoid division by 0
        valueBase: unitRange[index - 1].threshold || 1,
        unitLabel: unitRange[index - 1].label,
    };
}
/**
 * Computes unit label and normalizes chart data based on unit range.
 * This is shared logic used by both Barchart and LineTimeSerieChart.
 *
 * @param data - Chart data to normalize
 * @param maxValue - Maximum value in the dataset
 * @param unitRange - Optional unit range configuration for automatic scaling
 * @param excludeKey - Key to exclude from normalization (e.g., 'category' for Barchart, 'timestamp' for LineTimeSerieChart)
 * @returns Object containing unit label, top value for Y-axis, and normalized data
 */
export const normalizeChartDataWithUnits = (data, maxValue, unitRange, excludeKey) => {
    // If no unit range provided, just calculate top value without unit conversion
    if (!unitRange || unitRange.length === 0) {
        const topValue = getRoundReferenceValue(maxValue);
        return {
            unitLabel: undefined,
            topValue,
            rechartsData: data,
            topDomain: maxValue * 1.1,
        };
    }
    // Get appropriate unit and value base for normalization
    const { valueBase, unitLabel } = getUnitLabel(unitRange, maxValue);
    const basedValue = maxValue / valueBase;
    const topValue = getRoundReferenceValue(basedValue);
    const topDomain = basedValue * 1.1;
    // Normalize all numeric values by dividing by valueBase
    const rechartsData = data.map((dataPoint) => {
        const normalizedDataPoint = {
            ...dataPoint,
        };
        Object.entries(dataPoint).forEach(([key, value]) => {
            if (key !== excludeKey && typeof value === 'number') {
                normalizedDataPoint[key] = value / valueBase;
            }
        });
        return normalizedDataPoint;
    });
    return { unitLabel, topValue, rechartsData, topDomain };
};
/**
 * This function manually adds the missing data points with `null` value caused by downtime of the VMs
 * Missing data points are only added when the gap between consecutive data points is bigger than 2 intervals
 * Used by LineTimeSerieChart and Sparkline
 *
 * @param originalValues - The array of the data points are already sorted according to the time series
 * @param startingTimeStamp - The starting timestamp in seconds
 * @param sampleDuration - The time span value in seconds
 * @param sampleInterval - The time difference between two data points in seconds
 */
export function addMissingDataPoint(originalValues, startingTimeStamp, sampleDuration, sampleInterval) {
    if (!originalValues ||
        startingTimeStamp === undefined ||
        !sampleDuration ||
        !sampleInterval ||
        startingTimeStamp < 0 ||
        sampleDuration <= 0 ||
        sampleInterval <= 0) {
        return [];
    }
    // If there are no original values, generate placeholder timestamps for the entire duration
    if (originalValues.length === 0) {
        const newValues = [];
        for (let i = startingTimeStamp; i < startingTimeStamp + sampleDuration; i += sampleInterval) {
            newValues.push([i, NAN_STRING]);
        }
        return newValues;
    }
    const newValues = [];
    // add missing data points for the starting time
    for (let i = startingTimeStamp; i < originalValues[0][0]; i += sampleInterval) {
        newValues.push([i, NAN_STRING]);
    }
    // Process all but the last element
    for (let i = 0; i < originalValues.length - 1; i++) {
        if (originalValues[i][0] < startingTimeStamp ||
            originalValues[i][0] > startingTimeStamp + sampleDuration) {
            continue;
        }
        // Always add the current data point
        newValues.push(originalValues[i]);
        const currentTimestamp = originalValues[i][0];
        const nextTimestamp = originalValues[i + 1][0];
        const gap = nextTimestamp - currentTimestamp;
        // Calculate how many missing points to add
        const missingIntervals = Math.floor(gap / sampleInterval) - 1;
        // Add missing data points with NAN_STRING (only executes if missingIntervals > 0)
        for (let j = 1; j <= missingIntervals; j++) {
            const missingTimestamp = currentTimestamp + j * sampleInterval;
            newValues.push([missingTimestamp, NAN_STRING]);
        }
    }
    // Add the last element
    newValues.push(originalValues[originalValues.length - 1]);
    // add missing data points for the ending time
    for (let i = originalValues[originalValues.length - 1][0] + sampleInterval; i < startingTimeStamp + sampleDuration; i += sampleInterval) {
        newValues.push([i, NAN_STRING]);
    }
    return newValues;
}
/**
 * Date Format Reference Table
 * ============================
 *
 * This table documents the date formatting logic used across charts:
 * - X-Axis Format: Used for chart axis labels (formatXAxisDate + LineTimeSerieChart's formatXAxisLabel)
 * - Tooltip Format: Used for tooltip headers (getTooltipDateFormat)
 *
 * ┌─────────────────┬──────────────┬────────────────────────┬──────────────────┬──────────────────────────────────────────┬───────────────────────────┐
 * │ Interval        │ Duration (s) │ X-axis format          │ Example (X-axis) │ Tooltip format                           │ Example (Tooltip)         │
 * ├─────────────────┼──────────────┼────────────────────────┼──────────────────┼──────────────────────────────────────────┼───────────────────────────┤
 * │ Last hour       │ ≤ 3,600      │ HH:MM                  │ 14:05            │ DD MMM HH:MM:SS                          │ 01 Oct 00:15:00           │
 * │ Last 24 hours   │ ≤ 86,400     │ HH:MM                  │ 23:00            │ DD MMM HH:MM                             │ 01 Oct 00:15              │
 * │ Last 7 days     │ ≤ 604,800    │ DD MMM HH:MM           │ 27 Sep 10:12     │ DD MMM HH:MM                             │ 01 Oct 00:15              │
 * │ Long term       │ > 604,800    │ DDMMMYY                │ 15Sep25          │ DD MMM YYYY HH:MM                        │ 01 Oct 2025 00:15         │
 * └─────────────────┴──────────────┴────────────────────────┴──────────────────┴──────────────────────────────────────────┴───────────────────────────┘
 *
 * Note: Duration is in seconds. Some intervals share the same format, which is why both functions only have 3 cases.
 */
/**
 * Get the format of the date based on the duration
 * Used by Barchart CustomTick component
 * @param duration - Duration in seconds
 * @returns Formatted string type
 */
export const formatXAxisDate = (duration) => {
    if (duration <= 24 * 60 * 60) {
        return 'time';
    }
    else if (duration <= 7 * 24 * 60 * 60) {
        return 'day-month-abbreviated';
    }
    else {
        return 'chart-long-term-date';
    }
};
/**
 * Get the format of the date based on the duration
 * Used by TooltipHeader component
 * @param duration - Duration in seconds
 * @returns Formatted string type
 */
export const getTooltipDateFormat = (duration) => {
    if (duration <= 60 * 60) {
        return 'day-month-abbreviated-hour-minute-second';
    }
    else if (duration <= 7 * 24 * 60 * 60) {
        return 'day-month-abbreviated-hour-minute';
    }
    else {
        return 'day-month-abbreviated-year-hour-minute';
    }
};
/**
 * Formats a tick value for chart Y-axis display.
 * - Fixed decimals for alignment when topValue < 1 (e.g., 0.1 → 0.10)
 * - Compact notation for large values (>= 10k)
 */
export const formatTickValue = (value, topValue) => {
    const decimals = topValue < 1 ? Math.ceil(-Math.log10(topValue)) + 1 : 2;
    return formatISONumber(value, {
        decimals,
        fixedDecimals: topValue < 1,
        compact: topValue >= 10000,
    });
};
