import AppKit
import Combine

/// Application delegate that bootstraps the notch overlay system.
@MainActor
class AppDelegate: NSObject, NSApplicationDelegate {
    private enum UITestLaunchArgument {
        static let regularActivation = "--uitest-regular-activation"
        static let showSettings = "--uitest-show-settings"
        static let openNotch = "--uitest-open-notch"
    }

    private var cancellables = Set<AnyCancellable>()
    private let settingsController = AppSettingsController.shared

    func applicationDidFinishLaunching(_ notification: Notification) {
        NotchSettings.registerDefaults()

        if isRunningUITests {
            NSApp.setActivationPolicy(.regular)
        } else {
            NSApp.setActivationPolicy(.accessory)
        }

        // Sync the launch-at-login toggle with the actual system state
        // in case the user toggled it from System Settings.
        settingsController.update {
            $0.display.launchAtLogin = LaunchAtLoginHelper.isEnabled
        }

        ScreenManager.shared.start()
        observeDisplayPreference()
        observeSizePreferences()
        observeFontSizeChanges()
        observeTerminalThemeChanges()
        applyUITestLaunchBehaviorIfNeeded()
    }

    func applicationWillTerminate(_ notification: Notification) {
        ScreenManager.shared.stop()
    }

    // MARK: - Preference observers

    /// Only rebuild windows when the display-count preference changes.
    private func observeDisplayPreference() {
        settingsController.$settings
            .map(\.display.showOnAllDisplays)
            .removeDuplicates()
            .dropFirst()
            .sink { _ in
                ScreenManager.shared.rebuildWindows()
            }
            .store(in: &cancellables)
    }

    /// Reposition (not rebuild) when any sizing preference changes.
    private func observeSizePreferences() {
        settingsController.$settings
            .map(\.display.layoutSignature)
            .removeDuplicates()
            .dropFirst()
            .debounce(for: .milliseconds(300), scheduler: RunLoop.main)
            .sink { _ in
                ScreenManager.shared.repositionWindows()
            }
            .store(in: &cancellables)
    }

    /// Live-update terminal font size across all sessions.
    private func observeFontSizeChanges() {
        settingsController.$settings
            .map(\.terminal.fontSize)
            .removeDuplicates()
            .sink { newSize in
                guard newSize > 0 else { return }
                WorkspaceRegistry.shared.updateAllWorkspacesFontSizes(CGFloat(newSize))
            }
            .store(in: &cancellables)
    }

    /// Live-update terminal colors across all sessions.
    private func observeTerminalThemeChanges() {
        settingsController.$settings
            .map(\.terminal.themeRawValue)
            .removeDuplicates()
            .sink { newTheme in
                WorkspaceRegistry.shared.updateAllWorkspacesThemes(TerminalTheme.resolve(newTheme))
            }
            .store(in: &cancellables)
    }

    private var launchArguments: [String] {
        ProcessInfo.processInfo.arguments
    }

    private var isRunningUITests: Bool {
        launchArguments.contains(UITestLaunchArgument.regularActivation)
            || launchArguments.contains(UITestLaunchArgument.showSettings)
            || launchArguments.contains(UITestLaunchArgument.openNotch)
    }

    private func applyUITestLaunchBehaviorIfNeeded() {
        guard isRunningUITests else { return }

        DispatchQueue.main.async { @MainActor in
            if self.launchArguments.contains(UITestLaunchArgument.showSettings) {
                SettingsWindowController.shared.showSettings()
            }

            if self.launchArguments.contains(UITestLaunchArgument.openNotch),
               let screenID = ScreenRegistry.shared.activeScreenID() {
                ScreenManager.shared.openNotch(screenID: screenID)
            }
        }
    }
}
