import AppKit
import XCTest
@testable import AlignerCore

final class AlignerCoreTests: XCTestCase {
    func testAppIdentity() {
        XCTAssertEqual(AlignerAppInfo.name, "Aligner")
        XCTAssertEqual(AlignerAppInfo.bundleIdentifier, "com.ar.Aligner")
        XCTAssertEqual(AlignerAppInfo.minimumMacOSVersion, "26.0")
    }

    func testRound0OnlyDefinesQuickSwitchEntryPoint() {
        XCTAssertEqual(EntryPoint.allCases, [.quickSwitch])
    }

    func testTriggerDefaultsUseAltTabForQuickSwitch() {
        XCTAssertEqual(TriggerDefaults.quickSwitchShortcut.keyCode, TriggerKeyCode.tab)
        XCTAssertEqual(TriggerDefaults.quickSwitchShortcut.modifiers, [.option])
    }

    func testTriggerEventPolicyAllowsFutureCustomQuickSwitchShortcut() {
        let customShortcut = KeyboardShortcut(keyCode: 12, modifiers: [.control, .option])

        XCTAssertEqual(
            TriggerEventPolicy.decision(
                for: customShortcut,
                configuredQuickSwitch: customShortcut,
                overlayVisible: false
            ),
            .quickSwitch(consumeEvent: false)
        )
    }

    func testTriggerEventPolicyOnlyConsumesQuickSwitchWhenOverlayVisible() {
        let shortcut = TriggerDefaults.quickSwitchShortcut

        XCTAssertEqual(
            TriggerEventPolicy.decision(for: shortcut, overlayVisible: false),
            .quickSwitch(consumeEvent: false)
        )
        XCTAssertEqual(
            TriggerEventPolicy.decision(for: shortcut, overlayVisible: true),
            .quickSwitch(consumeEvent: true)
        )
    }

    func testTriggerEventPolicyCanConsumeInitialQuickSwitchForRound01FullTakeover() {
        XCTAssertEqual(
            TriggerEventPolicy.decision(
                for: TriggerDefaults.quickSwitchShortcut,
                overlayVisible: false,
                consumeInitialQuickSwitch: true
            ),
            .quickSwitch(consumeEvent: true)
        )
    }

    func testTriggerEventPolicyNeverConsumesForceQuit() {
        XCTAssertEqual(
            TriggerEventPolicy.decision(for: TriggerDefaults.forceQuitShortcut, overlayVisible: true),
            .passThrough
        )
        XCTAssertEqual(
            TriggerEventPolicy.decision(
                for: TriggerDefaults.forceQuitShortcut,
                overlayVisible: false,
                consumeInitialQuickSwitch: true
            ),
            .passThrough
        )
    }

    @MainActor
    func testManualTriggerServiceOnlyFiresWhileRunning() {
        let service = ManualTriggerService()
        var fireCount = 0
        service.onQuickSwitch = {
            fireCount += 1
        }

        service.triggerQuickSwitch()
        XCTAssertEqual(fireCount, 0)

        service.start()
        service.triggerQuickSwitch()
        XCTAssertEqual(fireCount, 1)

        service.stop()
        service.triggerQuickSwitch()
        XCTAssertEqual(fireCount, 1)
    }

    func testWindowDisplayPolicyShowsMinimizedWindows() {
        let app = AlignerApp(bundleIdentifier: "com.apple.TextEdit", name: "TextEdit", category: .documentNotes)
        let window = AlignerWindow(id: 1, app: app, title: "Notes", isMinimized: true)

        XCTAssertTrue(WindowDisplayPolicy.shouldDisplay(window))
    }

    func testWindowDisplayPolicyHidesHiddenAppsAndGhostWindows() {
        let hiddenApp = AlignerApp(bundleIdentifier: "com.example.Hidden", name: "Hidden", category: .generic, isHidden: true)
        let visibleApp = AlignerApp(bundleIdentifier: "com.example.Visible", name: "Visible", category: .generic)

        XCTAssertFalse(WindowDisplayPolicy.shouldDisplay(AlignerWindow(id: 2, app: hiddenApp, title: "Hidden")))
        XCTAssertFalse(WindowDisplayPolicy.shouldDisplay(AlignerWindow(id: 3, app: visibleApp, title: "Ghost", isGhost: true)))
    }

    func testWindowDisplayPolicyKeepsFullscreenWhenRunningAppReportsHidden() {
        let hiddenApp = AlignerApp(bundleIdentifier: "com.example.Fullscreen", name: "Fullscreen", category: .generic, isHidden: true)
        let window = AlignerWindow(id: 4, app: hiddenApp, title: "Fullscreen", isFullscreen: true)

        XCTAssertTrue(WindowDisplayPolicy.shouldDisplay(window))
    }

    func testWindowEnumerationPolicyIncludesRegularStandardWindows() {
        let window = WindowEnumerationPolicy.window(from: makeWindowRecord())

        XCTAssertEqual(window?.id, 100)
        XCTAssertEqual(window?.title, "Document")
        XCTAssertFalse(window?.isMinimized ?? true)
    }

    func testWindowEnumerationPolicyKeepsMinimizedWindowsDespiteSmallBounds() {
        let record = makeWindowRecord(size: .zero, isMinimized: true)

        XCTAssertTrue(WindowEnumerationPolicy.shouldInclude(record))
        XCTAssertTrue(WindowEnumerationPolicy.window(from: record)?.isMinimized ?? false)
    }

    func testMinimizedWindowFallbackPolicyCreatesSyntheticIDsOnlyForAXOnlyMinimizedWindows() {
        XCTAssertTrue(MinimizedWindowFallbackPolicy.shouldCreateAXOnlyFallback(hasCGWindowID: false, isMinimized: true))
        XCTAssertFalse(MinimizedWindowFallbackPolicy.shouldCreateAXOnlyFallback(hasCGWindowID: true, isMinimized: true))
        XCTAssertFalse(MinimizedWindowFallbackPolicy.shouldCreateAXOnlyFallback(hasCGWindowID: false, isMinimized: false))

        let windowID = MinimizedWindowFallbackPolicy.syntheticWindowID(
            processIdentifier: 42,
            windowIndex: 3,
            title: "Notes",
            subroleDescription: "standard"
        )

        XCTAssertTrue(MinimizedWindowFallbackPolicy.isSyntheticWindow(AlignerWindow(
            id: windowID,
            app: makeApp(),
            title: "Notes",
            identifierSource: .syntheticAX,
            isMinimized: true
        )))
        XCTAssertFalse(MinimizedWindowFallbackPolicy.isSyntheticWindow(AlignerWindow(
            id: windowID,
            app: makeApp(),
            title: "Notes",
            identifierSource: .cgWindow,
            isMinimized: true
        )))
        XCTAssertEqual(
            windowID,
            MinimizedWindowFallbackPolicy.syntheticWindowID(
                processIdentifier: 42,
                windowIndex: 3,
                title: "Notes",
                subroleDescription: "standard"
            )
        )
        XCTAssertNotEqual(
            windowID,
            MinimizedWindowFallbackPolicy.syntheticWindowID(
                processIdentifier: 42,
                windowIndex: 4,
                title: "Notes",
                subroleDescription: "standard"
            )
        )
    }

    func testMinimizedWindowFallbackPolicyAvoidsOccupiedSyntheticIDCollisions() {
        let windowID = MinimizedWindowFallbackPolicy.syntheticWindowID(
            processIdentifier: 42,
            windowIndex: 3,
            title: "Notes",
            subroleDescription: "standard"
        )
        let replacement = MinimizedWindowFallbackPolicy.syntheticWindowID(
            processIdentifier: 42,
            windowIndex: 3,
            title: "Notes",
            subroleDescription: "standard",
            occupiedWindowIDs: [windowID]
        )

        XCTAssertGreaterThanOrEqual(replacement, MinimizedWindowFallbackPolicy.syntheticWindowIDMarker)
        XCTAssertNotEqual(replacement, windowID)
    }

    func testWindowEnumerationPolicyKeepsFullscreenWindowsDespiteSmallBounds() {
        let record = makeWindowRecord(size: .zero, isFullscreen: true, spaceIDs: [42])
        let window = WindowEnumerationPolicy.window(from: record)

        XCTAssertEqual(window?.isFullscreen, true)
        XCTAssertEqual(window?.spaceIDs, [42])
    }

    func testWindowEnumerationPolicyKeepsFullscreenWhenRunningAppReportsHidden() {
        let record = makeWindowRecord(appHidden: true, isFullscreen: true, spaceIDs: [42])
        let window = WindowEnumerationPolicy.window(from: record)

        XCTAssertEqual(window?.id, 100)
        XCTAssertEqual(window?.isFullscreen, true)
    }

    func testWindowEnumerationPolicyExcludesCGOnlyOffscreenGhostWindows() {
        let record = makeWindowRecord(
            title: "Closed Document.md",
            hasAXBacking: false,
            isOnscreen: false
        )

        XCTAssertFalse(WindowEnumerationPolicy.shouldInclude(record))
        XCTAssertNil(WindowEnumerationPolicy.window(from: record))
    }

    func testWindowEnumerationPolicyKeepsCGOnlyOffscreenWindowsWithKnownSpaceIDs() {
        let record = makeWindowRecord(
            title: "Other Space Document.md",
            hasAXBacking: false,
            isOnscreen: false,
            spaceIDs: [5]
        )

        let window = WindowEnumerationPolicy.window(from: record)

        XCTAssertEqual(window?.title, "Other Space Document.md")
        XCTAssertEqual(window?.spaceIDs, [5])
    }

    func testWindowEnumerationPolicyKeepsAXBackedOffscreenWindows() {
        let record = makeWindowRecord(
            title: "Other Space Document.md",
            hasAXBacking: true,
            isOnscreen: false,
            spaceIDs: [5]
        )
        let window = WindowEnumerationPolicy.window(from: record)

        XCTAssertEqual(window?.title, "Other Space Document.md")
    }

    func testWindowEnumerationPolicyKeepsOnscreenCGOnlyWindows() {
        let record = makeWindowRecord(
            title: "Visible Document.md",
            hasAXBacking: false,
            isOnscreen: true,
            spaceIDs: [5]
        )
        let window = WindowEnumerationPolicy.window(from: record)

        XCTAssertEqual(window?.title, "Visible Document.md")
    }

    func testWindowEnumerationPolicyKeepsKnownCGOnlyOffscreenMainWindows() {
        let hbuilderApp = AlignerApp(
            bundleIdentifier: "io.dcloud.HBuilderX",
            name: "HBuilderX",
            category: .editorIDE
        )
        let record = WindowEnumerationRecord(
            id: 55_388,
            app: hbuilderApp,
            title: "lm-app - HBuilder X 5.07",
            size: CGSize(width: 1048, height: 796),
            subrole: .standard,
            hasAXBacking: false,
            isOnscreen: false
        )
        let emptyTitleRecord = WindowEnumerationRecord(
            id: 55_389,
            app: hbuilderApp,
            title: "",
            size: CGSize(width: 1048, height: 796),
            subrole: .standard,
            hasAXBacking: false,
            isOnscreen: false
        )
        let tinyRecord = WindowEnumerationRecord(
            id: 55_390,
            app: hbuilderApp,
            title: "HBuilderX Utility",
            size: CGSize(width: 80, height: 40),
            subrole: .standard,
            hasAXBacking: false,
            isOnscreen: false
        )

        XCTAssertEqual(WindowEnumerationPolicy.window(from: record)?.title, "lm-app - HBuilder X 5.07")
        XCTAssertNil(WindowEnumerationPolicy.window(from: emptyTitleRecord))
        XCTAssertNil(WindowEnumerationPolicy.window(from: tinyRecord))
    }

    func testWindowEnumerationPolicyExcludesClosedShottrCGPlaceholderButKeepsVisibleWindow() {
        let shottrApp = AlignerApp(
            bundleIdentifier: "cc.ffitch.shottr",
            name: "Shottr",
            category: .generic
        )
        let closedPlaceholderRecord = WindowEnumerationRecord(
            id: 78,
            app: shottrApp,
            activationPolicy: .accessory,
            title: "Shottr",
            size: CGSize(width: 1710, height: 997),
            subrole: .standard,
            hasAXBacking: false,
            isOnscreen: false
        )
        let visibleRecord = WindowEnumerationRecord(
            id: 79,
            app: shottrApp,
            activationPolicy: .accessory,
            title: "Shottr",
            size: CGSize(width: 1710, height: 997),
            subrole: .standard,
            hasAXBacking: false,
            isOnscreen: true
        )
        let utilityRecord = WindowEnumerationRecord(
            id: 102,
            app: shottrApp,
            activationPolicy: .accessory,
            title: "Colors",
            size: CGSize(width: 250, height: 397),
            subrole: .standard,
            hasAXBacking: false,
            isOnscreen: false
        )

        XCTAssertNil(WindowEnumerationPolicy.window(from: closedPlaceholderRecord))
        XCTAssertEqual(WindowEnumerationPolicy.window(from: visibleRecord)?.title, "Shottr")
        XCTAssertNil(WindowEnumerationPolicy.window(from: utilityRecord))
    }

    func testWindowEnumerationPolicyKeepsFinderCGOnlyOffscreenMainWindows() {
        let finderApp = AlignerApp(
            bundleIdentifier: "com.apple.finder",
            name: "访达",
            category: .finder
        )
        let record = WindowEnumerationRecord(
            id: 83_664,
            app: finderApp,
            title: "Screencapture",
            size: CGSize(width: 920, height: 436),
            subrole: .standard,
            hasAXBacking: false,
            isOnscreen: false
        )
        let emptyTitleRecord = WindowEnumerationRecord(
            id: 83_665,
            app: finderApp,
            title: "",
            size: CGSize(width: 920, height: 436),
            subrole: .standard,
            hasAXBacking: false,
            isOnscreen: false
        )
        let tinyRecord = WindowEnumerationRecord(
            id: 83_666,
            app: finderApp,
            title: "Finder Utility",
            size: CGSize(width: 80, height: 40),
            subrole: .standard,
            hasAXBacking: false,
            isOnscreen: false
        )

        XCTAssertEqual(WindowEnumerationPolicy.window(from: record)?.title, "Screencapture")
        XCTAssertNil(WindowEnumerationPolicy.window(from: emptyTitleRecord))
        XCTAssertNil(WindowEnumerationPolicy.window(from: tinyRecord))
    }

    func testWindowEnumerationPolicyKeepsMinimizedAndFullscreenCGOnlyOffscreenWindows() {
        let minimized = makeWindowRecord(
            title: "Minimized Document.md",
            isMinimized: true,
            hasAXBacking: false,
            isOnscreen: false
        )
        let fullscreen = makeWindowRecord(
            title: "Fullscreen Document.md",
            isFullscreen: true,
            hasAXBacking: false,
            isOnscreen: false,
            spaceIDs: [42]
        )

        XCTAssertEqual(WindowEnumerationPolicy.window(from: minimized)?.isMinimized, true)
        XCTAssertEqual(WindowEnumerationPolicy.window(from: fullscreen)?.isFullscreen, true)
    }

    func testWindowEnumerationPolicyExcludesVMarkClosedDocumentPlaceholders() {
        let record = WindowEnumerationRecord(
            id: 29966,
            app: AlignerApp(bundleIdentifier: "app.vmark", name: "VMark", category: .generic),
            title: "",
            identifierSource: .cgWindow,
            size: CGSize(width: 1434, height: 902),
            subrole: .standard,
            spaceIDs: [5]
        )

        XCTAssertFalse(WindowEnumerationPolicy.shouldInclude(record))
        XCTAssertNil(WindowEnumerationPolicy.window(from: record))
    }

    func testWindowEnumerationPolicyKeepsVMarkAXBackedUntitledWindows() {
        let record = WindowEnumerationRecord(
            id: 33973,
            app: AlignerApp(bundleIdentifier: "app.vmark", name: "VMark", category: .generic),
            title: "",
            identifierSource: .cgWindow,
            size: CGSize(width: 1434, height: 902),
            subrole: .standard,
            hasAXBacking: true,
            spaceIDs: [5]
        )

        let window = WindowEnumerationPolicy.window(from: record)

        XCTAssertEqual(window?.id, 33973)
        XCTAssertEqual(window?.title, "VMark Untitled")
    }

    func testWindowEnumerationPolicyKeepsVMarkOnscreenUntitledWindows() {
        let record = WindowEnumerationRecord(
            id: 33973,
            app: AlignerApp(bundleIdentifier: "app.vmark", name: "VMark", category: .generic),
            title: "",
            identifierSource: .cgWindow,
            size: CGSize(width: 1434, height: 902),
            subrole: .standard,
            isOnscreen: true,
            spaceIDs: [5]
        )

        let window = WindowEnumerationPolicy.window(from: record)

        XCTAssertEqual(window?.id, 33973)
        XCTAssertEqual(window?.title, "VMark Untitled")
    }

    func testWindowEnumerationPolicyExcludesNonProductWindows() {
        XCTAssertFalse(WindowEnumerationPolicy.shouldInclude(makeWindowRecord(id: 0)))
        XCTAssertFalse(WindowEnumerationPolicy.shouldInclude(makeWindowRecord(activationPolicy: .accessory)))
        XCTAssertFalse(WindowEnumerationPolicy.shouldInclude(makeWindowRecord(size: CGSize(width: 100, height: 60))))
        XCTAssertFalse(WindowEnumerationPolicy.shouldInclude(makeWindowRecord(size: CGSize(width: 120, height: 50))))
        XCTAssertFalse(WindowEnumerationPolicy.shouldInclude(makeWindowRecord(subrole: .unknown("AXSystemDialog"))))
        XCTAssertFalse(WindowEnumerationPolicy.shouldInclude(makeWindowRecord(appHidden: true)))
        XCTAssertFalse(WindowEnumerationPolicy.shouldInclude(makeWindowRecord(isGhost: true)))
        XCTAssertFalse(WindowEnumerationPolicy.shouldInclude(makeWindowRecord(isDesktopElement: true)))
        XCTAssertFalse(WindowEnumerationPolicy.shouldInclude(makeWindowRecord(isSystemCritical: true)))
        XCTAssertFalse(WindowEnumerationPolicy.shouldInclude(makeWindowRecord(isInteractable: false)))
    }

    func testWindowEnumerationPolicyIncludesDialogsSheetsAndFullscreenWindows() {
        let dialogRecord = makeWindowRecord(subrole: .dialog, isFullscreen: true, spaceIDs: [42])
        let sheetRecord = makeWindowRecord(id: 101, title: "Attached Sheet", subrole: .sheet)
        let untitledRecord = makeWindowRecord(id: 102, title: "")
        let dialogWindow = WindowEnumerationPolicy.window(from: dialogRecord)
        let sheetWindow = WindowEnumerationPolicy.window(from: sheetRecord)
        let untitledWindow = WindowEnumerationPolicy.window(from: untitledRecord)

        XCTAssertEqual(dialogWindow?.isFullscreen, true)
        XCTAssertEqual(dialogWindow?.spaceIDs, [42])
        XCTAssertEqual(sheetWindow?.title, "Attached Sheet")
        XCTAssertEqual(untitledWindow?.title, "Example Untitled")
    }

    func testWindowServiceProtocolExposesDelegateSubscription() {
        let service = MockWindowService()
        let delegate = MockWindowServiceDelegate()

        service.delegate = delegate
        service.startObserving()

        XCTAssertEqual(delegate.changes, [WindowChange(kind: .opened, windowID: 700)])
    }

    func testThumbnailPolicyPrefersScreenshotsForAllAppCategories() {
        XCTAssertEqual(ThumbnailPolicy.preferredStrategy(for: .finder), .screenshotPreferred)
        XCTAssertEqual(ThumbnailPolicy.preferredStrategy(for: .browser), .screenshotPreferred)
        XCTAssertEqual(ThumbnailPolicy.preferredStrategy(for: .terminal), .screenshotPreferred)
    }

    func testSkeletonThumbnailTemplatesCoverAllAppCategories() {
        let categories = AppCategory.allCases
        let templates = categories.map(SkeletonThumbnailTemplate.template(for:))

        XCTAssertEqual(templates.map(\.category), categories)
        XCTAssertTrue(templates.allSatisfy { $0.headerHeight > 0 })
        XCTAssertTrue(templates.allSatisfy { $0.contentRows > 0 })
    }

    @MainActor
    func testNativeSkeletonThumbnailProviderProducesValidImages() {
        let provider = NativeSkeletonThumbnailProvider()

        for category in AppCategory.allCases {
            let app = AlignerApp(bundleIdentifier: "com.example.\(category.rawValue)", name: category.rawValue, category: category)
            let image = provider.skeleton(for: app)

            XCTAssertEqual(image.size, NativeSkeletonThumbnailProvider.defaultSize)
            XCTAssertTrue(image.isValid)
        }
    }

    @MainActor
    func testNativeSkeletonThumbnailProviderCanOverlayTitle() {
        let provider = NativeSkeletonThumbnailProvider()
        let app = AlignerApp(bundleIdentifier: "com.example.Editor", name: "Editor", category: .editorIDE)

        let image = provider.withOverlayTitle("Project Notes", for: app)

        XCTAssertEqual(image.size, NativeSkeletonThumbnailProvider.defaultSize)
        XCTAssertTrue(image.isValid)
    }

    func testSpaceEnumerationPolicyMapsCGSSpaceTypes() {
        XCTAssertEqual(SpaceEnumerationPolicy.spaceType(fromCGSType: 0), .user)
        XCTAssertEqual(SpaceEnumerationPolicy.spaceType(fromCGSType: 4), .fullscreen)
        XCTAssertEqual(SpaceEnumerationPolicy.spaceType(fromCGSType: 99), .unknown(99))
    }

    func testSpaceEnumerationPolicyKeepsFullscreenSpacesWithContinuousIndexes() {
        let displays = SpaceEnumerationPolicy.normalizedDisplays(from: [
            SpaceEnumerationRecord(id: 10, cgsType: 0, displayUUID: "display-a"),
            SpaceEnumerationRecord(id: 11, cgsType: 4, displayUUID: "display-a"),
            SpaceEnumerationRecord(id: 12, cgsType: 0, displayUUID: "display-a")
        ])

        XCTAssertEqual(displays.count, 1)
        XCTAssertEqual(displays[0].spaces.map(\.id), [10, 11, 12])
        XCTAssertEqual(displays[0].spaces.map(\.type), [.user, .fullscreen, .user])
        XCTAssertEqual(displays[0].spaces.map(\.index), [1, 2, 3])
    }

    func testSpaceEnumerationPolicyTreatsMainAsSharedDisplayGroup() {
        let displays = SpaceEnumerationPolicy.normalizedDisplays(from: [
            SpaceEnumerationRecord(id: 20, cgsType: 0, displayUUID: "Main"),
            SpaceEnumerationRecord(id: 21, cgsType: 4, displayUUID: "")
        ])

        XCTAssertEqual(displays.count, 1)
        XCTAssertEqual(displays[0].uuid, SpaceEnumerationPolicy.sharedDisplayUUID)
        XCTAssertEqual(displays[0].spaces.map(\.displayUUID), [
            SpaceEnumerationPolicy.sharedDisplayUUID,
            SpaceEnumerationPolicy.sharedDisplayUUID
        ])
    }

    func testSpaceEnumerationPolicyMarksKnownPhysicalDisplays() {
        let displays = SpaceEnumerationPolicy.normalizedDisplays(
            from: [
                SpaceEnumerationRecord(id: 30, cgsType: 0, displayUUID: "physical-display"),
                SpaceEnumerationRecord(id: 31, cgsType: 0, displayUUID: "virtual-display")
            ],
            physicalDisplayUUIDs: ["physical-display"]
        )

        XCTAssertEqual(displays.map(\.uuid), ["physical-display", "virtual-display"])
        XCTAssertEqual(displays.map(\.physical), [true, false])
    }

    func testBundleIDAppCategorizerUsesBuiltInMappings() {
        let categorizer = BundleIDAppCategorizer()
        let chrome = AlignerApp(bundleIdentifier: "com.google.Chrome", name: "Chrome", category: .generic)

        XCTAssertEqual(categorizer.category(for: chrome), .browser)
    }

    @MainActor
    func testWorkspaceAppIconProviderUsesApplicationURLIcon() {
        let resolver = MockApplicationIconResolver()
        let provider = WorkspaceAppIconProvider(resolver: resolver)
        let app = AlignerApp(bundleIdentifier: "com.example.Found", name: "Found", category: .generic)

        resolver.applicationURLs[app.bundleIdentifier] = URL(fileURLWithPath: "/Applications/Found.app")

        let icon = provider.icon(for: app)

        XCTAssertTrue(icon === resolver.fileIcon)
        XCTAssertEqual(resolver.requestedIconPaths, ["/Applications/Found.app"])
    }

    @MainActor
    func testWorkspaceAppIconProviderFallsBackWhenApplicationIsMissing() {
        let resolver = MockApplicationIconResolver()
        let provider = WorkspaceAppIconProvider(resolver: resolver)
        let app = AlignerApp(bundleIdentifier: "com.example.Missing", name: "Missing", category: .generic)

        let icon = provider.icon(for: app)

        XCTAssertTrue(icon === resolver.fallbackIcon)
        XCTAssertTrue(resolver.requestedIconPaths.isEmpty)
    }

    @MainActor
    func testWorkspaceAppIconProviderUsesExecutablePathFallback() {
        let resolver = MockApplicationIconResolver()
        let provider = WorkspaceAppIconProvider(resolver: resolver)
        let app = AlignerApp(
            bundleIdentifier: "com.example.Unregistered",
            name: "Unregistered",
            category: .generic,
            executablePath: "/Applications/Unregistered.app/Contents/MacOS/Unregistered"
        )

        let icon = provider.icon(for: app)

        XCTAssertTrue(icon === resolver.fileIcon)
        XCTAssertEqual(resolver.requestedIconPaths, ["/Applications/Unregistered.app/Contents/MacOS/Unregistered"])
    }

    @MainActor
    func testWorkspaceAppIconProviderFallsBackWhenResolvedIconIsInvalid() {
        let resolver = MockApplicationIconResolver(fileIcon: NSImage(size: .zero))
        let provider = WorkspaceAppIconProvider(resolver: resolver)
        let app = AlignerApp(bundleIdentifier: "com.example.Invalid", name: "Invalid", category: .generic)

        resolver.applicationURLs[app.bundleIdentifier] = URL(fileURLWithPath: "/Applications/Invalid.app")

        let icon = provider.icon(for: app)

        XCTAssertTrue(icon === resolver.fallbackIcon)
        XCTAssertEqual(resolver.requestedIconPaths, ["/Applications/Invalid.app"])
    }

    @MainActor
    func testScreenCaptureKitScreenshotProviderReturnsCapturedImage() async throws {
        let capturer = MockWindowScreenshotCapturer(pointSize: NSSize(width: 12, height: 6))
        let provider = ScreenCaptureKitScreenshotProvider(
            capturer: capturer,
            permissionChecker: MockPermissionChecker()
        )
        let window = AlignerWindow(id: 321, app: makeApp(), title: "Captured")

        let image = try await provider.screenshot(for: window)

        XCTAssertEqual(capturer.requestedWindowIDs, [321])
        XCTAssertEqual(image.size, NSSize(width: 12, height: 6))
        XCTAssertTrue(image.isValid)
    }

    @MainActor
    func testScreenCaptureKitScreenshotProviderPropagatesCaptureFailure() async {
        let capturer = MockWindowScreenshotCapturer(error: ScreenshotProviderError.windowNotFound(404))
        let provider = ScreenCaptureKitScreenshotProvider(
            capturer: capturer,
            permissionChecker: MockPermissionChecker()
        )
        let window = AlignerWindow(id: 404, app: makeApp(), title: "Missing")

        do {
            _ = try await provider.screenshot(for: window)
            XCTFail("Expected screenshot capture to fail.")
        } catch let error as ScreenshotProviderError {
            XCTAssertEqual(error, .windowNotFound(404))
        } catch {
            XCTFail("Expected ScreenshotProviderError, got \(error).")
        }
    }

    @MainActor
    func testScreenCaptureKitScreenshotProviderFallsBackOnCaptureFailure() async {
        let fallbackImage = NSImage.alignerTestImage()
        let skeletonProvider = MockSkeletonThumbnailProvider(image: fallbackImage)
        let logger = MockScreenshotDebugLogger()
        let capturer = MockWindowScreenshotCapturer(error: ScreenshotProviderError.windowNotFound(505))
        let provider: any ScreenshotProviderProtocol = ScreenCaptureKitScreenshotProvider(
            capturer: capturer,
            skeletonProvider: skeletonProvider,
            debugLogger: logger,
            permissionChecker: MockPermissionChecker()
        )
        let session = ScreenshotCaptureSession()
        let window = AlignerWindow(id: 505, app: makeApp(), title: "Fallback Title")

        let resolution = await provider.resolvedScreenshot(for: window, in: session)

        XCTAssertTrue(resolution.image === fallbackImage)
        XCTAssertEqual(capturer.requestedWindowIDs, [505])
        XCTAssertEqual(skeletonProvider.overlayTitles, ["Fallback Title"])
        guard case .skeletonFallback(.captureFailed(let failureSummary)) = resolution.source else {
            XCTFail("Expected sanitized capture failure fallback.")
            return
        }
        XCTAssertFalse(failureSummary.contains("windowNotFound"))
        XCTAssertTrue(failureSummary.contains("domain="))
        XCTAssertTrue(failureSummary.contains("code="))
        XCTAssertTrue(failureSummary.contains("descriptionHash="))
        XCTAssertTrue(failureSummary.contains("descriptionLength="))
        XCTAssertEqual(
            logger.events,
            [
                .captureStarted(windowID: 505),
                .captureFailed(windowID: 505, reason: .captureFailed(failureSummary)),
                .fallbackUsed(windowID: 505, reason: .captureFailed(failureSummary))
            ]
        )
    }

    @MainActor
    func testScreenCaptureKitScreenshotProviderFallsBackWhenScreenRecordingIsDenied() async {
        let skeletonProvider = MockSkeletonThumbnailProvider()
        let logger = MockScreenshotDebugLogger()
        let capturer = MockWindowScreenshotCapturer()
        let provider: any ScreenshotProviderProtocol = ScreenCaptureKitScreenshotProvider(
            capturer: capturer,
            skeletonProvider: skeletonProvider,
            debugLogger: logger,
            permissionChecker: MockPermissionChecker(screenRecording: .notGranted)
        )
        let session = ScreenshotCaptureSession()
        let window = AlignerWindow(id: 909, app: makeApp(), title: "Needs Permission")

        let resolution = await provider.resolvedScreenshot(for: window, in: session)

        XCTAssertEqual(capturer.requestedWindowIDs, [])
        XCTAssertEqual(resolution.source, .skeletonFallback(.screenRecordingDenied))
        XCTAssertEqual(skeletonProvider.overlayTitles, ["Needs Permission"])
        XCTAssertEqual(
            logger.events,
            [
                .fallbackUsed(windowID: 909, reason: .screenRecordingDenied)
            ]
        )
    }

    @MainActor
    func testScreenCaptureKitScreenshotProviderSkipsCaptureAfterRetryBudgetIsExhausted() async {
        let skeletonProvider = MockSkeletonThumbnailProvider()
        let logger = MockScreenshotDebugLogger()
        let capturer = MockWindowScreenshotCapturer(error: ScreenshotProviderError.windowNotFound(606))
        let provider = ScreenCaptureKitScreenshotProvider(
            capturer: capturer,
            skeletonProvider: skeletonProvider,
            policy: ScreenshotCapturePolicy(timeoutMilliseconds: 300, maxRetriesPerSession: 1),
            debugLogger: logger,
            permissionChecker: MockPermissionChecker()
        )
        let session = ScreenshotCaptureSession()
        let window = AlignerWindow(id: 606, app: makeApp(), title: "Retry")

        _ = await provider.resolvedScreenshot(for: window, in: session)
        _ = await provider.resolvedScreenshot(for: window, in: session)
        let skipped = await provider.resolvedScreenshot(for: window, in: session)

        XCTAssertEqual(capturer.requestedWindowIDs, [606, 606])
        XCTAssertEqual(
            skipped.source,
            .skeletonFallback(.retryLimitReached(failedAttempts: 2))
        )
        XCTAssertTrue(
            logger.events.contains(.retrySkipped(windowID: 606, failedAttempts: 2))
        )

        _ = await provider.resolvedScreenshot(for: window, in: ScreenshotCaptureSession())
        XCTAssertEqual(capturer.requestedWindowIDs, [606, 606, 606])
    }

    @MainActor
    func testScreenCaptureKitScreenshotProviderFallsBackOnTimeout() async {
        let skeletonProvider = MockSkeletonThumbnailProvider()
        let logger = MockScreenshotDebugLogger()
        let capturer = MockWindowScreenshotCapturer(delayNanoseconds: 50_000_000)
        let provider = ScreenCaptureKitScreenshotProvider(
            capturer: capturer,
            skeletonProvider: skeletonProvider,
            policy: ScreenshotCapturePolicy(timeoutMilliseconds: 1, maxRetriesPerSession: 1),
            debugLogger: logger,
            permissionChecker: MockPermissionChecker()
        )
        let session = ScreenshotCaptureSession()
        let window = AlignerWindow(id: 707, app: makeApp(), title: "Slow")

        let startedAt = Date()
        let resolution = await provider.resolvedScreenshot(for: window, in: session)
        let elapsed = Date().timeIntervalSince(startedAt)

        XCTAssertEqual(capturer.requestedWindowIDs, [707])
        XCTAssertLessThan(elapsed, 0.1)
        XCTAssertEqual(
            resolution.source,
            .skeletonFallback(.timedOut(windowID: 707, timeoutMilliseconds: 1))
        )
        XCTAssertTrue(
            logger.events.contains(.fallbackUsed(
                windowID: 707,
                reason: .timedOut(windowID: 707, timeoutMilliseconds: 1)
            ))
        )
    }

    @MainActor
    func testScreenCaptureKitScreenshotProviderFallsBackOnInvalidCapture() async {
        let skeletonProvider = MockSkeletonThumbnailProvider()
        let logger = MockScreenshotDebugLogger()
        let capturer = MockWindowScreenshotCapturer(pointSize: .zero)
        let provider = ScreenCaptureKitScreenshotProvider(
            capturer: capturer,
            skeletonProvider: skeletonProvider,
            debugLogger: logger,
            permissionChecker: MockPermissionChecker()
        )
        let window = AlignerWindow(id: 808, app: makeApp(), title: "Invalid")

        let resolution = await provider.resolvedScreenshot(for: window, in: ScreenshotCaptureSession())

        XCTAssertEqual(capturer.requestedWindowIDs, [808])
        XCTAssertEqual(
            resolution.source,
            .skeletonFallback(.invalidCapture(
                windowID: 808,
                pixelWidth: 24,
                pixelHeight: 12,
                pointWidth: 0,
                pointHeight: 0
            ))
        )
        XCTAssertEqual(skeletonProvider.overlayTitles, ["Invalid"])
    }

    @MainActor
    func testScreenCaptureKitScreenshotProviderUsesSkeletonForSyntheticWindowID() async {
        let skeletonProvider = MockSkeletonThumbnailProvider()
        let logger = MockScreenshotDebugLogger()
        let capturer = MockWindowScreenshotCapturer()
        let provider = ScreenCaptureKitScreenshotProvider(
            capturer: capturer,
            skeletonProvider: skeletonProvider,
            debugLogger: logger,
            permissionChecker: MockPermissionChecker()
        )
        let windowID = MinimizedWindowFallbackPolicy.syntheticWindowID(
            processIdentifier: 42,
            windowIndex: 0,
            title: "Minimized",
            subroleDescription: "standard"
        )
        let window = AlignerWindow(
            id: windowID,
            app: makeApp(),
            title: "Minimized",
            identifierSource: .syntheticAX,
            isMinimized: true
        )

        let resolution = await provider.resolvedScreenshot(for: window, in: ScreenshotCaptureSession())

        XCTAssertEqual(capturer.requestedWindowIDs, [])
        XCTAssertEqual(resolution.source, .skeletonFallback(.syntheticWindowID(windowID: windowID)))
        XCTAssertEqual(
            logger.events,
            [
                .fallbackUsed(windowID: windowID, reason: .syntheticWindowID(windowID: windowID))
            ]
        )
        XCTAssertEqual(skeletonProvider.overlayTitles, ["Minimized"])
    }

    func testPreferenceKeysAreStableAndNamespaced() {
        XCTAssertEqual(
            PreferenceKey.allCases.map(\.rawValue),
            [
                "schemaVersion",
                "quickSwitch.showMinimizedWindows",
                "quickSwitch.showFullscreenWindows",
                "appearance.theme",
                "appearance.language",
                "spaces.displayStrategy"
            ]
        )

        XCTAssertTrue(PreferenceKey.allCases.allSatisfy {
            $0.storageKey.hasPrefix("com.ar.Aligner.preferences.")
        })
    }

    func testDefaultPreferencesMatchRound0Decisions() {
        XCTAssertEqual(PreferenceSchema.version, 1)
        XCTAssertEqual(PreferenceSchema.defaults.showMinimizedWindows, true)
        XCTAssertEqual(PreferenceSchema.defaults.showFullscreenWindows, true)
        XCTAssertEqual(PreferenceSchema.defaults.theme, .system)
        XCTAssertEqual(PreferenceSchema.defaults.language, .system)
        XCTAssertEqual(PreferenceSchema.defaults.spaceDisplayStrategy, .includeVisibleAndFullscreen)
    }

    func testUserDefaultsPreferenceStoreRoundTrips() throws {
        let suiteName = "com.ar.Aligner.tests.\(UUID().uuidString)"
        let userDefaults = try XCTUnwrap(UserDefaults(suiteName: suiteName))
        defer {
            userDefaults.removePersistentDomain(forName: suiteName)
        }

        let store = UserDefaultsPreferenceStore(userDefaults: userDefaults)
        XCTAssertEqual(store.read(), PreferenceSchema.defaults)
        XCTAssertEqual(store.storedSchemaVersion(), PreferenceSchema.version)
        XCTAssertFalse(store.needsMigration())

        let preferences = AlignerPreferences(
            showMinimizedWindows: false,
            showFullscreenWindows: false,
            theme: .dark,
            language: .simplifiedChinese,
            spaceDisplayStrategy: .currentSpaceOnly
        )

        store.write(preferences)
        XCTAssertEqual(store.read(), preferences)
        XCTAssertEqual(store.storedSchemaVersion(), PreferenceSchema.version)

        store.reset()
        XCTAssertEqual(store.read(), PreferenceSchema.defaults)
    }

    func testUserDefaultsPreferenceStoreDetectsOldSchemaVersion() throws {
        let suiteName = "com.ar.Aligner.tests.\(UUID().uuidString)"
        let userDefaults = try XCTUnwrap(UserDefaults(suiteName: suiteName))
        defer {
            userDefaults.removePersistentDomain(forName: suiteName)
        }

        userDefaults.set(0, forKey: PreferenceKey.schemaVersion.storageKey)

        let store = UserDefaultsPreferenceStore(userDefaults: userDefaults)
        XCTAssertEqual(store.storedSchemaVersion(), 0)
        XCTAssertTrue(store.needsMigration())
    }

    func testUserDefaultsPreferenceStoreFallsBackForInvalidEnumValues() throws {
        let suiteName = "com.ar.Aligner.tests.\(UUID().uuidString)"
        let userDefaults = try XCTUnwrap(UserDefaults(suiteName: suiteName))
        defer {
            userDefaults.removePersistentDomain(forName: suiteName)
        }

        userDefaults.set("not-a-theme", forKey: PreferenceKey.theme.storageKey)
        userDefaults.set("not-a-language", forKey: PreferenceKey.language.storageKey)
        userDefaults.set("not-a-space-strategy", forKey: PreferenceKey.spaceDisplayStrategy.storageKey)

        let preferences = UserDefaultsPreferenceStore(userDefaults: userDefaults).read()

        XCTAssertEqual(preferences.theme, PreferenceSchema.defaults.theme)
        XCTAssertEqual(preferences.language, PreferenceSchema.defaults.language)
        XCTAssertEqual(preferences.spaceDisplayStrategy, PreferenceSchema.defaults.spaceDisplayStrategy)
    }

    @MainActor
    func testOverlayWindowPolicyMatchesRound0LayeringDecision() {
        let styleMask = OverlayWindowPolicy.styleMask
        XCTAssertTrue(styleMask.contains(.borderless))
        XCTAssertTrue(styleMask.contains(.fullSizeContentView))
        XCTAssertTrue(styleMask.contains(.nonactivatingPanel))

        let collectionBehavior = OverlayWindowPolicy.collectionBehavior
        XCTAssertTrue(collectionBehavior.contains(.canJoinAllSpaces))
        XCTAssertTrue(collectionBehavior.contains(.fullScreenAuxiliary))
        XCTAssertTrue(collectionBehavior.contains(.stationary))
        XCTAssertTrue(collectionBehavior.contains(.transient))
        XCTAssertTrue(collectionBehavior.contains(.ignoresCycle))

        XCTAssertEqual(
            OverlayWindowPolicy.quickSwitchLevel.rawValue,
            Int(CGWindowLevelForKey(.mainMenuWindow) - OverlayWindowPolicy.levelOffsetBelowMainMenu)
        )
        XCTAssertTrue(OverlayWindowPolicy.isBelowRescueSystemWindows())
    }

    func testSystemCriticalWindowClassifierCoversRescueAndPermissionWindows() {
        XCTAssertTrue(SystemCriticalWindowClassifier.isSystemCritical(
            SystemCriticalWindowDescriptor(title: "Force Quit Applications")
        ))
        XCTAssertTrue(SystemCriticalWindowClassifier.isSystemCritical(
            SystemCriticalWindowDescriptor(ownerName: "loginwindow", title: "强制退出应用程序")
        ))
        XCTAssertTrue(SystemCriticalWindowClassifier.isSystemCritical(
            SystemCriticalWindowDescriptor(ownerName: "SecurityAgent", title: "Security Confirmation")
        ))
        XCTAssertTrue(SystemCriticalWindowClassifier.isSystemCritical(
            SystemCriticalWindowDescriptor(ownerName: "CoreServicesUIAgent", title: "Privacy & Security")
        ))
        XCTAssertTrue(SystemCriticalWindowClassifier.isSystemCriticalTitle("屏幕录制权限"))
        XCTAssertTrue(SystemCriticalWindowClassifier.isSystemCriticalTitle("Accessibility Permissions"))
        XCTAssertFalse(SystemCriticalWindowClassifier.isSystemCritical(
            SystemCriticalWindowDescriptor(ownerName: "Safari", title: "Permissions API Guide")
        ))
        XCTAssertFalse(SystemCriticalWindowClassifier.isSystemCritical(
            SystemCriticalWindowDescriptor(ownerName: "System Settings", title: "Privacy & Security")
        ))
        XCTAssertFalse(SystemCriticalWindowClassifier.isSystemCritical(
            SystemCriticalWindowDescriptor(ownerName: "loginwindow", title: "")
        ))
        XCTAssertFalse(SystemCriticalWindowClassifier.isSystemCritical(
            SystemCriticalWindowDescriptor(ownerName: "Notes", title: "权限设计草稿")
        ))
        XCTAssertFalse(SystemCriticalWindowClassifier.isSystemCritical(
            SystemCriticalWindowDescriptor(ownerName: "Safari", title: "Example")
        ))
    }

    @MainActor
    func testOverlaySessionStartsWithQuickSwitchOnly() {
        let session = OverlaySession()

        XCTAssertEqual(session.state, .idle)

        session.openQuickSwitch()
        XCTAssertEqual(session.state, .browsing)
        XCTAssertEqual(session.context?.entryPoint, .quickSwitch)
    }

    @MainActor
    func testOverlaySessionIgnoresSearchUnlessBrowsing() {
        let session = OverlaySession()

        session.enterQuickSearch()
        XCTAssertEqual(session.state, .idle)

        session.openQuickSwitch()
        session.enterQuickSearch()
        XCTAssertEqual(session.state, .quickSearch(.title))
    }

    func testOverlayPlacementUsesMouseContainingScreen() {
        let placement = OverlayPlacementPolicy.placement(
            forMouseLocation: CGPoint(x: 1500, y: 500),
            screens: [
                OverlayScreenRecord(
                    displayUUID: "display-a",
                    frame: CGRect(x: 0, y: 0, width: 1000, height: 800),
                    visibleFrame: CGRect(x: 0, y: 0, width: 1000, height: 760)
                ),
                OverlayScreenRecord(
                    displayUUID: "display-b",
                    frame: CGRect(x: 1000, y: 0, width: 1000, height: 800),
                    visibleFrame: CGRect(x: 1000, y: 0, width: 1000, height: 760)
                )
            ]
        )

        XCTAssertEqual(placement.targetDisplayUUID, "display-b")
        XCTAssertTrue(placement.coverFullscreenSpace)
        XCTAssertEqual(placement.overlayContext.targetDisplayUUID, "display-b")
        XCTAssertTrue(placement.overlayContext.spacePolicy.avoidSpaceSwitch)
    }

    func testOverlayPlacementFallsBackToFirstScreenWhenMouseIsOutsideScreens() {
        let placement = OverlayPlacementPolicy.placement(
            forMouseLocation: CGPoint(x: -200, y: -200),
            screens: [
                OverlayScreenRecord(
                    displayUUID: "display-a",
                    frame: CGRect(x: 0, y: 0, width: 1000, height: 800),
                    visibleFrame: CGRect(x: 0, y: 0, width: 1000, height: 760)
                )
            ]
        )

        XCTAssertEqual(placement.targetDisplayUUID, "display-a")
    }

    func testOverlayPlacementRecordsFullscreenAndSplitViewState() {
        let placement = OverlayPlacementPolicy.placement(
            forMouseLocation: CGPoint(x: 1500, y: 500),
            screens: [
                OverlayScreenRecord(
                    displayUUID: "display-a",
                    frame: CGRect(x: 0, y: 0, width: 1000, height: 800),
                    visibleFrame: CGRect(x: 0, y: 0, width: 1000, height: 760)
                ),
                OverlayScreenRecord(
                    displayUUID: "display-b",
                    frame: CGRect(x: 1000, y: 0, width: 1000, height: 800),
                    visibleFrame: CGRect(x: 1000, y: 0, width: 1000, height: 760),
                    isFullscreenSpace: true,
                    isSplitViewSpace: true
                )
            ]
        )

        XCTAssertEqual(placement.targetDisplayUUID, "display-b")
        XCTAssertTrue(placement.isFullscreenSpace)
        XCTAssertTrue(placement.isSplitViewSpace)
        XCTAssertTrue(placement.avoidSpaceSwitch)
        XCTAssertEqual(placement.overlayContext.spacePolicy, SpacePolicy(isFullscreenSpace: true, avoidSpaceSwitch: true))
    }

    func testQuickSwitchSnapshotFiltersCandidatesAndSortsAppsByStableName() {
        let beta = AlignerApp(bundleIdentifier: "com.example.Beta", name: "Beta", category: .generic)
        let alpha = AlignerApp(bundleIdentifier: "com.example.Alpha", name: "Alpha", category: .generic)
        let hidden = AlignerApp(bundleIdentifier: "com.example.Hidden", name: "Hidden", category: .generic, isHidden: true)
        let displays = [makeDisplay(spaceIDs: [1])]
        let snapshot = QuickSwitchSnapshotBuilder.snapshot(
            displays: displays,
            windows: [
                AlignerWindow(id: 3, app: beta, title: "Beta"),
                AlignerWindow(id: 1, app: hidden, title: "Hidden"),
                AlignerWindow(id: 2, app: alpha, title: "Alpha"),
                AlignerWindow(id: 4, app: beta, title: "Ghost", isGhost: true)
            ]
        )

        XCTAssertEqual(snapshot.appGroups.map(\.app.name), ["Alpha", "Beta"])
        XCTAssertEqual(snapshot.appGroups.flatMap(\.windows).map(\.window.id), [2, 3])
    }

    func testQuickSwitchSnapshotUsesBundlePathAndPIDAsStableAppIdentityFallbacks() {
        let firstCopy = AlignerApp(
            bundleIdentifier: "com.example.Editor",
            name: "Editor",
            category: .generic,
            executablePath: "/Applications/Editor.app",
            processIdentifier: 100
        )
        let secondCopy = AlignerApp(
            bundleIdentifier: "com.example.Editor",
            name: "Editor",
            category: .generic,
            executablePath: "/Users/ar/Downloads/Editor.app",
            processIdentifier: 200
        )
        let noBundleA = AlignerApp(
            bundleIdentifier: "",
            name: "Editor",
            category: .generic,
            executablePath: "/Applications/NoBundleA.app",
            processIdentifier: 300
        )
        let noBundleB = AlignerApp(
            bundleIdentifier: "",
            name: "Editor",
            category: .generic,
            executablePath: "/Applications/NoBundleB.app",
            processIdentifier: 400
        )

        let snapshot = QuickSwitchSnapshotBuilder.snapshot(
            displays: [makeDisplay(spaceIDs: [1])],
            windows: [
                AlignerWindow(id: 4, app: noBundleB, title: "No Bundle B"),
                AlignerWindow(id: 2, app: secondCopy, title: "Second Copy"),
                AlignerWindow(id: 3, app: noBundleA, title: "No Bundle A"),
                AlignerWindow(id: 1, app: firstCopy, title: "First Copy")
            ]
        )

        XCTAssertEqual(snapshot.appGroups.count, 4)
        XCTAssertEqual(snapshot.appGroups.flatMap(\.windows).map(\.window.id), [1, 2, 3, 4])
    }

    func testQuickSwitchSnapshotSortsWindowsBySpaceThenTitleThenID() {
        let app = makeApp()
        let displays = [makeDisplay(spaceIDs: [10, 11, 12])]
        let snapshot = QuickSwitchSnapshotBuilder.snapshot(
            displays: displays,
            windows: [
                AlignerWindow(id: 40, app: app, title: "Zulu", spaceIDs: [12]),
                AlignerWindow(id: 10, app: app, title: "Beta", spaceIDs: [10]),
                AlignerWindow(id: 30, app: app, title: "Alpha", spaceIDs: [12]),
                AlignerWindow(id: 20, app: app, title: "Alpha", spaceIDs: [11]),
                AlignerWindow(id: 50, app: app, title: "No Space")
            ]
        )

        XCTAssertEqual(snapshot.appGroups.single?.windows.map(\.window.id), [10, 20, 30, 40, 50])
        XCTAssertEqual(snapshot.appGroups.single?.windows.map(\.primarySpaceID), [10, 11, 12, 12, nil])
    }

    func testWindowSpaceMappingFillsUnresolvedWindowsFromSpaceInverseMap() {
        let mapping = WindowSpaceMappingPolicy.mergedSpaceIDsByWindowID(
            requestedWindowIDs: [10, 20, 30],
            directSpaceIDsByWindowID: [10: [100]],
            windowIDsBySpaceID: [
                100: [10, 20],
                101: [30]
            ],
            orderedSpaceIDs: [100, 101]
        )

        XCTAssertEqual(mapping[10], [100])
        XCTAssertEqual(mapping[20], [100])
        XCTAssertEqual(mapping[30], [101])
    }

    func testWindowSpaceMappingDoesNotOverwriteDirectSpaceAssignments() {
        let mapping = WindowSpaceMappingPolicy.mergedSpaceIDsByWindowID(
            requestedWindowIDs: [10],
            directSpaceIDsByWindowID: [10: [101]],
            windowIDsBySpaceID: [
                100: [10]
            ],
            orderedSpaceIDs: [100, 101]
        )

        XCTAssertEqual(mapping[10], [101])
    }

    func testWindowSpaceMappingLeavesAmbiguousNoSpaceWindowsUnassigned() {
        let mapping = WindowSpaceMappingPolicy.mergedSpaceIDsByWindowID(
            requestedWindowIDs: [10, 20],
            directSpaceIDsByWindowID: [:],
            windowIDsBySpaceID: [
                100: [10, 99]
            ],
            orderedSpaceIDs: [100]
        )

        XCTAssertEqual(mapping[10], [100])
        XCTAssertNil(mapping[20])
        XCTAssertNil(mapping[99])
    }

    func testQuickSwitchSnapshotKeepsMinimizedAndFullscreenWindows() {
        let app = makeApp()
        let snapshot = QuickSwitchSnapshotBuilder.snapshot(
            displays: [makeDisplay(spaceIDs: [1, 2])],
            windows: [
                AlignerWindow(id: 1, app: app, title: "Minimized", isMinimized: true, spaceIDs: [1]),
                AlignerWindow(id: 2, app: app, title: "Fullscreen", isFullscreen: true, spaceIDs: [2])
            ]
        )

        XCTAssertEqual(snapshot.appGroups.single?.windows.map(\.window.id), [1, 2])
    }

    func testQuickSwitchViewModelBuilderKeepsSpaceAppAndWaterfallOrderAligned() {
        let alpha = AlignerApp(bundleIdentifier: "com.example.Alpha", name: "Alpha", category: .generic)
        let beta = AlignerApp(bundleIdentifier: "com.example.Beta", name: "Beta", category: .generic)
        let snapshot = QuickSwitchSnapshotBuilder.snapshot(
            displays: [
                makeDisplay(spaceIDs: [20], uuid: "display-b"),
                makeDisplay(spaceIDs: [10, 11], uuid: "display-a")
            ],
            windows: [
                AlignerWindow(id: 3, app: beta, title: "B", spaceIDs: [20]),
                AlignerWindow(id: 1, app: alpha, title: "A1", spaceIDs: [10]),
                AlignerWindow(id: 2, app: alpha, title: "A2", isMinimized: true, spaceIDs: [11])
            ]
        )

        let viewModel = QuickSwitchViewModelBuilder.viewModel(from: snapshot, currentSpaceIDs: [11])

        XCTAssertEqual(viewModel.displays.map(\.displayLabel), ["A", "B"])
        XCTAssertEqual(viewModel.displays.flatMap { $0.spaces.map(\.label) }, ["A1", "A2", "B1"])
        XCTAssertEqual(viewModel.displays.flatMap { $0.spaces.map(\.windowCount) }, [1, 1, 1])
        XCTAssertEqual(viewModel.displays.flatMap { $0.spaces.map(\.appNames) }, [["Alpha"], ["Alpha"], ["Beta"]])
        XCTAssertEqual(viewModel.displays.flatMap { $0.spaces.filter(\.isCurrent).map(\.label) }, ["A2"])
        XCTAssertEqual(viewModel.appShelf.map(\.app.name), ["Alpha", "Beta"])
        XCTAssertEqual(viewModel.appShelf.map(\.windowCount), [2, 1])
        XCTAssertEqual(viewModel.appShelf.first?.primarySpaceIDs, [10, 11])
        XCTAssertEqual(viewModel.appShelf.first?.hasMinimizedWindows, true)
        XCTAssertEqual(viewModel.waterfallColumns.map(\.app.name), viewModel.appShelf.map(\.app.name))
        XCTAssertEqual(viewModel.waterfallColumns.flatMap { $0.windows.map(\.primarySpaceLabel) }, ["A1", "A2", "B1"])
        XCTAssertEqual(viewModel.initialSelection, QuickSwitchSelection(appGroupIndex: 0, windowIndex: 0, windowID: 1))
    }

    func testQuickSwitchViewModelBuilderUsesNumericSpaceLabelsForSingleDisplay() {
        let app = AlignerApp(bundleIdentifier: "com.example.Alpha", name: "Alpha", category: .generic)
        let snapshot = QuickSwitchSnapshotBuilder.snapshot(
            displays: [makeDisplay(spaceIDs: [10, 11, 12])],
            windows: [
                AlignerWindow(id: 1, app: app, title: "A1", spaceIDs: [10]),
                AlignerWindow(id: 2, app: app, title: "A2", spaceIDs: [11])
            ]
        )

        let viewModel = QuickSwitchViewModelBuilder.viewModel(from: snapshot, currentSpaceIDs: [11])

        XCTAssertEqual(viewModel.displays.flatMap { $0.spaces.map(\.label) }, ["01", "02", "03"])
        XCTAssertEqual(viewModel.displays.flatMap { $0.spaces.filter(\.isCurrent).map(\.label) }, ["02"])
        XCTAssertEqual(viewModel.waterfallColumns.flatMap { $0.windows.map(\.primarySpaceLabel) }, ["01", "02"])
    }

    func testQuickSwitchFocusPolicySelectsFirstStableWindowInitially() {
        let app = makeApp()
        let snapshot = QuickSwitchSnapshotBuilder.snapshot(
            displays: [makeDisplay(spaceIDs: [1])],
            windows: [
                AlignerWindow(id: 2, app: app, title: "Beta", spaceIDs: [1]),
                AlignerWindow(id: 1, app: app, title: "Alpha", spaceIDs: [1])
            ]
        )

        XCTAssertEqual(
            QuickSwitchFocusPolicy.initialSelection(in: snapshot),
            QuickSwitchSelection(appGroupIndex: 0, windowIndex: 0, windowID: 1)
        )
    }

    func testQuickSwitchVisualPriorityKeepsKeyboardSelectedAsPrimaryFocus() {
        XCTAssertEqual(
            QuickSwitchFocusPolicy.visualState(for: 1, selectedIndex: 1, hoveredIndex: 2),
            .selected
        )
        XCTAssertEqual(
            QuickSwitchFocusPolicy.visualState(for: 2, selectedIndex: 1, hoveredIndex: 2),
            .hover
        )
        XCTAssertEqual(
            QuickSwitchFocusPolicy.visualState(for: 1, selectedIndex: 1, hoveredIndex: 1),
            .selectedAndHover
        )
    }

    func testQuickSwitchKeyboardNavigationMovesByAppColumnsAndColumnWindows() {
        let snapshot = makeNavigationSnapshot(columnWindowCounts: [2, 1, 3])
        let current = QuickSwitchSelection(appGroupIndex: 0, windowIndex: 1, windowID: 2)

        XCTAssertEqual(
            QuickSwitchFocusPolicy.nextSelection(from: current, in: snapshot, direction: .right),
            QuickSwitchSelection(appGroupIndex: 1, windowIndex: 0, windowID: 3)
        )
        XCTAssertEqual(
            QuickSwitchFocusPolicy.nextSelection(from: current, in: snapshot, direction: .up),
            QuickSwitchSelection(appGroupIndex: 0, windowIndex: 0, windowID: 1)
        )
        XCTAssertEqual(
            QuickSwitchFocusPolicy.nextSelection(from: current, in: snapshot, direction: .down),
            current
        )
    }

    func testQuickSwitchKeyboardNavigationDoesNotWrapAcrossColumnBoundaries() {
        let snapshot = makeNavigationSnapshot(columnWindowCounts: [2, 1, 3])

        XCTAssertEqual(
            QuickSwitchFocusPolicy.nextSelection(
                from: QuickSwitchSelection(appGroupIndex: 0, windowIndex: 0, windowID: 1),
                in: snapshot,
                direction: .left
            ),
            QuickSwitchSelection(appGroupIndex: 0, windowIndex: 0, windowID: 1)
        )
        XCTAssertEqual(
            QuickSwitchFocusPolicy.nextSelection(
                from: QuickSwitchSelection(appGroupIndex: 2, windowIndex: 2, windowID: 6),
                in: snapshot,
                direction: .right
            ),
            QuickSwitchSelection(appGroupIndex: 2, windowIndex: 2, windowID: 6)
        )
        XCTAssertEqual(
            QuickSwitchFocusPolicy.nextSelection(
                from: QuickSwitchSelection(appGroupIndex: 2, windowIndex: 0, windowID: 4),
                in: snapshot,
                direction: .up
            ),
            QuickSwitchSelection(appGroupIndex: 2, windowIndex: 0, windowID: 4)
        )
    }

    func testQuickSwitchKeyboardNavigationRestoresColumnHistoryOrFallsBackToFirstWindow() {
        let snapshot = makeNavigationSnapshot(columnWindowCounts: [2, 1, 3])
        let current = QuickSwitchSelection(appGroupIndex: 1, windowIndex: 0, windowID: 3)

        XCTAssertEqual(
            QuickSwitchFocusPolicy.nextSelection(
                from: current,
                in: snapshot,
                direction: .right,
                columnHistory: [2: 2]
            ),
            QuickSwitchSelection(appGroupIndex: 2, windowIndex: 2, windowID: 6)
        )
        XCTAssertEqual(
            QuickSwitchFocusPolicy.nextSelection(
                from: current,
                in: snapshot,
                direction: .left,
                columnHistory: [0: 99]
            ),
            QuickSwitchSelection(appGroupIndex: 0, windowIndex: 1, windowID: 2)
        )
    }

    private func makeWindowRecord(
        id: UInt32 = 100,
        activationPolicy: AlignerActivationPolicy = .regular,
        title: String = "Document",
        size: CGSize = CGSize(width: 640, height: 480),
        subrole: AlignerWindowSubrole = .standard,
        appHidden: Bool = false,
        isMinimized: Bool = false,
        isFullscreen: Bool = false,
        isGhost: Bool = false,
        isDesktopElement: Bool = false,
        isSystemCritical: Bool = false,
        isInteractable: Bool = true,
        hasAXBacking: Bool = true,
        isOnscreen: Bool = true,
        spaceIDs: [UInt64] = []
    ) -> WindowEnumerationRecord {
        WindowEnumerationRecord(
            id: id,
            app: AlignerApp(
                bundleIdentifier: "com.example.App",
                name: "Example",
                category: .generic,
                isHidden: appHidden
            ),
            activationPolicy: activationPolicy,
            title: title,
            size: size,
            subrole: subrole,
            isMinimized: isMinimized,
            isFullscreen: isFullscreen,
            isGhost: isGhost,
            isDesktopElement: isDesktopElement,
            isSystemCritical: isSystemCritical,
            isInteractable: isInteractable,
            hasAXBacking: hasAXBacking,
            isOnscreen: isOnscreen,
            spaceIDs: spaceIDs
        )
    }

    private func makeApp() -> AlignerApp {
        AlignerApp(bundleIdentifier: "com.example.App", name: "Example", category: .generic)
    }

    private func makeDisplay(spaceIDs: [UInt64], uuid: String = "display-a") -> AlignerDisplay {
        AlignerDisplay(
            uuid: uuid,
            physical: true,
            spaces: spaceIDs.enumerated().map { offset, id in
                AlignerSpace(id: id, type: .user, displayUUID: uuid, index: offset + 1)
            }
        )
    }

    private func makeNavigationSnapshot(columnWindowCounts: [Int]) -> QuickSwitchSnapshot {
        var nextWindowID: UInt32 = 1
        let groups = columnWindowCounts.enumerated().map { appIndex, windowCount in
            let app = AlignerApp(
                bundleIdentifier: "com.example.App\(appIndex)",
                name: "App \(appIndex)",
                category: .generic
            )
            let windows = (0..<windowCount).map { _ in
                let window = AlignerWindow(id: nextWindowID, app: app, title: "Window \(nextWindowID)")
                nextWindowID += 1
                return QuickSwitchWindowItem(window: window, primarySpaceID: nil)
            }
            return QuickSwitchAppGroup(app: app, windows: windows)
        }

        return QuickSwitchSnapshot(displays: [makeDisplay(spaceIDs: [1])], appGroups: groups)
    }
}

private extension Array {
    var single: Element? {
        count == 1 ? self[0] : nil
    }
}

private final class MockWindowService: WindowServiceProtocol {
    weak var delegate: WindowServiceDelegate?

    func allWindows() throws -> [AlignerWindow] {
        []
    }

    func windows(for space: AlignerSpace) throws -> [AlignerWindow] {
        []
    }

    func refresh() {}

    func startObserving() {
        delegate?.windowServiceDidDetectWindowChange(WindowChange(kind: .opened, windowID: 700))
    }

    func stopObserving() {}
}

private final class MockWindowServiceDelegate: WindowServiceDelegate {
    var changes: [WindowChange] = []

    func windowServiceDidDetectWindowChange(_ change: WindowChange) {
        changes.append(change)
    }
}

@MainActor
private final class MockApplicationIconResolver: ApplicationIconResolving {
    var applicationURLs: [String: URL] = [:]
    var requestedIconPaths: [String] = []
    let fileIcon: NSImage
    let fallbackIcon: NSImage

    init(fileIcon: NSImage = NSImage.alignerTestImage(), fallbackIcon: NSImage = NSImage.alignerTestImage()) {
        self.fileIcon = fileIcon
        self.fallbackIcon = fallbackIcon
    }

    func applicationURL(forBundleIdentifier bundleIdentifier: String) -> URL? {
        applicationURLs[bundleIdentifier]
    }

    func icon(forFile path: String) -> NSImage {
        requestedIconPaths.append(path)
        return fileIcon
    }

    func fallbackApplicationIcon() -> NSImage {
        fallbackIcon
    }
}

@MainActor
private final class MockSkeletonThumbnailProvider: SkeletonThumbnailProviderProtocol {
    var requestedApps: [AlignerApp] = []
    var overlayTitles: [String] = []
    let image: NSImage

    init(image: NSImage = NSImage.alignerTestImage()) {
        self.image = image
    }

    func skeleton(for app: AlignerApp) -> NSImage {
        requestedApps.append(app)
        return image
    }

    func withOverlayTitle(_ title: String, for app: AlignerApp) -> NSImage {
        overlayTitles.append(title)
        requestedApps.append(app)
        return image
    }
}

@MainActor
private final class MockScreenshotDebugLogger: ScreenshotDebugLogging {
    var events: [ScreenshotDebugEvent] = []

    func record(_ event: ScreenshotDebugEvent) {
        events.append(event)
    }
}

private struct MockPermissionChecker: SystemPermissionChecking {
    let accessibility: PermissionStatus
    let screenRecording: PermissionStatus

    init(
        accessibility: PermissionStatus = .granted,
        screenRecording: PermissionStatus = .granted
    ) {
        self.accessibility = accessibility
        self.screenRecording = screenRecording
    }

    func status(for kind: PermissionKind) -> PermissionStatus {
        switch kind {
        case .accessibility:
            return accessibility
        case .screenRecording:
            return screenRecording
        }
    }
}

@MainActor
private extension NSImage {
    static func alignerTestImage() -> NSImage {
        let image = NSImage(size: NSSize(width: 16, height: 16))
        image.lockFocus()
        NSColor.systemBlue.setFill()
        NSRect(x: 0, y: 0, width: 16, height: 16).fill()
        image.unlockFocus()
        return image
    }
}

@MainActor
private final class MockWindowScreenshotCapturer: WindowScreenshotCapturing {
    var requestedWindowIDs: [UInt32] = []
    let image: CGImage
    let pointSize: NSSize
    let error: Error?
    let delayNanoseconds: UInt64
    var observedCancellation = false

    init(
        image: CGImage = CGImage.alignerTestImage(),
        pointSize: NSSize = NSSize(width: 24, height: 12),
        error: Error? = nil,
        delayNanoseconds: UInt64 = 0
    ) {
        self.image = image
        self.pointSize = pointSize
        self.error = error
        self.delayNanoseconds = delayNanoseconds
    }

    func captureWindow(windowID: UInt32) async throws -> WindowScreenshotCapture {
        requestedWindowIDs.append(windowID)
        if delayNanoseconds > 0 {
            do {
                try await Task.sleep(nanoseconds: delayNanoseconds)
            } catch is CancellationError {
                observedCancellation = true
                throw error ?? CancellationError()
            }
        }
        if let error {
            throw error
        }
        return WindowScreenshotCapture(image: image, pointSize: pointSize)
    }
}

private extension CGImage {
    static func alignerTestImage(width: Int = 24, height: Int = 12) -> CGImage {
        let colorSpace = CGColorSpaceCreateDeviceRGB()
        let context = CGContext(
            data: nil,
            width: width,
            height: height,
            bitsPerComponent: 8,
            bytesPerRow: width * 4,
            space: colorSpace,
            bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue
        )!
        context.setFillColor(NSColor.systemBlue.cgColor)
        context.fill(CGRect(x: 0, y: 0, width: width, height: height))
        return context.makeImage()!
    }
}
