import ApplicationServices
import CoreGraphics
import Foundation

func fail(_ message: String) -> Never {
    FileHandle.standardError.write((message + "\n").data(using: .utf8)!)
    exit(2)
}

let args = CommandLine.arguments
guard args.count >= 2 else { fail("Missing action") }

let action = args[1]
let displayBounds = CGDisplayBounds(CGMainDisplayID())

func ensureAccessibilityTrusted() {
    let promptKey = kAXTrustedCheckOptionPrompt.takeUnretainedValue() as String
    let options = [promptKey: true] as CFDictionary
    guard AXIsProcessTrustedWithOptions(options) else {
        fail("辅助功能未授权：请在系统弹窗或 系统设置 > 隐私与安全性 > 辅助功能 中允许 Her Desktop Agent，然后重新尝试操作。")
    }
}

ensureAccessibilityTrusted()

func pointFromNormalized(_ xRaw: String?, _ yRaw: String?) -> CGPoint {
    guard let xRaw, let yRaw, let xNorm = Double(xRaw), let yNorm = Double(yRaw) else {
        fail("Missing normalized coordinates")
    }
    let clampedX = max(0.0, min(1000.0, xNorm)) / 1000.0
    let clampedY = max(0.0, min(1000.0, yNorm)) / 1000.0
    return CGPoint(
        x: displayBounds.minX + clampedX * displayBounds.width,
        y: displayBounds.minY + clampedY * displayBounds.height
    )
}

func postMouse(_ type: CGEventType, _ point: CGPoint, _ button: CGMouseButton) {
    guard let event = CGEvent(
        mouseEventSource: nil,
        mouseType: type,
        mouseCursorPosition: point,
        mouseButton: button
    ) else {
        fail("Unable to create mouse event")
    }
    event.post(tap: .cghidEventTap)
}

func mouseDown(_ point: CGPoint, button: CGMouseButton) {
    postMouse(button == .right ? .rightMouseDown : .leftMouseDown, point, button)
}

func mouseUp(_ point: CGPoint, button: CGMouseButton) {
    postMouse(button == .right ? .rightMouseUp : .leftMouseUp, point, button)
}

func mouseDrag(_ point: CGPoint, button: CGMouseButton) {
    postMouse(button == .right ? .rightMouseDragged : .leftMouseDragged, point, button)
}

func click(_ point: CGPoint, button: CGMouseButton) {
    let down: CGEventType = button == .right ? .rightMouseDown : .leftMouseDown
    let up: CGEventType = button == .right ? .rightMouseUp : .leftMouseUp
    postMouse(.mouseMoved, point, button)
    usleep(20_000)
    postMouse(down, point, button)
    usleep(40_000)
    postMouse(up, point, button)
}

func drag(from start: CGPoint, to end: CGPoint) {
    postMouse(.mouseMoved, start, .left)
    usleep(30_000)
    postMouse(.leftMouseDown, start, .left)
    usleep(80_000)
    let steps = 16
    for step in 1...steps {
        let progress = CGFloat(step) / CGFloat(steps)
        let point = CGPoint(
            x: start.x + (end.x - start.x) * progress,
            y: start.y + (end.y - start.y) * progress
        )
        postMouse(.leftMouseDragged, point, .left)
        usleep(18_000)
    }
    usleep(60_000)
    postMouse(.leftMouseUp, end, .left)
}

func scroll(_ amount: Int32) {
    guard let event = CGEvent(
        scrollWheelEvent2Source: nil,
        units: .line,
        wheelCount: 1,
        wheel1: amount,
        wheel2: 0,
        wheel3: 0
    ) else {
        fail("Unable to create scroll event")
    }
    event.post(tap: .cghidEventTap)
}

func key(_ keyCode: CGKeyCode, flags: CGEventFlags = []) {
    guard let down = CGEvent(keyboardEventSource: nil, virtualKey: keyCode, keyDown: true),
          let up = CGEvent(keyboardEventSource: nil, virtualKey: keyCode, keyDown: false) else {
        fail("Unable to create keyboard event")
    }
    down.flags = flags
    up.flags = flags
    down.post(tap: .cghidEventTap)
    usleep(30_000)
    up.post(tap: .cghidEventTap)
}

switch action {
case "move":
    postMouse(.mouseMoved, pointFromNormalized(args.count > 2 ? args[2] : nil, args.count > 3 ? args[3] : nil), .left)
case "left_click", "click":
    click(pointFromNormalized(args.count > 2 ? args[2] : nil, args.count > 3 ? args[3] : nil), button: .left)
case "double_click":
    let point = pointFromNormalized(args.count > 2 ? args[2] : nil, args.count > 3 ? args[3] : nil)
    click(point, button: .left)
    usleep(120_000)
    click(point, button: .left)
case "right_click":
    click(pointFromNormalized(args.count > 2 ? args[2] : nil, args.count > 3 ? args[3] : nil), button: .right)
case "left_down":
    mouseDown(pointFromNormalized(args.count > 2 ? args[2] : nil, args.count > 3 ? args[3] : nil), button: .left)
case "left_drag":
    mouseDrag(pointFromNormalized(args.count > 2 ? args[2] : nil, args.count > 3 ? args[3] : nil), button: .left)
case "left_up":
    mouseUp(pointFromNormalized(args.count > 2 ? args[2] : nil, args.count > 3 ? args[3] : nil), button: .left)
case "right_down":
    mouseDown(pointFromNormalized(args.count > 2 ? args[2] : nil, args.count > 3 ? args[3] : nil), button: .right)
case "right_drag":
    mouseDrag(pointFromNormalized(args.count > 2 ? args[2] : nil, args.count > 3 ? args[3] : nil), button: .right)
case "right_up":
    mouseUp(pointFromNormalized(args.count > 2 ? args[2] : nil, args.count > 3 ? args[3] : nil), button: .right)
case "drag":
    let start = pointFromNormalized(args.count > 2 ? args[2] : nil, args.count > 3 ? args[3] : nil)
    let end = pointFromNormalized(args.count > 4 ? args[4] : nil, args.count > 5 ? args[5] : nil)
    drag(from: start, to: end)
case "scroll":
    let direction = args.count > 2 ? args[2] : "down"
    let repeats = max(1, min(20, Int(args.count > 3 ? args[3] : "6") ?? 6))
    let unit: Int32 = direction == "up" ? 3 : -3
    for _ in 0..<repeats {
        scroll(unit)
        usleep(25_000)
    }
case "paste":
    key(9, flags: .maskCommand)
case "key":
    let raw = (args.count > 2 ? args[2] : "").lowercased()
    let keyCodes: [String: CGKeyCode] = [
        "return": 36,
        "enter": 36,
        "tab": 48,
        "space": 49,
        "delete": 51,
        "backspace": 51,
        "escape": 53,
        "esc": 53,
        "left": 123,
        "right": 124,
        "down": 125,
        "up": 126
    ]
    guard let code = keyCodes[raw] else {
        fail("Unsupported key: \(raw)")
    }
    key(code)
default:
    fail("Unsupported action: \(action)")
}

let result = [
    "ok": true,
    "action": action,
    "display": [
        "x": displayBounds.minX,
        "y": displayBounds.minY,
        "width": displayBounds.width,
        "height": displayBounds.height,
    ],
] as [String : Any]

let data = try JSONSerialization.data(withJSONObject: result, options: [])
FileHandle.standardOutput.write(data)
