import AppKit
import QuartzCore
import SwiftUI

struct WindowFrameCalculator {
    static let horizontalPadding: CGFloat = 40
    static let verticalPadding: CGFloat = 20

    static func targetFrame(
        screenFrame: CGRect,
        currentWindowFrame: CGRect,
        notchState: NotchState,
        contentSize: CGSize,
        centerHorizontally: Bool
    ) -> CGRect {
        let windowWidth = contentSize.width + horizontalPadding
        let windowHeight = contentSize.height + verticalPadding
        let centeredX = screenFrame.origin.x + ((screenFrame.width - windowWidth) / 2)

        let x: CGFloat
        if centerHorizontally || notchState == .closed {
            x = centeredX
        } else {
            x = min(
                max(currentWindowFrame.minX, screenFrame.minX),
                screenFrame.maxX - windowWidth
            )
        }

        return CGRect(
            x: x,
            y: screenFrame.origin.y + screenFrame.height - windowHeight,
            width: windowWidth,
            height: windowHeight
        )
    }
}

@MainActor
final class WindowCoordinator {
    private let focusRetryDelay: TimeInterval
    private let presetResizeFrameInterval: TimeInterval
    private let screenLookup: @MainActor (ScreenID) -> NSScreen?
    private let applicationActivator: @MainActor () -> Void
    private let hotkeyOpenStateHandler: @MainActor (Bool) -> Void

    private(set) var windows: [ScreenID: NotchWindow] = [:]
    private var presetResizeTimers: [ScreenID: Timer] = [:]

    init(
        focusRetryDelay: TimeInterval = 0.01,
        presetResizeFrameInterval: TimeInterval = 1.0 / 60.0,
        screenLookup: @escaping @MainActor (ScreenID) -> NSScreen? = { screenID in
            NSScreen.screens.first { $0.displayUUID == screenID }
        },
        applicationActivator: @escaping @MainActor () -> Void = {
            NSApp.activate(ignoringOtherApps: true)
        },
        hotkeyOpenStateHandler: @escaping @MainActor (Bool) -> Void = { isOpen in
            HotkeyManager.shared.isNotchOpen = isOpen
        }
    ) {
        self.focusRetryDelay = focusRetryDelay
        self.presetResizeFrameInterval = presetResizeFrameInterval
        self.screenLookup = screenLookup
        self.applicationActivator = applicationActivator
        self.hotkeyOpenStateHandler = hotkeyOpenStateHandler
    }

    func hasWindow(for screenID: ScreenID) -> Bool {
        windows[screenID] != nil
    }

    func windowScreenIDs() -> Set<ScreenID> {
        Set(windows.keys)
    }

    func createWindow(
        on screen: NSScreen,
        context: ScreenContext,
        contentView: NSView,
        onResignKey: @escaping () -> Void
    ) {
        let initialFrame = WindowFrameCalculator.targetFrame(
            screenFrame: screen.frame,
            currentWindowFrame: .zero,
            notchState: context.notchState,
            contentSize: context.openNotchSize,
            centerHorizontally: true
        )

        let window = NotchWindow(
            contentRect: initialFrame,
            styleMask: [.borderless, .nonactivatingPanel, .utilityWindow],
            backing: .buffered,
            defer: false
        )

        window.onResignKey = onResignKey

        let containerView = NSView(frame: NSRect(origin: .zero, size: initialFrame.size))
        containerView.autoresizesSubviews = true
        containerView.wantsLayer = true
        containerView.layer?.backgroundColor = NSColor.clear.cgColor

        contentView.frame = containerView.bounds
        contentView.autoresizingMask = [.width, .height]
        containerView.addSubview(contentView)
        window.contentView = containerView

        windows[context.id] = window

        updateWindowFrame(for: context.id, context: context, centerHorizontally: true)
        window.orderFrontRegardless()
    }

    func repositionWindow(for screenID: ScreenID, context: ScreenContext, centerHorizontally: Bool = false) {
        updateWindowFrame(for: screenID, context: context, centerHorizontally: centerHorizontally)
    }

    func updateWindowFrame(
        for screenID: ScreenID,
        context: ScreenContext,
        contentSize: CGSize? = nil,
        centerHorizontally: Bool = false
    ) {
        guard let screen = screenLookup(screenID),
              let window = windows[screenID] else {
            return
        }

        let frame = WindowFrameCalculator.targetFrame(
            screenFrame: screen.frame,
            currentWindowFrame: window.frame,
            notchState: context.notchState,
            contentSize: resolvedContentSize(for: context, override: contentSize),
            centerHorizontally: centerHorizontally
        )

        guard !window.frame.equalTo(frame) else { return }
        window.setFrame(frame, display: false)
    }

    func animatePresetResize(
        for screenID: ScreenID,
        context: ScreenContext,
        from startSize: CGSize,
        to targetSize: CGSize,
        duration: TimeInterval
    ) {
        cancelPresetResize(for: screenID)

        guard startSize != targetSize else {
            context.notchSize = targetSize
            updateWindowFrame(for: screenID, context: context, contentSize: targetSize, centerHorizontally: true)
            return
        }

        context.isPresetResizing = true
        let startTime = CACurrentMediaTime()
        let frameInterval = max(duration, presetResizeFrameInterval)

        let timer = Timer(timeInterval: presetResizeFrameInterval, repeats: true) { [weak self] timer in
            MainActor.assumeIsolated {
                guard let self else {
                    timer.invalidate()
                    return
                }

                let elapsed = CACurrentMediaTime() - startTime
                let progress = min(1, elapsed / frameInterval)
                let easedProgress = 0.5 - (cos(.pi * progress) / 2)
                let size = CGSize(
                    width: startSize.width + ((targetSize.width - startSize.width) * easedProgress),
                    height: startSize.height + ((targetSize.height - startSize.height) * easedProgress)
                )

                context.notchSize = size
                self.updateWindowFrame(for: screenID, context: context, contentSize: size, centerHorizontally: true)

                if progress >= 1 {
                    context.notchSize = targetSize
                    context.isPresetResizing = false
                    self.updateWindowFrame(for: screenID, context: context, contentSize: targetSize, centerHorizontally: true)
                    self.presetResizeTimers[screenID] = nil
                    timer.invalidate()
                }
            }
        }

        presetResizeTimers[screenID] = timer
        RunLoop.main.add(timer, forMode: .common)
        timer.fire()
    }

    func presentOpen(
        for screenID: ScreenID,
        terminalViewProvider: @escaping @MainActor () -> NSView?
    ) {
        guard let window = windows[screenID] else { return }

        window.isNotchOpen = true
        updateHotkeyOpenState()
        applicationActivator()
        window.makeKeyAndOrderFront(nil)

        focusActiveTerminal(
            in: screenID,
            attemptsRemaining: 12,
            terminalViewProvider: terminalViewProvider
        )
    }

    func focusActiveTerminal(
        for screenID: ScreenID,
        terminalViewProvider: @escaping @MainActor () -> NSView?
    ) {
        focusActiveTerminal(
            in: screenID,
            attemptsRemaining: 12,
            terminalViewProvider: terminalViewProvider
        )
    }

    func presentClose(for screenID: ScreenID) {
        guard let window = windows[screenID] else { return }

        window.isNotchOpen = false
        updateHotkeyOpenState()
    }

    func cleanupAllWindows() {
        for timer in presetResizeTimers.values {
            timer.invalidate()
        }
        presetResizeTimers.removeAll()

        for window in windows.values {
            window.orderOut(nil)
            window.close()
        }

        windows.removeAll()
        updateHotkeyOpenState()
    }

    private func focusActiveTerminal(
        in screenID: ScreenID,
        attemptsRemaining: Int,
        terminalViewProvider: @escaping @MainActor () -> NSView?
    ) {
        guard let window = windows[screenID],
              let terminalView = terminalViewProvider() else {
            return
        }

        if terminalView.window === window {
            window.makeFirstResponder(terminalView)
            return
        }

        guard attemptsRemaining > 0 else { return }

        DispatchQueue.main.asyncAfter(deadline: .now() + focusRetryDelay) { [weak self] in
            Task { @MainActor in
                self?.focusActiveTerminal(
                    in: screenID,
                    attemptsRemaining: attemptsRemaining - 1,
                    terminalViewProvider: terminalViewProvider
                )
            }
        }
    }

    private func cancelPresetResize(for screenID: ScreenID) {
        presetResizeTimers[screenID]?.invalidate()
        presetResizeTimers[screenID] = nil
    }

    private func resolvedContentSize(for context: ScreenContext, override: CGSize?) -> CGSize {
        if let override {
            return override
        }

        return context.notchState == .open ? context.notchSize : context.openNotchSize
    }

    private func updateHotkeyOpenState() {
        hotkeyOpenStateHandler(windows.values.contains(where: \.isNotchOpen))
    }
}
