/**
 * Type definitions for the Soulcraft Brainy
 */

import { NounType, VerbType } from './types/graphTypes.js'

// Re-export NounType and VerbType for use in other modules (as values, not just types)
export { NounType, VerbType }

/**
 * Vector representation - an array of numbers
 */
export type Vector = number[]

/**
 * A document with a vector embedding and optional metadata
 */
export interface VectorDocument<T = any> {
  id: string
  vector: Vector
  metadata?: T
}

/**
 * Search result with similarity score
 */
export interface SearchResult<T = any> {
  id: string
  score: number
  vector: Vector
  metadata?: T
}

/**
 * Cursor for pagination through search results
 */
export interface SearchCursor {
  lastId: string
  lastScore: number
  position: number // For debugging/logging
}

/**
 * Paginated search result with cursor support
 */
export interface PaginatedSearchResult<T = any> {
  results: SearchResult<T>[]
  cursor?: SearchCursor
  hasMore: boolean
  totalEstimate?: number
}

/**
 * Distance function for comparing vectors
 */
export type DistanceFunction = (a: Vector, b: Vector) => number

/**
 * Embedding function for converting data to vectors
 * Now properly typed - accepts string, string array (batch), or object, no `any`
 */
export type EmbeddingFunction = (data: string | string[] | Record<string, unknown>) => Promise<Vector>

/**
 * Embedding model interface
 */
export interface EmbeddingModel {
  /**
   * Initialize the embedding model
   */
  init(): Promise<void>

  /**
   * Embed data into a vector
   * Now properly typed - accepts string, string array (batch), or object, no `any`
   */
  embed(data: string | string[] | Record<string, unknown>): Promise<Vector>

  /**
   * Dispose of the model resources
   */
  dispose(): Promise<void>
}

/**
 * HNSW graph noun - Pure vector structure
 *
 * metadata field removed
 * - Stores ONLY vector data for optimal memory usage
 * - Metadata stored separately and combined on retrieval
 * - 25% memory reduction @ 1B scale (no in-memory metadata)
 * - Prevents metadata explosion bugs at compile-time
 */
export interface HNSWNoun {
  id: string
  vector: Vector
  connections: Map<number, Set<string>> // level -> set of connected noun ids
  level: number // The highest layer this noun appears in
  // ✅ NO metadata field - stored separately for optimization
}

/**
 * Lightweight verb for HNSW index storage - Core relational structure
 *
 * Core fields: verb/sourceId/targetId are first-class fields
 * These are NOT metadata - they're the essence of what a verb IS:
 * - verb: The relationship type (creates, contains, etc.) - needed for routing & display
 * - sourceId: What entity this verb connects FROM - needed for graph traversal
 * - targetId: What entity this verb connects TO - needed for graph traversal
 *
 * metadata field removed
 * - Stores ONLY vector + core relational data
 * - User metadata (weight, custom fields) stored separately
 * - 10x faster metadata-only updates (skip HNSW rebuild)
 * - Prevents metadata explosion bugs at compile-time
 *
 * Benefits:
 * - ONE file read for graph operations (core fields always available)
 * - No type caching needed (type is always available)
 * - Faster graph traversal (source/target immediately available)
 * - Optimal memory usage (no user metadata in HNSW)
 */
export interface HNSWVerb {
  id: string
  vector: Vector
  connections: Map<number, Set<string>> // level -> set of connected verb ids

  // CORE RELATIONAL DATA (not metadata!)
  verb: VerbType      // Relationship type - REQUIRED, validated at compile + runtime
  sourceId: string    // Source entity UUID - REQUIRED for graph traversal
  targetId: string    // Target entity UUID - REQUIRED for graph traversal

  // ✅ NO metadata field - stored separately for optimization
}

/**
 * Noun metadata structure
 *
 * Now contains ONLY custom user-defined fields
 * - Standard fields (confidence, weight, timestamps, etc.) moved to top-level in HNSWNounWithMetadata
 * - This interface represents custom metadata stored separately from vector data
 * - Storage format unchanged (backward compatible at storage layer)
 * - Combines with HNSWNoun to form complete entity
 *
 * NOTE: For storage backward compatibility, we still store all fields in metadata files,
 * but in-memory entity structures have standard fields at top-level.
 */
export interface NounMetadata {
  // Storage backward compatibility: these fields still exist in storage
  // but are extracted to top-level when creating HNSWNounWithMetadata
  noun?: string  // NounType as string (stored for backward compat, extracted to type)
  data?: unknown
  createdAt?: { seconds: number; nanoseconds: number } | number
  updatedAt?: { seconds: number; nanoseconds: number } | number
  createdBy?: { augmentation: string; version: string }
  service?: string
  confidence?: number
  weight?: number

  // User-defined custom fields
  [key: string]: unknown
}

/**
 * Verb metadata structure
 *
 * Now contains ONLY custom user-defined fields
 * - Standard fields (weight, confidence, timestamps, etc.) moved to top-level in HNSWVerbWithMetadata
 * - This interface represents custom metadata stored separately from vector + core relational data
 * - Storage format unchanged (backward compatible at storage layer)
 * - Core fields (verb, sourceId, targetId) remain in HNSWVerb
 *
 * NOTE: For storage backward compatibility, we still store all fields in metadata files,
 * but in-memory entity structures have standard fields at top-level.
 */
export interface VerbMetadata {
  // Storage backward compatibility: these fields still exist in storage
  // but are extracted to top-level when creating HNSWVerbWithMetadata
  verb?: string  // For count tracking (stored for backward compat)
  weight?: number
  confidence?: number
  data?: unknown
  createdAt?: { seconds: number; nanoseconds: number } | number
  updatedAt?: { seconds: number; nanoseconds: number } | number
  createdBy?: { augmentation: string; version: string }
  service?: string

  // User-defined custom fields
  [key: string]: unknown
}

/**
 * Visibility tier for an entity or relationship — controls whether it surfaces
 * on Brainy's default user-facing read paths. A reserved, top-level field; the
 * absence of the field is exactly equivalent to `'public'`.
 *
 * - `'public'` (DEFAULT, and the meaning when the field is absent) — counted and
 *   returned everywhere: `find()`, `related()`, `getNounCount()`/`getVerbCount()`,
 *   `stats()`.
 * - `'internal'` — a consumer's app-internal data. HIDDEN from the default
 *   `find()` / `related()` / counts / `stats()`, but retrievable with an explicit
 *   opt-in (`find({ includeInternal: true })`, `related({ includeInternal: true })`).
 * - `'system'` — Brainy's own plumbing (e.g. the VFS root entity). Hidden
 *   EVERYWHERE by default and surfaced only via the explicit `includeSystem`
 *   opt-in. NOT settable by consumer code — only internal Brainy code assigns it,
 *   which is why the `add()` / `relate()` params type narrows to
 *   `'public' | 'internal'`.
 */
export type EntityVisibility = 'public' | 'internal' | 'system'

/**
 * Combined noun structure for transport/API boundaries
 *
 * Standard fields moved to top-level
 * - ALL standard fields (confidence, weight, timestamps, etc.) are now at top-level
 * - metadata contains ONLY custom user-defined fields
 * - Provides clean, predictable API: entity.confidence always works
 * - 20% memory reduction @ billion scale (no duplicate storage)
 *
 * Used for API responses and storage retrieval.
 */
export interface HNSWNounWithMetadata {
  // HNSW Core (unchanged)
  id: string
  vector: Vector
  connections: Map<number, Set<string>>
  level: number

  // TYPE (required, explicit)
  type: NounType

  // SUBTYPE — optional per-product sub-classification within a NounType (e.g. a Person
  // entity might have subtype 'employee' / 'customer' / 'vendor'; an Event might have
  // subtype 'meeting' / 'milestone'). Flat string (no hierarchy) — consumers decide the
  // vocabulary. Indexed and rolled up into per-NounType statistics so it's queryable
  // (`find({ type, subtype })`) and aggregable (`groupBy:['subtype']`) on the standard-field
  // fast path, never falling through to the metadata fallback.
  subtype?: string

  // VISIBILITY — three-value tier gating whether this entity surfaces on default
  // user-facing reads. Absent === 'public' (counted + returned everywhere). 'internal'
  // hides the entity from default find()/counts/stats but keeps it retrievable via
  // find({ includeInternal: true }). 'system' (Brainy plumbing, e.g. the VFS root) is
  // hidden everywhere unless find({ includeSystem: true }). Stored only when not 'public'
  // so the common case stays lean. See {@link EntityVisibility}.
  visibility?: EntityVisibility

  // QUALITY METRICS (top-level, explicit)
  confidence?: number
  weight?: number

  // TIMESTAMPS (top-level, always numbers for consistency)
  createdAt: number
  updatedAt: number

  // SYSTEM METADATA (top-level)
  service?: string
  createdBy?: { augmentation: string; version: string }

  // USER DATA (top-level) - compatible with other types
  data?: Record<string, any>

  // REVISION COUNTER (7.31.0) — monotonic per-write integer for optimistic concurrency.
  // Initialized to 1 on add(); bumped on every successful update(). Pre-7.31.0
  // entities without _rev are surfaced as `1` so existing callers see a consistent value.
  _rev?: number

  // CUSTOM USER METADATA (only custom fields, no standard fields)
  metadata?: Record<string, unknown>
}

/**
 * Standard top-level fields on HNSWNounWithMetadata.
 *
 * Single source of truth for the entity shape contract: fields listed here
 * live at the top level of the entity; any other field is a custom user
 * field and lives in `entity.metadata`.
 *
 * Keep this set in lockstep with the HNSWNounWithMetadata interface above.
 * Adding a new top-level field to the interface? Add it here too, or
 * `resolveEntityField` will look for it in the wrong place.
 */
export const STANDARD_ENTITY_FIELDS: ReadonlySet<string> = new Set([
  'id',
  'vector',
  'connections',
  // 'level' is deliberately ABSENT: it is HNSW plumbing, not an entity field.
  // Listing it here made every by-name read of a user metadata field called
  // `level` resolve to the engine's internal node layer instead — a silent
  // shadow that broke sort/filter/aggregation on a perfectly natural field
  // name (VENUE-BRAINY-ORDERBY-NOOP). Engine plumbing is invisible to the
  // query surface; a bare `level` reads `entity.metadata.level`.
  'type',
  'subtype',
  'visibility',
  'confidence',
  'weight',
  'createdAt',
  'updatedAt',
  'service',
  'createdBy',
  'data',
  '_rev'
])

/**
 * Resolve a field value off an entity by name.
 *
 * Encodes the HNSWNounWithMetadata shape contract in one place: standard
 * fields live at the top level, custom user fields live in `entity.metadata`.
 * Use this helper anywhere code needs to read a field by name (sorting,
 * filtering, aggregation) instead of reaching into the entity directly.
 *
 * @param entity - The entity to read from
 * @param field - The field name to resolve
 * @returns The field value, or undefined if not present
 *
 * @example
 * resolveEntityField(noun, 'createdAt')  // reads noun.createdAt (top-level)
 * resolveEntityField(noun, 'customTag')  // reads noun.metadata?.customTag
 */
export function resolveEntityField(
  entity: HNSWNounWithMetadata,
  field: string
): unknown {
  // `noun` is an alias for the entity's type — some shapes carry `noun`, the
  // canonical HNSWNounWithMetadata carries `type`. Resolve it consistently so
  // groupBy/source/sort on 'noun' isn't silently null (it otherwise falls through
  // to metadata.noun, which doesn't exist). Mirrors matchesSource's `type ?? noun`.
  if (field === 'noun') {
    const e = entity as unknown as Record<string, unknown>
    return e.type ?? e.noun
  }
  if (STANDARD_ENTITY_FIELDS.has(field)) {
    return (entity as unknown as Record<string, unknown>)[field]
  }
  return entity.metadata?.[field]
}

/**
 * Standard top-level fields on `HNSWVerbWithMetadata`. Parallel to
 * `STANDARD_ENTITY_FIELDS` — the source of truth that drives `resolveVerbField`'s
 * fast-path branch and the storage destructure-and-resurface pattern in
 * `baseStorage.getVerb()`. Verb-specific entries: `verb` (the VerbType),
 * `sourceId`, `targetId`. `subtype` is the 7.30 addition.
 */
export const STANDARD_VERB_FIELDS: ReadonlySet<string> = new Set([
  'id',
  'vector',
  'connections',
  'verb',
  'sourceId',
  'targetId',
  'subtype',
  'visibility',
  'confidence',
  'weight',
  'createdAt',
  'updatedAt',
  'service',
  'createdBy',
  'data'
])

/**
 * Resolve a field value off a verb by name. Mirror of `resolveEntityField` for
 * `HNSWVerbWithMetadata`: standard fields at the top level, custom user fields
 * in `verb.metadata`.
 *
 * Includes the same `type` / `verb` alias treatment as the noun helper
 * (`resolveEntityField`'s `noun`/`type` alias) — relation shapes that carry `type`
 * instead of `verb` (e.g. the public `Relation<T>` API surface) resolve correctly
 * for sorts/groupBys/filters that ask for `verb`.
 *
 * @param verb - The verb to read from
 * @param field - The field name to resolve
 * @returns The field value, or undefined if not present
 *
 * @example
 * resolveVerbField(edge, 'createdAt')  // reads edge.createdAt (top-level)
 * resolveVerbField(edge, 'subtype')    // reads edge.subtype (top-level — fast path)
 * resolveVerbField(edge, 'customTag')  // reads edge.metadata?.customTag
 */
export function resolveVerbField(
  verb: HNSWVerbWithMetadata,
  field: string
): unknown {
  if (field === 'verb' || field === 'type') {
    const v = verb as unknown as Record<string, unknown>
    return v.verb ?? v.type
  }
  if (STANDARD_VERB_FIELDS.has(field)) {
    return (verb as unknown as Record<string, unknown>)[field]
  }
  return verb.metadata?.[field]
}

/**
 * Combined verb structure for transport/API boundaries
 *
 * Standard fields moved to top-level
 * - ALL standard fields (weight, confidence, timestamps, etc.) are now at top-level
 * - metadata contains ONLY custom user-defined fields
 * - Provides clean, predictable API: verb.weight always works
 * - 20% memory reduction @ billion scale (no duplicate storage)
 *
 * Used for API responses and storage retrieval.
 */
export interface HNSWVerbWithMetadata {
  // HNSW Core + Relational (unchanged)
  id: string
  vector: Vector
  connections: Map<number, Set<string>>
  verb: VerbType
  sourceId: string
  targetId: string

  // SUBTYPE — optional per-product sub-classification within a VerbType (e.g. a
  // `ReportsTo` relationship might have subtype 'direct' vs 'dotted-line'; a `RelatedTo`
  // edge might carry 'spouse' / 'sibling' / 'colleague'). Flat string (no hierarchy) —
  // consumers decide the vocabulary. Indexed and rolled up into per-VerbType statistics
  // so it's queryable (`related({ verb, subtype })`) and aggregable
  // (`groupBy:['subtype']`) on the standard-field fast path, never falling through to
  // the metadata fallback.
  subtype?: string

  // VISIBILITY — verb mirror of the entity tier. Absent === 'public' (counted +
  // returned everywhere). 'internal' hides the edge from default related()/counts/stats
  // but keeps it retrievable via related({ includeInternal: true }). 'system' is hidden
  // everywhere unless related({ includeSystem: true }). Stored only when not 'public'.
  // See {@link EntityVisibility}.
  visibility?: EntityVisibility

  // QUALITY METRICS (top-level, explicit)
  weight?: number
  confidence?: number

  // TIMESTAMPS (top-level, always numbers for consistency)
  createdAt: number
  updatedAt: number

  // SYSTEM METADATA (top-level)
  service?: string
  createdBy?: { augmentation: string; version: string }

  // USER DATA (top-level) - compatible with GraphVerb
  data?: Record<string, any>

  // CUSTOM USER METADATA (only custom fields, no standard fields)
  metadata?: Record<string, unknown>
}

/**
 * Verb representing a relationship between nouns
 * Stored separately from the HNSW vector index for lightweight performance.
 * This is the canonical verb shape — Brainy's internal graph engine, the public
 * `relate()` / `related()` surface, and storage adapters all speak it.
 */
export interface GraphVerb {
  id: string // Unique identifier — always a UUID (8.0 contract: brainy generates every verb id, so providers may key verb-int interning on the raw UUID bytes)
  sourceId: string // ID of the source noun
  targetId: string // ID of the target noun
  vector: Vector // Vector representation of the relationship
  connections?: Map<number, Set<string>> // Optional connections from the vector index
  type?: string // Optional verb type
  subtype?: string // Optional sub-classification within the VerbType (7.30+)
  visibility?: EntityVisibility // Visibility tier (8.0); absent === 'public'. See {@link EntityVisibility}.
  weight?: number // Optional weight of the relationship
  confidence?: number // Optional confidence score (0-1)
  metadata?: any // Optional metadata for the verb
  service?: string // Multi-tenancy support - which service created this verb
  verb?: string // Alias for type — preserved for ergonomic call sites
  data?: Record<string, any> // Additional flexible data storage
  embedding?: Vector // Alias for vector — preserved for ergonomic call sites

  // Timestamp and creator properties
  createdAt?: number | { seconds: number; nanoseconds: number } // When the verb was created
  updatedAt?: number | { seconds: number; nanoseconds: number } // When the verb was last updated
  createdBy?: { augmentation: string; version: string } // Information about what created this verb

  /**
   * Derived state: the source entity's interned integer ID (u64-safe BigInt).
   * Populated by the coordinator (`brainy.ts`) from the shared entity-id mapper
   * at add time, immediately before the verb is handed to the graph-index
   * provider. NEVER persisted to storage JSON — the mapper is the source of
   * truth, and persisting would denormalize state that can go stale across a
   * mapper rebuild.
   */
  sourceInt?: bigint
  /**
   * Derived state: the target entity's interned integer ID (u64-safe BigInt).
   * Same lifecycle as {@link GraphVerb.sourceInt}: coordinator-populated at add
   * time, never persisted to storage JSON.
   */
  targetInt?: bigint
}

/**
 * HNSW index configuration
 */
export interface HNSWConfig {
  /**
   * Index engine selector. `'vector'` is the only admissible value in 8.0
   * and means the same thing as omitting the field: Brainy resolves the
   * `'vector'` provider when a plugin registers one and falls back to its
   * built-in JS HNSW index otherwise. Engine specifics (in-memory vs hybrid
   * vs on-disk) are the provider's internal decision, not a config choice.
   */
  type?: 'vector'
  M: number // Maximum number of connections per noun
  efConstruction: number // Size of the dynamic candidate list during construction
  efSearch: number // Size of the dynamic candidate list during search
  ml: number // Maximum level
  useDiskBasedIndex?: boolean // Whether to use disk-based index
  maxConcurrentNeighborWrites?: number // Maximum concurrent neighbor updates during insert. Default: unlimited (full concurrency)
  // Vector storage mode
  vectorStorage?: 'memory' | 'lazy'  // default: 'memory' — 'lazy' evicts vectors after insert
}

/**
 * Storage interface for persistence
 */
/**
 * Statistics data structure for tracking counts by service
 */
/**
 * Per-service statistics tracking
 */
export interface ServiceStatistics {
  /**
   * Service name
   */
  name: string

  /**
   * Total number of nouns created by this service
   */
  totalNouns: number

  /**
   * Total number of verbs created by this service
   */
  totalVerbs: number

  /**
   * Total number of metadata entries created by this service
   */
  totalMetadata: number

  /**
   * First activity timestamp for this service
   */
  firstActivity?: string

  /**
   * Last activity timestamp for this service
   */
  lastActivity?: string

  /**
   * Error count for this service
   */
  errorCount?: number

  /**
   * Operation breakdown for this service
   */
  operations?: {
    adds: number
    updates: number
    deletes: number
  }

  /**
   * Status of the service (active, inactive, read-only)
   */
  status?: 'active' | 'inactive' | 'read-only'
}

export interface StatisticsData {
  /**
   * Count of nouns by service
   */
  nounCount: Record<string, number>

  /**
   * Count of verbs by service
   */
  verbCount: Record<string, number>

  /**
   * Count of metadata entries by service
   */
  metadataCount: Record<string, number>

  /**
   * Size of the HNSW index
   */
  hnswIndexSize: number

  /**
   * Total number of nodes
   */
  totalNodes?: number

  /**
   * Total number of edges
   */
  totalEdges?: number

  /**
   * Total metadata count
   */
  totalMetadata?: number

  /**
   * Operation counts
   */
  operations?: {
    add: number
    search: number
    delete: number
    update: number
    relate: number
    total: number
  }

  /**
   * Field names available for searching, organized by service
   * This helps users understand what fields are available from different data sources
   */
  fieldNames?: Record<string, string[]>

  /**
   * Standard field mappings for common field names across services
   * Maps standard field names to the actual field names used by each service
   */
  standardFieldMappings?: Record<string, Record<string, string[]>>

  /**
   * Content type breakdown (e.g., Person, Repository, Issue, etc.)
   */
  contentTypes?: Record<string, number>

  /**
   * Data freshness metrics
   */
  dataFreshness?: {
    oldestEntry: string
    newestEntry: string
    updatesLastHour: number
    updatesLastDay: number
    ageDistribution: {
      last24h: number
      last7d: number
      last30d: number
      older: number
    }
  }

  /**
   * Storage utilization metrics
   */
  storageMetrics?: {
    totalSizeBytes: number
    nounsSizeBytes: number
    verbsSizeBytes: number
    metadataSizeBytes: number
    indexSizeBytes: number
  }

  /**
   * Search performance metrics
   */
  searchMetrics?: {
    totalSearches: number
    averageSearchTimeMs: number
    searchesLastHour: number
    searchesLastDay: number
    topSearchTerms?: string[]
  }

  /**
   * Verb statistics similar to nouns
   */
  verbStatistics?: {
    totalVerbs: number
    verbTypes: Record<string, number>
    averageConnectionsPerVerb: number
  }

  /**
   * Service-level activity timestamps
   */
  serviceActivity?: Record<string, {
    firstActivity: string
    lastActivity: string
    totalOperations: number
  }>

  /**
   * List of all services that have written data
   */
  services?: ServiceStatistics[]

  /**
   * Throttling metrics for storage operations
   */
  throttlingMetrics?: {
    /**
     * Storage-level throttling information
     */
    storage?: {
      currentlyThrottled: boolean
      lastThrottleTime?: string
      consecutiveThrottleEvents: number
      currentBackoffMs: number
      totalThrottleEvents: number
      throttleEventsByHour?: number[]  // Last 24 hours
      throttleReasons?: Record<string, number>  // Count by reason (429, 503, timeout, etc.)
    }
    
    /**
     * Operation impact metrics
     */
    operationImpact?: {
      delayedOperations: number
      retriedOperations: number
      failedDueToThrottling: number
      averageDelayMs: number
      totalDelayMs: number
    }
    
    /**
     * Service-level throttling breakdown
     */
    serviceThrottling?: Record<string, {
      throttleCount: number
      lastThrottle: string
      status: 'normal' | 'throttled' | 'recovering'
    }>
  }

  /**
   * Last updated timestamp
   */
  lastUpdated: string
}

/**
 * Change record for getChangesSince
 * Replaces `any[]` with properly typed structure
 */
export interface Change {
  id: string
  type: 'noun' | 'verb'
  operation: 'create' | 'update' | 'delete'
  timestamp: number
  data?: HNSWNounWithMetadata | HNSWVerbWithMetadata
}

/**
 * @description A declared derived-index blob FAMILY (the
 * registered-blob contract). A family names the set of on-disk blobs that a
 * derived index needs AS A SET (e.g. the vector base = `main.dkann` +
 * `main.slotmap` + `main.slotrev`): losing ANY member corrupts the index. Once a
 * family is declared:
 * - its members are UNDELETABLE through the storage layer — `deleteBinaryBlob` /
 *   `removeRawPrefix` refuse with a `ProtectedArtifactError`, so an in-process
 *   GC/sweeper cannot remove a load-bearing file (intentional retirement =
 *   `unregisterDerivedFamily` first);
 * - a member missing on open is a loud `DerivedArtifactMissingError` → rebuild.
 * `COLD ≠ DEAD`: a write-once segment or a recovery-critical archive is
 * load-bearing even when it has not been touched in a long time.
 */
export interface DerivedFamilyDeclaration {
  /** Stable family id, e.g. `'vector-base'` / `'metadata-sstables'`. */
  name: string
  /**
   * The logical blob keys that make up the family. When {@link namespace} is
   * set, each entry is a PREFIX protecting every key beneath it (for growing
   * sets like `seg-*`); otherwise each entry is an exact member key.
   */
  members: string[]
  /** When true, {@link members} are prefixes (protect all keys beneath each). */
  namespace?: boolean
  /**
   * Whether a missing family can be rebuilt from the canonical records (default
   * `true`). `false` marks an irreplaceable family (a missing member is data
   * loss, not a rebuild).
   */
  rebuildable?: boolean
}

export interface StorageAdapter {
  init(): Promise<void>

  /**
   * Save noun - Pure HNSW vector data only
   * @param noun Pure HNSW vector data (no metadata)
   * Note: Use saveNounMetadata() to save metadata separately
   */
  saveNoun(noun: HNSWNoun): Promise<void>

  /**
   * Save noun metadata separately
   * @param id Noun ID
   * @param metadata Noun metadata
   */
  saveNounMetadata(id: string, metadata: NounMetadata): Promise<void>

  /**
   * Delete noun metadata
   * @param id Noun ID
   */
  deleteNounMetadata(id: string): Promise<void>

  /**
   * Get noun with metadata combined
   * @returns Combined HNSWNounWithMetadata or null
   */
  getNoun(id: string): Promise<HNSWNounWithMetadata | null>

  /**
   * Get nouns with pagination and filtering
   * @param options Pagination and filtering options
   * @returns Promise that resolves to a paginated result of nouns
   */
  getNouns(options?: {
    pagination?: {
      offset?: number
      limit?: number
      cursor?: string
    }
    filter?: {
      nounType?: string | string[]
      service?: string | string[]
      metadata?: Record<string, any>
    }
  }): Promise<{
    items: HNSWNounWithMetadata[]
    totalCount?: number
    hasMore: boolean
    nextCursor?: string
  }>

  /**
   * Get all nouns of a given type. Convenience wrapper over `getNouns()` for
   * code paths that need the full set rather than a paginated cursor walk.
   *
   * @param nounType The noun type to filter by.
   * @returns All matching nouns.
   */
  getNounsByNounType(nounType: string): Promise<HNSWNounWithMetadata[]>

  /**
   * Delete a noun — FULL canonical removal (both legs + the entity container).
   *
   * @param id The entity id.
   * @param priorMetadata OPTIONAL already-known metadata of the entity being
   *   removed (the caller's pre-delete read). The count decrement must never
   *   REQUIRE re-reading the record being removed: when the internal read
   *   returns `null` (replace race, or a ghost left by an earlier version) the
   *   decrement falls back to this record instead of being silently skipped.
   */
  deleteNoun(id: string, priorMetadata?: NounMetadata | null): Promise<void>

  /**
   * Save verb - Pure HNSW verb with core fields only
   * @param verb Pure HNSW verb data (vector + core fields, no user metadata)
   * Note: Use saveVerbMetadata() to save metadata separately
   */
  saveVerb(verb: HNSWVerb): Promise<void>

  /**
   * Get verb with metadata combined
   * @returns Combined HNSWVerbWithMetadata or null
   */
  getVerb(id: string): Promise<HNSWVerbWithMetadata | null>

  /**
   * Get verbs with pagination and filtering
   * @param options Pagination and filtering options
   * @returns Promise that resolves to a paginated result of verbs
   */
  getVerbs(options?: {
    pagination?: {
      offset?: number
      limit?: number
      cursor?: string
    }
    filter?: {
      verbType?: string | string[]
      sourceId?: string | string[]
      targetId?: string | string[]
      service?: string | string[]
      metadata?: Record<string, any>
    }
  }): Promise<{
    items: HNSWVerbWithMetadata[]
    totalCount?: number
    hasMore: boolean
    nextCursor?: string
  }>

  /**
   * Get all verbs originating from a given source node. Convenience wrapper
   * over `getVerbs()` for code paths that want the full set rather than a
   * paginated cursor walk.
   */
  getVerbsBySource(sourceId: string): Promise<HNSWVerbWithMetadata[]>

  /**
   * Get all verbs targeting a given node. See `getVerbsBySource()`.
   */
  getVerbsByTarget(targetId: string): Promise<HNSWVerbWithMetadata[]>

  /**
   * Get all verbs of a given type. See `getVerbsBySource()`.
   */
  getVerbsByType(type: string): Promise<HNSWVerbWithMetadata[]>

  /**
   * Delete a verb — FULL canonical removal (both legs + the container).
   *
   * @param id The relationship id.
   * @param priorMetadata OPTIONAL already-known metadata of the edge being
   *   removed (the caller's pre-delete read); keeps the count decrement honest
   *   when the internal read returns `null` (see `deleteNoun`).
   */
  deleteVerb(id: string, priorMetadata?: VerbMetadata | null): Promise<void>

  /**
   * Save metadata
   * @param id Entity ID
   * @param metadata Typed noun metadata
   */
  saveMetadata(id: string, metadata: NounMetadata): Promise<void>

  /**
   * Get metadata
   * @param id Entity ID
   * @returns Typed noun metadata or null
   */
  getMetadata(id: string): Promise<NounMetadata | null>

  /**
   * Delete a system/metadata object previously written with {@link saveMetadata}.
   * Routes by the same key analysis as save/get (system channel), so callers that
   * persist their own keyed payloads — e.g. the LSM graph store reclaiming its
   * compacted-away SSTables — can free the storage instead of orphaning it.
   * Idempotent: a missing key is a no-op, not an error.
   * @param id The same key passed to {@link saveMetadata}.
   */
  deleteMetadata(id: string): Promise<void>

  /**
   * Get multiple metadata objects in batches (prevents socket exhaustion)
   * @param ids Array of IDs to get metadata for
   * @returns Promise that resolves to a Map of id -> metadata
   */
  getMetadataBatch?(ids: string[]): Promise<Map<string, NounMetadata>>

  /**
   * Get noun metadata from storage
   * @param id The ID of the noun
   * @returns Promise that resolves to the metadata or null if not found
   */
  getNounMetadata(id: string): Promise<NounMetadata | null>

  /**
   * Batch get multiple nouns with vectors
   * @param ids Array of noun IDs to fetch
   * @returns Map of id → HNSWNounWithMetadata (only successful reads included)
   */
  getNounBatch?(ids: string[]): Promise<Map<string, HNSWNounWithMetadata>>

  /**
   * Save verb metadata to storage
   * @param id The ID of the verb
   * @param metadata The metadata to save
   * @returns Promise that resolves when the metadata is saved
   */
  saveVerbMetadata(id: string, metadata: VerbMetadata): Promise<void>

  /**
   * Get verb metadata from storage
   * @param id The ID of the verb
   * @returns Promise that resolves to the metadata or null if not found
   */
  getVerbMetadata(id: string): Promise<VerbMetadata | null>

  /**
   * Batch get multiple verbs
   * @param ids Array of verb IDs to fetch
   * @returns Map of id → HNSWVerbWithMetadata (only successful reads included)
   */
  getVerbsBatch?(ids: string[]): Promise<Map<string, HNSWVerbWithMetadata>>

  clear(): Promise<void>

  /**
   * Batch delete multiple objects from storage
   * Efficient deletion of large numbers of entities using cloud provider batch APIs.
   * Significantly faster and cheaper than individual deletes (up to 1000x speedup).
   *
   * @param keys - Array of object keys (paths) to delete
   * @param options - Optional configuration for batch deletion
   * @param options.maxRetries - Maximum number of retry attempts per batch (default: 3)
   * @param options.retryDelayMs - Base delay between retries in milliseconds (default: 1000)
   * @param options.continueOnError - Continue processing remaining batches if one fails (default: true)
   * @returns Promise with deletion statistics
   *
   * @example
   * const result = await storage.batchDelete(
   *   ['path1', 'path2', 'path3'],
   *   { continueOnError: true }
   * )
   * console.log(`Deleted: ${result.successfulDeletes}/${result.totalRequested}`)
   * console.log(`Failed: ${result.failedDeletes}`)
   */
  batchDelete?(
    keys: string[],
    options?: {
      maxRetries?: number
      retryDelayMs?: number
      continueOnError?: boolean
    }
  ): Promise<{
    totalRequested: number
    successfulDeletes: number
    failedDeletes: number
    errors: Array<{ key: string; error: string }>
  }>

  /**
   * Get information about storage usage and capacity
   * @returns Promise that resolves to an object containing storage status information
   */
  getStorageStatus(): Promise<{
    /**
     * The type of storage being used (e.g., 'filesystem', 'opfs', 'memory')
     */
    type: string

    /**
     * The amount of storage being used in bytes
     */
    used: number

    /**
     * The total amount of storage available in bytes, or null if unknown
     */
    quota: number | null

    /**
     * Additional storage-specific information
     */
    details?: Record<string, any>
  }>

  /**
   * Persist a raw binary blob under `key`, writing the bytes verbatim with no
   * JSON envelope and no base64 inflation. Overwrites any existing blob at the
   * same key. On filesystem-backed adapters the write is atomic (temp + rename).
   *
   * This is the zero-copy / mmap-friendly counterpart to the JSON object
   * primitives — intended for column-store segments and batch vector payloads
   * where base64-in-JSON would inflate size ~33% and force full materialization.
   *
   * @param key - Logical blob key; "/"-separated segments nest under the
   *   adapter's `_blobs/` prefix (e.g. `"graph-lsm/source/sstable-123"`).
   * @param data - The exact bytes to store.
   */
  saveBinaryBlob(key: string, data: Buffer): Promise<void>

  /**
   * Load the raw bytes stored under `key`, byte-identical to what was saved, or
   * `null` if no blob exists at that key.
   *
   * @param key - The blob key used when saving.
   * @returns The blob bytes, or `null` if absent.
   */
  loadBinaryBlob(key: string): Promise<Buffer | null>

  /**
   * Delete the blob stored under `key`. Missing blobs are ignored, so delete is
   * idempotent.
   *
   * @param key - The blob key to delete.
   */
  deleteBinaryBlob(key: string): Promise<void>

  /**
   * Resolve `key` to a real local filesystem path that native code can `mmap`
   * directly, or `null` when this backend has no local file for the blob.
   *
   * Filesystem storage returns the on-disk path; remote object stores, in-memory
   * storage, browser (OPFS) storage, and historical (read-only) storage return
   * `null`. `null` is correct behavior for those backends, not a fallback —
   * callers must fall back to {@link loadBinaryBlob} when no path is available.
   *
   * @param key - The blob key.
   * @returns An absolute local filesystem path, or `null` if none exists.
   */
  getBinaryBlobPath(key: string): string | null

  /**
   * @description OPTIONAL (registered-blob contract). Declare a
   * derived-index blob {@link DerivedFamilyDeclaration | family} whose members
   * become UNDELETABLE through this adapter — a subsequent `deleteBinaryBlob` /
   * `removeRawPrefix` that would remove a declared member throws a
   * `ProtectedArtifactError`. Providers declare their families on create; the
   * declaration is persisted so protection survives a reopen. Idempotent per
   * `name` (re-declaring replaces).
   * @param family - The family to protect.
   */
  registerDerivedFamily?(family: DerivedFamilyDeclaration): Promise<void>

  /**
   * @description OPTIONAL. Remove a family's protection so its members can be
   * deleted again — the explicit, auditable step for intentional retirement of a
   * derived index (the ONLY way a declared member becomes deletable).
   * @param name - The {@link DerivedFamilyDeclaration.name} to unregister.
   */
  unregisterDerivedFamily?(name: string): Promise<void>

  /**
   * @description OPTIONAL. List the currently-declared derived-index families —
   * the source of truth for what `clear()` must wipe and what a
   * missing-on-open check verifies.
   */
  listDerivedFamilies?(): Promise<DerivedFamilyDeclaration[]>

  /**
   * @description OPTIONAL binary raw-byte primitives — the substrate for
   * append-only log-structured files (the generation fact log's CRC-framed
   * segments). Feature-detected: an adapter that omits them simply hosts no
   * fact log (readers fall back to canonical enumeration). Paths are
   * storage-root-relative and used VERBATIM (no `.gz`/`.bin` suffixing —
   * unlike the JSON object and blob primitives).
   *
   * Append to a raw binary file, creating it (and parent directories) when
   * absent. Append durability is the CALLER's job via `syncRawObjects` —
   * matching the commit protocol, which batches fsyncs at its barrier.
   */
  appendRawBytes?(path: string, bytes: Uint8Array): Promise<void>

  /**
   * Read a raw binary file whole. Absent → `null`; a real IO fault throws
   * (never masked as absence).
   */
  readRawBytes?(path: string): Promise<Uint8Array | null>

  /**
   * Replace a raw binary file atomically (write-new → fsync → rename) — the
   * reconcile primitive (e.g. truncating a fact-log tail back to committed
   * truth after a crash).
   */
  writeRawBytes?(path: string, bytes: Uint8Array): Promise<void>

  /**
   * Byte size of a raw binary file, or `null` when absent.
   */
  rawByteSize?(path: string): Promise<number | null>

  /**
   * @description OPTIONAL fact-scan capability — how an index provider that
   * holds only `storage` reaches the generation fact log (the host brain
   * wires it at init; providers must never construct their own fact-log
   * reader — the log's open path is writer-side). Present ⟺ this store hosts
   * a fact log AND the host wired the capability. Returns a scan handle over
   * committed facts (heal-grade telemetry included), or `null` when no fact
   * log exists — callers fall back to the canonical enumeration walk.
   */
  scanFacts?(options?: {
    fromGeneration?: number
    toGeneration?: number
    kinds?: Array<'noun' | 'verb'>
    batchSize?: number
  }): import('./db/factLog.js').FactScanHandle | null

  /**
   * @description OPTIONAL (rides the fact-scan capability): the fact log's
   * head generation — the replay target for `stamp.sourceGeneration + 1`
   * catch-ups. `null` when no fact log exists.
   */
  factLogHeadGeneration?(): number | null

  /**
   * @description OPTIONAL (rides the fact-scan capability): the COMMITTED
   * generation watermark — the manifest truth a projection's
   * `sourceGeneration` compares against. Exposed as a capability so a
   * provider never parses the store's private manifest format. `null` when
   * the capability is unwired.
   */
  committedGeneration?(): number | null

  /**
   * @description OPTIONAL (rides the fact-scan capability): the immutable,
   * sealed fact-segment file paths covering `fromGeneration` — the zero-copy
   * handoff. The mutable tail is excluded (read it via `scanFacts`).
   */
  factSegmentPaths?(options?: { fromGeneration?: number }): string[]

  /**
   * Save statistics data
   * @param statistics The statistics data to save
   */
  saveStatistics(statistics: StatisticsData): Promise<void>

  /**
   * Get statistics data
   * @returns Promise that resolves to the statistics data
   */
  getStatistics(): Promise<StatisticsData | null>

  /**
   * Increment a statistic counter
   * @param type The type of statistic to increment ('noun', 'verb', 'metadata')
   * @param service The service that inserted the data
   * @param amount The amount to increment by (default: 1)
   */
  incrementStatistic(
    type: 'noun' | 'verb' | 'metadata',
    service: string,
    amount?: number
  ): Promise<void>

  /**
   * Decrement a statistic counter
   * @param type The type of statistic to decrement ('noun', 'verb', 'metadata')
   * @param service The service that inserted the data
   * @param amount The amount to decrement by (default: 1)
   */
  decrementStatistic(
    type: 'noun' | 'verb' | 'metadata',
    service: string,
    amount?: number
  ): Promise<void>

  /**
   * Update the HNSW index size statistic
   * @param size The new size of the HNSW index
   */
  updateHnswIndexSize(size: number): Promise<void>

  /**
   * Force an immediate flush of statistics to storage
   * This ensures that any pending statistics updates are written to persistent storage
   */
  flushStatisticsToStorage(): Promise<void>

  /**
   * Track field names from a JSON document
   * @param jsonDocument The JSON document to extract field names from
   * @param service The service that inserted the data
   */
  trackFieldNames(jsonDocument: Record<string, unknown>, service: string): Promise<void>

  /**
   * Get available field names by service
   * @returns Record of field names by service
   */
  getAvailableFieldNames(): Promise<Record<string, string[]>>

  /**
   * Get standard field mappings
   * @returns Record of standard field mappings
   */
  getStandardFieldMappings(): Promise<Record<string, Record<string, string[]>>>

  /**
   * Get changes since a specific timestamp
   * @param timestamp The timestamp to get changes since
   * @param limit Optional limit on the number of changes to return
   * @returns Promise that resolves to an array of properly typed changes
   */
  getChangesSince?(timestamp: number, limit?: number): Promise<Change[]>
  
  // NOTE: getAllNouns and getAllVerbs have been removed to prevent expensive full scans.
  // Use getNouns() and getVerbs() with pagination instead.

  /**
   * Get total count of nouns in storage - O(1) operation
   * @returns Promise that resolves to the total number of nouns
   */
  getNounCount(): Promise<number>

  /**
   * Get total count of verbs in storage - O(1) operation
   * @returns Promise that resolves to the total number of verbs
   */
  getVerbCount(): Promise<number>

  /**
   * OPTIONAL — create a pre-upgrade backup of the whole store and return its
   * location, or `null` when there is nothing to back up (empty store). On the
   * filesystem adapter this is a zero-copy hard-link snapshot into a sibling
   * directory; other backends may omit it. Callers feature-detect. Paired with
   * {@link removeMigrationBackup}. Used by the 7.x → 8.0 migration lifecycle.
   */
  createMigrationBackup?(): Promise<string | null>

  /**
   * OPTIONAL — remove a backup created by {@link createMigrationBackup}.
   * Best-effort: a missing `location` is a no-op. Removing a hard-link snapshot
   * never affects the live store (shared inodes; only the extra links are gone).
   */
  removeMigrationBackup?(location: string): Promise<void>
}
