/**
 * Kinds of entities in the knowledge graph.
 *
 * Entity IDs live in a flat namespace today (a page `project` and a tag
 * `project` would collide). This is acceptable for current scale but
 * may need prefixed IDs (`page:project`, `tag:project`) in the future
 * if collisions become a problem — update GraphProvider.getEntity and
 * getEntitiesByIds to prepend/strip prefixes at that point.
 */
export type EntityKind = "page" | "block" | "tag"

/**
 * A node in the knowledge graph.
 *
 * Every entity is addressable by its `id`, which is unique within
 * its kind but may collide across kinds (see EntityKind JSDoc).
 *
 * - `page`: id = workspace-relative file path (e.g. "pages/Architecture.md")
 * - `block`: id = nanoId (stamped or transient)
 * - `tag`: id = normalized tag name (lowercase)
 */
export interface Entity {
  id: string
  kind: EntityKind
  /** Human-readable title. For pages: first H1 or filename stem. For blocks: first 80 chars. For tags: the tag name. */
  title?: string
  /** For pages and blocks: the workspace-relative file path containing this entity. */
  path?: string
}

/**
 * Relations form a directed graph where `from` points to `to`.
 *
 * - `references`: a block references a page or another block
 *   (from: source_block_id, to: target page name or block id)
 * - `contains`: a page contains a block
 *   (from: page_path, to: block_id)
 * - `tagged`: a block is tagged with a tag
 *   (from: block_id, to: tag name)
 */
export interface Relation {
  from: string
  to: string
  type: "references" | "contains" | "tagged"
}

/**
 * A key-value property extracted from block content.
 *
 * Grammar (must match the indexer's extractProperties):
 *   PropertyLine  ::= Key "::" " "? Value?
 *   Key           ::= [\w][\w-]*   (alphanumeric + hyphens, no spaces)
 *   Value         ::= .*           (everything after `::` until newline)
 *
 * Examples:
 *   status:: active
 *   priority:: high
 *   due-date:: 2026-07-01
 *   project_name:: enesis
 *
 * This grammar is stable once users rely on it. Changing it is a
 * breaking change for the index and query system.
 */
export interface Property {
  entityId: string
  key: string
  value: string
}

/**
 * Abstraction over the entity/relation/property storage layer.
 *
 * Implementations wrap different backends (SQLite, in-memory for tests,
 * cached, remote) while exposing a uniform graph query interface.
 * The provider is read-only — it never mutates the underlying index.
 */
export interface GraphProvider {
  /**
   * Return all entities of the given kind, or all entities if kind is omitted.
   * Results should be ordered: pages by title, blocks by recency, tags alphabetically.
   */
  getEntities(kind?: EntityKind): Promise<Entity[]>

  /**
   * Look up a single entity by ID. Returns undefined when not found.
   * Checks all entity kinds (page, block, tag) in that order.
   */
  getEntity(id: string): Promise<Entity | undefined>

  /**
   * Batch entity lookup. Accepts any number of IDs and returns only
   * entities that exist — missing IDs are silently dropped.
   *
   * IDs live in a flat namespace; a request for `["project"]` could
   * match a page OR a tag with that name. The implementation should
   * check all entity kinds. See EntityKind JSDoc for future plans to
   * adopt prefixed IDs (`page:project`, `tag:project`).
   *
   * This is the escape hatch for avoiding N+1 query patterns in
   * getIncoming, getOutgoing, and queryByProperty.
   */
  getEntitiesByIds(ids: string[]): Promise<Entity[]>

  /**
   * Query the relation graph. All parameters are optional filters:
   * - from: filter by source_block_id
   * - to: filter by target
   * - type: restrict to a relation type
   */
  getRelations(
    from?: string,
    to?: string,
    type?: Relation["type"],
  ): Promise<Relation[]>

  /**
   * Query the properties table. Both parameters optional:
   * - entityId: filter to a single entity's properties
   * - key: filter to a specific property key
   * Omitting both returns all properties across all entities.
   */
  getProperties(entityId?: string, key?: string): Promise<Property[]>
}
