import { QdrantClient } from "@qdrant/js-client-rest";
import type { ArtifactIndexState, CodeGraph, FileChunk, SearchResult } from "../types.js";
import type { GraphInputRecord } from "./graph-inputs.js";
import { type EffectiveIndexProfile } from "./index-profile.js";
/**
 * Render a Qdrant client error including the reason the server actually gave.
 *
 * `@qdrant/js-client-rest` builds an `ApiError` whose `message` is only the HTTP
 * status text — a 400 reads as the bare, useless "Bad Request" — while the
 * server's explanation ("JSON payload (N bytes) is larger than allowed …") sits
 * in `err.data.status.error`. Logging `err.message` alone therefore throws away
 * the one part a user can act on, so every symbol-graph failure looked
 * identical. Appends that reason when present, and looks through a `cause`
 * chain so an error already wrapped by {@link wrapQdrantError} still resolves.
 *
 * Without a server reason, appends the innermost cause's message and `code`
 * instead, so a transport failure reads as
 * `fetch failed: other side closed [UND_ERR_SOCKET]` rather than `fetch failed`.
 */
export declare function describeQdrantError(err: unknown): string;
export declare function getClient(): QdrantClient;
/**
 * Ensure a code collection is ready, sharing one attempt between concurrent
 * callers in this process. Failed attempts are removed so a later call retries.
 */
export declare function ensureCollection(name: string): Promise<void>;
/** True when an error means "someone else already created it" — safe to ignore. */
export declare function isAlreadyExistsError(err: unknown): boolean;
/** Create a payload index on a collection (best effort — never throws) */
export declare function ensurePayloadIndex(collName: string, fieldName: string): Promise<void>;
/** Delete a collection */
export declare function deleteCollection(name: string): Promise<void>;
export type ProjectPathState = "present-on-this-host" | "absent-on-this-host" | "unknown/inaccessible";
/** One metadata point, projected to the fields that decide whether a preview is still current. */
export interface ProjectMetadataRecord {
    pointId: string | number;
    collectionName: string;
    projectPath: string | null;
    indexingStatus: string | null;
    lastIndexedAt: string | null;
    lastBuiltAt: string | null;
    builtByVersion: string | null;
    /** Absent in legacy records. Refused or malformed ownership remains report-only. */
    localIndexOwnership?: unknown;
}
export interface ProjectReclamationEntry {
    identity: string;
    projectPath: string | null;
    canonicalPath: string | null;
    pathState: ProjectPathState;
    resourceCollections: string[];
    metadataRecords: ProjectMetadataRecord[];
    /** Some record says an indexer has this identity mid-write. */
    inProgress: boolean;
    possibleSuperseded: boolean;
    requiresManualInspection: boolean;
    /** Why a person has to look before this identity can be deleted; empty when nothing holds it back. */
    manualInspectionReasons: string[];
    confirmationToken: string;
}
/** A metadata point the inventory could not attribute to an identity, kept for a person to look at. */
export interface UnrecognisedMetadataEntry {
    pointId: string | number;
    collectionName: string | null;
    projectPath: string | null;
    reason: string;
}
/** A collection under the configured prefix whose name fits two identities and nothing settles which. */
export interface UnattributedCollection {
    name: string;
    reason: string;
    /** Every identity the name fits; each is held back from deletion while the name stays unsettled. */
    candidateIdentities: string[];
}
export interface ProjectReclamationInventory {
    entries: ProjectReclamationEntry[];
    unrecognisedMetadata: UnrecognisedMetadataEntry[];
    unattributedCollections: UnattributedCollection[];
}
export interface ProjectReclamationOutcome {
    resource: string;
    kind: "collection" | "metadata";
    /** `skipped`: the caller withdrew consent before this write; nothing was attempted. */
    outcome: "deleted" | "failed" | "skipped";
    error?: string;
}
/**
 * Read the stored identities without deriving an identity from the local path.
 * The result is an inventory: an absent path is local observation, never proof
 * that a shared-store index may be deleted.
 */
export declare function getProjectReclamationInventory(): Promise<ProjectReclamationInventory>;
/** Delete exactly the inventoried resources, one outcome each; a resource already gone counts as deleted. */
export declare function removeProjectReclamationEntry(entry: ProjectReclamationEntry, 
/** Asked before every write; once it answers false, nothing more is attempted. */
mayContinue?: () => boolean | Promise<boolean>, 
/** Automatic reclamation retains its proof until collection deletion is verified. Manual prune is unchanged. */
preserveMetadataUntilCollectionsGone?: boolean): Promise<ProjectReclamationOutcome[]>;
/** List all codebase, codegraph, and context artifact entries.
 * Codebase and context entries are actual collections; codegraph entries come from metadata.
 *
 * The collection-name filters honour `QDRANT_COLLECTION_PREFIX` so when
 * sharing a Qdrant server with other applications or other SocratiCode
 * instances, only this instance's collections are listed. */
export declare function listCodebaseCollections(): Promise<string[]>;
/** Upsert chunks into a collection */
export declare function upsertChunks(collectionName: string, chunks: FileChunk[], contentHash: string, profile: EffectiveIndexProfile): Promise<void>;
/** Upsert pre-embedded points into a collection (no embedding generation).
 * bm25Text is forwarded to Qdrant's server-side BM25 inference (truncated if too long).
 *
 * A failing batch is retried point by point to isolate the bad point(s). If any
 * point still fails, this throws: a partial write must fail the whole indexing
 * operation.
 *
 * Callers must not treat a partial write as success. A re-indexed file has its
 * previous chunks deleted before this call, so recording it as indexed after a
 * partial failure strands it at zero chunks with a content hash that suppresses
 * every future re-index. Throwing leaves stored hashes and earlier checkpoints
 * untouched, so the next index or update retries the file naturally. */
export declare function upsertPreEmbeddedChunks(collectionName: string, points: Array<{
    id: string;
    vector: number[];
    bm25Text: string;
    payload: Record<string, unknown>;
}>): Promise<void>;
/** Delete a file's chunks, optionally waiting for application before reporting freshness. */
export declare function deleteFileChunks(collectionName: string, relativePath: string, waitForCompletion?: boolean): Promise<void>;
/** Hybrid search: combines dense semantic search with BM25 lexical search via RRF fusion.
 * Dense vector is generated client-side; BM25 inference runs server-side in Qdrant (requires v1.15.2+). */
export declare function searchChunks(collectionName: string, query: string, limit?: number, fileFilter?: string, languageFilter?: string): Promise<SearchResult[]>;
export declare function loadEffectiveIndexProfileForCollection(collectionName: string): Promise<EffectiveIndexProfile>;
/** Merge results from multiple collection queries using client-side Reciprocal Rank Fusion.
 * Deduplicates by `label::relativePath` so that files with the same relative path
 * in different projects are kept as separate hits. Within a single project,
 * the first (higher-priority) occurrence wins on conflict.
 * Exported for unit testing. */
export declare function mergeMultiCollectionResults(collectionResults: Array<{
    label: string;
    results: SearchResult[];
}>, limit: number): SearchResult[];
/** Search across multiple collections in parallel with client-side RRF fusion and deduplication.
 * Each collection's results are queried independently, then merged using Reciprocal Rank Fusion.
 * When the same relativePath appears in multiple collections, the result from the
 * earlier (higher-priority) collection wins.
 *
 * @param collections - Array of { name, label } where label identifies the source project
 *   in results. Order defines priority for deduplication (first wins).
 * @param query - Natural language search query.
 * @param limit - Maximum total results to return after merge.
 * @param fileFilter - Optional relativePath filter applied to every collection.
 * @param languageFilter - Optional language filter applied to every collection.
 */
export declare function searchMultipleCollections(collections: Array<{
    name: string;
    label: string;
}>, query: string, limit?: number, fileFilter?: string, languageFilter?: string): Promise<SearchResult[]>;
/** Hybrid search with arbitrary payload filters.
 * Used by context artifacts to filter by artifactName. */
export declare function searchChunksWithFilter(collectionName: string, query: string, limit: number, filters: Array<{
    key: string;
    value: string;
}>): Promise<SearchResult[]>;
/**
 * Every `relativePath` that currently has at least one point in the collection.
 *
 * Exists so a caller can tell "this file is unchanged, skip it" apart from "this
 * file's chunks are gone". The stored hash map cannot make that distinction on
 * its own: it records what was hashed, not what survived.
 *
 * Pages through with `scroll` rather than `facet` deliberately — facet takes a
 * limit and offers no cursor, so it cannot enumerate a repository's worth of
 * paths reliably. Payload is narrowed to the one field and vectors are excluded,
 * so the cost is one small round trip per `pageSize` points.
 */
export declare function listIndexedFilePaths(collName: string, pageSize?: number): Promise<Set<string>>;
/** Get collection info.
 * Returns the collection info if it exists, null if the collection does not exist,
 * or throws an error if the request fails for any other reason (network, timeout, etc.).
 * This distinction is critical: callers must NOT treat transient errors as "collection missing". */
export interface CollectionInfo {
    pointsCount: number;
    status: string;
    /** Stored dense-vector width, when Qdrant exposes a supported vector config. */
    denseVectorSize?: number;
}
/**
 * Read a collection's point count, status and dense vector size.
 *
 * Returns `null` only when the collection does not exist; every other failure
 * propagates.
 *
 * A read of a collection this process is still initializing waits for that
 * initialization first: Qdrant can answer a read made during the create with a
 * 500 rather than 404 or the new collection. If the initialization fails, that
 * failure propagates. Reads with no local initialization in flight are
 * unaffected.
 */
export declare function getCollectionInfo(name: string): Promise<CollectionInfo | null>;
/** Reset the metadata collection readiness cache (for testing only) */
export declare function resetMetadataCollectionCache(): void;
/** Indexing status persisted in Qdrant metadata */
export type IndexingStatus = "in-progress" | "completed";
export interface ProjectMetadata {
    projectPath: string;
    lastIndexedAt: string;
    filesTotal: number;
    filesIndexed: number;
    indexingStatus: IndexingStatus;
    effectiveProfile: EffectiveIndexProfile | null;
}
/** Save project metadata and file hashes to Qdrant */
export declare function saveProjectMetadata(collName: string, projectPath: string, filesTotal: number, filesIndexed: number, fileHashes: Map<string, string>, indexingStatus: IndexingStatus, effectiveProfile: EffectiveIndexProfile): Promise<void>;
/** Load only the stored code-index profile. Missing profile means legacy metadata. */
export declare function loadProjectEffectiveProfile(collName: string): Promise<EffectiveIndexProfile | null>;
/** Load file hashes for a project from Qdrant.
 * Returns the hash map if found, null if the metadata point doesn't exist,
 * or throws on transient/unexpected errors so callers can distinguish
 * "no metadata" from "Qdrant unreachable". */
export declare function loadProjectHashes(collName: string): Promise<Map<string, string> | null>;
/** Get project metadata (for list display).
 * Returns null if metadata doesn't exist or on any error (logged as warning). */
export declare function getProjectMetadata(collName: string): Promise<ProjectMetadata | null>;
/**
 * Persisted indexing status, without the best-effort swallowing.
 *
 * `getProjectMetadata()` is display-oriented: it catches every read error and
 * answers `null`, which is right for showing a status line and wrong for
 * deciding one. A caller that asks "was the last run interrupted?" and takes
 * `null` for "no" will, on a transient metadata read failure, skip the recovery
 * that failure should have triggered — and then persist the still-damaged index
 * as `completed`, which is worse than not having run at all.
 *
 * Returns `null` only when there is genuinely no metadata for the collection.
 * Transport, parsing and validation failures propagate, so the caller aborts
 * rather than guessing.
 */
export declare function loadIndexingStatus(collName: string): Promise<IndexingStatus | null>;
/** Delete project metadata.
 * Errors are logged but not propagated (best-effort deletion). */
export declare function deleteProjectMetadata(collName: string): Promise<void>;
/** Save a code graph to Qdrant as a single metadata point */
export declare function saveGraphData(graphCollName: string, projectPath: string, graph: CodeGraph, graphInputs: GraphInputRecord): Promise<void>;
/** Load a code graph from Qdrant.
 * Returns null if no graph exists or on any error (logged as warning). */
export declare function loadGraphData(graphCollName: string): Promise<CodeGraph | null>;
/** Get graph metadata (for list/status display).
 * Returns null if no graph exists or on any error (logged as warning). */
export declare function getGraphMetadata(graphCollName: string): Promise<{
    projectPath: string;
    lastBuiltAt: string;
    nodeCount: number;
    edgeCount: number;
    /** Absent on graphs persisted before this field was recorded. */
    importCount?: number;
    /** SocratiCode version that built this graph. Absent on graphs persisted
     * before this field was recorded. */
    builtByVersion?: string;
} | null>;
/**
 * What is stored about the graph's inputs, and whether anything is stored at
 * all — three outcomes the caller must not confuse.
 */
export type GraphInputsLoad = 
/** No metadata point: this project has no persisted graph. */
{
    status: "absent";
}
/** A graph is there. `value` is its record, or undefined on one built before records existed. */
 | {
    status: "stored";
    value: unknown;
}
/** The read itself failed. Nothing is known, least of all that there is no record. */
 | {
    status: "failed";
    error: string;
};
/**
 * The record of what the graph was built from, without the graph.
 *
 * Projected to that one field deliberately. The same point carries
 * `graphData`, the whole serialized graph — megabytes on a large repository —
 * and every incremental update asks this question. Pulling the graph across to
 * answer it would cost more than the rebuild the answer is meant to avoid —
 * the same amplification that made `listCodebaseCollections` project its
 * scroll rather than read whole payloads.
 *
 * A missing point and a failed read are reported apart, and neither is
 * reported as "no record". A graph with no record must rebuild once and write
 * one however quiet the update was; a graph that could not be read tells us
 * nothing and must be treated as the most conservative of the three; and only
 * a genuinely absent point means there is no graph here to refresh.
 */
export declare function loadGraphInputs(graphCollName: string): Promise<GraphInputsLoad>;
/** Delete graph data from metadata.
 * Errors are logged but not propagated (best-effort deletion). */
export declare function deleteGraphData(graphCollName: string): Promise<void>;
/** Save context artifact metadata to Qdrant */
export declare function saveContextMetadata(contextCollName: string, projectPath: string, artifacts: ArtifactIndexState[], effectiveProfile: EffectiveIndexProfile): Promise<void>;
export interface ContextIndexMetadata {
    artifacts: ArtifactIndexState[];
    effectiveProfile: EffectiveIndexProfile | null;
}
/**
 * Load context states and profile for mutation paths. Missing metadata returns
 * null; transport, JSON, and profile validation failures propagate.
 */
export declare function loadContextIndexMetadata(contextCollName: string): Promise<ContextIndexMetadata | null>;
/** Load only the stored context-index profile. Missing profile means legacy metadata. */
export declare function loadContextEffectiveProfile(contextCollName: string): Promise<EffectiveIndexProfile | null>;
/** Load context artifact metadata from Qdrant.
 * Returns null if no metadata exists or on any error (logged as warning). */
export declare function loadContextMetadata(contextCollName: string): Promise<ArtifactIndexState[] | null>;
/** Get context collection metadata (for list/status display).
 * Returns null if no metadata exists or on any error (logged as warning). */
export declare function getContextMetadata(contextCollName: string): Promise<{
    projectPath: string;
    lastIndexedAt: string;
    artifactCount: number;
    effectiveProfile: EffectiveIndexProfile | null;
} | null>;
/** Delete context artifact metadata.
 * Errors are logged but not propagated (best-effort deletion). */
export declare function deleteContextMetadata(contextCollName: string): Promise<void>;
/** Delete all chunks for a specific artifact within a collection */
export declare function deleteArtifactChunks(collectionName: string, artifactName: string): Promise<void>;
//# sourceMappingURL=qdrant.d.ts.map