/**
 * @fileoverview Shared bulk CRUD orchestration core.
 * @description Target-agnostic orchestrator that owns the bulk policy
 *   (wire validation, collision detection, per-bucket ACL, per-row schema
 *   validation, id minting, metadata stamping, audit, response validation).
 *   Target storage is delegated to a caller-supplied `transact` callback —
 *   this lets Firestore admin, Supabase RPC, Vercel firebase-admin, future
 *   GraphQL and future REST-CRUD server aids share one source of truth for
 *   correctness without pulling any platform-specific dependencies here.
 *
 *   This module is server-only (`shared/` but never imported from `client/`
 *   or `common/`) and designed to stay portable across Node and Deno — it
 *   does not import `fs`, `net`, `firebase-admin`, `supabase-js`, etc.
 *
 * @version 0.1.0
 * @since 0.7.0
 * @author AMBROISE PARK Consulting
 */
import * as v from 'valibot';
import type { BulkRequest, BulkResponse, EntityAccessConfig, UserRole } from '@donotdev/core/server';
/**
 * Valibot schema alias accepted by {@link executeBulk}. Kept loose (`any` on
 * the parsed output) so callers can pass entity-specific schemas generated by
 * `createSchemas()` without fighting variance.
 */
export type BulkRowSchema = v.BaseSchema<unknown, any, v.BaseIssue<unknown>>;
/**
 * A prepared insert row passed to the caller-supplied `transact` callback.
 * When {@link ExecuteBulkParams.mintInsertId} is provided, `id` is the minted
 * value; otherwise it carries whatever id (if any) the client supplied and
 * the storage layer is expected to mint its own.
 */
export interface PreparedInsert {
    /** Minted insert id, or the empty string when the target mints its own. */
    id: string;
    /** Fully stamped row ready for storage. */
    row: Record<string, unknown>;
}
/**
 * A prepared update passed to `transact`.
 */
export interface PreparedUpdate {
    /** Target row id. */
    id: string;
    /** Fully stamped patch ready for storage. */
    patch: Record<string, unknown>;
}
/**
 * Payload handed to the caller-supplied `transact` callback. All three
 * arrays preserve input order and only include non-empty buckets' work.
 */
export interface PreparedBulk {
    inserts: PreparedInsert[];
    updates: PreparedUpdate[];
    deletes: string[];
}
/**
 * Parameters for {@link executeBulk}.
 *
 * @template TRow - Row type recorded for documentation; runtime is opaque.
 */
export interface ExecuteBulkParams<TRow = unknown> {
    /** Collection / table name. Passed through to audit + transact as metadata. */
    collection: string;
    /** Raw wire payload to orchestrate. Parsed with {@link BulkRequestSchema}. */
    ops: BulkRequest;
    /** Valibot schema validating each insert row. */
    createSchema: BulkRowSchema;
    /** Valibot schema validating each insert row when status === 'draft'. */
    draftCreateSchema?: BulkRowSchema;
    /** Valibot schema validating each update patch. */
    updateSchema: BulkRowSchema;
    /** Per-bucket required roles. Missing entries are treated as "no bucket allowed". */
    access: EntityAccessConfig;
    /** Authenticated caller id (for metadata + audit). */
    uid: string;
    /** Caller's role (for per-bucket ACL). */
    userRole: UserRole;
    /**
     * Target-specific storage. Invoked exactly once when the bulk carries at
     * least one op. Must run all three buckets inside one atomic transaction
     * (writeBatch, runTransaction, Postgres RPC wrapping BEGIN/COMMIT, etc.)
     * and return ids per bucket in input order.
     *
     * The returned shape is validated against `BulkResponseSchema` before
     * being returned to the caller.
     */
    transact: (prepared: PreparedBulk) => Promise<BulkResponse>;
    /**
     * Optional id minting. When provided, every insert is stamped with a
     * freshly-minted id before `transact` runs. When omitted, `id` on each
     * {@link PreparedInsert} is the empty string and the storage layer is
     * expected to mint its own ids (Supabase RPC, GraphQL server, etc.).
     */
    mintInsertId?: () => string;
    /**
     * Optional hook to stamp platform-specific metadata on every insert row
     * (timestamps, createdById, defaults, field-name mapping). Called once per
     * insert row, in input order.
     */
    stampInsertMetadata?: (row: Record<string, unknown>, uid: string) => Record<string, unknown>;
    /**
     * Optional hook to stamp platform-specific metadata on every update patch.
     */
    stampUpdateMetadata?: (patch: Record<string, unknown>, uid: string) => Record<string, unknown>;
    /**
     * Optional audit hook. Fires exactly once when `transact` succeeds, with the
     * three op counts. Never fires for empty bulks or rejected validations —
     * matches the "one audit entry" contract from `BULK_CRUD_TODO.md` §4.
     */
    audit?: (event: string, payload: {
        counts: {
            inserts: number;
            updates: number;
            deletes: number;
        };
    }) => void;
    /**
     * Optional rate-limit bookkeeping key. `executeBulk` does NOT perform
     * rate-limit enforcement itself — the surrounding base-function wrapper
     * already charges one hit at entry. This field is forwarded to `audit()`
     * callers that want to correlate telemetry.
     */
    rateLimitKey?: string;
}
/**
 * Run a bulk CRUD operation against a caller-supplied storage target.
 *
 * **Order of operations** (every step fails loud — no fallbacks, no partial
 * writes):
 *
 * 1. Parse `ops` with {@link BulkRequestSchema}. Invalid shape → throws.
 * 2. Collision detection via {@link detectBulkCollisions}. Collision →
 *    throws {@link BulkCollisionError}.
 * 3. Empty short-circuit — if no op carries work, returns a zeroed
 *    {@link BulkResponse} without invoking `transact` or `audit`.
 * 4. Per-bucket ACL — for each populated bucket, asserts the caller's role
 *    clears the corresponding `access.{create|update|delete}`. A missing
 *    role on a populated bucket is a configuration error and is rejected.
 * 5. Per-row validation — inserts against `createSchema`, updates against
 *    `updateSchema`. The first failure throws a `DoNotDevError` prefixed
 *    with `inserts[i]` or `updates[i]` so callers can pinpoint the row.
 * 6. Id minting — if `mintInsertId` is supplied, every insert gets a fresh
 *    id stamped onto its {@link PreparedInsert}. Otherwise `id` is `""` and
 *    the storage layer mints its own.
 * 7. Metadata stamping — `stampInsertMetadata` / `stampUpdateMetadata` run
 *    per row / per patch if provided.
 * 8. `transact(prepared)` runs exactly once. Must be atomic. Throws
 *    propagate unchanged so the caller's error handler can classify them.
 * 9. Response-shape validation via {@link BulkResponseSchema}. A non-conforming
 *    target response throws `DoNotDevError(internal)`.
 * 10. `audit()` fires once with the three op counts.
 *
 * **Not this function's job:** rate-limiting, authentication, HTTP status
 * mapping, target-specific connection pooling, retry policy. All of those
 * belong to the surrounding handler wrapper.
 *
 * @template TRow - Row type (documentation only).
 * @param params - See {@link ExecuteBulkParams}.
 * @returns The validated {@link BulkResponse} from the target.
 *
 * @throws {BulkCollisionError} When the payload contains the same id in
 *   two mutually-exclusive buckets.
 * @throws {DoNotDevError} `invalid-argument` on wire-schema failure, per-row
 *   validation failure, or collision (wrapped for callers that only rethrow
 *   `DoNotDevError`).
 * @throws {DoNotDevError} `permission-denied` when the caller's role cannot
 *   satisfy a populated bucket's access requirement.
 * @throws {DoNotDevError} `internal` when the `transact` return value fails
 *   {@link BulkResponseSchema}.
 *
 * @example
 * ```typescript
 * import { executeBulk } from '@donotdev/functions/shared';
 * import { BulkResponseSchema } from '@donotdev/core/server';
 *
 * const response = await executeBulk({
 *   collection: 'events',
 *   ops: req.body,
 *   createSchema,
 *   updateSchema,
 *   access,
 *   uid,
 *   userRole,
 *   mintInsertId: () => crypto.randomUUID(),
 *   stampInsertMetadata: (row, uid) => ({
 *     ...row,
 *     createdById: uid,
 *     createdAt: new Date().toISOString(),
 *   }),
 *   transact: async ({ inserts, updates, deletes }) => {
 *     return db.runTransaction(async (tx) => {
 *       for (const { id, row } of inserts) tx.set(col.doc(id), row);
 *       for (const { id, patch } of updates) tx.update(col.doc(id), patch);
 *       for (const id of deletes) tx.delete(col.doc(id));
 *       return {
 *         insertedIds: inserts.map((i) => i.id),
 *         updatedIds: updates.map((u) => u.id),
 *         deletedIds: deletes,
 *       };
 *     });
 *   },
 *   audit: (event, { counts }) => logger.info(event, counts),
 * });
 * ```
 *
 * @version 0.1.0
 * @since 0.7.0
 * @author AMBROISE PARK Consulting
 */
export declare function executeBulk<TRow = unknown>(params: ExecuteBulkParams<TRow>): Promise<BulkResponse>;
