import * as React from "react"
import {Keyboard, Pressable, StyleSheet, View} from "react-native"
import Animated, {
    Extrapolate,
    interpolate,
    type SharedValue,
    useAnimatedReaction,
    useAnimatedStyle,
    useSharedValue,
    withSpring,
} from "react-native-reanimated"

import {useBottomSheetToolboxInternalContext} from "../context"
import {useBottomSheetActions} from "../hooks"
import {CLOSED_SNAP_POINT, normalizeSheetPosition, type SheetPosition} from "../utils"

/**
 * We want the bottom sheet's opacity to persist across new mounts.
 * However, we want to avoid polluting the internal context.
 */
interface BottomSheetToolboxInternalBackdropContextShape {
    opacitySharedValue: SharedValue<number>
    hide: boolean
}

const BottomSheetToolboxBackdropContext = React.createContext<
    BottomSheetToolboxInternalBackdropContextShape | undefined
>(undefined)

export function NewBottomSheetToolboxBackdropProvider({
    children,
    hide = false,
}: React.PropsWithChildren<{hide?: boolean}>) {
    const opacitySharedValue = useSharedValue(0)
    const contextValue = React.useMemo(() => ({opacitySharedValue, hide}), [hide])

    return (
        <BottomSheetToolboxBackdropContext.Provider value={contextValue}>
            {children}
        </BottomSheetToolboxBackdropContext.Provider>
    )
}

export function OldBottomSheetToolboxBackdropProvider({
    children,
    inheritHide,
    hide = false,
}: React.PropsWithChildren<{hide?: boolean; inheritHide: boolean}>) {
    const backdropContext = React.useContext(BottomSheetToolboxBackdropContext)
    if (!backdropContext) throw new Error("BottomSheetToolboxBackdropProvider not found.")

    const {opacitySharedValue, hide: ancestorHide} = backdropContext

    const contextValue = React.useMemo(
        () => ({
            opacitySharedValue,
            hide: hide || (ancestorHide && inheritHide),
        }),
        [ancestorHide, hide, inheritHide, opacitySharedValue]
    )

    return (
        <BottomSheetToolboxBackdropContext.Provider value={contextValue}>
            {children}
        </BottomSheetToolboxBackdropContext.Provider>
    )
}

export function BottomSheetToolboxBackdropProvider({
    children,
    reset = false,
    hide = false,
    inheritHide = false,
}: React.PropsWithChildren<{
    reset?: boolean
    hide?: boolean
    inheritHide?: boolean
}>) {
    const inheritedContext = React.useContext(BottomSheetToolboxBackdropContext)

    return inheritedContext !== undefined && !reset ? (
        <OldBottomSheetToolboxBackdropProvider hide={hide} inheritHide={inheritHide}>
            {children}
        </OldBottomSheetToolboxBackdropProvider>
    ) : (
        <NewBottomSheetToolboxBackdropProvider hide={hide}>
            {children}
        </NewBottomSheetToolboxBackdropProvider>
    )
}

/**
 * Retrieve the bottom sheet internal context, and throw if unavailable.
 */
export function useBottomSheetToolboxBackdropContext() {
    const context = React.useContext(BottomSheetToolboxBackdropContext)

    if (!context) {
        throw new Error("BottomSheetToolboxBackdropProvider not found.")
    }

    return context
}

export interface BottomSheetToolboxDefaultBackdropProps extends React.PropsWithChildren {
    /**
     * What opacity should the backdrop have?
     */
    maxOpacity?: number

    /**
     * What color should the backdrop be?
     */
    color?: string

    /**
     * At what position should the backdrop reach its max opacity?
     * The default strategy is to use the first non-zero snap point.
     */
    maxOpacityPoint?: SheetPosition

    /**
     * Convenience alternative to maxOpacityPoint for selecting SnapPoints.
     */
    maxOpacitySnapIndex?: number
}

export function BottomSheetToolboxDefaultBackdrop({
    maxOpacity = 0.7,
    color = "#000000",
    children,
    maxOpacityPoint,
    maxOpacitySnapIndex,
}: BottomSheetToolboxDefaultBackdropProps) {
    const {close} = useBottomSheetActions("BottomSheetToolboxDefaultBackdrop")
    const {
        currentPositionSharedValue,
        normalizedSnapPointsSharedValue,
        childrenHeightSharedValue,
        wrapperHeightSharedValue,
        footerHeightSharedValue,
    } = useBottomSheetToolboxInternalContext()
    const {opacitySharedValue, hide} = useBottomSheetToolboxBackdropContext()

    useAnimatedReaction(
        () => ({
            currentPosition: currentPositionSharedValue.get(),
            snapPoints: normalizedSnapPointsSharedValue.get(),
        }),
        ({snapPoints, currentPosition}) => {
            if (hide) return

            const maxOpacityPosition = maxOpacityPoint
                ? normalizeSheetPosition({
                      sheetPosition: maxOpacityPoint,
                      childrenHeightSharedValue,
                      wrapperHeightSharedValue,
                      footerHeightSharedValue,
                  })
                : null
            const maxOpacitySnapIndexPosition =
                maxOpacitySnapIndex && snapPoints && maxOpacitySnapIndex in snapPoints
                    ? snapPoints[maxOpacitySnapIndex]
                    : null

            const maxPosition =
                (maxOpacityPosition ||
                    maxOpacitySnapIndexPosition ||
                    snapPoints?.find((point) => point !== CLOSED_SNAP_POINT)) ??
                CLOSED_SNAP_POINT

            const openRatio = maxPosition === 0 ? 0 : currentPosition / maxPosition
            const opacity = interpolate(openRatio, [0, 1], [0, maxOpacity], Extrapolate.CLAMP)

            // The snap-points, position, and maxOpacity can make notable instant
            // changes. We don't want the backdrop to appear glitchy when this happens.
            opacitySharedValue.set(
                withSpring(opacity, {
                    stiffness: 200,
                    mass: 0.2,
                })
            )
        },
        [currentPositionSharedValue, normalizedSnapPointsSharedValue]
    )

    const animatedBackdropStyle = useAnimatedStyle(
        () => ({
            opacity: opacitySharedValue.get(),
            backgroundColor: color,
        }),
        [color, opacitySharedValue]
    )

    const handleClose = React.useCallback(() => {
        Keyboard.isVisible() && Keyboard.dismiss()
        close()
    }, [close])

    return hide ? null : (
        <Pressable onPress={handleClose} style={StyleSheet.absoluteFill}>
            <Animated.View style={[StyleSheet.absoluteFill, animatedBackdropStyle]}>
                <View>{children}</View>
            </Animated.View>
        </Pressable>
    )
}
