import { jsx } from 'react/jsx-runtime';
import { useCallback, useMemo } from 'react';
import { useReducedMotion, motion } from 'motion/react';
import { cn } from '../../../../utils/cn.js';

// Animation timing constants
const STACKED_CATEGORY_DELAY = 0.25;
const STACKED_SEGMENT_STAGGER = 0.12;
const GROUPED_BAR_STAGGER = 0.05;
/**
 * Simple color functions based on direct CSS variables
 */
const getSeriesFillColor = (index) => {
    const colorVars = [
        "var(--color-primary)",
        "var(--color-warning)",
        "var(--color-success)",
        "var(--color-danger)",
    ];
    return colorVars[index % colorVars.length];
};
/**
 * Helper function to create a path for bars with only top corners rounded
 */
const createRoundedTopBarPath = (x, y, width, height, radius) => {
    const r = Math.min(radius, width / 2, height / 2);
    return `
    M ${x},${y + height}
    L ${x},${y + r}
    Q ${x},${y} ${x + r},${y}
    L ${x + width - r},${y}
    Q ${x + width},${y} ${x + width},${y + r}
    L ${x + width},${y + height}
    Z
  `.trim();
};
/**
 * Helper function to create a path for bars with only bottom corners rounded (for negative bars)
 */
const createRoundedBottomBarPath = (x, y, width, height, radius) => {
    const r = Math.min(radius, width / 2, height / 2);
    return `
    M ${x},${y}
    L ${x + width},${y}
    L ${x + width},${y + height - r}
    Q ${x + width},${y + height} ${x + width - r},${y + height}
    L ${x + r},${y + height}
    Q ${x},${y + height} ${x},${y + height - r}
    Z
  `.trim();
};
/**
 * BarRenderer Component
 *
 * Pure bar rendering component that can be used standalone or within ChartContainer.
 * Handles both grouped and stacked bar layouts with Framer Motion animations.
 */
const BarRenderer = ({ data, config, scales, dimensions, margin, stacked = false, radius = 4, gap = 4, categoryGap = 8, showAnimation = true, hoveredCategory: externalHoveredCategory, onHoverCategory, onMouseEnter, onMouseMove, onMouseLeave, focusedElementIndex = -1, isKeyboardMode = false, className, }) => {
    const shouldReduceMotion = useReducedMotion();
    // Get all data keys from config
    const dataKeys = Object.keys(config || {});
    // Calculate inner dimensions
    const innerWidth = Math.max(0, dimensions.width - margin.left - margin.right);
    const innerHeight = Math.max(0, dimensions.height - margin.top - margin.bottom);
    // Get min/max from scales
    const { min: minValue, max: maxValue } = scales.y;
    // Safe division helper
    const safeDivide = (numerator, denominator, fallback = 0) => {
        if (denominator === 0 || !isFinite(denominator))
            return fallback;
        const result = numerator / denominator;
        return isFinite(result) ? result : fallback;
    };
    // Safe scale helper
    const safeScale = (value, inMin, inMax, outMin, outMax) => {
        const inRange = inMax - inMin;
        if (inRange === 0 || !isFinite(inRange))
            return outMin;
        const normalized = (value - inMin) / inRange;
        const scaled = normalized * (outMax - outMin) + outMin;
        return isFinite(scaled) ? scaled : outMin;
    };
    // Calculate bar dimensions
    const totalCategoryGap = Math.max(0, (data.length - 1) * categoryGap);
    const availableWidth = Math.max(0, innerWidth - totalCategoryGap);
    const barWidth = safeDivide(availableWidth, data.length, 0);
    const totalBarGap = Math.max(0, (dataKeys.length - 1) * gap);
    const availableBarWidth = Math.max(0, barWidth - totalBarGap);
    const individualBarWidth = stacked
        ? barWidth
        : safeDivide(availableBarWidth, dataKeys.length, 0);
    // Helper function to calculate average of non-null values for a specific key
    const calculateAverageForKey = useCallback((key) => {
        const nonNullValues = data
            .map((item) => item[key])
            .filter((value) => typeof value === "number" && isFinite(value));
        if (nonNullValues.length === 0)
            return 0;
        const sum = nonNullValues.reduce((acc, val) => acc + val, 0);
        return safeDivide(sum, nonNullValues.length);
    }, [data]);
    // Render individual bar path
    const renderBarPath = useCallback((item, index, key, keyIndex, barX, barY, barHeight, applyRadius, isStacked, stackOrderIndex, isNegative) => {
        var _a;
        const rawValue = item[key];
        const isNull = rawValue === null ||
            rawValue === undefined ||
            (typeof rawValue === "number" && !isFinite(rawValue));
        // Get fill color for this data series
        const fillColor = ((_a = config[key]) === null || _a === void 0 ? void 0 : _a.color) || getSeriesFillColor(keyIndex);
        const isHovered = externalHoveredCategory === index;
        const isAnyBarHovered = externalHoveredCategory !== null;
        const shouldReduceOpacity = isAnyBarHovered && !isHovered;
        const isFocused = isKeyboardMode && focusedElementIndex === index;
        // Don't render null bars
        if (isNull) {
            return null;
        }
        // Skip rendering if bar dimensions are invalid
        if (barHeight <= 0 || individualBarWidth <= 0) {
            return null;
        }
        const pathD = isNegative
            ? createRoundedBottomBarPath(barX, barY, individualBarWidth, barHeight, applyRadius ? radius : 0)
            : createRoundedTopBarPath(barX, barY, individualBarWidth, barHeight, applyRadius ? radius : 0);
        const stackedDelayOrder = typeof stackOrderIndex === "number" ? stackOrderIndex : keyIndex;
        const delay = isStacked
            ? index * STACKED_CATEGORY_DELAY +
                stackedDelayOrder * STACKED_SEGMENT_STAGGER
            : (index * dataKeys.length + keyIndex) * GROUPED_BAR_STAGGER;
        return (jsx(motion.path, { d: pathD, initial: showAnimation && !shouldReduceMotion
                ? { scaleY: 0, opacity: 0 }
                : { scaleY: 1, opacity: 1 }, animate: {
                scaleY: 1,
                opacity: 1,
            }, transition: isStacked
                ? {
                    duration: 0.4,
                    ease: "easeOut",
                    delay,
                }
                : {
                    type: "spring",
                    stiffness: 100,
                    damping: 20,
                    delay,
                }, className: cn("transition-colors duration-200", !isNull && "cursor-pointer", isFocused && "stroke-2"), style: {
                originY: isNegative ? 0 : 1,
                transformOrigin: isNegative ? "top" : "bottom",
                fill: isNull
                    ? "var(--color-background-tertiary)"
                    : shouldReduceOpacity
                        ? "var(--color-background-tertiary)"
                        : fillColor,
                opacity: !isNull && shouldReduceOpacity ? 0.9 : 1,
                stroke: isFocused ? "var(--color-primary)" : "none",
            }, onMouseEnter: !isNull && onMouseEnter
                ? (e) => {
                    if (onHoverCategory) {
                        onHoverCategory(index);
                    }
                    // Create content with ALL series for this category
                    const series = dataKeys
                        .map((dataKey, idx) => {
                        var _a, _b;
                        const seriesValue = item[dataKey];
                        const isSeriesNull = seriesValue === null ||
                            seriesValue === undefined ||
                            (typeof seriesValue === "number" &&
                                !isFinite(seriesValue));
                        return {
                            key: dataKey,
                            label: ((_a = config[dataKey]) === null || _a === void 0 ? void 0 : _a.label) || dataKey,
                            value: isSeriesNull ? 0 : seriesValue,
                            color: ((_b = config[dataKey]) === null || _b === void 0 ? void 0 : _b.color) || getSeriesFillColor(idx),
                        };
                    })
                        .filter((series) => {
                        // Only include non-null and non-zero values in tooltip
                        const rawVal = item[series.key];
                        return (rawVal != null &&
                            typeof rawVal === "number" &&
                            isFinite(rawVal) &&
                            rawVal !== 0);
                    });
                    const content = {
                        category: item.name || `Category ${index + 1}`,
                        series: stacked ? series.reverse() : series,
                        total: stacked
                            ? series.reduce((acc, s) => acc + (typeof s.value === "number" ? s.value : 0), 0)
                            : undefined,
                    };
                    const svgCoordinates = {
                        x: barX + individualBarWidth / 2,
                        y: barY,
                        dimensions,
                    };
                    onMouseEnter(e, content, svgCoordinates);
                }
                : undefined, onMouseMove: !isNull && onMouseMove
                ? (e) => {
                    if (externalHoveredCategory === index) {
                        const svgCoordinates = {
                            x: barX + individualBarWidth / 2,
                            y: barY,
                            dimensions,
                        };
                        onMouseMove(e, svgCoordinates);
                    }
                }
                : undefined, onMouseLeave: !isNull && onMouseLeave
                ? () => {
                    if (onHoverCategory) {
                        onHoverCategory(null);
                    }
                    onMouseLeave();
                }
                : undefined }, keyIndex));
    }, [
        config,
        dataKeys,
        dimensions,
        externalHoveredCategory,
        focusedElementIndex,
        individualBarWidth,
        isKeyboardMode,
        onHoverCategory,
        onMouseEnter,
        onMouseLeave,
        onMouseMove,
        radius,
        shouldReduceMotion,
        showAnimation,
        stacked,
    ]);
    // Render all bars
    const bars = useMemo(() => {
        return data.map((item, index) => {
            const x = margin.left + index * (barWidth + categoryGap);
            if (stacked) {
                const visibleBarCount = dataKeys.filter((key) => typeof item[key] === 'number' && item[key] !== 0).length;
                let stackedY = margin.top + innerHeight;
                let visibleStackIndex = 0;
                // Find the index of the topmost visible (non-zero) bar to apply radius
                const topmostVisibleIndex = dataKeys.reduce((topIndex, key, idx) => {
                    const rawVal = item[key];
                    const isVisible = typeof rawVal === "number" &&
                        isFinite(rawVal) &&
                        rawVal !== 0;
                    return isVisible ? idx : topIndex;
                }, -1);
                return (jsx("g", { children: dataKeys.map((key, keyIndex) => {
                        const rawValue = item[key];
                        const value = typeof rawValue === "number" && isFinite(rawValue)
                            ? rawValue
                            : 0;
                        const isNull = rawValue === null ||
                            rawValue === undefined ||
                            (typeof rawValue === "number" && !isFinite(rawValue));
                        // For null values, use the average
                        const actualValue = isNull
                            ? calculateAverageForKey(key)
                            : value;
                        const range = maxValue - minValue;
                        const barHeight = range > 0
                            ? safeScale(actualValue, minValue, maxValue, 0, innerHeight)
                            : 0;
                        const currentY = stackedY - barHeight;
                        stackedY = currentY;
                        const stackOrderIndex = !isNull && barHeight > 0
                            ? visibleBarCount - 1 - visibleStackIndex++
                            : null;
                        return renderBarPath(item, index, key, keyIndex, x, currentY, barHeight, keyIndex === topmostVisibleIndex, true, stackOrderIndex);
                    }) }, index));
            }
            else {
                // Calculate the zero baseline position (where value=0 maps to in pixel space)
                const scaledZero = safeScale(0, minValue, maxValue, 0, innerHeight);
                const zeroY = margin.top + innerHeight - scaledZero;
                return (jsx("g", { children: dataKeys.map((key, keyIndex) => {
                        const rawValue = item[key];
                        const value = typeof rawValue === "number" && isFinite(rawValue)
                            ? rawValue
                            : 0;
                        const isNull = rawValue === null ||
                            rawValue === undefined ||
                            (typeof rawValue === "number" && !isFinite(rawValue));
                        // For null values, use the average
                        const actualValue = isNull
                            ? calculateAverageForKey(key)
                            : value;
                        const range = maxValue - minValue;
                        const scaledValue = range > 0
                            ? safeScale(actualValue, minValue, maxValue, 0, innerHeight)
                            : 0;
                        const barHeight = Math.abs(scaledValue - scaledZero);
                        const isNeg = actualValue < 0;
                        const barX = x + keyIndex * (individualBarWidth + gap);
                        // Positive: bar extends upward from zero baseline
                        // Negative: bar extends downward from zero baseline
                        const barY = isNeg ? zeroY : zeroY - barHeight;
                        return renderBarPath(item, index, key, keyIndex, barX, barY, barHeight, true, false, undefined, isNeg);
                    }) }, index));
            }
        });
    }, [
        data,
        dataKeys,
        margin,
        barWidth,
        categoryGap,
        innerHeight,
        stacked,
        minValue,
        maxValue,
        individualBarWidth,
        gap,
        calculateAverageForKey,
        renderBarPath,
    ]);
    return (jsx("g", { className: cn("chart-bars", className), children: bars }));
};

export { BarRenderer };
