import { Image } from 'expo-image'
import { Gesture, GestureDetector } from 'react-native-gesture-handler'
import Animated, {
  useAnimatedStyle,
  useSharedValue,
  withSpring,
} from 'react-native-reanimated'

const AnimatedImage = Animated.createAnimatedComponent(Image)

// Pinch-to-zoom image that SELF-SIZES to the explicit width/height carried in
// `style`. There is deliberately no `flex: 1` wrapper:
//
// The lightbox nests this inside SaveableMedia, whose iOS host is a native
// context-menu view (zeego → UIContextMenuInteraction). That native host does
// not reliably forward a definite height to its children, so a `flex: 1` chain
// here has no parent height to grow into — it collapses and the screen-tall
// image top-anchors (the "image stuck to the top, half off-screen" bug). By
// shrink-wrapping the gesture surface to the image's own width/height this
// becomes a self-sized leaf like every other media node, so the wrapper never
// has to forward flex sizing and the overlay's center/center lands it
// full-screen. The image's own `contentFit="contain"` then letterboxes within
// that screen-sized box, centering it.
const ZoomableImage = ({
  source,
  style,
  placeholder,
  contentFit,
}: {
  source: any
  style: any
  placeholder: any
  contentFit?: 'cover' | 'contain' | 'fill' | 'none' | 'scale-down'
}) => {
  const scale = useSharedValue(1)
  const savedScale = useSharedValue(1)
  const translateX = useSharedValue(0)
  const translateY = useSharedValue(0)
  const originX = useSharedValue(0)
  const originY = useSharedValue(0)

  const pinchGesture = Gesture.Pinch()
    .onUpdate((event) => {
      const pinchScale = event.scale
      const nextScale = savedScale.value * pinchScale
      const touchX = event.focalX
      const touchY = event.focalY

      if (scale.value === savedScale.value) {
        originX.value = touchX
        originY.value = touchY
      }

      const focalDeltaX = (touchX - originX.value) * (pinchScale - 1)
      const focalDeltaY = (touchY - originY.value) * (pinchScale - 1)

      scale.value = nextScale
      translateX.value = focalDeltaX
      translateY.value = focalDeltaY
    })
    .onEnd(() => {
      savedScale.value = 1
      scale.value = withSpring(1)
      translateX.value = withSpring(0)
      translateY.value = withSpring(0)
    })

  const animatedStyle = useAnimatedStyle(() => ({
    transform: [
      { translateX: translateX.value },
      { translateY: translateY.value },
      { scale: scale.value },
    ],
  }))

  // The wrapper shrink-wraps to the image's explicit width/height; the zoom
  // transform lives on the image and (being a transform) doesn't change layout,
  // so the gesture surface stays the un-zoomed image box.
  return (
    <GestureDetector gesture={pinchGesture}>
      <Animated.View collapsable={false}>
        <AnimatedImage
          source={source}
          style={[style, animatedStyle]}
          placeholder={placeholder}
          contentFit={contentFit}
          cachePolicy="memory"
          priority="high"
        />
      </Animated.View>
    </GestureDetector>
  )
}

export default ZoomableImage
