/**
 * Canonical entity-id + provenance normalization (SB-S0 spine).
 *
 * Pure + deterministic — no filesystem, no clock, no network. The id is keyed on
 * a STABLE SLUG of the label, not the raw label, so editing a fact's *value* (or
 * the label's case/whitespace) never re-mints the id. SB-S3c threads this id
 * through the tributaries (the published record carries the `specifics`/`trends`
 * ids it was built from; analytics is joined by resolver — see `assemble.ts`);
 * SB-S0 established mint + shape.
 */

import { createHash } from "node:crypto";

import { PROVENANCE_VALUES } from "./types.js";
import type { Provenance } from "./types.js";

/**
 * Stable slug: lowercase, trim, every non-alphanumeric run → a single `-`, with
 * leading/trailing dashes stripped. Stable across case + whitespace variation of
 * the same label, so it is a safe id key.
 */
export function slugify(label: string): string {
  return label
    .trim()
    .toLowerCase()
    .replace(/[^a-z0-9]+/g, "-")
    .replace(/^-+|-+$/g, "");
}

/**
 * One canonical entity id = first 12 hex of `sha256(`${kind}:${slugify(key)}`)`.
 * Matches the trends/specifics `sha256[:12]` idiom (`specifics-bank/src/bank.ts:58`).
 * `kind` namespaces the id so the same key under different kinds never collides.
 */
export function mintEntityId(seed: { kind: string; key: string }): string {
  return createHash("sha256")
    .update(`${seed.kind}:${slugify(seed.key)}`)
    .digest("hex")
    .slice(0, 12);
}

/**
 * Content-hash id for an ingested published post = first 12 hex of
 * `sha256(VERBATIM body)`. Mirrors the specifics-bank `specificId` hash idiom
 * (`specifics-bank/src/bank.ts:57`) but deliberately hashes the body BYTE-EXACT —
 * NOT through `normalizeContent` (which lowercases + collapses whitespace). For a
 * full post body that normalization would map two structurally-different posts
 * (same words, different line breaks/case) to the same id, and the second would be
 * silently skipped on write — data loss of the exact gold signal SB-S1 captures.
 * Byte-identity hashing makes the id true content identity: only an identical
 * re-ingest collides; any real difference mints a distinct id (SB-S1).
 */
export function mintContentId(text: string): string {
  return createHash("sha256").update(text).digest("hex").slice(0, 12);
}

/**
 * Normalize a raw provenance string to the single brain vocabulary. Trims +
 * lowercases, then returns the match or THROWS — provenance is load-bearing for
 * the model-collapse guard, so an unknown value is an error, never a silent pass.
 */
export function normalizeProvenance(raw: string): Provenance {
  const candidate = raw.trim().toLowerCase();
  const match = PROVENANCE_VALUES.find((v) => v === candidate);
  if (!match) {
    throw new Error(
      `invalid provenance: ${JSON.stringify(raw)} — expected one of ${PROVENANCE_VALUES.join(", ")}`,
    );
  }
  return match;
}
