import CoreImage
import CoreMedia
import CoreVideo
import Foundation
import ImageIO
import ScreenCaptureKit

func clamp(_ value: Int, min minValue: Int, max maxValue: Int) -> Int {
    Swift.max(minValue, Swift.min(maxValue, value))
}

func resizedImage(_ image: CGImage, targetWidth: Int) -> CGImage? {
    if targetWidth <= 0 || targetWidth >= image.width {
        return image
    }
    let targetHeight = Swift.max(1, Int(Double(image.height) * Double(targetWidth) / Double(image.width)))
    let colorSpace = CGColorSpaceCreateDeviceRGB()
    guard let context = CGContext(
        data: nil,
        width: targetWidth,
        height: targetHeight,
        bitsPerComponent: 8,
        bytesPerRow: 0,
        space: colorSpace,
        bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue
    ) else {
        return nil
    }
    context.interpolationQuality = .medium
    context.draw(image, in: CGRect(x: 0, y: 0, width: targetWidth, height: targetHeight))
    return context.makeImage()
}

func encode(_ image: CGImage, format: String, quality: Int) -> Data? {
    let data = NSMutableData()
    let type = format == "png" ? "public.png" : "public.jpeg"
    guard let destination = CGImageDestinationCreateWithData(
        data,
        type as CFString,
        1,
        nil
    ) else {
        return nil
    }

    var options: [CFString: Any] = [:]
    if format != "png" {
        options[kCGImageDestinationLossyCompressionQuality] = Double(clamp(quality, min: 1, max: 100)) / 100.0
        options[kCGImagePropertyJFIFDictionary] = [
            kCGImagePropertyJFIFIsProgressive: false
        ]
    }
    CGImageDestinationAddImage(destination, image, options as CFDictionary)
    guard CGImageDestinationFinalize(destination) else { return nil }
    return data as Data
}

@available(macOS 12.3, *)
final class OneShotOutput: NSObject, SCStreamOutput {
    private let context = CIContext()
    private let lock = NSLock()
    private var continuation: CheckedContinuation<CGImage, Error>?

    func waitForFrame() async throws -> CGImage {
        try await withCheckedThrowingContinuation { continuation in
            lock.lock()
            self.continuation = continuation
            lock.unlock()
        }
    }

    func fail(_ error: Error) {
        lock.lock()
        let continuation = self.continuation
        self.continuation = nil
        lock.unlock()
        continuation?.resume(throwing: error)
    }

    func stream(_ stream: SCStream, didOutputSampleBuffer sampleBuffer: CMSampleBuffer, of type: SCStreamOutputType) {
        guard type == .screen,
              sampleBuffer.isValid,
              let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else {
            return
        }
        let ciImage = CIImage(cvPixelBuffer: pixelBuffer)
        guard let cgImage = context.createCGImage(ciImage, from: ciImage.extent) else {
            return
        }

        lock.lock()
        let continuation = self.continuation
        self.continuation = nil
        lock.unlock()
        continuation?.resume(returning: cgImage)
    }
}

struct ScreenshotError: LocalizedError {
    let message: String
    var errorDescription: String? { message }
}

@available(macOS 12.3, *)
func captureFrame(requestedWidth: Int, fast: Bool) async throws -> CGImage {
    let content = try await SCShareableContent.excludingDesktopWindows(false, onScreenWindowsOnly: true)
    guard let display = content.displays.first else {
        throw ScreenshotError(message: "No display is available.")
    }

    let scale = Double(requestedWidth) / Double(max(display.width, 1))
    let outputWidth = max(320, requestedWidth)
    let outputHeight = max(180, Int(Double(display.height) * scale))
    let filter = SCContentFilter(display: display, excludingWindows: [])
    let configuration = SCStreamConfiguration()
    configuration.width = outputWidth
    configuration.height = outputHeight
    configuration.pixelFormat = kCVPixelFormatType_32BGRA
    configuration.queueDepth = fast ? 1 : 2
    configuration.minimumFrameInterval = CMTime(value: 1, timescale: fast ? 30 : 10)
    configuration.showsCursor = true

    let output = OneShotOutput()
    let stream = SCStream(filter: filter, configuration: configuration, delegate: nil)
    try stream.addStreamOutput(output, type: .screen, sampleHandlerQueue: DispatchQueue(label: "her.screenshot.oneshot"))
    try await stream.startCapture()
    defer { Task { try? await stream.stopCapture() } }
    let timeoutNs: UInt64 = fast ? 3_500_000_000 : 8_000_000_000

    return try await withThrowingTaskGroup(of: CGImage.self) { group in
        group.addTask {
            try await output.waitForFrame()
        }
        group.addTask {
            try await Task.sleep(nanoseconds: timeoutNs)
            throw ScreenshotError(message: "Timed out waiting for screen frame.")
        }
        guard let image = try await group.next() else {
            throw ScreenshotError(message: "No screen frame returned.")
        }
        group.cancelAll()
        return image
    }
}

let args = CommandLine.arguments
let formatRaw = args.dropFirst().first?.lowercased() ?? "jpeg"
let format = formatRaw == "png" ? "png" : "jpeg"
let width = clamp(Int(args.dropFirst(2).first ?? "") ?? 960, min: 320, max: 2560)
let quality = clamp(Int(args.dropFirst(3).first ?? "") ?? 62, min: 25, max: 90)
let fast = (args.dropFirst(4).first ?? "0") == "1"

if #available(macOS 12.3, *) {
    Task {
        do {
            let image = try await captureFrame(requestedWidth: width, fast: fast)
            guard let scaled = resizedImage(image, targetWidth: width),
                  let bytes = encode(scaled, format: format, quality: quality) else {
                FileHandle.standardError.write(Data("Unable to encode screenshot.\n".utf8))
                exit(2)
            }
            FileHandle.standardOutput.write(bytes)
            exit(0)
        } catch {
            FileHandle.standardError.write(Data("ScreenCaptureKit screenshot failed: \(error.localizedDescription)\n".utf8))
            exit(1)
        }
    }
    RunLoop.main.run()
} else {
    FileHandle.standardError.write(Data("ScreenCaptureKit requires macOS 12.3 or later.\n".utf8))
    exit(1)
}
