/**
 * Image dimension constraints and header-based dimension parser.
 *
 * PURPOSE_CONSTRAINTS defines the minimum dimensions and enforced aspect ratio
 * for each upload purpose. Used by both the client (ImageUploadField) and the
 * server (finalizeUpload) so validation is consistent and cannot be bypassed.
 *
 * parseDimensions reads image dimensions from raw file bytes without any
 * external dependencies - safe to run inside Cloudflare Workers.
 */

// ─── Constraint map ───────────────────────────────────────────────────────────

import type { UploadPurpose } from '@/lib/enums/upload-purpose';
export type { UploadPurpose };

export interface DimensionConstraint {
  minWidth: number;
  minHeight: number;
  /** Null = flexible (no aspect-ratio lock). */
  aspectRatio: '1:1' | '16:9' | null;
}

// Vite replaces import.meta.env.DEV with a boolean literal at build time.
const _isDev = import.meta.env.DEV === true;

export const PURPOSE_CONSTRAINTS: Record<UploadPurpose, DimensionConstraint> = {
  // In development, vendor_hero is relaxed to 768×432 so SD-Turbo generated
  // images can be tested via the upload UI.
  // Production enforces 1600×900 - the full hero variant size.
  vendor_hero: _isDev
    ? { minWidth: 768, minHeight: 432, aspectRatio: '16:9' }
    : { minWidth: 1600, minHeight: 900, aspectRatio: '16:9' },
  vendor_logo: { minWidth: 80, minHeight: 80, aspectRatio: '1:1' },
  vendor_gallery: { minWidth: 800, minHeight: 450, aspectRatio: null },
  deal_image: { minWidth: 400, minHeight: 400, aspectRatio: '1:1' },
  user_gallery: { minWidth: 800, minHeight: 450, aspectRatio: null },
  avatar: { minWidth: 80, minHeight: 80, aspectRatio: '1:1' },
  homepage_banner: _isDev
    ? { minWidth: 960, minHeight: 540, aspectRatio: '16:9' }
    : { minWidth: 1920, minHeight: 1080, aspectRatio: '16:9' },
  support_attachment: { minWidth: 200, minHeight: 200, aspectRatio: null },
};

// ─── Header-based dimension parser ───────────────────────────────────────────

export interface ImageDimensions {
  width: number;
  height: number;
}

/**
 * Reads image dimensions from raw file bytes without external dependencies.
 * Supports PNG, JPEG, WebP (VP8/VP8L/VP8X). Returns null for unsupported
 * formats or if the header is too short / malformed.
 *
 * NOTE: Pass as many bytes as possible; JPEG dimension scanning may need up to
 * a few kilobytes to reach the first SOF marker.
 */
export function parseDimensions(bytes: Uint8Array): ImageDimensions | null {
  if (bytes.length < 12) return null;

  // ── PNG ─────────────────────────────────────────────────────────────────────
  // Signature: 89 50 4E 47 0D 0A 1A 0A at bytes 0-7
  // IHDR chunk:  length (4 bytes) + "IHDR" (4 bytes) + width (4 BE) + height (4 BE)
  //              starts at byte 8, so width is at 16 and height at 20.
  if (
    bytes[0] === 0x89 &&
    bytes[1] === 0x50 &&
    bytes[2] === 0x4e &&
    bytes[3] === 0x47 &&
    bytes.length >= 24
  ) {
    const width = readUint32BE(bytes, 16);
    const height = readUint32BE(bytes, 20);
    if (width > 0 && height > 0) return { width, height };
    return null;
  }

  // ── WebP ────────────────────────────────────────────────────────────────────
  // RIFF....WEBP at bytes 0-11
  if (
    bytes[0] === 0x52 &&
    bytes[1] === 0x49 &&
    bytes[2] === 0x46 &&
    bytes[3] === 0x46 && // RIFF
    bytes[8] === 0x57 &&
    bytes[9] === 0x45 &&
    bytes[10] === 0x42 &&
    bytes[11] === 0x50 // WEBP
  ) {
    return parseWebPDimensions(bytes);
  }

  // ── JPEG ────────────────────────────────────────────────────────────────────
  // FF D8 FF at bytes 0-2
  if (bytes[0] === 0xff && bytes[1] === 0xd8 && bytes[2] === 0xff) {
    return parseJpegDimensions(bytes);
  }

  return null;
}

// ─── Internal helpers ─────────────────────────────────────────────────────────

function readUint32BE(buf: Uint8Array, offset: number): number {
  return (
    (((buf[offset] ?? 0) << 24) |
      ((buf[offset + 1] ?? 0) << 16) |
      ((buf[offset + 2] ?? 0) << 8) |
      (buf[offset + 3] ?? 0)) >>>
    0
  );
}

function readUint16BE(buf: Uint8Array, offset: number): number {
  return (((buf[offset] ?? 0) << 8) | (buf[offset + 1] ?? 0)) >>> 0;
}

function readUint24LE(buf: Uint8Array, offset: number): number {
  return (
    ((buf[offset] ?? 0) | ((buf[offset + 1] ?? 0) << 8) | ((buf[offset + 2] ?? 0) << 16)) >>> 0
  );
}

/**
 * Parse WebP sub-format:
 *   VP8  (lossy):  chunk id "VP8 " at offset 12; width/height in frame header at +14 (14-bit LE)
 *   VP8L (lossless): chunk id "VP8L" at offset 12; packed dimensions at +21
 *   VP8X (extended): chunk id "VP8X" at offset 12; canvas width-1/height-1 at +24/+27 (24-bit LE)
 */
function parseWebPDimensions(bytes: Uint8Array): ImageDimensions | null {
  if (bytes.length < 16) return null;

  const chunk = String.fromCharCode(bytes[12] ?? 0, bytes[13] ?? 0, bytes[14] ?? 0, bytes[15] ?? 0);

  if (chunk === 'VP8 ' && bytes.length >= 30) {
    // VP8 bitstream frame tag starts at offset 20; width/height at 26-29
    // width_and_scale: 16-bit LE at offset 26; height_and_scale at 28
    const rawW = (bytes[26] ?? 0) | ((bytes[27] ?? 0) << 8);
    const rawH = (bytes[28] ?? 0) | ((bytes[29] ?? 0) << 8);
    const width = rawW & 0x3fff;
    const height = rawH & 0x3fff;
    if (width > 0 && height > 0) return { width, height };
    return null;
  }

  if (chunk === 'VP8L' && bytes.length >= 25) {
    // Signature byte 0x2f at offset 20; packed bits at 21+
    // bits 0-13: width-1; bits 14-27: height-1
    const b0 = bytes[21] ?? 0;
    const b1 = bytes[22] ?? 0;
    const b2 = bytes[23] ?? 0;
    const b3 = bytes[24] ?? 0;
    const packed = b0 | (b1 << 8) | (b2 << 16) | (b3 << 24);
    const width = (packed & 0x3fff) + 1;
    const height = ((packed >> 14) & 0x3fff) + 1;
    if (width > 0 && height > 0) return { width, height };
    return null;
  }

  if (chunk === 'VP8X' && bytes.length >= 33) {
    // Canvas width-1 at offset 24 (24-bit LE); canvas height-1 at offset 27 (24-bit LE)
    const width = readUint24LE(bytes, 24) + 1;
    const height = readUint24LE(bytes, 27) + 1;
    if (width > 0 && height > 0) return { width, height };
    return null;
  }

  return null;
}

/**
 * Scan a JPEG bitstream for the first SOF0/SOF1/SOF2 marker (0xFFC0/FFC1/FFC2)
 * which contains the image height and width.
 */
function parseJpegDimensions(bytes: Uint8Array): ImageDimensions | null {
  let i = 2; // skip FF D8 SOI
  while (i < bytes.length - 8) {
    if (bytes[i] !== 0xff) break;
    const marker = bytes[i + 1] ?? 0;

    // SOF0, SOF1, SOF2 - Start Of Frame markers that carry dimensions
    if (marker === 0xc0 || marker === 0xc1 || marker === 0xc2) {
      // segment layout: FF Cx [length 2B] [precision 1B] [height 2B] [width 2B]
      if (i + 8 < bytes.length) {
        const height = readUint16BE(bytes, i + 5);
        const width = readUint16BE(bytes, i + 7);
        if (width > 0 && height > 0) return { width, height };
      }
      return null;
    }

    // Skip non-SOF segments: FF XX [length 2B including length itself, not FF XX]
    if (marker === 0xd9 || marker === 0xda) break; // EOI or SOS - stop scanning
    const segLen = readUint16BE(bytes, i + 2);
    if (segLen < 2) break;
    i += 2 + segLen;
  }
  return null;
}
