import {Component, createRef} from 'react'
import {
  Dimensions,
  type LayoutChangeEvent,
  type NativeSyntheticEvent,
  Platform,
  type StyleProp,
  useWindowDimensions,
  View,
  type ViewStyle,
} from 'react-native'
import {useSafeAreaInsets} from 'react-native-safe-area-context'
import {requireNativeModule, requireNativeViewManager} from 'expo-modules-core'

import {
  type BottomSheetState,
  type BottomSheetViewProps,
} from './BottomSheet.types'
import {
  BottomSheetPortalProvider,
  Context as PortalContext,
} from './BottomSheetPortal'

const NativeView: React.ComponentType<
  BottomSheetViewProps & {
    ref: React.RefObject<any>
    style: StyleProp<ViewStyle>
  }
> = requireNativeViewManager('BottomSheet')

const NativeModule = requireNativeModule('BottomSheet')

const IS_IOS15 =
  Platform.OS === 'ios' &&
  // semvar - can be 3 segments, so can't use Number(Platform.Version)
  Number(Platform.Version.split('.').at(0)) < 16
// older android versions (15 and below) aren't naturally edge-to-edge
// and behave a little differently
const IS_NON_E2E_ANDROID =
  Platform.OS === 'android' && Number(Platform.Version) < 35

export class BottomSheetNativeComponent extends Component<
  BottomSheetViewProps,
  {
    open: boolean
    viewHeight?: number
  }
> {
  ref = createRef<any>()

  static contextType = PortalContext

  constructor(props: BottomSheetViewProps) {
    super(props)
    this.state = {
      open: false,
    }
  }

  present() {
    this.setState({open: true})
  }

  dismiss() {
    this.ref.current?.dismiss()
  }

  private onStateChange = (
    event: NativeSyntheticEvent<{state: BottomSheetState}>,
  ) => {
    const {state} = event.nativeEvent
    const isOpen = state !== 'closed'
    this.setState({open: isOpen})
    this.props.onStateChange?.(event)
  }

  static dismissAll = async () => {
    await NativeModule.dismissAll()
  }

  render() {
    const Portal = this.context as React.ContextType<typeof PortalContext>
    if (!Portal) {
      throw new Error(
        'BottomSheet: You need to wrap your component tree with a <BottomSheetPortalProvider> to use the bottom sheet.',
      )
    }

    if (!this.state.open) {
      return null
    }

    let extraStyles
    if (IS_IOS15 && this.state.viewHeight) {
      const screenHeight = Dimensions.get('screen').height
      const {viewHeight} = this.state
      const cornerRadius = this.props.cornerRadius ?? 0
      if (viewHeight < screenHeight / 2) {
        extraStyles = {
          height: viewHeight,
          marginTop: screenHeight / 2 - viewHeight,
          borderTopLeftRadius: cornerRadius,
          borderTopRightRadius: cornerRadius,
        }
      }
    }

    return (
      <Portal>
        <BottomSheetNativeComponentInner
          {...this.props}
          nativeViewRef={this.ref}
          onStateChange={this.onStateChange}
          extraStyles={extraStyles}
          onLayout={
            IS_IOS15
              ? e => {
                  const {height} = e.nativeEvent.layout
                  this.setState({viewHeight: height})
                }
              : undefined
          }
        />
      </Portal>
    )
  }
}

function BottomSheetNativeComponentInner({
  children,
  backgroundColor,
  maxHeight,
  onLayout,
  onStateChange,
  nativeViewRef,
  extraStyles,
  ...rest
}: BottomSheetViewProps & {
  extraStyles?: StyleProp<ViewStyle>
  onStateChange: (
    event: NativeSyntheticEvent<{state: BottomSheetState}>,
  ) => void
  nativeViewRef: React.RefObject<View>
  onLayout?: (event: LayoutChangeEvent) => void
}) {
  const insets = useSafeAreaInsets()
  const cornerRadius = rest.cornerRadius ?? 0
  const {height: screenHeight} = useWindowDimensions()

  // sigh... on older Android versions, screenHeight does not include safe area insets
  // on newer Androids + iOS, it does. we need to find the inner bit + the bottom inset
  // for the sheet content
  const sheetHeight = IS_NON_E2E_ANDROID
    ? screenHeight + insets.bottom
    : screenHeight - insets.top

  return (
    <NativeView
      {...rest}
      maxHeight={maxHeight}
      onStateChange={onStateChange}
      ref={nativeViewRef}
      style={{
        position: 'absolute',
        height: sheetHeight,
        width: '100%',
      }}
      containerBackgroundColor={backgroundColor}>
      <View
        style={[
          {
            flex: 1,
            backgroundColor,
          },
          maxHeight != null && {maxHeight},
          Platform.OS === 'android' && {
            borderTopLeftRadius: cornerRadius,
            borderTopRightRadius: cornerRadius,
            overflow: 'hidden',
          },
          extraStyles,
        ]}>
        <View
          onLayout={onLayout}
          style={maxHeight == null ? undefined : {flex: 1}}>
          <BottomSheetPortalProvider>{children}</BottomSheetPortalProvider>
        </View>
      </View>
    </NativeView>
  )
}
