/**
 * community blueprint · wiring seam for `@platform-modules/uploads`.
 *
 * Adapter-minimalism (CLAUDE.md §4): uploads is a SUBPATH-ONLY module (no barrel). This seam
 * composes its two TRUST-BOUNDARY gates — `/magic-bytes` (header content-type sniffing) and
 * `/size-limit` — into one validate() the host calls before accepting an attachment.
 *
 * HARD FLOOR (CLAUDE.md: trust-boundary validation is never on the chopping block): a forged image
 * (an SVG/HTML/script blob masquerading as `image/png` via a lying Content-Type) or an oversized blob
 * is rejected HERE, before the post is ever published, indexed, or announced. The composition test's
 * fail-closed case turns on this gate running first.
 */
import { detectMimeFromMagicBytes, type DetectedImageMime } from '@platform-modules/uploads/magic-bytes'
import { assertSizeLimit } from '@platform-modules/uploads/size-limit'

export class RejectedAttachmentError extends Error {
  readonly name = 'RejectedAttachmentError'
  constructor(reason: string) {
    super(`attachment rejected: ${reason}`)
  }
}

/** An upload candidate — only the raw bytes; the client's claimed Content-Type is deliberately absent (forgeable). */
export type Attachment = { bytes: Uint8Array }

/**
 * Validate an upload at the trust boundary. Returns the SNIFFED mime (derived from the bytes, never a
 * client claim). Throws `SizeLimitExceededError` (from the module) when over `maxBytes`, and
 * `RejectedAttachmentError` when the header is not a permitted image (spoof / script-bearing SVG).
 * Size is checked first so an oversized blob is rejected before any header work.
 */
export function validateAttachment(att: Attachment, maxBytes: number): DetectedImageMime {
  assertSizeLimit(att.bytes.byteLength, maxBytes)
  const mime = detectMimeFromMagicBytes(att.bytes)
  if (mime === null) {
    throw new RejectedAttachmentError('content does not match a permitted image type (possible spoof)')
  }
  return mime
}
