import CoreGraphics
import Foundation

public struct QuickSwitchViewModel: Equatable, Sendable {
    public let displays: [QuickSwitchDisplayViewModel]
    public let appShelf: [QuickSwitchAppShelfItemViewModel]
    public let waterfallColumns: [QuickSwitchWaterfallColumnViewModel]
    public let initialSelection: QuickSwitchSelection?
    public let lockedSpaceID: UInt64?

    public init(
        displays: [QuickSwitchDisplayViewModel],
        appShelf: [QuickSwitchAppShelfItemViewModel],
        waterfallColumns: [QuickSwitchWaterfallColumnViewModel],
        initialSelection: QuickSwitchSelection?,
        lockedSpaceID: UInt64? = nil
    ) {
        self.displays = displays
        self.appShelf = appShelf
        self.waterfallColumns = waterfallColumns
        self.initialSelection = initialSelection
        self.lockedSpaceID = lockedSpaceID
    }

    public var spaceCount: Int {
        displays.flatMap(\.spaces).count
    }

    public var windowCount: Int {
        waterfallColumns.flatMap(\.windows).count
    }
}

public struct QuickSwitchDisplayViewModel: Equatable, Sendable {
    public let displayUUID: String
    public let displayIndex: Int
    public let displayLabel: String
    public let physical: Bool
    public let spaces: [QuickSwitchSpaceViewModel]

    public init(
        displayUUID: String,
        displayIndex: Int,
        displayLabel: String,
        physical: Bool,
        spaces: [QuickSwitchSpaceViewModel]
    ) {
        self.displayUUID = displayUUID
        self.displayIndex = displayIndex
        self.displayLabel = displayLabel
        self.physical = physical
        self.spaces = spaces
    }
}

public struct QuickSwitchSpaceViewModel: Equatable, Sendable {
    public let id: UInt64
    public let displayUUID: String
    public let index: Int
    public let label: String
    public let type: AlignerSpaceType
    public let isCurrent: Bool
    public let windowCount: Int
    public let appCount: Int
    public let appNames: [String]
    public let splitViewAppNames: [String]

    public init(
        id: UInt64,
        displayUUID: String,
        index: Int,
        label: String,
        type: AlignerSpaceType,
        isCurrent: Bool = false,
        windowCount: Int,
        appCount: Int,
        appNames: [String] = [],
        splitViewAppNames: [String] = []
    ) {
        self.id = id
        self.displayUUID = displayUUID
        self.index = index
        self.label = label
        self.type = type
        self.isCurrent = isCurrent
        self.windowCount = windowCount
        self.appCount = appCount
        self.appNames = appNames
        self.splitViewAppNames = splitViewAppNames
    }
}

public struct QuickSwitchAppShelfItemViewModel: Equatable, Sendable {
    public let appGroupIndex: Int
    public let app: AlignerApp
    public let windowCount: Int
    public let primarySpaceIDs: [UInt64]
    public let hasMinimizedWindows: Bool
    public let hasFullscreenWindows: Bool

    public init(
        appGroupIndex: Int,
        app: AlignerApp,
        windowCount: Int,
        primarySpaceIDs: [UInt64],
        hasMinimizedWindows: Bool,
        hasFullscreenWindows: Bool
    ) {
        self.appGroupIndex = appGroupIndex
        self.app = app
        self.windowCount = windowCount
        self.primarySpaceIDs = primarySpaceIDs
        self.hasMinimizedWindows = hasMinimizedWindows
        self.hasFullscreenWindows = hasFullscreenWindows
    }
}

public struct QuickSwitchWaterfallColumnViewModel: Equatable, Sendable {
    public let appGroupIndex: Int
    public let app: AlignerApp
    public let windows: [QuickSwitchWindowCardViewModel]

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

public struct QuickSwitchWindowCardViewModel: Equatable, Sendable {
    public let appGroupIndex: Int
    public let windowIndex: Int
    public let globalIndex: Int
    public let window: AlignerWindow
    public let primarySpaceID: UInt64?
    public let primarySpaceLabel: String?

    public init(
        appGroupIndex: Int,
        windowIndex: Int,
        globalIndex: Int,
        window: AlignerWindow,
        primarySpaceID: UInt64?,
        primarySpaceLabel: String?
    ) {
        self.appGroupIndex = appGroupIndex
        self.windowIndex = windowIndex
        self.globalIndex = globalIndex
        self.window = window
        self.primarySpaceID = primarySpaceID
        self.primarySpaceLabel = primarySpaceLabel
    }
}

public enum QuickSwitchViewModelBuilder {
    public static func viewModel(from snapshot: QuickSwitchSnapshot) -> QuickSwitchViewModel {
        viewModel(from: snapshot, currentSpaceIDs: [])
    }

    public static func viewModel(
        from snapshot: QuickSwitchSnapshot,
        currentSpaceIDs: Set<UInt64>
    ) -> QuickSwitchViewModel {
        let spaceLabels = labelsBySpaceID(from: snapshot.displays)
        let windowItems = snapshot.appGroups.flatMap(\.windows)
        let windowCountsBySpace = countWindowsBySpace(windowItems)
        let appCountsBySpace = countAppsBySpace(snapshot.appGroups)
        let appNamesBySpace = appNamesBySpace(snapshot.appGroups)
        let splitViewAppNamesBySpace = splitViewAppNamesBySpace(snapshot.appGroups)
        let displays = displayViewModels(
            from: snapshot.displays,
            currentSpaceIDs: currentSpaceIDs,
            windowCountsBySpace: windowCountsBySpace,
            appCountsBySpace: appCountsBySpace,
            appNamesBySpace: appNamesBySpace,
            splitViewAppNamesBySpace: splitViewAppNamesBySpace
        )
        let appShelf = appShelfViewModels(from: snapshot.appGroups)

        var globalIndex = 0
        let columns = snapshot.appGroups.enumerated().map { appGroupIndex, group in
            let windows = group.windows.enumerated().map { windowIndex, item in
                defer { globalIndex += 1 }
                return QuickSwitchWindowCardViewModel(
                    appGroupIndex: appGroupIndex,
                    windowIndex: windowIndex,
                    globalIndex: globalIndex,
                    window: item.window,
                    primarySpaceID: item.primarySpaceID,
                    primarySpaceLabel: item.primarySpaceID.flatMap { spaceLabels[$0] }
                )
            }

            return QuickSwitchWaterfallColumnViewModel(
                appGroupIndex: appGroupIndex,
                app: group.app,
                windows: windows
            )
        }

        return QuickSwitchViewModel(
            displays: displays,
            appShelf: appShelf,
            waterfallColumns: columns,
            initialSelection: QuickSwitchFocusPolicy.initialSelection(in: snapshot),
            lockedSpaceID: nil
        )
    }

    private static func displayViewModels(
        from displays: [AlignerDisplay],
        currentSpaceIDs: Set<UInt64>,
        windowCountsBySpace: [UInt64: Int],
        appCountsBySpace: [UInt64: Int],
        appNamesBySpace: [UInt64: [String]],
        splitViewAppNamesBySpace: [UInt64: [String]]
    ) -> [QuickSwitchDisplayViewModel] {
        let sortedDisplays = displays
            .sorted { $0.uuid.localizedStandardCompare($1.uuid) == .orderedAscending }
        let displayCount = sortedDisplays.count

        return sortedDisplays
            .enumerated()
            .map { displayIndex, display in
                let displayLabel = displayLabel(for: displayIndex)
                let spaces = display.spaces
                    .sorted { $0.index < $1.index }
                    .map { space in
                        QuickSwitchSpaceViewModel(
                            id: space.id,
                            displayUUID: space.displayUUID,
                            index: space.index,
                            label: spaceLabel(for: space, displayIndex: displayIndex, displayCount: displayCount),
                            type: space.type,
                            isCurrent: currentSpaceIDs.contains(space.id),
                            windowCount: windowCountsBySpace[space.id] ?? 0,
                            appCount: appCountsBySpace[space.id] ?? 0,
                            appNames: appNamesBySpace[space.id] ?? [],
                            splitViewAppNames: splitViewAppNamesBySpace[space.id] ?? []
                        )
                    }

                return QuickSwitchDisplayViewModel(
                    displayUUID: display.uuid,
                    displayIndex: displayIndex,
                    displayLabel: displayLabel,
                    physical: display.physical,
                    spaces: spaces
                )
            }
    }

    private static func appShelfViewModels(
        from appGroups: [QuickSwitchAppGroup]
    ) -> [QuickSwitchAppShelfItemViewModel] {
        appGroups.enumerated().map { appGroupIndex, group in
            QuickSwitchAppShelfItemViewModel(
                appGroupIndex: appGroupIndex,
                app: group.app,
                windowCount: group.windows.count,
                primarySpaceIDs: orderedUnique(group.windows.compactMap(\.primarySpaceID)),
                hasMinimizedWindows: group.windows.contains { $0.window.isMinimized },
                hasFullscreenWindows: group.windows.contains { $0.window.isFullscreen }
            )
        }
    }

    private static func labelsBySpaceID(from displays: [AlignerDisplay]) -> [UInt64: String] {
        var labels: [UInt64: String] = [:]
        let sortedDisplays = displays
            .sorted(by: { $0.uuid.localizedStandardCompare($1.uuid) == .orderedAscending })
        let displayCount = sortedDisplays.count

        for (displayIndex, display) in sortedDisplays.enumerated() {
            for space in display.spaces {
                labels[space.id] = spaceLabel(for: space, displayIndex: displayIndex, displayCount: displayCount)
            }
        }

        return labels
    }

    private static func countWindowsBySpace(_ windowItems: [QuickSwitchWindowItem]) -> [UInt64: Int] {
        var counts: [UInt64: Int] = [:]
        for item in windowItems {
            guard let primarySpaceID = item.primarySpaceID else { continue }
            counts[primarySpaceID, default: 0] += 1
        }
        return counts
    }

    private static func countAppsBySpace(_ appGroups: [QuickSwitchAppGroup]) -> [UInt64: Int] {
        var appIdentitiesBySpace: [UInt64: Set<Int>] = [:]

        for (appGroupIndex, group) in appGroups.enumerated() {
            for spaceID in orderedUnique(group.windows.compactMap(\.primarySpaceID)) {
                appIdentitiesBySpace[spaceID, default: []].insert(appGroupIndex)
            }
        }

        return appIdentitiesBySpace.mapValues(\.count)
    }

    private static func appNamesBySpace(_ appGroups: [QuickSwitchAppGroup]) -> [UInt64: [String]] {
        var namesBySpace: [UInt64: [String]] = [:]

        for group in appGroups {
            for spaceID in orderedUnique(group.windows.compactMap(\.primarySpaceID)) {
                namesBySpace[spaceID, default: []].append(group.app.name)
            }
        }

        return namesBySpace
    }

    private static func splitViewAppNamesBySpace(_ appGroups: [QuickSwitchAppGroup]) -> [UInt64: [String]] {
        struct Candidate {
            let appName: String
            let windowID: UInt32
            let frame: CGRect
        }

        var candidatesBySpace: [UInt64: [Candidate]] = [:]

        for group in appGroups {
            for item in group.windows {
                guard item.window.isFullscreen,
                      let primarySpaceID = item.primarySpaceID,
                      let frame = item.window.frame,
                      frame.width > 0,
                      frame.height > 0
                else {
                    continue
                }

                candidatesBySpace[primarySpaceID, default: []].append(
                    Candidate(
                        appName: group.app.name,
                        windowID: item.window.id,
                        frame: frame
                    )
                )
            }
        }

        return candidatesBySpace.compactMapValues { candidates in
            guard candidates.count >= 2 else { return nil }

            return candidates
                .sorted { lhs, rhs in
                    if lhs.frame.midX != rhs.frame.midX {
                        return lhs.frame.midX < rhs.frame.midX
                    }
                    if lhs.frame.midY != rhs.frame.midY {
                        return lhs.frame.midY < rhs.frame.midY
                    }
                    return lhs.windowID < rhs.windowID
                }
                .prefix(2)
                .map(\.appName)
        }
    }

    private static func orderedUnique<T: Hashable>(_ values: [T]) -> [T] {
        var seen = Set<T>()
        var result: [T] = []

        for value in values where !seen.contains(value) {
            seen.insert(value)
            result.append(value)
        }

        return result
    }

    private static func displayLabel(for index: Int) -> String {
        let scalarA = Int(UnicodeScalar("A").value)
        var value = index
        var scalars: [UnicodeScalar] = []

        repeat {
            let remainder = value % 26
            scalars.insert(UnicodeScalar(scalarA + remainder)!, at: 0)
            value = value / 26 - 1
        } while value >= 0

        return String(String.UnicodeScalarView(scalars))
    }

    private static func spaceLabel(for space: AlignerSpace, displayIndex: Int, displayCount: Int) -> String {
        if displayCount <= 1 {
            return String(format: "%02d", space.index)
        }

        return "\(displayLabel(for: displayIndex))\(space.index)"
    }
}

public enum QuickSwitchSpaceFilterPolicy {
    public static func projectedViewModel(
        from viewModel: QuickSwitchViewModel,
        lockedSpaceID: UInt64?,
        preferredSelection: QuickSwitchSelection? = nil
    ) -> QuickSwitchViewModel {
        guard let lockedSpaceID else {
            return replacingInitialSelection(
                in: viewModel,
                lockedSpaceID: nil,
                preferredSelection: preferredSelection
            )
        }

        var nextGlobalIndex = 0
        let columns = viewModel.waterfallColumns.compactMap { column -> QuickSwitchWaterfallColumnViewModel? in
            let windows = column.windows.filter { $0.primarySpaceID == lockedSpaceID }
            guard !windows.isEmpty else { return nil }

            let reindexedWindows = windows.enumerated().map { windowIndex, card in
                defer { nextGlobalIndex += 1 }
                return QuickSwitchWindowCardViewModel(
                    appGroupIndex: column.appGroupIndex,
                    windowIndex: windowIndex,
                    globalIndex: nextGlobalIndex,
                    window: card.window,
                    primarySpaceID: card.primarySpaceID,
                    primarySpaceLabel: card.primarySpaceLabel
                )
            }

            return QuickSwitchWaterfallColumnViewModel(
                appGroupIndex: column.appGroupIndex,
                app: column.app,
                windows: reindexedWindows
            )
        }

        let columnsByAppGroupIndex = Dictionary(uniqueKeysWithValues: columns.map { ($0.appGroupIndex, $0) })
        let appShelf = viewModel.appShelf.compactMap { item -> QuickSwitchAppShelfItemViewModel? in
            guard let column = columnsByAppGroupIndex[item.appGroupIndex] else { return nil }
            return QuickSwitchAppShelfItemViewModel(
                appGroupIndex: item.appGroupIndex,
                app: item.app,
                windowCount: column.windows.count,
                primarySpaceIDs: orderedPrimarySpaceIDs(in: column.windows),
                hasMinimizedWindows: column.windows.contains { $0.window.isMinimized },
                hasFullscreenWindows: column.windows.contains { $0.window.isFullscreen }
            )
        }

        return QuickSwitchViewModel(
            displays: viewModel.displays,
            appShelf: appShelf,
            waterfallColumns: columns,
            initialSelection: initialSelection(in: columns, preferredSelection: preferredSelection),
            lockedSpaceID: lockedSpaceID
        )
    }

    public static func singleFullscreenSelection(
        inSpaceID spaceID: UInt64,
        viewModel: QuickSwitchViewModel
    ) -> QuickSwitchSelection? {
        guard viewModel.displays
            .flatMap(\.spaces)
            .contains(where: { $0.id == spaceID && $0.type == .fullscreen })
        else {
            return nil
        }

        let cards = viewModel.waterfallColumns
            .flatMap(\.windows)
            .filter { $0.primarySpaceID == spaceID }

        guard cards.count == 1,
              let card = cards.first,
              card.window.isFullscreen
        else {
            return nil
        }

        return QuickSwitchSelection(
            appGroupIndex: card.appGroupIndex,
            windowIndex: card.windowIndex,
            windowID: card.window.id
        )
    }

    private static func replacingInitialSelection(
        in viewModel: QuickSwitchViewModel,
        lockedSpaceID: UInt64?,
        preferredSelection: QuickSwitchSelection?
    ) -> QuickSwitchViewModel {
        QuickSwitchViewModel(
            displays: viewModel.displays,
            appShelf: viewModel.appShelf,
            waterfallColumns: viewModel.waterfallColumns,
            initialSelection: initialSelection(in: viewModel.waterfallColumns, preferredSelection: preferredSelection)
                ?? viewModel.initialSelection,
            lockedSpaceID: lockedSpaceID
        )
    }

    private static func initialSelection(
        in columns: [QuickSwitchWaterfallColumnViewModel],
        preferredSelection: QuickSwitchSelection?
    ) -> QuickSwitchSelection? {
        if let preferredSelection,
           let matchingCard = columns
            .flatMap(\.windows)
            .first(where: { $0.window.id == preferredSelection.windowID }) {
            return QuickSwitchSelection(
                appGroupIndex: matchingCard.appGroupIndex,
                windowIndex: matchingCard.windowIndex,
                windowID: matchingCard.window.id
            )
        }

        guard let firstCard = columns.first?.windows.first else { return nil }
        return QuickSwitchSelection(
            appGroupIndex: firstCard.appGroupIndex,
            windowIndex: firstCard.windowIndex,
            windowID: firstCard.window.id
        )
    }

    private static func orderedPrimarySpaceIDs(in cards: [QuickSwitchWindowCardViewModel]) -> [UInt64] {
        var seen = Set<UInt64>()
        var result: [UInt64] = []

        for card in cards {
            guard let spaceID = card.primarySpaceID,
                  !seen.contains(spaceID)
            else {
                continue
            }
            seen.insert(spaceID)
            result.append(spaceID)
        }

        return result
    }
}
