/**
 * Utilities for reading and writing WebP metadata (custom XMP chunks)
 * Used to embed user color in avatar images
 * 
 * WebP uses RIFF container format:
 * - File starts with "RIFF" (4 bytes) + file size (4 bytes LE) + "WEBP" (4 bytes)
 * - Chunks: chunk ID (4 bytes) + chunk size (4 bytes LE) + chunk data (+ padding byte if odd size)
 */

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

/** RIFF signature bytes */
const RIFF_SIGNATURE = new Uint8Array([0x52, 0x49, 0x46, 0x46]); // "RIFF"
const WEBP_SIGNATURE = new Uint8Array([0x57, 0x45, 0x42, 0x50]); // "WEBP"

/** Key used to store user color in metadata */
export const PAARROT_COLOR_KEY = 'paarrot:color';

/** Custom chunk ID for paarrot color - using XMP_ as it's a standard metadata chunk */
const XMP_CHUNK_ID = new Uint8Array([0x58, 0x4D, 0x50, 0x20]); // "XMP "

/**
 * Read a 32-bit little-endian integer from a buffer
 */
function readUint32LE(data: Uint8Array, offset: number): number {
  return data[offset] | (data[offset + 1] << 8) | (data[offset + 2] << 16) | (data[offset + 3] << 24);
}

/**
 * Write a 32-bit little-endian integer to a buffer
 */
function writeUint32LE(value: number): Uint8Array {
  return new Uint8Array([
    value & 0xff,
    (value >> 8) & 0xff,
    (value >> 16) & 0xff,
    (value >> 24) & 0xff,
  ]);
}

/**
 * Check if data is a valid WebP file
 */
function isWebP(data: Uint8Array): boolean {
  if (data.length < 12) return false;
  
  // Check RIFF signature
  for (let i = 0; i < 4; i++) {
    if (data[i] !== RIFF_SIGNATURE[i]) return false;
  }
  
  // Check WEBP signature at offset 8
  for (let i = 0; i < 4; i++) {
    if (data[8 + i] !== WEBP_SIGNATURE[i]) return false;
  }
  
  return true;
}

/**
 * Create XMP data containing the color value
 */
function createXMPData(color: string): string {
  return `<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?>
<x:xmpmeta xmlns:x="adobe:ns:meta/">
  <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
    <rdf:Description rdf:about="" xmlns:paarrot="http://paarrot.app/ns/1.0/">
      <paarrot:color>${color}</paarrot:color>
    </rdf:Description>
  </rdf:RDF>
</x:xmpmeta>
<?xpacket end="w"?>`;
}

/**
 * Extract color from XMP data
 */
function extractColorFromXMP(xmpData: string): string | undefined {
  // Look for paarrot:color tag
  const match = xmpData.match(/<paarrot:color>([^<]+)<\/paarrot:color>/);
  if (match) {
    return match[1];
  }
  return undefined;
}

/**
 * Parse WebP chunks from image data
 */
function parseChunks(data: Uint8Array): Array<{ id: string; data: Uint8Array; offset: number; length: number }> {
  const chunks: Array<{ id: string; data: Uint8Array; offset: number; length: number }> = [];
  let offset = 12; // Skip RIFF header (4) + size (4) + WEBP (4)
  
  while (offset + 8 <= data.length) {
    const id = new TextDecoder().decode(data.slice(offset, offset + 4));
    const size = readUint32LE(data, offset + 4);
    const chunkData = data.slice(offset + 8, offset + 8 + size);
    
    // Chunk length includes padding byte if size is odd
    const paddedSize = size + (size % 2);
    
    chunks.push({
      id,
      data: chunkData,
      offset,
      length: 8 + paddedSize, // header (8) + data + optional padding
    });
    
    offset += 8 + paddedSize;
  }
  
  return chunks;
}

/**
 * Extract paarrot color from WebP image data
 * @param imageData - Raw WebP image data as ArrayBuffer or Uint8Array
 * @returns The color string if found, or undefined
 */
export function extractColorFromWebP(imageData: ArrayBuffer | Uint8Array): string | undefined {
  const data = imageData instanceof Uint8Array ? imageData : new Uint8Array(imageData);
  
  if (!isWebP(data)) {
    return undefined;
  }
  
  const chunks = parseChunks(data);
  
  for (const chunk of chunks) {
    if (chunk.id === 'XMP ') {
      const xmpString = new TextDecoder().decode(chunk.data);
      const color = extractColorFromXMP(xmpString);
      if (color) {
        return color;
      }
    }
  }
  
  return undefined;
}

/**
 * Embed paarrot color into WebP image data
 * @param imageData - Raw WebP image data as ArrayBuffer or Uint8Array
 * @param color - The color string to embed (e.g., '#ff5500')
 * @returns New WebP data with embedded color, or null if not a valid WebP
 */
export function embedColorInWebP(imageData: ArrayBuffer | Uint8Array, color: string): Uint8Array | null {
  const data = imageData instanceof Uint8Array ? imageData : new Uint8Array(imageData);
  
  if (!isWebP(data)) {
    return null;
  }
  
  const chunks = parseChunks(data);
  
  // Find existing XMP chunk with our color
  let existingXMPChunk: { offset: number; length: number } | null = null;
  
  for (const chunk of chunks) {
    if (chunk.id === 'XMP ') {
      const xmpString = new TextDecoder().decode(chunk.data);
      if (xmpString.includes('paarrot:color') || xmpString.includes('paarrot.app')) {
        existingXMPChunk = { offset: chunk.offset, length: chunk.length };
        break;
      }
    }
  }
  
  // Create XMP chunk with color
  const xmpData = createXMPData(color);
  const xmpBytes = new TextEncoder().encode(xmpData);
  const xmpPaddedSize = xmpBytes.length + (xmpBytes.length % 2); // Add padding byte if odd
  
  // XMP chunk: ID (4) + size (4) + data + optional padding
  const xmpChunk = new Uint8Array(8 + xmpPaddedSize);
  xmpChunk.set(XMP_CHUNK_ID, 0);
  xmpChunk.set(writeUint32LE(xmpBytes.length), 4);
  xmpChunk.set(xmpBytes, 8);
  // Padding byte is already 0 from Uint8Array initialization
  
  let newData: Uint8Array;
  
  if (existingXMPChunk) {
    // Replace existing chunk
    newData = new Uint8Array(data.length - existingXMPChunk.length + xmpChunk.length);
    newData.set(data.slice(0, existingXMPChunk.offset), 0);
    newData.set(xmpChunk, existingXMPChunk.offset);
    newData.set(
      data.slice(existingXMPChunk.offset + existingXMPChunk.length),
      existingXMPChunk.offset + xmpChunk.length
    );
  } else {
    // Insert new chunk at end (before any trailing data)
    newData = new Uint8Array(data.length + xmpChunk.length);
    newData.set(data, 0);
    newData.set(xmpChunk, data.length);
  }
  
  // Update RIFF file size (at offset 4)
  const newFileSize = newData.length - 8; // RIFF size excludes first 8 bytes
  newData.set(writeUint32LE(newFileSize), 4);
  
  return newData;
}

/**
 * Remove paarrot color from WebP image data
 * @param imageData - Raw WebP image data as ArrayBuffer or Uint8Array
 * @returns New WebP data without the color chunk, or null if not a valid WebP
 */
export function removeColorFromWebP(imageData: ArrayBuffer | Uint8Array): Uint8Array | null {
  const data = imageData instanceof Uint8Array ? imageData : new Uint8Array(imageData);
  
  if (!isWebP(data)) {
    return null;
  }
  
  const chunks = parseChunks(data);
  
  // Find existing XMP chunk with our color
  let existingXMPChunk: { offset: number; length: number } | null = null;
  
  for (const chunk of chunks) {
    if (chunk.id === 'XMP ') {
      const xmpString = new TextDecoder().decode(chunk.data);
      if (xmpString.includes('paarrot:color') || xmpString.includes('paarrot.app')) {
        existingXMPChunk = { offset: chunk.offset, length: chunk.length };
        break;
      }
    }
  }
  
  if (!existingXMPChunk) {
    // No color to remove
    return data;
  }
  
  // Build new WebP data without the XMP chunk
  const newData = new Uint8Array(data.length - existingXMPChunk.length);
  newData.set(data.slice(0, existingXMPChunk.offset), 0);
  newData.set(
    data.slice(existingXMPChunk.offset + existingXMPChunk.length),
    existingXMPChunk.offset
  );
  
  // Update RIFF file size
  const newFileSize = newData.length - 8;
  newData.set(writeUint32LE(newFileSize), 4);
  
  return newData;
}

/**
 * Fetch and extract color from a WebP URL
 * @param url - HTTP URL to fetch the WebP from
 * @returns The color string if found, or undefined
 */
export async function fetchAndExtractColorFromWebP(url: string): Promise<string | undefined> {
  try {
    const response = await fetch(url);
    if (!response.ok) return undefined;
    
    const data = await response.arrayBuffer();
    return extractColorFromWebP(data);
  } catch {
    return undefined;
  }
}
