import AppKit
import Darwin
import AlignerCore

@MainActor
final class AlignerApplicationDelegate: NSObject, NSApplicationDelegate {
    private let permissionChecker = SystemPermissionChecker()
    private let launchOptions = Round0DebugLaunchOptions.parse(arguments: ProcessInfo.processInfo.arguments)
    private let round1PoCOptions = Round1PerformancePoCOptions.parse(arguments: ProcessInfo.processInfo.arguments)
    private let round1TriggerProbeOptions = Round1TriggerProbeOptions.parse(arguments: ProcessInfo.processInfo.arguments)
    private let round1SnapshotDumpOptions = Round1SnapshotDumpOptions.parse(arguments: ProcessInfo.processInfo.arguments)
    private let round1ScreenshotProbeOptions = Round1ScreenshotProbeOptions.parse(arguments: ProcessInfo.processInfo.arguments)
    private let round1QuickSwitchOptions = Round1QuickSwitchLaunchOptions.parse(arguments: ProcessInfo.processInfo.arguments)
    private var permissionWindowController: PermissionsWindowController?
    private var debugOverlayController: Round0DebugOverlayController?
    private var round1PerformancePoCController: Round1PerformancePoCController?
    private var round1TriggerProbe: CGEventTapTriggerProbe?
    private var quickSwitchSessionController: QuickSwitchSessionController?
    private var quickSwitchTriggerService: CGEventTapTriggerService?
    private var isRunningQuickSwitchLifecycle = false
    private var statusItem: NSStatusItem?

    func applicationDidFinishLaunching(_ notification: Notification) {
        let bundlePathSummary = DevelopmentDiagnostics.pathSummary(Bundle.main.bundlePath)
        DevelopmentDiagnostics.log("app.lifecycle.didFinishLaunching.start", [
            "bundlePathKind": bundlePathSummary["locationKind"],
            "bundlePathBasename": bundlePathSummary["basename"],
            "bundlePathHash": bundlePathSummary["fingerprint"],
            "accessibility": permissionStatusString(accessibilityStatus()),
            "screenRecording": permissionStatusString(screenRecordingStatus()),
            "skipPermissions": launchOptions.skipPermissions,
            "openQuickSwitch": round1QuickSwitchOptions.openQuickSwitch,
            "quickSwitchLifecycle": round1QuickSwitchOptions.lifecycleCycles
        ])

        if runRound1SnapshotDumpIfRequested() {
            DevelopmentDiagnostics.log("app.lifecycle.didFinishLaunching.exitForSnapshotDump")
            return
        }
        if runRound1ScreenshotProbeIfRequested() {
            DevelopmentDiagnostics.log("app.lifecycle.didFinishLaunching.exitForScreenshotProbe")
            return
        }

        configureStatusItem()
        if !launchOptions.skipPermissions {
            showPermissionsIfNeeded()
        }
        startQuickSwitchTriggerServiceIfPossible()
        showDebugOverlayIfRequested()
        showRound1PerformancePoCIfRequested()
        showQuickSwitchIfRequested()
        startRound1TriggerProbeIfRequested()

        DevelopmentDiagnostics.log("app.lifecycle.didFinishLaunching.end", [
            "activationPolicy": NSApp.activationPolicy().rawValue,
            "quickSwitchTriggerRunning": quickSwitchTriggerService?.isRunning ?? false,
            "statusItemVisible": statusItem != nil
        ])
    }

    func applicationWillTerminate(_ notification: Notification) {
        DevelopmentDiagnostics.logSync("app.lifecycle.willTerminate", [
            "quickSwitchVisible": quickSwitchSessionController?.isVisible ?? false,
            "hasPermissionWindow": permissionWindowController?.window != nil,
            "activationPolicy": NSApp.activationPolicy().rawValue
        ])
    }

    func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
        DevelopmentDiagnostics.log("app.lifecycle.shouldTerminateAfterLastWindowClosed", [
            "decision": false,
            "visibleWindowCount": sender.windows.filter(\.isVisible).count
        ])
        return false
    }

    func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool {
        DevelopmentDiagnostics.log("app.lifecycle.shouldHandleReopen", [
            "hasVisibleWindows": flag,
            "activationPolicy": sender.activationPolicy().rawValue
        ])
        return true
    }

    private func configureStatusItem() {
        DevelopmentDiagnostics.log("app.statusItem.configure.start")
        let item = NSStatusBar.system.statusItem(withLength: NSStatusItem.squareLength)
        item.button?.title = "A"

        let menu = NSMenu()
        menu.addItem(NSMenuItem(title: "Check Permissions...", action: #selector(showPermissions), keyEquivalent: ","))
        menu.addItem(.separator())
        menu.addItem(NSMenuItem(title: "Show Quick Switch", action: #selector(showQuickSwitchFromMenu), keyEquivalent: ""))
        menu.addItem(NSMenuItem(title: "Hide Quick Switch", action: #selector(hideQuickSwitchFromMenu), keyEquivalent: ""))
        menu.addItem(.separator())
        menu.addItem(NSMenuItem(title: "Show Round0 Debug Overlay", action: #selector(showRound0DebugOverlay), keyEquivalent: "d"))
        menu.addItem(NSMenuItem(title: "Hide Round0 Debug Overlay", action: #selector(hideRound0DebugOverlay), keyEquivalent: ""))
        menu.addItem(.separator())
        menu.addItem(NSMenuItem(title: "Quit Aligner", action: #selector(quit), keyEquivalent: "q"))
        item.menu = menu

        statusItem = item
        DevelopmentDiagnostics.log("app.statusItem.configure.end", [
            "hasButton": item.button != nil
        ])
    }

    private func showPermissionsIfNeeded() {
        let accessibility = accessibilityStatus()
        let screenRecording = screenRecordingStatus()
        DevelopmentDiagnostics.log("app.permissions.checkOnLaunch", [
            "accessibility": permissionStatusString(accessibility),
            "screenRecording": permissionStatusString(screenRecording)
        ])
        if accessibility != .granted || screenRecording != .granted {
            showPermissions()
        }
    }

    private func runRound1SnapshotDumpIfRequested() -> Bool {
        guard round1SnapshotDumpOptions.enabled else { return false }
        DevelopmentDiagnostics.log("app.diagnostic.snapshotDump.start")

        do {
            try Round1SnapshotDump.run(prettyPrinted: round1SnapshotDumpOptions.prettyPrinted)
            DevelopmentDiagnostics.logSync("app.diagnostic.snapshotDump.success")
            NSApp.terminate(nil)
        } catch {
            DevelopmentDiagnostics.logSync("app.diagnostic.snapshotDump.failed", DevelopmentDiagnostics.errorSummaryFields(error))
            fputs("Round01 snapshot dump failed: \(DevelopmentDiagnostics.errorSummaryString(error))\n", stderr)
            exit(2)
        }

        return true
    }

    private func runRound1ScreenshotProbeIfRequested() -> Bool {
        guard round1ScreenshotProbeOptions.enabled else { return false }
        DevelopmentDiagnostics.log("app.diagnostic.screenshotProbe.start")

        Task { @MainActor in
            do {
                try await Round1ScreenshotProbe.run(prettyPrinted: round1ScreenshotProbeOptions.prettyPrinted)
                DevelopmentDiagnostics.logSync("app.diagnostic.screenshotProbe.success")
                NSApp.terminate(nil)
            } catch {
                DevelopmentDiagnostics.logSync("app.diagnostic.screenshotProbe.failed", DevelopmentDiagnostics.errorSummaryFields(error))
                fputs("Round01 screenshot probe failed: \(DevelopmentDiagnostics.errorSummaryString(error))\n", stderr)
                exit(2)
            }
        }

        return true
    }

    private func showDebugOverlayIfRequested() {
        guard launchOptions.showOverlay else { return }

        showDebugOverlay(includeSettingsChild: launchOptions.showSettingsChild)

        if let autoHideAfter = launchOptions.autoHideAfter {
            DispatchQueue.main.asyncAfter(deadline: .now() + autoHideAfter) { [weak self] in
                self?.hideDebugOverlay()
            }
        }

        if let quitAfter = launchOptions.quitAfter {
            DispatchQueue.main.asyncAfter(deadline: .now() + quitAfter) {
                NSApp.terminate(nil)
            }
        }
    }

    private func showRound1PerformancePoCIfRequested() {
        guard round1PoCOptions.showPerformancePoC else { return }

        if let lifecycleCycles = round1PoCOptions.lifecycleCycles {
            runRound1PerformancePoCLifecycle(cycles: lifecycleCycles)
            return
        }

        openRound1PerformancePoC(autoCycle: round1PoCOptions.autoCycle)

        if let autoHideAfter = round1PoCOptions.autoHideAfter {
            DispatchQueue.main.asyncAfter(deadline: .now() + autoHideAfter) { [weak self] in
                self?.writeRound1PerformancePoCReportIfNeeded()
                self?.hideRound1PerformancePoC()
            }
        }

        if let quitAfter = round1PoCOptions.quitAfter {
            DispatchQueue.main.asyncAfter(deadline: .now() + quitAfter) { [weak self] in
                self?.writeRound1PerformancePoCReportIfNeeded()
                NSApp.terminate(nil)
            }
        }
    }

    private func runRound1PerformancePoCLifecycle(cycles: Int) {
        if round1PerformancePoCController == nil {
            round1PerformancePoCController = Round1PerformancePoCController()
        }

        round1PerformancePoCController?.prepareLifecycleRun(targetCycles: cycles)
        runRound1PerformancePoCLifecycleCycle(currentCycle: 0, totalCycles: max(0, cycles))
    }

    private func runRound1PerformancePoCLifecycleCycle(currentCycle: Int, totalCycles: Int) {
        guard currentCycle < totalCycles else {
            round1PerformancePoCController?.finalizeLifecycleRun()
            writeRound1PerformancePoCReportIfNeeded()
            NSApp.terminate(nil)
            return
        }

        openRound1PerformancePoC(autoCycle: false)

        DispatchQueue.main.asyncAfter(deadline: .now() + round1PoCOptions.lifecycleVisibleDuration) { [weak self] in
            guard let self else { return }
            self.hideRound1PerformancePoC()
            self.round1PerformancePoCController?.markLifecycleCycleCompleted()

            DispatchQueue.main.asyncAfter(deadline: .now() + self.round1PoCOptions.lifecycleInterval) { [weak self] in
                self?.runRound1PerformancePoCLifecycleCycle(currentCycle: currentCycle + 1, totalCycles: totalCycles)
            }
        }
    }

    private func startRound1TriggerProbeIfRequested() {
        guard round1TriggerProbeOptions.enabled else { return }
        DevelopmentDiagnostics.log("app.triggerProbe.startRequested")

        let probe = CGEventTapTriggerProbe()

        do {
            try probe.start()
            round1TriggerProbe = probe
            DevelopmentDiagnostics.log("app.triggerProbe.started")
            print("Round01 trigger probe started")
            fflush(stdout)
        } catch {
            DevelopmentDiagnostics.logSync("app.triggerProbe.failed", DevelopmentDiagnostics.errorSummaryFields(error))
            fputs("Round01 trigger probe failed: \(DevelopmentDiagnostics.errorSummaryString(error))\n", stderr)
            exit(2)
        }

        if let quitAfter = round1TriggerProbeOptions.quitAfter {
            DispatchQueue.main.asyncAfter(deadline: .now() + quitAfter) {
                NSApp.terminate(nil)
            }
        }
    }

    private func startQuickSwitchTriggerServiceIfPossible() {
        guard !round1TriggerProbeOptions.enabled else {
            DevelopmentDiagnostics.log("app.quickSwitchTrigger.skip", [
                "reason": "trigger-probe-enabled"
            ])
            return
        }
        let accessibility = accessibilityStatus()
        DevelopmentDiagnostics.log("app.quickSwitchTrigger.startCheck", [
            "accessibility": permissionStatusString(accessibility),
            "alreadyRunning": quickSwitchTriggerService?.isRunning ?? false
        ])
        guard accessibility == .granted else {
            stopQuickSwitchTriggerService(reason: "accessibility-not-granted")
            return
        }
        guard quickSwitchTriggerService == nil else {
            DevelopmentDiagnostics.log("app.quickSwitchTrigger.alreadyRunning")
            return
        }

        let service = CGEventTapTriggerService(
            overlayVisibleProvider: { [weak self] in
                self?.quickSwitchSessionController?.isVisible ?? false
            },
            accessibilityTrustedProvider: { [weak self] in
                self?.accessibilityStatus() == .granted
            }
        )
        service.onQuickSwitch = { [weak self] in
            self?.openQuickSwitchFromTrigger()
        }
        service.onStartFailure = { error in
            DevelopmentDiagnostics.log("app.quickSwitchTrigger.startFailure", DevelopmentDiagnostics.errorSummaryFields(error))
            fputs("Round01 Quick Switch trigger service failed: \(DevelopmentDiagnostics.errorSummaryString(error))\n", stderr)
        }
        service.start()

        if service.isRunning {
            quickSwitchTriggerService = service
        }
        DevelopmentDiagnostics.log("app.quickSwitchTrigger.startResult", [
            "isRunning": service.isRunning
        ])
    }

    private func stopQuickSwitchTriggerService(reason: String) {
        guard let quickSwitchTriggerService else {
            DevelopmentDiagnostics.log("app.quickSwitchTrigger.stopSkipped", [
                "reason": reason,
                "state": "not-running"
            ])
            return
        }

        quickSwitchTriggerService.stop()
        self.quickSwitchTriggerService = nil
        DevelopmentDiagnostics.log("app.quickSwitchTrigger.stopped", [
            "reason": reason
        ])
        fputs("Round01 Quick Switch trigger service stopped: \(reason)\n", stderr)
    }

    private func showQuickSwitchIfRequested() {
        guard round1QuickSwitchOptions.openQuickSwitch || round1QuickSwitchOptions.lifecycleCycles != nil else { return }
        DevelopmentDiagnostics.log("app.quickSwitch.launchOptionRequested", [
            "openQuickSwitch": round1QuickSwitchOptions.openQuickSwitch,
            "lifecycleCycles": round1QuickSwitchOptions.lifecycleCycles
        ])

        if let lifecycleCycles = round1QuickSwitchOptions.lifecycleCycles {
            runRound1QuickSwitchLifecycle(cycles: lifecycleCycles)
            return
        }

        openQuickSwitchFromTrigger()
        writeQuickSwitchReportIfNeeded()

        if let autoHideAfter = round1QuickSwitchOptions.autoHideAfter {
            DispatchQueue.main.asyncAfter(deadline: .now() + autoHideAfter) { [weak self] in
                self?.hideQuickSwitchSession(reason: .timeout)
            }
        }

        if let quitAfter = round1QuickSwitchOptions.quitAfter {
            DispatchQueue.main.asyncAfter(deadline: .now() + quitAfter) {
                NSApp.terminate(nil)
            }
        }
    }

    private func runRound1QuickSwitchLifecycle(cycles: Int) {
        DevelopmentDiagnostics.log("app.quickSwitch.lifecycle.start", [
            "cycles": cycles
        ])
        isRunningQuickSwitchLifecycle = true
        if quickSwitchSessionController == nil {
            let controller = QuickSwitchSessionController(
                snapshotLoader: quickSwitchSnapshotLoader(),
                windowActivationService: quickSwitchWindowActivationService(),
                disableScreenshotRefresh: round1QuickSwitchOptions.disableScreenshotRefresh,
                debugHoveredAppGroupIndex: round1QuickSwitchOptions.debugHoveredAppGroupIndex,
                debugOverlayWidth: round1QuickSwitchOptions.debugOverlayWidth,
                debugKeySequence: round1QuickSwitchOptions.debugKeySequence,
                debugMouseSequence: round1QuickSwitchOptions.debugMouseSequence,
                debugSystemCriticalAfter: round1QuickSwitchOptions.debugSystemCriticalAfter
            )
            controller.onSnapshotUpdated = { [weak self] in
                self?.writeQuickSwitchReportIfNeeded()
            }
            quickSwitchSessionController = controller
        }

        quickSwitchSessionController?.prepareLifecycleRun(targetCycles: cycles)
        runRound1QuickSwitchLifecycleCycle(currentCycle: 0, totalCycles: max(0, cycles))
    }

    private func runRound1QuickSwitchLifecycleCycle(currentCycle: Int, totalCycles: Int) {
        guard currentCycle < totalCycles else {
            DevelopmentDiagnostics.log("app.quickSwitch.lifecycle.complete", [
                "totalCycles": totalCycles
            ])
            quickSwitchSessionController?.finalizeLifecycleRun()
            isRunningQuickSwitchLifecycle = false
            writeQuickSwitchReportIfNeeded()
            NSApp.terminate(nil)
            return
        }

        openQuickSwitchSession()

        DispatchQueue.main.asyncAfter(deadline: .now() + round1QuickSwitchOptions.lifecycleVisibleDuration) { [weak self] in
            guard let self else { return }
            self.hideQuickSwitchSession(reason: .timeout)
            self.quickSwitchSessionController?.markLifecycleCycleCompleted()

            DispatchQueue.main.asyncAfter(deadline: .now() + self.round1QuickSwitchOptions.lifecycleInterval) { [weak self] in
                self?.runRound1QuickSwitchLifecycleCycle(currentCycle: currentCycle + 1, totalCycles: totalCycles)
            }
        }
    }

    private func openQuickSwitchFromTrigger() {
        let accessibility = accessibilityStatus()
        DevelopmentDiagnostics.log("app.quickSwitch.openFromTrigger", [
            "accessibility": permissionStatusString(accessibility),
            "screenRecording": permissionStatusString(screenRecordingStatus()),
            "alreadyVisible": quickSwitchSessionController?.isVisible ?? false
        ])
        guard accessibility == .granted else {
            stopQuickSwitchTriggerService(reason: "quick-switch-trigger-without-accessibility")
            hideQuickSwitchSession(reason: .userClosed)
            showPermissions()
            return
        }

        if quickSwitchSessionController?.isVisible == true {
            quickSwitchSessionController?.retriggerFromShortcut()
        } else {
            openQuickSwitchSession()
        }
    }

    private func openQuickSwitchSession() {
        if quickSwitchSessionController == nil {
            DevelopmentDiagnostics.log("app.quickSwitch.createSessionController")
            let controller = QuickSwitchSessionController(
                snapshotLoader: quickSwitchSnapshotLoader(),
                windowActivationService: quickSwitchWindowActivationService(),
                disableScreenshotRefresh: round1QuickSwitchOptions.disableScreenshotRefresh,
                debugHoveredAppGroupIndex: round1QuickSwitchOptions.debugHoveredAppGroupIndex,
                debugOverlayWidth: round1QuickSwitchOptions.debugOverlayWidth,
                debugKeySequence: round1QuickSwitchOptions.debugKeySequence,
                debugMouseSequence: round1QuickSwitchOptions.debugMouseSequence,
                debugSystemCriticalAfter: round1QuickSwitchOptions.debugSystemCriticalAfter
            )
            controller.onSnapshotUpdated = { [weak self] in
                self?.writeQuickSwitchReportIfNeeded()
            }
            quickSwitchSessionController = controller
        }

        DevelopmentDiagnostics.log("app.quickSwitch.session.showRequested")
        quickSwitchSessionController?.show()
        writeQuickSwitchReportIfNeeded()
    }

    private func hideQuickSwitchSession(reason: DismissReason = .userClosed) {
        DevelopmentDiagnostics.log("app.quickSwitch.session.hideRequested", [
            "reason": Self.dismissReasonString(reason),
            "wasVisible": quickSwitchSessionController?.isVisible ?? false
        ])
        quickSwitchSessionController?.hide(reason: reason)
    }

    private func writeQuickSwitchReportIfNeeded() {
        guard let reportPath = round1QuickSwitchOptions.reportPath else { return }
        guard !isRunningQuickSwitchLifecycle else { return }
        let reportPathSummary = DevelopmentDiagnostics.pathSummary(reportPath)

        let report = quickSwitchSessionController?.reportDictionary() ?? [
            "snapshotLoaded": false,
            "quickSwitchVisible": false,
            "permissionStatus": "\(accessibilityStatus())",
            "diagnosticLogLocationKind": DevelopmentDiagnostics.pathSummary(DevelopmentDiagnostics.logPath)["locationKind"] ?? "unknown",
            "diagnosticLogBasename": DevelopmentDiagnostics.pathSummary(DevelopmentDiagnostics.logPath)["basename"] ?? "aligner-dev.log"
        ]

        do {
            let data = try JSONSerialization.data(withJSONObject: report, options: [.prettyPrinted, .sortedKeys])
            try data.write(to: URL(fileURLWithPath: reportPath), options: [.atomic])
            DevelopmentDiagnostics.log("app.quickSwitch.reportWritten", [
                "reportPathKind": reportPathSummary["locationKind"],
                "reportPathBasename": reportPathSummary["basename"],
                "reportPathHash": reportPathSummary["fingerprint"]
            ])
        } catch {
            var fields: [String: CustomStringConvertible?] = [
                "reportPathKind": reportPathSummary["locationKind"],
                "reportPathBasename": reportPathSummary["basename"],
                "reportPathHash": reportPathSummary["fingerprint"]
            ]
            DevelopmentDiagnostics.errorSummaryFields(error).forEach { fields[$0.key] = $0.value }
            DevelopmentDiagnostics.log("app.quickSwitch.reportFailed", fields)
            fputs("Round01 Quick Switch report failed: \(DevelopmentDiagnostics.errorSummaryString(error))\n", stderr)
        }
    }

    private func accessibilityStatus() -> PermissionStatus {
        if round1QuickSwitchOptions.simulateAccessibilityDenied {
            return .notGranted
        }

        return permissionChecker.status(for: .accessibility)
    }

    private func screenRecordingStatus() -> PermissionStatus {
        if round1QuickSwitchOptions.simulateScreenRecordingDenied {
            return .notGranted
        }

        return permissionChecker.status(for: .screenRecording)
    }

    private func quickSwitchSnapshotLoader() -> any QuickSwitchSnapshotLoading {
        if round1QuickSwitchOptions.fixtureActivation {
            return FixtureQuickSwitchSnapshotLoader(appCount: 2, windowsPerApp: 1, mode: .activation)
        }

        if round1QuickSwitchOptions.fixtureCandidateFiltering {
            return FixtureQuickSwitchSnapshotLoader(appCount: 1, mode: .candidateFiltering)
        }

        if let fixtureAppCount = round1QuickSwitchOptions.fixtureAppCount {
            return FixtureQuickSwitchSnapshotLoader(
                appCount: fixtureAppCount,
                windowsPerApp: round1QuickSwitchOptions.fixtureWindowsPerApp ?? 1,
                mode: .layout
            )
        }

        let liveLoader = LiveQuickSwitchSnapshotLoader()
        guard let delay = round1QuickSwitchOptions.snapshotLoadDelay else {
            return liveLoader
        }

        return DelayedQuickSwitchSnapshotLoader(base: liveLoader, delay: delay)
    }

    private func quickSwitchWindowActivationService() -> any WindowActivationServiceProtocol {
        if round1QuickSwitchOptions.debugWindowActivation {
            return DebugWindowActivationService()
        }

        return CGWindowAXWindowService(spaceIDsByWindowIDProvider: { _ in [:] })
    }

    @objc private func showPermissions() {
        DevelopmentDiagnostics.log("app.permissions.show", [
            "accessibility": permissionStatusString(accessibilityStatus()),
            "screenRecording": permissionStatusString(screenRecordingStatus())
        ])
        NSApp.setActivationPolicy(.regular)
        stopQuickSwitchTriggerService(reason: "show-permissions")
        hideQuickSwitchSession(reason: .userClosed)

        if permissionWindowController == nil {
            let controller = PermissionsWindowController(permissionManager: permissionChecker)
            controller.onPermissionStatusChanged = { [weak self] kind, status in
                DevelopmentDiagnostics.log("app.permissions.statusChanged", [
                    "kind": kind.rawValue,
                    "status": self?.permissionStatusString(status) ?? "\(status)"
                ])
                guard kind == .accessibility else { return }

                switch status {
                case .granted:
                    self?.startQuickSwitchTriggerServiceIfPossible()
                case .notGranted:
                    self?.stopQuickSwitchTriggerService(reason: "accessibility-status-not-granted")
                    self?.hideQuickSwitchSession(reason: .userClosed)
                }
            }
            controller.onPermissionRequestStarted = { [weak self] kind in
                DevelopmentDiagnostics.log("app.permissions.requestStarted", [
                    "kind": kind.rawValue
                ])
                if kind == .accessibility {
                    self?.stopQuickSwitchTriggerService(reason: "accessibility-request-started")
                    self?.hideQuickSwitchSession(reason: .userClosed)
                }
            }
            permissionWindowController = controller
        }

        permissionWindowController?.showWindow(nil)
        permissionWindowController?.window?.makeKeyAndOrderFront(nil)
        NSApp.activate(ignoringOtherApps: true)
        DevelopmentDiagnostics.log("app.permissions.windowShown", [
            "activationPolicy": NSApp.activationPolicy().rawValue,
            "windowVisible": permissionWindowController?.window?.isVisible ?? false
        ])
    }

    @objc private func showRound0DebugOverlay() {
        DevelopmentDiagnostics.log("app.menu.showRound0DebugOverlay")
        showDebugOverlay(includeSettingsChild: true)
    }

    @objc private func hideRound0DebugOverlay() {
        DevelopmentDiagnostics.log("app.menu.hideRound0DebugOverlay")
        hideDebugOverlay()
    }

    @objc private func showQuickSwitchFromMenu() {
        DevelopmentDiagnostics.log("app.menu.showQuickSwitch")
        openQuickSwitchFromTrigger()
    }

    @objc private func hideQuickSwitchFromMenu() {
        DevelopmentDiagnostics.log("app.menu.hideQuickSwitch")
        hideQuickSwitchSession()
    }

    private func hideRound1PerformancePoC() {
        round1PerformancePoCController?.hide()
    }

    private func writeRound1PerformancePoCReportIfNeeded() {
        guard let reportPath = round1PoCOptions.reportPath,
              let report = round1PerformancePoCController?.reportDictionary()
        else { return }
        let reportPathSummary = DevelopmentDiagnostics.pathSummary(reportPath)

        do {
            let data = try JSONSerialization.data(withJSONObject: report, options: [.prettyPrinted, .sortedKeys])
            try data.write(to: URL(fileURLWithPath: reportPath), options: [.atomic])
            DevelopmentDiagnostics.log("app.performancePoC.reportWritten", [
                "reportPathKind": reportPathSummary["locationKind"],
                "reportPathBasename": reportPathSummary["basename"],
                "reportPathHash": reportPathSummary["fingerprint"]
            ])
        } catch {
            var fields: [String: CustomStringConvertible?] = [
                "reportPathKind": reportPathSummary["locationKind"],
                "reportPathBasename": reportPathSummary["basename"],
                "reportPathHash": reportPathSummary["fingerprint"]
            ]
            DevelopmentDiagnostics.errorSummaryFields(error).forEach { fields[$0.key] = $0.value }
            DevelopmentDiagnostics.log("app.performancePoC.reportFailed", fields)
            fputs("Round01 performance PoC report failed: \(DevelopmentDiagnostics.errorSummaryString(error))\n", stderr)
        }
    }

    private func showDebugOverlay(includeSettingsChild: Bool) {
        DevelopmentDiagnostics.log("app.debugOverlay.show", [
            "includeSettingsChild": includeSettingsChild
        ])
        if debugOverlayController == nil {
            debugOverlayController = Round0DebugOverlayController()
        }

        debugOverlayController?.show(includeSettingsChild: includeSettingsChild)
    }

    private func hideDebugOverlay() {
        DevelopmentDiagnostics.log("app.debugOverlay.hide")
        debugOverlayController?.hide()
    }

    private func openRound1PerformancePoC(autoCycle: Bool) {
        DevelopmentDiagnostics.log("app.performancePoC.show", [
            "autoCycle": autoCycle
        ])
        if round1PerformancePoCController == nil {
            round1PerformancePoCController = Round1PerformancePoCController()
        }

        round1PerformancePoCController?.show(autoCycle: autoCycle)
    }

    @objc private func quit() {
        DevelopmentDiagnostics.logSync("app.menu.quit")
        NSApp.terminate(nil)
    }

    private func permissionStatusString(_ status: PermissionStatus) -> String {
        switch status {
        case .granted:
            return "granted"
        case .notGranted:
            return "notGranted"
        }
    }

    private static func dismissReasonString(_ reason: DismissReason) -> String {
        switch reason {
        case .escape:
            return "escape"
        case .focusLost:
            return "focusLost"
        case .systemCriticalWindow:
            return "systemCriticalWindow"
        case .timeout:
            return "timeout"
        case .userClosed:
            return "userClosed"
        }
    }
}
