@file:OptIn(kotlinx.cinterop.ExperimentalForeignApi::class)

package dev.ulfrx.recipe.ui.keyboard

import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.ime
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.unit.dp
import kotlinx.cinterop.DoubleVar
import kotlinx.cinterop.allocArray
import kotlinx.cinterop.get
import kotlinx.cinterop.memScoped
import kotlinx.cinterop.sizeOf
import kotlinx.cinterop.useContents
import platform.CoreGraphics.CGRect
import platform.Foundation.NSNotificationCenter
import platform.Foundation.NSNumber
import platform.Foundation.NSOperationQueue
import platform.Foundation.NSValue
import platform.UIKit.UIKeyboardAnimationDurationUserInfoKey
import platform.UIKit.UIKeyboardFrameEndUserInfoKey
import platform.UIKit.UIKeyboardWillChangeFrameNotification
import platform.UIKit.UIScreen
import kotlin.math.roundToInt

@Composable
internal actual fun rememberKeyboardTransitionState(): KeyboardTransitionState {
    val currentInset = WindowInsets.ime.asPaddingValues().calculateBottomPadding()
    var targetInset by remember { mutableStateOf(0.dp) }
    var animationDurationMillis by remember { mutableStateOf(IosDefaultKeyboardAnimationDurationMillis) }

    DisposableEffect(Unit) {
        val observer =
            NSNotificationCenter.defaultCenter.addObserverForName(
                name = UIKeyboardWillChangeFrameNotification,
                `object` = null,
                queue = NSOperationQueue.mainQueue,
                usingBlock = { notification ->
                    val userInfo = notification?.userInfo ?: return@addObserverForName
                    val frameValue = userInfo[UIKeyboardFrameEndUserInfoKey] as? NSValue
                        ?: return@addObserverForName
                    val durationValue = userInfo[UIKeyboardAnimationDurationUserInfoKey] as? NSNumber

                    val screenHeight =
                        UIScreen.mainScreen.bounds.useContents {
                            size.height
                        }
                    val keyboardTop =
                        memScoped {
                            // iOS app targets are arm64; CGRect is x, y, width, height
                            // as CGFloat/Double fields.
                            val keyboardFrame = allocArray<DoubleVar>(CGRectDoubleFieldCount)
                            frameValue.getValue(
                                value = keyboardFrame,
                                size = sizeOf<CGRect>().toULong(),
                            )
                            keyboardFrame[CGRectOriginYFieldIndex]
                        }
                    val targetHeight = (screenHeight - keyboardTop).coerceAtLeast(0.0)

                    targetInset = targetHeight.toFloat().dp
                    animationDurationMillis =
                        durationValue?.doubleValue
                            ?.times(MillisPerSecond)
                            ?.roundToInt()
                            ?.takeIf { it > 0 }
                            ?: IosDefaultKeyboardAnimationDurationMillis
                },
            )

        onDispose {
            NSNotificationCenter.defaultCenter.removeObserver(observer)
        }
    }

    return KeyboardTransitionState(
        currentInset = currentInset,
        targetInset = targetInset,
        animationDurationMillis = animationDurationMillis,
    )
}

private const val IosDefaultKeyboardAnimationDurationMillis = 250
private const val MillisPerSecond = 1_000.0
private const val CGRectDoubleFieldCount = 4
private const val CGRectOriginYFieldIndex = 1
