import { BaseDBEntity, CommonLogger, ErrorMode, NumberOfMilliseconds, Promisable, UnixTimestamp, ZodError, ZodSchema } from '@naturalcycles/js-lib';
import { AjvSchema, AjvValidationError, JoiValidationError, ObjectSchema, TransformLogProgressOptions, TransformMapOptions } from '@naturalcycles/nodejs-lib';
import { CommonDB } from '../common.db';
import { CommonDBCreateOptions, CommonDBOptions, CommonDBSaveOptions } from '../db.model';
export interface CommonDaoHooks<BM extends BaseDBEntity, DBM extends BaseDBEntity, ID = BM['id']> {
    /**
     * Allows to override the id generation function.
     * By default it uses `stringId` from nodejs-lib
     * (which uses lowercase alphanumberic alphabet and the size of 16).
     */
    createRandomId: () => ID;
    /**
     * createNaturalId hook is called (tried) first.
     * If it doesn't exist - createRandomId is called.
     */
    createNaturalId: (obj: DBM | BM) => ID;
    /**
     * It's a counter-part of `createNaturalId`.
     * Allows to provide a parser function to parse "natural id" into
     * DBM components (e.g accountId and some other property that is part of the id).
     */
    parseNaturalId: (id: ID) => Partial<DBM>;
    /**
     * It is called only on `dao.create` method.
     * Dao.create method is called in:
     *
     * - getByIdOrEmpty, getByIdAsDBMOrEmpty
     * - patch, patchAsDBM
     */
    beforeCreate: (bm: Partial<BM>) => Partial<BM>;
    beforeDBMToBM: (dbm: DBM) => Partial<BM> | Promise<Partial<BM>>;
    beforeBMToDBM: (bm: BM) => Partial<DBM> | Promise<Partial<DBM>>;
    /**
     * Allows to access the DBM just after it has been loaded from the DB.
     *
     * Normally does nothing.
     *
     * You can change the DBM as you want here: ok to mutate or not, but you need to return the DBM
     * to pass it further.
     *
     * You can return `null` to make it look "not found".
     *
     * You can do validations as needed here and throw errors, they will be propagated.
     */
    afterLoad?: (dbm: DBM) => Promisable<DBM | null>;
    /**
     * Allows to access the DBM just before it's supposed to be saved to the DB.
     *
     * Normally does nothing.
     *
     * You can change the DBM as you want here: ok to mutate or not, but you need to return the DBM
     * to pass it further.
     *
     * You can return `null` to prevent it from being saved, without throwing an error.
     * `.save` method will then return the BM/DBM as it has entered the method (it **won't** return the null value!).
     *
     * You can do validations as needed here and throw errors, they will be propagated.
     */
    beforeSave?: (dbm: DBM) => Promisable<DBM | null>;
    /**
     * Called in:
     * - dbmToBM (applied before DBM becomes BM)
     * - anyToDBM
     *
     * Hook only allows to apply anonymization to DBM (not to BM).
     * It still applies to BM "transitively", during dbmToBM
     * (e.g after loaded from the Database).
     */
    anonymize: (dbm: DBM) => DBM;
    /**
     * If hook is defined - allows to prevent or modify the error thrown.
     * Return `false` to prevent throwing an error.
     * Return original `err` to pass the error through (will be thrown in CommonDao).
     * Return modified/new `Error` if needed.
     */
    onValidationError: (err: JoiValidationError | AjvValidationError | ZodError) => Error | false;
}
export declare enum CommonDaoLogLevel {
    /**
     * Same as undefined
     */
    NONE = 0,
    /**
     * Log operations (e.g "getById returned 1 row"), but not data
     */
    OPERATIONS = 10,
    /**
     * Log operations and data for single operations (e.g getById), but not batch operations.
     */
    DATA_SINGLE = 20,
    /**
     * Log EVERYTHING - all data passing in and out (max 10 rows). Very verbose!
     */
    DATA_FULL = 30
}
export interface CommonDaoCfg<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM, ID = BM['id']> {
    db: CommonDB;
    table: string;
    /**
     * Joi, AjvSchema or ZodSchema is supported.
     */
    bmSchema?: ObjectSchema<BM> | AjvSchema<BM> | ZodSchema<BM>;
    excludeFromIndexes?: (keyof DBM)[];
    /**
     * Defaults to true.
     * If set to false - load (read) operations will skip validation (and conversion).
     */
    validateOnLoad?: boolean;
    /**
     * Defaults to true.
     * If set to false - save (write) operations will skip validation (and conversion).
     */
    validateOnSave?: boolean;
    /**
     * The hook allows to get a callback, to instrument "validation time".
     *
     * @experimental
     */
    onValidationTime?: (data: OnValidationTimeData) => void;
    /**
     * Defaults to false.
     * Setting it to true will set saveMethod to `insert` for save/saveBatch, which will
     * fail for rows that already exist in the DB (if CommonDB implementation supports it).
     *
     * `delete*` and `patch` will throw.
     *
     * You can still override saveMethod, or set opt.allowMutability to allow deletion.
     */
    immutable?: boolean;
    /**
     * Defaults to false.
     * Set to true to limit DB writing (will throw an error in such case).
     */
    readOnly?: boolean;
    /**
     * Defaults to `console`
     */
    logger?: CommonLogger;
    /**
     * @default OPERATIONS
     */
    logLevel?: CommonDaoLogLevel;
    /**
     * @default false
     */
    logStarted?: boolean;
    hooks?: Partial<CommonDaoHooks<BM, DBM, ID>>;
    /**
     * Defaults to true.
     * Set to false to disable auto-generation of `id`.
     * Useful e.g when your DB is generating ids by itself (e.g mysql auto_increment).
     */
    generateId?: boolean;
    /**
     * See the same option in CommonDB.
     * Defaults to false normally.
     */
    assignGeneratedIds?: boolean;
    /**
     * Defaults to true
     * Set to false to disable `created` field management.
     */
    useCreatedProperty?: boolean;
    /**
     * Defaults to true
     * Set to false to disable `updated` field management.
     */
    useUpdatedProperty?: boolean;
    /**
     * Defaults to false.
     * If true - run patch operations (patch, patchById, patchByIdOrCreate) in a Transaction.
     *
     * @experimental
     */
    patchInTransaction?: boolean;
}
/**
 * All properties default to undefined.
 */
export interface CommonDaoOptions extends CommonDBOptions {
    /**
     * Defaults to false.
     *
     * If set to true - will disable validation (and conversion).
     * One possible use case of doing this is - performance (as validation/conversion takes time, especially with Joi).
     */
    skipValidation?: boolean;
    /**
     * @default false
     */
    preserveUpdatedCreated?: boolean;
    /**
     * @default false (for streams). Setting to true enables deletion of immutable objects
     */
    allowMutability?: boolean;
    /**
     * If true - data will be anonymized (by calling a BaseDao.anonymize() hook that you can extend in your Dao implementation).
     * Only applicable to loading/querying/streaming_loading operations (n/a for saving).
     * There is additional validation applied AFTER Anonymization, so your anonymization implementation should keep the object valid.
     */
    anonymize?: boolean;
    /**
     * Allows to override the Table that this Dao is connected to, only in the context of this call.
     *
     * Useful e.g in AirtableDB where you can have one Dao to control multiple tables.
     */
    table?: string;
}
export interface CommonDaoReadOptions extends CommonDaoOptions {
    /**
     * If provided (and supported by the DB) - will read the data at that point in time (aka "Time machine" feature).
     * This feature is named PITR (point-in-time-recovery) query in Datastore.
     */
    readAt?: UnixTimestamp;
}
export interface CommonDaoSaveOptions<BM extends BaseDBEntity, DBM extends BaseDBEntity> extends CommonDaoSaveBatchOptions<DBM> {
    /**
     * If provided - a check will be made.
     * If the object for saving equals to the object passed to `skipIfEquals` - save operation will be skipped.
     *
     * Equality is checked with _deepJsonEquals (aka "deep equals after JSON.stringify/parse", which removes keys with undefined values).
     *
     * It's supposed to be used to prevent "unnecessary saves", when data is not changed.
     */
    skipIfEquals?: BM;
}
export interface CommonDaoPatchByIdOptions<DBM extends BaseDBEntity> extends CommonDaoSaveBatchOptions<DBM> {
    /**
     * Defaults to false.
     * With false, if the row doesn't exist - it will throw an error.
     * With true, if the row doesn't exist - it will be auto-created with `dao.create`.
     *
     * Use true when you expect the row to exist and it would be an error if it doesn't.
     */
    createIfMissing?: boolean;
}
export interface CommonDaoPatchOptions<DBM extends BaseDBEntity> extends CommonDaoSaveBatchOptions<DBM> {
    /**
     * If true - patch will skip loading from DB, and will just optimistically patch passed object.
     *
     * Consequently, when the row doesn't exist - it will be auto-created with `dao.create`.
     */
    skipDBRead?: boolean;
}
/**
 * All properties default to undefined.
 */
export interface CommonDaoSaveBatchOptions<DBM extends BaseDBEntity> extends CommonDaoOptions, CommonDBSaveOptions<DBM> {
    /**
     * @default false
     *
     * True would make sure that auto-generated id (only auto-generated, not passed!) is unique (not already present in DB).
     * If id is already present - auto-generator will retry auto-generating it few times, until it finds unused id.
     * If failed X times - will throw an error.
     *
     * Only applies to auto-generated ids! Does not apply to passed id.
     */
    ensureUniqueId?: boolean;
}
export interface CommonDaoStreamDeleteOptions<DBM extends BaseDBEntity> extends CommonDaoStreamOptions<DBM> {
}
export interface CommonDaoStreamSaveOptions<DBM extends BaseDBEntity> extends CommonDaoSaveBatchOptions<DBM>, CommonDaoStreamOptions<DBM> {
}
export interface CommonDaoStreamForEachOptions<IN> extends CommonDaoStreamOptions<IN>, TransformMapOptions<IN, any> {
}
export interface CommonDaoStreamOptions<IN> extends CommonDaoReadOptions, TransformLogProgressOptions<IN> {
    /**
     * @default true (for streams)
     */
    skipValidation?: boolean;
    /**
     * @default ErrorMode.SUPPRESS for returning ReadableStream, because .pipe() has no concept of "error propagation"
     * @default ErrorMode.SUPPRESS for .forEach() streams as well, but overridable
     */
    errorMode?: ErrorMode;
    /**
     * Applicable to some of stream operations, e.g deleteByQuery.
     * If set - `deleteByQuery` won't execute it "all at once", but in batches (chunks).
     *
     * Defaults to undefined, so the operation is executed "all at once".
     */
    chunkSize?: number;
    /**
     * When chunkSize is set - this option controls how many chunks to run concurrently.
     * Defaults to 32.
     */
    chunkConcurrency?: number;
}
export type CommonDaoCreateOptions = CommonDBCreateOptions;
export interface OnValidationTimeData {
    tookMillis: NumberOfMilliseconds;
    table: string;
    obj: any;
}
