"use client";

import React, { useState, useCallback, useMemo, useRef } from "react";
import { useSpring, animated, config, to } from "@react-spring/web";
import { useDrag } from "@use-gesture/react";
import NumberFlow from "@number-flow/react";
import { useAbacusConfig, getDefaultAbacusConfig } from "./AbacusContext";
import { playBeadSound } from "./soundManager";
import * as Abacus3DUtils from "./Abacus3DUtils";
import "./Abacus3D.css";

// Types
export interface BeadConfig {
  type: "heaven" | "earth";
  value: number;
  active: boolean;
  position: number; // 0-based position within its type group
  placeValue: ValidPlaceValues; // 0=ones, 1=tens, 2=hundreds, etc. - NATIVE place-value architecture!
}

// Comprehensive styling system
export interface BeadStyle {
  fill?: string;
  stroke?: string;
  strokeWidth?: number;
  opacity?: number;
  cursor?: string;
  className?: string;
}

export interface ColumnPostStyle {
  fill?: string;
  stroke?: string;
  strokeWidth?: number;
  opacity?: number;
  className?: string;
}

export interface ReckoningBarStyle {
  fill?: string;
  stroke?: string;
  strokeWidth?: number;
  opacity?: number;
  className?: string;
}

export interface NumeralStyle {
  color?: string;
  backgroundColor?: string;
  fontSize?: string;
  fontFamily?: string;
  fontWeight?: string;
  borderColor?: string;
  borderWidth?: number;
  borderRadius?: number;
  className?: string;
}

export interface BackgroundGlowStyle {
  fill?: string;
  blur?: number;
  spread?: number;
  opacity?: number;
}

export interface AbacusCustomStyles {
  // Global defaults
  heavenBeads?: BeadStyle;
  earthBeads?: BeadStyle;
  activeBeads?: BeadStyle;
  inactiveBeads?: BeadStyle;
  columnPosts?: ColumnPostStyle;
  reckoningBar?: ReckoningBarStyle;
  numerals?: NumeralStyle;
  numeralContainers?: NumeralStyle;

  // Column-specific overrides (by column index)
  columns?: {
    [columnIndex: number]: {
      heavenBeads?: BeadStyle;
      earthBeads?: BeadStyle;
      activeBeads?: BeadStyle;
      inactiveBeads?: BeadStyle;
      columnPost?: ColumnPostStyle;
      numerals?: NumeralStyle;
      numeralContainer?: NumeralStyle;
      backgroundGlow?: BackgroundGlowStyle;
    };
  };

  // Individual bead overrides (by column and bead position)
  beads?: {
    [columnIndex: number]: {
      heaven?: BeadStyle;
      earth?: {
        [position: number]: BeadStyle; // position 0-3 for earth beads
      };
    };
  };
}

// Branded types to prevent mixing place values and column indices
export type PlaceValue = number & { readonly __brand: "PlaceValue" };
export type ColumnIndex = number & { readonly __brand: "ColumnIndex" };

// Type-safe constructors
export const PlaceValue = (value: number): PlaceValue => {
  if (value < 0) {
    throw new Error(`Place value must be non-negative, got ${value}`);
  }
  return value as PlaceValue;
};

export const ColumnIndex = (value: number): ColumnIndex => {
  if (value < 0) {
    throw new Error(`Column index must be non-negative, got ${value}`);
  }
  return value as ColumnIndex;
};

// Utility types for better type safety
export type ValidPlaceValues = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
export type EarthBeadPosition = 0 | 1 | 2 | 3;

// Place-value based bead specification (new API)
export interface PlaceValueBead {
  placeValue: ValidPlaceValues; // 0=ones, 1=tens, 2=hundreds, etc.
  beadType: "heaven" | "earth";
  position?: EarthBeadPosition; // for earth beads, 0-3
}

// Legacy column-index based bead specification
export interface ColumnIndexBead {
  columnIndex: number; // array index (0=leftmost)
  beadType: "heaven" | "earth";
  position?: EarthBeadPosition; // for earth beads, 0-3
}

// Type-safe conversion utilities
export namespace PlaceValueUtils {
  export function toColumnIndex(
    placeValue: ValidPlaceValues,
    totalColumns: number,
  ): number {
    const result = totalColumns - 1 - placeValue;
    if (result < 0 || result >= totalColumns) {
      throw new Error(
        `Place value ${placeValue} is out of range for ${totalColumns} columns`,
      );
    }
    return result;
  }

  export function fromColumnIndex(
    columnIndex: number,
    totalColumns: number,
  ): ValidPlaceValues {
    const result = totalColumns - 1 - columnIndex;
    if (result < 0 || result > 9) {
      throw new Error(
        `Column index ${columnIndex} maps to invalid place value ${result}`,
      );
    }
    return result as ValidPlaceValues;
  }

  // Type-safe creation helpers
  export const ones = (): PlaceValueBead["placeValue"] => 0;
  export const tens = (): PlaceValueBead["placeValue"] => 1;
  export const hundreds = (): PlaceValueBead["placeValue"] => 2;
  export const thousands = (): PlaceValueBead["placeValue"] => 3;
}

// Union type for backward compatibility
export type BeadHighlight = PlaceValueBead | ColumnIndexBead;

// Enhanced bead highlight with step progression and direction indicators
export interface StepBeadHighlight extends PlaceValueBead {
  stepIndex: number; // Which instruction step this bead belongs to
  direction: "up" | "down" | "activate" | "deactivate"; // Movement direction
  order?: number; // Order within the step (for multiple beads per step)
}

// Type guards to distinguish between the two APIs
export function isPlaceValueBead(bead: BeadHighlight): bead is PlaceValueBead {
  return "placeValue" in bead;
}

export function isColumnIndexBead(
  bead: BeadHighlight,
): bead is ColumnIndexBead {
  return "columnIndex" in bead;
}

// Event system
export interface BeadClickEvent {
  bead: BeadConfig;
  columnIndex: number;
  beadType: "heaven" | "earth";
  position: number;
  active: boolean;
  value: number;
  event: React.MouseEvent;
}

export interface AbacusCallbacks {
  onBeadClick?: (event: BeadClickEvent) => void;
  onBeadHover?: (event: BeadClickEvent) => void;
  onBeadLeave?: (event: BeadClickEvent) => void;
  onColumnClick?: (columnIndex: number, event: React.MouseEvent) => void;
  onColumnHover?: (columnIndex: number, event: React.MouseEvent) => void;
  onColumnLeave?: (columnIndex: number, event: React.MouseEvent) => void;
  onNumeralClick?: (
    columnIndex: number,
    value: number,
    event: React.MouseEvent,
  ) => void;
  onValueChange?: (newValue: number | bigint) => void;
  onBeadRef?: (bead: BeadConfig, element: SVGElement | null) => void;
  // Legacy callback for backward compatibility
  onClick?: (bead: BeadConfig) => void;
}

// Overlay and injection system
export interface AbacusOverlay {
  id: string;
  type: "tooltip" | "arrow" | "highlight" | "custom";
  target: {
    type: "bead" | "column" | "numeral" | "bar" | "coordinates";
    columnIndex?: number;
    beadType?: "heaven" | "earth";
    beadPosition?: number; // for earth beads
    x?: number; // for coordinate-based positioning
    y?: number;
  };
  content: React.ReactNode;
  style?: React.CSSProperties;
  className?: string;
  offset?: { x: number; y: number };
  visible?: boolean;
}

// 3D Enhancement Configuration
export type BeadMaterial = "glossy" | "satin" | "matte";
export type FrameMaterial = "wood" | "metal" | "minimal";
export type LightingStyle = "top-down" | "ambient" | "dramatic";

export interface Abacus3DMaterial {
  heavenBeads?: BeadMaterial;
  earthBeads?: BeadMaterial;
  frame?: FrameMaterial;
  lighting?: LightingStyle;
  woodGrain?: boolean; // Add wood texture to frame
}

export interface Abacus3DPhysics {
  wobble?: boolean; // Beads rotate slightly during movement
  clackEffect?: boolean; // Visual ripple when beads snap
  hoverParallax?: boolean; // Beads lift on hover
  particleSnap?: "off" | "subtle" | "sparkle"; // Particle effects on snap
  hapticFeedback?: boolean; // Trigger haptic feedback on mobile
}

export interface AbacusConfig {
  // Basic configuration
  value?: number | bigint;
  columns?: number | "auto";
  showEmptyColumns?: boolean;
  hideInactiveBeads?: boolean;
  beadShape?: "diamond" | "square" | "circle";
  colorScheme?: "monochrome" | "place-value" | "alternating" | "heaven-earth";
  colorPalette?: "default" | "colorblind" | "mnemonic" | "grayscale" | "nature";
  scaleFactor?: number;
  animated?: boolean;
  interactive?: boolean;
  gestures?: boolean;
  showNumbers?: boolean;
  soundEnabled?: boolean;
  soundVolume?: number;

  // 3D Enhancement
  enhanced3d?: boolean | "subtle" | "realistic" | "delightful";
  material3d?: Abacus3DMaterial;
  physics3d?: Abacus3DPhysics;

  // Advanced customization
  customStyles?: AbacusCustomStyles;
  callbacks?: AbacusCallbacks;
  overlays?: AbacusOverlay[];

  // Tutorial and accessibility features
  highlightColumns?: number[]; // Highlight specific columns (legacy - array indices)
  highlightBeads?: BeadHighlight[]; // Support both place-value and column-index based highlighting
  stepBeadHighlights?: StepBeadHighlight[]; // Progressive step-based highlighting with directions
  currentStep?: number; // Current step index for progressive highlighting
  showDirectionIndicators?: boolean; // Show direction arrows/indicators on beads
  disabledColumns?: number[]; // Disable interaction on specific columns (legacy - array indices)
  disabledBeads?: BeadHighlight[]; // Support both place-value and column-index based disabling

  // Legacy callbacks for backward compatibility
  onClick?: (bead: BeadConfig) => void;
  onValueChange?: (newValue: number | bigint) => void;
}

export interface AbacusDimensions {
  width: number;
  height: number;
  rodSpacing: number;
  beadSize: number;
  rodWidth: number;
  barThickness: number;
  heavenEarthGap: number;
  activeGap: number;
  inactiveGap: number;
  adjacentSpacing: number;
}

// Hooks
export function useAbacusDimensions(
  columns: number,
  scaleFactor: number = 1,
  showNumbers: boolean = false,
): AbacusDimensions {
  return useMemo(() => {
    // Exact Typst parameters (lines 33-39 in flashcards.typ)
    const rodWidth = 3 * scaleFactor;
    const beadSize = 12 * scaleFactor;
    const adjacentSpacing = 0.5 * scaleFactor; // Minimal spacing for adjacent beads of same type
    const columnSpacing = 25 * scaleFactor; // rod spacing
    const heavenEarthGap = 30 * scaleFactor;
    const barThickness = 2 * scaleFactor;

    // Positioning gaps (lines 169-170 in flashcards.typ)
    const activeGap = 1 * scaleFactor; // Gap between active beads and reckoning bar
    const inactiveGap = 8 * scaleFactor; // Gap between inactive beads and active beads/bar

    // Calculate total dimensions based on Typst logic (line 154-155)
    const totalWidth = columns * columnSpacing;
    const baseHeight =
      heavenEarthGap + 5 * (beadSize + 4 * scaleFactor) + 10 * scaleFactor;

    // Add space for numbers if they are visible
    const numbersSpace = 40 * scaleFactor; // Space for NumberFlow components
    const totalHeight = showNumbers ? baseHeight + numbersSpace : baseHeight;

    return {
      width: totalWidth,
      height: totalHeight,
      rodSpacing: columnSpacing,
      beadSize,
      rodWidth,
      barThickness,
      heavenEarthGap,
      activeGap,
      inactiveGap,
      adjacentSpacing,
    };
  }, [columns, scaleFactor, showNumbers]);
}

// Legacy column state interface (deprecated)
interface ColumnState {
  heavenActive: boolean; // true if heaven bead (value 5) is active
  earthActive: number; // 0-4, number of active earth beads
}

// Native place-value state (no more array indices!)
export interface PlaceState {
  placeValue: ValidPlaceValues;
  heavenActive: boolean;
  earthActive: number; // 0-4, number of active earth beads
}

// State map keyed by place value - this eliminates the indexing nightmare!
export type PlaceStatesMap = Map<ValidPlaceValues, PlaceState>;

/**
 * @deprecated Use useAbacusPlaceStates() instead.
 * This hook uses array-based column indexing which requires totalColumns threading.
 * The new hook uses Map-based place values for cleaner architecture.
 */
export function useAbacusState(
  initialValue: number = 0,
  targetColumns?: number,
) {
  // Initialize state from the initial value
  const initializeFromValue = useCallback(
    (value: number, minColumns?: number): ColumnState[] => {
      if (value === 0) {
        // Special case: for value 0, use minColumns if provided, otherwise single column
        const columnCount = minColumns || 1;
        return Array(columnCount)
          .fill(null)
          .map(() => ({ heavenActive: false, earthActive: 0 }));
      }
      const digits = value.toString().split("").map(Number);
      const result = digits.map((digit) => ({
        heavenActive: digit >= 5,
        earthActive: digit % 5,
      }));

      // Ensure we have at least minColumns if specified
      if (minColumns && result.length < minColumns) {
        const paddingNeeded = minColumns - result.length;
        const padding = Array(paddingNeeded)
          .fill(null)
          .map(() => ({ heavenActive: false, earthActive: 0 }));
        return [...padding, ...result];
      }

      return result;
    },
    [targetColumns],
  );

  const [columnStates, setColumnStates] = useState<ColumnState[]>(() =>
    initializeFromValue(initialValue, targetColumns),
  );

  // Calculate current value from independent column states
  const value = useMemo(() => {
    return columnStates.reduce((total, columnState, index) => {
      const placeValue = Math.pow(10, columnStates.length - index - 1);
      const columnValue =
        (columnState.heavenActive ? 5 : 0) + columnState.earthActive;
      return total + columnValue * placeValue;
    }, 0);
  }, [columnStates]);

  const setValue = useCallback(
    (newValue: number) => {
      setColumnStates(initializeFromValue(newValue, targetColumns));
    },
    [initializeFromValue, targetColumns],
  );

  const getColumnState = useCallback(
    (columnIndex: number): ColumnState => {
      return (
        columnStates[columnIndex] || { heavenActive: false, earthActive: 0 }
      );
    },
    [columnStates],
  );

  const setColumnState = useCallback(
    (columnIndex: number, newState: ColumnState) => {
      setColumnStates((prev) => {
        const newStates = [...prev];
        // Extend array if necessary
        while (newStates.length <= columnIndex) {
          newStates.push({ heavenActive: false, earthActive: 0 });
        }
        newStates[columnIndex] = newState;
        return newStates;
      });
    },
    [],
  );

  const toggleBead = useCallback(
    (bead: BeadConfig) => {
      // Convert place value to column index for this legacy hook
      const placeIndex = columnStates.length - 1 - bead.placeValue;
      const currentState = getColumnState(placeIndex);

      if (bead.type === "heaven") {
        // Toggle heaven bead independently
        setColumnState(placeIndex, {
          ...currentState,
          heavenActive: !currentState.heavenActive,
        });
      } else {
        // Toggle earth bead - affects the number of active earth beads
        if (bead.active) {
          // Deactivate this bead and all higher positioned earth beads
          setColumnState(placeIndex, {
            ...currentState,
            earthActive: Math.min(currentState.earthActive, bead.position),
          });
        } else {
          // Activate this bead and all lower positioned earth beads
          setColumnState(placeIndex, {
            ...currentState,
            earthActive: Math.max(currentState.earthActive, bead.position + 1),
          });
        }
      }
    },
    [getColumnState, setColumnState, columnStates.length],
  );

  return {
    value,
    setValue,
    columnStates,
    getColumnState,
    setColumnState,
    toggleBead,
  };
}

// NEW: Native place-value state management hook (eliminates the column index nightmare!)
export function useAbacusPlaceStates(
  controlledValue: number | bigint = 0,
  maxPlaceValue: ValidPlaceValues = 4,
) {
  // Initialize state from value using place values as keys - NO MORE ARRAY INDICES!
  const initializeFromValue = useCallback(
    (value: number | bigint): PlaceStatesMap => {
      const states = new Map<ValidPlaceValues, PlaceState>();

      // Convert to string to handle both number and bigint
      const valueStr = value.toString();
      const digits = valueStr.split('').map(Number);

      // Always create ALL place values from 0 to maxPlaceValue (to match columns)
      for (let place = 0; place <= maxPlaceValue; place++) {
        // Get digit from right: place 0 = rightmost, place 1 = second from right, etc.
        const digitIndex = digits.length - 1 - place;
        const digit = digitIndex >= 0 ? digits[digitIndex] : 0;

        states.set(place as ValidPlaceValues, {
          placeValue: place as ValidPlaceValues,
          heavenActive: digit >= 5,
          earthActive: digit >= 5 ? digit - 5 : digit,
        });
      }

      return states;
    },
    [maxPlaceValue],
  );

  const [placeStates, setPlaceStates] = useState<PlaceStatesMap>(() =>
    initializeFromValue(controlledValue),
  );

  // Calculate current value from place states - NO MORE INDEX MATH!
  // Use BigInt for numbers that exceed safe integer range (>15 digits)
  const value = useMemo(() => {
    // Check if we need BigInt (maxPlaceValue > 14 means >15 digits)
    const useBigInt = maxPlaceValue > 14;

    if (useBigInt) {
      let total = 0n;
      placeStates.forEach((state) => {
        const placeValueNum = 10n ** BigInt(state.placeValue);
        const digitValue = BigInt((state.heavenActive ? 5 : 0) + state.earthActive);
        total += digitValue * placeValueNum;
      });
      return total;
    } else {
      let total = 0;
      placeStates.forEach((state) => {
        const placeValueNum = Math.pow(10, state.placeValue);
        const digitValue = (state.heavenActive ? 5 : 0) + state.earthActive;
        total += digitValue * placeValueNum;
      });
      return total;
    }
  }, [placeStates, maxPlaceValue]);

  const setValue = useCallback(
    (newValue: number | bigint) => {
      setPlaceStates(initializeFromValue(newValue));
    },
    [initializeFromValue],
  );

  // Update internal state when external controlled value changes
  // Only update if the controlled value is different from our current value
  // This prevents infinite loops while allowing controlled updates
  React.useEffect(() => {
    const currentInternalValue = value;
    if (controlledValue !== currentInternalValue) {
      setPlaceStates(initializeFromValue(controlledValue));
    }
  }, [controlledValue, initializeFromValue, value]);

  // Clean up place states when maxPlaceValue decreases (columns decrease)
  // This prevents stale place values from causing out-of-bounds access
  React.useEffect(() => {
    setPlaceStates((prev) => {
      const newStates = new Map(prev);
      let hasChanges = false;

      // Remove any place values greater than maxPlaceValue
      for (const placeValue of newStates.keys()) {
        if (placeValue > maxPlaceValue) {
          newStates.delete(placeValue);
          hasChanges = true;
        }
      }

      // Add missing place values up to maxPlaceValue
      for (let place = 0; place <= maxPlaceValue; place++) {
        if (!newStates.has(place as ValidPlaceValues)) {
          newStates.set(place as ValidPlaceValues, {
            placeValue: place as ValidPlaceValues,
            heavenActive: false,
            earthActive: 0,
          });
          hasChanges = true;
        }
      }

      return hasChanges ? newStates : prev;
    });
  }, [maxPlaceValue]);

  const getPlaceState = useCallback(
    (placeValue: ValidPlaceValues): PlaceState => {
      return (
        placeStates.get(placeValue) || {
          placeValue,
          heavenActive: false,
          earthActive: 0,
        }
      );
    },
    [placeStates],
  );

  const setPlaceState = useCallback(
    (
      placeValue: ValidPlaceValues,
      newState: Omit<PlaceState, "placeValue">,
    ) => {
      setPlaceStates((prev) => {
        const newStates = new Map(prev);
        newStates.set(placeValue, { placeValue, ...newState });
        return newStates;
      });
    },
    [],
  );

  const toggleBead = useCallback(
    (bead: BeadConfig) => {
      const currentState = getPlaceState(bead.placeValue);

      if (bead.type === "heaven") {
        setPlaceState(bead.placeValue, {
          ...currentState,
          heavenActive: !currentState.heavenActive,
        });
      } else {
        // Earth bead toggle logic - same as legacy but cleaner
        if (bead.active) {
          // Deactivate this bead and all higher positioned earth beads
          setPlaceState(bead.placeValue, {
            ...currentState,
            earthActive: Math.min(currentState.earthActive, bead.position),
          });
        } else {
          // Activate this bead and all lower positioned earth beads
          setPlaceState(bead.placeValue, {
            ...currentState,
            earthActive: Math.max(currentState.earthActive, bead.position + 1),
          });
        }
      }
    },
    [getPlaceState, setPlaceState],
  );

  return {
    value,
    setValue,
    placeStates,
    getPlaceState,
    setPlaceState,
    toggleBead,
  };
}

// Utility functions for customization system
function mergeBeadStyles(
  baseStyle: BeadStyle,
  customStyles?: AbacusCustomStyles,
  columnIndex?: number,
  beadType?: "heaven" | "earth",
  position?: number,
  isActive?: boolean,
): BeadStyle {
  let mergedStyle = { ...baseStyle };

  // Apply global bead type styles
  if (customStyles?.heavenBeads && beadType === "heaven") {
    mergedStyle = { ...mergedStyle, ...customStyles.heavenBeads };
  }
  if (customStyles?.earthBeads && beadType === "earth") {
    mergedStyle = { ...mergedStyle, ...customStyles.earthBeads };
  }

  // Apply active/inactive styles
  if (isActive && customStyles?.activeBeads) {
    mergedStyle = { ...mergedStyle, ...customStyles.activeBeads };
  }
  if (!isActive && customStyles?.inactiveBeads) {
    mergedStyle = { ...mergedStyle, ...customStyles.inactiveBeads };
  }

  // Apply column-specific styles
  if (columnIndex !== undefined && customStyles?.columns?.[columnIndex]) {
    const columnStyles = customStyles.columns[columnIndex];
    if (columnStyles.heavenBeads && beadType === "heaven") {
      mergedStyle = { ...mergedStyle, ...columnStyles.heavenBeads };
    }
    if (columnStyles.earthBeads && beadType === "earth") {
      mergedStyle = { ...mergedStyle, ...columnStyles.earthBeads };
    }
    if (isActive && columnStyles.activeBeads) {
      mergedStyle = { ...mergedStyle, ...columnStyles.activeBeads };
    }
    if (!isActive && columnStyles.inactiveBeads) {
      mergedStyle = { ...mergedStyle, ...columnStyles.inactiveBeads };
    }
  }

  // Apply individual bead styles (highest specificity)
  if (columnIndex !== undefined && customStyles?.beads?.[columnIndex]) {
    const beadStyles = customStyles.beads[columnIndex];
    if (beadType === "heaven" && beadStyles.heaven) {
      mergedStyle = { ...mergedStyle, ...beadStyles.heaven };
    }
    if (
      beadType === "earth" &&
      position !== undefined &&
      beadStyles.earth?.[position]
    ) {
      mergedStyle = { ...mergedStyle, ...beadStyles.earth[position] };
    }
  }

  return mergedStyle;
}

// REMOVED: normalizeBeadHighlight function - no longer needed with place-value architecture!

/**
 * @deprecated Use isBeadHighlightedByPlaceValue() instead.
 * This function requires totalColumns threading which the new architecture eliminates.
 */
function isBeadHighlighted(
  columnIndex: number,
  beadType: "heaven" | "earth",
  position: number | undefined,
  highlightBeads?: BeadHighlight[],
  totalColumns?: number,
): boolean {
  if (!highlightBeads || !totalColumns) return false;

  return highlightBeads.some((highlight) => {
    // Convert column index to place value for pure place-value API
    const targetPlaceValue = totalColumns - 1 - columnIndex;
    if ("placeValue" in highlight) {
      return (
        highlight.placeValue === targetPlaceValue &&
        highlight.beadType === beadType &&
        (highlight.position === undefined || highlight.position === position)
      );
    } else {
      return (
        highlight.columnIndex === columnIndex &&
        highlight.beadType === beadType &&
        (highlight.position === undefined || highlight.position === position)
      );
    }
  });
}

/**
 * @deprecated Use isBeadDisabledByPlaceValue() instead.
 * This function requires totalColumns threading which the new architecture eliminates.
 */
function isBeadDisabled(
  columnIndex: number,
  beadType: "heaven" | "earth",
  position: number | undefined,
  disabledColumns?: number[],
  disabledBeads?: BeadHighlight[],
  totalColumns?: number,
): boolean {
  // Check if entire column is disabled (legacy column index system)
  if (disabledColumns?.includes(columnIndex)) {
    return true;
  }

  // Check if specific bead is disabled
  if (!disabledBeads || !totalColumns) return false;

  return disabledBeads.some((disabled) => {
    // Convert column index to place value for pure place-value API
    const targetPlaceValue = totalColumns - 1 - columnIndex;
    if ("placeValue" in disabled) {
      return (
        disabled.placeValue === targetPlaceValue &&
        disabled.beadType === beadType &&
        (disabled.position === undefined || disabled.position === position)
      );
    } else {
      return (
        disabled.columnIndex === columnIndex &&
        disabled.beadType === beadType &&
        (disabled.position === undefined || disabled.position === position)
      );
    }
  });
}

// NEW: Native place-value highlighting (eliminates totalColumns threading!)
function isBeadHighlightedByPlaceValue(
  bead: BeadConfig,
  highlightBeads?: BeadHighlight[],
): boolean {
  if (!highlightBeads) return false;

  return highlightBeads.some((highlight) => {
    // Direct place value matching - NO MORE CONVERSION NEEDED!
    if ("placeValue" in highlight) {
      return (
        highlight.placeValue === bead.placeValue &&
        highlight.beadType === bead.type &&
        (highlight.position === undefined ||
          highlight.position === bead.position)
      );
    }

    // Legacy columnIndex support - convert to place value for comparison
    if ("columnIndex" in highlight) {
      // We need to know total columns to convert - for now, warn about legacy usage
      console.warn(
        "Legacy columnIndex highlighting detected - migrate to placeValue API for better performance",
      );
      return false; // Cannot properly support without totalColumns threading
    }

    return false;
  });
}

// NEW: Step-based highlighting with progressive revelation
function getBeadStepHighlight(
  bead: BeadConfig,
  stepBeadHighlights?: StepBeadHighlight[],
  currentStep?: number,
): { isHighlighted: boolean; direction?: string; isCurrentStep: boolean } {
  if (!stepBeadHighlights || currentStep === undefined) {
    return { isHighlighted: false, isCurrentStep: false };
  }

  const matchingStepBead = stepBeadHighlights.find((stepBead) => {
    const matches =
      stepBead.placeValue === bead.placeValue &&
      stepBead.beadType === bead.type &&
      (stepBead.position === undefined || stepBead.position === bead.position);

    return matches;
  });

  if (!matchingStepBead) {
    return { isHighlighted: false, isCurrentStep: false };
  }

  const isCurrentStep = matchingStepBead.stepIndex === currentStep;
  const isCompleted = matchingStepBead.stepIndex < currentStep;
  const isHighlighted = isCurrentStep || isCompleted;

  return {
    isHighlighted,
    direction: isCurrentStep ? matchingStepBead.direction : undefined,
    isCurrentStep,
  };
}

// NEW: Native place-value disabling (eliminates totalColumns threading!)
function isBeadDisabledByPlaceValue(
  bead: BeadConfig,
  disabledBeads?: BeadHighlight[],
): boolean {
  if (!disabledBeads) return false;

  return disabledBeads.some((disabled) => {
    // Direct place value matching - NO MORE CONVERSION NEEDED!
    if ("placeValue" in disabled) {
      return (
        disabled.placeValue === bead.placeValue &&
        disabled.beadType === bead.type &&
        (disabled.position === undefined || disabled.position === bead.position)
      );
    }

    // Legacy columnIndex support - convert to place value for comparison
    if ("columnIndex" in disabled) {
      // We need to know total columns to convert - for now, warn about legacy usage
      console.warn(
        "Legacy columnIndex disabling detected - migrate to placeValue API for better performance",
      );
      return false; // Cannot properly support without totalColumns threading
    }

    return false;
  });
}

function calculateOverlayPosition(
  overlay: AbacusOverlay,
  dimensions: AbacusDimensions,
  columnIndex?: number,
  beadPosition?: { x: number; y: number },
): { x: number; y: number } {
  let x = 0;
  let y = 0;

  switch (overlay.target.type) {
    case "coordinates":
      x = overlay.target.x || 0;
      y = overlay.target.y || 0;
      break;

    case "bead":
      if (beadPosition) {
        x = beadPosition.x;
        y = beadPosition.y;
      }
      break;

    case "column":
      if (overlay.target.columnIndex !== undefined) {
        x =
          overlay.target.columnIndex * dimensions.rodSpacing +
          dimensions.rodSpacing / 2;
        y = dimensions.height / 2;
      }
      break;

    case "numeral":
      if (overlay.target.columnIndex !== undefined) {
        x =
          overlay.target.columnIndex * dimensions.rodSpacing +
          dimensions.rodSpacing / 2;
        const baseHeight =
          dimensions.heavenEarthGap + 5 * (dimensions.beadSize + 4) + 10;
        y = baseHeight + 25;
      }
      break;

    case "bar":
      x = dimensions.width / 2;
      y = dimensions.heavenEarthGap;
      break;
  }

  // Apply offset
  if (overlay.offset) {
    x += overlay.offset.x;
    y += overlay.offset.y;
  }

  return { x, y };
}

// Color palettes
const COLOR_PALETTES = {
  default: ["#2E86AB", "#A23B72", "#F18F01", "#6A994E", "#BC4B51"],
  colorblind: ["#0173B2", "#DE8F05", "#CC78BC", "#029E73", "#D55E00"],
  mnemonic: ["#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd"],
  grayscale: ["#000000", "#404040", "#808080", "#b0b0b0", "#d0d0d0"],
  nature: ["#4E79A7", "#F28E2C", "#E15759", "#76B7B2", "#59A14F"],
};

// Utility functions
function getBeadColor(
  bead: BeadConfig,
  totalColumns: number,
  colorScheme: string,
  colorPalette: string,
  isHighlighted: boolean = false,
): string {
  const inactiveColor = "rgb(211, 211, 211)"; // Typst uses gray.lighten(70%)
  const highlightColor = "#FFD700"; // Gold color for highlighting

  // If highlighted, return the highlight color regardless of active state
  if (isHighlighted) return highlightColor;

  if (!bead.active) return inactiveColor;

  switch (colorScheme) {
    case "place-value": {
      const colors =
        COLOR_PALETTES[colorPalette as keyof typeof COLOR_PALETTES] ||
        COLOR_PALETTES.default;
      return colors[bead.placeValue % colors.length];
    }
    case "alternating":
      return bead.placeValue % 2 === 0 ? "#1E88E5" : "#43A047";
    case "heaven-earth":
      return bead.type === "heaven" ? "#F18F01" : "#2E86AB"; // Exact Typst colors (lines 228, 265)
    default:
      return "#000000";
  }
}

// Get arrow colors that respect color schemes and accessibility
function getArrowColors(
  bead: BeadConfig,
  direction: string,
  totalColumns: number,
  colorScheme: string,
  colorPalette: string,
): { fill: string; stroke: string } {
  const isActivating = direction === "activate" || direction === "up";

  switch (colorScheme) {
    case "monochrome":
      return isActivating
        ? { fill: "rgba(100, 100, 100, 0.8)", stroke: "rgba(50, 50, 50, 1)" }
        : {
            fill: "rgba(150, 150, 150, 0.8)",
            stroke: "rgba(100, 100, 100, 1)",
          };

    case "grayscale":
      return isActivating
        ? { fill: "rgba(60, 60, 60, 0.8)", stroke: "rgba(30, 30, 30, 1)" }
        : { fill: "rgba(120, 120, 120, 0.8)", stroke: "rgba(80, 80, 80, 1)" };

    case "place-value": {
      const colors =
        COLOR_PALETTES[colorPalette as keyof typeof COLOR_PALETTES] ||
        COLOR_PALETTES.default;
      const baseColor = colors[bead.placeValue % colors.length];

      // Create darker/lighter variants for arrows
      const activateColor = isActivating
        ? baseColor
        : adjustColorBrightness(baseColor, -30);
      const strokeColor = adjustColorBrightness(activateColor, -40);

      return {
        fill: `${activateColor}CC`, // Add alpha
        stroke: strokeColor,
      };
    }

    case "heaven-earth": {
      const baseColor = bead.type === "heaven" ? "#F18F01" : "#2E86AB";
      const activateColor = isActivating
        ? baseColor
        : adjustColorBrightness(baseColor, -30);
      const strokeColor = adjustColorBrightness(activateColor, -40);

      return {
        fill: `${activateColor}CC`,
        stroke: strokeColor,
      };
    }

    case "alternating": {
      const baseColor = bead.placeValue % 2 === 0 ? "#1E88E5" : "#43A047";
      const activateColor = isActivating
        ? baseColor
        : adjustColorBrightness(baseColor, -30);
      const strokeColor = adjustColorBrightness(activateColor, -40);

      return {
        fill: `${activateColor}CC`,
        stroke: strokeColor,
      };
    }

    default:
      // Fallback to original green/red system
      return isActivating
        ? { fill: "rgba(0, 150, 0, 0.8)", stroke: "rgba(0, 100, 0, 1)" }
        : { fill: "rgba(200, 0, 0, 0.8)", stroke: "rgba(150, 0, 0, 1)" };
  }
}

// Helper function to adjust color brightness
function adjustColorBrightness(hex: string, percent: number): string {
  // Remove # if present
  hex = hex.replace("#", "");

  // Parse RGB components
  const r = parseInt(hex.substr(0, 2), 16);
  const g = parseInt(hex.substr(2, 2), 16);
  const b = parseInt(hex.substr(4, 2), 16);

  // Adjust brightness
  const newR = Math.max(0, Math.min(255, r + (r * percent) / 100));
  const newG = Math.max(0, Math.min(255, g + (g * percent) / 100));
  const newB = Math.max(0, Math.min(255, b + (b * percent) / 100));

  // Convert back to hex
  return `#${Math.round(newR).toString(16).padStart(2, "0")}${Math.round(newG).toString(16).padStart(2, "0")}${Math.round(newB).toString(16).padStart(2, "0")}`;
}

function calculateBeadStates(
  columnStates: ColumnState[],
  originalLength: number,
): BeadConfig[][] {
  return columnStates.map((columnState, arrayIndex) => {
    const beads: BeadConfig[] = [];
    // Convert array index to place value: leftmost = highest place value
    const placeValue = (columnStates.length -
      1 -
      arrayIndex) as ValidPlaceValues;

    // Heaven bead (value 5) - independent state
    beads.push({
      type: "heaven",
      value: 5,
      active: columnState.heavenActive,
      position: 0,
      placeValue: placeValue,
    });

    // Earth beads (4 beads, each value 1) - independent state
    for (let i = 0; i < 4; i++) {
      beads.push({
        type: "earth",
        value: 1,
        active: i < columnState.earthActive,
        position: i,
        placeValue: placeValue,
      });
    }

    return beads;
  });
}

// NEW: Native place-value bead state calculation (eliminates array index math!)
function calculateBeadStatesFromPlaces(
  placeStates: PlaceStatesMap,
  maxPlaceValue: ValidPlaceValues,
): BeadConfig[][] {
  const columnsList: BeadConfig[][] = [];

  // Convert Map to sorted array by place value (ascending order for correct visual layout)
  // Filter to only include place values that are within the current column count
  const sortedPlaces = Array.from(placeStates.entries())
    .filter(([placeValue]) => placeValue <= maxPlaceValue)
    .sort(([a], [b]) => a - b);

  for (const [placeValue, placeState] of sortedPlaces) {
    const beads: BeadConfig[] = [];

    // Heaven bead (value 5) - independent state
    beads.push({
      type: "heaven",
      value: 5,
      active: placeState.heavenActive,
      position: 0,
      placeValue: placeValue, // Direct place value - no conversion needed!
    });

    // Earth beads (4 beads, each value 1) - independent state
    for (let i = 0; i < 4; i++) {
      beads.push({
        type: "earth",
        value: 1,
        active: i < placeState.earthActive,
        position: i,
        placeValue: placeValue, // Direct place value - no conversion needed!
      });
    }

    columnsList.push(beads);
  }

  // Return in visual order (left-to-right = highest-to-lowest place value)
  return columnsList.reverse();
}

// Calculate numeric value from column states
function calculateValueFromColumnStates(
  columnStates: ColumnState[],
  totalColumns: number,
): number {
  let value = 0;

  columnStates.forEach((columnState, index) => {
    const placeValue = Math.pow(10, totalColumns - 1 - index);
    const columnValue =
      (columnState.heavenActive ? 5 : 0) + columnState.earthActive;
    value += columnValue * placeValue;
  });

  return value;
}

// NEW: Native place-value calculation (eliminates the array index nightmare!)
function calculateValueFromPlaceStates(placeStates: PlaceStatesMap): number | bigint {
  // Determine if we need BigInt based on the largest place value
  const maxPlace = Math.max(...Array.from(placeStates.keys()));
  const useBigInt = maxPlace > 14; // >15 digits

  if (useBigInt) {
    let value = 0n;
    for (const [placeValue, placeState] of placeStates) {
      const digitValue = BigInt((placeState.heavenActive ? 5 : 0) + placeState.earthActive);
      value += digitValue * (10n ** BigInt(placeValue));
    }
    return value;
  } else {
    let value = 0;
    for (const [placeValue, placeState] of placeStates) {
      const digitValue = (placeState.heavenActive ? 5 : 0) + placeState.earthActive;
      value += digitValue * Math.pow(10, placeValue);
    }
    return value;
  }
}

// Components
interface BeadProps {
  bead: BeadConfig;
  x: number;
  y: number;
  size: number;
  shape: "diamond" | "square" | "circle";
  color: string;
  customStyle?: BeadStyle;
  isHighlighted?: boolean;
  isDisabled?: boolean;
  enableAnimation: boolean;
  enableGestures?: boolean;
  hideInactiveBeads?: boolean;
  showDirectionIndicator?: boolean;
  direction?: string;
  isCurrentStep?: boolean;
  onClick?: (event: React.MouseEvent) => void;
  onHover?: (event: React.MouseEvent) => void;
  onLeave?: (event: React.MouseEvent) => void;
  onGestureToggle?: (
    bead: BeadConfig,
    direction: "activate" | "deactivate",
  ) => void;
  onRef?: (element: SVGElement | null) => void;
  heavenEarthGap: number;
  barY: number;
  // Arrow color scheme integration
  colorScheme?: string;
  colorPalette?: string;
  totalColumns?: number;
  // 3D Enhancement
  enhanced3d?: boolean | "subtle" | "realistic" | "delightful";
  material3d?: Abacus3DMaterial;
  physics3d?: Abacus3DPhysics;
  columnIndex?: number;
  mousePosition?: { x: number; y: number };
  containerBounds?: { x: number; y: number };
}

const Bead: React.FC<BeadProps> = ({
  bead,
  x,
  y,
  size,
  shape,
  color,
  customStyle,
  isHighlighted = false,
  isDisabled = false,
  enableAnimation,
  enableGestures = false,
  hideInactiveBeads = false,
  showDirectionIndicator = false,
  direction,
  isCurrentStep = false,
  onClick,
  onHover,
  onLeave,
  onGestureToggle,
  onRef,
  heavenEarthGap,
  barY,
  colorScheme = "monochrome",
  colorPalette = "default",
  totalColumns = 1,
  enhanced3d,
  material3d,
  physics3d,
  columnIndex,
  mousePosition,
  containerBounds,
}) => {
  // Detect server-side rendering
  const isServer = typeof window === 'undefined';

  // Use springs only if not on server and animations are enabled
  // Enhanced physics config for 3D modes
  const physicsConfig = React.useMemo(() => {
    if (!enableAnimation || isServer) return { duration: 0 };
    if (!enhanced3d || enhanced3d === true || enhanced3d === 'subtle') return config.default;
    return Abacus3DUtils.getPhysicsConfig(enhanced3d);
  }, [enableAnimation, isServer, enhanced3d]);

  const [{ x: springX, y: springY }, api] = useSpring(() => ({
    x,
    y,
    config: physicsConfig
  }));

  // Track velocity for wobble effect (delightful mode only)
  const velocityRef = useRef(0);
  const lastYRef = useRef(y);

  // Calculate parallax offset for hover effect
  const parallaxOffset = React.useMemo(() => {
    if (enhanced3d === 'delightful' && physics3d?.hoverParallax && mousePosition && containerBounds) {
      return Abacus3DUtils.calculateParallaxOffset(
        x,
        y,
        mousePosition.x,
        mousePosition.y,
        containerBounds.x,
        containerBounds.y,
        0.5
      );
    }
    return { x: 0, y: 0, z: 0 };
  }, [enhanced3d, physics3d?.hoverParallax, mousePosition, containerBounds, x, y]);

  // Arrow pulse animation for urgency indication
  const [{ arrowPulse }, arrowApi] = useSpring(() => ({
    arrowPulse: 1,
    config: enableAnimation && !isServer ? { tension: 200, friction: 10 } : { duration: 0 },
  }));

  const gestureStateRef = useRef({
    isDragging: false,
    lastDirection: null as "activate" | "deactivate" | null,
    startY: 0,
    threshold: size * 0.3, // Minimum movement to trigger toggle
    hasGestureTriggered: false, // Track if a gesture has triggered to avoid click conflicts
  });

  // Calculate gesture direction based on bead type and position
  const getGestureDirection = useCallback(
    (deltaY: number) => {
      const movement = Math.abs(deltaY);
      if (movement < gestureStateRef.current.threshold) return null;

      if (bead.type === "heaven") {
        // Heaven bead: down toward bar = activate, up away from bar = deactivate
        return deltaY > 0 ? "activate" : "deactivate";
      } else {
        // Earth bead: up toward bar = activate, down away from bar = deactivate
        return deltaY < 0 ? "activate" : "deactivate";
      }
    },
    [bead.type],
  );

  // Directional gesture handler - only on client with gestures enabled
  const bind = (enableGestures && !isServer) ? useDrag(
    ({ event, movement: [, deltaY], first, active }) => {
      if (first) {
        event?.preventDefault();
        gestureStateRef.current.isDragging = true;
        gestureStateRef.current.lastDirection = null;
        gestureStateRef.current.hasGestureTriggered = false;
        return;
      }

      // Only process during active drag, ignore drag end
      if (!active || !gestureStateRef.current.isDragging) {
        if (!active) {
          // Clean up on drag end but don't revert state
          gestureStateRef.current.isDragging = false;
          gestureStateRef.current.lastDirection = null;
          // Reset the gesture trigger flag after a short delay to allow clicks
          setTimeout(() => {
            gestureStateRef.current.hasGestureTriggered = false;
          }, 100);
        }
        return;
      }

      const currentDirection = getGestureDirection(deltaY);

      // Only trigger toggle on direction change or first significant movement
      if (
        currentDirection &&
        currentDirection !== gestureStateRef.current.lastDirection
      ) {
        gestureStateRef.current.lastDirection = currentDirection;
        gestureStateRef.current.hasGestureTriggered = true;
        onGestureToggle?.(bead, currentDirection);
      }
    },
    {
      enabled: enableGestures,
      preventDefault: true,
    },
  ) : () => ({});

  React.useEffect(() => {
    if (enableAnimation) {
      // Calculate velocity for wobble effect
      const deltaY = y - lastYRef.current;
      velocityRef.current = deltaY;
      lastYRef.current = y;

      api.start({ x, y, config: physicsConfig });
    } else {
      api.set({ x, y });
    }
  }, [x, y, enableAnimation, api, physicsConfig]);

  // Pulse animation for direction arrows to indicate urgency
  React.useEffect(() => {
    if (showDirectionIndicator && direction && isCurrentStep) {
      const startPulse = () => {
        arrowApi.start({
          from: { arrowPulse: 1 },
          to: async (next) => {
            await next({ arrowPulse: 1.3 });
            await next({ arrowPulse: 1 });
          },
          loop: true,
        });
      };

      const timeoutId = setTimeout(startPulse, 200); // Small delay before starting pulse
      return () => {
        clearTimeout(timeoutId);
        arrowApi.stop();
      };
    } else {
      arrowApi.set({ arrowPulse: 1 });
    }
  }, [showDirectionIndicator, direction, isCurrentStep, arrowApi]);

  const renderShape = () => {
    const halfSize = size / 2;

    // Determine fill - use gradient for realistic/delightful modes, otherwise use color
    let fillValue = color;
    if ((enhanced3d === 'realistic' || enhanced3d === 'delightful') && columnIndex !== undefined) {
      if (bead.type === 'heaven') {
        fillValue = `url(#bead-gradient-${columnIndex}-heaven)`;
      } else {
        fillValue = `url(#bead-gradient-${columnIndex}-earth-${bead.position})`;
      }
    }

    switch (shape) {
      case "diamond":
        return (
          <polygon
            points={`${size * 0.7},0 ${size * 1.4},${halfSize} ${size * 0.7},${size} 0,${halfSize}`}
            fill={fillValue}
            stroke="#000"
            strokeWidth="0.5"
          />
        );
      case "square":
        return (
          <rect
            width={size}
            height={size}
            fill={fillValue}
            stroke="#000"
            strokeWidth="0.5"
            rx="1"
          />
        );
      case "circle":
      default:
        return (
          <circle
            cx={halfSize}
            cy={halfSize}
            r={halfSize}
            fill={fillValue}
            stroke="#000"
            strokeWidth="0.5"
          />
        );
    }
  };

  // Use animated.g only if animations are enabled, otherwise use regular g
  const GElement = enableAnimation ? animated.g : 'g';
  const DirectionIndicatorG = (enableAnimation && showDirectionIndicator && direction) ? animated.g : 'g';

  // Calculate correct offset based on shape (matching Typst positioning)
  const getXOffset = () => {
    return shape === "diamond" ? size * 0.7 : size / 2;
  };

  const getYOffset = () => {
    return size / 2; // Y offset is always size/2 for all shapes
  };

  // Calculate static transform for direction indicator
  const getDirectionIndicatorTransform = () => {
    const centerX = shape === "diamond" ? size * 0.7 : size / 2;
    const centerY = size / 2;
    const pulse = enableAnimation ? undefined : 1;
    return `translate(${centerX}, ${centerY}) scale(${pulse})`;
  };

  // Build style object based on animation mode
  const wobbleEnabled = enhanced3d === 'delightful' && physics3d?.wobble;
  const parallaxEnabled = enhanced3d === 'delightful' && physics3d?.hoverParallax;
  const beadStyle: any = enableAnimation
    ? {
        transform: to(
          [springX, springY],
          (sx, sy) => {
            const translate = `translate(${sx - getXOffset() + parallaxOffset.x}px, ${sy - getYOffset() + parallaxOffset.y}px)`;
            const transforms = [translate];

            // Add parallax Z translation
            if (parallaxEnabled && parallaxOffset.z > 0) {
              transforms.push(`translateZ(${parallaxOffset.z}px)`);
            }

            // Add wobble rotation
            if (wobbleEnabled && velocityRef.current !== 0) {
              transforms.push(Abacus3DUtils.getWobbleRotation(velocityRef.current, 'x'));
            }

            return transforms.join(' ');
          },
        ),
        transformOrigin: 'center center',
        transformStyle: 'preserve-3d',
        cursor: enableGestures ? "grab" : onClick ? "pointer" : "default",
        touchAction: "none" as const,
        transition: "opacity 0.2s ease-in-out",
      }
    : {
        cursor: enableGestures ? "grab" : onClick ? "pointer" : "default",
        touchAction: "none" as const,
        transition: "opacity 0.2s ease-in-out",
      };

  return (
    <GElement
      ref={onRef}
      {...(enableGestures ? bind() : {})}
      className={`abacus-bead ${bead.active ? "active" : "inactive"} ${hideInactiveBeads && !bead.active ? "hidden-inactive" : ""}`}
      role={onClick ? "button" : undefined}
      tabIndex={onClick ? 0 : undefined}
      data-testid={
        onClick
          ? `bead-place-${bead.placeValue}-${bead.type}${bead.type === "earth" ? `-pos-${bead.position}` : ""}`
          : undefined
      }
      transform={
        enableAnimation
          ? undefined
          : `translate(${x - getXOffset()}, ${y - getYOffset()})`
      }
      style={beadStyle}
      onClick={(e) => {
        // Prevent click if a gesture just triggered to avoid double-toggling
        if (enableGestures && gestureStateRef.current.hasGestureTriggered) {
          e.preventDefault();
          return;
        }
        onClick?.(e);
      }} // Enable click with gesture conflict prevention
    >
      {renderShape()}
      {showDirectionIndicator && direction && (() => {
        const indicatorTransform: any = enableAnimation
          ? to([arrowPulse], (pulse) => {
              const centerX = shape === "diamond" ? size * 0.7 : size / 2;
              const centerY = size / 2;
              return `translate(${centerX}, ${centerY}) scale(${pulse})`;
            })
          : getDirectionIndicatorTransform();

        return (
          <DirectionIndicatorG
            className="direction-indicator"
            style={{ pointerEvents: "none" as const }}
            transform={indicatorTransform}
          >
          {(() => {
            const arrowColors = getArrowColors(
              bead,
              direction,
              totalColumns,
              colorScheme,
              colorPalette,
            );
            const isUpArrow =
              direction === "up" ||
              (direction === "activate" && bead.type === "earth") ||
              (direction === "deactivate" && bead.type === "heaven");

            return isUpArrow ? (
              // Up arrow - centered with color scheme
              <polygon
                points={`${-size * 0.15},${size * 0.05} ${size * 0.15},${size * 0.05} 0,${-size * 0.15}`}
                fill={arrowColors.fill}
                stroke={arrowColors.stroke}
                strokeWidth="1.5"
              />
            ) : (
              // Down arrow - centered with color scheme
              <polygon
                points={`${-size * 0.15},${-size * 0.1} ${size * 0.15},${-size * 0.1} 0,${size * 0.1}`}
                fill={arrowColors.fill}
                stroke={arrowColors.stroke}
                strokeWidth="1.5"
              />
            );
          })()}
          </DirectionIndicatorG>
        );
      })()}
    </GElement>
  );
};

// Main component
export const AbacusReact: React.FC<AbacusConfig> = ({
  value = 0,
  columns = "auto",
  showEmptyColumns = false,
  hideInactiveBeads,
  beadShape,
  colorScheme,
  colorPalette,
  scaleFactor,
  animated,
  interactive,
  gestures,
  showNumbers,
  soundEnabled,
  soundVolume,
  // 3D enhancement props
  enhanced3d,
  material3d,
  physics3d,
  // Advanced customization props
  customStyles,
  callbacks,
  overlays = [],
  highlightColumns = [],
  highlightBeads = [],
  stepBeadHighlights = [],
  currentStep = 0,
  showDirectionIndicators = false,
  disabledColumns = [],
  disabledBeads = [],
  // Legacy callbacks
  onClick,
  onValueChange,
}) => {
  // Try to use context config, fallback to defaults if no context
  let contextConfig;
  try {
    contextConfig = useAbacusConfig();
  } catch {
    // No context provider, use defaults
    contextConfig = getDefaultAbacusConfig();
  }

  // Use props if provided, otherwise fall back to context config
  const finalConfig = {
    hideInactiveBeads: hideInactiveBeads ?? contextConfig.hideInactiveBeads,
    beadShape: beadShape ?? contextConfig.beadShape,
    colorScheme: colorScheme ?? contextConfig.colorScheme,
    colorPalette: colorPalette ?? contextConfig.colorPalette,
    scaleFactor: scaleFactor ?? contextConfig.scaleFactor,
    animated: animated ?? contextConfig.animated,
    interactive: interactive ?? contextConfig.interactive,
    gestures: gestures ?? contextConfig.gestures,
    showNumbers: showNumbers ?? contextConfig.showNumbers,
    soundEnabled: soundEnabled ?? contextConfig.soundEnabled,
    soundVolume: soundVolume ?? contextConfig.soundVolume,
  };
  // Calculate effective columns first, without depending on columnStates
  const effectiveColumns = useMemo(() => {
    if (columns === "auto") {
      const minColumns = Math.max(1, value.toString().length);
      return showEmptyColumns ? minColumns : minColumns;
    }
    return columns;
  }, [columns, value, showEmptyColumns]);

  // Switch to place-value architecture!
  const maxPlaceValue = (effectiveColumns - 1) as ValidPlaceValues;
  const {
    value: currentValue,
    placeStates,
    toggleBead,
    getPlaceState,
    setPlaceState,
  } = useAbacusPlaceStates(value, maxPlaceValue);

  // Legacy compatibility - convert placeStates back to columnStates for components that still need it
  const columnStates = useMemo(() => {
    const states: ColumnState[] = [];
    for (let col = 0; col < effectiveColumns; col++) {
      const placeValue = (effectiveColumns - 1 - col) as ValidPlaceValues;
      const placeState = placeStates.get(placeValue);
      states[col] = placeState
        ? {
            heavenActive: placeState.heavenActive,
            earthActive: placeState.earthActive,
          }
        : { heavenActive: false, earthActive: 0 };
    }
    return states;
  }, [placeStates, effectiveColumns]);

  // Legacy setColumnState for backward compatibility during transition
  const setColumnState = useCallback(
    (columnIndex: number, state: ColumnState) => {
      const placeValue = (effectiveColumns -
        1 -
        columnIndex) as ValidPlaceValues;
      if (placeStates.has(placeValue)) {
        const currentState = placeStates.get(placeValue)!;
        // This would need the place state setter from the hook - simplified for now
        console.warn(
          "setColumnState called - should migrate to place value operations",
        );
      }
    },
    [placeStates, effectiveColumns],
  );

  // Track when changes are from external control vs user interaction
  const isExternalChange = useRef(false);
  const previousControlledValue = useRef(value);

  // Debug prop changes and mark external changes
  React.useEffect(() => {
    // console.log(`🔄 Component received value prop: ${value}, internal value: ${currentValue}`);

    // Mark this as an external change when controlled value prop changes
    if (value !== previousControlledValue.current) {
      isExternalChange.current = true;
    }
  }, [value, currentValue]);

  // Notify about value changes only when user interacts (not external control)
  React.useEffect(() => {
    // Skip callback if this change was from external control
    if (isExternalChange.current) {
      isExternalChange.current = false;
      return;
    }

    // Skip callback if value hasn't actually changed from user interaction
    if (currentValue === previousControlledValue.current) {
      return;
    }

    // This is a user-initiated change, notify parent
    onValueChange?.(currentValue);
  }, [currentValue, onValueChange]);

  // Track controlled value changes
  React.useEffect(() => {
    previousControlledValue.current = value;
  }, [value]);

  const dimensions = useAbacusDimensions(
    effectiveColumns,
    finalConfig.scaleFactor,
    finalConfig.showNumbers,
  );

  // Use new place-value bead calculation!
  const beadStates = useMemo(
    () => calculateBeadStatesFromPlaces(placeStates, maxPlaceValue),
    [placeStates, maxPlaceValue],
  );

  // Layout calculations using exact Typst positioning
  // In Typst, the reckoning bar is positioned at heaven-earth-gap from the top
  const barY = dimensions.heavenEarthGap;

  const handleBeadClick = useCallback(
    (bead: BeadConfig, event?: React.MouseEvent) => {
      // Check if bead is disabled using new place-value system
      const columnIndex = effectiveColumns - 1 - bead.placeValue; // Convert place value to legacy column index for disabled check
      const isDisabled =
        isBeadDisabledByPlaceValue(bead, disabledBeads) ||
        disabledColumns?.includes(columnIndex);

      if (isDisabled) {
        return;
      }

      // Calculate how many beads will change to determine sound intensity
      const currentState = getPlaceState(bead.placeValue);
      let beadMovementCount = 1; // Default for single bead movements

      if (bead.type === "earth") {
        if (bead.active) {
          // Deactivating: count beads from this position to end of active beads
          beadMovementCount = currentState.earthActive - bead.position;
        } else {
          // Activating: count beads from current active count to this position + 1
          beadMovementCount = bead.position + 1 - currentState.earthActive;
        }
      }
      // Heaven bead always moves just 1 bead

      // Create enhanced event object
      const beadClickEvent: BeadClickEvent = {
        bead,
        columnIndex: columnIndex, // Legacy API compatibility
        beadType: bead.type,
        position: bead.position,
        active: bead.active,
        value: bead.value,
        event: event!,
      };

      // Call new callback system
      callbacks?.onBeadClick?.(beadClickEvent);

      // Legacy callback for backward compatibility
      onClick?.(bead);

      // Play sound if enabled with intensity based on bead movement count
      if (finalConfig.soundEnabled) {
        playBeadSound(finalConfig.soundVolume, beadMovementCount);
      }

      // Toggle the bead - NO MORE EFFECTIVECOLUMNS THREADING!
      toggleBead(bead);
    },
    [
      onClick,
      callbacks,
      toggleBead,
      disabledColumns,
      disabledBeads,
      finalConfig.soundEnabled,
      finalConfig.soundVolume,
      getPlaceState,
    ],
  );

  const handleGestureToggle = useCallback(
    (bead: BeadConfig, direction: "activate" | "deactivate") => {
      const currentState = getPlaceState(bead.placeValue);

      // Calculate bead movement count for sound intensity
      let beadMovementCount = 1;
      if (bead.type === "earth") {
        if (direction === "activate") {
          beadMovementCount = Math.max(
            0,
            bead.position + 1 - currentState.earthActive,
          );
        } else {
          beadMovementCount = Math.max(
            0,
            currentState.earthActive - bead.position,
          );
        }
      }

      // Play sound if enabled with intensity
      if (finalConfig.soundEnabled) {
        playBeadSound(finalConfig.soundVolume, beadMovementCount);
      }

      if (bead.type === "heaven") {
        // Heaven bead: directly set the state based on direction
        const newHeavenActive = direction === "activate";
        setPlaceState(bead.placeValue, {
          ...currentState,
          heavenActive: newHeavenActive,
        });
      } else {
        // Earth bead: set the correct number of active earth beads
        const shouldActivate = direction === "activate";
        let newEarthActive;

        if (shouldActivate) {
          // When activating, ensure this bead position and all below are active
          newEarthActive = Math.max(
            currentState.earthActive,
            bead.position + 1,
          );
        } else {
          // When deactivating, ensure this bead position and all above are inactive
          newEarthActive = Math.min(currentState.earthActive, bead.position);
        }

        setPlaceState(bead.placeValue, {
          ...currentState,
          earthActive: newEarthActive,
        });
      }
    },
    [
      getPlaceState,
      setPlaceState,
      finalConfig.soundEnabled,
      finalConfig.soundVolume,
    ],
  );

  // Place value editing - FRESH IMPLEMENTATION
  const [activeColumn, setActiveColumn] = React.useState<number | null>(null);

  // Calculate current place values
  const placeValues = React.useMemo(() => {
    return columnStates.map(
      (state) => (state.heavenActive ? 5 : 0) + state.earthActive,
    );
  }, [columnStates]);

  // Update a column from a digit
  const setColumnValue = React.useCallback(
    (columnIndex: number, digit: number) => {
      if (digit < 0 || digit > 9) return;

      // Convert column index to place value
      const placeValue = (effectiveColumns -
        1 -
        columnIndex) as ValidPlaceValues;
      const currentState = getPlaceState(placeValue);

      // Calculate how many beads change for sound intensity
      const currentValue =
        (currentState.heavenActive ? 5 : 0) + currentState.earthActive;
      const newHeavenActive = digit >= 5;
      const newEarthActive = digit % 5;

      // Count bead movements: heaven bead + earth bead changes
      let beadMovementCount = 0;
      if (currentState.heavenActive !== newHeavenActive) beadMovementCount += 1;
      beadMovementCount += Math.abs(currentState.earthActive - newEarthActive);

      // Play sound if enabled with intensity based on bead changes
      if (finalConfig.soundEnabled && beadMovementCount > 0) {
        playBeadSound(finalConfig.soundVolume, beadMovementCount);
      }

      setPlaceState(placeValue, {
        heavenActive: newHeavenActive,
        earthActive: newEarthActive,
      });
    },
    [
      setPlaceState,
      effectiveColumns,
      finalConfig.soundEnabled,
      finalConfig.soundVolume,
      getPlaceState,
    ],
  );

  // Keyboard handler - only active when interactive
  React.useEffect(() => {
    // Clear activeColumn if abacus becomes non-interactive
    if (!finalConfig.interactive && activeColumn !== null) {
      setActiveColumn(null);
      return;
    }

    // Only set up keyboard listener when interactive
    if (!finalConfig.interactive) {
      return;
    }

    const handleKey = (e: KeyboardEvent) => {
      // console.log(`🎹 KEY: "${e.key}" | activeColumn: ${activeColumn} | code: ${e.code}`);

      if (activeColumn === null) {
        // console.log(`❌ activeColumn is null, ignoring`);
        return;
      }

      if (e.key >= "0" && e.key <= "9") {
        // console.log(`🔢 DIGIT: ${e.key} for column ${activeColumn}`);
        e.preventDefault();

        const digit = parseInt(e.key);
        // console.log(`📝 About to call setColumnValue(${activeColumn}, ${digit})`);
        setColumnValue(activeColumn, digit);

        // Move focus to the next column to the right
        const nextColumn = activeColumn + 1;
        if (nextColumn < effectiveColumns) {
          // console.log(`➡️ Moving focus to next column: ${nextColumn}`);
          setActiveColumn(nextColumn);
        } else {
          // console.log(`🏁 Reached last column, staying at: ${activeColumn}`);
        }
      } else if (e.key === "Backspace") {
        e.preventDefault();
        // console.log(`⬅️ BACKSPACE: clearing current column and moving to previous column`);

        // Clear current column (set to 0)
        setColumnValue(activeColumn, 0);

        // Move focus to the previous column to the left
        const prevColumn = activeColumn - 1;
        if (prevColumn >= 0) {
          // console.log(`⬅️ Moving focus to previous column: ${prevColumn}`);
          setActiveColumn(prevColumn);
        } else {
          // console.log(`🏁 Reached first column, wrapping to last column`);
          setActiveColumn(effectiveColumns - 1); // Wrap around to last column
        }
      } else if (e.key === "Tab" && e.shiftKey) {
        e.preventDefault();
        // console.log(`⬅️ SHIFT+TAB: moving to higher place value (left)`);

        // Shift+Tab moves LEFT (to higher place values): ones → tens → hundreds
        // Lower columnIndex = higher place value
        const nextColumn = activeColumn - 1;
        if (nextColumn >= 0) {
          // console.log(`⬅️ Moving focus to higher place value: ${nextColumn}`);
          setActiveColumn(nextColumn);
        } else {
          // console.log(`🏁 Reached highest place, wrapping to ones place`);
          setActiveColumn(effectiveColumns - 1); // Wrap to rightmost (ones place)
        }
      } else if (e.key === "Tab") {
        e.preventDefault();
        // console.log(`➡️ TAB: moving to lower place value (right)`);

        // Tab moves RIGHT (to lower place values): hundreds → tens → ones
        // Higher columnIndex = lower place value
        const nextColumn = activeColumn + 1;
        if (nextColumn < effectiveColumns) {
          // console.log(`➡️ Moving focus to lower place value: ${nextColumn}`);
          setActiveColumn(nextColumn);
        } else {
          // console.log(`🏁 Reached lowest place, wrapping to highest place`);
          setActiveColumn(0); // Wrap to leftmost (highest place)
        }
      } else if (e.key === "Escape") {
        e.preventDefault();
        // console.log(`🚪 ESCAPE: setting activeColumn to null`);
        setActiveColumn(null);
      }
    };

    // console.log(`🔧 Setting up keyboard listener for activeColumn: ${activeColumn}`);
    document.addEventListener("keydown", handleKey);
    return () => {
      // console.log(`🗑️ Cleaning up keyboard listener for activeColumn: ${activeColumn}`);
      document.removeEventListener("keydown", handleKey);
    };
  }, [activeColumn, setColumnValue, effectiveColumns, finalConfig.interactive]);

  // Debug activeColumn changes
  React.useEffect(() => {
    // console.log(`🎯 activeColumn changed to: ${activeColumn}`);
  }, [activeColumn]);

  // 3D Enhancement: Calculate container classes
  const containerClasses = Abacus3DUtils.get3DContainerClasses(
    enhanced3d,
    material3d?.lighting,
    physics3d?.hoverParallax
  );

  // 3D Enhancement: Track mouse position for parallax
  const containerRef = useRef<HTMLDivElement>(null);
  const [mousePos, setMousePos] = React.useState({ x: 0, y: 0 });
  const [containerBounds, setContainerBounds] = React.useState({ x: 0, y: 0 });

  const handleMouseMove = useCallback((e: React.MouseEvent<HTMLDivElement>) => {
    if (enhanced3d === 'delightful' && physics3d?.hoverParallax && containerRef.current) {
      const rect = containerRef.current.getBoundingClientRect();
      setMousePos({ x: e.clientX, y: e.clientY });
      setContainerBounds({ x: rect.left, y: rect.top });
    }
  }, [enhanced3d, physics3d?.hoverParallax]);

  return (
    <div
      ref={containerRef}
      className={containerClasses}
      style={{
        display: "inline-block",
        textAlign: "center",
        position: "relative",
      }}
      onMouseMove={handleMouseMove}
      tabIndex={
        finalConfig.interactive && finalConfig.showNumbers ? 0 : undefined
      }
      onFocus={() => {
        if (
          finalConfig.interactive &&
          finalConfig.showNumbers &&
          activeColumn === null
        ) {
          // Start at the rightmost column (ones place)
          setActiveColumn(effectiveColumns - 1);
        }
      }}
      onBlur={() => {
        if (finalConfig.interactive && finalConfig.showNumbers) {
          setActiveColumn(null);
        }
      }}
    >
      <svg
        width={dimensions.width}
        height={dimensions.height}
        viewBox={`0 0 ${dimensions.width} ${dimensions.height}`}
        className={`abacus-svg ${finalConfig.hideInactiveBeads ? "hide-inactive-mode" : ""} ${finalConfig.interactive ? "interactive" : ""}`}
        style={{ overflow: "visible", display: "block" }}
      >
        <defs>
          <style>{`
          /* CSS-based opacity system for hidden inactive beads */
          .abacus-bead {
            transition: opacity 0.2s ease-in-out;
          }

          /* Hidden inactive beads are invisible by default */
          .hide-inactive-mode .abacus-bead.hidden-inactive {
            opacity: 0;
          }

          /* Interactive abacus: When hovering over the abacus, hidden inactive beads become semi-transparent */
          .abacus-svg.hide-inactive-mode.interactive:hover .abacus-bead.hidden-inactive {
            opacity: 0.5;
          }

          /* Interactive abacus: When hovering over a specific hidden inactive bead, it becomes fully visible */
          .hide-inactive-mode.interactive .abacus-bead.hidden-inactive:hover {
            opacity: 1 !important;
          }

          /* Non-interactive abacus: Hidden inactive beads always stay at opacity 0 */
          .abacus-svg.hide-inactive-mode:not(.interactive) .abacus-bead.hidden-inactive {
            opacity: 0 !important;
          }
        `}</style>

          {/* 3D Enhancement: Material gradients for beads */}
          {(enhanced3d === 'realistic' || enhanced3d === 'delightful') && material3d && (
            <>
              {/* Generate gradients for all beads based on material type */}
              {Array.from({ length: effectiveColumns }, (_, colIndex) => {
                const placeValue = (effectiveColumns - 1 - colIndex) as ValidPlaceValues;

                // Create dummy beads to get their colors
                const heavenBead: BeadConfig = {
                  type: 'heaven',
                  value: 5,
                  active: true,
                  position: 0,
                  placeValue
                };
                const earthBead: BeadConfig = {
                  type: 'earth',
                  value: 1,
                  active: true,
                  position: 0,
                  placeValue
                };

                const heavenColor = getBeadColor(heavenBead, effectiveColumns, finalConfig.colorScheme, finalConfig.colorPalette, false);
                const earthColor = getBeadColor(earthBead, effectiveColumns, finalConfig.colorScheme, finalConfig.colorPalette, false);

                return (
                  <React.Fragment key={`gradients-col-${colIndex}`}>
                    {/* Heaven bead gradient */}
                    <defs dangerouslySetInnerHTML={{
                      __html: Abacus3DUtils.getBeadGradient(
                        `bead-gradient-${colIndex}-heaven`,
                        heavenColor,
                        material3d.heavenBeads || 'satin',
                        true
                      )
                    }} />

                    {/* Earth bead gradients */}
                    {[0, 1, 2, 3].map(pos => (
                      <defs key={`earth-${pos}`} dangerouslySetInnerHTML={{
                        __html: Abacus3DUtils.getBeadGradient(
                          `bead-gradient-${colIndex}-earth-${pos}`,
                          earthColor,
                          material3d.earthBeads || 'satin',
                          true
                        )
                      }} />
                    ))}
                  </React.Fragment>
                );
              }).filter(Boolean)}

              {/* Wood grain texture pattern */}
              {material3d.woodGrain && (
                <defs dangerouslySetInnerHTML={{
                  __html: Abacus3DUtils.getWoodGrainPattern('wood-grain-pattern')
                }} />
              )}
            </>
          )}
        </defs>

        {/* Background glow effects - rendered behind everything */}
        {Array.from({ length: effectiveColumns }, (_, colIndex) => {
          const placeValue = effectiveColumns - 1 - colIndex;
          const columnStyles = customStyles?.columns?.[colIndex];
          const backgroundGlow = columnStyles?.backgroundGlow;

          if (!backgroundGlow) return null;

          const x =
            colIndex * dimensions.rodSpacing + dimensions.rodSpacing / 2;
          const glowWidth =
            dimensions.rodSpacing + (backgroundGlow.spread || 0);
          const glowHeight = dimensions.height + (backgroundGlow.spread || 0);

          return (
            <rect
              key={`background-glow-pv${placeValue}`}
              x={x - glowWidth / 2}
              y={-(backgroundGlow.spread || 0) / 2}
              width={glowWidth}
              height={glowHeight}
              fill={backgroundGlow.fill || "rgba(59, 130, 246, 0.2)"}
              filter={
                backgroundGlow.blur ? `blur(${backgroundGlow.blur}px)` : "none"
              }
              opacity={0.6}
              rx={8}
              style={{ pointerEvents: "none" }}
            />
          );
        })}

        {/* Rods - positioned as rectangles like in Typst */}
        {Array.from({ length: effectiveColumns }, (_, colIndex) => {
          const placeValue = effectiveColumns - 1 - colIndex;
          const x =
            colIndex * dimensions.rodSpacing + dimensions.rodSpacing / 2;

          // Calculate rod bounds based on visible beads (matching Typst logic)
          const rodStartY = 0; // Start from top for now, will be refined
          const rodEndY = dimensions.height; // End at bottom for now, will be refined

          // Apply custom column post styling (column-specific overrides global)
          const columnStyles = customStyles?.columns?.[colIndex];
          const globalColumnPosts = customStyles?.columnPosts;
          const rodStyle = {
            fill:
              columnStyles?.columnPost?.fill ||
              globalColumnPosts?.fill ||
              "rgb(0, 0, 0, 0.1)", // Default Typst color
            stroke:
              columnStyles?.columnPost?.stroke ||
              globalColumnPosts?.stroke ||
              "none",
            strokeWidth:
              columnStyles?.columnPost?.strokeWidth ??
              globalColumnPosts?.strokeWidth ??
              0,
            opacity:
              columnStyles?.columnPost?.opacity ??
              globalColumnPosts?.opacity ??
              1,
          };

          return (
            <React.Fragment key={`rod-pv${placeValue}`}>
              <rect
                x={x - dimensions.rodWidth / 2}
                y={rodStartY}
                width={dimensions.rodWidth}
                height={rodEndY - rodStartY}
                fill={rodStyle.fill}
                stroke={rodStyle.stroke}
                strokeWidth={rodStyle.strokeWidth}
                opacity={rodStyle.opacity}
                className="column-post"
              />
              {/* Wood grain texture overlay for column posts */}
              {(enhanced3d === 'realistic' || enhanced3d === 'delightful') && material3d?.woodGrain && (
                <rect
                  x={x - dimensions.rodWidth / 2}
                  y={rodStartY}
                  width={dimensions.rodWidth}
                  height={rodEndY - rodStartY}
                  fill="url(#wood-grain-pattern)"
                  className="frame-wood"
                  style={{ pointerEvents: 'none' }}
                />
              )}
            </React.Fragment>
          );
        })}

        {/* Reckoning bar - spans from leftmost to rightmost bead */}
        <rect
          x={dimensions.rodSpacing / 2 - dimensions.beadSize / 2}
          y={barY}
          width={
            (effectiveColumns - 1) * dimensions.rodSpacing + dimensions.beadSize
          }
          height={dimensions.barThickness}
          fill={customStyles?.reckoningBar?.fill || "black"} // Typst default is black
          stroke={customStyles?.reckoningBar?.stroke || "none"}
          strokeWidth={customStyles?.reckoningBar?.strokeWidth ?? 0}
          opacity={customStyles?.reckoningBar?.opacity ?? 1}
          className="reckoning-bar"
        />
        {/* Wood grain texture overlay for reckoning bar */}
        {(enhanced3d === 'realistic' || enhanced3d === 'delightful') && material3d?.woodGrain && (
          <rect
            x={dimensions.rodSpacing / 2 - dimensions.beadSize / 2}
            y={barY}
            width={
              (effectiveColumns - 1) * dimensions.rodSpacing + dimensions.beadSize
            }
            height={dimensions.barThickness}
            fill="url(#wood-grain-pattern)"
            className="frame-wood"
            style={{ pointerEvents: 'none' }}
          />
        )}

        {/* Beads */}
        {beadStates.map((columnBeads, colIndex) =>
          columnBeads.map((bead, beadIndex) => {
            // Render all beads - CSS handles visibility for inactive beads

            // x-offset calculation matching Typst (line 160)
            const x =
              colIndex * dimensions.rodSpacing + dimensions.rodSpacing / 2;
            let y: number;

            if (bead.type === "heaven") {
              // Heaven bead positioning - exact Typst formulas (lines 173-179)
              if (bead.active) {
                // Active heaven bead: positioned close to reckoning bar (line 175)
                y =
                  dimensions.heavenEarthGap -
                  dimensions.beadSize / 2 -
                  dimensions.activeGap;
              } else {
                // Inactive heaven bead: positioned away from reckoning bar (line 178)
                y =
                  dimensions.heavenEarthGap -
                  dimensions.inactiveGap -
                  dimensions.beadSize / 2;
              }
            } else {
              // Earth bead positioning - exact Typst formulas (lines 249-261)
              const columnState = columnStates[colIndex];
              if (!columnState) {
                throw new Error(
                  `Invalid abacus state: columnState is undefined for column index ${colIndex}. ` +
                  `effectiveColumns=${effectiveColumns}, columnStates.length=${columnStates.length}, ` +
                  `beadStates.length=${beadStates.length}, placeValue=${bead.placeValue}. ` +
                  `This indicates a mismatch between the number of columns and the bead states. ` +
                  `Please report this issue with the abacus configuration that triggered it.`
                );
              }
              const earthActive = columnState.earthActive;

              if (bead.active) {
                // Active beads: positioned near reckoning bar, adjacent beads touch (line 251)
                y =
                  dimensions.heavenEarthGap +
                  dimensions.barThickness +
                  dimensions.activeGap +
                  dimensions.beadSize / 2 +
                  bead.position *
                    (dimensions.beadSize + dimensions.adjacentSpacing);
              } else {
                // Inactive beads: positioned after active beads + gap (lines 254-261)
                if (earthActive > 0) {
                  // Position after the last active bead + gap, then adjacent inactive beads touch (line 256)
                  y =
                    dimensions.heavenEarthGap +
                    dimensions.barThickness +
                    dimensions.activeGap +
                    dimensions.beadSize / 2 +
                    (earthActive - 1) *
                      (dimensions.beadSize + dimensions.adjacentSpacing) +
                    dimensions.beadSize / 2 +
                    dimensions.inactiveGap +
                    dimensions.beadSize / 2 +
                    (bead.position - earthActive) *
                      (dimensions.beadSize + dimensions.adjacentSpacing);
                } else {
                  // No active beads: position after reckoning bar + gap, adjacent inactive beads touch (line 259)
                  y =
                    dimensions.heavenEarthGap +
                    dimensions.barThickness +
                    dimensions.inactiveGap +
                    dimensions.beadSize / 2 +
                    bead.position *
                      (dimensions.beadSize + dimensions.adjacentSpacing);
                }
              }
            }

            // Check if bead is highlighted - NO MORE EFFECTIVECOLUMNS THREADING!
            const regularHighlight = isBeadHighlightedByPlaceValue(
              bead,
              highlightBeads,
            );
            const stepHighlight = getBeadStepHighlight(
              bead,
              stepBeadHighlights,
              currentStep,
            );
            const isHighlighted =
              regularHighlight || stepHighlight.isHighlighted;

            const color = getBeadColor(
              bead,
              effectiveColumns,
              finalConfig.colorScheme,
              finalConfig.colorPalette,
              isHighlighted,
            );

            // Apply custom styling
            const beadStyle = mergeBeadStyles(
              { fill: color },
              customStyles,
              effectiveColumns - 1 - bead.placeValue, // Convert place value to column index for styling
              bead.type,
              bead.type === "earth" ? bead.position : undefined,
              bead.active,
            );

            // Check if bead is disabled - NO MORE EFFECTIVECOLUMNS THREADING!
            const isDisabled =
              isBeadDisabledByPlaceValue(bead, disabledBeads) ||
              disabledColumns?.includes(effectiveColumns - 1 - bead.placeValue);

            return (
              <Bead
                key={`bead-pv${bead.placeValue}-${bead.type}-${bead.type === "earth" ? bead.position : 0}`}
                bead={bead}
                x={x}
                y={y}
                size={dimensions.beadSize}
                shape={finalConfig.beadShape}
                color={beadStyle.fill || color}
                customStyle={beadStyle}
                isHighlighted={isHighlighted}
                isDisabled={isDisabled}
                enableAnimation={finalConfig.animated}
                enableGestures={finalConfig.interactive || finalConfig.gestures}
                hideInactiveBeads={finalConfig.hideInactiveBeads}
                showDirectionIndicator={
                  showDirectionIndicators && stepHighlight.isCurrentStep
                }
                direction={stepHighlight.direction}
                isCurrentStep={stepHighlight.isCurrentStep}
                onClick={
                  finalConfig.interactive && !isDisabled
                    ? (event) => handleBeadClick(bead, event)
                    : undefined
                }
                onHover={
                  callbacks?.onBeadHover
                    ? (event) => {
                        const beadClickEvent: BeadClickEvent = {
                          bead,
                          columnIndex: effectiveColumns - 1 - bead.placeValue, // Convert place value to column index for callback
                          beadType: bead.type,
                          position: bead.position,
                          active: bead.active,
                          value: bead.value,
                          event,
                        };
                        callbacks.onBeadHover?.(beadClickEvent);
                      }
                    : undefined
                }
                onLeave={
                  callbacks?.onBeadLeave
                    ? (event) => {
                        const beadClickEvent: BeadClickEvent = {
                          bead,
                          columnIndex: effectiveColumns - 1 - bead.placeValue, // Convert place value to column index for callback
                          beadType: bead.type,
                          position: bead.position,
                          active: bead.active,
                          value: bead.value,
                          event,
                        };
                        callbacks.onBeadLeave?.(beadClickEvent);
                      }
                    : undefined
                }
                onGestureToggle={handleGestureToggle}
                onRef={
                  callbacks?.onBeadRef
                    ? (element) => callbacks.onBeadRef!(bead, element)
                    : undefined
                }
                heavenEarthGap={dimensions.heavenEarthGap}
                barY={barY}
                colorScheme={finalConfig.colorScheme}
                colorPalette={finalConfig.colorPalette}
                totalColumns={effectiveColumns}
                enhanced3d={enhanced3d}
                material3d={material3d}
                physics3d={physics3d}
                columnIndex={colIndex}
                mousePosition={mousePos}
                containerBounds={containerBounds}
              />
            );
          }),
        )}

        {/* Background rectangles for place values - in SVG */}
        {finalConfig.showNumbers &&
          placeValues.map((value, columnIndex) => {
            const placeValue = effectiveColumns - 1 - columnIndex;
            const x =
              columnIndex * dimensions.rodSpacing + dimensions.rodSpacing / 2;
            // Position background rectangles to match the text positioning
            const baseHeight =
              dimensions.heavenEarthGap +
              5 * (dimensions.beadSize + 4 * finalConfig.scaleFactor) +
              10 * finalConfig.scaleFactor;
            const y = baseHeight + 25;
            const isActive = activeColumn === columnIndex;

            return (
              <rect
                key={`place-bg-pv${placeValue}`}
                x={x - 12 * finalConfig.scaleFactor}
                y={y - 12 * finalConfig.scaleFactor}
                width={24 * finalConfig.scaleFactor}
                height={24 * finalConfig.scaleFactor}
                fill={isActive ? "#e3f2fd" : "#f5f5f5"}
                stroke={isActive ? "#2196f3" : "#ccc"}
                strokeWidth={
                  isActive
                    ? 2 * finalConfig.scaleFactor
                    : 1 * finalConfig.scaleFactor
                }
                rx={3 * finalConfig.scaleFactor}
                style={{
                  cursor: finalConfig.interactive ? "pointer" : "default",
                }}
                onClick={
                  finalConfig.interactive
                    ? () => setActiveColumn(columnIndex)
                    : undefined
                }
              />
            );
          })}

        {/* NumberFlow place value displays - inside SVG using foreignObject */}
        {finalConfig.showNumbers &&
          placeValues.map((value, columnIndex) => {
            const placeValue = effectiveColumns - 1 - columnIndex;
            const x =
              columnIndex * dimensions.rodSpacing + dimensions.rodSpacing / 2;
            // Position numbers within the allocated numbers space (below the baseHeight)
            const baseHeight =
              dimensions.heavenEarthGap +
              5 * (dimensions.beadSize + 4 * finalConfig.scaleFactor) +
              10 * finalConfig.scaleFactor;
            const y = baseHeight + 25;

            return (
              <foreignObject
                key={`place-number-pv${placeValue}`}
                x={x - 12 * finalConfig.scaleFactor}
                y={y - 8 * finalConfig.scaleFactor}
                width={24 * finalConfig.scaleFactor}
                height={16 * finalConfig.scaleFactor}
                style={{ pointerEvents: "none" }}
              >
                <div
                  style={{
                    width: "100%",
                    height: "100%",
                    display: "flex",
                    alignItems: "center",
                    justifyContent: "center",
                    fontSize: `${Math.max(8, 14 * finalConfig.scaleFactor)}px`,
                    fontFamily: "monospace",
                    fontWeight: "bold",
                    pointerEvents: finalConfig.interactive ? "auto" : "none",
                    cursor: finalConfig.interactive ? "pointer" : "default",
                  }}
                  onClick={
                    finalConfig.interactive
                      ? () => setActiveColumn(columnIndex)
                      : undefined
                  }
                >
                  <NumberFlow
                    value={value}
                    format={{ style: "decimal" }}
                    style={{
                      fontFamily: "monospace",
                      fontWeight: "bold",
                      fontSize: `${Math.max(8, 14 * finalConfig.scaleFactor)}px`,
                    }}
                  />
                </div>
              </foreignObject>
            );
          })}

        {/* Overlay system for tooltips, arrows, highlights, etc. */}
        {overlays.map((overlay) => {
          if (overlay.visible === false) return null;

          let position = { x: 0, y: 0 };

          // Calculate overlay position based on target
          if (overlay.target.type === "bead") {
            // Find the bead position
            const targetColumn = overlay.target.columnIndex;
            const targetBeadType = overlay.target.beadType;
            const targetBeadPosition = overlay.target.beadPosition;

            if (targetColumn !== undefined && targetBeadType) {
              const x =
                targetColumn * dimensions.rodSpacing +
                dimensions.rodSpacing / 2;
              let y = 0;

              if (targetBeadType === "heaven") {
                const columnState = columnStates[targetColumn];
                if (!columnState) {
                  console.error(
                    `Invalid abacus overlay: columnState is undefined for overlay targeting column ${targetColumn}`
                  );
                  return;
                }
                y = columnState.heavenActive
                  ? dimensions.heavenEarthGap -
                    dimensions.beadSize / 2 -
                    dimensions.activeGap
                  : dimensions.heavenEarthGap -
                    dimensions.inactiveGap -
                    dimensions.beadSize / 2;
              } else if (
                targetBeadType === "earth" &&
                targetBeadPosition !== undefined
              ) {
                const columnState = columnStates[targetColumn];
                if (!columnState) {
                  console.error(
                    `Invalid abacus overlay: columnState is undefined for overlay targeting column ${targetColumn}`
                  );
                  return;
                }
                const earthActive = columnState.earthActive;
                const isActive = targetBeadPosition < earthActive;

                if (isActive) {
                  y =
                    dimensions.heavenEarthGap +
                    dimensions.barThickness +
                    dimensions.activeGap +
                    dimensions.beadSize / 2 +
                    targetBeadPosition *
                      (dimensions.beadSize + dimensions.adjacentSpacing);
                } else {
                  if (earthActive > 0) {
                    y =
                      dimensions.heavenEarthGap +
                      dimensions.barThickness +
                      dimensions.activeGap +
                      dimensions.beadSize / 2 +
                      (earthActive - 1) *
                        (dimensions.beadSize + dimensions.adjacentSpacing) +
                      dimensions.beadSize / 2 +
                      dimensions.inactiveGap +
                      dimensions.beadSize / 2 +
                      (targetBeadPosition - earthActive) *
                        (dimensions.beadSize + dimensions.adjacentSpacing);
                  } else {
                    y =
                      dimensions.heavenEarthGap +
                      dimensions.barThickness +
                      dimensions.inactiveGap +
                      dimensions.beadSize / 2 +
                      targetBeadPosition *
                        (dimensions.beadSize + dimensions.adjacentSpacing);
                  }
                }
              }

              position = calculateOverlayPosition(
                overlay,
                dimensions,
                targetColumn,
                { x, y },
              );
            }
          } else {
            position = calculateOverlayPosition(overlay, dimensions);
          }

          return (
            <foreignObject
              key={overlay.id}
              x={position.x}
              y={position.y}
              width="200"
              height="100"
              style={{
                overflow: "visible",
                pointerEvents: "none",
                ...overlay.style,
              }}
              className={overlay.className}
            >
              <div style={{ position: "relative", pointerEvents: "none" }}>
                {overlay.content}
              </div>
            </foreignObject>
          );
        })}

        {/* Column interaction areas - rendered last to be on top of all other elements */}
        {Array.from({ length: effectiveColumns }, (_, colIndex) => {
          const placeValue = effectiveColumns - 1 - colIndex;
          const x =
            colIndex * dimensions.rodSpacing + dimensions.rodSpacing / 2;
          const columnStyles = customStyles?.columns?.[colIndex];
          const hasColumnHighlight = columnStyles?.columnPost;

          const backgroundWidth = dimensions.rodSpacing; // Full column width for better interaction
          const backgroundHeight = dimensions.height;

          return (
            <rect
              key={`column-interaction-pv${placeValue}`}
              x={x - backgroundWidth / 2}
              y={0}
              width={backgroundWidth}
              height={backgroundHeight}
              fill="transparent"
              stroke="none"
              style={{
                cursor:
                  callbacks?.onColumnClick || callbacks?.onColumnHover
                    ? "pointer"
                    : "default",
                pointerEvents:
                  callbacks?.onColumnClick || callbacks?.onColumnHover
                    ? "all"
                    : "none", // Only capture events when callbacks exist
              }}
              onClick={(e) => callbacks?.onColumnClick?.(colIndex, e)}
              onMouseEnter={(e) => callbacks?.onColumnHover?.(colIndex, e)}
              onMouseLeave={(e) => callbacks?.onColumnLeave?.(colIndex, e)}
            />
          );
        })}
      </svg>
    </div>
  );
};

export default AbacusReact;
