#!/usr/bin/swift
// ui-tree-dumper.swift
// Reads the iOS Simulator accessibility tree via macOS AX APIs (host-side)
// Usage: swift ui-tree-dumper.swift [max-depth]
// Output: JSON array of accessibility elements

import Cocoa
import Foundation

struct AXNode: Codable {
    let role: String
    let title: String?
    let value: String?
    let description: String?
    let identifier: String?
    let frame: [String: Double]
    let enabled: Bool
    let focused: Bool
    let children: [AXNode]
}

func getAXValue(_ element: AXUIElement, _ attr: String) -> AnyObject? {
    var value: AnyObject?
    AXUIElementCopyAttributeValue(element, attr as CFString, &value)
    return value
}

func getString(_ element: AXUIElement, _ attr: String) -> String? {
    return getAXValue(element, attr) as? String
}

func getBool(_ element: AXUIElement, _ attr: String) -> Bool {
    return (getAXValue(element, attr) as? Bool) ?? false
}

func getFrame(_ element: AXUIElement) -> [String: Double] {
    var pos = CGPoint.zero
    var size = CGSize.zero
    var posValue: AnyObject?
    var sizeValue: AnyObject?
    AXUIElementCopyAttributeValue(element, kAXPositionAttribute as CFString, &posValue)
    AXUIElementCopyAttributeValue(element, kAXSizeAttribute as CFString, &sizeValue)
    if let pv = posValue { AXValueGetValue(pv as! AXValue, .cgPoint, &pos) }
    if let sv = sizeValue { AXValueGetValue(sv as! AXValue, .cgSize, &size) }
    return ["x": Double(pos.x), "y": Double(pos.y), "w": Double(size.width), "h": Double(size.height)]
}

func dumpElement(_ element: AXUIElement, depth: Int, maxDepth: Int) -> AXNode? {
    guard depth < maxDepth else { return nil }

    let role = getString(element, kAXRoleAttribute) ?? "unknown"
    let title = getString(element, kAXTitleAttribute)
    let value = getString(element, kAXValueAttribute)
    let desc = getString(element, kAXDescriptionAttribute)
    let identifier = getString(element, kAXIdentifierAttribute)
    let enabled = getBool(element, kAXEnabledAttribute)
    let focused = getBool(element, kAXFocusedAttribute)
    let frame = getFrame(element)

    var children: [AXNode] = []
    var childrenRef: AnyObject?
    AXUIElementCopyAttributeValue(element, kAXChildrenAttribute as CFString, &childrenRef)
    if let kids = childrenRef as? [AXUIElement] {
        for kid in kids {
            if let child = dumpElement(kid, depth: depth + 1, maxDepth: maxDepth) {
                children.append(child)
            }
        }
    }

    // Skip empty nodes with no useful info
    if title == nil && value == nil && desc == nil && identifier == nil && children.isEmpty && role == "AXGroup" {
        return nil
    }

    return AXNode(
        role: role,
        title: title,
        value: value,
        description: desc,
        identifier: identifier,
        frame: frame,
        enabled: enabled,
        focused: focused,
        children: children
    )
}

func findSimulatorWindow() -> AXUIElement? {
    let apps = NSWorkspace.shared.runningApplications
    for app in apps {
        if app.bundleIdentifier == "com.apple.iphonesimulator" {
            let axApp = AXUIElementCreateApplication(app.processIdentifier)
            var windows: AnyObject?
            AXUIElementCopyAttributeValue(axApp, kAXWindowsAttribute as CFString, &windows)
            if let wins = windows as? [AXUIElement], let first = wins.first {
                return first
            }
        }
    }
    return nil
}

// Main
let maxDepth = CommandLine.arguments.count > 1 ? Int(CommandLine.arguments[1]) ?? 10 : 10

guard let simWindow = findSimulatorWindow() else {
    let error = ["error": "Simulator not running or no window found"]
    let data = try! JSONSerialization.data(withJSONObject: error)
    FileHandle.standardOutput.write(data)
    exit(1)
}

if let tree = dumpElement(simWindow, depth: 0, maxDepth: maxDepth) {
    let encoder = JSONEncoder()
    encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
    if let data = try? encoder.encode(tree) {
        FileHandle.standardOutput.write(data)
    }
} else {
    print("{\"error\": \"Could not parse accessibility tree\"}")
}
