import {nanoid} from "nanoid/non-secure"
import * as React from "react"
import {type SharedValue, useDerivedValue, useSharedValue} from "react-native-reanimated"

interface InternalKeyboardContextShape {
    additionalKeyboardHeights: Record<string, SharedValue<number>>
    setAdditionalKeyboardHeights: React.Dispatch<
        React.SetStateAction<InternalKeyboardContextShape["additionalKeyboardHeights"]>
    >
}

const InternalKeyboardContext = React.createContext<InternalKeyboardContextShape | null>(null)

function useInternalKeyboardContext() {
    const context = React.useContext(InternalKeyboardContext)

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

    return context
}

interface InternalKeyboardProviderProps extends React.PropsWithChildren {}

/**
 * The internal provider which maintains the additional keyboard height state.
 */
export function InternalKeyboardProvider({children}: InternalKeyboardProviderProps) {
    const [additionalKeyboardHeights, setAdditionalKeyboardHeights] = React.useState<
        InternalKeyboardContextShape["additionalKeyboardHeights"]
    >({})

    const value = React.useMemo(
        () => ({additionalKeyboardHeights, setAdditionalKeyboardHeights}),
        [additionalKeyboardHeights]
    )

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

/**
 * Returns a shared value representing the total of all additional keyboard
 * heights.
 */
export function useAdditionalKeyboardHeightTotal() {
    const {additionalKeyboardHeights} = useInternalKeyboardContext()

    return useDerivedValue(
        () =>
            Object.values(additionalKeyboardHeights).reduce(
                (accumulator, value) => accumulator + value.get(),
                0
            ),
        [additionalKeyboardHeights]
    )
}

/**
 * Returns a shared value enabling you to attach an artificial offset to the
 * internal keyboard height.
 *
 * This is useful when implementing features such as keyboard toolbars.
 */
export function useAdditionalKeyboardHeight(id = nanoid()) {
    const {additionalKeyboardHeights, setAdditionalKeyboardHeights} = useInternalKeyboardContext()

    const newHeightSharedValue = useSharedValue(0)
    const heightSharedValue = additionalKeyboardHeights[id] ?? newHeightSharedValue

    React.useEffect(() => {
        setAdditionalKeyboardHeights((previous) => ({
            ...previous,
            [id]: heightSharedValue,
        }))

        return () => {
            setAdditionalKeyboardHeights((previous) => {
                const next = {...previous}
                delete next[id]

                return next
            })
        }
    }, [heightSharedValue, id, setAdditionalKeyboardHeights])

    return heightSharedValue
}
