/**
 * Utilities for reading and writing JPEG metadata (XMP/APP1 segments)
 * Used to embed user color in avatar images
 * 
 * JPEG structure:
 * - Starts with FFD8 (SOI marker)
 * - Contains segments: FF + marker type + length (2 bytes BE) + data
 * - APP1 (FFE1) is used for EXIF/XMP data
 * - Ends with FFD9 (EOI marker)
 */

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

/** JPEG signature bytes */
const JPEG_SOI = new Uint8Array([0xFF, 0xD8]); // Start of Image

/** XMP namespace identifier in APP1 segment */
const XMP_NAMESPACE = 'http://ns.adobe.com/xap/1.0/\0';
const XMP_NAMESPACE_BYTES = new TextEncoder().encode(XMP_NAMESPACE);

/** Custom XMP namespace for paarrot */
const PAARROT_XMP_START = '<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?>';
const PAARROT_XMP_END = '<?xpacket end="w"?>';

/**
 * Check if data is a valid JPEG file
 */
function isJPEG(data: Uint8Array): boolean {
  if (data.length < 2) return false;
  return data[0] === 0xFF && data[1] === 0xD8;
}

/**
 * Read a 16-bit big-endian integer from a buffer
 */
function readUint16BE(data: Uint8Array, offset: number): number {
  return (data[offset] << 8) | data[offset + 1];
}

/**
 * Write a 16-bit big-endian integer
 */
function writeUint16BE(value: number): Uint8Array {
  return new Uint8Array([(value >> 8) & 0xff, value & 0xff]);
}

/**
 * Create XMP data containing the color value
 */
function createXMPData(color: string): string {
  return `${PAARROT_XMP_START}
<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>
${PAARROT_XMP_END}`;
}

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

/**
 * Parse JPEG segments
 */
function parseSegments(data: Uint8Array): Array<{ marker: number; data: Uint8Array; offset: number; length: number }> {
  const segments: Array<{ marker: number; data: Uint8Array; offset: number; length: number }> = [];
  let offset = 2; // Skip SOI marker
  
  while (offset < data.length - 1) {
    // Find next marker (0xFF followed by non-zero, non-0xFF byte)
    if (data[offset] !== 0xFF) {
      offset++;
      continue;
    }
    
    const marker = data[offset + 1];
    
    // Skip padding bytes (0xFF)
    if (marker === 0xFF || marker === 0x00) {
      offset++;
      continue;
    }
    
    // EOI (End of Image) or SOS (Start of Scan) - stop parsing
    if (marker === 0xD9 || marker === 0xDA) {
      break;
    }
    
    // Markers without length field (RST0-RST7, SOI, EOI)
    if ((marker >= 0xD0 && marker <= 0xD7) || marker === 0xD8 || marker === 0xD9) {
      offset += 2;
      continue;
    }
    
    // Read segment length (includes the 2 length bytes)
    const segmentLength = readUint16BE(data, offset + 2);
    const segmentData = data.slice(offset + 4, offset + 2 + segmentLength);
    
    segments.push({
      marker,
      data: segmentData,
      offset,
      length: 2 + segmentLength, // marker (2) + length field + data
    });
    
    offset += 2 + segmentLength;
  }
  
  return segments;
}

/**
 * Check if APP1 segment contains paarrot XMP data
 */
function isPaarrotXMP(segmentData: Uint8Array): boolean {
  // Check for XMP namespace
  if (segmentData.length < XMP_NAMESPACE_BYTES.length) return false;
  
  for (let i = 0; i < XMP_NAMESPACE_BYTES.length; i++) {
    if (segmentData[i] !== XMP_NAMESPACE_BYTES[i]) return false;
  }
  
  const xmpString = new TextDecoder().decode(segmentData.slice(XMP_NAMESPACE_BYTES.length));
  return xmpString.includes('paarrot:color') || xmpString.includes('paarrot.app');
}

/**
 * Extract paarrot color from JPEG image data
 */
export function extractColorFromJPEG(imageData: ArrayBuffer | Uint8Array): string | undefined {
  const data = imageData instanceof Uint8Array ? imageData : new Uint8Array(imageData);
  
  if (!isJPEG(data)) {
    return undefined;
  }
  
  const segments = parseSegments(data);
  
  for (const segment of segments) {
    // APP1 marker (0xE1) is used for XMP
    if (segment.marker === 0xE1) {
      // Check for XMP namespace
      if (segment.data.length >= XMP_NAMESPACE_BYTES.length) {
        let isXMP = true;
        for (let i = 0; i < XMP_NAMESPACE_BYTES.length; i++) {
          if (segment.data[i] !== XMP_NAMESPACE_BYTES[i]) {
            isXMP = false;
            break;
          }
        }
        
        if (isXMP) {
          const xmpString = new TextDecoder().decode(segment.data.slice(XMP_NAMESPACE_BYTES.length));
          const color = extractColorFromXMP(xmpString);
          if (color) {
            return color;
          }
        }
      }
    }
  }
  
  return undefined;
}

/**
 * Embed paarrot color into JPEG image data
 */
export function embedColorInJPEG(imageData: ArrayBuffer | Uint8Array, color: string): Uint8Array | null {
  const data = imageData instanceof Uint8Array ? imageData : new Uint8Array(imageData);
  
  if (!isJPEG(data)) {
    return null;
  }
  
  const segments = parseSegments(data);
  
  // Find existing paarrot XMP segment
  let existingSegment: { offset: number; length: number } | null = null;
  
  for (const segment of segments) {
    if (segment.marker === 0xE1 && isPaarrotXMP(segment.data)) {
      existingSegment = { offset: segment.offset, length: segment.length };
      break;
    }
  }
  
  // Create XMP APP1 segment
  const xmpData = createXMPData(color);
  const xmpBytes = new TextEncoder().encode(xmpData);
  const segmentData = new Uint8Array(XMP_NAMESPACE_BYTES.length + xmpBytes.length);
  segmentData.set(XMP_NAMESPACE_BYTES, 0);
  segmentData.set(xmpBytes, XMP_NAMESPACE_BYTES.length);
  
  // APP1 segment: FF E1 + length (2 bytes) + data
  const segmentLength = 2 + segmentData.length; // length field + data
  const app1Segment = new Uint8Array(4 + segmentData.length);
  app1Segment[0] = 0xFF;
  app1Segment[1] = 0xE1;
  app1Segment.set(writeUint16BE(segmentLength), 2);
  app1Segment.set(segmentData, 4);
  
  let newData: Uint8Array;
  
  if (existingSegment) {
    // Replace existing segment
    newData = new Uint8Array(data.length - existingSegment.length + app1Segment.length);
    newData.set(data.slice(0, existingSegment.offset), 0);
    newData.set(app1Segment, existingSegment.offset);
    newData.set(
      data.slice(existingSegment.offset + existingSegment.length),
      existingSegment.offset + app1Segment.length
    );
  } else {
    // Insert after SOI marker (at offset 2)
    newData = new Uint8Array(data.length + app1Segment.length);
    newData.set(JPEG_SOI, 0);
    newData.set(app1Segment, 2);
    newData.set(data.slice(2), 2 + app1Segment.length);
  }
  
  return newData;
}

/**
 * Remove paarrot color from JPEG image data
 */
export function removeColorFromJPEG(imageData: ArrayBuffer | Uint8Array): Uint8Array | null {
  const data = imageData instanceof Uint8Array ? imageData : new Uint8Array(imageData);
  
  if (!isJPEG(data)) {
    return null;
  }
  
  const segments = parseSegments(data);
  
  // Find existing paarrot XMP segment
  let existingSegment: { offset: number; length: number } | null = null;
  
  for (const segment of segments) {
    if (segment.marker === 0xE1 && isPaarrotXMP(segment.data)) {
      existingSegment = { offset: segment.offset, length: segment.length };
      break;
    }
  }
  
  if (!existingSegment) {
    return data;
  }
  
  // Remove the segment
  const newData = new Uint8Array(data.length - existingSegment.length);
  newData.set(data.slice(0, existingSegment.offset), 0);
  newData.set(
    data.slice(existingSegment.offset + existingSegment.length),
    existingSegment.offset
  );
  
  return newData;
}
