import Foundation

/// Slicing a payload into ≤ 2 MiB plaintext shards and reassembling it — shared by the
/// producer (§6.1 step 7) and restore (§9.1 step 3). Slice boundaries are fixed-size, so an
/// unchanged payload re-shards to identical plaintexts and the deterministic AEAD dedups them.
enum CarbonPayload {
    /// Plaintext bytes per shard (spec §2: shard ≤ 2 MiB; the ciphertext adds nonce+tag).
    static let maxShardPlaintextBytes = 2 * 1024 * 1024

    /// Seal `payload` into ordered shards and store each. Returns the manifest's shard refs.
    static func shard(
        _ payload: Data, projectUUID: UUID, crypto: CarbonCrypto, store: CarbonShardStore
    ) async throws -> [CarbonManifest.ShardRef] {
        var refs: [CarbonManifest.ShardRef] = []
        var offset = payload.startIndex
        // An empty payload still yields zero shards — a manifest may legitimately carry none
        // (e.g. a history manifest whose pack is empty because the tip was already covered).
        while offset < payload.endIndex {
            let end = min(offset + maxShardPlaintextBytes, payload.endIndex)
            let sealed = try crypto.seal(Data(payload[offset..<end]))
            try await store.putShard(
                ciphertext: sealed.ciphertext, expectedID: sealed.id, projectUUID: projectUUID)
            refs.append(.init(id: sealed.id, byteCount: UInt64(sealed.ciphertext.count)))
            offset = end
        }
        return refs
    }

    /// Fetch, verify, decrypt, and concatenate a manifest's shards back into its payload.
    static func assemble(
        _ refs: [CarbonManifest.ShardRef], projectUUID: UUID, crypto: CarbonCrypto,
        store: CarbonShardStore
    ) async throws -> Data {
        var payload = Data()
        for ref in refs {
            guard let blob = await store.shard(id: ref.id, projectUUID: projectUUID) else {
                throw CarbonRestoreError.missingShard(ref.id)
            }
            payload.append(try crypto.open(blob))
        }
        return payload
    }
}

public enum CarbonRestoreError: Error, Equatable {
    case missingShard(String)
    case missingManifest(String)
    case keyMissing(UUID)
    case noSnapshotForSession(String)
    case branchCollision(branch: String, existing: String)
    case packApplyFailed(String)
    case worktreeExists(String)
}
