import type { ColumnFilter } from './operators.js';
import type { ComputedSource, GroupByCountOptions, SortItem } from './types.js';
/**
 * The structural subset of a Lucid `ModelQueryBuilder` / `DatabaseQueryBuilder`
 * the adapter drives. An `@adonisjs/lucid` query builder satisfies it — declared
 * locally so the adapter never hard-imports Lucid (and stays unit-testable with a
 * recording mock). The nested-callback `where`/`orWhere` overloads model Lucid's
 * grouping closures used for AND/OR composition.
 */
export interface QueryBuilderLike {
    where(callback: (qb: QueryBuilderLike) => void): QueryBuilderLike;
    where(column: string, value: unknown): QueryBuilderLike;
    where(column: string, operator: string, value: unknown): QueryBuilderLike;
    orWhere(callback: (qb: QueryBuilderLike) => void): QueryBuilderLike;
    /**
     * Constrain the query to rows whose `relation` has at least one related row
     * matching the nested conditions — Lucid's `whereHas`. Used to translate a
     * dotted relation-path filter (`posts.title = x`) into a real subquery
     * (`whereHas('posts', (q) => q.where('title', x))`) instead of a dotted
     * column reference. The recording mock implements it so relation translation
     * stays unit-testable.
     *
     * `relation` is `any`, not `string`, and that is load-bearing: Lucid types its
     * own `whereHas` as `<Name extends ExtractModelRelations<Model>>(relation: Name, ...)`
     * — a union of the model's *literal* relation names. `string` is not assignable
     * to that union, so declaring `relation: string` here makes every real Lucid
     * builder fail to satisfy this interface, and TS reports the failure against
     * `where` (the first member it tries), which sends you hunting in the wrong
     * place. Widening the member to optional does NOT help: an optional member
     * that is present is still checked. Since the parameter must accept the
     * `string` the adapter passes AND be assignable to each model's relation-name
     * union, `any` is the only type that works.
     */
    whereHas(relation: any, callback: (qb: QueryBuilderLike) => void): QueryBuilderLike;
    whereNot(column: string, value: unknown): QueryBuilderLike;
    whereIn(column: string, values: unknown[]): QueryBuilderLike;
    whereNotIn(column: string, values: unknown[]): QueryBuilderLike;
    whereNull(column: string): QueryBuilderLike;
    whereNotNull(column: string): QueryBuilderLike;
    whereBetween(column: string, range: [unknown, unknown]): QueryBuilderLike;
    whereNotBetween(column: string, range: [unknown, unknown]): QueryBuilderLike;
    whereILike(column: string, value: string): QueryBuilderLike;
    orWhereILike(column: string, value: string): QueryBuilderLike;
    orderBy(column: string, direction: 'asc' | 'desc'): QueryBuilderLike;
    /**
     * Project columns — Lucid's `select`. Optional on the seam: only aggregations need it
     * (entity-row listings select whole rows), so a minimal custom implementation can omit it
     * until it serves a `groupByCount`.
     */
    select?: (...columns: string[]) => unknown;
    /**
     * Aggregate rows — Lucid's `count`. Optional, same reason as {@link select}: only the
     * group-by-count aggregation calls it, with a `'* AS count'`-style argument.
     */
    count?: (column: string) => unknown;
    /**
     * Group rows — Lucid's `groupBy`. Optional, same reason: only the group-by-count aggregation
     * calls it.
     */
    groupBy?: (...columns: string[]) => unknown;
    /**
     * Skip rows — Lucid's `offset`. Optional: entity-row pagination goes through Lucid's own
     * `paginate`, so only the group-by-count aggregation pages with limit/offset directly.
     */
    offset?: (n: number) => unknown;
    /**
     * Add a raw SQL predicate with positional bindings — Lucid's `whereRaw`. The
     * escape hatch for constraints no structured method can express, used here for
     * pgvector similarity (`embedding <=> ?::vector < ?`). `sql` is server-authored
     * (never client text); every user value travels as a `?` binding. Optional on
     * real Lucid builders; the recording mock implements it so vector translation
     * stays unit-testable and the package stays framework-free.
     */
    whereRaw(sql: string, bindings?: readonly unknown[]): QueryBuilderLike;
    /**
     * Add a raw SQL ordering with positional bindings — Lucid's `orderByRaw`. Used
     * to order by a pgvector distance expression (`embedding <=> ?::vector asc`),
     * which no column-name `orderBy` can express. Same seam contract as
     * {@link QueryBuilderLike.whereRaw}.
     */
    orderByRaw(sql: string, bindings?: readonly unknown[]): QueryBuilderLike;
    /**
     * Restrict the query to DISTINCT tuples of `columns` — Lucid's `distinct`.
     * Used by {@link applyDistinct} to execute a client `.distinct(...)`
     * projection. The columns are the already alias-resolved, allow-listed field
     * names; a variadic column list matches Lucid's signature exactly. Optional on
     * real Lucid builders; the recording mock implements it so the translation
     * stays unit-testable.
     */
    distinct(...columns: string[]): QueryBuilderLike;
    limit(count: number): QueryBuilderLike;
}
/**
 * A pgvector distance metric → the operator applied between the vector column and
 * the query embedding:
 *
 * - `'cosine'` — cosine distance, pgvector `<=>` (default; the usual choice for
 *   normalized embeddings).
 * - `'l2'` — Euclidean / L2 distance, pgvector `<->`.
 * - `'innerProduct'` — negative inner product, pgvector `<#>`.
 *
 * All three are *distance* operators (smaller = more similar), so nearest-first
 * ranking is always ascending order.
 */
export type VectorDistanceMetric = 'cosine' | 'l2' | 'innerProduct';
/**
 * Options for {@link applyVectorSimilarity} — an **embedding similarity** ordering
 * (pgvector) against a `vector` column. This is DISTINCT from full-text search
 * ({@link applyFullTextSearch}): here a query *embedding* is ranked against the
 * column by a distance metric, optionally filtered by a distance threshold and
 * truncated to the top-K nearest rows. (Full-text search matches a text *query
 * string* against a tsvector document.)
 */
export interface VectorSimilarityOptions {
    /** The pgvector column to compare the query embedding against (e.g. `'embedding'`). */
    column: string;
    /** The query embedding — the vector rows are ranked by similarity to. */
    vector: readonly number[];
    /** Distance metric → pgvector operator. Default `'cosine'` (`<=>`). */
    metric?: VectorDistanceMetric;
    /**
     * Keep only rows whose distance to the query embedding is strictly below this
     * value (a `WHERE embedding <=> ? < threshold`). Omitted → no distance filter.
     */
    threshold?: number;
    /**
     * Limit the result to the K nearest rows (a `LIMIT`). Omitted → no limit is
     * added here (the caller's pagination still applies).
     */
    topK?: number;
    /**
     * Order by ascending distance (nearest first). Default `true`. Set `false` to
     * apply only a threshold filter without changing the query's ordering.
     */
    order?: boolean;
}
/**
 * Apply an **embedding similarity** ordering (pgvector) to a Lucid query builder.
 *
 * This is *not* full-text search — it ranks rows by distance between a stored
 * embedding column and a query *embedding vector*. For matching a text query
 * string, use {@link applyFullTextSearch} instead.
 *
 * pgvector expresses similarity through raw distance operators (`<=>`, `<->`,
 * `<#>`) that no structured query-builder method covers, so this drives the
 * adapter's raw seam ({@link QueryBuilderLike.whereRaw}/`orderByRaw`) with the
 * query embedding passed as a positional binding — the column name is the only
 * interpolated fragment, and it is validated against a strict identifier charset
 * so it can never carry injection. The distance expression is
 * `<column> <op> ?::vector`, cast to `vector` so a text binding compares against
 * the column.
 *
 * A no-op when the embedding is empty. When `order` is not `false`, rows are
 * ordered nearest-first (ascending distance); `threshold` adds a max-distance
 * filter; `topK` truncates to the K nearest.
 */
export declare function applyVectorSimilarity(qb: QueryBuilderLike, opts: VectorSimilarityOptions): void;
/**
 * Options for {@link applyFullTextSearch} — a Postgres **tsvector full-text
 * search** (the parity behavior of the NestJS reference's `applyVectorSearch`).
 * The user query string is matched against a text-search document with
 * `websearch_to_tsquery` and the `@@` operator, optionally ranked by `ts_rank`.
 *
 * This is DISTINCT from {@link VectorSimilarityOptions} (embedding similarity):
 * here the input is arbitrary *text*, tokenized by a language config; there the
 * input is a numeric *embedding vector*.
 */
export interface FullTextSearchOptions {
    /**
     * The user's raw search text. Always passed as a positional binding to
     * `websearch_to_tsquery` — never interpolated into SQL. A no-op when blank.
     */
    query: string;
    /**
     * The document to match against. Either a single precomputed `tsvector`
     * column (matched directly with `@@`), or one-or-more plain text columns that
     * are wrapped in `to_tsvector(<language>, ...)` at query time (see
     * {@link FullTextSearchOptions.columnKind}). Passing multiple columns implies
     * `'text'`.
     */
    column: string | readonly string[];
    /**
     * Postgres text-search config / language used for both `websearch_to_tsquery`
     * and any `to_tsvector` wrapping (e.g. `'english'`, `'simple'`). Default
     * `'english'`. Validated against the strict identifier charset before it is
     * spliced into SQL.
     */
    language?: string;
    /**
     * Add relevance ordering for matched rows (`ORDER BY ts_rank(...) DESC`). Off
     * by default because it changes the query's default ordering; opt in for
     * best-match-first results.
     */
    rank?: boolean;
    /**
     * How {@link FullTextSearchOptions.column} is treated:
     * - `'tsvector'` — a precomputed `tsvector` column, matched directly (`col @@ ...`);
     * - `'text'` — plain text column(s), wrapped in `to_tsvector(<language>, col ...)`.
     *
     * Defaults to `'tsvector'` for a single column and `'text'` when multiple
     * columns are given.
     */
    columnKind?: 'tsvector' | 'text';
}
/**
 * Apply a Postgres **tsvector full-text search** to a Lucid query builder — the
 * parity port of the NestJS reference's `applyVectorSearch` (tsvector), NOT the
 * embedding similarity {@link applyVectorSimilarity}.
 *
 * Injection-safety: the user `query` string ALWAYS travels as a positional
 * binding (`websearch_to_tsquery('<lang>', ?)`); it is never interpolated.
 * `websearch_to_tsquery` (not the raw `to_tsquery`) parses arbitrary user input
 * — multi-word text, `"quoted phrases"`, `-exclude`, `or` — without throwing a
 * syntax error. The only spliced fragments are the column name(s) and the
 * language config, each validated against the strict identifier charset
 * ({@link SAFE_IDENTIFIER}) before use, so neither can carry injection.
 *
 * The match predicate is `<document> @@ websearch_to_tsquery('<lang>', ?)` where
 * `<document>` is either the tsvector column directly, or
 * `to_tsvector('<lang>', coalesce(col1,'') || ' ' || coalesce(col2,''))` for
 * text columns. When `rank` is set, rows are additionally ordered by descending
 * `ts_rank(<document>, websearch_to_tsquery('<lang>', ?))`. A no-op for a blank
 * query.
 */
export declare function applyFullTextSearch(qb: QueryBuilderLike, opts: FullTextSearchOptions): void;
/** Apply an array of column filters (combined with AND) to a Lucid query builder. */
export declare function applyColumnFilters(qb: QueryBuilderLike, filters: ColumnFilter[]): void;
/** Apply sort directives to a Lucid query builder, in order. */
export declare function applySort(qb: QueryBuilderLike, sorts: SortItem[]): void;
/**
 * Apply a DISTINCT projection over `columns` to a Lucid query builder — the
 * executable half of the client's `.distinct(...)` (which until now the server
 * silently ignored). A no-op for an empty list. The columns are the
 * already-validated, alias-resolved field names (the runner resolves aliases and
 * enforces the allow-list before calling this), so nothing client-controlled is
 * interpolated: Lucid quotes each identifier itself.
 */
export declare function applyDistinct(qb: QueryBuilderLike, columns: string[]): void;
/**
 * Terminal group-by-count aggregation over one column:
 * `SELECT <col> AS value, COUNT(*) AS count … GROUP BY <col>`, most groups first —
 * what populates a filter dropdown. `column` must already be validated (allow-listed): it is
 * interpolated as an identifier, while every client VALUE rides a positional binding.
 *
 * Fixed ordering (count desc, value asc) is load-bearing, not cosmetic: the answer is pageable,
 * and paging an unordered listing repeats and skips rows.
 *
 * Requires a builder carrying the optional aggregation seam (`select`/`count`/`groupBy`/`offset`);
 * throws a plain `Error` naming the missing method otherwise, so a minimal custom implementation
 * fails loudly instead of silently returning entity rows.
 */
export declare function applyGroupByCount(qb: QueryBuilderLike, column: string, opts?: GroupByCountOptions): void;
/**
 * Resolve a {@link ComputedSource} to its final SQL expression string. The
 * string form is verbatim; the function form is invoked with the root table
 * {@link ComputedContext} so it can splice the outer alias into a correlated
 * subquery. Either way the result is dev-authored — never client text.
 */
export declare function resolveComputedExpression(source: ComputedSource, alias: string): string;
/**
 * Apply a filter on a dev-declared **computed field** to a Lucid query builder.
 *
 * The already-resolved `expression` is inlined as the raw left-hand side (it is
 * dev-authored — a verbatim string or a function's output — never client text);
 * the client's filter VALUE always rides through as a positional `?` binding,
 * exactly the injection-safety contract real-column filters have. The whole
 * expression is parenthesized so a compound source
 * (`first || ' ' || last`, `(SELECT …)`) composes under the operator without a
 * precedence surprise.
 */
export declare function applyComputedField(qb: QueryBuilderLike, expression: string, filter: ColumnFilter): void;
/**
 * Append an ORDER BY on a dev-declared **computed field** to a Lucid query
 * builder via `orderByRaw`. Uses append semantics (like `orderBy`) so a computed
 * sort composes with real-column sorts in request order. The expression is
 * dev-authored and parenthesized; the direction is a validated literal.
 */
export declare function applyComputedSort(qb: QueryBuilderLike, expression: string, direction: 'asc' | 'desc'): void;
/**
 * Apply a keyset (cursor) seek predicate to a Lucid query builder for row-value
 * comparison across the keyset columns.
 *
 * Given a keyset (the active sort columns plus a primary-key tiebreaker, each
 * with a direction) and a boundary row's values, this constrains the query to
 * rows strictly *after* the boundary in keyset order. The row-value comparison
 * is expanded into the portable "OR of AND tiers" form that works across all
 * SQL dialects:
 *
 * ```text
 *   (c0 OP0 v0)
 *   OR (c0 = v0 AND c1 OP1 v1)
 *   OR (c0 = v0 AND c1 = v1 AND c2 OP2 v2) ...
 * ```
 *
 * where `OPi` is `>` for an `asc` column and `<` for a `desc` column. The whole
 * predicate is wrapped in one AND-group so it composes with any existing
 * `where` conditions. A no-op when the keyset is empty or `values` does not line
 * up positionally with it.
 */
export declare function applyKeyset(qb: QueryBuilderLike, keyset: SortItem[], values: unknown[]): void;
/** Apply a free-text ILIKE search across `columns` (OR-combined) to a Lucid query builder. */
export declare function applySearch(qb: QueryBuilderLike, term: string, columns: string[]): void;
//# sourceMappingURL=lucid_adapter.d.ts.map