import ApplicationServices
import Foundation

struct Candidate {
    let role: String
    let exactIdentifier: String
    let selected: Bool?
    let rect: CGRect?
    let hasParentWindow: Bool
}

func selectUnique(_ candidates: [Candidate], expected: String) -> Candidate? {
    let matches = candidates.filter {
        $0.role == (kAXTextFieldRole as String) && $0.exactIdentifier == expected
    }
    guard matches.count == 1,
          matches[0].hasParentWindow,
          let rect = matches[0].rect,
          rect.origin.x.isFinite,
          rect.origin.y.isFinite,
          rect.width.isFinite,
          rect.height.isFinite,
          rect.width > 0,
          rect.height > 0 else { return nil }
    return matches[0]
}

func selfTest() -> Int32 {
    let rect = CGRect(x: 10, y: 20, width: 30, height: 40)
    let good = Candidate(role: kAXTextFieldRole as String, exactIdentifier: "fixture", selected: true, rect: rect, hasParentWindow: true)
    guard selectUnique([good], expected: "fixture") != nil,
          selectUnique([good, good], expected: "fixture") == nil,
          selectUnique([good], expected: "fix") == nil,
          selectUnique([Candidate(role: good.role, exactIdentifier: good.exactIdentifier, selected: true, rect: .zero, hasParentWindow: true)], expected: "fixture") == nil,
          selectUnique([Candidate(role: good.role, exactIdentifier: good.exactIdentifier, selected: true, rect: rect, hasParentWindow: false)], expected: "fixture") == nil else { return 1 }
    print("self_test=PASS exact_unique_binding=true minimal_output=true")
    return 0
}

if CommandLine.arguments == [CommandLine.arguments[0], "--self-test"] {
    exit(selfTest())
}

guard CommandLine.arguments.count == 3,
      let pid = pid_t(CommandLine.arguments[1]),
      !CommandLine.arguments[2].isEmpty else {
    fputs("usage: AXInspect pid exactSourceIdentifier\n", stderr)
    exit(64)
}
let expected = CommandLine.arguments[2]

enum AXFailure: Error {
    case operation(String, AXError)
}

func attribute(_ element: AXUIElement, _ name: String) throws -> CFTypeRef? {
    var value: CFTypeRef?
    let error = AXUIElementCopyAttributeValue(element, name as CFString, &value)
    if error == .success { return value }
    if error == .noValue || error == .attributeUnsupported { return nil }
    throw AXFailure.operation(name, error)
}

func stringValue(_ value: CFTypeRef?) -> String? {
    guard let value, CFGetTypeID(value) == CFStringGetTypeID() else { return nil }
    return value as? String
}

func boolValue(_ value: CFTypeRef?) -> Bool? {
    guard let value, CFGetTypeID(value) == CFBooleanGetTypeID() else { return nil }
    return CFBooleanGetValue((value as! CFBoolean))
}

func rect(_ element: AXUIElement) throws -> CGRect? {
    guard let pv = try attribute(element, kAXPositionAttribute), CFGetTypeID(pv) == AXValueGetTypeID(),
          let sv = try attribute(element, kAXSizeAttribute), CFGetTypeID(sv) == AXValueGetTypeID() else { return nil }
    var point = CGPoint.zero
    var size = CGSize.zero
    guard AXValueGetValue(pv as! AXValue, .cgPoint, &point),
          AXValueGetValue(sv as! AXValue, .cgSize, &size) else { return nil }
    return CGRect(origin: point, size: size)
}

func parentWindow(_ element: AXUIElement) throws -> AXUIElement? {
    var current = element
    for _ in 0..<16 {
        if stringValue(try attribute(current, kAXRoleAttribute)) == (kAXWindowRole as String) {
            return current
        }
        guard let parent = try attribute(current, kAXParentAttribute),
              CFGetTypeID(parent) == AXUIElementGetTypeID() else { return nil }
        current = parent as! AXUIElement
    }
    return nil
}

func collectMatches(_ element: AXUIElement, depth: Int = 0, into matches: inout [AXUIElement]) throws {
    let role = stringValue(try attribute(element, kAXRoleAttribute))
    if role == (kAXTextFieldRole as String),
       stringValue(try attribute(element, kAXValueAttribute)) == expected {
        matches.append(element)
    }
    guard depth < 14,
          let children = try attribute(element, kAXChildrenAttribute) as? [AXUIElement] else { return }
    for child in children { try collectMatches(child, depth: depth + 1, into: &matches) }
}

do {
    var matches: [AXUIElement] = []
    try collectMatches(AXUIElementCreateApplication(pid), into: &matches)
    let descriptors = try matches.map { element in
        Candidate(
            role: stringValue(try attribute(element, kAXRoleAttribute)) ?? "",
            exactIdentifier: stringValue(try attribute(element, kAXValueAttribute)) ?? "",
            selected: boolValue(try attribute(element, kAXSelectedAttribute)),
            rect: try rect(element),
            hasParentWindow: try parentWindow(element) != nil
        )
    }
    guard matches.count == 1,
          selectUnique(descriptors, expected: expected) != nil,
          let targetRect = try rect(matches[0]),
          let window = try parentWindow(matches[0]),
          let windowRect = try rect(window),
          targetRect.origin.x.isFinite, targetRect.origin.y.isFinite,
          targetRect.width.isFinite, targetRect.height.isFinite,
          targetRect.width > 0, targetRect.height > 0 else {
        fputs("exact source binding failed\n", stderr)
        exit(67)
    }
    let selected = descriptors[0].selected
    print("target=<QA_SOURCE> role=AXTextField selected=\(selected.map(String.init) ?? "unknown")")
    print("target_rect=\(targetRect.origin.x),\(targetRect.origin.y),\(targetRect.width),\(targetRect.height)")
    print("parent_window=<QA_WINDOW> role=AXWindow")
    print("window_rect=\(windowRect.origin.x),\(windowRect.origin.y),\(windowRect.width),\(windowRect.height)")
} catch {
    fputs("AX inspection failed\n", stderr)
    exit(70)
}
