const MAX_BYTES = 2_000_000;
const crcTable = Uint32Array.from({ length: 256 }, (_, index) => {
  let value = index;
  for (let bit = 0; bit < 8; bit++) value = value & 1 ? 0xedb88320 ^ (value >>> 1) : value >>> 1;
  return value >>> 0;
});
function crc32(bytes: Uint8Array, start: number, end: number): number {
  let value = 0xffffffff;
  for (let i = start; i < end; i++) value = crcTable[(value ^ bytes[i]) & 0xff] ^ (value >>> 8);
  return (value ^ 0xffffffff) >>> 0;
}
export function inspectLogo(bytes: Uint8Array): { mime: 'image/png' | 'image/jpeg'; width: number; height: number } {
  if (bytes.byteLength > MAX_BYTES || bytes.byteLength < 24) throw new Error('Choose a PNG or JPEG logo smaller than 2 MB.');
  const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
  let width = 0; let height = 0; let mime: 'image/png' | 'image/jpeg';
  if ([137, 80, 78, 71, 13, 10, 26, 10].every((value, i) => bytes[i] === value)) {
    if (view.getUint32(8) !== 13 || String.fromCharCode(...bytes.slice(12, 16)) !== 'IHDR') throw new Error('The PNG logo is not valid.');
    mime = 'image/png'; width = view.getUint32(16); height = view.getUint32(20);
    if (width < 1 || height < 1 || width > 4096 || height > 4096 || width * height > 8_000_000) throw new Error('Choose a logo up to 4096 pixels per side and 8 million pixels in total.');
    let cursor = 8; let imageData = false; let ended = false;
    while (cursor + 12 <= bytes.length) {
      const length = view.getUint32(cursor); const type = String.fromCharCode(...bytes.slice(cursor + 4, cursor + 8));
      if (length > bytes.length - cursor - 12) throw new Error('The PNG logo is incomplete.');
      if (crc32(bytes, cursor + 4, cursor + 8 + length) !== view.getUint32(cursor + 8 + length)) throw new Error('The PNG logo is damaged. Choose another image file.');
      if (type === 'acTL') throw new Error('Use a still PNG or JPEG logo, rather than an animated image.');
      if (type === 'IDAT') imageData = true;
      cursor += length + 12;
      if (type === 'IEND') { ended = length === 0 && cursor === bytes.length; break; }
    }
    if (!imageData || !ended) throw new Error('The PNG logo is incomplete.');
  } else if (bytes[0] === 0xff && bytes[1] === 0xd8) {
    if (bytes.at(-2) !== 0xff || bytes.at(-1) !== 0xd9) throw new Error('The JPEG logo is incomplete.');
    mime = 'image/jpeg'; let cursor = 2;
    while (cursor + 4 <= bytes.length) {
      if (bytes[cursor] !== 0xff) throw new Error('The JPEG logo is not valid.');
      while (bytes[cursor] === 0xff) cursor++;
      const marker = bytes[cursor++];
      if (marker === 0xd9 || marker === 0xda) break;
      if (marker === 0x01 || (marker >= 0xd0 && marker <= 0xd7)) continue;
      if (cursor + 2 > bytes.length) break;
      const length = view.getUint16(cursor);
      if (length < 2 || cursor + length > bytes.length) throw new Error('The JPEG logo is incomplete.');
      if ([0xc0, 0xc1, 0xc2].includes(marker) && length >= 8) { height = view.getUint16(cursor + 3); width = view.getUint16(cursor + 5); break; }
      cursor += length;
    }
  } else throw new Error('Only local PNG and JPEG logos are supported. SVG and remote images are not accepted.');
  if (width < 1 || height < 1 || width > 4096 || height > 4096 || width * height > 8_000_000) throw new Error('Choose a logo up to 4096 pixels per side and 8 million pixels in total.');
  return { mime, width, height };
}
export function inspectLogoDataUrl(url: string) {
  const match = /^data:(image\/(?:png|jpeg));base64,([A-Za-z0-9+/]+={0,2})$/.exec(url);
  if (!match || match[2].length > 2_666_672 || match[2].length % 4 !== 0) throw new Error('Use a local PNG or JPEG logo.');
  const binary = atob(match[2]);
  const bytes = Uint8Array.from(binary, ch => ch.charCodeAt(0));
  const info = inspectLogo(bytes);
  if (info.mime !== match[1]) throw new Error('The logo file type does not match its contents.');
  return { ...info, bytes };
}
export async function sanitizeLogo(file: Blob): Promise<string> {
  if (file.size > MAX_BYTES || file.size < 24) throw new Error('Choose a PNG or JPEG logo smaller than 2 MB.');
  const bytes = new Uint8Array(await file.arrayBuffer());
  const info = inspectLogo(bytes);
  let bitmap: ImageBitmap;
  try { bitmap = await createImageBitmap(new Blob([bytes], { type: info.mime })); } catch { throw new Error('This logo could not be decoded. Choose another PNG or JPEG file.'); }
  try {
    if (bitmap.width !== info.width || bitmap.height !== info.height) throw new Error('The logo dimensions do not match the image.');
    const factor = Math.min(1, 1200 / Math.max(bitmap.width, bitmap.height));
    const canvas = document.createElement('canvas'); canvas.width = Math.max(1, Math.round(bitmap.width * factor)); canvas.height = Math.max(1, Math.round(bitmap.height * factor));
    const context = canvas.getContext('2d'); if (!context) throw new Error('Your browser could not prepare this logo.');
    context.drawImage(bitmap, 0, 0, canvas.width, canvas.height);
    const url = canvas.toDataURL('image/png'); inspectLogoDataUrl(url); return url;
  } finally { bitmap.close(); }
}
