package com.performancecoachlab.posedetection.camera

import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
import com.performancecoachlab.posedetection.custom.ObjectModel
import com.performancecoachlab.posedetection.recording.AnalysisObject
import com.performancecoachlab.posedetection.recording.FrameSize
import com.performancecoachlab.posedetection.recording.Label
import com.performancecoachlab.posedetection.skeleton.Skeleton
import kotlinx.cinterop.BetaInteropApi
import kotlinx.cinterop.CValue
import kotlinx.cinterop.ExperimentalForeignApi
import kotlinx.cinterop.ObjCObjectVar
import kotlinx.cinterop.alloc
import kotlinx.cinterop.autoreleasepool
import kotlinx.cinterop.memScoped
import kotlinx.cinterop.ptr
import kotlinx.cinterop.useContents
import kotlinx.cinterop.value
import platform.AVFoundation.AVCaptureVideoOrientationLandscapeRight
import platform.AVFoundation.AVCaptureVideoPreviewLayer
import platform.CoreFoundation.CFRelease
import platform.CoreFoundation.CFRetain
import platform.CoreGraphics.CGImageGetHeight
import platform.CoreGraphics.CGImageGetWidth
import platform.CoreGraphics.CGImageRef
import platform.CoreGraphics.CGPoint
import platform.CoreGraphics.CGRect
import platform.CoreGraphics.CGRectMake
import platform.CoreVideo.CVImageBufferRef
import platform.Foundation.NSError
import platform.Foundation.NSUUID
import platform.Vision.VNClassificationObservation
import platform.Vision.VNCoreMLModel
import platform.Vision.VNCoreMLRequest
import platform.Vision.VNDetectHumanBodyPoseRequest
import platform.Vision.VNHumanBodyPoseObservation
import platform.Vision.VNHumanBodyPoseObservationJointName
import platform.Vision.VNHumanBodyPoseObservationJointNameLeftAnkle
import platform.Vision.VNHumanBodyPoseObservationJointNameLeftElbow
import platform.Vision.VNHumanBodyPoseObservationJointNameLeftHip
import platform.Vision.VNHumanBodyPoseObservationJointNameLeftKnee
import platform.Vision.VNHumanBodyPoseObservationJointNameLeftShoulder
import platform.Vision.VNHumanBodyPoseObservationJointNameLeftWrist
import platform.Vision.VNHumanBodyPoseObservationJointNameRightAnkle
import platform.Vision.VNHumanBodyPoseObservationJointNameRightElbow
import platform.Vision.VNHumanBodyPoseObservationJointNameRightHip
import platform.Vision.VNHumanBodyPoseObservationJointNameRightKnee
import platform.Vision.VNHumanBodyPoseObservationJointNameRightShoulder
import platform.Vision.VNHumanBodyPoseObservationJointNameRightWrist
import platform.Vision.VNImagePointForNormalizedPoint
import platform.Vision.VNImageRequestHandler
import platform.Vision.VNRecognizedObjectObservation
import platform.Vision.VNRecognizedPoint
import platform.Vision.VNRequest
import kotlin.experimental.ExperimentalNativeApi
import kotlin.native.identityHashCode

fun bodyPoseHandler(request: VNRequest): List<MutableMap<VNHumanBodyPoseObservationJointName, VNRecognizedPoint>>? {
    try {
        val observations = request.results as List<VNHumanBodyPoseObservation>
        // Process each observation to find the recognized body pose points.
        return observations.map { processObservation(it) }
    } catch (e: Exception) {
        //println("Error processing observations: ${e.message}")
        return null
    }
}

@OptIn(ExperimentalForeignApi::class)
fun processObservation(observation: VNHumanBodyPoseObservation): MutableMap<VNHumanBodyPoseObservationJointName, VNRecognizedPoint> {
    val points = emptyMap<VNHumanBodyPoseObservationJointName, VNRecognizedPoint>().toMutableMap()
    observation.availableJointNames.forEach {
        observation.recognizedPointForJointName(it as VNHumanBodyPoseObservationJointName, null)
            ?.also { point ->
                if (point.confidence > 0.2f) {
                    points[it] = point
                }
            }
    }
    return points
}

@OptIn(ExperimentalForeignApi::class)
fun CValue<CGPoint>.toSkeletonPoint(width: ULong, height: ULong): Skeleton.SkeletonCoordinate {
    return VNImagePointForNormalizedPoint(
        this, width, height
    ).let {
        it.useContents {
            Skeleton.SkeletonCoordinate(
                x.toFloat(), height.toFloat() - y.toFloat()
            )
        }
    }
}

class SkelBuffer(private val maxSize: Int) {
    private val buffer = ArrayDeque<Skeleton>()

    fun add(skeleton: Skeleton) {
        buffer.addLast(skeleton)
        if (buffer.size > maxSize) buffer.removeFirst()
    }

    fun smooth(skeleton: Skeleton): Skeleton {
        add(skeleton)
        val recentSkeletons = buffer.filter { it.timestamp >= skeleton.timestamp - 200 }
        fun findMostRecentNonNullPoint(selector: (Skeleton) -> Skeleton.SkeletonCoordinate?): Skeleton.SkeletonCoordinate? {
            return recentSkeletons.asReversed().firstNotNullOfOrNull(selector)
        }

        return Skeleton(
            timestamp = skeleton.timestamp,
            leftShoulder = skeleton.leftShoulder ?: findMostRecentNonNullPoint { it.leftShoulder },
            rightShoulder = skeleton.rightShoulder
                ?: findMostRecentNonNullPoint { it.rightShoulder },
            leftElbow = skeleton.leftElbow ?: findMostRecentNonNullPoint { it.leftElbow },
            rightElbow = skeleton.rightElbow ?: findMostRecentNonNullPoint { it.rightElbow },
            leftWrist = skeleton.leftWrist ?: findMostRecentNonNullPoint { it.leftWrist },
            rightWrist = skeleton.rightWrist ?: findMostRecentNonNullPoint { it.rightWrist },
            leftHip = skeleton.leftHip ?: findMostRecentNonNullPoint { it.leftHip },
            rightHip = skeleton.rightHip ?: findMostRecentNonNullPoint { it.rightHip },
            leftKnee = skeleton.leftKnee ?: findMostRecentNonNullPoint { it.leftKnee },
            rightKnee = skeleton.rightKnee ?: findMostRecentNonNullPoint { it.rightKnee },
            leftAnkle = skeleton.leftAnkle ?: findMostRecentNonNullPoint { it.leftAnkle },
            rightAnkle = skeleton.rightAnkle ?: findMostRecentNonNullPoint { it.rightAnkle },
            width = skeleton.width,
            height = skeleton.height
        )
    }
}

@OptIn(ExperimentalForeignApi::class)
class FrameProcessor(var modelObj: VNCoreMLModel?) {
    private val skelBuffer = SkelBuffer(maxSize = 10)
    private var regionOfInterest = CGRectMake(0.0, 0.0, 1.0, 1.0)
    private var requests = mutableListOf<VNRequest>()
    val objectRecognition = setUpRecognition()
    private var focusArea: Rect? = null
    private var path = ""
    private var detectMode = DetectMode.BOTH

    fun setFocusArea(focusArea: Rect?) {
        this.focusArea = focusArea
    }

    fun setDetectMode(detectMode: DetectMode) {
        this.detectMode = detectMode
    }

    fun setObjectModel(objectModel: ObjectModel?){
        modelObj = objectModel?.getModel()
        setUpRecognition()
    }

    private fun setUpRecognition() {
        modelObj?.let {
            try {
                val objectRecognition = VNCoreMLRequest(it) { request, error ->
                    val results = request?.results as? List<*> ?: return@VNCoreMLRequest
                    val recognized = results.filterIsInstance<VNRecognizedObjectObservation>()
                    processResults(recognized)
                }
                requests = mutableListOf(objectRecognition)
            } catch (e: Throwable) {
                //println("Vision setup error: ${e.message}")
            }
        }
    }

    private fun processResults(results: List<VNRecognizedObjectObservation>) {
        val detectedObjects = results.map { observation ->
            val label = observation.labels.firstOrNull()?.toString() ?: "Unknown"
            val confidence = observation.confidence
            val boundingBox = observation.boundingBox
            DetectedObject(
                label = label, confidence = confidence, boundingBox = boundingBox
            )
        }
    }

    @OptIn(BetaInteropApi::class, ExperimentalNativeApi::class)
    fun analyseFrameForAll(
        cgImage: CGImageRef?,
        timestamp: Long,
        onObjectsProcessed: (List<AnalysisObject>) -> Unit,
        onSkeletonProcessed: (Skeleton?) -> Unit
    ) {
        autoreleasepool {
            if (cgImage == null) {
                onObjectsProcessed(emptyList())
                onSkeletonProcessed(null)
                return
            }
            val width = CGImageGetWidth(cgImage)
            val height = CGImageGetHeight(cgImage)
            if (width.toUInt() == 0u || height.toUInt() == 0u) {
                onObjectsProcessed(emptyList())
                onSkeletonProcessed(null)
                return
            }
            memScoped {
                val errorPtr = alloc<ObjCObjectVar<NSError?>>()
                try {
                    val requestForObjects = modelObj?.let {
                        VNCoreMLRequest(it) { request, error ->
                            if (error != null) {
                                onObjectsProcessed(emptyList())
                                return@VNCoreMLRequest
                            }
                            val results = request?.results as? List<*> ?: emptyList<Any>()
                            val recognized =
                                results.filterIsInstance<VNRecognizedObjectObservation>()
                            val analysisObjects = recognized.map { observation ->
                                val confidence = observation.confidence
                                val boundingBox = observation.boundingBox.useContents {
                                    val w = width.toFloat()
                                    val h = height.toFloat()
                                    val left = origin.x * w
                                    val top = (1.0 - origin.y) * h
                                    val right = (origin.x + size.width) * w
                                    val bottom = (1.0 - (origin.y + size.height)) * h
                                    Rect(
                                        left = left.toFloat(),
                                        top = top.toFloat(),
                                        right = right.toFloat(),
                                        bottom = bottom.toFloat()
                                    )
                                }
                                val labels = observation.labels.mapNotNull {
                                    (it as VNClassificationObservation).let { ca ->
                                        if (ca.confidence > 0.0) Label(
                                            ca.identifier,
                                            ca.confidence
                                        ) else null
                                    }
                                }
                                AnalysisObject(
                                    trackingId = observation.identityHashCode(),
                                    labels = labels,
                                    boundingBox = boundingBox,
                                    frameSize = FrameSize(
                                        width = width.toInt(),
                                        height = height.toInt()
                                    )
                                )
                            }
                            onObjectsProcessed(analysisObjects)
                        }
                    }
                    val options = mapOf<Any?, Any?>(
                        "orientation" to AVCaptureVideoOrientationLandscapeRight
                    )
                    val requestForSkeleton = VNDetectHumanBodyPoseRequest { request, error ->
                        if (error != null) {
                            onSkeletonProcessed(null)
                        } else {
                            request?.also { vnRequest ->
                                val recognizedPoints = bodyPoseHandler(vnRequest)?.firstOrNull{
                                    isInFocusArea(it, focusArea, width, height)
                                }
                                regionOfInterest =
                                    calculateRegionOfInterest(recognizedPoints)
                                val updatedSkeleton = Skeleton(
                                    timestamp = timestamp,
                                    leftShoulder = recognizedPoints?.get(
                                        VNHumanBodyPoseObservationJointNameLeftShoulder
                                    )?.location?.toSkeletonPoint(width, height),
                                    rightShoulder = recognizedPoints?.get(
                                        VNHumanBodyPoseObservationJointNameRightShoulder
                                    )?.location?.toSkeletonPoint(width, height),
                                    leftElbow = recognizedPoints?.get(
                                        VNHumanBodyPoseObservationJointNameLeftElbow
                                    )?.location?.toSkeletonPoint(width, height),
                                    rightElbow = recognizedPoints?.get(
                                        VNHumanBodyPoseObservationJointNameRightElbow
                                    )?.location?.toSkeletonPoint(width, height),
                                    leftWrist = recognizedPoints?.get(
                                        VNHumanBodyPoseObservationJointNameLeftWrist
                                    )?.location?.toSkeletonPoint(width, height),
                                    rightWrist = recognizedPoints?.get(
                                        VNHumanBodyPoseObservationJointNameRightWrist
                                    )?.location?.toSkeletonPoint(width, height),
                                    leftHip = recognizedPoints?.get(
                                        VNHumanBodyPoseObservationJointNameLeftHip
                                    )?.location?.toSkeletonPoint(width, height),
                                    rightHip = recognizedPoints?.get(
                                        VNHumanBodyPoseObservationJointNameRightHip
                                    )?.location?.toSkeletonPoint(width, height),
                                    leftKnee = recognizedPoints?.get(
                                        VNHumanBodyPoseObservationJointNameLeftKnee
                                    )?.location?.toSkeletonPoint(width, height),
                                    rightKnee = recognizedPoints?.get(
                                        VNHumanBodyPoseObservationJointNameRightKnee
                                    )?.location?.toSkeletonPoint(width, height),
                                    leftAnkle = recognizedPoints?.get(
                                        VNHumanBodyPoseObservationJointNameLeftAnkle
                                    )?.location?.toSkeletonPoint(width, height),
                                    rightAnkle = recognizedPoints?.get(
                                        VNHumanBodyPoseObservationJointNameRightAnkle
                                    )?.location?.toSkeletonPoint(width, height),
                                    height = height.toFloat(),
                                    width = width.toFloat()
                                )
                                onSkeletonProcessed(skelBuffer.smooth(updatedSkeleton))
                            }
                        }
                    }
                    requestForSkeleton.regionOfInterest = regionOfInterest
                    val handler = VNImageRequestHandler(cgImage, options)
                    handler.performRequests(
                        listOfNotNull(requestForObjects, requestForSkeleton), errorPtr.ptr
                    )
                    if (errorPtr.value != null) {
                        //println("Error performing object detection request: ${errorPtr.value}")
                        onObjectsProcessed(emptyList())
                        onSkeletonProcessed(null)
                    }
                } catch (e: Throwable) {
                    //println("Unable to perform the object detection request: ${e.message}")
                    onObjectsProcessed(emptyList())
                    onSkeletonProcessed(null)
                }
            }
        }
    }

    fun isInFocusArea(
        recognizedPoints: MutableMap<VNHumanBodyPoseObservationJointName, VNRecognizedPoint>?,
        focusArea: Rect?,
        width: ULong,
        height: ULong,
        mapPoint: (Skeleton.SkeletonCoordinate) -> Skeleton.SkeletonCoordinate = {it}
    ): Boolean {
        if (focusArea == null || recognizedPoints.isNullOrEmpty()) return true
        val focusRect = Rect(
            left = focusArea.left * width.toFloat(),
            top = focusArea.top * height.toFloat(),
            right = focusArea.right * width.toFloat(),
            bottom = focusArea.bottom * height.toFloat()
        )
        return recognizedPoints.values.all { point ->
            val pointInFocus = mapPoint(point.location.toSkeletonPoint(width, height))
            focusRect.contains(
                Offset(pointInFocus.x, pointInFocus.y)
            )
        }
    }

    fun Skeleton.isInFocusArea(focusArea: Rect?): Boolean {
        if (focusArea == null || joints().isEmpty()) return true
        val focusRect = Rect(
            left = focusArea.left * width,
            top = focusArea.top * height,
            right = focusArea.right * width,
            bottom = focusArea.bottom * height
        )
        return joints().all { point ->
            focusRect.contains(Offset(point.x, point.y))
        }
    }

    @OptIn(BetaInteropApi::class, ExperimentalNativeApi::class)
    fun analyseBufferForAll(
        buffer: CVImageBufferRef?,
        timestamp: Long,
        preview: AVCaptureVideoPreviewLayer?,
        onObjectsProcessed: (List<AnalysisObject>) -> Unit,
        onSkeletonProcessed: (Skeleton?) -> Unit
    ) {
        autoreleasepool {
            if (buffer == null) {
                onObjectsProcessed(emptyList())
                onSkeletonProcessed(null)
                return
            }
            val retainedBuffer = CFRetain(buffer)
            val width = 480uL // You may want to get actual width from buffer if needed
            val height = 360uL // You may want to get actual height from buffer if needed
            memScoped {
                val errorPtr = alloc<ObjCObjectVar<NSError?>>()
                try {
                    val requestForObjects = if(detectMode.doObject()){
                        modelObj?.let {
                            VNCoreMLRequest(it) { request, error ->
                                if (error != null) {
                                    onObjectsProcessed(emptyList())
                                    return@VNCoreMLRequest
                                }
                                val results = request?.results as? List<*> ?: emptyList<Any>()
                                val recognized =
                                    results.filterIsInstance<VNRecognizedObjectObservation>()
                                val analysisObjects = recognized.map { observation ->
                                    val confidence = observation.confidence
                                    val boundingBox = observation.boundingBox.useContents {
                                        val w = width.toFloat()
                                        val h = height.toFloat()
                                        val left = origin.x * w
                                        val top = (1.0 - origin.y) * h
                                        val right = (origin.x + size.width) * w
                                        val bottom = (1.0 - (origin.y + size.height)) * h
                                        Rect(
                                            left = left.toFloat(),
                                            top = top.toFloat(),
                                            right = right.toFloat(),
                                            bottom = bottom.toFloat()
                                        )
                                    }
                                    val labels = observation.labels.mapNotNull {
                                        (it as VNClassificationObservation).let { ca ->
                                            if (ca.confidence > 0.0) Label(
                                                ca.identifier,
                                                ca.confidence
                                            ) else null
                                        }
                                    }
                                    AnalysisObject(
                                        trackingId = observation.identityHashCode(),
                                        labels = labels,
                                        boundingBox = boundingBox,
                                        frameSize = FrameSize(
                                            width = width.toInt(),
                                            height = height.toInt()
                                        )
                                    )
                                }
                                onObjectsProcessed(analysisObjects)
                            }
                        }
                    }else null


                    val options = mapOf<Any?, Any?>(
                        "orientation" to AVCaptureVideoOrientationLandscapeRight
                    )
                    val requestForSkeleton =  if(detectMode.doPose()) {
                        VNDetectHumanBodyPoseRequest { request, error ->
                            if (error != null) {
                                onSkeletonProcessed(null)
                            } else {
                                request?.also { vnRequest ->
                                    val recognizedPoints = bodyPoseHandler(vnRequest)?.firstOrNull {
                                        Skeleton(
                                            timestamp = timestamp,
                                            leftShoulder = it[VNHumanBodyPoseObservationJointNameLeftShoulder]?.location?.toSkeletonPoint(
                                                width,
                                                height
                                            ),
                                            rightShoulder = it[VNHumanBodyPoseObservationJointNameRightShoulder]?.location?.toSkeletonPoint(
                                                width,
                                                height
                                            ),
                                            leftElbow = it[VNHumanBodyPoseObservationJointNameLeftElbow]?.location?.toSkeletonPoint(
                                                width,
                                                height
                                            ),
                                            rightElbow = it[VNHumanBodyPoseObservationJointNameRightElbow]?.location?.toSkeletonPoint(
                                                width,
                                                height
                                            ),
                                            leftWrist = it[VNHumanBodyPoseObservationJointNameLeftWrist]?.location?.toSkeletonPoint(
                                                width,
                                                height
                                            ),
                                            rightWrist = it[VNHumanBodyPoseObservationJointNameRightWrist]?.location?.toSkeletonPoint(
                                                width,
                                                height
                                            ),
                                            leftHip = it[VNHumanBodyPoseObservationJointNameLeftHip]?.location?.toSkeletonPoint(
                                                width,
                                                height
                                            ),
                                            rightHip = it[VNHumanBodyPoseObservationJointNameRightHip]?.location?.toSkeletonPoint(
                                                width,
                                                height
                                            ),
                                            leftKnee = it[VNHumanBodyPoseObservationJointNameLeftKnee]?.location?.toSkeletonPoint(
                                                width,
                                                height
                                            ),
                                            rightKnee = it[VNHumanBodyPoseObservationJointNameRightKnee]?.location?.toSkeletonPoint(
                                                width,
                                                height
                                            ),
                                            leftAnkle = it[VNHumanBodyPoseObservationJointNameLeftAnkle]?.location?.toSkeletonPoint(
                                                width,
                                                height
                                            ),
                                            rightAnkle = it[VNHumanBodyPoseObservationJointNameRightAnkle]?.location?.toSkeletonPoint(
                                                width,
                                                height
                                            ),
                                            height = height.toFloat(),
                                            width = width.toFloat()
                                        ).let { skel ->
                                            preview?.let { layer ->
                                                mapSkeletonToPreview(
                                                    skeleton = skel,
                                                    previewLayer = layer,
                                                    width = 480f,
                                                    height = 360f
                                                )
                                            }
                                        }?.isInFocusArea(focusArea) ?: false
                                    }
                                    regionOfInterest =
                                        calculateRegionOfInterest(recognizedPoints)
                                    val updatedSkeleton = Skeleton(
                                        timestamp = timestamp,
                                        leftShoulder = recognizedPoints?.get(
                                            VNHumanBodyPoseObservationJointNameLeftShoulder
                                        )?.location?.toSkeletonPoint(width, height),
                                        rightShoulder = recognizedPoints?.get(
                                            VNHumanBodyPoseObservationJointNameRightShoulder
                                        )?.location?.toSkeletonPoint(width, height),
                                        leftElbow = recognizedPoints?.get(
                                            VNHumanBodyPoseObservationJointNameLeftElbow
                                        )?.location?.toSkeletonPoint(width, height),
                                        rightElbow = recognizedPoints?.get(
                                            VNHumanBodyPoseObservationJointNameRightElbow
                                        )?.location?.toSkeletonPoint(width, height),
                                        leftWrist = recognizedPoints?.get(
                                            VNHumanBodyPoseObservationJointNameLeftWrist
                                        )?.location?.toSkeletonPoint(width, height),
                                        rightWrist = recognizedPoints?.get(
                                            VNHumanBodyPoseObservationJointNameRightWrist
                                        )?.location?.toSkeletonPoint(width, height),
                                        leftHip = recognizedPoints?.get(
                                            VNHumanBodyPoseObservationJointNameLeftHip
                                        )?.location?.toSkeletonPoint(width, height),
                                        rightHip = recognizedPoints?.get(
                                            VNHumanBodyPoseObservationJointNameRightHip
                                        )?.location?.toSkeletonPoint(width, height),
                                        leftKnee = recognizedPoints?.get(
                                            VNHumanBodyPoseObservationJointNameLeftKnee
                                        )?.location?.toSkeletonPoint(width, height),
                                        rightKnee = recognizedPoints?.get(
                                            VNHumanBodyPoseObservationJointNameRightKnee
                                        )?.location?.toSkeletonPoint(width, height),
                                        leftAnkle = recognizedPoints?.get(
                                            VNHumanBodyPoseObservationJointNameLeftAnkle
                                        )?.location?.toSkeletonPoint(width, height),
                                        rightAnkle = recognizedPoints?.get(
                                            VNHumanBodyPoseObservationJointNameRightAnkle
                                        )?.location?.toSkeletonPoint(width, height),
                                        height = height.toFloat(),
                                        width = width.toFloat()
                                    )
                                    onSkeletonProcessed(skelBuffer.smooth(updatedSkeleton))
                                }
                            }
                        }
                    }else null
                    requestForSkeleton?.regionOfInterest = regionOfInterest
                    val handler = VNImageRequestHandler(buffer, options)
                    handler.performRequests(
                        listOfNotNull(requestForObjects, requestForSkeleton), errorPtr.ptr
                    )
                    CFRelease(retainedBuffer)
                    if (errorPtr.value != null) {
                        //println("Error performing object detection request: ${errorPtr.value}")
                        onObjectsProcessed(emptyList())
                        onSkeletonProcessed(null)
                    }
                } catch (e: Throwable) {
                    //println("Unable to perform the object detection request: ${e.message}")
                    onObjectsProcessed(emptyList())
                    onSkeletonProcessed(null)
                }
            }
        }
    }
}

@OptIn(ExperimentalForeignApi::class)
fun calculateRegionOfInterest(
    recognizedPoints: MutableMap<VNHumanBodyPoseObservationJointName, VNRecognizedPoint>?
): CValue<CGRect> {
    val margin = 0.0
    if (recognizedPoints.isNullOrEmpty()) {
        return CGRectMake(0.0, 0.0, 1.0, 1.0)
    }
    recognizedPoints.values.let { allPoints ->
        val maxX = allPoints.maxOf { it.x }
        val minX = allPoints.minOf { it.x }
        val maxY = allPoints.maxOf { it.y }
        val minY = allPoints.minOf { it.y }

        val x = (minX - margin).coerceIn(0.0, 1.0)
        val y = (minY - margin).coerceIn(0.0, 1.0)
        val width = (maxX - minX + 2 * margin).coerceIn(0.0, 1.0 - x)
        val height = (maxY - minY + 2 * margin).coerceIn(0.0, 1.0 - y)

        return CGRectMake(
            x = x,
            y = y,
            width = width,
            height = height,
        )
    }
}

@OptIn(ExperimentalForeignApi::class)
private fun Rect?.toCGRect(): CValue<CGRect> {
    return if (this == null) {
        CGRectMake(0.0, 0.0, 1.0, 1.0)
    } else {
        val uiX = left
        val uiY = top
        val uiWidth = width
        val uiHeight = height

        // Transform to camera coordinates (90° clockwise rotation)
        val cameraX = (1.0 - uiY - uiHeight)
        val cameraY = uiX
        val cameraWidth = uiHeight
        val cameraHeight = uiWidth

        val xHun = (cameraX * 1000f).toInt().coerceIn(0, 1000)
        val yHun = (cameraY * 1000f).toInt().coerceIn(0, 1000)
        val widthHun = (cameraWidth * 1000f).toInt().coerceIn(1, 999 - xHun)
        val heightHun = (cameraHeight * 1000f).toInt().coerceIn(1, 999 - yHun)
        val x = xHun / 1000.0
        val y = yHun / 1000.0
        val width = widthHun / 1000.0
        val height = heightHun / 1000.0
        CGRectMake(
            x = x,
            y = y,
            width = width,
            height = height,
        )
    }
}

data class DetectedObject @OptIn(ExperimentalForeignApi::class) constructor(
    val id: String = NSUUID().UUIDString(),
    val label: String,
    val confidence: Float,
    val boundingBox: CValue<CGRect>
)

