import Foundation

public struct QuickSwitchWindowItem: Equatable, Sendable {
    public let window: AlignerWindow
    public let primarySpaceID: UInt64?

    public init(window: AlignerWindow, primarySpaceID: UInt64?) {
        self.window = window
        self.primarySpaceID = primarySpaceID
    }
}

public struct QuickSwitchAppGroup: Equatable, Sendable {
    public let app: AlignerApp
    public let windows: [QuickSwitchWindowItem]

    public init(app: AlignerApp, windows: [QuickSwitchWindowItem]) {
        self.app = app
        self.windows = windows
    }
}

public struct QuickSwitchSnapshot: Equatable, Sendable {
    public let displays: [AlignerDisplay]
    public let appGroups: [QuickSwitchAppGroup]

    public init(displays: [AlignerDisplay], appGroups: [QuickSwitchAppGroup]) {
        self.displays = displays
        self.appGroups = appGroups
    }
}

public enum QuickSwitchSnapshotBuilder {
    public static func snapshot(
        displays: [AlignerDisplay],
        windows: [AlignerWindow]
    ) -> QuickSwitchSnapshot {
        let displayOrder = spaceOrder(from: displays)
        let candidateWindows = windows.filter(WindowDisplayPolicy.shouldDisplay)
        let groupedWindows = Dictionary(grouping: candidateWindows) {
            AppIdentity(app: $0.app)
        }

        let appGroups = groupedWindows.values.compactMap { windows -> QuickSwitchAppGroup? in
            guard let firstWindow = windows.first else { return nil }
            let sortedWindows = windows
                .sorted { lhs, rhs in
                    compareWindows(lhs, rhs, displayOrder: displayOrder)
                }
                .map { window in
                    QuickSwitchWindowItem(
                        window: window,
                        primarySpaceID: primarySpaceID(for: window, displayOrder: displayOrder)
                    )
                }

            return QuickSwitchAppGroup(app: firstWindow.app, windows: sortedWindows)
        }
        .sorted { lhs, rhs in
            compareApps(lhs.app, rhs.app)
        }

        return QuickSwitchSnapshot(displays: displays, appGroups: appGroups)
    }

    private static func spaceOrder(from displays: [AlignerDisplay]) -> [UInt64: Int] {
        var order: [UInt64: Int] = [:]
        var nextIndex = 0

        for display in displays.sorted(by: { $0.uuid.localizedStandardCompare($1.uuid) == .orderedAscending }) {
            for space in display.spaces.sorted(by: { $0.index < $1.index }) {
                order[space.id] = nextIndex
                nextIndex += 1
            }
        }

        return order
    }

    private static func compareApps(_ lhs: AlignerApp, _ rhs: AlignerApp) -> Bool {
        let nameOrder = lhs.name.localizedCaseInsensitiveCompare(rhs.name)
        if nameOrder != .orderedSame {
            return nameOrder == .orderedAscending
        }

        let bundleOrder = normalized(lhs.bundleIdentifier)
            .localizedCaseInsensitiveCompare(normalized(rhs.bundleIdentifier))
        if bundleOrder != .orderedSame {
            return bundleOrder == .orderedAscending
        }

        let pathOrder = normalized(lhs.executablePath)
            .localizedCaseInsensitiveCompare(normalized(rhs.executablePath))
        if pathOrder != .orderedSame {
            return pathOrder == .orderedAscending
        }

        return (lhs.processIdentifier ?? Int32.max) < (rhs.processIdentifier ?? Int32.max)
    }

    private static func compareWindows(
        _ lhs: AlignerWindow,
        _ rhs: AlignerWindow,
        displayOrder: [UInt64: Int]
    ) -> Bool {
        let lhsSpaceOrder = primarySpaceOrder(for: lhs, displayOrder: displayOrder)
        let rhsSpaceOrder = primarySpaceOrder(for: rhs, displayOrder: displayOrder)
        if lhsSpaceOrder != rhsSpaceOrder {
            return lhsSpaceOrder < rhsSpaceOrder
        }

        let titleOrder = lhs.title.localizedCaseInsensitiveCompare(rhs.title)
        if titleOrder != .orderedSame {
            return titleOrder == .orderedAscending
        }

        return lhs.id < rhs.id
    }

    private static func primarySpaceID(
        for window: AlignerWindow,
        displayOrder: [UInt64: Int]
    ) -> UInt64? {
        window.spaceIDs.min { lhs, rhs in
            (displayOrder[lhs] ?? Int.max) < (displayOrder[rhs] ?? Int.max)
        }
    }

    private static func primarySpaceOrder(
        for window: AlignerWindow,
        displayOrder: [UInt64: Int]
    ) -> Int {
        guard let primarySpaceID = primarySpaceID(for: window, displayOrder: displayOrder) else {
            return Int.max
        }

        return displayOrder[primarySpaceID] ?? Int.max
    }

    private static func normalized(_ value: String?) -> String {
        guard let value, !value.isEmpty else { return "\u{10FFFF}" }
        return value
    }
}

private struct AppIdentity: Hashable {
    let bundleIdentifier: String?
    let executablePath: String?
    let processIdentifier: Int32?
    let fallbackName: String

    init(app: AlignerApp) {
        self.bundleIdentifier = app.bundleIdentifier.isEmpty ? nil : app.bundleIdentifier
        self.executablePath = app.executablePath?.isEmpty == false ? app.executablePath : nil
        self.processIdentifier = app.processIdentifier
        self.fallbackName = app.name
    }
}
