package com.tt.honeyDue.util

import kotlinx.cinterop.ExperimentalForeignApi
import kotlinx.cinterop.addressOf
import kotlinx.cinterop.useContents
import kotlinx.cinterop.usePinned
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import platform.CoreGraphics.CGRectMake
import platform.CoreGraphics.CGSizeMake
import platform.Foundation.NSData
import platform.Foundation.create
import platform.UIKit.UIGraphicsBeginImageContextWithOptions
import platform.UIKit.UIGraphicsEndImageContext
import platform.UIKit.UIGraphicsGetImageFromCurrentImageContext
import platform.UIKit.UIImage
import platform.UIKit.UIImageJPEGRepresentation
import platform.posix.memcpy
import kotlin.math.max

/**
 * iOS implementation of [ImageCompression].
 *
 * Matches the iOS reference helper at
 * `iosApp/iosApp/Helpers/ImageCompression.swift`:
 *   - decode bytes into a [UIImage]
 *   - optionally downscale so the long edge ≤ `maxEdgePx`, aspect preserved
 *   - JPEG-encode at `quality` via [UIImageJPEGRepresentation]
 *
 * `UIImage(data:)` preserves EXIF via `imageOrientation`; drawing the
 * image into a graphics context — or calling `UIImageJPEGRepresentation`
 * directly — bakes that orientation into the output pixels so the result
 * is always upright (no orientation tag round-tripped).
 */
@OptIn(ExperimentalForeignApi::class, kotlinx.cinterop.BetaInteropApi::class)
actual object ImageCompression {

    actual suspend fun compress(
        input: ByteArray,
        maxEdgePx: Int,
        quality: Float
    ): ByteArray = withContext(Dispatchers.Default) {
        // --- wrap input in NSData -----------------------------------------
        val nsData: NSData = input.usePinned { pinned ->
            NSData.create(
                bytes = pinned.addressOf(0),
                length = input.size.toULong()
            )
        }

        // --- decode UIImage -----------------------------------------------
        val decoded = UIImage.imageWithData(nsData) ?: return@withContext input

        // --- downscale if needed ------------------------------------------
        val resized = downscaleIfNeeded(decoded, maxEdgePx)

        // --- encode JPEG (orientation is baked into pixels) ---------------
        val clampedQuality = quality.coerceIn(0f, 1f).toDouble()
        val jpeg = UIImageJPEGRepresentation(resized, clampedQuality)
            ?: return@withContext input

        jpeg.toByteArray()
    }

    /**
     * Downscale a [UIImage] so its long edge is at most [maxEdgePx].
     * Aspect ratio is preserved. Returns the original image if it already
     * fits. Drawing through `UIGraphicsBeginImageContextWithOptions` also
     * normalizes `imageOrientation` into the pixel data.
     */
    private fun downscaleIfNeeded(image: UIImage, maxEdgePx: Int): UIImage {
        if (maxEdgePx <= 0) return image

        val (srcW, srcH) = image.size.useContents { width to height }
        val longEdge = max(srcW, srcH)
        if (longEdge <= maxEdgePx) return image

        val scale = maxEdgePx.toDouble() / longEdge
        val targetW = (srcW * scale).coerceAtLeast(1.0)
        val targetH = (srcH * scale).coerceAtLeast(1.0)

        // `scale = 1.0` keeps the output at target pixel dimensions
        // regardless of screen scale (important in unit tests / server uploads).
        UIGraphicsBeginImageContextWithOptions(
            size = CGSizeMake(targetW, targetH),
            opaque = false,
            scale = 1.0
        )
        image.drawInRect(CGRectMake(0.0, 0.0, targetW, targetH))
        val output = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return output ?: image
    }

    /** Copy an [NSData] buffer into a Kotlin [ByteArray]. */
    private fun NSData.toByteArray(): ByteArray {
        val len = length.toInt()
        if (len == 0) return ByteArray(0)
        return ByteArray(len).apply {
            usePinned { pinned ->
                memcpy(pinned.addressOf(0), this@toByteArray.bytes, this@toByteArray.length)
            }
        }
    }
}
