import * as drizzle_orm_pg_core from 'drizzle-orm/pg-core';
import { PgTableWithColumns, PgColumn } from 'drizzle-orm/pg-core';
import { SQL } from 'drizzle-orm';
import { DrizzleClient, DrizzleTx } from '../types/drizzle.js';
import 'drizzle-orm/node-postgres';

/**
 * Behavior flags for the repository. Controls automatic timestamp injection
 * and soft-delete filtering.
 */
interface BehaviorConfig {
    timestamps: boolean;
    softDelete: boolean;
    userTracking: boolean;
}
/**
 * Options for the list() method.
 */
interface ListOptions {
    where?: SQL;
    limit?: number;
    offset?: number;
    orderBy?: PgColumn | SQL;
}
declare abstract class BaseRepository<TEntity> {
    /**
     * The Drizzle table schema for this entity.
     * Concrete repositories declare this as a class property.
     */
    protected abstract readonly table: PgTableWithColumns<any>;
    /**
     * Behavior flags controlling automatic behavior injection.
     * Override in concrete repositories to enable behaviors.
     */
    protected readonly behaviors: BehaviorConfig;
    /**
     * Ambient tenant-scope enforcement for `userTracking` repos (see
     * `scopePredicate`). Only has effect when `behaviors.userTracking === true`.
     *
     * - `'lenient'` (default): when no ambient requester context is active,
     *   reads/writes are NOT scoped — preserves pre-scoping behavior, so adopting
     *   ambient scoping is additive. Scoping kicks in automatically once a
     *   boundary installs `withRequester(...)`.
     * - `'strict'`: a missing ambient context throws (`requireRequester`),
     *   making a forgotten boundary fail loud instead of silently returning
     *   cross-tenant rows. Recommended for new multi-tenant consumers — override
     *   in a concrete repo or a family base class.
     */
    protected readonly scopeEnforcement: 'lenient' | 'strict';
    protected readonly db: DrizzleClient;
    constructor(db: DrizzleClient);
    /**
     * Pick the runner for a write: the caller-supplied transaction handle
     * if present, otherwise the repository's own client. Keeps the `tx`
     * parameter purely additive — callers without a transaction call as
     * before. Used by the write methods below + consumer overrides (e.g.
     * the generated `upsertCurrentValues` on EAV value tables).
     */
    protected runner(tx?: DrizzleTx): DrizzleClient;
    /**
     * Find a single entity by its primary key.
     * Returns null if not found (or soft-deleted when softDelete=true).
     */
    findById(id: string): Promise<TEntity | null>;
    /**
     * Find multiple entities by their primary keys.
     * Returns empty array immediately for empty input (avoids DB errors).
     */
    findByIds(ids: string[]): Promise<TEntity[]>;
    /**
     * List entities with optional filtering, pagination, and ordering.
     */
    list(options?: ListOptions): Promise<TEntity[]>;
    /**
     * Count entities matching an optional WHERE clause.
     * Soft-deleted rows are always excluded when softDelete=true.
     */
    count(where?: SQL): Promise<number>;
    /**
     * Check whether an entity with the given id exists.
     */
    exists(id: string): Promise<boolean>;
    /**
     * Insert a new entity. Timestamps are auto-injected when timestamps=true.
     */
    create(input: Partial<TEntity>, tx?: DrizzleTx): Promise<TEntity>;
    /**
     * Update an existing entity by id. updatedAt is auto-injected when timestamps=true.
     * Returns the updated entity.
     */
    update(id: string, input: Partial<TEntity>, tx?: DrizzleTx): Promise<TEntity>;
    /**
     * Delete an entity by id.
     * - softDelete=true: sets deletedAt to current timestamp
     * - softDelete=false: hard-deletes the row
     */
    delete(id: string, tx?: DrizzleTx): Promise<void>;
    /**
     * Insert or update multiple entities.
     * Default naive implementation — family repositories override with
     * proper conflict-target upsert (e.g., CrmEntityRepository).
     */
    upsertMany(inputs: Array<Partial<TEntity>>, tx?: DrizzleTx): Promise<TEntity[]>;
    /**
     * Base SELECT query that automatically applies the ambient guards —
     * soft-delete exclusion (when `softDelete`) and tenant scope (when
     * `userTracking` + an active requester context) — combined with an optional
     * caller `extra` predicate into a SINGLE `WHERE`.
     *
     * Pass the leaf predicate as `extra` rather than chaining a second
     * `.where(...)`: Drizzle's `.where()` OVERRIDES (does not AND) a prior
     * `.where()` on a `$dynamic()` query, so a chained call would silently drop
     * the soft-delete and scope guards. `baseQuery(extra)` is the safe form.
     */
    protected baseQuery(extra?: SQL): drizzle_orm_pg_core.PgSelectBase<any, any, "single", {} | Record<any, "not-null">, true, never, {
        [x: string]: any;
    }[], any>;
    /**
     * Build the ambient tenant-scope predicate for this repo's table.
     *
     * Returns `undefined` (no scoping) when:
     *   - `behaviors.userTracking` is false (repo is not user-owned), or
     *   - no ambient requester context is active AND `scopeEnforcement` is
     *     `'lenient'` (the default — preserves pre-scoping behavior).
     *
     * When a requester context is active, scopes by `user_id` per the ambient
     * scope: `'user'` → `user_id = ctx.userId`; `'org'` → `user_id IN
     * ctx.orgUserIds` (empty list matches nothing — fail-closed); `'superuser'`
     * → no filter. See `tenant-context.ts` for the boundary-install contract.
     */
    protected scopePredicate(): SQL | undefined;
    /**
     * Combine the ambient scope predicate (and, optionally, the soft-delete
     * guard) with a caller `extra` predicate into one `SQL`. Returns `undefined`
     * when nothing applies. Used by read + by-id write paths so a single
     * `.where(...)` carries every guard.
     */
    protected scopeAnd(extra?: SQL, opts?: {
        softDelete?: boolean;
    }): SQL | undefined;
    /**
     * Merge timestamp fields into an input object.
     * - mode='create': adds createdAt and updatedAt
     * - mode='update': adds updatedAt only
     *
     * No-op when timestamps behavior is disabled.
     */
    protected withTimestamps(input: Record<string, unknown>, mode: 'create' | 'update'): Record<string, unknown>;
    /**
     * Build a WHERE clause fragment that restricts results to rows whose
     * parent (identified by a belongs_to FK) is not soft-deleted.
     *
     * Use this in custom repository methods when you need "rows reachable
     * from an active parent". The default findAll / findById behavior is
     * NOT changed by this helper — opt in explicitly where needed.
     *
     * ADR-021 — Soft-delete cascade: Option A (filter at query time).
     * `on_delete` FK rules do not fire for soft-deletes; use this helper
     * instead of expecting cascade semantics on the DB level.
     *
     * Example:
     *   async listActiveMessages(): Promise<Message[]> {
     *     return this.list({
     *       where: this.activeParentFilter(conversations, this.table['conversationId']),
     *     });
     *   }
     *
     * @param parentTable  The Drizzle table object for the parent entity.
     * @param parentFkColumn  The FK column on this (child) table that references parent.id.
     */
    protected activeParentFilter(parentTable: PgTableWithColumns<any>, // eslint-disable-line @typescript-eslint/no-explicit-any
    parentFkColumn: PgColumn): SQL;
}

export { BaseRepository, type BehaviorConfig, type ListOptions };
