/**
 * BlobStore — server-side storage interface for the V4.9 encrypted-blob
 * primitive. A "slot" is a single AEAD-sealed blob keyed by a
 * deterministic 32-byte slotId derived client-side via HKDF from a
 * master key. The relay never sees plaintext, never holds private keys,
 * and never decrypts.
 *
 * Auth model (TOFU per slot, mirrors the inbox-owner pattern):
 *  - First PUT to an empty slot stores the caller's Ed25519 signing
 *    pubkey alongside the blob. Subsequent writes must produce a valid
 *    signature verifiable by that pubkey.
 *  - GET is unauthenticated — slotId is itself a 256-bit secret derived
 *    from the master key, so knowing it implies you derived the master.
 *  - DELETE clears the blob AND the owner pubkey, allowing future TOFU
 *    re-claim by a fresh signing key derived from the same master (e.g.
 *    after a rotation).
 *
 * CAS / etag semantics:
 *  - Every successful PUT bumps a per-slot monotonic etag (returned to
 *    the caller as a string).
 *  - A stale `ifMatch` triggers `PreconditionFailedError` (HTTP 412).
 *  - `ifMatch === undefined` against a populated slot triggers
 *    `ConflictError` (HTTP 409) — clients must read-then-write.
 *  - `ifMatch === '*'` against a populated slot is unconditional
 *    overwrite (escape hatch). Against an empty slot it's still 412
 *    per RFC 7232 (no entity to match).
 */
export interface BlobSlotRecord {
  /** Lower-hex 64-char slotId (32 bytes). */
  slotId: string;
  /** Raw AEAD ciphertext (bytes). The relay never decrypts. */
  blob: Uint8Array;
  /** Owner Ed25519 signing pubkey, established TOFU on the first PUT. */
  ownerPubkey: Uint8Array;
  /** Monotonic per-slot version. Used as the ETag on the wire. */
  etag: number;
  /** Wall-clock ms of the last successful write. */
  updatedAt: number;
}

/** Returned to the route layer after a successful PUT. */
export interface PutBlobResult {
  /** Whether the slot was created (true) or updated in place (false). */
  created: boolean;
  /** New etag after the write. */
  etag: number;
  /** Wall-clock ms of the write. */
  updatedAt: number;
}

export interface BlobStore {
  /** Read a slot, or null if it has never been written (or was deleted). */
  get(slotId: string): Promise<BlobSlotRecord | null>;

  /**
   * Create or update a slot.
   *
   * Implementations MUST treat `(slotId, ownerPubkey)` atomically: the
   * route layer has already verified the signature, but the store is the
   * authority on whether the slot exists and what etag it has. Callers
   * pass the verified `ownerPubkey` (used on first-write to record the
   * owner; ignored on subsequent writes — the existing pubkey is the
   * source of truth for who's allowed to write).
   *
   * `expectedEtag` semantics (mirror the wire-level If-Match):
   *  - `undefined`  : create-only. Slot must be empty.
   *  - `<number>`  : compare-and-swap. Must equal the current etag.
   *  - `'*'`       : unconditional overwrite. Slot must already exist.
   *
   * On precondition mismatch the store throws `PreconditionFailedError`
   * (stale etag) or `ConflictError` (slot exists, no ifMatch).
   */
  put(args: {
    slotId: string;
    blob: Uint8Array;
    ownerPubkey: Uint8Array;
    expectedEtag: number | '*' | undefined;
    now: number;
  }): Promise<PutBlobResult>;

  /**
   * Delete a slot. Authentication has already been checked by the route
   * layer. Returns true if a row was removed (i.e. the slot existed).
   */
  delete(slotId: string): Promise<boolean>;
}
