import Combine
import SwiftUI

@MainActor
protocol ScreenRegistryType: AnyObject {
    func allScreens() -> [ScreenContext]
    func screenContext(for id: ScreenID) -> ScreenContext?
    func activeScreenID() -> ScreenID?
    func presentingScreenID(for workspaceID: WorkspaceID) -> ScreenID?
    @discardableResult
    func claimWorkspacePresentation(for screenID: ScreenID) -> ScreenID?
    func releaseWorkspacePresentation(for screenID: ScreenID)
}

@MainActor
protocol NotchPresentationHost: AnyObject {
    func canPresentNotch(for screenID: ScreenID) -> Bool
    func performOpenPresentation(for screenID: ScreenID)
    func performClosePresentation(for screenID: ScreenID)
}

protocol SchedulerType {
    @MainActor
    func schedule(after interval: TimeInterval, action: @escaping @MainActor () -> Void) -> AnyCancellable
}

struct TaskScheduler: SchedulerType {
    @MainActor
    func schedule(after interval: TimeInterval, action: @escaping @MainActor () -> Void) -> AnyCancellable {
        let task = Task { @MainActor in
            try? await Task.sleep(nanoseconds: UInt64(interval * 1_000_000_000))
            guard !Task.isCancelled else { return }
            action()
        }

        return AnyCancellable {
            task.cancel()
        }
    }
}

@MainActor
final class NotchOrchestrator {
    private let screenRegistry: any ScreenRegistryType
    private weak var host: (any NotchPresentationHost)?
    private let settingsController: AppSettingsController
    private let scheduler: any SchedulerType

    private var hoverOpenTasks: [ScreenID: AnyCancellable] = [:]
    private var closeTransitionTasks: [ScreenID: AnyCancellable] = [:]

    init(
        screenRegistry: any ScreenRegistryType,
        host: any NotchPresentationHost,
        settingsController: AppSettingsController? = nil,
        scheduler: (any SchedulerType)? = nil
    ) {
        self.screenRegistry = screenRegistry
        self.host = host
        self.settingsController = settingsController ?? AppSettingsController.shared
        self.scheduler = scheduler ?? TaskScheduler()
    }

    func toggleOnActiveScreen() {
        guard let screenID = screenRegistry.activeScreenID(),
              host?.canPresentNotch(for: screenID) == true,
              let context = screenRegistry.screenContext(for: screenID) else {
            return
        }

        if context.notchState == .open {
            close(screenID: screenID)
        } else {
            open(screenID: screenID)
        }
    }

    func open(screenID: ScreenID) {
        guard host?.canPresentNotch(for: screenID) == true,
              let context = screenRegistry.screenContext(for: screenID) else {
            return
        }

        if let presentingScreenID = screenRegistry.presentingScreenID(for: context.workspaceID),
           presentingScreenID != screenID {
            close(screenID: presentingScreenID)
        }

        cancelHoverOpen(for: screenID)
        cancelCloseTransition(for: screenID)
        context.cancelCloseTransition()

        withAnimation(context.openAnimation) {
            context.open()
        }

        _ = screenRegistry.claimWorkspacePresentation(for: screenID)
        host?.performOpenPresentation(for: screenID)
    }

    func close(screenID: ScreenID) {
        guard let context = screenRegistry.screenContext(for: screenID) else { return }

        cancelHoverOpen(for: screenID)
        cancelCloseTransition(for: screenID)
        context.beginCloseTransition()

        closeTransitionTasks[screenID] = scheduler.schedule(after: context.closeInteractionLockDuration) { [weak self] in
            self?.finishCloseTransition(for: screenID)
        }

        withAnimation(context.closeAnimation) {
            context.close()
        }

        screenRegistry.releaseWorkspacePresentation(for: screenID)
        host?.performClosePresentation(for: screenID)
    }

    func handleHoverChange(_ hovering: Bool, for screenID: ScreenID) {
        guard let context = screenRegistry.screenContext(for: screenID) else { return }

        context.isHovering = hovering

        if hovering {
            scheduleHoverOpenIfNeeded(for: screenID)
        } else {
            cancelHoverOpen(for: screenID)
            context.clearHoverOpenSuppression()
        }
    }

    func cancelAllPendingWork() {
        for task in hoverOpenTasks.values {
            task.cancel()
        }
        for task in closeTransitionTasks.values {
            task.cancel()
        }

        hoverOpenTasks.removeAll()
        closeTransitionTasks.removeAll()
    }

    private func scheduleHoverOpenIfNeeded(for screenID: ScreenID) {
        cancelHoverOpen(for: screenID)

        guard let context = screenRegistry.screenContext(for: screenID) else { return }
        guard settingsController.settings.behavior.openNotchOnHover,
              context.notchState == .closed,
              !context.isCloseTransitionActive,
              !context.suppressHoverOpenUntilHoverExit,
              context.isHovering else {
            return
        }

        hoverOpenTasks[screenID] = scheduler.schedule(after: settingsController.settings.behavior.minimumHoverDuration) { [weak self] in
            guard let self,
                  let context = self.screenRegistry.screenContext(for: screenID),
                  context.isHovering,
                  context.notchState == .closed,
                  !context.isCloseTransitionActive,
                  !context.suppressHoverOpenUntilHoverExit else {
                return
            }

            self.hoverOpenTasks[screenID] = nil
            self.open(screenID: screenID)
        }
    }

    private func finishCloseTransition(for screenID: ScreenID) {
        closeTransitionTasks[screenID] = nil
        guard let context = screenRegistry.screenContext(for: screenID) else { return }

        context.endCloseTransition()
        scheduleHoverOpenIfNeeded(for: screenID)
    }

    private func cancelHoverOpen(for screenID: ScreenID) {
        hoverOpenTasks[screenID]?.cancel()
        hoverOpenTasks[screenID] = nil
    }

    private func cancelCloseTransition(for screenID: ScreenID) {
        closeTransitionTasks[screenID]?.cancel()
        closeTransitionTasks[screenID] = nil
    }
}
