import Foundation

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

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

    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 init(
        id: UInt64,
        displayUUID: String,
        index: Int,
        label: String,
        type: AlignerSpaceType,
        isCurrent: Bool = false,
        windowCount: Int,
        appCount: Int,
        appNames: [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
    }
}

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 displays = displayViewModels(
            from: snapshot.displays,
            currentSpaceIDs: currentSpaceIDs,
            windowCountsBySpace: windowCountsBySpace,
            appCountsBySpace: appCountsBySpace,
            appNamesBySpace: appNamesBySpace
        )
        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)
        )
    }

    private static func displayViewModels(
        from displays: [AlignerDisplay],
        currentSpaceIDs: Set<UInt64>,
        windowCountsBySpace: [UInt64: Int],
        appCountsBySpace: [UInt64: Int],
        appNamesBySpace: [UInt64: [String]]
    ) -> [QuickSwitchDisplayViewModel] {
        displays
            .sorted { $0.uuid.localizedStandardCompare($1.uuid) == .orderedAscending }
            .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: "\(displayLabel)\(space.index)",
                            type: space.type,
                            isCurrent: currentSpaceIDs.contains(space.id),
                            windowCount: windowCountsBySpace[space.id] ?? 0,
                            appCount: appCountsBySpace[space.id] ?? 0,
                            appNames: appNamesBySpace[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] = [:]

        for (displayIndex, display) in displays
            .sorted(by: { $0.uuid.localizedStandardCompare($1.uuid) == .orderedAscending })
            .enumerated()
        {
            let displayLabel = displayLabel(for: displayIndex)
            for space in display.spaces {
                labels[space.id] = "\(displayLabel)\(space.index)"
            }
        }

        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 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))
    }
}
