/**
 * Thumbnail format-hardening helper.
 *
 * Magic-byte verification for the three formats `@shade/files` advertises
 * over the wire (`png`, `webp`, `jpeg`). Apps producing thumbnails outside
 * the dispatcher (e.g. precomputing on disk) can call `assertThumbnailFormat`
 * to fail-fast on mislabelled bytes before they reach the network.
 *
 * The same magic-byte table is used inside `io-adapters.ts:adaptThumbnailResult`
 * to enforce the invariant on every outbound thumbnail.
 */

import { ConflictError } from '../schemas/errors.js';

const PNG_MAGIC = new Uint8Array([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
const JPEG_MAGIC = new Uint8Array([0xff, 0xd8, 0xff]);
const WEBP_RIFF = new Uint8Array([0x52, 0x49, 0x46, 0x46]);
const WEBP_HEAD = new Uint8Array([0x57, 0x45, 0x42, 0x50]);

export type ThumbnailFormat = 'png' | 'webp' | 'jpeg';

/**
 * Verify the bytes look like the declared format. Returns boolean — does
 * not throw. For a throwing variant, use `assertThumbnailFormat`.
 */
export function isThumbnailFormat(bytes: Uint8Array, format: ThumbnailFormat): boolean {
  switch (format) {
    case 'png':
      return startsWith(bytes, PNG_MAGIC);
    case 'jpeg':
      return startsWith(bytes, JPEG_MAGIC);
    case 'webp': {
      if (bytes.byteLength < 12) return false;
      if (!startsWith(bytes, WEBP_RIFF)) return false;
      for (let i = 0; i < 4; i++) {
        if (bytes[8 + i] !== WEBP_HEAD[i]) return false;
      }
      return true;
    }
  }
}

/** Throws `ConflictError` if `bytes` doesn't match the declared `format`. */
export function assertThumbnailFormat(
  bytes: Uint8Array,
  format: ThumbnailFormat,
): void {
  if (!isThumbnailFormat(bytes, format)) {
    throw new ConflictError(
      `thumbnail bytes do not match declared format=${format}`,
    );
  }
}

function startsWith(bytes: Uint8Array, prefix: Uint8Array): boolean {
  if (bytes.byteLength < prefix.byteLength) return false;
  for (let i = 0; i < prefix.byteLength; i++) {
    if (bytes[i] !== prefix[i]) return false;
  }
  return true;
}
