/**
 * heic.js — 浏览器端 HEIC 编码（libheif + x265 wasm，来自开源项目 LumaHEIC 的预编译产物）
 *
 * 流程：
 * 1. 加载 wasm 编码器（vendor/apple-hdr-heic.js/.wasm）；
 * 2. encode_apple_hdr_heic(rgba, w, h, gainLuma, gw, gh, quality, headroom, out, len)
 *    传入中性（全黑）增益图 → 输出视觉上无 HDR 效果的普通 HEIC；
 * 3. 二进制手术：定位 HEIC 中 libheif 写入的 Exif item（iinf/iloc），
 *    将其数据替换为「原 JPEG EXIF + Apple MakerNote(ContentIdentifier)」，
 *    新数据追加到文件末尾，就地修改 iloc 的 extent_offset/length。
 */

let modPromise = null;

/** 加载 wasm 模块（懒加载，全局单例） */
export function loadHeicEncoder() {
  if (!modPromise) {
    modPromise = (async () => {
      const createModule = (await import('../vendor/apple-hdr-heic.js')).default;
      const wasmBinary = await (await fetch('../vendor/apple-hdr-heic.wasm')).arrayBuffer();
      const mod = await createModule({ wasmBinary: new Uint8Array(wasmBinary) });
      return mod;
    })();
  }
  return modPromise;
}

/**
 * 将 RGBA 像素编码为 HEIC
 * @param {Uint8Array} rgba
 * @param {number} width
 * @param {number} height
 * @param {number} quality 0-100
 */
export async function encodeRgbaToHeic(rgba, width, height, quality = 90) {
  const mod = await loadHeicEncoder();
  const basePtr = mod._malloc(rgba.length);
  mod.HEAPU8.set(rgba, basePtr);
  // 中性增益图（小尺寸全黑 = 1x 增益，无 HDR 视觉效果）
  const GW = 64, GH = 64;
  const gainPtr = mod._malloc(GW * GH);
  mod.HEAPU8.fill(0, gainPtr, gainPtr + GW * GH);
  const outPtrPtr = mod._malloc(4);
  const outLenPtr = mod._malloc(4);

  let rc;
  try {
    rc = mod._encode_apple_hdr_heic(basePtr, width, height, gainPtr, GW, GH, quality, 1.05, outPtrPtr, outLenPtr, 0);
  } finally {
    mod._free(basePtr);
    mod._free(gainPtr);
  }
  if (rc !== 0) throw new Error(`HEIC 编码失败（错误码 ${rc}）`);

  const H32 = new Uint32Array(mod.HEAPU8.buffer);
  const outPtr = H32[outPtrPtr >> 2];
  const outLen = H32[outLenPtr >> 2];
  const heic = mod.HEAPU8.slice(outPtr, outPtr + outLen);
  mod._free_encoded_buffer(outPtr);
  mod._free(outPtrPtr);
  mod._free(outLenPtr);
  return heic;
}

/* ---------------- HEIC Exif item 替换 ---------------- */

function readU32(b, o) { return ((b[o] << 24) | (b[o + 1] << 16) | (b[o + 2] << 8) | b[o + 3]) >>> 0; }
function writeU32(b, o, v) { b[o] = (v >>> 24) & 255; b[o + 1] = (v >> 16) & 255; b[o + 2] = (v >> 8) & 255; b[o + 3] = v & 255; }

/** 遍历 box */
function boxes(b, start, end) {
  const out = [];
  let off = start;
  while (off + 8 <= end) {
    let size = readU32(b, off);
    const type = String.fromCharCode(b[off + 4], b[off + 5], b[off + 6], b[off + 7]);
    let hdr = 8;
    if (size === 1) {
      size = Number((BigInt(readU32(b, off + 8)) << 32n) | BigInt(readU32(b, off + 12)));
      hdr = 16;
    } else if (size === 0) size = end - off;
    if (size < 8) break;
    out.push({ type, off, size, hdr });
    off += size;
  }
  return out;
}

/**
 * 解析 HEIC meta，返回 Exif item 的 iloc 条目信息
 */
function findExifItem(heic) {
  const top = boxes(heic, 0, heic.length);
  const meta = top.find(c => c.type === 'meta');
  if (!meta) return null;
  const msubs = boxes(heic, meta.off + meta.hdr + 4, meta.off + meta.size);

  // iinf → Exif item id
  const iinf = msubs.find(c => c.type === 'iinf');
  if (!iinf) return null;
  const iv = heic[iinf.off + 8];
  let p = iinf.off + 12;
  let count;
  if (iv === 0) { count = (heic[p] << 8) | heic[p + 1]; p += 2; }
  else { count = readU32(heic, p); p += 4; }
  let exifId = null;
  for (let i = 0; i < count; i++) {
    if (p + 8 > iinf.off + iinf.size) break;
    const s = readU32(heic, p);
    const t = String.fromCharCode(heic[p + 4], heic[p + 5], heic[p + 6], heic[p + 7]);
    if (t === 'infe') {
      const infeVer = heic[p + 8];
      let id, itemType;
      if (infeVer === 2) { id = (heic[p + 12] << 8) | heic[p + 13]; itemType = String.fromCharCode(heic[p + 16], heic[p + 17], heic[p + 18], heic[p + 19]); }
      else if (infeVer === 3) { id = readU32(heic, p + 12); itemType = String.fromCharCode(heic[p + 16], heic[p + 17], heic[p + 18], heic[p + 19]); }
      if (itemType === 'Exif') exifId = id;
    }
    p += s;
  }
  if (exifId === null) return null;

  // iloc → 条目
  const iloc = msubs.find(c => c.type === 'iloc');
  if (!iloc) return null;
  const version = heic[iloc.off + 8];
  const offsetSize = heic[iloc.off + 12] >> 4, lengthSize = heic[iloc.off + 12] & 15;
  const baseOffsetSize = heic[iloc.off + 13] >> 4;
  const indexSize = (version === 1 || version === 2) ? (heic[iloc.off + 13] & 15) : 0;
  let q = iloc.off + 14; // 8(box头)+4(fullbox)+2(size字节) 之后是 item_count
  let itemCount;
  if (version < 2) { itemCount = (heic[q] << 8) | heic[q + 1]; q += 2; }
  else { itemCount = readU32(heic, q); q += 4; }

  function readN(pos, n) { let v = 0; for (let i = 0; i < n; i++) v = v * 256 + heic[pos + i]; return v; }
  function writeN(pos, n, val) { for (let i = n - 1; i >= 0; i--) { heic[pos + i] = val & 255; val = Math.floor(val / 256); } }

  for (let i = 0; i < itemCount; i++) {
    const idPos = q;
    const id = version < 2 ? readN(q, 2) : readN(q, 4);
    q += version < 2 ? 2 : 4;
    q += 2; // data_reference_index
    if (version >= 1) q += 2; // construction_method
    const basePos = q;
    const baseOffset = readN(q, baseOffsetSize);
    q += baseOffsetSize;
    const extentCountPos = q;
    const extentCount = readN(q, 2);
    q += 2;
    if (id === exifId) {
      if (extentCount !== 1) return null; // 仅支持单 extent（libheif 输出均为单 extent）
      // extent: [index(若 indexSize>0)] [offset(offsetSize)] [length(lengthSize)]
      let ep = q;
      if (indexSize) ep += indexSize;
      return {
        heic, id,
        offsetFieldPos: ep, lengthFieldPos: ep + offsetSize,
        offsetSize, lengthSize, basePos, baseOffsetSize,
        extentCountPos,
      };
    }
    for (let e = 0; e < extentCount; e++) {
      if (indexSize) q += indexSize;
      q += offsetSize + lengthSize;
    }
  }
  return null;
}

/* ---------------- ftyp 品牌改写 ---------------- */

/** 取字符串的 4 字节 fourCC 数组 */
function fourCC(s) {
  const e = new TextEncoder().encode(s);
  return [e[0] || 32, e[1] || 32, e[2] || 32, e[3] || 32];
}

/**
 * Apple 兼容的 HEIC ftyp 载荷（36 字节）。
 *
 * 与 iPhone 原生 apple.HEIC 实测一致，也与开源项目 pyheic_struct 的
 * APPLE_BRAND_PAYLOAD 完全相同：
 *   major='heic', minor=0,
 *   compatible=[mif1, MiHB, MiHE, MiPr, miaf, heic, tmap]
 *
 * libheif 默认只写 [mif1, heic, miaf]，缺失 Apple 私有品牌 MiHB / MiHE /
 * MiPr / tmap。Synology Photos 等按品牌嗅探"是否 Apple 设备产出"的索引器
 * 会因此不把文件归入 Live Photo 配对流程，导致只显示成普通 HEIC。
 */
export function buildAppleHeicFtypPayload() {
  return [
    ...fourCC('heic'), 0, 0, 0, 0,                                   // major + minor
    ...fourCC('mif1'), ...fourCC('MiHB'), ...fourCC('MiHE'),
    ...fourCC('MiPr'), ...fourCC('miaf'), ...fourCC('heic'), ...fourCC('tmap'),
  ];
}

/** 把 iloc 中所有 item 的数据偏移整体平移 delta 字节（用于文件头部增删字节后的修正） */
function shiftIlocOffsets(b, delta) {
  if (delta === 0) return;
  const top = boxes(b, 0, b.length);
  const meta = top.find(c => c.type === 'meta');
  if (!meta) return;
  const msubs = boxes(b, meta.off + meta.hdr + 4, meta.off + meta.size);
  const iloc = msubs.find(c => c.type === 'iloc');
  if (!iloc) return;

  const version = b[iloc.off + 8];
  const offsetSize = b[iloc.off + 12] >> 4, lengthSize = b[iloc.off + 12] & 15;
  const baseOffsetSize = b[iloc.off + 13] >> 4;
  const indexSize = (version === 1 || version === 2) ? (b[iloc.off + 13] & 15) : 0;

  let q = iloc.off + 14; // 8(box头) + 4(fullbox) + 2(尺寸字节)
  let itemCount;
  if (version < 2) { itemCount = (b[q] << 8) | b[q + 1]; q += 2; }
  else { itemCount = readU32(b, q); q += 4; }

  function readN(pos, n) { let v = 0; for (let i = 0; i < n; i++) v = v * 256 + b[pos + i]; return v; }
  function writeN(pos, n, val) { for (let i = n - 1; i >= 0; i--) { b[pos + i] = val & 255; val = Math.floor(val / 256); } }

  for (let i = 0; i < itemCount; i++) {
    q += version < 2 ? 2 : 4;      // item_id
    q += 2;                        // data_reference_index
    if (version >= 1) q += 2;      // construction_method
    const basePos = q;
    const base = readN(q, baseOffsetSize);
    q += baseOffsetSize;
    const extentCount = readN(q, 2);
    q += 2;
    for (let e = 0; e < extentCount; e++) {
      let ep = q;
      if (indexSize) ep += indexSize;
      const extOffPos = ep;
      const extOff = readN(ep, offsetSize);
      ep += offsetSize + lengthSize;
      // 实际位置 = base_offset + extent_offset；平移后优先回填到 base_offset
      const total = base + extOff + delta;
      if (baseOffsetSize >= 4) { writeN(basePos, baseOffsetSize, total); writeN(extOffPos, offsetSize, 0); }
      else if (offsetSize >= 4) { writeN(basePos, baseOffsetSize, 0); writeN(extOffPos, offsetSize, total); }
      q = ep;
    }
  }
}

/**
 * 将 HEIC 的 ftyp 盒改写为 Apple 兼容品牌列表。
 *
 * ftyp 位于文件开头，尺寸由 28 → 44 字节会使后续全部内容后移，
 * 因此必须同步修正 iloc 偏移（见 shiftIlocOffsets），否则图像数据读不出来。
 *
 * @param {Uint8Array} heic
 * @returns {Uint8Array} 新 HEIC
 */
export function patchHeicFtypBrand(heic) {
  const top = boxes(heic, 0, heic.length);
  const ftyp = top.find(c => c.type === 'ftyp');
  if (!ftyp || ftyp.off !== 0) throw new Error('HEIC 开头未找到 ftyp box，无法改写品牌');

  const payload = buildAppleHeicFtypPayload();
  const newSize = 8 + payload.length;
  const delta = newSize - ftyp.size;

  const out = new Uint8Array(heic.length + delta);
  writeU32(out, 0, newSize);
  out.set(new Uint8Array(fourCC('ftyp')), 4);
  out.set(new Uint8Array(payload), 8);
  out.set(heic.subarray(ftyp.size), newSize);

  shiftIlocOffsets(out, delta);
  return out;
}

/**
 * 替换 HEIC 的 Exif item 数据为新 TIFF（含 Apple ContentIdentifier）
 * 原理：新数据追加文件末尾，就地改写 iloc 条目的 offset/length（字段宽度不变）
 * @param {Uint8Array} heic
 * @param {Uint8Array} newTiff 新的 EXIF TIFF 字节
 * @returns {Uint8Array} 新 HEIC
 */
export function patchHeicExif(heic, newTiff) {
  const item = findExifItem(heic);
  if (!item) throw new Error('HEIC 中未找到 Exif item，无法注入 Live Photo 元数据');

  // Exif item 载荷 = 4 字节 tiff header 偏移 + TIFF
  const payload = new Uint8Array(4 + newTiff.length);
  payload.set(new Uint8Array([0, 0, 0, 0]), 0);
  payload.set(newTiff, 4);

  const out = new Uint8Array(heic.length + payload.length);
  out.set(heic, 0);
  out.set(payload, heic.length);
  const newOffset = heic.length;

  const { heic: _, basePos, baseOffsetSize, offsetFieldPos, offsetSize, lengthFieldPos, lengthSize } = item;
  // base_offset = 绝对新位置；extent offset = 0；length = 载荷长度
  const w = (pos, n, val) => { for (let i = n - 1; i >= 0; i--) { out[pos + i] = val & 255; val = Math.floor(val / 256); } };
  if (baseOffsetSize >= 4) {
    w(basePos, baseOffsetSize, newOffset);
    w(offsetFieldPos, offsetSize, 0);
  } else {
    // base_offset 字段过窄：把绝对偏移放进 extent offset（要求 offsetSize 足够）
    w(basePos, baseOffsetSize, 0);
    w(offsetFieldPos, offsetSize, newOffset);
  }
  w(lengthFieldPos, lengthSize, payload.length);
  return out;
}

/**
 * 完整流程：RGBA → HEIC（含 Apple ContentIdentifier 的 EXIF）
 */
export async function encodeHeicWithLivePhotoMeta(rgba, width, height, quality, exifTiff, uuid) {
  const { buildExifWithAppleMakerNote } = await import('./exif.js');
  const tiff = buildExifWithAppleMakerNote(exifTiff || null, uuid);
  const heic = await encodeRgbaToHeic(rgba, width, height, quality);
  // 先改写 ftyp 品牌（会使后续内容整体后移），再注入 Exif（追加到文件末尾），
  // 顺序不能颠倒，否则 patchHeicExif 算出的偏移会少算 ftyp 的增量。
  return patchHeicExif(patchHeicFtypBrand(heic), tiff);
}
