/**
 * @module db/fieldAddressing
 * @description The one field-addressing law for every query surface (find()'s
 * `where` / `orderBy` / `groupBy`, aggregation `source.where`), ruled
 * 2026-08-03 after a production incident in which a user metadata field
 * named `level` was silently shadowed by the engine's internal HNSW node
 * layer (VENUE-BRAINY-ORDERBY-NOOP — thread id kept verbatim as the audit
 * key; it names no product):
 *
 *   1. A BARE field name addresses the user's metadata field. Always.
 *      No priority resolution, no fallback chain — `orderBy: 'level'`
 *      reads `entity.metadata.level`, full stop.
 *   2. `system.<field>` addresses an engine scalar, reachable ONLY with the
 *      explicit prefix. The entity map is exactly ten scalars; the relation
 *      map mirrors it with `verb`/`sourceId`/`targetId` as the structural
 *      members.
 *   3. Engine plumbing (`vector`, `connections`, `level`, `data`, `_rev`) is
 *      INVISIBLE to the query surface in either spelling — `system.level`
 *      refuses; bare `level` is the user's field.
 *   4. `metadata.<field>` is the explicit spelling of the bare form —
 *      identical semantics on every path.
 *   5. Anything unresolvable refuses with a TYPED error naming both
 *      candidate spellings — an accepted name either works or refuses;
 *      there is no third state.
 *
 * This module is the SINGLE source of truth for the law: parsing, the maps,
 * and the refusal builders live here so the JS engine, the provider seams,
 * and the cross-engine conformance suite can never drift on the contract.
 */

import type { HNSWNounWithMetadata, HNSWVerbWithMetadata } from '../coreTypes.js'

/**
 * @description The entity-side `system.*` map — EXACTLY the ten engine
 * scalars David ruled queryable (2026-08-03). Adding a name here is a
 * cross-engine contract change: the native accelerator's conformance suite
 * pins this list verbatim, so any edit must ship as a paired release.
 */
export const SYSTEM_ENTITY_SCALARS: ReadonlySet<string> = new Set([
  'id',
  'type',
  'subtype',
  'createdAt',
  'updatedAt',
  'confidence',
  'weight',
  'visibility',
  'service',
  'createdBy'
])

/**
 * @description The relation-side `system.*` map — the verb mirror of
 * {@link SYSTEM_ENTITY_SCALARS}: `verb`, `sourceId`, `targetId` are the
 * structural members beside the eight shared scalars. Same one law, same
 * pairing rule for edits.
 */
export const SYSTEM_RELATION_SCALARS: ReadonlySet<string> = new Set([
  'verb',
  'sourceId',
  'targetId',
  'subtype',
  'createdAt',
  'updatedAt',
  'confidence',
  'weight',
  'visibility',
  'service',
  'createdBy'
])

/**
 * @description Engine plumbing — never addressable from the query surface in
 * ANY spelling. `level` is the HNSW node layer (the incident field: listing
 * it as resolvable shadowed real user data); `data` is the payload container,
 * not a scalar — content is reached through the content/text-search APIs,
 * and addressing it as a sortable field would lie about its shape.
 */
export const PLUMBING_FIELDS: ReadonlySet<string> = new Set([
  'vector',
  'connections',
  'level',
  'data',
  '_rev'
])

/** @description Which record kind a field address is being resolved against. */
export type FieldAddressKind = 'entity' | 'relation'

/**
 * @description A parsed, law-valid field address. `scope` says which side of
 * the record the name lives on; `field` is the unprefixed name to read.
 */
export interface FieldAddress {
  /** 'metadata' = the user's field (bare or `metadata.`-prefixed); 'system' = an engine scalar. */
  scope: 'metadata' | 'system'
  /** The field name with any scope prefix removed. */
  field: string
  /** The exact spelling the caller used — preserved for error text and telemetry. */
  raw: string
}

/**
 * Parse a query-surface field name under the one law. Pure and data-blind:
 * this validates the ADDRESS (spelling + map membership), not whether any
 * row actually carries the field — data-aware refusals (the did-you-mean
 * for a bare system-scalar name no row carries) belong to the query layer,
 * which calls {@link buildUnresolvableMessage} with index knowledge.
 *
 * @param raw - The field name as the caller wrote it (`level`,
 *   `metadata.level`, `system.createdAt`, …)
 * @param kind - Entity or relation resolution (selects the system map)
 * @returns The parsed {@link FieldAddress}
 * @throws {InvalidFieldAddressError} for a `system.*` name outside the ruled
 *   map (including every plumbing field) or a malformed spelling — the error
 *   text enumerates the valid system scalars so the fix is in the message.
 *
 * @example
 * parseFieldAddress('level', 'entity')            // { scope: 'metadata', field: 'level' }
 * parseFieldAddress('metadata.level', 'entity')   // { scope: 'metadata', field: 'level' }
 * parseFieldAddress('system.createdAt', 'entity') // { scope: 'system', field: 'createdAt' }
 * parseFieldAddress('system.level', 'entity')     // throws — plumbing is invisible
 */
export function parseFieldAddress(
  raw: string,
  kind: FieldAddressKind
): FieldAddress {
  const systemMap =
    kind === 'entity' ? SYSTEM_ENTITY_SCALARS : SYSTEM_RELATION_SCALARS

  if (raw.startsWith('system.')) {
    const field = raw.slice('system.'.length)
    if (!systemMap.has(field)) {
      throw new InvalidFieldAddressError(raw, kind, systemMap)
    }
    return { scope: 'system', field, raw }
  }

  if (raw.startsWith('metadata.')) {
    const field = raw.slice('metadata.'.length)
    if (field.length === 0) {
      throw new InvalidFieldAddressError(raw, kind, systemMap)
    }
    return { scope: 'metadata', field, raw }
  }

  if (raw.length === 0) {
    throw new InvalidFieldAddressError(raw, kind, systemMap)
  }

  // Bare name = the user's metadata field. Always. Even when the same name
  // exists in the system map — `confidence` as a bare name is the user's
  // metadata field named confidence; the engine scalar is system.confidence.
  return { scope: 'metadata', field: raw, raw }
}

/**
 * Read the addressed value off an entity. The ONLY sanctioned way a query
 * surface turns a {@link FieldAddress} into a value — direct property reads
 * against records re-create the shadow class this module exists to kill.
 *
 * @returns The value, or `undefined` when the record does not carry it
 *   (missing values sort LAST in both directions per the ordering contract —
 *   they are never grounds for dropping a row).
 */
export function readEntityFieldAddress(
  entity: HNSWNounWithMetadata,
  address: FieldAddress
): unknown {
  if (address.scope === 'system') {
    return (entity as unknown as Record<string, unknown>)[address.field]
  }
  return entity.metadata?.[address.field]
}

/**
 * Relation twin of {@link readEntityFieldAddress}. The stored flat record
 * keys the relation type under `verb`; public Relation shapes may carry it
 * as `type` — both spellings of the record are read, the ADDRESS is always
 * `system.verb`.
 */
export function readRelationFieldAddress(
  verb: HNSWVerbWithMetadata,
  address: FieldAddress
): unknown {
  if (address.scope === 'system') {
    const rec = verb as unknown as Record<string, unknown>
    if (address.field === 'verb') return rec.verb ?? rec.type
    return rec[address.field]
  }
  return verb.metadata?.[address.field]
}

/**
 * Build the ruled did-you-mean refusal text for a bare name that resolved to
 * metadata but is UNKNOWN to the index — the data-aware half of the law,
 * called by the query layer once it has consulted the known-field set:
 *
 *   "no metadata field 'createdAt' — did you mean system.createdAt or
 *    metadata.createdAt?"
 *
 * When the bare name is NOT a system scalar the system candidate is omitted
 * (there is only one thing the caller could have meant; the refusal exists
 * because refusing beats silently sorting nothing).
 */
export function buildUnresolvableMessage(
  raw: string,
  kind: FieldAddressKind
): string {
  const systemMap =
    kind === 'entity' ? SYSTEM_ENTITY_SCALARS : SYSTEM_RELATION_SCALARS
  if (systemMap.has(raw)) {
    return (
      `no metadata field '${raw}' — did you mean system.${raw} or metadata.${raw}? ` +
      `(bare names always address your metadata; engine fields need the system. prefix)`
    )
  }
  return (
    `no metadata field '${raw}' on this store — nothing carries it, so an ordered or ` +
    `filtered read against it cannot mean anything. Spell it metadata.${raw} once the ` +
    `field exists, or check the field name.`
  )
}

/**
 * @description Refusal for a malformed or out-of-map field ADDRESS —
 * `system.<anything-not-in-the-map>` (including all plumbing), an empty
 * name, or a bare `metadata.` prefix. The message carries the full valid
 * system map so the fix never needs a docs lookup.
 */
export class InvalidFieldAddressError extends Error {
  public readonly raw: string
  public readonly kind: FieldAddressKind

  constructor(raw: string, kind: FieldAddressKind, systemMap: ReadonlySet<string>) {
    const valid = [...systemMap].map((f) => `system.${f}`).join(', ')
    super(
      `'${raw}' is not an addressable ${kind} field. Bare names address your own ` +
        `metadata fields; engine fields are exactly: ${valid}. Engine plumbing ` +
        `(vector, connections, level, data, _rev) is not part of the query surface.`
    )
    this.name = 'InvalidFieldAddressError'
    this.raw = raw
    this.kind = kind
  }
}
