import AppKit
import Foundation
import AlignerCore

enum Round1SnapshotDump {
    static func run(prettyPrinted: Bool) throws {
        let spaceEnumerator = try SkyLightSpaceEnumerator()
        let displays = try spaceEnumerator.displays()
        let fullscreenSpaceIDs = Set(
            displays
                .flatMap(\.spaces)
                .filter { $0.type == .fullscreen }
                .map(\.id)
        )
        let windowService = CGWindowAXWindowService(
            spaceIDsByWindowIDProvider: spaceEnumerator.spaceIDsByWindowID(windowIDs:),
            fullscreenSpaceIDs: fullscreenSpaceIDs,
        )
        let windows = try windowService.allWindows()
        let snapshot = QuickSwitchSnapshotBuilder.snapshot(displays: displays, windows: windows)
        let json = try JSONSerialization.data(
            withJSONObject: dictionary(
                displays: displays,
                windows: windows,
                snapshot: snapshot
            ),
            options: prettyPrinted ? [.prettyPrinted, .sortedKeys] : [.sortedKeys]
        )

        FileHandle.standardOutput.write(json)
        FileHandle.standardOutput.write(Data("\n".utf8))
    }

    private static func dictionary(
        displays: [AlignerDisplay],
        windows: [AlignerWindow],
        snapshot: QuickSwitchSnapshot
    ) -> [String: Any] {
        [
            "generatedAt": ISO8601DateFormatter().string(from: Date()),
            "displayCount": displays.count,
            "spaceCount": displays.flatMap(\.spaces).count,
            "candidateWindowCount": windows.count,
            "snapshotAppCount": snapshot.appGroups.count,
            "snapshotWindowCount": snapshot.appGroups.flatMap(\.windows).count,
            "displays": displays.map(displayDictionary),
            "windows": windows.map(windowDictionary),
            "appGroups": snapshot.appGroups.map(appGroupDictionary)
        ]
    }

    private static func displayDictionary(_ display: AlignerDisplay) -> [String: Any] {
        [
            "uuid": display.uuid,
            "physical": display.physical,
            "spaces": display.spaces.map(spaceDictionary)
        ]
    }

    private static func spaceDictionary(_ space: AlignerSpace) -> [String: Any] {
        [
            "id": space.id,
            "type": string(from: space.type),
            "displayUUID": space.displayUUID,
            "index": space.index
        ]
    }

    private static func appGroupDictionary(_ group: QuickSwitchAppGroup) -> [String: Any] {
        [
            "app": appDictionary(group.app),
            "windows": group.windows.map { item in
                [
                    "primarySpaceID": item.primarySpaceID as Any,
                    "window": windowDictionary(item.window)
                ]
            }
        ]
    }

    private static func windowDictionary(_ window: AlignerWindow) -> [String: Any] {
        var result = [
            "id": window.id,
            "identifierSource": string(from: window.identifierSource),
            "isSyntheticWindowID": MinimizedWindowFallbackPolicy.isSyntheticWindow(window),
            "titleHash": DevelopmentDiagnostics.stableFingerprint(window.title),
            "titleLength": window.title.count,
            "titleIsEmpty": window.title.isEmpty,
            "isMinimized": window.isMinimized,
            "isFullscreen": window.isFullscreen,
            "isGhost": window.isGhost,
            "frame": frameDictionary(window.frame) as Any,
            "spaceIDs": window.spaceIDs,
            "app": appDictionary(window.app)
        ] as [String: Any]
        if DevelopmentDiagnostics.includesSensitiveFields {
            result["title"] = window.title
        }
        return result
    }

    private static func frameDictionary(_ frame: CGRect?) -> Any {
        guard let frame else { return NSNull() }

        return [
            "x": frame.origin.x,
            "y": frame.origin.y,
            "width": frame.size.width,
            "height": frame.size.height
        ]
    }

    private static func appDictionary(_ app: AlignerApp) -> [String: Any] {
        let pathSummary = DevelopmentDiagnostics.pathSummary(app.executablePath)
        var result = [
            "bundleIdentifier": app.bundleIdentifier,
            "name": app.name,
            "category": app.category.rawValue,
            "isHidden": app.isHidden,
            "executablePathPresent": pathSummary["present"] == "true",
            "executablePathKind": pathSummary["locationKind"] ?? "missing",
            "executablePathBasename": pathSummary["basename"] ?? "",
            "executablePathHash": pathSummary["fingerprint"] ?? DevelopmentDiagnostics.stableFingerprint(""),
            "processIdentifier": app.processIdentifier as Any
        ] as [String: Any]
        if DevelopmentDiagnostics.includesSensitiveFields {
            result["executablePath"] = app.executablePath as Any
        }
        return result
    }

    private static func string(from type: AlignerSpaceType) -> String {
        switch type {
        case .user:
            return "user"
        case .fullscreen:
            return "fullscreen"
        case .system:
            return "system"
        case .unknown(let rawValue):
            return "unknown(\(rawValue))"
        }
    }

    private static func string(from source: AlignerWindow.IdentifierSource) -> String {
        switch source {
        case .cgWindow:
            return "cgWindow"
        case .syntheticAX:
            return "syntheticAX"
        }
    }
}

struct Round1SnapshotDumpOptions {
    let enabled: Bool
    let prettyPrinted: Bool

    static func parse(arguments: [String]) -> Round1SnapshotDumpOptions {
        Round1SnapshotDumpOptions(
            enabled: arguments.contains("--round01-dump-window-snapshot"),
            prettyPrinted: arguments.contains("--round01-dump-window-snapshot-pretty")
        )
    }
}
