import AppKit
import SwiftUI
import Combine

final class DesktopWidgetWindowManager: ObservableObject {
    static let shared = DesktopWidgetWindowManager()

    private let storageKey = "meetings.widget.instancesData"
    private var windowsByInstanceID: [UUID: NSPanel] = [:]
    @Published private(set) var instancesChangeToken: Int = 0
    @Published private(set) var authStateRefreshToken: Int = 0

    private init() {}

    private struct StoredWidgetMeetingSnapshot: Decodable {
        let id: String
    }

    func hasInstance(variantID: String) -> Bool {
        loadInstances().contains { $0.variantID == variantID }
    }

    func show(instance: WidgetInstance) {
        let variant = WidgetTemplates.variant(for: instance.variantID)
        var resolved = instance
        if resolved.origin == nil {
            resolved.origin = nextAutoPlacement(for: variant)
        }

        let panel = makePanel(for: resolved, variant: variant)
        windowsByInstanceID[resolved.id] = panel
        persistUpsert(instance: resolved)
        if let origin = resolved.origin {
            panel.setFrameOrigin(NSPoint(x: origin.x, y: origin.y))
            persistOrigin(instanceID: resolved.id, origin: panel.frame.origin)
        } else {
            panel.center()
            persistOrigin(instanceID: resolved.id, origin: panel.frame.origin)
        }
        panel.orderFront(nil)
        panel.orderFrontRegardless()
        instancesChangeToken += 1
    }

    @discardableResult
    func removeInstances(variantID: String) -> Int {
        let instances = loadInstances()
        let removed = instances.filter { $0.variantID == variantID }
        let remaining = instances.filter { $0.variantID != variantID }
        for instance in removed {
            if let panel = windowsByInstanceID.removeValue(forKey: instance.id) {
                panel.close()
            }
        }
        saveInstances(remaining)
        instancesChangeToken += 1
        return removed.count
    }

    @discardableResult
    func removeAllInstances() -> Int {
        let instances = loadInstances()
        guard !instances.isEmpty else { return 0 }
        for instance in instances {
            if let panel = windowsByInstanceID.removeValue(forKey: instance.id) {
                panel.close()
            }
        }
        saveInstances([])
        instancesChangeToken += 1
        return instances.count
    }

    func restore() {
        for instance in loadInstances() where windowsByInstanceID[instance.id] == nil {
            show(instance: instance)
        }
    }

    func refreshForAuthStateChange() {
        resizeVisiblePanelsForCurrentContent()
        authStateRefreshToken += 1
    }

    private func makePanel(for instance: WidgetInstance, variant: WidgetVariant) -> NSPanel {
        let size = panelSize(for: variant.size)
        let panel = DesktopWidgetPanel(
            contentRect: NSRect(x: 0, y: 0, width: size.width, height: size.height),
            styleMask: [.nonactivatingPanel, .borderless],
            backing: .buffered,
            defer: false
        )
        panel.becomesKeyOnlyIfNeeded = false
        panel.isReleasedWhenClosed = false
        panel.isFloatingPanel = false
        panel.isMovable = true
        panel.hasShadow = false
        panel.isOpaque = false
        panel.backgroundColor = .clear
        panel.isMovableByWindowBackground = true
        panel.hidesOnDeactivate = false
        panel.ignoresMouseEvents = false
        let desktopIconLevel = Int(CGWindowLevelForKey(.desktopIconWindow))
        panel.level = NSWindow.Level(rawValue: desktopIconLevel + 1)
        panel.collectionBehavior = [.canJoinAllSpaces, .stationary, .ignoresCycle]

        let host = NSHostingView(rootView: DesktopWidgetHostView(instanceID: instance.id, variant: variant))
        host.translatesAutoresizingMaskIntoConstraints = false
        let container = NSView()
        container.wantsLayer = true
        container.layer?.backgroundColor = NSColor.clear.cgColor
        container.layer?.cornerRadius = 22
        container.layer?.masksToBounds = true
        container.addSubview(host)
        NSLayoutConstraint.activate([
            host.leadingAnchor.constraint(equalTo: container.leadingAnchor),
            host.trailingAnchor.constraint(equalTo: container.trailingAnchor),
            host.topAnchor.constraint(equalTo: container.topAnchor),
            host.bottomAnchor.constraint(equalTo: container.bottomAnchor)
        ])
        panel.contentView = container

        NotificationCenter.default.addObserver(
            forName: NSWindow.didMoveNotification,
            object: panel,
            queue: .main
        ) { [weak self, weak panel] _ in
            guard let self, let panel else { return }
            self.persistOrigin(instanceID: instance.id, origin: panel.frame.origin)
        }
        return panel
    }

    private func panelSize(for size: WidgetSize) -> CGSize {
        switch size {
        case .small:
            return CGSize(width: 220, height: 204)
        case .medium:
            return CGSize(width: 390, height: mediumPanelHeight())
        }
    }

    private func mediumPanelHeight() -> CGFloat {
        let visibleMeetingCount = min(loadWidgetMeetingCount(), 2)
        switch visibleMeetingCount {
        case 0:
            return 252
        case 1:
            return 270
        default:
            return 288
        }
    }

    private func loadWidgetMeetingCount() -> Int {
        let key = WidgetMeetingStore.key
        guard let data = UserDefaults.standard.data(forKey: key),
              let meetings = try? JSONDecoder().decode([StoredWidgetMeetingSnapshot].self, from: data) else {
            return 0
        }
        return meetings.count
    }

    private func resizeVisiblePanelsForCurrentContent() {
        let instancesByID = Dictionary(uniqueKeysWithValues: loadInstances().map { ($0.id, $0) })
        for (instanceID, panel) in windowsByInstanceID {
            guard let instance = instancesByID[instanceID] else { continue }
            let variant = WidgetTemplates.variant(for: instance.variantID)
            let targetSize = panelSize(for: variant.size)
            let currentSize = panel.frame.size
            guard abs(currentSize.height - targetSize.height) > 0.5 || abs(currentSize.width - targetSize.width) > 0.5 else {
                continue
            }

            var frame = panel.frame
            let oldMaxY = frame.maxY
            frame.size = targetSize
            frame.origin.y = oldMaxY - targetSize.height
            panel.setFrame(frame, display: true)
            persistOrigin(instanceID: instanceID, origin: frame.origin)
        }
    }

    private func nextAutoPlacement(for variant: WidgetVariant) -> WidgetInstance.Origin {
        let size = panelSize(for: variant.size)
        let frame = NSScreen.main?.visibleFrame ?? NSRect(x: 100, y: 100, width: 1200, height: 800)
        let margin: CGFloat = 18
        let gapX: CGFloat = 22
        let gapY: CGFloat = 18
        let cellW = size.width + gapX
        let cellH = size.height + gapY
        let existingRects = loadInstances().compactMap { instance -> NSRect? in
            guard let origin = instance.origin else { return nil }
            let instanceSize = panelSize(for: instance.size)
            return NSRect(x: origin.x, y: origin.y, width: instanceSize.width, height: instanceSize.height)
        }

        let cols = max(1, Int((frame.width - margin * 2) / max(cellW, 1)))
        let rows = max(1, Int((frame.height - margin * 2) / max(cellH, 1)))
        let maxSlots = max(120, cols * rows)
        for slot in 0..<maxSlots {
            let row = slot / cols
            let col = cols - 1 - (slot % cols)
            let x = frame.minX + margin + CGFloat(col) * cellW
            let y = frame.maxY - margin - size.height - CGFloat(row) * cellH
            let candidate = NSRect(x: x, y: y, width: size.width, height: size.height)
            guard candidate.minX >= frame.minX + margin,
                  candidate.maxX <= frame.maxX - margin,
                  candidate.minY >= frame.minY + margin else {
                continue
            }
            if !existingRects.contains(where: { $0.intersects(candidate.insetBy(dx: -8, dy: -8)) }) {
                return WidgetInstance.Origin(x: candidate.minX, y: candidate.minY)
            }
        }
        return WidgetInstance.Origin(
            x: frame.midX - size.width / 2,
            y: frame.midY - size.height / 2
        )
    }

    private func close(instanceID: UUID) {
        if let panel = windowsByInstanceID.removeValue(forKey: instanceID) {
            panel.close()
        }
        let remaining = loadInstances().filter { $0.id != instanceID }
        saveInstances(remaining)
        instancesChangeToken += 1
    }

    private func loadInstances() -> [WidgetInstance] {
        guard let data = UserDefaults.standard.data(forKey: storageKey) else { return [] }
        let decoded = (try? JSONDecoder().decode([WidgetInstance].self, from: data)) ?? []
        let allowedByVariantID = Dictionary(uniqueKeysWithValues: WidgetTemplates.meetVariants.map { ($0.id, $0.size) })
        let sanitized = decoded.compactMap { instance -> WidgetInstance? in
            guard let expectedSize = allowedByVariantID[instance.variantID] else { return nil }
            var normalized = instance
            normalized.size = expectedSize
            return normalized
        }
        if sanitized != decoded {
            saveInstances(sanitized)
        }
        return sanitized
    }

    private func saveInstances(_ instances: [WidgetInstance]) {
        guard let data = try? JSONEncoder().encode(instances) else { return }
        UserDefaults.standard.set(data, forKey: storageKey)
    }

    private func persistUpsert(instance: WidgetInstance) {
        var instances = loadInstances()
        if let idx = instances.firstIndex(where: { $0.id == instance.id }) {
            instances[idx] = instance
        } else {
            instances.append(instance)
        }
        saveInstances(instances)
    }

    private func persistOrigin(instanceID: UUID, origin: NSPoint) {
        var instances = loadInstances()
        guard let idx = instances.firstIndex(where: { $0.id == instanceID }) else { return }
        instances[idx].origin = WidgetInstance.Origin(x: origin.x, y: origin.y)
        saveInstances(instances)
    }

    private struct DesktopWidgetHostView: View {
        let instanceID: UUID
        let variant: WidgetVariant
        @ObservedObject private var manager = DesktopWidgetWindowManager.shared

        var body: some View {
            DesktopWidgetView(variant: variant, isPreview: false)
                .id(manager.authStateRefreshToken)
                .contextMenu {
                    Button("Remove Widget", role: .destructive) {
                        DesktopWidgetWindowManager.shared.close(instanceID: instanceID)
                    }
                }
        }
    }
}

private final class DesktopWidgetPanel: NSPanel {
    override var canBecomeKey: Bool { true }
    override var canBecomeMain: Bool { false }

    override func mouseDown(with event: NSEvent) {
        NSApp.activate(ignoringOtherApps: true)
        makeKeyAndOrderFront(nil)
        super.mouseDown(with: event)
    }
}
