import AppKit
import ApplicationServices
import CoreGraphics
import Foundation
import Vision

struct OverlayResult: Codable {
    let windowFound: Bool
    let overlayDetected: Bool
    let continueFound: Bool
    let clicked: Bool
    let windowTitle: String
    let recognizedText: [String]
    let error: String?
}

func emit(_ result: OverlayResult) -> Never {
    let encoder = JSONEncoder()
    if let data = try? encoder.encode(result) {
        FileHandle.standardOutput.write(data)
        FileHandle.standardOutput.write(Data("\n".utf8))
    }
    exit(0)
}

func normalized(_ value: String) -> String {
    return value
        .precomposedStringWithCompatibilityMapping
        .lowercased()
        .filter { $0.isLetter || $0.isNumber }
}

func raiseWindow(pid: pid_t, title: String) {
    let application = AXUIElementCreateApplication(pid)
    var rawWindows: CFTypeRef?
    guard AXUIElementCopyAttributeValue(
        application,
        kAXWindowsAttribute as CFString,
        &rawWindows
    ) == .success,
    let windows = rawWindows as? [AXUIElement] else { return }

    for window in windows {
        var rawTitle: CFTypeRef?
        guard AXUIElementCopyAttributeValue(
            window,
            kAXTitleAttribute as CFString,
            &rawTitle
        ) == .success,
        let windowTitle = rawTitle as? String,
        windowTitle == title else { continue }
        AXUIElementPerformAction(window, kAXRaiseAction as CFString)
        NSRunningApplication(processIdentifier: pid)?.activate(
            options: [.activateIgnoringOtherApps]
        )
        Thread.sleep(forTimeInterval: 0.15)
        return
    }
}

guard CommandLine.arguments.count == 3 else {
    emit(OverlayResult(
        windowFound: false,
        overlayDetected: false,
        continueFound: false,
        clicked: false,
        windowTitle: "",
        recognizedText: [],
        error: "usage: bluestacks_overlay <exact-window-title> <inspect|click>"
    ))
}

let targetTitle = CommandLine.arguments[1]
let shouldClick = CommandLine.arguments[2] == "click"
guard CommandLine.arguments[2] == "inspect" || shouldClick else {
    emit(OverlayResult(
        windowFound: false,
        overlayDetected: false,
        continueFound: false,
        clicked: false,
        windowTitle: targetTitle,
        recognizedText: [],
        error: "mode must be inspect or click"
    ))
}

let options: CGWindowListOption = [.optionOnScreenOnly, .excludeDesktopElements]
guard let rawWindows = CGWindowListCopyWindowInfo(options, kCGNullWindowID)
    as? [[String: Any]] else {
    emit(OverlayResult(
        windowFound: false,
        overlayDetected: false,
        continueFound: false,
        clicked: false,
        windowTitle: targetTitle,
        recognizedText: [],
        error: "CGWindowListCopyWindowInfo failed"
    ))
}

let matches = rawWindows.filter { window in
    let owner = window[kCGWindowOwnerName as String] as? String ?? ""
    let title = window[kCGWindowName as String] as? String ?? ""
    let layer = (window[kCGWindowLayer as String] as? NSNumber)?.intValue ?? -1
    return owner == "BlueStacks" && title == targetTitle && layer == 0
}

guard matches.count == 1,
      let windowNumber = matches[0][kCGWindowNumber as String] as? NSNumber,
      let ownerPID = matches[0][kCGWindowOwnerPID as String] as? NSNumber,
      let boundsValue = matches[0][kCGWindowBounds as String],
      let boundsDictionary = boundsValue as? NSDictionary,
      let bounds = CGRect(dictionaryRepresentation: boundsDictionary) else {
    emit(OverlayResult(
        windowFound: false,
        overlayDetected: false,
        continueFound: false,
        clicked: false,
        windowTitle: targetTitle,
        recognizedText: [],
        error: "expected exactly one visible BlueStacks window with the exact title"
    ))
}

let windowID = CGWindowID(windowNumber.uint32Value)
let captureURL = FileManager.default.temporaryDirectory
    .appendingPathComponent("aov-bluestacks-\(UUID().uuidString).png")
defer { try? FileManager.default.removeItem(at: captureURL) }
let capture = Process()
capture.executableURL = URL(fileURLWithPath: "/usr/sbin/screencapture")
capture.arguments = ["-x", "-o", "-l", String(windowID), captureURL.path]
do {
    try capture.run()
    capture.waitUntilExit()
} catch {
    emit(OverlayResult(
        windowFound: true,
        overlayDetected: false,
        continueFound: false,
        clicked: false,
        windowTitle: targetTitle,
        recognizedText: [],
        error: "could not start exact-window capture: \(error)"
    ))
}
guard capture.terminationStatus == 0,
      let nativeImage = NSImage(contentsOf: captureURL) else {
    emit(OverlayResult(
        windowFound: true,
        overlayDetected: false,
        continueFound: false,
        clicked: false,
        windowTitle: targetTitle,
        recognizedText: [],
        error: "could not capture the exact BlueStacks window"
    ))
}
var proposedRect = NSRect(origin: .zero, size: nativeImage.size)
guard let image = nativeImage.cgImage(
    forProposedRect: &proposedRect,
    context: nil,
    hints: nil
) else {
    emit(OverlayResult(
        windowFound: true,
        overlayDetected: false,
        continueFound: false,
        clicked: false,
        windowTitle: targetTitle,
        recognizedText: [],
        error: "could not decode exact-window capture"
    ))
}

let request = VNRecognizeTextRequest()
request.recognitionLevel = .accurate
request.recognitionLanguages = ["zh-Hant", "en-US"]
request.usesLanguageCorrection = true
request.minimumTextHeight = 0.012

do {
    try VNImageRequestHandler(cgImage: image, orientation: .up).perform([request])
} catch {
    emit(OverlayResult(
        windowFound: true,
        overlayDetected: false,
        continueFound: false,
        clicked: false,
        windowTitle: targetTitle,
        recognizedText: [],
        error: "Vision OCR failed: \(error)"
    ))
}

let observations: [(VNRecognizedTextObservation, VNRecognizedText)] =
    (request.results ?? []).compactMap { observation in
        guard let candidate = observation.topCandidates(1).first else { return nil }
        return (observation, candidate)
    }
let texts = observations.map { $0.1.string }
let joined = normalized(texts.joined(separator: ""))
let overlayDetected = joined.contains(normalized("使用鍵盤和滑鼠進行遊戲"))
    || (joined.contains(normalized("鍵盤"))
        && joined.contains(normalized("滑鼠"))
        && joined.contains(normalized("遊戲")))
let continueMatches = observations.filter {
    normalized($0.1.string) == normalized("繼續") && $0.1.confidence >= 0.35
}
let continueFound = continueMatches.count == 1
var clicked = false

if shouldClick && overlayDetected && continueFound,
   let observation = continueMatches.first?.0 {
    let box = observation.boundingBox
    let point = CGPoint(
        x: bounds.minX + (box.midX * bounds.width),
        y: bounds.minY + ((1.0 - box.midY) * bounds.height)
    )
    raiseWindow(pid: pid_t(ownerPID.int32Value), title: targetTitle)
    let down = CGEvent(
        mouseEventSource: nil,
        mouseType: .leftMouseDown,
        mouseCursorPosition: point,
        mouseButton: .left
    )
    let up = CGEvent(
        mouseEventSource: nil,
        mouseType: .leftMouseUp,
        mouseCursorPosition: point,
        mouseButton: .left
    )
    down?.post(tap: .cghidEventTap)
    up?.post(tap: .cghidEventTap)
    clicked = down != nil && up != nil
}

emit(OverlayResult(
    windowFound: true,
    overlayDetected: overlayDetected,
    continueFound: continueFound,
    clicked: clicked,
    windowTitle: targetTitle,
    recognizedText: texts,
    error: nil
))
