import * as React from "react"
import {Keyboard, Platform} from "react-native"
import {
    type AnimatedKeyboardOptions,
    Extrapolation,
    interpolate,
    type SharedValue,
    useAnimatedKeyboard,
    useDerivedValue,
    useSharedValue,
} from "react-native-reanimated"

import {
    InternalKeyboardProvider,
    useAdditionalKeyboardHeightTotal,
} from "./internal-keyboard-context"

interface KeyboardContextShape {
    keyboardHeightSharedValue: SharedValue<number>
    keyboardProgressSharedValue: SharedValue<number>
}

const KeyboardContext = React.createContext<KeyboardContextShape | null>(null)

const FALLBACK_KEYBOARD_HEIGHT = Platform.select({
    ios: 346,
    android: 330,
    default: 346,
})

function useDeriveKeyboardProgressSharedValue(keyboardHeightSharedValue: SharedValue<number>) {
    // Keyboard metrics are not available on web, so we use a fallback.
    const defaultKeyboardHeight = Keyboard.metrics?.()?.height || FALLBACK_KEYBOARD_HEIGHT

    const keyboardEndHeightSharedValue = useSharedValue(defaultKeyboardHeight)
    React.useEffect(() => {
        const sub = Keyboard.addListener("keyboardWillChangeFrame", (evt) => {
            keyboardEndHeightSharedValue.set(evt.endCoordinates.height)
        })

        return () => {
            sub.remove()
        }
    }, [])

    return useDerivedValue(() => {
        const keyboardEndHeight = keyboardEndHeightSharedValue.get()
        if (keyboardEndHeight === 0) return 0
        return interpolate(
            keyboardHeightSharedValue.get(),
            [0, keyboardEndHeight],
            [0, 1],
            Extrapolation.CLAMP
        )
    }, [keyboardHeightSharedValue])
}

function useDeriveKeyboardHeightSharedValue(
    keyboardHeightSharedValue: SharedValue<number>,
    keyboardProgressSharedValue: SharedValue<number>
) {
    const additionalKeyboardHeightTotalSharedValue = useAdditionalKeyboardHeightTotal()

    return useDerivedValue(() => {
        const offset =
            additionalKeyboardHeightTotalSharedValue.get() * keyboardProgressSharedValue.get()

        return keyboardHeightSharedValue.get() + offset
    }, [
        keyboardHeightSharedValue,
        keyboardProgressSharedValue,
        additionalKeyboardHeightTotalSharedValue.get,
    ])
}

interface WithInternalKeyboardProviderProps extends React.PropsWithChildren {
    keyboardHeightSharedValue: SharedValue<number>
    keyboardProgressSharedValue: SharedValue<number>
}

function WithInternalKeyboardProvider({
    children,
    keyboardHeightSharedValue: outerKeyboardHeightSharedValue,
    keyboardProgressSharedValue: outerKeyboardProgressSharedValue,
}: WithInternalKeyboardProviderProps) {
    const keyboardProgressSharedValue = useDeriveKeyboardProgressSharedValue(
        outerKeyboardHeightSharedValue
    )
    const keyboardHeightSharedValue = useDeriveKeyboardHeightSharedValue(
        outerKeyboardHeightSharedValue,
        outerKeyboardProgressSharedValue
    )

    const value = React.useMemo(
        () => ({keyboardHeightSharedValue, keyboardProgressSharedValue}),
        [keyboardHeightSharedValue, keyboardProgressSharedValue]
    )

    return <KeyboardContext.Provider value={value}>{children}</KeyboardContext.Provider>
}

type KeyboardProviderProps = Omit<
    WithInternalKeyboardProviderProps,
    "keyboardHeightSharedValue" | "keyboardProgressSharedValue"
> &
    (
        | {
              animatedKeyboardOptions?: AnimatedKeyboardOptions
          }
        | {
              keyboardHeightSharedValue: SharedValue<number>
              keyboardProgressSharedValue: SharedValue<number>
          }
    )

/**
 * Provides shared values representing the keyboard height and progress that
 * various bottom sheet behaviors can hook into.
 *
 * This also support additional keyboard height, which is useful when implementing
 * features such as keyboard toolbars.
 */
export function KeyboardProvider({children, ...rest}: KeyboardProviderProps) {
    const animatedKeyboard = useAnimatedKeyboard(
        "animatedKeyboardOptions" in rest ? rest.animatedKeyboardOptions : undefined
    )
    const innerKeyboardHeightSharedValue = animatedKeyboard.height
    const innerKeyboardProgressSharedValue = useDeriveKeyboardProgressSharedValue(
        innerKeyboardHeightSharedValue
    )

    const hasKeyboardOverride =
        "keyboardHeightSharedValue" in rest && "keyboardProgressSharedValue" in rest
    const keyboardHeightSharedValue = hasKeyboardOverride
        ? rest.keyboardHeightSharedValue
        : innerKeyboardHeightSharedValue
    const keyboardProgressSharedValue = hasKeyboardOverride
        ? rest.keyboardProgressSharedValue
        : innerKeyboardProgressSharedValue

    return (
        <InternalKeyboardProvider>
            <WithInternalKeyboardProvider
                keyboardHeightSharedValue={keyboardHeightSharedValue}
                keyboardProgressSharedValue={keyboardProgressSharedValue}
            >
                {children}
            </WithInternalKeyboardProvider>
        </InternalKeyboardProvider>
    )
}

function useKeyboardContext() {
    const context = React.useContext(KeyboardContext)

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

    return context
}

/**
 * Returns a shared value representing the keyboard height plus any additional
 * keyboard height.
 */
export function useKeyboardHeightSharedValue() {
    return useKeyboardContext().keyboardHeightSharedValue
}

/**
 * Returns a shared value, from 0 to 1, representing the keyboard's opening
 * progress.
 */
export function useKeyboardProgressSharedValue() {
    return useKeyboardContext().keyboardProgressSharedValue
}
