import CoreMedia
import CoreVideo
import Foundation
import ScreenCaptureKit

@available(macOS 12.3, *)
final class ScreenFrameOutput: NSObject, SCStreamOutput {
    private let stdout = FileHandle.standardOutput
    private var lastFrameAt = DispatchTime.now()

    func stream(_ stream: SCStream, didOutputSampleBuffer sampleBuffer: CMSampleBuffer, of type: SCStreamOutputType) {
        guard type == .screen,
              sampleBuffer.isValid,
              let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else {
            return
        }

        CVPixelBufferLockBaseAddress(pixelBuffer, .readOnly)
        defer { CVPixelBufferUnlockBaseAddress(pixelBuffer, .readOnly) }

        guard let baseAddress = CVPixelBufferGetBaseAddress(pixelBuffer) else { return }
        let width = CVPixelBufferGetWidth(pixelBuffer)
        let height = CVPixelBufferGetHeight(pixelBuffer)
        let bytesPerRow = CVPixelBufferGetBytesPerRow(pixelBuffer)
        let frame = bgraToI420(
            baseAddress: baseAddress.assumingMemoryBound(to: UInt8.self),
            width: width,
            height: height,
            bytesPerRow: bytesPerRow
        )

        guard let header = "FRAME \(width) \(height) \(frame.count)\n".data(using: .utf8) else { return }
        stdout.write(header)
        stdout.write(frame)
    }
}

func clampByte(_ value: Int) -> UInt8 {
    UInt8(max(0, min(255, value)))
}

func bgraToI420(baseAddress: UnsafePointer<UInt8>, width: Int, height: Int, bytesPerRow: Int) -> Data {
    let ySize = width * height
    let uvWidth = (width + 1) / 2
    let uvHeight = (height + 1) / 2
    let uvSize = uvWidth * uvHeight
    var frame = Data(count: ySize + uvSize * 2)

    frame.withUnsafeMutableBytes { raw in
        guard let out = raw.bindMemory(to: UInt8.self).baseAddress else { return }
        let uOffset = ySize
        let vOffset = ySize + uvSize

        for y in 0..<height {
            for x in 0..<width {
                let source = y * bytesPerRow + x * 4
                let b = Int(baseAddress[source])
                let g = Int(baseAddress[source + 1])
                let r = Int(baseAddress[source + 2])
                out[y * width + x] = clampByte(((66 * r + 129 * g + 25 * b + 128) >> 8) + 16)
            }
        }

        var uvY = 0
        var y = 0
        while y < height {
            var uvX = 0
            var x = 0
            while x < width {
                var rSum = 0
                var gSum = 0
                var bSum = 0
                var count = 0
                for yy in 0..<2 {
                    for xx in 0..<2 {
                        let sx = x + xx
                        let sy = y + yy
                        if sx >= width || sy >= height { continue }
                        let source = sy * bytesPerRow + sx * 4
                        bSum += Int(baseAddress[source])
                        gSum += Int(baseAddress[source + 1])
                        rSum += Int(baseAddress[source + 2])
                        count += 1
                    }
                }
                let r = rSum / max(1, count)
                let g = gSum / max(1, count)
                let b = bSum / max(1, count)
                let uvIndex = uvY * uvWidth + uvX
                out[uOffset + uvIndex] = clampByte(((-38 * r - 74 * g + 112 * b + 128) >> 8) + 128)
                out[vOffset + uvIndex] = clampByte(((112 * r - 94 * g - 18 * b + 128) >> 8) + 128)
                uvX += 1
                x += 2
            }
            uvY += 1
            y += 2
        }
    }

    return frame
}

@available(macOS 12.3, *)
func runCapture() async throws -> Never {
    let args = CommandLine.arguments
    let requestedWidth = Int(args.dropFirst().first ?? "") ?? 1280
    let requestedFps = Int(args.dropFirst(2).first ?? "") ?? 30

    let content = try await SCShareableContent.excludingDesktopWindows(false, onScreenWindowsOnly: true)
    guard let display = content.displays.first else {
        FileHandle.standardError.write(Data("No display is available.\n".utf8))
        exit(2)
    }

    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 = 4
    configuration.minimumFrameInterval = CMTime(value: 1, timescale: CMTimeScale(max(1, min(60, requestedFps))))
    configuration.showsCursor = true

    let output = ScreenFrameOutput()
    let stream = SCStream(filter: filter, configuration: configuration, delegate: nil)
    try stream.addStreamOutput(output, type: .screen, sampleHandlerQueue: DispatchQueue(label: "her.screen.capture"))
    try await stream.startCapture()
    RunLoop.main.run()
    fatalError("RunLoop exited unexpectedly.")
}

if #available(macOS 12.3, *) {
    Task {
        do {
            try await runCapture()
        } catch {
            FileHandle.standardError.write(Data("ScreenCaptureKit failed: \(error)\n".utf8))
            exit(1)
        }
    }
    RunLoop.main.run()
} else {
    FileHandle.standardError.write(Data("ScreenCaptureKit requires macOS 12.3 or later.\n".utf8))
    exit(1)
}
