//
// Copyright 2023 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//

import GRDB

public enum BatchingPreference {
    case batched(UInt = Batching.kDefaultBatchSize)
    case unbatched
}

extension SDSCodableModelDatabaseInterfaceImpl {
    /// Traverse all records, in no particular order.
    func enumerateModels<Model: SDSCodableModel>(
        modelType: Model.Type,
        transaction: DBReadTransaction,
        batchingPreference: BatchingPreference,
        block: (Model, inout Bool) -> Void,
    ) {
        let batchSize = batchSize(batchingPreference: batchingPreference)
        enumerateModels(
            modelType: modelType,
            transaction: transaction,
            sql: nil,
            arguments: nil,
            batchSize: batchSize,
            block: block,
        )
    }

    /// Traverse all records, in no particular order.
    func enumerateModels<Model: SDSCodableModel>(
        modelType: Model.Type,
        transaction: DBReadTransaction,
        sql: String,
        arguments: StatementArguments,
        batchingPreference: BatchingPreference,
        block: (Model, inout Bool) -> Void,
    ) {
        let batchSize = batchSize(batchingPreference: batchingPreference)
        enumerateModels(
            modelType: modelType,
            transaction: transaction,
            sql: sql,
            arguments: arguments,
            batchSize: batchSize,
            block: block,
        )
    }

    /// The batch size for enumeration.
    private func batchSize(batchingPreference: BatchingPreference) -> UInt {
        switch batchingPreference {
        case .batched(let size):
            return size
        case .unbatched:
            return 0
        }
    }

    /// Traverse all records, in no particular order.
    /// - Parameter batchSize
    /// If nonzero, enumeration is performed in autoreleased batches.
    private func enumerateModels<Model: SDSCodableModel>(
        modelType: Model.Type,
        transaction: DBReadTransaction,
        sql: String? = nil,
        arguments: StatementArguments? = nil,
        batchSize: UInt,
        block: (Model, inout Bool) -> Void,
    ) {
        failIfThrows {
            var recordCursor: RecordCursor<Model>
            if let sql, let arguments {
                recordCursor = try Model.fetchCursor(
                    transaction.database,
                    sql: sql,
                    arguments: arguments,
                )
            } else {
                recordCursor = try modelType.fetchCursor(transaction.database)
            }

            try Batching.loop(batchSize: batchSize) { stop in
                guard let value = try recordCursor.next() else {
                    stop = true
                    return
                }
                value.anyDidEnumerateOne(transaction: transaction)
                block(value, &stop)
            }
        }
    }
}
