import * as React from "react"
import {StyleSheet, View} from "react-native"
import {runOnJS, type SharedValue, useAnimatedReaction} from "react-native-reanimated"
import {useSafeAreaInsets} from "react-native-safe-area-context"

import type {SheetPosition} from "./utils"
import {
    BottomSheetDragGesture,
    BottomSheetToolboxDefaultBackdrop,
    BottomSheetToolboxSheet,
    type BottomSheetToolboxSheetProps,
    BottomSheetToolboxWrapper,
} from "./components"
import {useBottomSheetToolboxInternalContext} from "./context/bottom-sheet-toolbox-internal-context"
import {
    type BottomSheetToolboxActions,
    useBottomSheetActions,
    useBottomSheetDevToolsAdapter,
    useBottomSheetIsReady,
    useBottomSheetOnCloseDetection,
    useBottomSheetOnSnapDetection,
    useBottomSheetOpenWhenReady,
    useBottomSheetSnapWhenIdle,
    useKeyboardAvoidingSnapPoints,
    useNormalizedSnapPoints,
} from "./hooks"

export interface BottomSheetToolboxComposerProps {
    /**
     * The contents of the bottom-sheet.
     */
    children: React.ReactNode

    /**
     * The positions we want the bottom sheet to snap to.
     */
    snapPoints: readonly SheetPosition[]

    /**
     * By default, the snapPoints you provide will be offset by the height of the
     * keyboard. This prop allows you to disable that behavior.
     */
    disableSnapPointKeyboardAdjust?: boolean

    /**
     * Where should the bottom-sheet initially snap to once it is ready?
     */
    initialSnapPointIndex: number

    /**
     * The top inset to apply to the wrapper of our bottom-sheet.
     * This can be used to keep a portion of the backdrop always visible.
     */
    insetTop?: number

    /**
     * A component that lives alongside and behind the wrapper (and sheet).
     */
    backdropComponent?: React.ReactNode

    /**
     * A component that lives within the bottom-sheet wrapper, but outside and above the sheet itself.
     */
    overlayComponent?: React.ReactNode

    /**
     * Called when the sheet's current position changes snap-point.
     */
    onSnapChange?: (currentSnapIndex: number, lastSnapIndex: number | null) => void

    /**
     * Called when the sheet is closed.
     */
    onClose?: () => void

    /**
     * The style of the bottom-sheet.
     */
    sheetStyle?: BottomSheetToolboxSheetProps["style"]

    /**
     * Should the entire sheet be draggable?
     */
    disableSheetDrag?: boolean

    /**
     * Internally, the bottom sheet is considered ready once the snap-points
     * are all not null and children/wrapper layout measurements have occurred.
     * However, sometimes the consumer may want to extend this to include
     * additional checks. This prop allows you to do that.
     */
    additionalIsReady?: SharedValue<boolean> | boolean

    /**
     * Called when the ready state changes.
     */
    onReadyChange?: (ready: boolean) => void

    /**
     * Ref object for the component.
     */
    ref?: React.ForwardedRef<BottomSheetToolboxComposerRef> | undefined
}

function useSetupSnapPoints(
    snapPoints: BottomSheetToolboxComposerProps["snapPoints"],
    disableSnapPointKeyboardAdjust: boolean
) {
    const {normalizedSnapPointsSharedValue} = useBottomSheetToolboxInternalContext()
    const normalizedSnapPoints = useNormalizedSnapPoints(snapPoints)
    const keyboardAvoidingSnapPointsSharedValue =
        useKeyboardAvoidingSnapPoints(normalizedSnapPoints)
    useAnimatedReaction(
        () =>
            disableSnapPointKeyboardAdjust
                ? normalizedSnapPoints.get()
                : keyboardAvoidingSnapPointsSharedValue.get(),
        (kb) => {
            normalizedSnapPointsSharedValue.set(kb)
        },
        [
            keyboardAvoidingSnapPointsSharedValue,
            normalizedSnapPoints,
            disableSnapPointKeyboardAdjust,
        ]
    )
}

function useSetupIsReady({
    additionalIsReady = true,
    onReadyChange,
}: Pick<BottomSheetToolboxComposerProps, "additionalIsReady" | "onReadyChange">) {
    const {isReadySharedValue} = useBottomSheetToolboxInternalContext()

    useBottomSheetIsReady(additionalIsReady)

    useAnimatedReaction(
        () => isReadySharedValue.get(),
        (ready, lastReady) => {
            if (ready === lastReady) return
            if (onReadyChange) runOnJS(onReadyChange)(ready)
        },
        [isReadySharedValue]
    )
}

export type BottomSheetToolboxComposerRef = BottomSheetToolboxActions

function useInsetTopWithDefault(outerInsetTop?: number) {
    const insets = useSafeAreaInsets()
    const insetTop =
        outerInsetTop === undefined ? Math.max(insets.top + 12, 24) : outerInsetTop || 0

    return insetTop
}

/**
 * A general-purpose composer of all our bottom sheet utilities.
 */
export function BottomSheetToolboxComposer({
    children,
    snapPoints,
    initialSnapPointIndex,
    insetTop: outerInsetTop,
    backdropComponent = <BottomSheetToolboxDefaultBackdrop />,
    overlayComponent,
    onSnapChange,
    onClose,
    sheetStyle,
    disableSheetDrag = false,
    disableSnapPointKeyboardAdjust = false,
    additionalIsReady = true,
    onReadyChange,
    ref,
}: BottomSheetToolboxComposerProps) {
    useBottomSheetDevToolsAdapter()

    useSetupSnapPoints(snapPoints, disableSnapPointKeyboardAdjust)
    useSetupIsReady({additionalIsReady, ...(onReadyChange && {onReadyChange})})
    const insetTop = useInsetTopWithDefault(outerInsetTop)

    useBottomSheetOpenWhenReady(initialSnapPointIndex)
    useBottomSheetSnapWhenIdle()
    useBottomSheetOnSnapDetection(onSnapChange)
    useBottomSheetOnCloseDetection(onClose)

    const actions = useBottomSheetActions("BottomSheetImperativeHandle")
    React.useImperativeHandle(ref, () => actions, [actions])

    return (
        <>
            {backdropComponent && (
                <View style={StyleSheet.absoluteFill} pointerEvents="box-none">
                    {backdropComponent}
                </View>
            )}

            <BottomSheetToolboxWrapper insetTop={insetTop}>
                <BottomSheetDragGesture
                    id="BottomSheetToolboxComposer:sheet-drag"
                    disabled={disableSheetDrag}
                >
                    <BottomSheetToolboxSheet style={sheetStyle}>{children}</BottomSheetToolboxSheet>
                </BottomSheetDragGesture>

                {overlayComponent && (
                    <View style={StyleSheet.absoluteFill} pointerEvents="box-none">
                        {overlayComponent}
                    </View>
                )}
            </BottomSheetToolboxWrapper>
        </>
    )
}
