"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ColorBandValueType = void 0;
exports.getColorScale = getColorScale;
exports.getColorScaleWithBands = getColorScaleWithBands;
const chroma_js_1 = __importDefault(require("chroma-js"));
const d3_array_1 = require("d3-array");
const color_calcs_1 = require("../../../common/color_calcs");
const color_library_wrappers_1 = require("../../../common/color_library_wrappers");
const colors_1 = require("../../../common/colors");
const common_1 = require("../../../utils/common");
const logger_1 = require("../../../utils/logger");
exports.ColorBandValueType = Object.freeze({
    Scale: 'scale',
    Percentage: 'percentage',
});
const getValueByTypeFn = ([min, max]) => {
    const domainLength = max - min;
    const minOffset = domainLength / 100000;
    return (bandValue, openOffset = 0) => {
        const openOffsetValue = openOffset * minOffset;
        if (typeof bandValue === 'number')
            return bandValue + openOffsetValue;
        const { type, value } = bandValue;
        if (type === 'scale')
            return value + openOffsetValue;
        if (type === 'percentage')
            return min + (value / 100) * domainLength + openOffsetValue;
        return null;
    };
};
const getBandValueFn = (domain) => {
    const getValueByType = getValueByTypeFn(domain);
    return (bandValue, openOffset) => {
        if ((0, common_1.isNil)(bandValue))
            return null;
        return getValueByType(bandValue, openOffset);
    };
};
const getDomainPairFn = (domain) => {
    const getBandValue = getBandValueFn(domain);
    return (config) => {
        return [
            getBandValue(config.gt, 1) ?? getBandValue(config.gte),
            getBandValue(config.lt, -1) ?? getBandValue(config.lte),
        ];
    };
};
const isComplexConfig = (config) => Array.isArray(config) && typeof config[0] !== 'string';
const getColorBandsFromTicks = ([min, max], ticks) => {
    const fullTicks = ticks.slice();
    const first = fullTicks.at(0);
    const last = fullTicks.at(-1);
    const minIndex = first > last ? -1 : 0;
    const maxIndex = first < last ? -1 : 0;
    if (fullTicks.at(minIndex) !== min) {
        if (minIndex === 0)
            fullTicks.unshift(min);
        else
            fullTicks.push(min);
    }
    if (fullTicks.at(maxIndex) !== max) {
        if (maxIndex === 0)
            fullTicks.unshift(max);
        else
            fullTicks.push(max);
    }
    return fullTicks.flatMap((n, i, { length }) => (i === 0 || i === length - 1 ? [n] : [n, n]));
};
function getScaleInputs(baseDomain, config, backgroundColor) {
    if (!Array.isArray(config) || !isComplexConfig(config)) {
        const { colors: rawColors, steps } = !Array.isArray(config)
            ? config
            : {
                colors: config,
            };
        const colors = rawColors.map((c) => c.toLowerCase());
        if (colors.length === 1) {
            const [color] = colors;
            if (color) {
                const secondary = (0, color_library_wrappers_1.getChromaColor)(color).alpha(0.7).hex();
                const blendedSecondary = (0, color_calcs_1.combineColors)((0, color_library_wrappers_1.colorToRgba)(secondary), (0, color_library_wrappers_1.colorToRgba)(backgroundColor));
                colors.push((0, color_library_wrappers_1.RGBATupleToString)(blendedSecondary));
            }
        }
        return {
            colors,
            steps,
        };
    }
    if (!isComplexConfig(config)) {
        return {
            colors: config,
        };
    }
    const getDomainPair = getDomainPairFn(baseDomain);
    const { colors, boundedDomains } = config.reduce((acc, colorConfig) => {
        if (typeof colorConfig === 'string') {
            acc.colors.push(colorConfig);
        }
        else {
            acc.colors.push(colorConfig.color);
            const domainPair = getDomainPair(colorConfig);
            acc.boundedDomains.push(domainPair);
        }
        return acc;
    }, {
        boundedDomains: [],
        colors: [],
    });
    let prevMax = -Infinity;
    return boundedDomains.reduce((acc, [min, max], i) => {
        const testMinValue = (0, common_1.isFiniteNumber)(min) ? min : (0, common_1.isFiniteNumber)(max) ? max : null;
        if (testMinValue === null || testMinValue < prevMax) {
            logger_1.Logger.warn(`Error with ColorBandComplexConfig:

Ranges are incompatible with each other such that there is either overlapping or excluded range pairs`);
            return acc;
        }
        const newMaxValue = (0, common_1.isFiniteNumber)(max) ? max : (0, common_1.isFiniteNumber)(min) ? min : null;
        if (newMaxValue === null)
            return acc;
        prevMax = newMaxValue;
        const color = colors[i] ?? colors_1.Colors.Transparent.keyword;
        if ((0, common_1.isFiniteNumber)(min)) {
            acc.colorBandDomain.push(min);
            acc.colors.push(color);
        }
        if ((0, common_1.isFiniteNumber)(max)) {
            acc.colorBandDomain.push(max);
            acc.colors.push(color);
        }
        return acc;
    }, {
        colorBandDomain: [],
        colors: [],
    });
}
function getColorScale(baseDomain, config, backgroundColor, fallbackBandColor) {
    const { colors, colorBandDomain, steps: userSteps } = getScaleInputs(baseDomain, config, backgroundColor);
    const scale = chroma_js_1.default
        .scale(colors)
        .mode('lab')
        .domain(colorBandDomain ?? baseDomain);
    let steps = userSteps;
    let scaleDomain = baseDomain;
    if (steps !== undefined) {
        if (Array.isArray(steps) && !(0, common_1.isSorted)(steps, { order: 'ascending' })) {
            logger_1.Logger.warn(`Ignoring Bullet.colorBands.steps. Expected a sorted ascending array of numbers without duplicates.\n\n\tReceived:${JSON.stringify(steps)}`);
            steps = undefined;
        }
        else if (typeof steps === 'number' && steps < 1) {
            logger_1.Logger.warn(`Ignoring Bullet.colorBands.steps. Expected a positive number.\n\n\tReceived:${steps}`);
            steps = undefined;
        }
        else {
            if (Array.isArray(steps)) {
                const [min = NaN, max = NaN] = (0, d3_array_1.extent)(steps);
                scaleDomain = [min, max];
            }
            scale.classes(steps);
        }
    }
    const isInDomain = (0, common_1.isWithinRange)(scaleDomain);
    return [(n) => (isInDomain(n) ? scale(n) : (0, color_library_wrappers_1.getChromaColor)(fallbackBandColor)), { colors, colorBandDomain, steps }];
}
function getColorScaleWithBands(scale, config, ticks, backgroundColor, fallbackBandColor) {
    const domain = scale.domain();
    const baseDomain = (0, common_1.sortNumbers)(domain);
    const [colorScale, colorScaleInputs] = getColorScale(baseDomain, config, backgroundColor, fallbackBandColor);
    return {
        scale: colorScale,
        bands: getColorBands(scale, colorScale, ticks, baseDomain, colorScaleInputs),
    };
}
function getColorBands(scale, colorScale, ticks, baseDomain, { colorBandDomain, steps }) {
    const [min, max] = baseDomain;
    if (steps) {
        if (Array.isArray(steps)) {
            const bands = [];
            for (let i = 0; i < steps.length - 1; i++) {
                const start = steps[i];
                const end = steps[i + 1];
                const [scaledStart, scaledEnd] = (0, common_1.sortNumbers)([scale((0, common_1.clamp)(start, min, max)), scale((0, common_1.clamp)(end, min, max))]);
                bands.push({
                    start: scaledStart,
                    end: scaledEnd,
                    size: Math.abs(scaledEnd - scaledStart),
                    color: colorScale(start + Math.abs(end - start) / 2).hex(),
                });
            }
            return bands;
        }
        const domainDelta = max - min;
        const size = domainDelta / steps;
        return Array.from({ length: steps }, (_, i) => {
            const [start, end] = (0, common_1.sortNumbers)([scale(i * size + min), scale((i + 1) * size + min)]);
            return {
                start,
                end,
                color: colorScale((i + 0.5) * size + min).hex(),
                size: Math.abs(end - start),
            };
        });
    }
    const bandPositions = colorBandDomain ?? getColorBandsFromTicks(baseDomain, ticks);
    const scaledColorBands = [];
    for (let i = 0; i < bandPositions.length; i += 2) {
        const [start, end] = bandPositions.slice(i, i + 2);
        if (start === undefined || end === undefined)
            continue;
        const [scaledStart, scaledEnd] = (0, common_1.sortNumbers)([scale((0, common_1.clamp)(start, min, max)), scale((0, common_1.clamp)(end, min, max))]);
        const size = Math.abs(scaledEnd - scaledStart);
        const tick = (0, common_1.clamp)(start + (end - start) / 2, min, max);
        if (scaledStart === scaledEnd)
            continue;
        scaledColorBands.push({
            start: scaledStart,
            end: scaledEnd,
            size,
            color: colorScale(tick).hex(),
        });
    }
    return scaledColorBands;
}
//# sourceMappingURL=color.js.map