import { useEffect, useMemo, useRef, useState } from "react";
import {
  Animated,
  Image,
  ImageSourcePropType,
  Pressable,
  StyleProp,
  StyleSheet,
  View,
  ViewStyle,
} from "react-native";

const AnimatedImage = Animated.createAnimatedComponent(Image);

const SIZE_PRESETS = {
  sm: { width: 64, height: 32 },
  md: { width: 80, height: 40 },
  lg: { width: 96, height: 48 },
} as const;

type SwitchSize = keyof typeof SIZE_PRESETS;

const DEFAULT_TOGGLE_DURATION = 400;
const DEFAULT_OFF_IMAGE =
  "https://images.vexels.com/media/users/3/163966/isolated/preview/6ecbb5ec8c121c0699c9b9179d6b24aa-england-flag-language-icon-circle.png";
const DEFAULT_ON_IMAGE =
  "https://cdn-icons-png.flaticon.com/512/197/197473.png";
const DEFAULT_INACTIVE_BG = "#D3DAD9";
const DEFAULT_ACTIVE_BG = "#C2E2FA";
const PRESSED_SCALE = 0.96;
const PRESS_FEEDBACK_DURATION = 120;

type RotateSwitchProps = {
  size?: SwitchSize;
  onImage?: ImageSourcePropType | string;
  offImage?: ImageSourcePropType | string;
  initialValue?: boolean;
  duration?: number;
  activeBackgroundColor?: string;
  inactiveBackgroundColor?: string;
  style?: StyleProp<ViewStyle>;
  onChange?: (value: boolean) => void;
};

const toImageSource = (
  input: ImageSourcePropType | string | undefined,
  fallbackUri: string
): ImageSourcePropType => {
  if (typeof input === "string") {
    return { uri: input };
  }

  if (input) {
    return input;
  }

  return { uri: fallbackUri };
};

const RotateSwitch = ({
  size = "md",
  onImage,
  offImage,
  duration,
  activeBackgroundColor = DEFAULT_ACTIVE_BG,
  inactiveBackgroundColor = DEFAULT_INACTIVE_BG,
  initialValue = false,
  style,
  onChange,
}: RotateSwitchProps) => {
  const { width: containerWidth, height: containerHeight } =
    SIZE_PRESETS[size] ?? SIZE_PRESETS.md;
  const knobSize = containerHeight;
  const knobTravel = containerWidth - knobSize;
  const animationDuration = Math.max(duration ?? DEFAULT_TOGGLE_DURATION, 0);

  const resolvedOffImage = useMemo(
    () => toImageSource(offImage, DEFAULT_OFF_IMAGE),
    [offImage]
  );
  const resolvedOnImage = useMemo(
    () => toImageSource(onImage, DEFAULT_ON_IMAGE),
    [onImage]
  );

  const [isOn, setIsOn] = useState(initialValue);
  const [bgOn, setBgOn] = useState(initialValue);
  const [displaySource, setDisplaySource] = useState<ImageSourcePropType>(
    initialValue ? resolvedOnImage : resolvedOffImage
  );

  const progress = useRef(new Animated.Value(initialValue ? 1 : 0)).current;
  const pressScale = useRef(new Animated.Value(1)).current;
  const listenerIdRef = useRef<string | number | null>(null);

  useEffect(() => {
    setDisplaySource(bgOn ? resolvedOnImage : resolvedOffImage);
  }, [bgOn, resolvedOffImage, resolvedOnImage]);

  const removeProgressListener = () => {
    if (listenerIdRef.current != null) {
      progress.removeListener(listenerIdRef.current as string);
      listenerIdRef.current = null;
    }
  };

  const attachHalfwaySwapListener = (next: boolean) => {
    removeProgressListener();
    let swapped = false;
    listenerIdRef.current = progress.addListener(({ value }) => {
      if (swapped) return;
      const crossedHalfway = next ? value >= 0.5 : value <= 0.5;
      if (!crossedHalfway) return;
      swapped = true;
      setBgOn(next);
      setDisplaySource(next ? resolvedOnImage : resolvedOffImage);
      removeProgressListener();
    });
  };

  // Clean up listener on unmount
  useEffect(() => {
    return () => {
      removeProgressListener();
    };
  }, []);

  // Keep internal state in sync when `initialValue` prop changes.
  // Users may pass a changing `initialValue` (like from parent state) and
  // expect the switch to reflect that. Animate `progress` toward the
  // corresponding value and update images/background when done.
  useEffect(() => {
    // If no change, do nothing
    if (initialValue === isOn) return;

    const next = initialValue;
    const targetValue = next ? 1 : 0;

    progress.stopAnimation();
    removeProgressListener();

    if (animationDuration <= 0) {
      progress.setValue(targetValue);
      setIsOn(next);
      setBgOn(next);
      setDisplaySource(next ? resolvedOnImage : resolvedOffImage);
      return;
    }

    // Update isOn immediately so accessibilityState etc. reflect change.
    setIsOn(next);

    attachHalfwaySwapListener(next);

    Animated.timing(progress, {
      toValue: targetValue,
      duration: animationDuration,
      useNativeDriver: true,
    }).start(() => {
      // Ensure final state reflects the target in case animation skips halfway listener.
      setBgOn(next);
      setDisplaySource(next ? resolvedOnImage : resolvedOffImage);
    });
  }, [
    initialValue,
    isOn,
    animationDuration,
    progress,
    resolvedOffImage,
    resolvedOnImage,
  ]);

  const knobTranslateX = progress.interpolate({
    inputRange: [0, 1],
    outputRange: [0, knobTravel],
  });

  const knobRotation = progress.interpolate({
    inputRange: [0, 1],
    outputRange: ["0deg", "180deg"],
  });

  const animatePress = (toValue: number) => {
    Animated.timing(pressScale, {
      toValue,
      duration: PRESS_FEEDBACK_DURATION,
      useNativeDriver: true,
    }).start();
  };

  const handlePressIn = () => {
    animatePress(PRESSED_SCALE);
  };

  const handlePressOut = () => {
    animatePress(1);
  };

  const handleToggle = () => {
    const next = !isOn;
    const targetValue = next ? 1 : 0;

    progress.stopAnimation();
    removeProgressListener();

    if (animationDuration <= 0) {
      progress.setValue(targetValue);
      setIsOn(next);
      setBgOn(next);
      onChange?.(next);
      return;
    }

    setIsOn(next);

    attachHalfwaySwapListener(next);

    Animated.timing(progress, {
      toValue: targetValue,
      duration: animationDuration,
      useNativeDriver: true,
    }).start(() => {
      setBgOn(next);
      onChange?.(next);
    });
  };

  return (
    <Pressable
      onPress={handleToggle}
      onPressIn={handlePressIn}
      onPressOut={handlePressOut}
      accessibilityRole="switch"
      accessibilityState={{ checked: isOn }}
      style={[styles.pressable, style]}
    >
      <Animated.View
        style={[
          styles.shadowWrapper,
          {
            transform: [{ scale: pressScale }],
            width: containerWidth,
            height: containerHeight,
            borderRadius: containerHeight / 2,
          },
        ]}
      >
        <View
          style={[
            styles.container,
            {
              borderRadius: containerHeight / 2,
              backgroundColor: bgOn
                ? activeBackgroundColor
                : inactiveBackgroundColor,
            },
          ]}
        >
          <AnimatedImage
            source={displaySource}
            style={[
              styles.knob,
              {
                width: knobSize,
                height: knobSize,
                borderRadius: knobSize / 2,
                transform: [
                  { translateX: knobTranslateX },
                  { rotate: knobRotation },
                ],
              },
            ]}
          />
        </View>
      </Animated.View>
    </Pressable>
  );
};

const styles = StyleSheet.create({
  pressable: {
    alignSelf: "flex-start",
  },
  shadowWrapper: {
    justifyContent: "center",
    position: "relative",
    shadowColor: "#000",
    shadowOpacity: 0.15,
    shadowOffset: { width: 0, height: 4 },
    shadowRadius: 6,
    elevation: 6,
    backgroundColor: "transparent",
  },
  container: {
    flex: 1,
    justifyContent: "center",
    position: "relative",
    overflow: "hidden",
  },
  knob: {
    position: "absolute",
    top: 0,
    left: 0,
  },
});

export default RotateSwitch;
