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.AVCaptureConnection
import platform.AVFoundation.AVCaptureVideoOrientationLandscapeLeft
import platform.AVFoundation.AVCaptureVideoOrientationLandscapeRight
import platform.AVFoundation.AVCaptureVideoOrientationPortrait
import platform.AVFoundation.AVCaptureVideoOrientationPortraitUpsideDown
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.CoreVideo.CVPixelBufferGetHeight
import platform.CoreVideo.CVPixelBufferGetWidth
import platform.Foundation.NSError
import platform.Foundation.NSUUID
import platform.ImageIO.kCGImagePropertyOrientationDown
import platform.ImageIO.kCGImagePropertyOrientationDownMirrored
import platform.ImageIO.kCGImagePropertyOrientationLeft
import platform.ImageIO.kCGImagePropertyOrientationLeftMirrored
import platform.ImageIO.kCGImagePropertyOrientationRight
import platform.ImageIO.kCGImagePropertyOrientationRightMirrored
import platform.ImageIO.kCGImagePropertyOrientationUp
import platform.ImageIO.kCGImagePropertyOrientationUpMirrored
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.math.abs
import kotlin.math.absoluteValue
import kotlin.math.max
import kotlin.math.min

private const val VN_IMAGE_OPTION_CG_IMAGE_PROPERTY_ORIENTATION =
    "VNImageOptionCGImagePropertyOrientation"

// Vision returns points/rects in normalized image coordinates with origin at bottom-left.
// We convert everything into *oriented pixel coordinates* with origin at top-left.
private data class OrientedFrameSize(val width: Float, val height: Float)

private fun orientedFrameSize(
    rawWidth: ULong,
    rawHeight: ULong,
    exifOrientation: Int
): OrientedFrameSize {
    val w = rawWidth.toFloat()
    val h = rawHeight.toFloat()
    // Left/Right orientations swap dimensions.
    return when (exifOrientation.toUInt()) {
        kCGImagePropertyOrientationLeft,
        kCGImagePropertyOrientationLeftMirrored,
        kCGImagePropertyOrientationRight,
        kCGImagePropertyOrientationRightMirrored -> OrientedFrameSize(width = h, height = w)

        else -> OrientedFrameSize(width = w, height = h)
    }
}

@OptIn(ExperimentalForeignApi::class)
private fun CValue<CGPoint>.toOrientedPixelPoint(
    rawWidth: ULong,
    rawHeight: ULong,
    exifOrientation: Int
): Skeleton.SkeletonCoordinate {
    val oriented = orientedFrameSize(rawWidth, rawHeight, exifOrientation)
    // Convert from Vision normalized (origin bottom-left) to oriented pixel (origin top-left).
    // This assumes the EXIF orientation is already applied by VNImageRequestHandler.
    val xNorm: Float
    val yNorm: Float
    this.useContents {
        xNorm = x.toFloat()
        yNorm = y.toFloat()
    }

    val xPx = (xNorm * oriented.width).coerceIn(0f, oriented.width)
    val yPx = ((1f - yNorm) * oriented.height).coerceIn(0f, oriented.height)
    return Skeleton.SkeletonCoordinate(xPx, yPx)
}

@OptIn(ExperimentalForeignApi::class)
private fun CValue<CGRect>.toOrientedPixelRect(
    rawWidth: ULong,
    rawHeight: ULong,
    exifOrientation: Int
): Rect {
    val oriented = orientedFrameSize(rawWidth, rawHeight, exifOrientation)
    // Vision normalized bounding boxes use origin bottom-left.
    return this.useContents {
        val left = (origin.x.toFloat() * oriented.width)
        val bottom = (origin.y.toFloat() * oriented.height)
        val right = ((origin.x + size.width).toFloat() * oriented.width)
        val top = ((origin.y + size.height).toFloat() * oriented.height)

        // Convert bottom-left origin to top-left origin.
        val topPx = oriented.height - top
        val bottomPx = oriented.height - bottom

        Rect(
            left = min(left, right),
            top = min(topPx, bottomPx),
            right = max(left, right),
            bottom = max(topPx, bottomPx)
        ).normalize()
    }
}

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)
private fun AVCaptureConnection?.visionExifOrientation(): Int {
    // Prefer the actual capture connection orientation (not UI orientation).
    val videoOrientation = this?.videoOrientation ?: AVCaptureVideoOrientationLandscapeLeft
    val mirrored = this?.videoMirrored == true

    val exif: UInt = when (videoOrientation) {
        // These mappings match Apple's docs: EXIF/CGImagePropertyOrientation values describe
        // how to rotate the underlying pixel buffer into "Up".
        AVCaptureVideoOrientationPortrait -> if (mirrored) kCGImagePropertyOrientationLeftMirrored else kCGImagePropertyOrientationRight
        AVCaptureVideoOrientationPortraitUpsideDown -> if (mirrored) kCGImagePropertyOrientationRightMirrored else kCGImagePropertyOrientationLeft
        AVCaptureVideoOrientationLandscapeLeft -> if (mirrored) kCGImagePropertyOrientationDownMirrored else kCGImagePropertyOrientationUp
        AVCaptureVideoOrientationLandscapeRight -> if (mirrored) kCGImagePropertyOrientationUpMirrored else kCGImagePropertyOrientationDown
        else -> if (mirrored) kCGImagePropertyOrientationDownMirrored else kCGImagePropertyOrientationUp
    }

    return exif.toInt()
}

@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)
    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 = min(left, right).toFloat(),
                                        top = min(top, bottom).toFloat(),
                                        right = max(left, right).toFloat(),
                                        bottom = max(top, 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 = stableTrackingId(observation),
                                    labels = labels,
                                    boundingBox = boundingBox.normalize(),
                                    frameSize = FrameSize(
                                        width = abs(width.toInt()),
                                        height = abs(height.toInt())
                                    ),
                                    timestamp = timestamp
                                )
                            }
                            onObjectsProcessed(analysisObjects)
                        }.apply {
                            // Make preprocessing consistent across orientations.
                            imageCropAndScaleOption =
                                platform.Vision.VNImageCropAndScaleOptionCenterCrop
                        }
                    }
                    // For CGImage path we don't have an AVCaptureConnection; assume the CGImage
                    // is already "up".
                    val options: Map<Any?, Any?> = mapOf(
                        VN_IMAGE_OPTION_CG_IMAGE_PROPERTY_ORIENTATION to kCGImagePropertyOrientationUp.toInt()
                    )

                    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)
    fun analyseBufferForAll(
        buffer: CVImageBufferRef?,
        timestamp: Long,
        preview: AVCaptureVideoPreviewLayer?,
        captureConnection: AVCaptureConnection? = preview?.connection,
        onObjectsProcessed: (List<AnalysisObject>) -> Unit,
        onSkeletonProcessed: (Skeleton?) -> Unit
    ) {
        autoreleasepool {
            if (buffer == null) {
                onObjectsProcessed(emptyList())
                onSkeletonProcessed(null)
                return
            }
            val retainedBuffer = CFRetain(buffer)
            val rawWidth = CVPixelBufferGetWidth(buffer).toULong()
            val rawHeight = CVPixelBufferGetHeight(buffer).toULong()
            val exifOrientation = captureConnection.visionExifOrientation()
            val orientedSize = orientedFrameSize(rawWidth, rawHeight, exifOrientation)

            memScoped {
                val errorPtr = alloc<ObjCObjectVar<NSError?>>()
                try {
                    val requestForObjects = if (detectMode.doObject()) {
                        modelObj?.let { model ->
                            VNCoreMLRequest(model) { 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 boundingBox = observation.boundingBox.toOrientedPixelRect(
                                        rawWidth = rawWidth,
                                        rawHeight = rawHeight,
                                        exifOrientation = exifOrientation
                                    )

                                    val labels = observation.labels.mapNotNull {
                                        (it as VNClassificationObservation).let { ca ->
                                            if (ca.confidence > 0.0) Label(
                                                ca.identifier,
                                                ca.confidence
                                            ) else null
                                        }
                                    }

                                    AnalysisObject(
                                        trackingId = stableTrackingId(observation),
                                        labels = labels,
                                        boundingBox = boundingBox,
                                        frameSize = FrameSize(
                                            width = orientedSize.width.toInt().absoluteValue,
                                            height = orientedSize.height.toInt().absoluteValue
                                        ),
                                        timestamp = timestamp
                                    )
                                }

                                onObjectsProcessed(analysisObjects)
                            }.apply {
                                imageCropAndScaleOption =
                                    platform.Vision.VNImageCropAndScaleOptionCenterCrop
                            }
                        }
                    } else null

                    val options: Map<Any?, Any?> = mapOf(
                        VN_IMAGE_OPTION_CG_IMAGE_PROPERTY_ORIENTATION to exifOrientation
                    )

                    val requestForSkeleton = if (detectMode.doPose()) {
                        VNDetectHumanBodyPoseRequest { request, error ->
                            if (error != null) {
                                onSkeletonProcessed(null)
                            } else {
                                request?.also { vnRequest ->
                                    val recognizedPoints =
                                        bodyPoseHandler(vnRequest)?.firstOrNull { pointsMap ->
                                            // Build skeleton in oriented pixel space and check focus area there.
                                            val candidate = Skeleton(
                                                timestamp = timestamp,
                                                leftShoulder = pointsMap[VNHumanBodyPoseObservationJointNameLeftShoulder]?.location?.toOrientedPixelPoint(
                                                    rawWidth,
                                                    rawHeight,
                                                    exifOrientation
                                                ),
                                                rightShoulder = pointsMap[VNHumanBodyPoseObservationJointNameRightShoulder]?.location?.toOrientedPixelPoint(
                                                    rawWidth,
                                                    rawHeight,
                                                    exifOrientation
                                                ),
                                                leftElbow = pointsMap[VNHumanBodyPoseObservationJointNameLeftElbow]?.location?.toOrientedPixelPoint(
                                                    rawWidth,
                                                    rawHeight,
                                                    exifOrientation
                                                ),
                                                rightElbow = pointsMap[VNHumanBodyPoseObservationJointNameRightElbow]?.location?.toOrientedPixelPoint(
                                                    rawWidth,
                                                    rawHeight,
                                                    exifOrientation
                                                ),
                                                leftWrist = pointsMap[VNHumanBodyPoseObservationJointNameLeftWrist]?.location?.toOrientedPixelPoint(
                                                    rawWidth,
                                                    rawHeight,
                                                    exifOrientation
                                                ),
                                                rightWrist = pointsMap[VNHumanBodyPoseObservationJointNameRightWrist]?.location?.toOrientedPixelPoint(
                                                    rawWidth,
                                                    rawHeight,
                                                    exifOrientation
                                                ),
                                                leftHip = pointsMap[VNHumanBodyPoseObservationJointNameLeftHip]?.location?.toOrientedPixelPoint(
                                                    rawWidth,
                                                    rawHeight,
                                                    exifOrientation
                                                ),
                                                rightHip = pointsMap[VNHumanBodyPoseObservationJointNameRightHip]?.location?.toOrientedPixelPoint(
                                                    rawWidth,
                                                    rawHeight,
                                                    exifOrientation
                                                ),
                                                leftKnee = pointsMap[VNHumanBodyPoseObservationJointNameLeftKnee]?.location?.toOrientedPixelPoint(
                                                    rawWidth,
                                                    rawHeight,
                                                    exifOrientation
                                                ),
                                                rightKnee = pointsMap[VNHumanBodyPoseObservationJointNameRightKnee]?.location?.toOrientedPixelPoint(
                                                    rawWidth,
                                                    rawHeight,
                                                    exifOrientation
                                                ),
                                                leftAnkle = pointsMap[VNHumanBodyPoseObservationJointNameLeftAnkle]?.location?.toOrientedPixelPoint(
                                                    rawWidth,
                                                    rawHeight,
                                                    exifOrientation
                                                ),
                                                rightAnkle = pointsMap[VNHumanBodyPoseObservationJointNameRightAnkle]?.location?.toOrientedPixelPoint(
                                                    rawWidth,
                                                    rawHeight,
                                                    exifOrientation
                                                ),
                                                height = orientedSize.height,
                                                width = orientedSize.width
                                            )

                                            candidate.isInFocusArea(focusArea)
                                        }

                                    regionOfInterest = calculateRegionOfInterest(recognizedPoints)

                                    val updatedSkeleton = Skeleton(
                                        timestamp = timestamp,
                                        leftShoulder = recognizedPoints?.get(
                                            VNHumanBodyPoseObservationJointNameLeftShoulder
                                        )?.location?.toOrientedPixelPoint(
                                            rawWidth,
                                            rawHeight,
                                            exifOrientation
                                        ),
                                        rightShoulder = recognizedPoints?.get(
                                            VNHumanBodyPoseObservationJointNameRightShoulder
                                        )?.location?.toOrientedPixelPoint(
                                            rawWidth,
                                            rawHeight,
                                            exifOrientation
                                        ),
                                        leftElbow = recognizedPoints?.get(
                                            VNHumanBodyPoseObservationJointNameLeftElbow
                                        )?.location?.toOrientedPixelPoint(
                                            rawWidth,
                                            rawHeight,
                                            exifOrientation
                                        ),
                                        rightElbow = recognizedPoints?.get(
                                            VNHumanBodyPoseObservationJointNameRightElbow
                                        )?.location?.toOrientedPixelPoint(
                                            rawWidth,
                                            rawHeight,
                                            exifOrientation
                                        ),
                                        leftWrist = recognizedPoints?.get(
                                            VNHumanBodyPoseObservationJointNameLeftWrist
                                        )?.location?.toOrientedPixelPoint(
                                            rawWidth,
                                            rawHeight,
                                            exifOrientation
                                        ),
                                        rightWrist = recognizedPoints?.get(
                                            VNHumanBodyPoseObservationJointNameRightWrist
                                        )?.location?.toOrientedPixelPoint(
                                            rawWidth,
                                            rawHeight,
                                            exifOrientation
                                        ),
                                        leftHip = recognizedPoints?.get(
                                            VNHumanBodyPoseObservationJointNameLeftHip
                                        )?.location?.toOrientedPixelPoint(
                                            rawWidth,
                                            rawHeight,
                                            exifOrientation
                                        ),
                                        rightHip = recognizedPoints?.get(
                                            VNHumanBodyPoseObservationJointNameRightHip
                                        )?.location?.toOrientedPixelPoint(
                                            rawWidth,
                                            rawHeight,
                                            exifOrientation
                                        ),
                                        leftKnee = recognizedPoints?.get(
                                            VNHumanBodyPoseObservationJointNameLeftKnee
                                        )?.location?.toOrientedPixelPoint(
                                            rawWidth,
                                            rawHeight,
                                            exifOrientation
                                        ),
                                        rightKnee = recognizedPoints?.get(
                                            VNHumanBodyPoseObservationJointNameRightKnee
                                        )?.location?.toOrientedPixelPoint(
                                            rawWidth,
                                            rawHeight,
                                            exifOrientation
                                        ),
                                        leftAnkle = recognizedPoints?.get(
                                            VNHumanBodyPoseObservationJointNameLeftAnkle
                                        )?.location?.toOrientedPixelPoint(
                                            rawWidth,
                                            rawHeight,
                                            exifOrientation
                                        ),
                                        rightAnkle = recognizedPoints?.get(
                                            VNHumanBodyPoseObservationJointNameRightAnkle
                                        )?.location?.toOrientedPixelPoint(
                                            rawWidth,
                                            rawHeight,
                                            exifOrientation
                                        ),
                                        height = orientedSize.height,
                                        width = orientedSize.width
                                    )

                                    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) {
                        onObjectsProcessed(emptyList())
                        onSkeletonProcessed(null)
                    }
                } catch (_: Throwable) {
                    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,
        )
    }
}

private fun stableTrackingId(observation: VNRecognizedObjectObservation): Int =
    observation.hashCode()

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