// Bridge between Rust and Apple's ScreenCaptureKit (SCK).
//
// SCK is the supported macOS 14+ replacement for the deprecated
// `CGDisplayCreateImage` / `CGWindowListCreateImage` Quartz APIs.
// CG remains functional through the current macOS release but Apple
// has flagged both for removal; this shim is the migration path.
//
// API surface today: one-shot full-display capture via
// `SCScreenshotManager.captureImage(contentFilter:configuration:)`.
// Window capture is intentionally not implemented here — SCK's
// per-window capture requires `SCContentFilter(desktopIndependentWindow:)`
// which only matches windows the calling process can observe via
// `SCShareableContent.current`. That's the right behavior (closes
// the `CGWindowListCreateImage` privacy edge case where windows you
// don't have explicit permission for could leak through) but
// requires a different Rust-side handle resolution path. Tracked as
// future work; today the Rust caller falls back to CG for window
// capture.
//
// Output format: BGRA, 8 bits/channel, premultiplied alpha — the
// same byte order CG returns. The Rust side already does the
// BGRA→RGBA swap in capture.rs::frame_from_cgimage; matching the
// pixel format means that helper works unchanged.

import Foundation

#if canImport(ScreenCaptureKit)
import ScreenCaptureKit
import CoreGraphics
import CoreVideo
#endif

// MARK: - C helpers

private func writeError(
    _ outErr: UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>?,
    _ msg: String
) {
    guard let outErr = outErr else { return }
    outErr.pointee = strdup(msg)
}

@_cdecl("car_sck_free_string")
public func car_sck_free_string(_ ptr: UnsafeMutablePointer<CChar>?) {
    guard let ptr = ptr else { return }
    free(ptr)
}

@_cdecl("car_sck_free_buffer")
public func car_sck_free_buffer(_ ptr: UnsafeMutablePointer<UInt8>?, _ len: Int) {
    guard let ptr = ptr else { return }
    let _ = len // tracked in case we ever need to deallocate via munmap
    free(ptr)
}

// MARK: - Availability

@_cdecl("car_sck_is_available")
public func car_sck_is_available() -> Int32 {
    #if canImport(ScreenCaptureKit)
    if #available(macOS 14.0, *) {
        return 1
    }
    #endif
    return 0
}

// MARK: - Display capture
//
// Output: malloc'd BGRA8 buffer at *out_buffer, width/height/bytes_per_row
// stored in the corresponding out parameters. Caller frees via
// `car_sck_free_buffer`. Returns 0 on success.
//
// `displayId == 0` resolves to the main display.

@_cdecl("car_sck_capture_display")
public func car_sck_capture_display(
    _ displayId: UInt32,
    _ outBuffer: UnsafeMutablePointer<UnsafeMutablePointer<UInt8>?>?,
    _ outLen: UnsafeMutablePointer<Int>?,
    _ outWidth: UnsafeMutablePointer<UInt32>?,
    _ outHeight: UnsafeMutablePointer<UInt32>?,
    _ outBytesPerRow: UnsafeMutablePointer<Int>?,
    _ outErr: UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>?
) -> Int32 {
    #if canImport(ScreenCaptureKit)
    if #available(macOS 14.0, *) {
        let semaphore = DispatchSemaphore(value: 0)
        var resultImage: CGImage?
        var resultError: String?

        Task.detached(priority: .userInitiated) {
            defer { semaphore.signal() }
            do {
                let content = try await SCShareableContent.current
                let target: SCDisplay
                if displayId == 0 {
                    guard let main = content.displays.first else {
                        resultError = "no displays available"
                        return
                    }
                    target = main
                } else {
                    guard let match = content.displays.first(where: { $0.displayID == displayId }) else {
                        resultError = "display \(displayId) not present in SCShareableContent"
                        return
                    }
                    target = match
                }
                let filter = SCContentFilter(display: target, excludingWindows: [])
                let config = SCStreamConfiguration()
                // Pin pixel format to BGRA8 — same byte order CG
                // returns, lets the Rust side reuse its existing
                // BGRA→RGBA path in frame_from_cgimage.
                config.pixelFormat = kCVPixelFormatType_32BGRA
                config.width = target.width
                config.height = target.height
                config.scalesToFit = false
                config.showsCursor = false
                resultImage = try await SCScreenshotManager.captureImage(
                    contentFilter: filter,
                    configuration: config
                )
            } catch {
                resultError = "\(error)"
            }
        }
        semaphore.wait()

        if let err = resultError {
            writeError(outErr, err)
            return 2
        }
        guard let image = resultImage else {
            writeError(outErr, "SCScreenshotManager returned no image and no error")
            return 2
        }

        // Render the CGImage into a tightly-packed BGRA buffer.
        // CGImage from SCK doesn't guarantee a contiguous pixel
        // layout (per-row padding is platform-dependent), so we
        // redraw into our own context with a known stride. Same
        // pattern the Rust frame_from_cgimage uses, just done in
        // Swift here so the C ABI returns a flat buffer.
        let width = image.width
        let height = image.height
        let bytesPerRow = width * 4
        let totalBytes = bytesPerRow * height
        guard let buffer = malloc(totalBytes)?.assumingMemoryBound(to: UInt8.self) else {
            writeError(outErr, "malloc failed for \(totalBytes) bytes")
            return 2
        }
        let colorSpace = CGColorSpaceCreateDeviceRGB()
        // bitmapInfo: BGRA premultiplied first matches kCVPixelFormatType_32BGRA
        // and matches what the Rust frame_from_cgimage expects to swap.
        let bitmapInfo: UInt32 = CGBitmapInfo.byteOrder32Little.rawValue
            | CGImageAlphaInfo.premultipliedFirst.rawValue
        guard let ctx = CGContext(
            data: buffer,
            width: width,
            height: height,
            bitsPerComponent: 8,
            bytesPerRow: bytesPerRow,
            space: colorSpace,
            bitmapInfo: bitmapInfo
        ) else {
            free(buffer)
            writeError(outErr, "CGContext allocation failed")
            return 2
        }
        ctx.draw(image, in: CGRect(x: 0, y: 0, width: width, height: height))

        outBuffer?.pointee = buffer
        outLen?.pointee = totalBytes
        outWidth?.pointee = UInt32(width)
        outHeight?.pointee = UInt32(height)
        outBytesPerRow?.pointee = bytesPerRow
        return 0
    }
    #endif
    writeError(outErr, "ScreenCaptureKit requires macOS 14.0+")
    return 3
}
