import { useState, type ReactNode } from 'react';
import {
  StyleSheet,
  View,
  type LayoutChangeEvent
} from 'react-native';
import { Ionicons } from '@expo/vector-icons';
import {
  Gesture,
  GestureDetector,
  type GestureType
} from 'react-native-gesture-handler';
import Animated, {
  runOnJS,
  useAnimatedStyle,
  useSharedValue,
  withTiming
} from 'react-native-reanimated';
import { useColors } from '@/theme/themed';
import { motion } from '@/theme/motion';
import { playHaptic } from '@/lib/haptics';

type IconName = keyof typeof Ionicons.glyphMap;

const SWIPE_ACTIVE_OFFSET_X = 10;
// Scroll-slop-sized: at 30 every vertical drag starting on a row had to travel
// 30px before the pan failed and the surrounding scrollable could win.
const SWIPE_FAIL_OFFSET_Y = 12;

export interface SwipeAction {
  icon: IconName;
  /** Background of the revealed action lane. */
  color: string;
  iconColor?: string;
  onCommit: () => void;
}

interface SwipeableRowProps {
  /** Revealed on the LEFT as the row is dragged right (a rightward swipe). */
  swipeRight?: SwipeAction;
  /** Revealed on the RIGHT as the row is dragged left (a leftward swipe). */
  swipeLeft?: SwipeAction;
  /**
   * Optional vertical gesture (e.g. a hold-to-drag reorder) raced against the
   * horizontal swipe in the SAME detector — composing here avoids the nested
   * GestureDetectors that broke continuous hold-and-drag.
   */
  dragGesture?: GestureType;
  enabled?: boolean;
  children: ReactNode;
}

/**
 * Horizontally swipeable row. Translation is clamped to ±width/2; a haptic tick
 * fires when crossing the ±width/4 "arm" point in either direction (arming to
 * commit, or backing off). Releasing past the arm point runs the matching
 * action; otherwise the row springs back. Vertical drags fall through so the row
 * still scrolls / lets a parent sheet pan.
 */
export function SwipeableRow({
  swipeRight,
  swipeLeft,
  dragGesture,
  enabled = true,
  children,
}: SwipeableRowProps) {
  const colors = useColors();
  const tx = useSharedValue(0);
  const armed = useSharedValue(false);
  const [rowWidth, setRowWidth] = useState(0);

  const max = rowWidth / 2;
  const arm = rowWidth / 4;
  const hasRight = !!swipeRight;
  const hasLeft = !!swipeLeft;

  const onLayout = (e: LayoutChangeEvent) => setRowWidth(e.nativeEvent.layout.width);

  const onCommit = (direction: 'right' | 'left') => {
    if (direction === 'right') swipeRight?.onCommit();
    else swipeLeft?.onCommit();
    playHaptic('confirm');
  };

  const pan = Gesture.Pan()
    .enabled(enabled && rowWidth > 0 && (hasRight || hasLeft))
    .activeOffsetX([-SWIPE_ACTIVE_OFFSET_X, SWIPE_ACTIVE_OFFSET_X])
    .failOffsetY([-SWIPE_FAIL_OFFSET_Y, SWIPE_FAIL_OFFSET_Y])
    .onUpdate((e) => {
      let t = e.translationX;
      if (t > 0 && !hasRight) t = 0;
      if (t < 0 && !hasLeft) t = 0;
      t = Math.max(-max, Math.min(max, t));
      tx.value = t;
      const nowArmed = Math.abs(t) >= arm;
      if (nowArmed !== armed.value) {
        armed.value = nowArmed;
        runOnJS(playHaptic)('threshold');
      }
    })
    .onEnd(() => {
      const t = tx.value;
      if (t >= arm && hasRight) runOnJS(onCommit)('right');
      else if (t <= -arm && hasLeft) runOnJS(onCommit)('left');
      armed.value = false;
      tx.value = withTiming(0, motion.quick);
    });

  // Race so a horizontal swipe and a (long-press) vertical drag never fight:
  // whichever activates first wins and cancels the other.
  const gesture = dragGesture ? Gesture.Race(dragGesture, pan) : pan;

  const contentStyle = useAnimatedStyle(() => ({ transform: [{ translateX: tx.value }] }));
  const leftLaneStyle = useAnimatedStyle(() => ({ opacity: tx.value > 1 ? 1 : 0 }));
  const rightLaneStyle = useAnimatedStyle(() => ({ opacity: tx.value < -1 ? 1 : 0 }));

  return (
    <View style={styles.wrap} onLayout={onLayout}>
      {swipeRight ? (
        <Animated.View
          pointerEvents="none"
          style={[styles.lane, styles.laneLeft, { backgroundColor: swipeRight.color }, leftLaneStyle]}
        >
          <Ionicons name={swipeRight.icon} size={22} color={swipeRight.iconColor ?? colors.bgPrimary} />
        </Animated.View>
      ) : null}
      {swipeLeft ? (
        <Animated.View
          pointerEvents="none"
          style={[styles.lane, styles.laneRight, { backgroundColor: swipeLeft.color }, rightLaneStyle]}
        >
          <Ionicons name={swipeLeft.icon} size={22} color={swipeLeft.iconColor ?? colors.bgPrimary} />
        </Animated.View>
      ) : null}
      <GestureDetector gesture={gesture}>
        <Animated.View style={contentStyle}>{children}</Animated.View>
      </GestureDetector>
    </View>
  );
}

const styles = StyleSheet.create({
  wrap: {
    position: 'relative',
    overflow: 'hidden',
  },
  lane: {
    position: 'absolute',
    top: 0,
    left: 0,
    right: 0,
    bottom: 0,
    flexDirection: 'row',
    alignItems: 'center',
    paddingHorizontal: 24,
  },
  laneLeft: {
    justifyContent: 'flex-start',
  },
  laneRight: {
    justifyContent: 'flex-end',
  },
});
