/* eslint-disable react-hooks/immutability -- Reanimated shared values are mutable gesture state. */
import { useMemo, useState } from 'react';
import { StyleSheet, View, type LayoutChangeEvent } from 'react-native';
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
import Animated, { runOnJS, useAnimatedStyle, useSharedValue } from 'react-native-reanimated';
import { Text } from '@/components/Text';
import { radius, spacing } from '@/theme';
import { createThemedStyles } from '@/theme/themed';
import { rgbaFromHex } from '@/theme/colorUtils';
import { playHaptic } from '@/lib/haptics';
import { usePullSearchGestureRef } from '@/components/search/PullSearchGesture';
import { RAIL_LETTERS } from '@/lib/letterIndex';

const CELL_HEIGHT = 17;
const RAIL_PAD = spacing.xs;
const RAIL_HEIGHT = RAIL_LETTERS.length * CELL_HEIGHT + RAIL_PAD * 2;
const BUBBLE_SIZE = 52;

interface AlphabetRailProps {
  /** Letters present in the current list — the rest render dimmed. */
  activeLetters: ReadonlySet<string>;
  onJumpToLetter: (letter: string) => void;
  /**
   * Fired when the finger lifts. The screen debounces `onJumpToLetter` so a fast
   * scrub does not rebuild the list once per letter crossed; this is its cue to
   * commit the last letter immediately instead of waiting out the debounce.
   */
  onScrubEnd?: () => void;
}

/**
 * A-Z scrubber overlaid on the right edge of a library list. Fixed cell
 * geometry (full #A-Z always rendered) keeps the pointer math trivial; one
 * haptic tick per letter crossed. The magnified letter bubble tracks the
 * finger's vertical position (Y driven on the UI thread; the letter text only
 * changes on a letter-cross). Blocks the pull-to-search gesture so a scrub at
 * scroll-top never arms the search indicator.
 */
export function AlphabetRail({ activeLetters, onJumpToLetter, onScrubEnd }: AlphabetRailProps) {
  const styles = useStyles();
  const pullSearchRef = usePullSearchGestureRef();
  const [scrubLetter, setScrubLetter] = useState<string | null>(null);
  const lastLetter = useSharedValue('');
  // Rail's top offset inside the (vertically-centered) wrap + the finger's Y
  // within the rail, so the bubble can be placed in wrap-space.
  const railTop = useSharedValue(0);
  const bubbleY = useSharedValue(0);

  const scrubTo = (letter: string) => {
    if (!activeLetters.has(letter)) {
      setScrubLetter(null);
      return;
    }
    setScrubLetter(letter);
    playHaptic('frequentStep');
    onJumpToLetter(letter);
  };
  const endScrub = () => {
    setScrubLetter(null);
    onScrubEnd?.();
  };

  const pan = useMemo(() => {
    const gesture = Gesture.Pan()
      .minDistance(0)
      .onBegin((event) => {
        'worklet';
        lastLetter.value = '';
        const y = Math.max(0, Math.min(RAIL_HEIGHT, event.y));
        bubbleY.value = railTop.value + y;
        const index = Math.max(
          0,
          Math.min(RAIL_LETTERS.length - 1, Math.floor((y - RAIL_PAD) / CELL_HEIGHT))
        );
        const letter = RAIL_LETTERS[index];
        lastLetter.value = letter;
        runOnJS(scrubTo)(letter);
      })
      .onUpdate((event) => {
        'worklet';
        const y = Math.max(0, Math.min(RAIL_HEIGHT, event.y));
        // Track the finger every frame for a smooth bubble; the letter/haptic
        // below only fire when the letter actually changes.
        bubbleY.value = railTop.value + y;
        const index = Math.max(
          0,
          Math.min(RAIL_LETTERS.length - 1, Math.floor((y - RAIL_PAD) / CELL_HEIGHT))
        );
        const letter = RAIL_LETTERS[index];
        if (letter === lastLetter.value) return;
        lastLetter.value = letter;
        runOnJS(scrubTo)(letter);
      })
      .onFinalize(() => {
        'worklet';
        lastLetter.value = '';
        runOnJS(endScrub)();
      });
    return pullSearchRef ? gesture.blocksExternalGesture(pullSearchRef) : gesture;
    // eslint-disable-next-line react-hooks/exhaustive-deps -- scrubTo/endScrub capture the latest props via render closure
  }, [lastLetter, bubbleY, railTop, pullSearchRef, activeLetters, onJumpToLetter, onScrubEnd]);

  const bubbleStyle = useAnimatedStyle(() => ({
    transform: [{ translateY: bubbleY.value - BUBBLE_SIZE / 2 }],
  }));

  const onRailLayout = (e: LayoutChangeEvent) => {
    railTop.value = e.nativeEvent.layout.y;
  };

  return (
    <View style={styles.wrap} pointerEvents="box-none">
      {scrubLetter ? (
        <Animated.View style={[styles.bubble, bubbleStyle]} pointerEvents="none">
          <Text variant="mono" style={styles.bubbleLetter}>
            {scrubLetter}
          </Text>
        </Animated.View>
      ) : null}
      <GestureDetector gesture={pan}>
        <View style={styles.rail} hitSlop={{ left: 12, right: 8 }} onLayout={onRailLayout}>
          {RAIL_LETTERS.map((letter) => {
            const present = activeLetters.has(letter);
            const scrubbing = letter === scrubLetter;
            return (
              <View key={letter} style={styles.cell}>
                <Text
                  variant="mono"
                  style={[
                    styles.letter,
                    present ? styles.letterPresent : styles.letterAbsent,
                    scrubbing && styles.letterScrubbing,
                  ]}
                >
                  {letter}
                </Text>
              </View>
            );
          })}
        </View>
      </GestureDetector>
    </View>
  );
}

const useStyles = createThemedStyles((colors) => ({
  wrap: {
    position: 'absolute',
    top: 0,
    bottom: 0,
    // Overhang the Screen's horizontal padding so the rail hugs the true edge.
    right: -spacing.md,
    justifyContent: 'center',
    alignItems: 'flex-end',
  },
  // A faint scrim strip rather than a bordered glass pill: transparent enough to
  // feel like an overlay, dark enough to keep the letters legible over bright art.
  rail: {
    width: 16,
    paddingVertical: RAIL_PAD,
    alignItems: 'center',
    backgroundColor: rgbaFromHex(colors.bgPrimary, 0.35),
    borderRadius: radius.pill,
  },
  cell: {
    height: CELL_HEIGHT,
    alignItems: 'center',
    justifyContent: 'center',
  },
  letter: {
    fontSize: 10,
    lineHeight: CELL_HEIGHT,
  },
  letterPresent: {
    color: colors.textSecondary,
  },
  letterAbsent: {
    color: colors.textTertiary,
    opacity: 0.4,
  },
  letterScrubbing: {
    color: colors.accentTextStrong,
  },
  bubble: {
    position: 'absolute',
    top: 0,
    right: 34,
    width: BUBBLE_SIZE,
    height: BUBBLE_SIZE,
    borderRadius: radius.lg,
    alignItems: 'center',
    justifyContent: 'center',
    backgroundColor: colors.bgTertiary,
    borderColor: colors.glassBorder,
    borderWidth: StyleSheet.hairlineWidth,
    elevation: 8,
    shadowColor: '#000',
    shadowOpacity: 0.28,
    shadowRadius: 12,
    shadowOffset: { width: 0, height: 6 },
  },
  bubbleLetter: {
    fontSize: 26,
    lineHeight: 30,
    color: colors.accentTextStrong,
  },
}));
