import { scaleBand as d3ScaleBand, scaleLinear as d3ScaleLinear, scaleLog as d3ScaleLog, scaleTime as d3ScaleTime, scaleUtc as d3ScaleUtc } from 'd3-scale';
import { extent as d3Extent, range as d3Range } from 'd3-array';
import { format as d3Format } from 'd3-format';
import { generateDateTicks, generateNumericTicks, getDateFormatLevel, isValidDomainValue, precisionRound } from '../../utilities/utilities';
import { isInvalidValue, formatToLocaleString, getMultiLevelDateTimeFormatOptions, formatDateToLocaleString } from '@fluentui/chart-utilities';
import { timeFormat as d3TimeFormat, utcFormat as d3UtcFormat } from 'd3-time-format';
export const EPSILON = 1e-6;
export const createRadialScale = (scaleType, domain, range, opts = {})=>{
    if (scaleType === 'category') {
        const scale = d3ScaleBand().domain(domain).range(range).paddingInner(1);
        const tickValues = Array.isArray(opts.tickValues) ? opts.tickValues : domain;
        const tickFormat = (domainValue, index)=>{
            if (Array.isArray(opts.tickValues) && Array.isArray(opts.tickText) && !isInvalidValue(opts.tickText[index])) {
                return opts.tickText[index];
            }
            return domainValue;
        };
        return {
            scale,
            tickValues,
            tickLabels: tickValues.map(tickFormat)
        };
    }
    let scale;
    if (scaleType === 'date') {
        scale = opts.useUTC ? d3ScaleUtc() : d3ScaleTime();
    } else {
        scale = scaleType === 'log' ? d3ScaleLog() : d3ScaleLinear();
    }
    scale.domain(domain);
    scale.range(range);
    scale.nice();
    var _opts_tickCount;
    const tickCount = (_opts_tickCount = opts.tickCount) !== null && _opts_tickCount !== void 0 ? _opts_tickCount : 4;
    let tickFormat;
    let customTickValues = Array.isArray(opts.tickValues) ? opts.tickValues : undefined;
    if (scaleType === 'date') {
        let lowestFormatLevel = 100;
        let highestFormatLevel = -1;
        scale.ticks().forEach((domainValue)=>{
            const formatLevel = getDateFormatLevel(domainValue, opts.useUTC);
            if (formatLevel > highestFormatLevel) {
                highestFormatLevel = formatLevel;
            }
            if (formatLevel < lowestFormatLevel) {
                lowestFormatLevel = formatLevel;
            }
        });
        var _opts_dateLocalizeOptions;
        const formatOptions = (_opts_dateLocalizeOptions = opts.dateLocalizeOptions) !== null && _opts_dateLocalizeOptions !== void 0 ? _opts_dateLocalizeOptions : getMultiLevelDateTimeFormatOptions(lowestFormatLevel, highestFormatLevel);
        tickFormat = (domainValue, index)=>{
            if (Array.isArray(opts.tickValues) && Array.isArray(opts.tickText) && !isInvalidValue(opts.tickText[index])) {
                return opts.tickText[index];
            }
            if (isInvalidValue(opts.culture) && typeof opts.tickFormat === 'string') {
                if (opts.useUTC) {
                    return d3UtcFormat(opts.tickFormat)(domainValue);
                } else {
                    return d3TimeFormat(opts.tickFormat)(domainValue);
                }
            }
            return formatDateToLocaleString(domainValue, opts.culture, opts.useUTC, false, formatOptions);
        };
        if (opts.tickStep) {
            customTickValues = generateDateTicks(opts.tickStep, opts.tick0, scale.domain(), opts.useUTC);
        }
    } else {
        const defaultTickFormat = scale.tickFormat(tickCount);
        tickFormat = (domainValue, index)=>{
            if (Array.isArray(opts.tickValues) && Array.isArray(opts.tickText) && !isInvalidValue(opts.tickText[index])) {
                return opts.tickText[index];
            }
            if (typeof opts.tickFormat === 'string') {
                return d3Format(opts.tickFormat)(domainValue);
            }
            const value = typeof domainValue === 'number' ? domainValue : domainValue.valueOf();
            return defaultTickFormat(value) === '' ? '' : formatToLocaleString(value, opts.culture);
        };
        if (opts.tickStep) {
            customTickValues = generateNumericTicks(scaleType, opts.tickStep, opts.tick0, scale.domain());
        }
    }
    const tickValues = customTickValues !== null && customTickValues !== void 0 ? customTickValues : scale.ticks(tickCount);
    return {
        scale,
        tickValues,
        tickLabels: tickValues.map(tickFormat)
    };
};
export const getScaleType = (values, opts = {})=>{
    let scaleType = 'category';
    if (typeof values[0] === 'number') {
        if (opts.supportsLog && opts.scaleType === 'log') {
            scaleType = 'log';
        } else {
            scaleType = 'linear';
        }
    } else if (values[0] instanceof Date) {
        scaleType = 'date';
    }
    return scaleType;
};
export const getContinuousScaleDomain = (scaleType, values, opts = {})=>{
    let [min, max] = d3Extent(values.filter((v)=>isValidDomainValue(v, scaleType)));
    if (scaleType === 'linear') {
        [min, max] = d3Extent([
            min,
            max,
            0
        ]);
    }
    if (!isInvalidValue(opts.rangeStart)) {
        min = opts.rangeStart;
    }
    if (!isInvalidValue(opts.rangeEnd)) {
        max = opts.rangeEnd;
    }
    if (isInvalidValue(min) || isInvalidValue(max)) {
        return [];
    }
    return [
        min,
        max
    ];
};
const degToRad = (deg)=>deg * Math.PI / 180;
const radToDeg = (rad)=>rad * 180 / Math.PI;
const normalizeAngle = (deg, direction)=>((direction === 'clockwise' ? deg : 450 - deg) % 360 + 360) % 360;
export const createAngularScale = (scaleType, domain, opts = {})=>{
    if (scaleType === 'category') {
        const categoryIndexMap = {};
        domain.forEach((d, i)=>{
            categoryIndexMap[d] = i;
        });
        const period = 360 / domain.length;
        const tickValues = Array.isArray(opts.tickValues) ? opts.tickValues : domain;
        const tickFormat = (domainValue, index)=>{
            if (Array.isArray(opts.tickValues) && Array.isArray(opts.tickText) && !isInvalidValue(opts.tickText[index])) {
                return opts.tickText[index];
            }
            return domainValue;
        };
        return {
            scale: (v)=>degToRad(normalizeAngle(categoryIndexMap[v] * period, opts.direction)),
            tickValues,
            tickLabels: tickValues.map(tickFormat)
        };
    }
    let customTickValues = Array.isArray(opts.tickValues) ? opts.tickValues : undefined;
    const tickFormat = (domainValue, index)=>{
        if (Array.isArray(opts.tickValues) && Array.isArray(opts.tickText) && !isInvalidValue(opts.tickText[index])) {
            return opts.tickText[index];
        }
        if (typeof opts.tickFormat === 'string') {
            return d3Format(opts.tickFormat)(domainValue);
        }
        return formatAngle(domainValue, opts.unit);
    };
    if (opts.tickStep) {
        var _generateNumericTicks;
        customTickValues = (_generateNumericTicks = generateNumericTicks(scaleType, opts.tickStep, opts.tick0, [
            0,
            (opts.unit === 'radians' ? 2 * Math.PI : 360) - EPSILON
        ])) === null || _generateNumericTicks === void 0 ? void 0 : _generateNumericTicks.map((v)=>opts.unit === 'radians' ? radToDeg(v) : v);
    }
    var _opts_tickCount;
    const tickValues = customTickValues !== null && customTickValues !== void 0 ? customTickValues : d3Range(0, 360, 360 / ((_opts_tickCount = opts.tickCount) !== null && _opts_tickCount !== void 0 ? _opts_tickCount : 8));
    return {
        scale: (v)=>degToRad(normalizeAngle(v, opts.direction)),
        tickValues,
        tickLabels: tickValues.map(tickFormat)
    };
};
export const formatAngle = (value, unit)=>typeof value === 'string' ? value : unit === 'radians' ? `${precisionRound(value / 180, 6)}π` : `${precisionRound(value, 6)}°`;
