"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.applyRoundedCornersToStackData = exports.applyRoundedCornersToSqlRunnerStackData = exports.getIndexFromEncode = exports.getBarTotalLabelStyle = exports.getBarStyle = exports.getBarBorderRadius = exports.calculateDynamicBorderRadius = void 0;
const savedCharts_1 = require("../../../types/savedCharts");
const themeColors_1 = require("./themeColors");
/**
 * Calculate dynamic border radius based on estimated bar width
 * @param dataPointCount - Number of data points (categories) in the chart
 * @param seriesCount - Number of bar series (non-stacked bars in same category)
 * @param isStacked - Whether bars are stacked
 * @param isHorizontal - Whether the bar chart is horizontal (flipAxes)
 * @returns Appropriate border radius (max 4px, scales down for thin bars)
 */
const calculateDynamicBorderRadius = (dataPointCount, seriesCount, isStacked, isHorizontal = false) => {
    // Estimate relative bar width/height based on number of categories and series
    // Assumptions: typical chart is ~600px wide, horizontal charts use ~200px height (1/3), barCategoryGap 25%
    const estimatedChartSize = isHorizontal ? 200 : 600;
    const barCategoryGap = 0.25;
    const categorySize = estimatedChartSize / dataPointCount;
    // Size for bars (after gap)
    const barGroupSize = categorySize * (1 - barCategoryGap);
    // Individual bar size (stacked bars share size, non-stacked split it)
    const barSize = isStacked ? barGroupSize : barGroupSize / seriesCount;
    // Calculate radius as percentage of bar size (15%), capped at 4px minimum 2px
    const dynamicRadius = Math.max(2, Math.min(4, barSize * 0.15));
    return Math.round(dynamicRadius);
};
exports.calculateDynamicBorderRadius = calculateDynamicBorderRadius;
/**
 * Get border radius array for a bar chart data point
 * @param isHorizontal - Whether the bar chart is horizontal (flipAxes)
 * @param isStackEnd - Whether this data point is at the end (top/right) of the stack
 * @param radius - The border radius value to apply
 */
const getBarBorderRadius = (isHorizontal, isStackEnd, radius = 4) => {
    if (!isStackEnd) {
        return 0;
    }
    // Horizontal (flipAxes): round right side [top-right, bottom-right, bottom-left, top-left]
    // Vertical: round top side [top-left, top-right, bottom-right, bottom-left]
    return isHorizontal ? [0, radius, radius, 0] : [radius, radius, 0, 0];
};
exports.getBarBorderRadius = getBarBorderRadius;
/**
 * Get base bar styling configuration for cartesian charts
 */
const getBarStyle = () => ({
    barCategoryGap: '25%', // Gap between bars: width is 3x the gap (75% / 25% = 3)
});
exports.getBarStyle = getBarStyle;
/**
 * Get bar total label styling (values above/beside stacked bars)
 * This is specifically for stack totals, not individual bar labels
 */
const getBarTotalLabelStyle = () => ({
    color: themeColors_1.GRAY_9,
    fontWeight: '500',
    fontSize: 11,
});
exports.getBarTotalLabelStyle = getBarTotalLabelStyle;
/**
 * Resolve the tuple index for x/y given encode + dimensions
 * @param enc - The encode object
 * @param dimNames - The dimension names
 * @param which - The axis to resolve the index for
 * @returns The index of the axis
 */
const getIndexFromEncode = (enc, dimNames, which) => {
    const e = enc?.[which];
    if (typeof e === 'number')
        return e;
    if (Array.isArray(e))
        return typeof e[0] === 'number' ? e[0] : undefined;
    if (typeof e === 'string') {
        const idx = dimNames ? dimNames.indexOf(e) : undefined;
        return idx != null && idx >= 0 ? idx : undefined;
    }
    return undefined;
};
exports.getIndexFromEncode = getIndexFromEncode;
/**
 * Apply rounded corners to stacked bars for SQL Runner (flat data format)
 * SQL Runner data is flat: { field: value } instead of { field: { value: { raw: value } } }
 */
const applyRoundedCornersToSqlRunnerStackData = (series, rows, { radius = 4, isHorizontal = false, legendSelected, } = {}) => {
    const out = series.map((s) => ({ ...s }));
    const indexMap = new Map();
    series.forEach((s, i) => indexMap.set(s, i));
    const isVisible = (s) => {
        if (!legendSelected)
            return true;
        const name = s.name || (Array.isArray(s.dimensions) ? s.dimensions[1] : '');
        if (!name)
            return true;
        return legendSelected[name] ?? true;
    };
    const getDimNames = (s) => (s.dimensions || []).filter(Boolean);
    const getHashFromEncode = (s, which) => s.encode?.[which] || undefined;
    // Only stacked BAR series
    const stacks = {};
    series.forEach((s) => {
        var _a;
        if (s.type === 'bar' && s.stack) {
            (stacks[_a = s.stack] || (stacks[_a] = [])).push(s);
        }
    });
    Object.values(stacks).forEach((group) => {
        if (!group.length)
            return;
        const topPosAt = rows.map(() => -1);
        const topNegAt = rows.map(() => -1);
        // Determine which series sits at the visible end for each category index
        for (let i = group.length - 1; i >= 0; i--) {
            const s = group[i];
            if (!isVisible(s))
                continue;
            const valHash = isHorizontal
                ? getHashFromEncode(s, 'x')
                : getHashFromEncode(s, 'y');
            for (let r = 0; r < rows.length; r++) {
                // SQL Runner uses flat format: row[field] directly
                const raw = valHash ? rows[r]?.[valHash] : undefined;
                let v = null;
                if (typeof raw === 'number') {
                    v = raw;
                }
                else if (Number.isFinite(Number(raw))) {
                    v = Number(raw);
                }
                if (v == null || v === 0)
                    continue;
                if (v > 0 && topPosAt[r] === -1)
                    topPosAt[r] = i;
                if (v < 0 && topNegAt[r] === -1)
                    topNegAt[r] = i;
            }
        }
        // Build data with tuple indices aligned to encode+dimensions
        group.forEach((s, gi) => {
            const outIdx = indexMap.get(s);
            if (outIdx === undefined)
                return;
            const dimNames = getDimNames(s);
            const xIdx = (0, exports.getIndexFromEncode)(s.encode, dimNames, 'x') ?? 0;
            const yIdx = (0, exports.getIndexFromEncode)(s.encode, dimNames, 'y') ?? 1;
            const xHash = getHashFromEncode(s, 'x');
            const yHash = getHashFromEncode(s, 'y');
            const data = rows.map((row, r) => {
                const catHash = isHorizontal ? yHash : xHash;
                const valHash = isHorizontal ? xHash : yHash;
                // SQL Runner uses flat format
                const cat = catHash ? row[catHash] : undefined;
                const raw = valHash ? row[valHash] : undefined;
                let v = null;
                if (typeof raw === 'number') {
                    v = raw;
                }
                else if (Number.isFinite(Number(raw))) {
                    v = Number(raw);
                }
                const value = [];
                if (isHorizontal) {
                    value[xIdx] = v;
                    value[yIdx] = cat;
                }
                else {
                    value[xIdx] = cat;
                    value[yIdx] = v;
                }
                if (v == null)
                    return { value };
                const isTopPos = v > 0 && topPosAt[r] === gi;
                const isTopNeg = v < 0 && topNegAt[r] === gi;
                if (!isTopPos && !isTopNeg)
                    return { value };
                let borderRadius = 0;
                if (isHorizontal) {
                    borderRadius = isTopPos
                        ? [0, radius, radius, 0]
                        : [radius, 0, 0, radius];
                }
                else {
                    borderRadius = isTopPos
                        ? [radius, radius, 0, 0]
                        : [0, 0, radius, radius];
                }
                return {
                    value,
                    itemStyle: { ...(s.itemStyle || {}), borderRadius },
                };
            });
            out[outIdx] = { ...out[outIdx], data };
        });
    });
    return out;
};
exports.applyRoundedCornersToSqlRunnerStackData = applyRoundedCornersToSqlRunnerStackData;
/**
 * Apply rounded corners to the visible end-segments of stacked bar series.
 * Ref: https://github.com/apache/echarts/issues/12319#issuecomment-1341387601
 *
 * Consumes the same flat dataset that ECharts renders from (post-padding for
 * continuous-date axes) so category values match xAxis.data exactly.
 *
 * @param series ECharts series produced for a cartesian chart
 * @param rows Flat dataset rows aligned with the chart categories (same shape
 *             as dataset.source)
 * @param options.radius Corner radius in px (default: 4)
 * @param options.isHorizontal Whether the chart is horizontal (flipAxes)
 * @param options.legendSelected Map of legend selection states used to ignore hidden series
 * @returns New series array with `itemStyle.borderRadius` applied to end segments only
 *
 */
const applyRoundedCornersToStackData = (series, rows, { radius = 4, isHorizontal = false, legendSelected, } = {}) => {
    const out = series.map((s) => ({ ...s }));
    const indexMap = new Map();
    series.forEach((s, i) => indexMap.set(s, i));
    const isVisible = (s) => {
        if (!legendSelected)
            return true;
        const name = s.name || s.dimensions?.[1]?.displayName || '';
        if (!name)
            return true;
        return legendSelected[name] ?? true;
    };
    const getDimNames = (s) => (s.dimensions || [])
        .map((d) => (typeof d === 'string' ? d : d?.name))
        .filter(Boolean);
    const getHashFromEncode = (s, which) => s.encode?.[which] || undefined;
    // Only stacked BAR series
    const stacks = {};
    series.forEach((s) => {
        var _a;
        if (s.type === savedCharts_1.CartesianSeriesType.BAR && s.stack) {
            (stacks[_a = s.stack] || (stacks[_a] = [])).push(s);
        }
    });
    Object.values(stacks).forEach((group) => {
        if (!group.length)
            return;
        const topPosAt = rows.map(() => -1);
        const topNegAt = rows.map(() => -1);
        // Determine which series sits at the visible end for each category index
        for (let i = group.length - 1; i >= 0; i--) {
            const s = group[i];
            if (!isVisible(s))
                continue;
            // Get the hash of the value field - if the chart is horizontal, it's the x field, otherwise it's the y field
            const valHash = isHorizontal
                ? getHashFromEncode(s, 'x')
                : getHashFromEncode(s, 'y');
            for (let r = 0; r < rows.length; r++) {
                const raw = valHash ? rows[r]?.[valHash] : undefined;
                let v = null;
                if (typeof raw === 'number') {
                    v = raw;
                }
                else if (Number.isFinite(Number(raw))) {
                    v = Number(raw);
                }
                if (v == null || v === 0)
                    continue;
                // Track the index of the series that is at the top of the stack for each category
                if (v > 0 && topPosAt[r] === -1)
                    topPosAt[r] = i;
                if (v < 0 && topNegAt[r] === -1)
                    topNegAt[r] = i;
            }
        }
        // Build data with tuple indices aligned to encode+dimensions
        group.forEach((s, gi) => {
            const outIdx = indexMap.get(s);
            if (outIdx === undefined)
                return;
            const dimNames = getDimNames(s);
            const xIdx = (0, exports.getIndexFromEncode)(s.encode, dimNames, 'x') ?? 0;
            const yIdx = (0, exports.getIndexFromEncode)(s.encode, dimNames, 'y') ?? 1;
            const xHash = getHashFromEncode(s, 'x');
            const yHash = getHashFromEncode(s, 'y');
            const data = rows.map((row, r) => {
                const catHash = isHorizontal ? yHash : xHash; // category field in data
                const valHash = isHorizontal ? xHash : yHash; // numeric field in data
                const cat = catHash ? row[catHash] : undefined;
                const raw = valHash ? row[valHash] : undefined;
                let v = null;
                if (typeof raw === 'number') {
                    v = raw;
                }
                else if (Number.isFinite(Number(raw))) {
                    v = Number(raw);
                }
                // Place values at the correct tuple indices
                const value = [];
                if (isHorizontal) {
                    value[xIdx] = v;
                    value[yIdx] = cat;
                }
                else {
                    value[xIdx] = cat;
                    value[yIdx] = v;
                }
                if (v == null)
                    return { value };
                const isTopPos = v > 0 && topPosAt[r] === gi;
                const isTopNeg = v < 0 && topNegAt[r] === gi;
                if (!isTopPos && !isTopNeg)
                    return { value };
                let borderRadius = 0;
                if (isHorizontal) {
                    borderRadius = isTopPos
                        ? [0, radius, radius, 0] // round right side for +X
                        : [radius, 0, 0, radius]; // round left side for -X
                }
                else {
                    borderRadius = isTopPos
                        ? [radius, radius, 0, 0] // round top for +Y
                        : [0, 0, radius, radius]; // round bottom for -Y
                }
                return {
                    value,
                    itemStyle: { ...(s.itemStyle || {}), borderRadius },
                };
            });
            out[outIdx] = { ...out[outIdx], data };
        });
    });
    return out;
};
exports.applyRoundedCornersToStackData = applyRoundedCornersToStackData;
//# sourceMappingURL=barChartStyles.js.map