import AppKit
import CoreGraphics
import Foundation
import ScreenCaptureKit

/// Pixel capture — ScreenCaptureKit in-process (§7): no `screencapture` CLI, no `launchctl asuser`,
/// no base64-over-SSH. Needs the Screen Recording grant + an Aqua session. If capture comes back
/// blank or unavailable, AX (§6) is the primary framebuffer-independent "look".
extension ConnectionHandler {
    /// `screenshot` — capture one display (default: the first/main) and return base64 JPEG plus its
    /// pixel geometry. JPEG keeps a screenshot→act loop's payload ~10× smaller than PNG.
    func opScreenshot(_ request: [String: Any]) -> [String: Any] {
        guard CGPreflightScreenCaptureAccess() else {
            return AgentReply.err(
                "Screen Recording (kTCCServiceScreenCapture) is not granted to the agent — re-run "
                    + "provisioning")
        }
        let displayIndex = (request["display"] as? Int) ?? 0

        // The op handlers run on a plain blocking connection thread; SCK's surface is async-only,
        // so bridge with a semaphore (the thread has nothing else to do but wait).
        let semaphore = DispatchSemaphore(value: 0)
        let box = ResultBox<[String: Any]>()
        Task {
            box.value = await Self.captureDisplay(index: displayIndex)
            semaphore.signal()
        }
        guard semaphore.wait(timeout: .now() + 15) == .success, let reply = box.value else {
            return AgentReply.err("screenshot timed out")
        }
        return reply
    }

    private static func captureDisplay(index: Int) async -> [String: Any] {
        do {
            let content = try await SCShareableContent.current
            let displays = content.displays
            guard !displays.isEmpty else { return AgentReply.err("no shareable displays") }
            let display = displays.indices.contains(index) ? displays[index] : displays[0]

            let filter = SCContentFilter(display: display, excludingWindows: [])
            let configuration = SCStreamConfiguration()
            // Capture at the filter's native pixel size (`contentRect` is points; the scale is the
            // point→pixel factor — Float in the SDK, hence the CGFloat conversion).
            let scale = CGFloat(filter.pointPixelScale)
            configuration.width = Int(filter.contentRect.width * scale)
            configuration.height = Int(filter.contentRect.height * scale)
            configuration.showsCursor = true

            let image = try await SCScreenshotManager.captureImage(
                contentFilter: filter, configuration: configuration)
            let bitmap = NSBitmapImageRep(cgImage: image)
            guard
                let jpeg = bitmap.representation(
                    using: .jpeg, properties: [.compressionFactor: 0.7])
            else { return AgentReply.err("JPEG encoding failed") }
            return AgentReply.ok([
                "image": jpeg.base64EncodedString(),
                "format": "jpeg",
                "width": image.width,
                "height": image.height,
                "scale": Double(scale),
            ])
        } catch {
            return AgentReply.err("ScreenCaptureKit capture failed: \(error)")
        }
    }
}

/// Hands one value from a bridging Task back to the blocking connection thread; the semaphore
/// orders the accesses (signal happens-after the write, wait happens-before the read).
final class ResultBox<T> {
    var value: T?
}
