import AppKit
import Combine
import SwiftUI

/// Coordinates screen/workspace state with notch lifecycle and
/// delegates raw window work to `WindowCoordinator`.
@MainActor
final class ScreenManager: ObservableObject {
    static let shared = ScreenManager()

    private let screenRegistry = ScreenRegistry.shared
    private let workspaceRegistry = WorkspaceRegistry.shared
    private let windowCoordinator = WindowCoordinator()
    private lazy var orchestrator = NotchOrchestrator(screenRegistry: screenRegistry, host: self)

    private var cancellables = Set<AnyCancellable>()

    private init() {}

    private var showOnAllDisplays: Bool {
        AppSettingsController.shared.settings.display.showOnAllDisplays
    }

    // MARK: - Lifecycle

    func start() {
        screenRegistry.refreshConnectedScreens()
        observeScreenChanges()
        rebuildWindows()
        setupHotkeys()
    }

    func stop() {
        cleanupAllWindows()
        cancellables.removeAll()
        HotkeyManager.shared.stop()
    }

    // MARK: - Hotkey wiring

    private func setupHotkeys() {
        let hotkeyManager = HotkeyManager.shared

        hotkeyManager.onToggle = { [weak self] in
            MainActor.assumeIsolated { self?.orchestrator.toggleOnActiveScreen() }
        }
        hotkeyManager.onNewTab = { [weak self] in
            MainActor.assumeIsolated { self?.activeWorkspace().newTab() }
        }
        hotkeyManager.onCloseTab = { [weak self] in
            MainActor.assumeIsolated { self?.activeWorkspace().closeActiveTab() }
        }
        hotkeyManager.onNextTab = { [weak self] in
            MainActor.assumeIsolated { self?.activeWorkspace().nextTab() }
        }
        hotkeyManager.onPreviousTab = { [weak self] in
            MainActor.assumeIsolated { self?.activeWorkspace().previousTab() }
        }
        hotkeyManager.onNextWorkspace = { [weak self] in
            MainActor.assumeIsolated { self?.switchWorkspace(offset: 1) }
        }
        hotkeyManager.onPreviousWorkspace = { [weak self] in
            MainActor.assumeIsolated { self?.switchWorkspace(offset: -1) }
        }
        hotkeyManager.onDetachTab = { [weak self] in
            MainActor.assumeIsolated { self?.detachActiveTab() }
        }
        hotkeyManager.onApplySizePreset = { [weak self] preset in
            MainActor.assumeIsolated { self?.applySizePreset(preset) }
        }
        hotkeyManager.onSwitchToTab = { [weak self] index in
            MainActor.assumeIsolated { self?.activeWorkspace().switchToTab(at: index) }
        }
        hotkeyManager.onSwitchToWorkspace = { [weak self] workspaceID in
            MainActor.assumeIsolated { self?.switchActiveScreen(to: workspaceID) }
        }

        hotkeyManager.start()
    }

    // MARK: - Toggle

    func toggleNotchOnActiveScreen() {
        orchestrator.toggleOnActiveScreen()
    }

    // MARK: - Open / Close

    func openNotch(screenID: ScreenID) {
        orchestrator.open(screenID: screenID)
    }

    func closeNotch(screenID: ScreenID) {
        orchestrator.close(screenID: screenID)
    }

    private func detachActiveTab() {
        if let session = activeWorkspace().detachActiveTab() {
            DispatchQueue.main.async {
                PopoutWindowController.shared.popout(session: session)
            }
        }
    }

    private func switchWorkspace(offset: Int) {
        guard let screenID = screenRegistry.activeScreenID() else { return }
        let currentWorkspaceID = screenRegistry.screenContext(for: screenID)?.workspaceID ?? workspaceRegistry.defaultWorkspaceID
        let nextWorkspaceID = offset >= 0
            ? workspaceRegistry.nextWorkspaceID(after: currentWorkspaceID)
            : workspaceRegistry.previousWorkspaceID(before: currentWorkspaceID)

        guard let nextWorkspaceID else { return }
        switchScreen(screenID, to: nextWorkspaceID)
    }

    private func switchActiveScreen(to workspaceID: WorkspaceID) {
        guard let screenID = screenRegistry.activeScreenID() else { return }
        switchScreen(screenID, to: workspaceID)
    }

    private func switchScreen(_ screenID: ScreenID, to workspaceID: WorkspaceID) {
        screenRegistry.assignWorkspace(workspaceID, to: screenID)

        guard let context = screenRegistry.screenContext(for: screenID),
              context.notchState == .open else {
            return
        }

        orchestrator.open(screenID: screenID)
    }

    func applySizePreset(_ preset: TerminalSizePreset) {
        guard let context = screenRegistry.allScreens().first(where: { $0.notchState == .open }) else {
            AppSettingsController.shared.update {
                $0.display.openWidth = preset.width
                $0.display.openHeight = preset.height
            }
            return
        }

        let startSize = context.notchSize
        let targetSize = context.setStoredOpenSize(preset.size)
        windowCoordinator.animatePresetResize(
            for: context.id,
            context: context,
            from: startSize,
            to: targetSize,
            duration: context.openAnimationDuration
        )
    }

    // MARK: - Window creation

    func rebuildWindows() {
        cleanupAllWindows()
        screenRegistry.refreshConnectedScreens()

        for screen in visibleScreens() {
            createWindow(for: screen)
        }
    }

    private func createWindow(for screen: NSScreen) {
        let screenID = screen.displayUUID
        guard let context = screenRegistry.screenContext(for: screenID) else { return }

        context.requestOpen = { [weak self] in
            self?.orchestrator.open(screenID: screenID)
        }
        context.requestClose = { [weak self] in
            self?.orchestrator.close(screenID: screenID)
        }
        context.requestWindowResize = { [weak self] in
            guard let self,
                  let context = self.screenRegistry.screenContext(for: screenID) else {
                return
            }

            self.windowCoordinator.updateWindowFrame(
                for: screenID,
                context: context,
                centerHorizontally: true
            )
        }
        context.requestTerminalFocus = { [weak self] in
            guard let self else { return }

            self.windowCoordinator.focusActiveTerminal(for: screenID) { [weak self] in
                self?.screenRegistry.workspaceController(for: screenID).activeTab?.terminalView
            }
        }

        let hostingView = NSHostingView(
            rootView: ContentView(
                screen: context,
                orchestrator: orchestrator
            )
            .preferredColorScheme(.dark)
        )

        windowCoordinator.createWindow(
            on: screen,
            context: context,
            contentView: hostingView,
            onResignKey: { [weak self] in
                guard !context.suppressCloseOnFocusLoss else { return }
                self?.orchestrator.close(screenID: screenID)
            }
        )
    }

    // MARK: - Repositioning

    func repositionWindows() {
        screenRegistry.refreshConnectedScreens()

        for context in screenRegistry.allScreens() {
            context.refreshClosedSize()
            windowCoordinator.repositionWindow(
                for: context.id,
                context: context,
                centerHorizontally: true
            )
        }
    }

    // MARK: - Cleanup

    private func cleanupAllWindows() {
        orchestrator.cancelAllPendingWork()
        windowCoordinator.cleanupAllWindows()
    }

    // MARK: - Screen observation

    private func observeScreenChanges() {
        NotificationCenter.default.publisher(for: NSApplication.didChangeScreenParametersNotification)
            .debounce(for: .milliseconds(500), scheduler: RunLoop.main)
            .sink { [weak self] _ in
                self?.handleScreenConfigurationChange()
            }
            .store(in: &cancellables)
    }

    private func handleScreenConfigurationChange() {
        screenRegistry.refreshConnectedScreens()

        let currentScreenIDs = Set(visibleScreens().map(\.displayUUID))
        let knownScreenIDs = windowCoordinator.windowScreenIDs()

        if currentScreenIDs != knownScreenIDs {
            rebuildWindows()
        } else {
            repositionWindows()
        }
    }

    private func activeWorkspace() -> WorkspaceController {
        guard let screenID = screenRegistry.activeScreenID() else {
            return WorkspaceRegistry.shared.defaultWorkspaceController
        }

        return screenRegistry.workspaceController(for: screenID)
    }

    private func visibleScreens() -> [NSScreen] {
        if showOnAllDisplays {
            return NSScreen.screens
        }

        return [NSScreen.main].compactMap { $0 }
    }
}

extension ScreenManager: NotchPresentationHost {
    func canPresentNotch(for screenID: ScreenID) -> Bool {
        windowCoordinator.hasWindow(for: screenID)
    }

    func performOpenPresentation(for screenID: ScreenID) {
        guard screenRegistry.screenContext(for: screenID) != nil else {
            return
        }

        windowCoordinator.presentOpen(for: screenID) { [weak self] in
            self?.screenRegistry.workspaceController(for: screenID).activeTab?.terminalView
        }
    }

    func performClosePresentation(for screenID: ScreenID) {
        guard screenRegistry.screenContext(for: screenID) != nil else {
            return
        }

        windowCoordinator.presentClose(for: screenID)
    }
}
