/**
 * Utilities for reading and writing GIF metadata (Application Extension blocks)
 * Used to embed user color in avatar images
 * 
 * GIF structure:
 * - Header: GIF87a or GIF89a (6 bytes)
 * - Logical Screen Descriptor (7 bytes)
 * - Optional Global Color Table
 * - Extension blocks and image data
 * - Trailer: 0x3B
 * 
 * Application Extension format:
 * - 0x21 (Extension Introducer)
 * - 0xFF (Application Extension Label)
 * - 0x0B (Block size - always 11)
 * - 8 bytes: Application Identifier
 * - 3 bytes: Application Authentication Code
 * - Data sub-blocks (size byte + data, terminated by 0x00)
 */

/* eslint-disable no-bitwise */
/* eslint-disable no-plusplus */
/* eslint-disable no-console */
/* eslint-disable no-restricted-syntax */
/* eslint-disable no-continue */

/** GIF signature bytes */
const GIF87A_SIGNATURE = new Uint8Array([0x47, 0x49, 0x46, 0x38, 0x37, 0x61]); // "GIF87a"
const GIF89A_SIGNATURE = new Uint8Array([0x47, 0x49, 0x46, 0x38, 0x39, 0x61]); // "GIF89a"

/** Our custom application identifier (8 chars) */
const PAARROT_APP_ID = 'PAARROT\0'; // 8 bytes, null-padded
const PAARROT_APP_ID_BYTES = new TextEncoder().encode(PAARROT_APP_ID);

/** Application authentication code (3 bytes) */
const PAARROT_AUTH_CODE = new Uint8Array([0x31, 0x2E, 0x30]); // "1.0"

/**
 * Check if data is a valid GIF file
 */
function isGIF(data: Uint8Array): boolean {
  if (data.length < 6) return false;
  
  // Check GIF87a
  let is87a = true;
  for (let i = 0; i < 6; i++) {
    if (data[i] !== GIF87A_SIGNATURE[i]) {
      is87a = false;
      break;
    }
  }
  if (is87a) return true;
  
  // Check GIF89a
  let is89a = true;
  for (let i = 0; i < 6; i++) {
    if (data[i] !== GIF89A_SIGNATURE[i]) {
      is89a = false;
      break;
    }
  }
  return is89a;
}

/**
 * Get the size of the Global Color Table if present
 */
function getGlobalColorTableSize(data: Uint8Array): number {
  // Logical Screen Descriptor starts at offset 6
  // Packed byte is at offset 10
  const packedByte = data[10];
  const hasGCT = (packedByte & 0x80) !== 0;
  if (!hasGCT) return 0;
  
  const gctSize = packedByte & 0x07;
  return 3 * (2 ** (gctSize + 1));
}

/**
 * Find all Application Extension blocks
 */
function findAppExtensions(data: Uint8Array, startOffset: number): Array<{
  offset: number;
  length: number;
  appId: string;
  authCode: Uint8Array;
  dataBlocks: Uint8Array[];
}> {
  const extensions: Array<{
    offset: number;
    length: number;
    appId: string;
    authCode: Uint8Array;
    dataBlocks: Uint8Array[];
  }> = [];
  
  let offset = startOffset;
  
  while (offset < data.length - 1) {
    // Look for Extension Introducer (0x21)
    if (data[offset] === 0x21 && data[offset + 1] === 0xFF) {
      // Application Extension
      const blockSize = data[offset + 2];
      if (blockSize !== 0x0B) {
        offset++;
        continue;
      }
      
      const appId = new TextDecoder().decode(data.slice(offset + 3, offset + 11));
      const authCode = data.slice(offset + 11, offset + 14);
      
      // Read data sub-blocks
      let subOffset = offset + 14;
      const dataBlocks: Uint8Array[] = [];
      let totalLength = 14; // header length
      
      while (subOffset < data.length && data[subOffset] !== 0x00) {
        const subBlockSize = data[subOffset];
        dataBlocks.push(data.slice(subOffset + 1, subOffset + 1 + subBlockSize));
        subOffset += 1 + subBlockSize;
        totalLength += 1 + subBlockSize;
      }
      
      // Add terminator
      totalLength += 1;
      
      extensions.push({
        offset,
        length: totalLength,
        appId,
        authCode,
        dataBlocks,
      });
      
      offset = subOffset + 1;
    } else if (data[offset] === 0x3B) {
      // Trailer - end of GIF
      break;
    } else {
      offset++;
    }
  }
  
  return extensions;
}

/**
 * Create a PAARROT Application Extension block with color data
 */
function createPaarrotExtension(color: string): Uint8Array {
  const colorBytes = new TextEncoder().encode(color);
  
  // Calculate total size
  // 0x21 + 0xFF + 0x0B + appId(8) + authCode(3) + sub-block(1 + colorBytes.length) + 0x00
  const totalSize = 2 + 1 + 8 + 3 + 1 + colorBytes.length + 1;
  const extension = new Uint8Array(totalSize);
  
  let offset = 0;
  extension[offset++] = 0x21; // Extension Introducer
  extension[offset++] = 0xFF; // Application Extension Label
  extension[offset++] = 0x0B; // Block size (always 11)
  
  // Application Identifier (8 bytes)
  extension.set(PAARROT_APP_ID_BYTES, offset);
  offset += 8;
  
  // Application Authentication Code (3 bytes)
  extension.set(PAARROT_AUTH_CODE, offset);
  offset += 3;
  
  // Data sub-block
  extension[offset++] = colorBytes.length;
  extension.set(colorBytes, offset);
  offset += colorBytes.length;
  
  // Block terminator
  extension[offset] = 0x00;
  
  return extension;
}

/**
 * Extract paarrot color from GIF image data
 */
export function extractColorFromGIF(imageData: ArrayBuffer | Uint8Array): string | undefined {
  const data = imageData instanceof Uint8Array ? imageData : new Uint8Array(imageData);
  
  if (!isGIF(data)) {
    return undefined;
  }
  
  const gctSize = getGlobalColorTableSize(data);
  const startOffset = 13 + gctSize; // Header(6) + LSD(7) + GCT
  
  const extensions = findAppExtensions(data, startOffset);
  
  for (const ext of extensions) {
    if (ext.appId.startsWith('PAARROT')) {
      if (ext.dataBlocks.length > 0) {
        const color = new TextDecoder().decode(ext.dataBlocks[0]);
        return color;
      }
    }
  }
  
  return undefined;
}

/**
 * Embed paarrot color into GIF image data
 */
export function embedColorInGIF(imageData: ArrayBuffer | Uint8Array, color: string): Uint8Array | null {
  const data = imageData instanceof Uint8Array ? imageData : new Uint8Array(imageData);
  
  if (!isGIF(data)) {
    return null;
  }
  
  const gctSize = getGlobalColorTableSize(data);
  const insertOffset = 13 + gctSize; // After Header + LSD + GCT
  
  const extensions = findAppExtensions(data, insertOffset);
  
  // Find existing PAARROT extension
  let existingExt: { offset: number; length: number } | null = null;
  for (const ext of extensions) {
    if (ext.appId.startsWith('PAARROT')) {
      existingExt = { offset: ext.offset, length: ext.length };
      break;
    }
  }
  
  // Create new extension
  const newExtension = createPaarrotExtension(color);
  
  let newData: Uint8Array;
  
  if (existingExt) {
    // Replace existing extension
    newData = new Uint8Array(data.length - existingExt.length + newExtension.length);
    newData.set(data.slice(0, existingExt.offset), 0);
    newData.set(newExtension, existingExt.offset);
    newData.set(
      data.slice(existingExt.offset + existingExt.length),
      existingExt.offset + newExtension.length
    );
  } else {
    // Insert after GCT (before any other blocks)
    newData = new Uint8Array(data.length + newExtension.length);
    newData.set(data.slice(0, insertOffset), 0);
    newData.set(newExtension, insertOffset);
    newData.set(data.slice(insertOffset), insertOffset + newExtension.length);
  }
  
  return newData;
}

/**
 * Remove paarrot color from GIF image data
 */
export function removeColorFromGIF(imageData: ArrayBuffer | Uint8Array): Uint8Array | null {
  const data = imageData instanceof Uint8Array ? imageData : new Uint8Array(imageData);
  
  if (!isGIF(data)) {
    return null;
  }
  
  const gctSize = getGlobalColorTableSize(data);
  const startOffset = 13 + gctSize;
  
  const extensions = findAppExtensions(data, startOffset);
  
  // Find existing PAARROT extension
  let existingExt: { offset: number; length: number } | null = null;
  for (const ext of extensions) {
    if (ext.appId.startsWith('PAARROT')) {
      existingExt = { offset: ext.offset, length: ext.length };
      break;
    }
  }
  
  if (!existingExt) {
    return data;
  }
  
  // Remove the extension
  const newData = new Uint8Array(data.length - existingExt.length);
  newData.set(data.slice(0, existingExt.offset), 0);
  newData.set(
    data.slice(existingExt.offset + existingExt.length),
    existingExt.offset
  );
  
  return newData;
}
