// SPDX-FileCopyrightText: 2026 John Burwell and contributors
// SPDX-License-Identifier: AGPL-3.0-or-later

import Foundation

enum SourceScanPolicy {
    static func initialStatus(for source: MinecraftSource, mode: SourceDiscoveryMode) -> String {
        switch (source.origin, mode) {
        case (.localFolder, .fullScan), (.javaLocalFolder, .fullScan):
            return "Preparing folder scan..."
        case (.localFolder, .reconcile), (.javaLocalFolder, .reconcile):
            return "Preparing cached library refresh..."
        case (.connectedDevice, .fullScan):
            return "Connecting to device and discovering Minecraft items..."
        case (.connectedDevice, .reconcile):
            return "Connecting to device and refreshing cached library..."
        }
    }

    static func scanningLibraryStatus(for source: MinecraftSource, mode: SourceDiscoveryMode) -> String {
        switch (source.origin, mode) {
        case (.localFolder, .fullScan), (.javaLocalFolder, .fullScan):
            return "Scanning Minecraft library..."
        case (.localFolder, .reconcile), (.javaLocalFolder, .reconcile):
            return "Reconciling cached library..."
        case (.connectedDevice, .fullScan):
            return "Scanning Minecraft library on device..."
        case (.connectedDevice, .reconcile):
            return "Reconciling cached device library..."
        }
    }

    static func performanceContext(for source: MinecraftSource) -> String {
        switch source.origin {
        case .localFolder, .javaLocalFolder:
            return "source=\(source.displayName) kind=local"
        case .connectedDevice(let device, let container):
            let transport = device.connection == .usb ? "usb" : "network"
            return "source=\(source.displayName) kind=connected-device transport=\(transport) udid=\(device.udid) app=\(container.appID)"
        }
    }

    static func friendlyError(for error: Error, source: MinecraftSource) -> String {
        let description = error.localizedDescription

        guard source.origin.kind == .connectedDevice else {
            return "Failed to scan folder: \(description)"
        }

        if description.contains("AMDeviceCreateHouseArrestService returned -402653093")
            || description.contains("kAMDServiceLimitError") {
            return "Device is busy. Too many device access sessions were open, so the scan could not start."
        }

        if description.localizedCaseInsensitiveContains("InstallationLookupFailed") {
            return "The device refused access to the Minecraft app container."
        }

        if description.localizedCaseInsensitiveContains("not paired") {
            return "The device is not paired with this Mac."
        }

        if description.localizedCaseInsensitiveContains("no longer available") {
            return "The device disconnected during the scan."
        }

        return "Failed to scan device library."
    }

    static func availabilityStatus(
        for error: Error,
        defaultingTo currentAvailability: SourceAvailability
    ) -> SourceAvailability {
        if let accessError = error as? SourceAccessError {
            switch accessError {
            case .deviceUnavailable:
                return .disconnected
            case .deviceNotTrusted:
                return .limited
            case .appNotAccessible, .minecraftFolderMissing, .accessFailed:
                return .unavailable
            }
        }

        return currentAvailability
    }

    static func shouldReuseCachedItem(
        _ cachedItem: MinecraftContentItem,
        forDiscoveredItem discoveredItem: MinecraftContentItem,
        source: MinecraftSource,
        previousSnapshot: ItemSnapshot?
    ) -> Bool {
        guard cachedItem.contentType == discoveredItem.contentType else {
            return false
        }

        guard cachedItem.metadataLoaded, cachedItem.previewLoaded, cachedItem.sizeLoaded else {
            return false
        }

        switch source.origin.kind {
        case .localFolder:
            guard let previousSnapshot else {
                return false
            }

            let currentModifiedDate = try? discoveredItem.folderURL
                .resourceValues(forKeys: [.contentModificationDateKey])
                .contentModificationDate
            return previousSnapshot.modifiedDate == currentModifiedDate
        case .connectedDevice:
            return cachedItem.folderName == discoveredItem.folderName
                && cachedItem.displayName == discoveredItem.displayName
                && cachedItem.hasKnownIcon == discoveredItem.hasKnownIcon
                && cachedItem.packUUID == discoveredItem.packUUID
                && cachedItem.packVersion == discoveredItem.packVersion
                && cachedItem.packMetadataDetails == discoveredItem.packMetadataDetails
                && cachedItem.packReferences == discoveredItem.packReferences
        }
    }

    static func buildSnapshot(for source: MinecraftSource, scanRootURL: URL) -> SourceSnapshot {
        let collectionSnapshots: [CollectionSnapshot]
        switch source.edition {
        case .bedrock:
            collectionSnapshots = WorldScanner.collectionSnapshots(in: scanRootURL)
        case .java:
            collectionSnapshots = JavaContentScanner.collectionSnapshots(in: scanRootURL)
        }

        let itemSnapshots = source.rawItems.map { item in
            ItemSnapshot(
                id: item.id,
                relativePath: item.folderURL.path.replacingOccurrences(of: scanRootURL.path + "/", with: ""),
                modifiedDate: item.modifiedDate,
                sizeBytes: item.sizeBytes,
                packUUID: nil,
                packVersion: nil
            )
        }.sorted { lhs, rhs in
            lhs.relativePath.localizedStandardCompare(rhs.relativePath) == .orderedAscending
        }

        let rootModifiedDate = try? scanRootURL
            .resourceValues(forKeys: [.contentModificationDateKey])
            .contentModificationDate

        return SourceSnapshot(
            sourceID: source.id,
            rootModifiedDate: rootModifiedDate,
            collectionSnapshots: collectionSnapshots,
            itemSnapshots: itemSnapshots
        )
    }
}

enum SourceScanRecovery {
    static func restoreIndexedState(from previousSource: MinecraftSource, into source: inout MinecraftSource) {
        source.displayItems = previousSource.displayItems
        source.displayItemCountsByType = previousSource.displayItemCountsByType
        source.displayItemCountsByKind = previousSource.displayItemCountsByKind
        source.rawItems = previousSource.rawItems
        source.logicalPacks = previousSource.logicalPacks
        source.logicalWorlds = previousSource.logicalWorlds
        source.packInstances = previousSource.packInstances
        source.worldPackRelationships = previousSource.worldPackRelationships
        source.snapshot = previousSource.snapshot
        source.indexedItemCount = previousSource.indexedItemCount
        source.indexedDetailCount = previousSource.indexedDetailCount
        source.previewLoadedCount = previousSource.previewLoadedCount
        source.sizeLoadedCount = previousSource.sizeLoadedCount
        source.scanProgress = previousSource.scanProgress
        source.lastScanDate = previousSource.lastScanDate
    }

    static func shouldPreservePartialResults(
        currentSource: MinecraftSource,
        previousSource: MinecraftSource
    ) -> Bool {
        if currentSource.rawItems.count > previousSource.rawItems.count {
            return true
        }

        if currentSource.indexedDetailCount > previousSource.indexedDetailCount {
            return true
        }

        if currentSource.previewLoadedCount > previousSource.previewLoadedCount {
            return true
        }

        if currentSource.sizeLoadedCount > previousSource.sizeLoadedCount {
            return true
        }

        return false
    }
}
