package compose.utils

import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.unit.*
import androidx.compose.ui.window.PopupPositionProvider
import kotlin.math.max
import kotlin.math.min


/**
 * A dropdown popup position provider with zero paddings around the anchor and no min popup height.
 */
val ZeroPaddingsDropdownMenuPositionProvider = dropdownMenuPositionProvider(IntRect.Zero, 0)


/**
 * Returns a remembered [PopupPositionProvider] for a dropdown menu.
 *
 * The returned [PopupPositionProvider] will attempt to position the popup such that it doesn't overlap the bounds of
 * its anchor. It will try to position it below, then above, then to the side.
 *
 * @param anchorPadding The padding around (outside) the anchor the popup should try to avoid overlapping, and should
 * align with.
 * @param reservedHeight The height to reserve even if the popup is shorter than this. Useful for popups that change
 * their height, to avoid having them jump from position to position as they do.
 */
@Composable
fun rememberDropdownMenuPositionProvider(
    anchorPadding: DpRect = DpRect.Zero,
    reservedHeight: Dp = 0.dp,
): PopupPositionProvider {
    val layoutDirection = LocalLayoutDirection.current
    val density = LocalDensity.current
    val pixelPadding = remember(layoutDirection, density) {
        with(density) {
            IntRect(
                left = anchorPadding.left.roundToPx(),
                top = anchorPadding.top.roundToPx(),
                right = anchorPadding.right.roundToPx(),
                bottom = anchorPadding.bottom.roundToPx()
            )
        }
    }
    val reservedHeightPixels = remember(density) {
        with(density) {
            reservedHeight.roundToPx()
        }
    }

    if ((pixelPadding == IntRect.Zero) && (reservedHeightPixels == 0))
        return ZeroPaddingsDropdownMenuPositionProvider

    return remember(pixelPadding) {
        dropdownMenuPositionProvider(
            anchorPadding = pixelPadding,
            minReservedHeight = reservedHeightPixels
        )
    }
}


/**
 * Positions a dropdown popup.
 */
fun dropdownMenuPositionProvider(
    anchorPadding: IntRect,
    minReservedHeight: Int
): PopupPositionProvider {
    return object : PopupPositionProvider {

        override fun calculatePosition(
            anchorBounds: IntRect,
            windowSize: IntSize,
            layoutDirection: LayoutDirection,
            popupContentSize: IntSize
        ): IntOffset {
            val reservedHeight = max(minReservedHeight, popupContentSize.height)

            /**
             * Returns the position of the popup on the Y axis, and whether the X-axis positioning should put the popup
             * to the side (left or right) of the anchor.
             */
            fun positionY(): Pair<Int, Boolean> {
                // If it fits, position below the anchor
                if (anchorBounds.bottom + anchorPadding.bottom <= windowSize.height - reservedHeight)
                    return anchorBounds.bottom + anchorPadding.bottom to false

                // If it fits, position above the anchor
                if (anchorBounds.top - anchorPadding.top - reservedHeight >= 0)
                    return (anchorBounds.top - anchorPadding.top - popupContentSize.height) to false

                // Try positioning to the side
                if (max(anchorBounds.left - anchorPadding.left, windowSize.width - anchorBounds.right - anchorPadding.right) >= popupContentSize.width) {

                    // If it at all fits, try aligning popup top to anchor top but move up as necessary
                    if (windowSize.height >= reservedHeight)
                        return min(anchorBounds.top - anchorPadding.top, windowSize.height - reservedHeight) to true

                }

                // No room anywhere, just position below, as usual and pray for the best
                return anchorBounds.bottom + anchorPadding.bottom to false
            }

            /**
             * Returns the position of the popup on the X axis.
             */
            fun positionX(isSidePositioning: Boolean): Int{
                if (!isSidePositioning) {
                    // Try aligning the left side of the popup with the left side of the anchor, but move to the left as necessary
                    return min(anchorBounds.left - anchorPadding.left, windowSize.width - popupContentSize.width).coerceAtLeast(0)
                }
                else {
                    // If it fits, position to the right
                    if (windowSize.width - (anchorBounds.right + anchorPadding.right) >= popupContentSize.width)
                        return anchorBounds.right + anchorPadding.right

                    // If it fits, position to the left
                    if (anchorBounds.left - anchorPadding.left - popupContentSize.width >= 0)
                        return anchorBounds.left - anchorPadding.left - popupContentSize.width

                    // No room anywhere. The Y positioning will put it below, and we'll put it at 0
                    return 0
                }
            }

            val (y, isSidePositioning) = positionY()
            val x = positionX(isSidePositioning)

            return IntOffset(x, y)
        }


    }
}