/**
 * Safe math operations to prevent division by zero and other edge cases
 */
class SafeMath {
    /**
     * Safe division that returns 0 when dividing by zero
     */
    static divide(numerator, denominator, fallback = 0) {
        if (denominator === 0 || !isFinite(denominator)) {
            return fallback;
        }
        const result = numerator / denominator;
        return isFinite(result) ? result : fallback;
    }
    /**
     * Safe percentage calculation
     */
    static percentage(value, total, fallback = 0) {
        return this.divide(value * 100, total, fallback);
    }
    /**
     * Safe Math.max that handles empty arrays
     */
    static max(values, fallback = 0) {
        if (!Array.isArray(values) || values.length === 0) {
            return fallback;
        }
        const filtered = values.filter(v => isFinite(v));
        return filtered.length > 0 ? Math.max(...filtered) : fallback;
    }
    /**
     * Safe Math.min that handles empty arrays
     */
    static min(values, fallback = 0) {
        if (!Array.isArray(values) || values.length === 0) {
            return fallback;
        }
        const filtered = values.filter(v => isFinite(v));
        return filtered.length > 0 ? Math.min(...filtered) : fallback;
    }
    /**
     * Safe scale calculation for chart positioning
     */
    static scale(value, min, max, targetMin = 0, targetMax = 1) {
        const range = max - min;
        if (range === 0) {
            return targetMin + (targetMax - targetMin) / 2; // Return middle of target range
        }
        const ratio = this.divide(value - min, range);
        return targetMin + ratio * (targetMax - targetMin);
    }
    /**
     * Clamp a value between min and max
     */
    static clamp(value, min, max) {
        return Math.min(Math.max(value, min), max);
    }
}
/**
 * Validates chart data array
 */
function validateChartData(data) {
    const errors = [];
    const warnings = [];
    // Check if data exists
    if (!data) {
        errors.push({
            type: 'data',
            message: 'Chart data is required but was not provided',
            field: 'data'
        });
        return { isValid: false, errors, warnings };
    }
    // Check if data is an array
    if (!Array.isArray(data)) {
        errors.push({
            type: 'data',
            message: 'Chart data must be an array',
            field: 'data'
        });
        return { isValid: false, errors, warnings };
    }
    // Check if data is empty
    if (data.length === 0) {
        warnings.push({
            type: 'data',
            message: 'Chart data array is empty',
            field: 'data'
        });
    }
    // Validate each data item
    data.forEach((item, index) => {
        if (!item || typeof item !== 'object') {
            errors.push({
                type: 'data',
                message: `Data item at index ${index} must be an object`,
                field: `data[${index}]`
            });
            return;
        }
        // Check for at least one numeric value
        const hasNumericValue = Object.values(item).some(value => typeof value === 'number' && isFinite(value));
        // Only warn about missing numeric values if there are no valid numeric values 
        // in the entire dataset. Individual items with null/undefined values are 
        // handled gracefully by chart components (e.g., using averages for bar charts)
        if (!hasNumericValue) {
            const allDataHasNumericValues = data.some(dataItem => dataItem && typeof dataItem === 'object' &&
                Object.values(dataItem).some(value => typeof value === 'number' && isFinite(value)));
            if (!allDataHasNumericValues) {
                warnings.push({
                    type: 'data',
                    message: `Data item at index ${index} has no valid numeric values`,
                    field: `data[${index}]`
                });
            }
        }
    });
    return { isValid: errors.length === 0, errors, warnings };
}
/**
 * Validates chart configuration
 */
function validateChartConfig(config) {
    const errors = [];
    const warnings = [];
    // Check if config exists
    if (!config) {
        errors.push({
            type: 'config',
            message: 'Chart config is required but was not provided',
            field: 'config'
        });
        return { isValid: false, errors, warnings };
    }
    // Check if config is an object
    if (typeof config !== 'object') {
        errors.push({
            type: 'config',
            message: 'Chart config must be an object',
            field: 'config'
        });
        return { isValid: false, errors, warnings };
    }
    // Check if config has any keys
    const configKeys = Object.keys(config);
    if (configKeys.length === 0) {
        warnings.push({
            type: 'config',
            message: 'Chart config is empty - no data series will be displayed',
            field: 'config'
        });
    }
    return { isValid: errors.length === 0, errors, warnings };
}
/**
 * Validates that data and config are compatible
 */
function validateDataConfigCompatibility(data, config) {
    const errors = [];
    const warnings = [];
    const configKeys = Object.keys(config);
    const dataKeys = data.length > 0 ? Object.keys(data[0]) : [];
    // Check if any config keys exist in data
    const validKeys = configKeys.filter(key => dataKeys.includes(key) &&
        data.some(item => typeof item[key] === 'number' && isFinite(item[key])));
    if (validKeys.length === 0) {
        errors.push({
            type: 'config',
            message: 'No config keys match valid numeric data fields',
            field: 'config'
        });
    }
    // Warn about config keys that don't exist in data
    const missingKeys = configKeys.filter(key => !dataKeys.includes(key));
    missingKeys.forEach(key => {
        warnings.push({
            type: 'config',
            message: `Config key "${key}" not found in data`,
            field: `config.${key}`
        });
    });
    return { isValid: errors.length === 0, errors, warnings };
}
/**
 * Sanitizes chart data by removing invalid entries and normalizing values
 */
function sanitizeChartData(data) {
    if (!Array.isArray(data)) {
        return [];
    }
    return data
        .filter(item => item && typeof item === 'object')
        .map(item => {
        const sanitized = {};
        Object.entries(item).forEach(([key, value]) => {
            if (typeof value === 'number') {
                // Replace NaN and Infinity with null
                sanitized[key] = isFinite(value) ? value : null;
            }
            else if (typeof value === 'string') {
                // Keep string values (like names, categories)
                sanitized[key] = value;
            }
            else if (value === null || value === undefined) {
                // Keep explicit null/undefined
                sanitized[key] = null;
            }
            // Skip other types (objects, arrays, etc.)
        });
        return sanitized;
    });
}
/**
 * Calculates safe min/max values for chart scales using nice tick boundaries.
 */
function calculateSafeScaleRange(data, config, options = {}) {
    const { minValue: propMin, maxValue: propMax, includeZero = true, padding = 0.1, symmetric = false } = options;
    // If both min and max are provided, use them
    if (propMin !== undefined && propMax !== undefined) {
        return {
            min: propMin,
            max: propMax,
            hasValidData: true
        };
    }
    const configKeys = Object.keys(config);
    const allValues = [];
    // Extract all numeric values
    data.forEach(item => {
        configKeys.forEach(key => {
            const value = item[key];
            if (typeof value === 'number' && isFinite(value)) {
                allValues.push(value);
            }
        });
    });
    // No valid data found
    if (allValues.length === 0) {
        return {
            min: propMin !== null && propMin !== void 0 ? propMin : 0,
            max: propMax !== null && propMax !== void 0 ? propMax : 100,
            hasValidData: false
        };
    }
    let dataMin = propMin !== null && propMin !== void 0 ? propMin : SafeMath.min(allValues);
    let dataMax = propMax !== null && propMax !== void 0 ? propMax : SafeMath.max(allValues);
    // Include zero if requested
    if (includeZero) {
        dataMin = Math.min(dataMin, 0);
        dataMax = Math.max(dataMax, 0);
    }
    // Add padding if min and max are the same
    if (dataMin === dataMax) {
        const absValue = Math.abs(dataMin);
        const paddingValue = absValue > 0 ? absValue * padding : 1;
        dataMin -= paddingValue;
        dataMax += paddingValue;
    }
    // Make symmetric when explicitly requested
    if (symmetric && dataMin < 0 && dataMax > 0) {
        const absMax = Math.max(Math.abs(dataMin), Math.abs(dataMax));
        dataMin = -absMax;
        dataMax = absMax;
    }
    // Use nice tick generation to compute boundaries
    const { niceMin, niceMax } = generateNiceTicks(dataMin, dataMax, 5);
    return {
        min: propMin !== null && propMin !== void 0 ? propMin : niceMin,
        max: propMax !== null && propMax !== void 0 ? propMax : niceMax,
        hasValidData: true
    };
}
/**
 * Heckbert's "nice numbers" algorithm.
 * Returns a "nice" number approximately equal to x.
 * When round=true, rounds to nearest nice number; when false, ceiling.
 * Nice numbers are from the {1, 2, 5} × 10^n sequence.
 */
function niceNum(x, round = true) {
    if (x === 0)
        return 0;
    const negative = x < 0;
    const abs = Math.abs(x);
    const exponent = Math.floor(Math.log10(abs));
    const fraction = abs / Math.pow(10, exponent);
    let niceFraction;
    if (round) {
        if (fraction < 1.5)
            niceFraction = 1;
        else if (fraction < 3)
            niceFraction = 2;
        else if (fraction < 7)
            niceFraction = 5;
        else
            niceFraction = 10;
    }
    else {
        if (fraction <= 1)
            niceFraction = 1;
        else if (fraction <= 2)
            niceFraction = 2;
        else if (fraction <= 5)
            niceFraction = 5;
        else
            niceFraction = 10;
    }
    const result = niceFraction * Math.pow(10, exponent);
    return negative ? -result : result;
}
/**
 * Generate uniformly-spaced "nice" tick values for a given data range.
 * Uses Heckbert's algorithm to produce ticks from the {1,2,5}×10^n sequence.
 * Zero naturally falls on the grid when the range spans zero — no injection needed.
 */
function generateNiceTicks(dataMin, dataMax, desiredTickCount = 5) {
    // Handle edge cases
    if (!isFinite(dataMin) || !isFinite(dataMax)) {
        return { ticks: [0], niceMin: 0, niceMax: 0, step: 0 };
    }
    if (dataMin === dataMax) {
        // Single value: create a small range around it
        const padding = dataMin === 0 ? 1 : Math.abs(dataMin) * 0.1;
        return generateNiceTicks(dataMin - padding, dataMax + padding, desiredTickCount);
    }
    const range = niceNum(dataMax - dataMin, false);
    const step = niceNum(range / Math.max(1, desiredTickCount - 1), true);
    if (step === 0) {
        return { ticks: [dataMin], niceMin: dataMin, niceMax: dataMax, step: 0 };
    }
    const niceMin = Math.floor(dataMin / step) * step;
    const niceMax = Math.ceil(dataMax / step) * step;
    const ticks = [];
    // Use a small epsilon to handle floating point comparison
    for (let v = niceMin; v <= niceMax + step * 0.001; v += step) {
        // Round to remove floating point noise
        const rounded = Math.round(v * 1e10) / 1e10;
        ticks.push(rounded);
    }
    return { ticks, niceMin, niceMax, step };
}
/**
 * Comprehensive chart validation that combines all checks
 */
function validateChart(data, config) {
    const errors = [];
    const warnings = [];
    // Validate data
    const dataValidation = validateChartData(data);
    errors.push(...dataValidation.errors);
    warnings.push(...dataValidation.warnings);
    // Validate config
    const configValidation = validateChartConfig(config);
    errors.push(...configValidation.errors);
    warnings.push(...configValidation.warnings);
    // If basic validation passes, check compatibility
    if (dataValidation.isValid && configValidation.isValid) {
        const compatibilityValidation = validateDataConfigCompatibility(data, config);
        errors.push(...compatibilityValidation.errors);
        warnings.push(...compatibilityValidation.warnings);
    }
    return { isValid: errors.length === 0, errors, warnings };
}

export { SafeMath, calculateSafeScaleRange, generateNiceTicks, niceNum, sanitizeChartData, validateChart, validateChartConfig, validateChartData, validateDataConfigCompatibility };
