/**
 * # protocol/attestation — the shared WIRE constants of the signed grade root (OSS-ADR-0046)
 *
 * The load-bearing, OPAQUE wire constants that the Ed25519-signed grade root and the receipt
 * attestation label are built from. Extracted here as the ONE dependency-free home so the
 * platform signer and the OSS CLI verifier import the SAME literals instead of each re-declaring
 * them — a drift between two independently-declared copies is invisible until a cross-repo verify
 * fails (exactly the `kestrel-markets-ha87` failure mode: a verifier that mishandled the `sha256:`
 * display prefix falsely reported UNVERIFIED on every prod proof). One shared home makes any
 * future drift a RED conformance run instead of a silent cross-repo break.
 *
 * FAIL-CLOSED CONTRACT: the OSS verifier never trusts a proof body's own `verified` flag — it
 * rebuilds the signing input from THESE canonical constants (`${GRADE_SIGN_PREFIX}.<bare-hex-root>`)
 * and re-checks the signature locally. These constants ARE that rebuild's vocabulary.
 *
 * DEPENDENCY-FREE (OSS-ADR-0046 §2): plain named `const`s, zero runtime deps, no engine/frame/lang
 * — importable by the LIGHT `verify` path (node built-ins only) without dragging control-plane or
 * runtime code into its import graph.
 *
 * VERSIONING NOTE (OSS-ADR-0046 addendum): these attestation constants are their OWN independent
 * epochs and are deliberately NOT gated on `PROTOCOL_VERSION`. `JUDGE_VERSION` / `FILL_MODEL_VERSION`
 * pin the judge attestation label and the honest-fill model version; a wire major.minor bump must not
 * silently re-stamp a previously-signed grade (bead b83 froze the analogous catalog epoch for exactly
 * this reason). Bump each here, explicitly, only when its own semantics change.
 *
 * COLLISION WARNING — do NOT merge with `src/catalog/session-catalog.ts`'s `JUDGE_VERSION`
 * (`"kestrel-judge/1"`): that is the catalog/replay grade-engine EPOCH (frozen per bead b83), an
 * unrelated constant that merely shares a name with the attestation label `"0.1"` here. Import both
 * only under distinct local names.
 */

/**
 * The grade signing-input prefix (`kgrade1`): the Ed25519 signature attests over
 * `${GRADE_SIGN_PREFIX}.<bare-hex-root>`. An OPAQUE wire constant the OSS verifier and the platform
 * signer must agree on byte-for-byte — it carries no grade schema and no attribution meaning.
 */
export const GRADE_SIGN_PREFIX = "kgrade1";

/**
 * The judge identity STAMPED on every certified receipt (`${JUDGE_ID}@${JUDGE_VERSION}`) — names
 * "the one open judge" a grade was minted under. The COMPUTED numbers are the OSS grader's
 * (`grade()`); this is the platform ATTESTATION label the signature covers, not a metric owner.
 */
export const JUDGE_ID = "kestrel-open-judge";

/**
 * The judge ATTESTATION label version (`"0.1"`) — its OWN epoch, NOT gated on `PROTOCOL_VERSION`
 * (see the versioning note above). Distinct from the catalog `JUDGE_VERSION` (`"kestrel-judge/1"`,
 * the replay grade-engine epoch) despite the shared name.
 */
export const JUDGE_VERSION = "0.1";

/**
 * The honest-fill model version pin (`"honest-fill-v0"`) the signature covers — EVs computed under
 * different fill-model versions refuse naive comparison. Its OWN epoch, NOT gated on
 * `PROTOCOL_VERSION`.
 */
export const FILL_MODEL_VERSION = "honest-fill-v0";

/**
 * The `sha256:` DISPLAY prefix the wire `root` carries (platform bead 7ea) so it equals the Grade
 * artifact's `content_hash`. The Ed25519 signature covers the BARE lowercase-hex root
 * (`${GRADE_SIGN_PREFIX}.<hex>`, never `${GRADE_SIGN_PREFIX}.sha256:<hex>`), so a verifier MUST strip
 * this prefix before rebuilding the signed message — not doing so falsely reported UNVERIFIED on every
 * genuine prod proof (bead `kestrel-markets-ha87`). An OPAQUE wire constant — not business logic.
 */
export const ROOT_HASH_PREFIX = "sha256:";

/**
 * WHERE the published verify-set lives, relative to the API base — the anonymous discovery document
 * carrying `grade_verify_keys`. Quoted in the UNVERIFIED/KEY_RETIRED diagnostic so a reader can
 * independently re-adjudicate the signature against the SAME key material (beads
 * `kestrel-markets-5bjn` / `fjq7` — the key was published but no proof said WHERE, so a cold verifier
 * guessed and 404'd). The platform re-exports this as `GRADE_VERIFY_KEYS_PATH` — two names, ONE wire
 * path; a drift is a cold-verifier 404.
 */
export const VERIFY_KEYS_PATH = "/.well-known/kestrel-markets";

/* ─────────────────────────── the signed grade-root derivation (OSS-ADR-0051, kestrel-b0li) ─────────────────────────── */

/**
 * A JSON-representable value — the domain the canonical serializer + `certifiedRoot` operate over. A
 * covered-field value is exactly this shape (no `undefined` survives the canonical form — an absent
 * optional field is simply not a key, reading UNKNOWN).
 */
export type JsonValue = null | boolean | number | string | JsonValue[] | { readonly [k: string]: JsonValue };

/**
 * The EXACT top-level field keys the Ed25519 signature commits to — the SHAPE the root is a hash of,
 * versioned here as public dependency-free protocol (OSS-ADR-0051, superseding the platform-only
 * `SIGNATURE_COVERED_GRADE_FIELDS` in `apps/api/src/grade/signer.ts`). ADDITIVE-OPTIONAL: an absent
 * optional field (`pinnedProvenanceRef`, `pinnedAuthorProvenanceRef`, `counterfactual`) is dropped by
 * the canonical form and reads UNKNOWN, so a prior-version grade that never carried it still recomputes
 * byte-identically — additive fields never break prior-version recompute.
 *
 * VERSIONED WITH `PROTOCOL_VERSION`: this key SET + its membership is part of the wire contract. A
 * BREAKING change to the set (a covered field removed, or a former-optional made required) is a
 * coordinated `PROTOCOL_VERSION` bump that FAILS CLOSED on mismatch — never a silent divergence. (The
 * key ORDER here is documentary only: `canonicalizeJson` sorts keys, so the signed bytes depend on the
 * key SET and the values, never on this tuple's order.)
 */
export const SIGNATURE_COVERED_GRADE_FIELDS = Object.freeze([
  "kind",
  "result",
  "evView",
  "supportPartition",
  "pinnedDataRef",
  "pinnedProvenanceRef",
  "pinnedAuthorProvenanceRef",
  "pinnedFillModelVersion",
  "judge",
  "issuedAt",
  "boundRoots",
  "counterfactual",
  "kid",
  "epoch",
  "replayable",
] as const);

/** A single signature-covered grade field key (member of {@link SIGNATURE_COVERED_GRADE_FIELDS}). */
export type SignatureCoveredGradeField = (typeof SIGNATURE_COVERED_GRADE_FIELDS)[number];

/**
 * The covered fields fed INTO {@link certifiedRoot} — the values the signature commits to. `issuedAt`,
 * `kid`, and `epoch` are INPUTS (never generated here: no wall clock, determinism preserved — the OSS
 * path only ever RE-derives a root from published values, it never mints `issuedAt`). The structured
 * members (`result`, `supportPartition`, `counterfactual`) are opaque {@link JsonValue}s: `certifiedRoot`
 * commits to the SHAPE (the key set) and hashes whatever values are fed, so protocol stays generic and
 * dependency-free (no grade-arithmetic import). Matches the platform signer's `signingPayload` fold
 * byte-for-byte so the eventual signer swap is byte-identical.
 */
export interface CertifiedGradeCoveredFields {
  readonly kind: "grade";
  readonly result: JsonValue;
  readonly evView: string;
  readonly supportPartition: JsonValue;
  readonly pinnedDataRef: string;
  readonly pinnedProvenanceRef?: string;
  readonly pinnedAuthorProvenanceRef?: string;
  readonly pinnedFillModelVersion: string;
  readonly judge: string;
  readonly issuedAt: string;
  readonly boundRoots: readonly string[];
  readonly counterfactual?: JsonValue;
  readonly kid: string;
  readonly epoch: number;
  readonly replayable: boolean;
}

/**
 * Recursively rebuild a value with every object's keys sorted lexicographically; arrays keep their
 * order (meaningful); every `undefined`/absent entry is dropped (a canonical value carries no
 * `undefined`). Pure — no wall clock, no RNG. Self-contained inside the dependency-free protocol
 * boundary (it may NOT import the runtime's `src/canonical`); a drift-guard test asserts it is
 * byte-identical to that ONE canonical owner over the covered-field domain (mirroring how the platform
 * proved byte-identity before deleting its local copy).
 */
function canonicalize(value: unknown): JsonValue {
  if (value === null) return null;
  if (Array.isArray(value)) return value.map(canonicalize);
  if (typeof value === "object") {
    const src = value as Record<string, unknown>;
    const out: { [k: string]: JsonValue } = {};
    for (const key of Object.keys(src).sort()) {
      const v = src[key];
      if (v === undefined) continue; // absent, never `undefined`
      out[key] = canonicalize(v);
    }
    return out;
  }
  return value as JsonValue;
}

/**
 * Canonical JSON of a value: compact machine-JSON with lexicographically ordered keys and no trailing
 * newline — the exact bytes the signed root is a hash of. Pure and total; the same value always yields
 * byte-identical text (byte-identical to the runtime's `src/canonical/json.ts` `canonicalizeJson`, the
 * ONE canonical owner — asserted by the drift-guard test).
 */
export function canonicalizeJson(value: unknown): string {
  return JSON.stringify(canonicalize(value));
}

/**
 * Build the canonical covered-field payload the root hashes over: the {@link SIGNATURE_COVERED_GRADE_FIELDS}
 * key set, with the three additive-optional keys dropped when absent (so a grade that never carried them
 * recomputes byte-identically). Matches the platform signer's `signingPayload` exactly.
 */
function certifiedPayload(fields: CertifiedGradeCoveredFields): { readonly [k: string]: JsonValue } {
  return {
    kind: fields.kind,
    result: fields.result,
    evView: fields.evView,
    supportPartition: fields.supportPartition,
    pinnedDataRef: fields.pinnedDataRef,
    ...(fields.pinnedProvenanceRef !== undefined ? { pinnedProvenanceRef: fields.pinnedProvenanceRef } : {}),
    ...(fields.pinnedAuthorProvenanceRef !== undefined ? { pinnedAuthorProvenanceRef: fields.pinnedAuthorProvenanceRef } : {}),
    pinnedFillModelVersion: fields.pinnedFillModelVersion,
    judge: fields.judge,
    issuedAt: fields.issuedAt,
    boundRoots: [...fields.boundRoots],
    ...(fields.counterfactual !== undefined ? { counterfactual: fields.counterfactual } : {}),
    kid: fields.kid,
    epoch: fields.epoch,
    replayable: fields.replayable,
  };
}

/** Lowercase hex SHA-256 of a UTF-8 string via WebCrypto — portable across node (`globalThis.crypto`),
 * Bun, and Worker isolates, and byte-identical to the platform signer's `sha256Hex`. No import needed
 * (WebCrypto is a global), keeping the protocol boundary dependency-free. */
async function sha256Hex(text: string): Promise<string> {
  const digest = await globalThis.crypto.subtle.digest("SHA-256", new TextEncoder().encode(text));
  const bytes = new Uint8Array(digest);
  let hex = "";
  for (const byte of bytes) hex += byte.toString(16).padStart(2, "0");
  return hex;
}

/**
 * The signed grade ROOT derivation, as a PURE protocol function (OSS-ADR-0051): the BARE lowercase-hex
 * `sha256` of the canonical covered-field payload — `sha256Hex(canonicalizeJson(certifiedPayload(fields)))`.
 * This is the SHAPE the Ed25519 signature commits to (the signature attests `${GRADE_SIGN_PREFIX}.<root>`
 * over this bare hex; the wire `root` additionally carries the `${ROOT_HASH_PREFIX}` display prefix).
 *
 * Pure & deterministic: no wall clock, no RNG, no private key. `issuedAt`/`kid`/`epoch` are INPUTS. The
 * private signing key stays platform-side (OSS-ADR-0001/0009 open/closed seam) — only the DERIVATION is
 * shared, never the key. An outsider can now rebuild the signed root, not merely the numbers under it.
 *
 * Async because WebCrypto's digest is async; deterministic all the same. Byte-identical to the platform
 * signer's `sha256Hex(canonicalize(signingPayload(...)))`, so the eventual signer swap changes no signed byte.
 */
export async function certifiedRoot(fields: CertifiedGradeCoveredFields): Promise<string> {
  return sha256Hex(canonicalizeJson(certifiedPayload(fields)));
}
