import {type SharedValue, useDerivedValue} from "react-native-reanimated"

import {useBottomSheetToolboxInternalContext, useKeyboardHeightSharedValue} from "../context"
import {useFooterFreezeWhenExpanding} from "./use-footer-freeze-when-expanding"
import {useLastSharedValueBeforeLock} from "./use-last-shared-value-before-lock"

export interface UseFooterBottomPositionSharedValueArgs {
    /**
     * A shared value that tells the footer how it should be moved with the
     * sheet. By default, this is the internal children height of the sheet.
     */
    bufferSharedValue?: SharedValue<number | null> | undefined

    /**
     * Whether the footer should be adjusted for the keyboard.
     */
    disableKeyboardAdjust?: boolean | undefined

    /**
     * Whether the footer should freeze its position when the sheet is expanding.
     * This is useful when adding or removing items and is the default behavior.
     */
    freezeWhenExpanding?: boolean | undefined
}

/**
 * A shared value used as the bottom position for a footer.
 */
export function useFooterBottomPositionSharedValue({
    bufferSharedValue: outerBufferSharedValue,
    disableKeyboardAdjust = false,
    freezeWhenExpanding = true,
}: UseFooterBottomPositionSharedValueArgs) {
    const {
        isReadySharedValue,
        currentPositionSharedValue,
        childrenHeightSharedValue,
        wrapperHeightSharedValue,
        footerHeightSharedValue,
    } = useBottomSheetToolboxInternalContext()
    const keyboardHeightSharedValue = useKeyboardHeightSharedValue()

    const bufferSharedValue = useDerivedValue(() => {
        if (outerBufferSharedValue) return outerBufferSharedValue.get()

        const childrenHeight = childrenHeightSharedValue.get()
        const wrapperHeight = wrapperHeightSharedValue.get()
        const footerHeight = footerHeightSharedValue.get()

        if (childrenHeight === null || wrapperHeight === null || footerHeight === null) {
            return null
        }

        return Math.min(childrenHeight, wrapperHeight - footerHeight)
    }, [
        outerBufferSharedValue,
        childrenHeightSharedValue,
        wrapperHeightSharedValue,
        footerHeightSharedValue,
    ])

    const rawBottomSharedValue = useDerivedValue(() => {
        const footerHeight = footerHeightSharedValue.get()
        const buffer = bufferSharedValue.get()
        if (!isReadySharedValue.get() || buffer === null || footerHeight === null) {
            return null
        }

        const keyboardAdjust = disableKeyboardAdjust ? 0 : keyboardHeightSharedValue.get()

        const unclampedClosingOffset =
            currentPositionSharedValue.get() - footerHeight - buffer - keyboardAdjust
        const closingOffset = Math.min(0, unclampedClosingOffset)

        const bottom = keyboardAdjust + closingOffset

        return bottom
    }, [
        isReadySharedValue,
        currentPositionSharedValue,
        disableKeyboardAdjust,
        footerHeightSharedValue,
        keyboardHeightSharedValue,
    ])

    const bottomAtTimeOfLockSharedValue = useLastSharedValueBeforeLock(rawBottomSharedValue)

    const freezeWhenExpandingSharedValue = useFooterFreezeWhenExpanding({
        bufferSharedValue,
        bottomAtTimeOfLockSharedValue,
    })

    /**
     * If the content-size of the sheet increases, then we actually want the
     * footer to stay where it is (not shift down as if it's animating back in).
     *
     *
     * To determine this, we snapshot the sheet position at the time of the lock
     * and see if we're expanding up or down. We also keep a snapshot of the
     * bottom right before the lock was engaged.
     */
    const frozenBottomSharedValue = useDerivedValue(() => {
        const bottom = rawBottomSharedValue.get()
        const bottomAtTimeOfLock = bottomAtTimeOfLockSharedValue.get()

        const shouldFreezeBottom = freezeWhenExpanding && freezeWhenExpandingSharedValue.get()

        return shouldFreezeBottom ? (bottomAtTimeOfLock ?? bottom) : bottom
    }, [bottomAtTimeOfLockSharedValue, freezeWhenExpanding, freezeWhenExpandingSharedValue])

    /**
     * Note: returning different SharedValue references doesn't play well with
     * useAnimatedStyle. Hence we always return the derived value.
     */
    return frozenBottomSharedValue
}
