/** One indexed message document. `at` is epoch-ms so Meilisearch can sort recency-first. */
export interface SearchDoc {
  id: string; // interactionId
  scopeId: string;
  threadId: string;
  text: string;
  subject: string | null;
  source: string | null; // opaque app source tag (e.g. crm-messenger / crm-mail) — drives the surface
  actorId: string | null;
  at: number;
}

/** A search hit with a highlighted snippet. */
export interface SearchHit {
  id: string;
  threadId: string;
  text: string;
  subject: string | null;
  source: string | null;
  at: number;
  /** The match with `<em>…</em>` around the query terms (from the engine's highlighter). */
  snippet: string;
}

/**
 * The message-search seam. IIOS owns egress-style search: a message index + a permission-scoped
 * query. The concrete engine (Meilisearch) sits behind this; an unconfigured deployment binds a
 * no-op so search degrades to empty rather than erroring.
 */
export interface MessageSearchPort {
  /** True only when a real engine is configured (else index/search are inert). */
  ready(): boolean;
  index(docs: SearchDoc[]): Promise<void>;
  remove(ids: string[]): Promise<void>;
  /** Search WITHIN the given scope and thread set only (the permission fence — enforced here). */
  search(scopeId: string, threadIds: string[], query: string, limit: number): Promise<SearchHit[]>;
}

export const MESSAGE_SEARCH_PORT = Symbol('MESSAGE_SEARCH_PORT');
