import type { QmdSearchResult } from "../types.js";

/** Alias so consumers don't need to reference "Qmd" in a backend-agnostic context. */
export type SearchResult = QmdSearchResult;

export interface SearchQueryOptions {
  intent?: string;
  explain?: boolean;
  candidateLimit?: number;
  rerank?: boolean;
  chunkStrategy?: "auto" | "regex";
  structuredSearches?: Array<{ type: "lex" | "vec" | "hyde"; query: string }>;
}

/**
 * A search that returned empty (or partial) results because the backend was
 * unavailable, still loading, or timed out — cases that are otherwise
 * indistinguishable from a genuine "no matches" (issue #1536, CLAUDE.md
 * rule 34).
 */
export interface SearchDegradation {
  backend: "qmd";
  code:
    | "backend_unavailable"
    | "daemon_timeout"
    | "daemon_loading"
    | "subprocess_error"
    | "deadline_exceeded";
  detail?: string;
}

export interface SearchExecutionOptions {
  signal?: AbortSignal;
  /**
   * Observer invoked when the backend degrades during this call (#1536).
   * Callers that need to distinguish empty-because-degraded from
   * empty-because-no-matches (recall x-ray, fallback decisions) pass a
   * collector here. Observer failures are swallowed by the notifier —
   * observability must never break search.
   */
  onDegradation?: (degradation: SearchDegradation) => void;
}

export function resolveEnsureCollectionArgs(
  collectionOrExecution?: string | SearchExecutionOptions,
  execution?: SearchExecutionOptions,
): { collection?: string; execution?: SearchExecutionOptions } {
  if (typeof collectionOrExecution === "string") {
    return { collection: collectionOrExecution, execution };
  }
  return { collection: undefined, execution: collectionOrExecution ?? execution };
}

/**
 * Abstract search backend interface.
 *
 * Implementations:
 * - QmdClient (default, local hybrid BM25+vector+reranking)
 * - OramaBackend (embedded, pure JS, hybrid FTS+vector)
 * - LanceDbBackend (embedded, native Arrow bindings, RRF reranking)
 * - MeilisearchBackend (server-based SDK, hybrid search)
 * - RemoteSearchBackend (HTTP REST adapter)
 * - NoopSearchBackend (graceful degradation)
 *
 * See docs/writing-a-search-backend.md for the implementation guide.
 */
export interface SearchBackend {
  // ── Lifecycle ──
  probe(): Promise<boolean>;
  /**
   * Optional non-mutating availability probe for health/readiness checks.
   * Implementations must avoid auto-upgrades, collection creation, daemon
   * startup, or any other runtime-modifying side effects.
   */
  checkAvailability?(execution?: SearchExecutionOptions): Promise<boolean>;
  isAvailable(): boolean;
  debugStatus(): string;

  // ── Search ──
  search(
    query: string,
    collection?: string,
    maxResults?: number,
    options?: SearchQueryOptions,
    execution?: SearchExecutionOptions,
  ): Promise<SearchResult[]>;
  searchGlobal(query: string, maxResults?: number, execution?: SearchExecutionOptions): Promise<SearchResult[]>;
  bm25Search(
    query: string,
    collection?: string,
    maxResults?: number,
    execution?: SearchExecutionOptions,
  ): Promise<SearchResult[]>;
  vectorSearch(
    query: string,
    collection?: string,
    maxResults?: number,
    execution?: SearchExecutionOptions,
  ): Promise<SearchResult[]>;
  hybridSearch(
    query: string,
    collection?: string,
    maxResults?: number,
    execution?: SearchExecutionOptions,
  ): Promise<SearchResult[]>;

  // ── Maintenance ──
  update(execution?: SearchExecutionOptions): Promise<void>;
  /**
   * Optional strict refresh used by callers that must know whether the backend
   * was actually refreshed before writing success markers. Ordinary update
   * calls remain fail-open for migration/maintenance resilience.
   */
  updateStrict?(execution?: SearchExecutionOptions): Promise<void>;
  updateCollection(collection: string, execution?: SearchExecutionOptions): Promise<void>;
  updateCollectionFromDir?(collection: string, memoryDir: string, execution?: SearchExecutionOptions): Promise<void>;
  /**
   * True when update() refreshes every indexed collection, not just this
   * backend's configured collection. Namespace routers use this to avoid
   * repeating the same expensive global update once per namespace.
   */
  updatesAllCollections?(): boolean;
  /**
   * Optional strict refresh used by callers that must know whether a collection
   * was actually refreshed before writing success markers. Ordinary update
   * calls remain fail-open for migration/maintenance resilience.
   */
  updateCollectionStrict?(collection: string, execution?: SearchExecutionOptions): Promise<void>;
  embed(): Promise<void>;
  /**
   * Optional strict embed used by callers that must know vectors were actually
   * refreshed before writing success markers.
   */
  embedStrict?(): Promise<void>;
  embedCollection(collection: string): Promise<void>;
  /**
   * Optional strict collection embed used by callers that must know vectors were
   * actually refreshed before writing success markers.
   */
  embedCollectionStrict?(collection: string): Promise<void>;

  // ── Collection management ──
  /**
   * Optional non-mutating collection probe. Backends that can distinguish a
   * missing collection from a transient probe failure should implement this so
   * callers can avoid auto-creating collections in unsafe layouts.
   */
  checkCollection?(
    collectionOrExecution?: string | SearchExecutionOptions,
    execution?: SearchExecutionOptions,
  ): Promise<"present" | "missing" | "unknown" | "skipped">;
  ensureCollection(
    memoryDir: string,
    execution?: SearchExecutionOptions,
  ): Promise<"present" | "missing" | "unknown" | "skipped">;
  ensureCollection(
    memoryDir: string,
    collection?: string,
    execution?: SearchExecutionOptions,
  ): Promise<"present" | "missing" | "unknown" | "skipped">;
}
