import BitFoundation
import Foundation

struct BLEPendingPrivateMessage: Equatable {
    let content: String
    let messageID: String
}

struct BLEPendingTypedPayload: Equatable {
    let payload: Data
    /// Present for app-initiated media so handshake queuing preserves the
    /// fragment scheduler's progress/cancellation identity.
    let transferId: String?
}

struct BLENoiseSessionQueues {
    private var privateMessagesByPeerID: [PeerID: [BLEPendingPrivateMessage]] = [:]
    private var typedPayloadsByPeerID: [PeerID: [BLEPendingTypedPayload]] = [:]

    var isEmpty: Bool {
        privateMessagesByPeerID.isEmpty && typedPayloadsByPeerID.isEmpty
    }

    mutating func removeAll() {
        privateMessagesByPeerID.removeAll()
        typedPayloadsByPeerID.removeAll()
    }

    mutating func appendPrivateMessage(content: String, messageID: String, for peerID: PeerID) {
        privateMessagesByPeerID[peerID, default: []].append(BLEPendingPrivateMessage(content: content, messageID: messageID))
    }

    mutating func takePrivateMessages(for peerID: PeerID) -> [BLEPendingPrivateMessage] {
        let messages = privateMessagesByPeerID[peerID] ?? []
        privateMessagesByPeerID.removeValue(forKey: peerID)
        return messages
    }

    mutating func prependPrivateMessages(_ messages: [BLEPendingPrivateMessage], for peerID: PeerID) {
        guard !messages.isEmpty else { return }
        privateMessagesByPeerID[peerID, default: []].insert(contentsOf: messages, at: 0)
    }

    mutating func appendTypedPayload(_ payload: Data, transferId: String? = nil, for peerID: PeerID) {
        typedPayloadsByPeerID[peerID, default: []].append(
            BLEPendingTypedPayload(payload: payload, transferId: transferId)
        )
    }

    mutating func takeTypedPayloads(for peerID: PeerID) -> [BLEPendingTypedPayload] {
        let payloads = typedPayloadsByPeerID[peerID] ?? []
        typedPayloadsByPeerID.removeValue(forKey: peerID)
        return payloads
    }

    func containsTypedPayload(transferId: String) -> Bool {
        typedPayloadsByPeerID.values.contains { payloads in
            payloads.contains { $0.transferId == transferId }
        }
    }

    @discardableResult
    mutating func removeTypedPayload(transferId: String) -> Bool {
        for peerID in Array(typedPayloadsByPeerID.keys) {
            guard var payloads = typedPayloadsByPeerID[peerID],
                  let index = payloads.firstIndex(where: { $0.transferId == transferId }) else {
                continue
            }
            payloads.remove(at: index)
            typedPayloadsByPeerID[peerID] = payloads.isEmpty ? nil : payloads
            return true
        }
        return false
    }
}
