import Foundation

struct TaggedItem<T: Identifiable>: Identifiable {
    let id: String
    let sourceKey: String
    let item: T
    let instanceName: String
    var authService: AuthenticationService

    init(item: T, sourceKey: String, instanceName: String, authService: AuthenticationService) {
        id = "\(sourceKey):\(item.id)"
        self.sourceKey = sourceKey
        self.item = item
        self.instanceName = instanceName
        self.authService = authService
    }

    static func mergeAndDeduplicate(
        batches: [(Int, [T])],
        sources: [ConnectionSource],
    ) -> [TaggedItem<T>] {
        var seen = Set<String>()
        var merged = [TaggedItem<T>]()
        for (index, items) in batches {
            let source = sources[index]
            for item in items {
                let tagged = TaggedItem(
                    item: item, sourceKey: source.sourceKey,
                    instanceName: source.name, authService: source.authService,
                )
                if seen.insert(tagged.id).inserted {
                    merged.append(tagged)
                }
            }
        }
        return merged
    }
}

// MARK: - Codable (for disk cache)

extension TaggedItem: Codable where T: Codable {
    enum CodingKeys: String, CodingKey {
        case id, sourceKey, item, instanceName
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        id = try container.decode(String.self, forKey: .id)
        sourceKey = try container.decode(String.self, forKey: .sourceKey)
        item = try container.decode(T.self, forKey: .item)
        instanceName = try container.decode(String.self, forKey: .instanceName)
        authService = AuthenticationService()
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(id, forKey: .id)
        try container.encode(sourceKey, forKey: .sourceKey)
        try container.encode(item, forKey: .item)
        try container.encode(instanceName, forKey: .instanceName)
    }
}

extension PaginationState where Item: Identifiable {
    /// Replace placeholder auth services on cached TaggedItems with live ones from the manager.
    /// Items whose source is no longer connected are removed.
    func rehydrate<T>(from manager: MultiInstanceManager) where Item == TaggedItem<T> {
        let sources = manager.connectionSources()
        rehydrate(from: sources)
    }

    /// Replace placeholder auth services using the given connection sources.
    /// Items whose sourceKey has no matching source are removed.
    func rehydrate<T>(from sources: [ConnectionSource]) where Item == TaggedItem<T> {
        // Two connected accounts with the same server+username share a sourceKey,
        // so keep the first rather than trapping on a duplicate key.
        let lookup = Dictionary(
            sources.map { ($0.sourceKey, $0.authService) },
            uniquingKeysWith: { first, _ in first },
        )
        items = items.compactMap { item in
            guard let auth = lookup[item.sourceKey] else { return nil }
            var rehydrated = item
            rehydrated.authService = auth
            return rehydrated
        }
    }
}
