"use client";

import { localPoint } from "@visx/event";
import { LinearGradient as VisxLinearGradient } from "@visx/gradient";
import { GridColumns, GridRows } from "@visx/grid";
import { ParentSize } from "@visx/responsive";
import { scaleBand, scaleLinear } from "@visx/scale";
import {
  AnimatePresence,
  motion,
  useSpring,
} from "motion/react";
import {
  Children,
  createContext,
  isValidElement,
  useCallback,
  useContext,
  useEffect,
  useId,
  useLayoutEffect,
  useMemo,
  useRef,
  useState,
  type Dispatch,
  type ReactElement,
  type ReactNode,
  type RefObject,
  type SetStateAction,
} from "react";
import useMeasure from "react-use-measure";
import { createPortal } from "react-dom";
import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";

// ─── Utils ───────────────────────────────────────────────────────────────────

function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs));
}

// ─── CSS Vars ────────────────────────────────────────────────────────────────

export const chartCssVars = {
  background: "var(--chart-background)",
  foreground: "var(--chart-foreground)",
  foregroundMuted: "var(--chart-foreground-muted)",
  label: "var(--chart-label)",
  linePrimary: "var(--chart-line-primary)",
  lineSecondary: "var(--chart-line-secondary)",
  crosshair: "var(--chart-crosshair)",
  grid: "var(--chart-grid)",
};

// ─── Types ───────────────────────────────────────────────────────────────────

export interface Margin {
  top: number;
  right: number;
  bottom: number;
  left: number;
}

export interface TooltipData {
  point: Record<string, unknown>;
  index: number;
  x: number;
  yPositions: Record<string, number>;
  xPositions?: Record<string, number>;
}

export interface TooltipRow {
  color: string;
  label: string;
  value: string | number;
}

export interface LineConfig {
  dataKey: string;
  stroke: string;
  strokeWidth: number;
}

// ─── Bar Chart Context ───────────────────────────────────────────────────────

type ScaleLinearType<Output> = ReturnType<typeof scaleLinear<Output>>;
type ScaleBandType<Domain extends { toString(): string }> = ReturnType<
  typeof scaleBand<Domain>
>;

export interface BarChartContextValue {
  data: Record<string, unknown>[];
  xScale: ScaleBandType<string>;
  yScale: ScaleLinearType<number>;
  width: number;
  height: number;
  innerWidth: number;
  innerHeight: number;
  margin: Margin;
  bandWidth: number;
  tooltipData: TooltipData | null;
  setTooltipData: Dispatch<SetStateAction<TooltipData | null>>;
  containerRef: RefObject<HTMLDivElement | null>;
  bars: BarConfig[];
  isLoaded: boolean;
  animationDuration: number;
  xDataKey: string;
  hoveredBarIndex: number | null;
  setHoveredBarIndex: (index: number | null) => void;
  orientation: "vertical" | "horizontal";
  stacked: boolean;
  stackGap: number;
  stackOffsets: Map<number, Map<string, number>>;
  barGap: number;
  barWidth?: number;
}

interface BarConfig {
  dataKey: string;
  fill: string;
  stroke?: string;
}

const BarChartContext = createContext<BarChartContextValue | null>(null);

function BarChartProvider({
  children,
  value,
}: {
  children: ReactNode;
  value: BarChartContextValue;
}) {
  return (
    <BarChartContext.Provider value={value}>
      {children}
    </BarChartContext.Provider>
  );
}

export function useChart(): BarChartContextValue {
  const context = useContext(BarChartContext);
  if (!context) {
    throw new Error(
      "useChart must be used within a BarChartProvider. " +
        "Make sure your component is wrapped in <BarChart>."
    );
  }
  return context;
}

// ─── Tooltip Components ──────────────────────────────────────────────────────

interface TooltipDotProps {
  x: number;
  y: number;
  visible: boolean;
  color: string;
  size?: number;
  strokeColor?: string;
  strokeWidth?: number;
}

function TooltipDot({
  x,
  y,
  visible,
  color,
  size = 5,
  strokeColor = chartCssVars.background,
  strokeWidth = 2,
}: TooltipDotProps) {
  const springConfig = { stiffness: 300, damping: 30 };
  const animatedX = useSpring(x, springConfig);
  const animatedY = useSpring(y, springConfig);

  useEffect(() => {
    animatedX.set(x);
    animatedY.set(y);
  }, [x, y, animatedX, animatedY]);

  if (!visible) return null;

  return (
    <motion.circle
      cx={animatedX}
      cy={animatedY}
      fill={color}
      r={size}
      stroke={strokeColor}
      strokeWidth={strokeWidth}
    />
  );
}

TooltipDot.displayName = "TooltipDot";

interface TooltipIndicatorProps {
  x: number;
  height: number;
  visible: boolean;
  width?: number;
  colorEdge?: string;
  colorMid?: string;
  fadeEdges?: boolean;
  gradientId?: string;
}

function TooltipIndicator({
  x,
  height,
  visible,
  width = 1,
  colorEdge = chartCssVars.crosshair,
  colorMid = chartCssVars.crosshair,
  fadeEdges = true,
  gradientId = "bar-tooltip-indicator-gradient",
}: TooltipIndicatorProps) {
  const springConfig = { stiffness: 300, damping: 30 };
  const animatedX = useSpring(x - width / 2, springConfig);

  useEffect(() => {
    animatedX.set(x - width / 2);
  }, [x, animatedX, width]);

  if (!visible) return null;

  const edgeOpacity = fadeEdges ? 0 : 1;

  return (
    <g>
      <defs>
        <linearGradient id={gradientId} x1="0%" x2="0%" y1="0%" y2="100%">
          <stop offset="0%" style={{ stopColor: colorEdge, stopOpacity: edgeOpacity }} />
          <stop offset="10%" style={{ stopColor: colorEdge, stopOpacity: 1 }} />
          <stop offset="50%" style={{ stopColor: colorMid, stopOpacity: 1 }} />
          <stop offset="90%" style={{ stopColor: colorEdge, stopOpacity: 1 }} />
          <stop offset="100%" style={{ stopColor: colorEdge, stopOpacity: edgeOpacity }} />
        </linearGradient>
      </defs>
      <motion.rect fill={`url(#${gradientId})`} height={height} width={width} x={animatedX} y={0} />
    </g>
  );
}

TooltipIndicator.displayName = "TooltipIndicator";

interface TooltipContentProps {
  title?: string;
  rows: TooltipRow[];
  children?: ReactNode;
}

function TooltipContent({ title, rows, children }: TooltipContentProps) {
  const [measureRef, bounds] = useMeasure({ debounce: 0, scroll: false });
  const [committedHeight, setCommittedHeight] = useState<number | null>(null);
  const committedChildrenStateRef = useRef<boolean | null>(null);
  const frameRef = useRef<number | null>(null);

  const hasChildren = !!children;
  const markerKey = hasChildren ? "has-marker" : "no-marker";
  const isWaitingForSettlement = committedChildrenStateRef.current !== null && committedChildrenStateRef.current !== hasChildren;

  useEffect(() => {
    if (bounds.height <= 0) return;
    if (frameRef.current) { cancelAnimationFrame(frameRef.current); frameRef.current = null; }
    if (isWaitingForSettlement) {
      frameRef.current = requestAnimationFrame(() => {
        frameRef.current = requestAnimationFrame(() => {
          setCommittedHeight(bounds.height);
          committedChildrenStateRef.current = hasChildren;
        });
      });
    } else {
      setCommittedHeight(bounds.height);
      committedChildrenStateRef.current = hasChildren;
    }
    return () => { if (frameRef.current) cancelAnimationFrame(frameRef.current); };
  }, [bounds.height, hasChildren, isWaitingForSettlement]);

  const shouldAnimate = committedHeight !== null;

  return (
    <motion.div
      animate={committedHeight !== null ? { height: committedHeight } : undefined}
      className="overflow-hidden"
      initial={false}
      transition={shouldAnimate ? { type: "spring", stiffness: 500, damping: 35, mass: 0.8 } : { duration: 0 }}
    >
      <div className="px-3 py-2.5" ref={measureRef}>
        {title && <div className="mb-2 font-medium text-neutral-400 text-xs">{title}</div>}
        <div className="space-y-1.5">
          {rows.map((row) => (
            <div className="flex items-center justify-between gap-4" key={`${row.label}-${row.color}`}>
              <div className="flex items-center gap-2">
                <span className="h-2.5 w-2.5 shrink-0 rounded-full" style={{ backgroundColor: row.color }} />
                <span className="text-neutral-400 text-sm">{row.label}</span>
              </div>
              <span className="font-medium text-white text-sm tabular-nums">
                {typeof row.value === "number" ? row.value.toLocaleString() : row.value}
              </span>
            </div>
          ))}
        </div>
        <AnimatePresence mode="wait">
          {children && (
            <motion.div animate={{ opacity: 1, filter: "blur(0px)" }} className="mt-2" exit={{ opacity: 0, filter: "blur(4px)" }} initial={{ opacity: 0, filter: "blur(4px)" }} key={markerKey} transition={{ duration: 0.2, ease: "easeOut" }}>
              {children}
            </motion.div>
          )}
        </AnimatePresence>
      </div>
    </motion.div>
  );
}

TooltipContent.displayName = "TooltipContent";

interface TooltipBoxProps {
  x: number;
  y: number;
  visible: boolean;
  containerRef: RefObject<HTMLDivElement | null>;
  containerWidth: number;
  containerHeight: number;
  offset?: number;
  className?: string;
  children: ReactNode;
  top?: number | ReturnType<typeof useSpring>;
}

function TooltipBox({
  x, y, visible, containerRef, containerWidth, containerHeight, offset = 16, className = "", children, top: topOverride,
}: TooltipBoxProps) {
  const tooltipRef = useRef<HTMLDivElement>(null);
  const [tooltipWidth, setTooltipWidth] = useState(180);
  const [tooltipHeight, setTooltipHeight] = useState(80);
  const [mounted, setMounted] = useState(false);

  useEffect(() => { setMounted(true); }, []);

  useLayoutEffect(() => {
    if (tooltipRef.current) {
      const w = tooltipRef.current.offsetWidth;
      const h = tooltipRef.current.offsetHeight;
      if (w > 0 && w !== tooltipWidth) setTooltipWidth(w);
      if (h > 0 && h !== tooltipHeight) setTooltipHeight(h);
    }
  }, [tooltipWidth, tooltipHeight]);

  const shouldFlipX = x + tooltipWidth + offset > containerWidth;
  const targetX = shouldFlipX ? x - offset - tooltipWidth : x + offset;
  const targetY = Math.max(offset, Math.min(y - tooltipHeight / 2, containerHeight - tooltipHeight - offset));

  const prevFlipRef = useRef(shouldFlipX);
  const [flipKey, setFlipKey] = useState(0);

  useEffect(() => {
    if (prevFlipRef.current !== shouldFlipX) { setFlipKey((k) => k + 1); prevFlipRef.current = shouldFlipX; }
  }, [shouldFlipX]);

  const springConfig = { stiffness: 100, damping: 20 };
  const animatedLeft = useSpring(targetX, springConfig);
  const animatedTop = useSpring(targetY, springConfig);

  useEffect(() => { animatedLeft.set(targetX); }, [targetX, animatedLeft]);
  useEffect(() => { animatedTop.set(targetY); }, [targetY, animatedTop]);

  const finalTop = topOverride ?? animatedTop;
  const transformOrigin = shouldFlipX ? "right top" : "left top";

  const container = containerRef.current;
  if (!(mounted && container)) return null;
  if (!visible) return null;

  return createPortal(
    <motion.div animate={{ opacity: 1 }} className={cn("pointer-events-none absolute z-50", className)} exit={{ opacity: 0 }} initial={{ opacity: 0 }} ref={tooltipRef} style={{ left: animatedLeft, top: finalTop }} transition={{ duration: 0.1 }}>
      <motion.div animate={{ scale: 1, opacity: 1, x: 0 }} className="min-w-[140px] overflow-hidden rounded-lg bg-neutral-800/90 text-white shadow-lg backdrop-blur-md" initial={{ scale: 0.85, opacity: 0, x: shouldFlipX ? 20 : -20 }} key={flipKey} style={{ transformOrigin }} transition={{ type: "spring", stiffness: 300, damping: 25 }}>
        {children}
      </motion.div>
    </motion.div>,
    container
  );
}

TooltipBox.displayName = "TooltipBox";

// ─── ChartTooltip ────────────────────────────────────────────────────────────

export interface ChartTooltipProps {
  showCrosshair?: boolean;
  showDots?: boolean;
  content?: (props: { point: Record<string, unknown>; index: number }) => ReactNode;
  rows?: (point: Record<string, unknown>) => TooltipRow[];
  children?: ReactNode;
  className?: string;
}

export function ChartTooltip({ showCrosshair = true, showDots = true, content, rows: rowsRenderer, children, className = "" }: ChartTooltipProps) {
  const { tooltipData, width, height, innerHeight, margin, bars, xDataKey, containerRef, orientation, yScale } = useChart();
  const isHorizontal = orientation === "horizontal";
  const [mounted, setMounted] = useState(false);
  useEffect(() => { setMounted(true); }, []);

  const visible = tooltipData !== null;
  const x = tooltipData?.x ?? 0;
  const xWithMargin = x + margin.left;
  const firstBarDataKey = bars[0]?.dataKey;
  const firstBarY = firstBarDataKey ? (tooltipData?.yPositions[firstBarDataKey] ?? 0) : 0;
  const yWithMargin = firstBarY + margin.top;

  const springConfig = { stiffness: 300, damping: 30 };
  const animatedX = useSpring(xWithMargin, springConfig);
  useEffect(() => { animatedX.set(xWithMargin); }, [xWithMargin, animatedX]);

  const tooltipRows = useMemo(() => {
    if (!tooltipData) return [];
    if (rowsRenderer) return rowsRenderer(tooltipData.point);
    return bars.map((bar) => ({ color: bar.stroke || bar.fill, label: bar.dataKey, value: (tooltipData.point[bar.dataKey] as number) ?? 0 }));
  }, [tooltipData, bars, rowsRenderer]);

  const title = useMemo(() => {
    if (!tooltipData) return undefined;
    return String(tooltipData.point[xDataKey] ?? "");
  }, [tooltipData, xDataKey]);

  const container = containerRef.current;
  if (!(mounted && container)) return null;

  const tooltipContent = (
    <>
      {showCrosshair && !isHorizontal && (
        <svg aria-hidden="true" className="pointer-events-none absolute inset-0" height="100%" width="100%">
          <g transform={`translate(${margin.left},${margin.top})`}>
            <TooltipIndicator height={innerHeight} visible={visible} width={1} x={x} />
          </g>
        </svg>
      )}
      {showDots && visible && !isHorizontal && (
        <svg aria-hidden="true" className="pointer-events-none absolute inset-0" height="100%" width="100%">
          <g transform={`translate(${margin.left},${margin.top})`}>
            {bars.map((bar) => (
              <TooltipDot color={bar.stroke || bar.fill} key={bar.dataKey} strokeColor={chartCssVars.background} visible={visible} x={tooltipData?.xPositions?.[bar.dataKey] ?? x} y={tooltipData?.yPositions[bar.dataKey] ?? 0} />
            ))}
          </g>
        </svg>
      )}
      <TooltipBox className={className} containerHeight={height} containerRef={containerRef} containerWidth={width} top={isHorizontal ? undefined : margin.top} visible={visible} x={xWithMargin} y={isHorizontal ? yWithMargin : margin.top}>
        {content ? content({ point: tooltipData?.point ?? {}, index: tooltipData?.index ?? 0 }) : (
          <TooltipContent rows={tooltipRows} title={title}>{children}</TooltipContent>
        )}
      </TooltipBox>
    </>
  );

  return createPortal(tooltipContent, container);
}

ChartTooltip.displayName = "ChartTooltip";

// ─── Grid ────────────────────────────────────────────────────────────────────

export interface GridProps {
  horizontal?: boolean;
  vertical?: boolean;
  numTicksRows?: number;
  numTicksColumns?: number;
  rowTickValues?: number[];
  stroke?: string;
  strokeOpacity?: number;
  strokeWidth?: number;
  strokeDasharray?: string;
  fadeHorizontal?: boolean;
  fadeVertical?: boolean;
}

export function Grid({
  horizontal = true, vertical = false, numTicksRows = 5, numTicksColumns = 10, rowTickValues,
  stroke = chartCssVars.grid, strokeOpacity = 1, strokeWidth = 1, strokeDasharray = "4,4",
  fadeHorizontal = true, fadeVertical = false,
}: GridProps) {
  const { xScale, yScale, innerWidth, innerHeight, orientation } = useChart();
  const isHorizontalBar = orientation === "horizontal";
  const columnScale = isHorizontalBar ? yScale : xScale;
  const uniqueId = useId();
  const hMaskId = `grid-rows-fade-${uniqueId}`;
  const hGradientId = `${hMaskId}-gradient`;
  const vMaskId = `grid-cols-fade-${uniqueId}`;
  const vGradientId = `${vMaskId}-gradient`;

  return (
    <g className="chart-grid">
      {horizontal && fadeHorizontal && (
        <defs>
          <linearGradient id={hGradientId} x1="0%" x2="100%" y1="0%" y2="0%">
            <stop offset="0%" style={{ stopColor: "white", stopOpacity: 0 }} />
            <stop offset="10%" style={{ stopColor: "white", stopOpacity: 1 }} />
            <stop offset="90%" style={{ stopColor: "white", stopOpacity: 1 }} />
            <stop offset="100%" style={{ stopColor: "white", stopOpacity: 0 }} />
          </linearGradient>
          <mask id={hMaskId}><rect fill={`url(#${hGradientId})`} height={innerHeight} width={innerWidth} x="0" y="0" /></mask>
        </defs>
      )}
      {vertical && fadeVertical && (
        <defs>
          <linearGradient id={vGradientId} x1="0%" x2="0%" y1="0%" y2="100%">
            <stop offset="0%" style={{ stopColor: "white", stopOpacity: 0 }} />
            <stop offset="10%" style={{ stopColor: "white", stopOpacity: 1 }} />
            <stop offset="90%" style={{ stopColor: "white", stopOpacity: 1 }} />
            <stop offset="100%" style={{ stopColor: "white", stopOpacity: 0 }} />
          </linearGradient>
          <mask id={vMaskId}><rect fill={`url(#${vGradientId})`} height={innerHeight} width={innerWidth} x="0" y="0" /></mask>
        </defs>
      )}
      {horizontal && (
        <g mask={fadeHorizontal ? `url(#${hMaskId})` : undefined}>
          <GridRows numTicks={rowTickValues ? undefined : numTicksRows} scale={yScale} stroke={stroke} strokeDasharray={strokeDasharray} strokeOpacity={strokeOpacity} strokeWidth={strokeWidth} tickValues={rowTickValues} width={innerWidth} />
        </g>
      )}
      {vertical && columnScale && typeof columnScale === "function" && (
        <g mask={fadeVertical ? `url(#${vMaskId})` : undefined}>
          <GridColumns height={innerHeight} numTicks={numTicksColumns} scale={columnScale} stroke={stroke} strokeDasharray={strokeDasharray} strokeOpacity={strokeOpacity} strokeWidth={strokeWidth} />
        </g>
      )}
    </g>
  );
}

Grid.displayName = "Grid";

// ─── BarXAxis ────────────────────────────────────────────────────────────────

export interface BarXAxisProps {
  tickerHalfWidth?: number;
  showAllLabels?: boolean;
  maxLabels?: number;
}

export function BarXAxis({ tickerHalfWidth = 50, showAllLabels = false, maxLabels = 12 }: BarXAxisProps) {
  const { xScale, margin, tooltipData, containerRef, bandWidth } = useChart();
  const [mounted, setMounted] = useState(false);
  useEffect(() => { setMounted(true); }, []);

  const labelsToShow = useMemo(() => {
    const domain = xScale.domain();
    if (domain.length === 0) return [];
    let labels = domain.map((label) => ({ label, x: (xScale(label) ?? 0) + bandWidth / 2 + margin.left }));
    if (!showAllLabels && labels.length > maxLabels) {
      const step = Math.ceil(labels.length / maxLabels);
      labels = labels.filter((_, i) => i % step === 0);
    }
    return labels;
  }, [xScale, margin.left, bandWidth, showAllLabels, maxLabels]);

  const isHovering = tooltipData !== null;
  const crosshairX = tooltipData ? tooltipData.x + margin.left : null;

  const container = containerRef.current;
  if (!(mounted && container)) return null;

  return createPortal(
    <div className="pointer-events-none absolute inset-0">
      {labelsToShow.map((item) => {
        let opacity = 1;
        if (isHovering && crosshairX !== null) {
          const fadeBuffer = 20;
          const fadeRadius = tickerHalfWidth + fadeBuffer;
          const distance = Math.abs(item.x - crosshairX);
          if (distance < tickerHalfWidth) opacity = 0;
          else if (distance < fadeRadius) opacity = (distance - tickerHalfWidth) / fadeBuffer;
        }
        return (
          <div className="absolute" key={item.label} style={{ left: item.x, bottom: 12, width: 0, display: "flex", justifyContent: "center" }}>
            <motion.span animate={{ opacity }} className="whitespace-nowrap text-neutral-500 text-xs" initial={{ opacity: 1 }} transition={{ duration: 0.4, ease: "easeInOut" }}>
              {item.label}
            </motion.span>
          </div>
        );
      })}
    </div>,
    container
  );
}

BarXAxis.displayName = "BarXAxis";

// ─── BarValueAxis (numeric Y-axis for vertical bar charts) ───────────────

export interface BarValueAxisProps {
  numTicks?: number;
  formatValue?: (value: number) => string;
}

export function BarValueAxis({ numTicks = 5, formatValue }: BarValueAxisProps) {
  const { yScale, margin, containerRef } = useChart();
  const [container, setContainer] = useState<HTMLDivElement | null>(null);

  useEffect(() => { setContainer(containerRef.current); }, [containerRef]);

  const ticks = useMemo(() => {
    const domain = yScale.domain() as [number, number];
    const min = domain[0];
    const max = domain[1];
    const step = (max - min) / (numTicks - 1);
    return Array.from({ length: numTicks }, (_, i) => {
      const value = min + step * i;
      return {
        value,
        y: (yScale(value) ?? 0) + margin.top,
        label: formatValue ? formatValue(value) : value >= 1000 ? `${(value / 1000).toFixed(value % 1000 === 0 ? 0 : 1)}k` : Math.round(value).toLocaleString(),
      };
    });
  }, [yScale, margin.top, numTicks, formatValue]);

  if (!container) return null;

  return createPortal(
    <div className="pointer-events-none absolute inset-0">
      {ticks.map((tick) => (
        <div key={tick.value} className="absolute" style={{ left: 0, top: tick.y, width: margin.left - 8, display: "flex", justifyContent: "flex-end", transform: "translateY(-50%)" }}>
          <span className="whitespace-nowrap text-neutral-500 text-xs tabular-nums">{tick.label}</span>
        </div>
      ))}
    </div>,
    container
  );
}

BarValueAxis.displayName = "BarValueAxis";

// ─── Bar ─────────────────────────────────────────────────────────────────────

export interface BarProps {
  dataKey: string;
  fill?: string;
  stroke?: string;
  lineCap?: "round" | "butt" | number;
  animate?: boolean;
  animationType?: "grow" | "fade";
  fadedOpacity?: number;
  staggerDelay?: number;
  stackGap?: number;
}

function resolveRadius(lineCap: "round" | "butt" | number, barWidth: number): number {
  if (lineCap === "butt") return 0;
  if (lineCap === "round") return barWidth / 2;
  return lineCap;
}

export function Bar({
  dataKey, fill = chartCssVars.linePrimary, stroke, lineCap = "round", animate = true,
  animationType = "grow", fadedOpacity = 0.3, staggerDelay, stackGap = 0,
}: BarProps) {
  const {
    data, xScale, yScale, innerHeight, innerWidth, bandWidth, hoveredBarIndex, isLoaded, animationDuration,
    xDataKey, orientation, stacked, stackOffsets, bars, barWidth: fixedBarWidth,
  } = useChart();

  const isHorizontal = orientation === "horizontal";
  const barIndex = bars.findIndex((b) => b.dataKey === dataKey);
  const barCount = bars.length;
  const singleBarWidth = stacked ? bandWidth : bandWidth / barCount;
  const actualBarWidth = fixedBarWidth ?? singleBarWidth;
  const radius = resolveRadius(lineCap, actualBarWidth);
  const autoStagger = staggerDelay ?? Math.min(0.06, 0.8 / data.length);

  return (
    <>
      {data.map((d, i) => {
        const category = String(d[xDataKey] ?? "");
        const value = typeof d[dataKey] === "number" ? (d[dataKey] as number) : 0;
        const bandStart = xScale(category) ?? 0;
        const stackOffset = stacked ? stackOffsets.get(i)?.get(dataKey) ?? 0 : 0;

        let barX: number, barY: number, barW: number, barH: number;

        if (isHorizontal) {
          const barLength = innerWidth - (yScale(value) ?? innerWidth);
          barY = bandStart + (stacked ? 0 : barIndex * singleBarWidth);
          barH = actualBarWidth;
          barW = barLength;
          barX = stacked ? stackOffset : 0;
          if (stacked && stackGap > 0 && barIndex > 0) { barX += stackGap; barW = Math.max(0, barW - stackGap); }
        } else {
          const scaledY = yScale(value) ?? innerHeight;
          barX = bandStart + (stacked ? 0 : barIndex * singleBarWidth);
          barW = actualBarWidth;
          barH = innerHeight - scaledY;
          barY = stacked ? scaledY - stackOffset : scaledY;
          if (stacked && stackGap > 0 && barIndex > 0) { barY += stackGap; barH = Math.max(0, barH - stackGap); }
        }

        if (barW <= 0 || barH <= 0) return null;

        const isHovered = hoveredBarIndex === i;
        const someoneHovered = hoveredBarIndex !== null;
        const barOpacity = someoneHovered ? (isHovered ? 1 : fadedOpacity) : 1;
        const delay = i * autoStagger;
        const r = Math.min(radius, barW / 2, barH / 2);

        let path: string;
        if (isHorizontal) {
          path = `M${barX},${barY} L${barX + barW - r},${barY} Q${barX + barW},${barY} ${barX + barW},${barY + r} L${barX + barW},${barY + barH - r} Q${barX + barW},${barY + barH} ${barX + barW - r},${barY + barH} L${barX},${barY + barH}Z`;
        } else {
          path = `M${barX},${barY + barH} L${barX},${barY + r} Q${barX},${barY} ${barX + r},${barY} L${barX + barW - r},${barY} Q${barX + barW},${barY} ${barX + barW},${barY + r} L${barX + barW},${barY + barH}Z`;
        }

        const originX = isHorizontal ? barX : barX + barW / 2;
        const originY = isHorizontal ? barY + barH / 2 : innerHeight;
        const shouldAnimateEntry = animate && !isLoaded;

        const growInitial = isHorizontal ? { scaleX: 0, opacity: 0 } : { scaleY: 0, opacity: 0 };
        const growAnimate = isHorizontal ? { scaleX: 1, opacity: barOpacity } : { scaleY: 1, opacity: barOpacity };
        const growTransition = {
          [isHorizontal ? "scaleX" : "scaleY"]: { duration: animationDuration / 1000, ease: [0.85, 0, 0.15, 1] as [number, number, number, number], delay },
          opacity: { duration: 0.3, ease: "easeInOut" as const },
        };

        return (
          <motion.path
            key={`${category}-${dataKey}`}
            d={path}
            fill={fill}
            style={{ transformOrigin: `${originX}px ${originY}px` }}
            initial={shouldAnimateEntry && animationType === "grow" ? growInitial : shouldAnimateEntry && animationType === "fade" ? { opacity: 0, filter: "blur(4px)" } : { opacity: barOpacity }}
            animate={shouldAnimateEntry && animationType === "grow" ? growAnimate : shouldAnimateEntry && animationType === "fade" ? { opacity: barOpacity, filter: "blur(0px)" } : { opacity: barOpacity }}
            transition={shouldAnimateEntry && animationType === "grow" ? growTransition : shouldAnimateEntry && animationType === "fade" ? { duration: 0.5, delay, ease: "easeOut" } : { opacity: { duration: 0.3, ease: "easeInOut" } }}
          />
        );
      })}
    </>
  );
}

Bar.displayName = "Bar";

// ─── Re-exports ──────────────────────────────────────────────────────────────

export { VisxLinearGradient as LinearGradient };

// ─── BarChart ────────────────────────────────────────────────────────────────

function extractBarConfigs(children: ReactNode): BarConfig[] {
  const configs: BarConfig[] = [];
  Children.forEach(children, (child) => {
    if (!isValidElement(child)) return;
    const childType = child.type as { displayName?: string; name?: string };
    const componentName = typeof child.type === "function" ? childType.displayName || childType.name || "" : "";
    const props = child.props as BarProps | undefined;
    const isBarComponent = componentName === "Bar" || child.type === Bar || (props && typeof props.dataKey === "string" && props.dataKey.length > 0);
    if (isBarComponent && props?.dataKey) {
      configs.push({ dataKey: props.dataKey, fill: props.fill || "var(--chart-line-primary)", stroke: props.stroke });
    }
  });
  return configs;
}

export interface BarChartProps {
  data: Record<string, unknown>[];
  xDataKey?: string;
  margin?: Partial<Margin>;
  animationDuration?: number;
  aspectRatio?: string;
  barGap?: number;
  barWidth?: number;
  orientation?: "vertical" | "horizontal";
  stacked?: boolean;
  stackGap?: number;
  className?: string;
  children: ReactNode;
}

const DEFAULT_MARGIN: Margin = { top: 40, right: 40, bottom: 40, left: 40 };

interface BarChartInnerProps {
  width: number;
  height: number;
  data: Record<string, unknown>[];
  xDataKey: string;
  margin: Margin;
  animationDuration: number;
  barGap: number;
  barWidth?: number;
  orientation: "vertical" | "horizontal";
  stacked: boolean;
  stackGap: number;
  children: ReactNode;
  containerRef: RefObject<HTMLDivElement | null>;
}

function BarChartInner({
  width, height, data, xDataKey, margin, animationDuration, barGap, barWidth, orientation, stacked, stackGap, children, containerRef,
}: BarChartInnerProps) {
  const [isLoaded, setIsLoaded] = useState(false);
  const [hoveredBarIndex, setHoveredBarIndex] = useState<number | null>(null);
  const bars = useMemo(() => extractBarConfigs(children), [children]);
  const innerWidth = width - margin.left - margin.right;
  const innerHeight = height - margin.top - margin.bottom;
  const isHorizontal = orientation === "horizontal";

  const xScale = useMemo(() => {
    const domain = data.map((d) => String(d[xDataKey] ?? ""));
    return scaleBand<string>({ range: isHorizontal ? [0, innerHeight] : [0, innerWidth], domain, padding: barGap });
  }, [data, xDataKey, innerWidth, innerHeight, barGap, isHorizontal]);

  const bandWidth = xScale.bandwidth();

  const yScale = useMemo(() => {
    let maxValue = 0;
    if (stacked) {
      for (const d of data) { let sum = 0; for (const bar of bars) { const v = d[bar.dataKey]; if (typeof v === "number") sum += v; } if (sum > maxValue) maxValue = sum; }
    } else {
      for (const bar of bars) { for (const d of data) { const v = d[bar.dataKey]; if (typeof v === "number" && v > maxValue) maxValue = v; } }
    }
    if (maxValue === 0) maxValue = 100;
    return scaleLinear<number>({ range: isHorizontal ? [innerWidth, 0] : [innerHeight, 0], domain: [0, maxValue * 1.1], nice: true });
  }, [data, bars, innerWidth, innerHeight, stacked, isHorizontal]);

  const stackOffsets = useMemo(() => {
    if (!stacked) return new Map<number, Map<string, number>>();
    const offsets = new Map<number, Map<string, number>>();
    for (let i = 0; i < data.length; i++) {
      const d = data[i]!;
      let cumulative = 0;
      const barOffsets = new Map<string, number>();
      for (const bar of bars) {
        barOffsets.set(bar.dataKey, cumulative);
        const v = d[bar.dataKey];
        if (typeof v === "number") { cumulative += isHorizontal ? innerWidth - (yScale(v) ?? innerWidth) : innerHeight - (yScale(v) ?? innerHeight); }
      }
      offsets.set(i, barOffsets);
    }
    return offsets;
  }, [data, bars, stacked, yScale, innerHeight, innerWidth, isHorizontal]);

  const [tooltipData, setTooltipData] = useState<TooltipData | null>(null);

  useEffect(() => { const timer = setTimeout(() => setIsLoaded(true), animationDuration); return () => clearTimeout(timer); }, [animationDuration]);

  const handleMouseMove = useCallback((event: React.MouseEvent<SVGGElement>) => {
    const point = localPoint(event);
    if (!point) return;
    const chartX = point.x - margin.left;
    const chartY = point.y - margin.top;
    const domain = xScale.domain();
    let foundIndex = -1;
    for (let i = 0; i < domain.length; i++) {
      const cat = domain[i]!;
      const bandStart = xScale(cat) ?? 0;
      const bandEnd = bandStart + bandWidth;
      if (isHorizontal ? (chartY >= bandStart && chartY <= bandEnd) : (chartX >= bandStart && chartX <= bandEnd)) { foundIndex = i; break; }
    }
    if (foundIndex >= 0) {
      setHoveredBarIndex(foundIndex);
      const d = data[foundIndex]!;
      const yPositions: Record<string, number> = {};
      const xPositions: Record<string, number> = {};
      for (const bar of bars) {
        const v = d[bar.dataKey];
        if (typeof v === "number") {
          if (isHorizontal) { xPositions[bar.dataKey] = innerWidth - (yScale(v) ?? innerWidth); yPositions[bar.dataKey] = (xScale(domain[foundIndex]!) ?? 0) + bandWidth / 2; }
          else { yPositions[bar.dataKey] = yScale(v) ?? 0; xPositions[bar.dataKey] = (xScale(domain[foundIndex]!) ?? 0) + bandWidth / 2; }
        }
      }
      const tooltipX = isHorizontal ? innerWidth - (yScale(Number(d[bars[0]?.dataKey ?? ""] ?? 0)) ?? 0) : (xScale(domain[foundIndex]!) ?? 0) + bandWidth / 2;
      setTooltipData({ point: d, index: foundIndex, x: tooltipX, yPositions, xPositions });
    } else { setHoveredBarIndex(null); setTooltipData(null); }
  }, [xScale, yScale, data, bars, margin, bandWidth, isHorizontal, innerWidth]);

  const handleMouseLeave = useCallback(() => { setHoveredBarIndex(null); setTooltipData(null); }, []);

  if (width < 10 || height < 10) return null;

  const contextValue: BarChartContextValue = {
    data, xScale, yScale, width, height, innerWidth, innerHeight, margin, bandWidth, tooltipData, setTooltipData, containerRef, bars, isLoaded, animationDuration, xDataKey, hoveredBarIndex, setHoveredBarIndex, orientation, stacked, stackGap, stackOffsets, barGap, barWidth,
  };

  return (
    <BarChartProvider value={contextValue}>
      <svg aria-hidden="true" height={height} width={width}>
        <rect fill="transparent" height={height} width={width} x={0} y={0} />
        <g onMouseMove={isLoaded ? handleMouseMove : undefined} onMouseLeave={isLoaded ? handleMouseLeave : undefined} style={{ cursor: isLoaded ? "crosshair" : "default", touchAction: "none" }} transform={`translate(${margin.left},${margin.top})`}>
          <rect fill="transparent" height={innerHeight} width={innerWidth} x={0} y={0} />
          {children}
        </g>
      </svg>
    </BarChartProvider>
  );
}

export function BarChart({
  data, xDataKey = "name", margin: marginProp, animationDuration = 1100, aspectRatio = "2 / 1",
  barGap = 0.2, barWidth, orientation = "vertical", stacked = false, stackGap = 0, className = "", children,
}: BarChartProps) {
  const containerRef = useRef<HTMLDivElement>(null);
  const margin = { ...DEFAULT_MARGIN, ...marginProp };

  return (
    <div className={cn("relative w-full", className)} ref={containerRef} style={{ aspectRatio, touchAction: "none" }}>
      <ParentSize debounceTime={10}>
        {({ width, height }) => (
          <BarChartInner animationDuration={animationDuration} barGap={barGap} barWidth={barWidth} containerRef={containerRef} data={data} height={height} margin={margin} orientation={orientation} stacked={stacked} stackGap={stackGap} width={width} xDataKey={xDataKey}>
            {children}
          </BarChartInner>
        )}
      </ParentSize>
    </div>
  );
}

export default BarChart;
