/**
 * ESP32 image format parser.
 *
 * The backend produces a merged 4 MB flash image containing:
 *   offset 0x0000 : empty (0xFF)
 *   offset 0x1000 : bootloader  (ESP32 image format)
 *   offset 0x8000 : partition table
 *   offset 0x10000: application (ESP32 image format)  ← we parse this
 *
 * ESP32 image header (24 bytes, ESP-IDF esp_image_format.h):
 *   +0  magic          0xE9
 *   +1  segment_count
 *   +2  spi_mode
 *   +3  spi_speed_size
 *   +4  entry_addr     uint32 LE
 *   +8  … extended fields …
 *   total: 24 bytes
 *
 * Each segment (8-byte header + data):
 *   +0  load_addr  uint32 LE  — virtual address to load data at
 *   +4  data_len   uint32 LE
 *   +8  data[data_len]
 */

export const ESP32_APP_FLASH_OFFSET = 0x10000;
const ESP32_MAGIC    = 0xE9;
const HEADER_SIZE    = 24;
const SEG_HDR_SIZE   = 8;

export interface Esp32Segment {
  loadAddr: number;
  data: Uint8Array;
}

export interface Esp32ParsedImage {
  entryPoint: number;
  segments: Esp32Segment[];
}

function parseAppAt(img: Uint8Array, base: number): Esp32ParsedImage {
  const view = new DataView(img.buffer, img.byteOffset, img.byteLength);

  if (view.getUint8(base) !== ESP32_MAGIC) {
    throw new Error(
      `Bad ESP32 magic at 0x${base.toString(16)}: ` +
      `expected 0xE9, got 0x${view.getUint8(base).toString(16)}`
    );
  }

  const segCount   = view.getUint8(base + 1);
  const entryPoint = view.getUint32(base + 4, /*littleEndian=*/true);

  const segments: Esp32Segment[] = [];
  let pos = base + HEADER_SIZE;

  for (let i = 0; i < segCount; i++) {
    if (pos + SEG_HDR_SIZE > img.length) break;

    const loadAddr = view.getUint32(pos,     true);
    const dataLen  = view.getUint32(pos + 4, true);
    pos += SEG_HDR_SIZE;

    if (pos + dataLen > img.length) {
      console.warn(`[esp32ImageParser] Segment ${i} data truncated`);
      break;
    }

    segments.push({ loadAddr, data: img.slice(pos, pos + dataLen) });
    pos += dataLen;
  }

  return { entryPoint, segments };
}

/**
 * Parse a merged ESP32 flash image or a raw app binary.
 *
 * Accepts:
 *  - 4 MB merged image (bootloader + partitions + app) — reads app at 0x10000
 *  - Raw app binary (magic 0xE9 at offset 0)
 *
 * Throws if no valid image is found.
 */
export function parseMergedFlashImage(data: Uint8Array): Esp32ParsedImage {
  // Standard merged image: app at offset 0x10000
  if (data.length >= ESP32_APP_FLASH_OFFSET + HEADER_SIZE && data[ESP32_APP_FLASH_OFFSET] === ESP32_MAGIC) {
    return parseAppAt(data, ESP32_APP_FLASH_OFFSET);
  }

  // Fallback: raw app binary (no bootloader prefix)
  if (data.length >= HEADER_SIZE && data[0] === ESP32_MAGIC) {
    return parseAppAt(data, 0);
  }

  throw new Error(
    `No valid ESP32 image magic found ` +
    `(tried offsets 0x10000 and 0x0, image size ${data.length} bytes)`
  );
}
