import { DAY_MONTH_FORMATER, TIME_FORMATER } from '../date/FormattedDateTime';
import { chartColors } from '../../style/theme';
export const getRoundReferenceValue = (value) => {
    if (value <= 0)
        return 10; // Default for zero or negative values
    // Get the magnitude (10^n where n is the number of digits - 1)
    const magnitude = Math.pow(10, Math.floor(Math.log10(value)));
    // Normalized value between 1 and 10
    const normalized = value / magnitude;
    // Round to nice numbers based on normalized value
    if (normalized <= 1)
        return magnitude;
    if (normalized <= 2.5)
        return 2.5 * magnitude;
    if (normalized <= 5)
        return 5 * magnitude;
    return 10 * magnitude;
};
export const getMaxBarValue = (data, stacked) => {
    const values = data.map((item) => {
        // If stacked, we need to filter out category and sum the values in the same object
        if (stacked) {
            // Get objects keys except category
            const filterOutCategory = Object.keys(item).filter((key) => key !== 'category');
            // Sum the values in the same object (corresponding to one bar) based on the keys
            const sumValues = filterOutCategory.reduce((acc, curr) => {
                return acc + Number(item[curr]);
            }, 0);
            return sumValues;
        }
        //filter out the category key
        const numberValues = Object.keys(item)
            .filter((key) => key !== 'category')
            .map((key) => Number(item[key]));
        // Get the max value among the values in the object (corresponding to one bar)
        return Math.max(...numberValues);
    });
    return Math.max(...values);
};
/**
 * Generates time ranges between start and end dates based on the given interval
 * @param startDate - Start date
 * @param endDate - End date
 * @param interval - Interval in milliseconds
 * @returns Array of time ranges with start and end properties as Date objects
 */
const generateTimeRanges = (startDate, endDate, interval) => {
    const ranges = [];
    if (!startDate || !endDate || !interval) {
        return ranges;
    }
    let currentDate = new Date(startDate.getTime());
    while (currentDate.getTime() <= endDate.getTime()) {
        const rangeEnd = new Date(currentDate.getTime() + interval);
        ranges.push({
            start: new Date(currentDate.getTime()),
            end: rangeEnd,
        });
        currentDate = new Date(currentDate.getTime() + interval);
    }
    return ranges;
};
/**
 * Formats a date based on the interval
 * @param date - Date object
 * @param interval - Interval in milliseconds
 * @returns Formatted string
 */
const formatDate = (date, interval) => {
    if (interval > 24 * 60 * 60 * 1000) {
        return (DAY_MONTH_FORMATER.format(date).replace(/[ ,]/g, '') +
            ' ' +
            TIME_FORMATER.format(date));
    }
    else if (interval === 24 * 60 * 60 * 1000) {
        // Daily or longer intervals - use day format
        return DAY_MONTH_FORMATER.format(date).replace(/[ ,]/g, '');
    }
    else if (interval >= 60 * 1000) {
        //Handle hourly and minute intervals - use minute format
        return TIME_FORMATER.format(date);
    }
    else {
        // Second intervals or less - use full timestamp
        return date.toISOString();
    }
};
/**
 * Finds the time range that contains the given date
 * @param date - Data point date
 * @param ranges - Array of time ranges
 * @returns The range that contains the date, or null if not found
 */
const findRangeForDate = (date, ranges) => {
    const timestamp = date.getTime();
    return (ranges.find((range) => timestamp >= range.start.getTime() && timestamp < range.end.getTime()) || null);
};
/**
 * Transforms time-based data into chart format
 */
export const transformTimeData = (bars, type, barDataKeys) => {
    const timeRanges = generateTimeRanges(type.timeRange.startDate, type.timeRange.endDate, type.timeRange.interval);
    const categoryMap = new Map();
    // Initialize all ranges with zeros
    timeRanges.forEach((range) => {
        const categoryDisplay = formatDate(range.start, type.timeRange.interval);
        const initialData = {
            category: categoryDisplay,
        };
        barDataKeys.forEach((dataKey) => {
            initialData[dataKey] = 0;
        });
        categoryMap.set(range.start.getTime(), initialData);
    });
    // Populate with actual data
    bars.forEach((bar) => {
        bar.data.forEach(([dateValue, value]) => {
            // Convert to Date if it's not already a Date object
            const date = dateValue instanceof Date
                ? dateValue
                : new Date(dateValue);
            const range = findRangeForDate(date, timeRanges);
            if (range) {
                const existingData = categoryMap.get(range.start.getTime());
                existingData[bar.label] = value;
            }
        });
    });
    return Array.from(categoryMap.values());
};
/**
 * Transforms category-based data into chart format
 */
export const transformCategoryData = (bars, barDataKeys) => {
    const categoryMap = new Map();
    bars.forEach((bar) => {
        bar.data.forEach(([key, value]) => {
            const categoryKey = String(key);
            if (!categoryMap.has(categoryKey)) {
                const newData = {
                    category: categoryKey,
                };
                barDataKeys.forEach((dataKey) => {
                    newData[dataKey] = 0;
                });
                categoryMap.set(categoryKey, newData);
            }
            const existingData = categoryMap.get(categoryKey);
            existingData[bar.label] = value;
        });
    });
    return Array.from(categoryMap.values());
};
/**
 * Applies custom sorting to chart data
 */
export const applySortingToData = (data, barDataKeys, defaultSort) => {
    const points = data.map((item) => {
        const point = { category: item.category };
        barDataKeys.forEach((dataKey) => {
            point[dataKey] = Number(item[dataKey]) || 0;
        });
        return point;
    });
    points.sort(defaultSort);
    return points.map((point) => {
        const dataItem = {
            category: point.category,
        };
        barDataKeys.forEach((dataKey) => {
            dataItem[dataKey] = point[dataKey];
        });
        return dataItem;
    });
};
const getRechartsBarsAndBarDataKeys = (bars, colorSet, stacked) => {
    const rechartsBars = [];
    const barDataKeys = [];
    bars.forEach((bar) => {
        const colorName = colorSet[bar.label];
        const rechartsBar = {
            dataKey: bar.label,
            fill: chartColors[colorName] || colorName,
            stackId: stacked ? 'stacked' : undefined,
        };
        rechartsBars.push(rechartsBar);
        barDataKeys.push(bar.label);
    });
    return {
        rechartsBars,
        barDataKeys,
    };
};
/**
 * Converts prometheus data to recharts data format
 * @param bars - The bars to convert
 * @param type - The chart type (category or time)
 * @returns Recharts data format
 */
export const formatPrometheusDataToRechartsDataAndBars = (bars, type, colorSet, stacked, defaultSort) => {
    const { rechartsBars, barDataKeys } = getRechartsBarsAndBarDataKeys(bars, colorSet, stacked);
    let data = type !== 'category' && type.type === 'time'
        ? transformTimeData(bars, type, barDataKeys)
        : transformCategoryData(bars, barDataKeys);
    if (type === 'category' && defaultSort) {
        data = applySortingToData(data, barDataKeys, defaultSort);
    }
    const sortedRechartsBars = sortStackedBars(rechartsBars, data, stacked);
    return {
        rechartsBars: sortedRechartsBars,
        data,
    };
};
export const computeUnitLabelAndRoundReferenceValue = (data, maxValue, unitRange) => {
    if (!unitRange) {
        const roundReferenceValue = getRoundReferenceValue(maxValue);
        return { unitLabel: '', roundReferenceValue, rechartsData: data };
    }
    const { valueBase, unitLabel } = getUnitLabel(unitRange !== null && unitRange !== void 0 ? unitRange : [], maxValue);
    const topValue = Math.ceil(maxValue / valueBase / 10) * 10;
    const roundReferenceValue = getRoundReferenceValue(topValue);
    const rechartsData = data.map((dataPoint) => {
        const normalizedDataPoint = { ...dataPoint };
        Object.entries(dataPoint).forEach(([key, value]) => {
            if (key !== 'category' && typeof value === 'number') {
                normalizedDataPoint[key] = value / valueBase;
            }
        });
        return normalizedDataPoint;
    });
    return { unitLabel, roundReferenceValue, rechartsData };
};
/**
 * Return the unit label base on the current dataset, and the valueBase which is used to convert the data
 * @param {any} unitRange
 * @param {any} maxValue the maximum value among the data set
 * @returns {any}
 */
export function getUnitLabel(unitRange, maxValue) {
    // 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,
            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,
    };
}
// Sort stacked bars by their average values in descending order
// This ensures the largest bars appear at the bottom of the stack
export const sortStackedBars = (rechartsBars, data, stacked) => {
    if (!stacked) {
        return rechartsBars;
    }
    const barAverages = rechartsBars.map((bar) => {
        const values = data
            .map((item) => Number(item[bar.dataKey]) || 0)
            .filter((value) => !isNaN(value));
        const average = values.length > 0 ? values.reduce((a, b) => a + b, 0) / values.length : 0;
        return { ...bar, average };
    });
    // Sort by average in descending order (largest first, which will be at bottom in stack)
    barAverages.sort((a, b) => b.average - a.average);
    // Remove the average property and keep only the bar data
    return barAverages.map(({ average, ...bar }) => bar);
};
export const renderTooltipContent = (props, tooltip, hoveredValue) => {
    const { active, payload, label } = props;
    if (!active || !tooltip) {
        return null;
    }
    const tooltipValues = payload.map((item) => ({
        label: item.name,
        value: item.value,
        isHovered: item.name === hoveredValue,
    }));
    const currentPoint = {
        category: label,
        values: tooltipValues,
    };
    return tooltip(currentPoint);
};
export const useChartData = (bars, type, colorSet, stacked, defaultSort, unitRange) => {
    const { data, rechartsBars } = formatPrometheusDataToRechartsDataAndBars(bars, type, colorSet, stacked, defaultSort);
    const maxValue = getMaxBarValue(data, stacked);
    const { unitLabel, roundReferenceValue, rechartsData } = computeUnitLabelAndRoundReferenceValue(data, maxValue, unitRange);
    return {
        rechartsBars,
        unitLabel,
        roundReferenceValue,
        rechartsData,
    };
};
