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

import type {BottomSheetLock} from "../utils"

/**
 * The context that describes the current position and state of the bottom-sheet.
 *
 * Note: you probably shouldn't write to any of these values directly, instead
 * refer to the appropriate hook counterparts.
 */
interface BottomSheetToolboxInternalContextShape {
    /**
     * The current position of the bottom-sheet, defined as distance from the bottom.
     */
    currentPositionSharedValue: SharedValue<number>

    /**
     * The position the bottom-sheet was last known to be moving towards.
     * This is only different from currentPositionSharedValue when the sheet
     * is animating to a new destination.
     */
    currentPositionDestinationSharedValue: SharedValue<number>

    /**
     * The last known velocity of the bottom-sheet.
     */
    currentPositionLastVelocitySharedValue: SharedValue<number>

    /**
     * The current write lock held on the sheet's position.
     */
    currentPositionLockSharedValue: SharedValue<BottomSheetLock | null>

    /**
     * An array of positions the bottom-sheet is allowed to snap to.
     */
    normalizedSnapPointsSharedValue: SharedValue<number[] | null>

    /**
     * The maximum height available to the bottom-sheet.
     */
    wrapperHeightSharedValue: SharedValue<number | null>

    /**
     * The current internally calculated height of the bottom-sheet's content.
     */
    childrenHeightSharedValue: SharedValue<number | null>

    /**
     * The current internally calculated height of the bottom-sheet's footer.
     */
    footerHeightSharedValue: SharedValue<number | null>

    /**
     * A flag used to indicate whether the bottom-sheet is ready to be displayed.
     * This lets us wait for layout calculations, etc..
     */
    isReadySharedValue: SharedValue<boolean>

    /**
     * The debug ID used to identify this sheet in the DevTools and elsewhere.
     */
    debugId: string
}

const BottomSheetToolboxInternalContext = React.createContext<
    BottomSheetToolboxInternalContextShape | undefined
>(undefined)

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

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

    return context
}

interface NewBottomSheetToolboxInternalProviderProps extends React.PropsWithChildren {
    children: React.ReactNode
    debugId?: string | null
}

/**
 * Provides a new context for managing the position state of a bottom-sheet.
 */
function NewBottomSheetToolboxInternalProvider({
    children,
    debugId = null,
}: NewBottomSheetToolboxInternalProviderProps) {
    const [defaultDebugId] = React.useState(() => nanoid())
    const internalDebugId = debugId === null ? defaultDebugId : debugId

    const currentPositionSharedValue = useSharedValue(0)
    const currentPositionDestinationSharedValue = useSharedValue(0)
    const currentPositionLastVelocitySharedValue = useSharedValue(0)
    const currentPositionLockSharedValue = useSharedValue<BottomSheetLock | null>(null)

    const normalizedSnapPointsSharedValue = useSharedValue<number[] | null>(null)
    const wrapperHeightSharedValue = useSharedValue<number | null>(null)
    const childrenHeightSharedValue = useSharedValue<number | null>(null)
    const footerHeightSharedValue = useSharedValue<number | null>(null)
    const isReadySharedValue = useSharedValue(false)

    const contextValue = React.useMemo(
        () => ({
            currentPositionSharedValue,
            currentPositionDestinationSharedValue,
            currentPositionLastVelocitySharedValue,
            currentPositionLockSharedValue,
            normalizedSnapPointsSharedValue,
            wrapperHeightSharedValue,
            childrenHeightSharedValue,
            footerHeightSharedValue,
            isReadySharedValue,
            debugId: internalDebugId,
        }),
        [internalDebugId]
    )

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

export interface BottomSheetToolboxInternalProviderProps {
    children: React.ReactNode

    /**
     * When provided, the reset will discard the inherited context,
     * and create a new one.
     */
    reset?: boolean
}

/**
 * Provides a context for managing the position state of a bottom-sheet.
 * By default, this provider will do nothing if a context is already provided.
 *
 * If you wish to reset the context, you can pass the 'reset' prop.
 * This is useful when nesting bottom-sheets.
 */
export function BottomSheetToolboxInternalProvider({
    children,
    reset = false,
}: BottomSheetToolboxInternalProviderProps) {
    const inheritedContext = React.useContext(BottomSheetToolboxInternalContext)

    return inheritedContext !== undefined && !reset ? (
        children
    ) : (
        <NewBottomSheetToolboxInternalProvider>{children}</NewBottomSheetToolboxInternalProvider>
    )
}
