import AppKit
import CodexBarCore
import SwiftUI
import Vortex

@MainActor
final class ScreenConfettiOverlayController {
    private static let overlayLifetime: TimeInterval = 5

    private let logger = CodexBarLog.logger(LogCategories.confetti)
    private var windows: [NSWindow] = []
    private var dismissalTask: Task<Void, Never>?

    func play(originInScreen origin: CGPoint?, colors: [ProviderColor]) {
        guard self.windows.isEmpty else {
            self.logger.debug("Ignoring confetti trigger while overlay is already active")
            return
        }

        let screens = NSScreen.screens
        guard !screens.isEmpty else {
            self.logger.error("Cannot present confetti overlay because no screens were found")
            return
        }

        let palette = colors.map { color in
            Color(red: color.red, green: color.green, blue: color.blue)
        }
        self.windows = screens.map { screen in
            let frame = screen.frame
            let localOrigin = Self.localOrigin(in: frame, from: origin)
            let contentView = ScreenConfettiOverlayView(origin: localOrigin, colors: palette)
                .allowsHitTesting(false)
            let hostingView = NSHostingView(rootView: contentView)
            hostingView.wantsLayer = true
            hostingView.layer?.backgroundColor = NSColor.clear.cgColor

            let window = ClickThroughOverlayPanel(
                contentRect: frame,
                styleMask: [.borderless, .nonactivatingPanel],
                backing: .buffered,
                defer: false,
                screen: screen)
            window.contentView = hostingView
            window.level = .statusBar
            window.collectionBehavior = [.canJoinAllSpaces, .fullScreenAuxiliary, .ignoresCycle, .stationary]
            window.backgroundColor = .clear
            window.isOpaque = false
            window.hasShadow = false
            window.ignoresMouseEvents = true
            window.acceptsMouseMovedEvents = false
            window.isMovable = false
            window.isReleasedWhenClosed = false
            window.canHide = false
            window.hidesOnDeactivate = false
            window.becomesKeyOnlyIfNeeded = false
            window.isExcludedFromWindowsMenu = true
            window.setFrame(frame, display: false)
            return window
        }

        self.logger.info(
            "Presenting confetti overlay",
            metadata: [
                "screenCount": "\(self.windows.count)",
                "originKnown": origin == nil ? "0" : "1",
            ])

        for window in self.windows {
            window.orderFrontRegardless()
        }

        self.dismissalTask = Task { @MainActor [weak self] in
            try? await Task.sleep(for: .seconds(Self.overlayLifetime))
            self?.dismiss()
        }
    }

    func dismiss() {
        self.dismissalTask?.cancel()
        self.dismissalTask = nil

        guard !self.windows.isEmpty else { return }
        for window in self.windows {
            window.orderOut(nil)
            window.close()
        }
        self.windows.removeAll(keepingCapacity: true)
    }

    private static func localOrigin(in screenFrame: CGRect, from globalOrigin: CGPoint?) -> CGPoint {
        let fallback = CGPoint(x: screenFrame.maxX - 28, y: screenFrame.maxY - 8)
        let resolved: CGPoint = if let globalOrigin, screenFrame.contains(globalOrigin) {
            globalOrigin
        } else {
            fallback
        }

        let insetFrame = screenFrame.insetBy(dx: 8, dy: 8)
        return CGPoint(
            x: min(max(resolved.x, insetFrame.minX), insetFrame.maxX) - screenFrame.minX,
            y: min(max(resolved.y, insetFrame.minY), insetFrame.maxY) - screenFrame.minY)
    }
}

private final class ClickThroughOverlayPanel: NSPanel {
    override var canBecomeKey: Bool {
        false
    }

    override var canBecomeMain: Bool {
        false
    }

    override var acceptsFirstResponder: Bool {
        false
    }
}

private struct ScreenConfettiOverlayView: View {
    private static let clockwiseRotationAngles: [Double] = [270, 234, 198, 162, 126, 90]
    private static let counterclockwiseRotationAngles: [Double] = [90, 126, 162, 198, 234, 270]

    let origin: CGPoint
    let colors: [Color]

    @Environment(\.self) private var environment
    @State private var visiblePhaseCount = 0

    var body: some View {
        GeometryReader { proxy in
            let clockwiseAngles = Array(Self.clockwiseRotationAngles.prefix(self.visiblePhaseCount).enumerated())
            let counterclockwiseAngles = Array(
                Self.counterclockwiseRotationAngles.prefix(self.visiblePhaseCount).enumerated())
            ZStack {
                ForEach(clockwiseAngles, id: \.offset) { index, angle in
                    VortexView(self.makeFireworkConfettiSystem(
                        in: proxy.size,
                        launchAngle: angle,
                        phaseIndex: index,
                        lateralOffset: -12))
                    {
                        RoundedRectangle(cornerRadius: 2, style: .continuous)
                            .fill(.white)
                            .frame(width: 10, height: 20)
                            .tag("confetti-bar")

                        Circle()
                            .fill(.white)
                            .frame(width: 9, height: 9)
                            .tag("confetti-dot")

                        Capsule(style: .continuous)
                            .fill(.white)
                            .frame(width: 8, height: 16)
                            .rotationEffect(.degrees(30))
                            .tag("confetti-pill")

                        Circle()
                            .fill(.white)
                            .frame(width: 6, height: 6)
                            .blur(radius: 1)
                            .tag("confetti-tracer")
                    }
                }

                ForEach(counterclockwiseAngles, id: \.offset) { index, angle in
                    VortexView(self.makeFireworkConfettiSystem(
                        in: proxy.size,
                        launchAngle: angle,
                        phaseIndex: index,
                        lateralOffset: 12))
                    {
                        RoundedRectangle(cornerRadius: 2, style: .continuous)
                            .fill(.white)
                            .frame(width: 10, height: 20)
                            .tag("confetti-bar")

                        Circle()
                            .fill(.white)
                            .frame(width: 9, height: 9)
                            .tag("confetti-dot")

                        Capsule(style: .continuous)
                            .fill(.white)
                            .frame(width: 8, height: 16)
                            .rotationEffect(.degrees(30))
                            .tag("confetti-pill")

                        Circle()
                            .fill(.white)
                            .frame(width: 6, height: 6)
                            .blur(radius: 1)
                            .tag("confetti-tracer")
                    }
                }
            }
            .ignoresSafeArea()
            .allowsHitTesting(false)
            .task {
                self.visiblePhaseCount = 1
                for phaseCount in 2...Self.clockwiseRotationAngles.count {
                    try? await Task.sleep(for: .milliseconds(60))
                    self.visiblePhaseCount = phaseCount
                }
            }
        }
    }

    private func makeFireworkConfettiSystem(
        in size: CGSize,
        launchAngle: Double,
        phaseIndex: Int,
        lateralOffset: CGFloat)
        -> VortexSystem
    {
        let canvasOrigin = self.canvasOrigin(in: size, lateralOffset: lateralOffset)
        let normalizedX = size.width > 0 ? canvasOrigin.x / size.width : 1
        let normalizedY = size.height > 0 ? canvasOrigin.y / size.height : 0
        let resolvedColors = self.colors.map { color -> VortexSystem.Color in
            let components = color.resolve(in: self.environment)
            return VortexSystem.Color(
                red: Double(components.red),
                green: Double(components.green),
                blue: Double(components.blue),
                opacity: Double(components.opacity))
        }

        let explosion = VortexSystem(
            tags: ["confetti-bar", "confetti-dot", "confetti-pill"],
            spawnOccasion: .onDeath,
            shape: .point,
            birthRate: 24000,
            emissionLimit: 42,
            emissionDuration: 0.08,
            idleDuration: 10,
            lifespan: 4.2,
            speed: 0.72,
            speedVariation: 0.44,
            angleRange: .degrees(360),
            acceleration: [0, 0.32],
            dampingFactor: 0.18,
            angularSpeed: [0, 0, 3],
            angularSpeedVariation: [2, 2, 14],
            colors: .random(resolvedColors),
            size: 0.74,
            sizeVariation: 0.26,
            sizeMultiplierAtDeath: 0.94,
            stretchFactor: 0.82)

        return VortexSystem(
            tags: ["confetti-tracer"],
            secondarySystems: [explosion],
            position: [normalizedX, normalizedY],
            shape: .point,
            birthRate: 18,
            emissionLimit: 4,
            emissionDuration: 0.22,
            idleDuration: 10,
            lifespan: 0.58 + (Double(phaseIndex) * 0.03),
            speed: 1.36 + (Double(phaseIndex) * 0.04),
            speedVariation: 0.12,
            angle: .degrees(launchAngle),
            angleRange: .degrees(12),
            acceleration: [0, 0.12],
            dampingFactor: 0.06,
            angularSpeed: [0, 0, 6],
            angularSpeedVariation: [1, 1, 8],
            colors: .single(.white),
            size: 0.34,
            sizeVariation: 0.08,
            sizeMultiplierAtDeath: 0.4,
            stretchFactor: 1.3)
    }

    private func canvasOrigin(in size: CGSize, lateralOffset: CGFloat = 0) -> CGPoint {
        CGPoint(
            x: min(max(self.origin.x + lateralOffset, 0), size.width),
            y: min(max(size.height - self.origin.y + 18, 0), size.height))
    }
}
