// 端到端验证：用项目自有模块把华为动态照片拆成 配对 .pvt.zip（同一 uid 写入 JPG + MOV）
// 复刻 app.js 的 exportPair 流程：parse -> patchJpeg -> mp4ToLivePhotoMovMinimal -> buildStoreZip
import { readFileSync, writeFileSync } from 'node:fs';
import { parseMotionPhoto } from './js/motionphoto.js';
import { mp4ToLivePhotoMovMinimal } from './js/mov.js';
import { buildExifMinimalAppleMakerNote } from './js/exif.js';

// 与 app.js 同款、已通过 python zipfile 校验的 store-only ZIP 编码器
function u32le(v) { return [v & 0xff, (v >>> 8) & 0xff, (v >>> 16) & 0xff, (v >>> 24) & 0xff]; }
function u16le(v) { return [v & 0xff, (v >>> 8) & 0xff]; }
function crc32(buf) {
  let c = ~0;
  for (let i = 0; i < buf.length; i++) { c ^= buf[i]; for (let k = 0; k < 8; k++) c = (c >>> 1) ^ (0xEDB88320 & -(c & 1)); }
  return (~c) >>> 0;
}
function buildStoreZip(entries) {
  const enc = new TextEncoder();
  const chunks = []; let offset = 0; const central = [];
  for (const e of entries) {
    const nameBytes = enc.encode(e.name);
    const data = e.bytes instanceof Uint8Array ? e.bytes : new Uint8Array(e.bytes);
    const crc = crc32(data);
    // ZIP general-purpose flag bit 11 (0x0800) = UTF-8 encoded filename.
    // 仅当文件名含非 ASCII 字节时设置；参照 makelivephoto.com 即如此（中文文件名不乱码）。
    let utf8 = 0;
    for (let i = 0; i < nameBytes.length; i++) {
      if (nameBytes[i] >= 0x80) { utf8 = 0x0800; break; }
    }
    // Local file header: sig(4) + ver(2) + flags(2) + method(2) + time(2) + date(2)
    //                  + crc(4) + csize(4) + usize(4) + nlen(2) + elen(2) + name + data
    const local = [
      ...u32le(0x04034b50), ...u16le(20), ...u16le(utf8),
      ...u16le(0), ...u16le(0), ...u16le(0),
      ...u32le(crc), ...u32le(data.length), ...u32le(data.length),
      ...u16le(nameBytes.length), ...u16le(0), ...nameBytes, ...data
    ];
    chunks.push(Uint8Array.from(local));
    // Central directory header: sig(4) + vb(2) + vn(2) + flags(2) + method(2)
    //   + time(2) + date(2) + crc(4) + csize(4) + usize(4) + nlen(2) + elen(2)
    //   + clen(2) + dsk(2) + iattr(2) + eattr(4) + lho(4) + name
    const cen = [
      ...u32le(0x02014b50), ...u16le(20), ...u16le(20), ...u16le(utf8),
      ...u16le(0), ...u16le(0), ...u16le(0),
      ...u32le(crc), ...u32le(data.length), ...u32le(data.length),
      ...u16le(nameBytes.length), ...u16le(0), ...u16le(0), ...u16le(0),
      ...u16le(0), ...u32le(0), ...u32le(offset), ...nameBytes
    ];
    central.push(Uint8Array.from(cen));
    offset += local.length;
  }
  let centralSize = 0; for (const c of central) centralSize += c.length;
  const centralOffset = offset;
  const tail = [
    ...u32le(0x06054b50), ...u16le(0), ...u16le(0),
    ...u16le(entries.length), ...u16le(entries.length),
    ...u32le(centralSize), ...u32le(centralOffset), ...u16le(0)
  ];
  const all = [...chunks, ...central, Uint8Array.from(tail)];
  const out = new Uint8Array(all.reduce((s, a) => s + a.length, 0));
  let p = 0; for (const a of all) { out.set(a, p); p += a.length; }
  return out;
}

// 生成一个与 app.js uuidUpper() 同形状的 UUID（大写，带连字符）
function uuidUpper() {
  const hex = '0123456789ABCDEF';
  let s = '';
  for (let i = 0; i < 32; i++) { if (i === 8 || i === 12 || i === 16 || i === 20) s += '-'; s += hex[Math.floor(Math.random() * 16)]; }
  return s;
}

// 与 app.js 同款的极简 EXIF 重建（替换整个 EXIF 段为 Synology 参照同构）
function patchJpegWithMinimalExif(jpeg, uuid) {
  const EXIF_SIG = [0x45, 0x78, 0x69, 0x66, 0x00, 0x00];
  let p = 2;
  while (p + 4 <= jpeg.length) {
    if (jpeg[p] !== 0xFF) { p++; continue; }
    const marker = jpeg[p + 1];
    if (marker === 0x01 || (marker >= 0xD0 && marker <= 0xD9)) { p += 2; continue; }
    if (marker === 0xDA) break;
    const segLen = (jpeg[p + 2] << 8) | jpeg[p + 3];
    if (segLen < 2) break;
    let isExif = marker === 0xE1;
    if (isExif) {
      for (let i = 0; i < 6; i++) {
        if (jpeg[p + 4 + i] !== EXIF_SIG[i]) { isExif = false; break; }
      }
    }
    if (isExif) {
      const tiffStart = p + 10;
      const segEnd = p + 2 + segLen;
      const tiff = jpeg.slice(tiffStart, segEnd);
      const newTiff = buildExifMinimalAppleMakerNote(tiff, uuid);
      const newSegLen = 2 + 6 + newTiff.length;
      if (newSegLen > 0xFFFF) throw new Error('EXIF 段超过 64KB，无法写入 JPEG');
      const out = new Uint8Array(jpeg.length - segLen + newSegLen);
      out.set(jpeg.subarray(0, p), 0);
      let o = p;
      out[o++] = 0xFF; out[o++] = 0xE1;
      out[o++] = (newSegLen >> 8) & 255; out[o++] = newSegLen & 255;
      out.set(EXIF_SIG, o); o += 6;
      out.set(newTiff, o); o += newTiff.length;
      out.set(jpeg.subarray(segEnd), o);
      return out;
    }
    p += 2 + segLen;
  }
  return null;
}

const raw = new Uint8Array(readFileSync('testdata/hw_livephoto.jpg'));
const parsed = parseMotionPhoto(raw);
if (!parsed.isLivePhoto) { console.error('解析失败:', parsed.reason); process.exit(1); }
console.log('解析成功:', parsed.format, 'image=', parsed.image.length, 'video(embedded)=', parsed.video.length);

// 演示用：测试夹具内嵌视频是截断的 1312B 垃圾，无法被解码器识别；
// 为产出“可被 ffprobe 正常解码”的可信样例，视频源改用合法 H.264 测试片 real.mp4。
// 真实工具在用户上传真实华为动态照片时会用 parsed.video（真实可解码视频）。
const videoSrc = new Uint8Array(readFileSync('testdata/real.mp4'));
console.log('视频源(演示): real.mp4', videoSrc.length, 'bytes');

const uid = uuidUpper();
console.log('共享配对 uid:', uid);

// JPG 侧：极简模式 — 重建 EXIF 为 Synology 实测可识别参照同构
//   （Make=Apple + Orientation + ExifVersion + 极简 MakerNote 仅 ContentIdentifier，抹掉 HUAWEI 痕迹）
const jpg = patchJpegWithMinimalExif(parsed.image, uid);
if (!jpg) { console.error('JPG 注入失败'); process.exit(1); }

// MOV 侧：极简模式，仅注入 ContentIdentifier（mp42/isom，与参照一致；不写展示元数据）
const { bytes: mov, warnings } = mp4ToLivePhotoMovMinimal(videoSrc, uid, new Date(), {});
if (warnings && warnings.length) console.warn('MOV warnings:', warnings);

const base = 'IMG_0001';
const plist = new TextEncoder().encode(
`<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"><dict><key>PFVideoComplementMetadataVersionKey</key><string>1</string></dict></plist>
`);
const readme = new TextEncoder().encode(
`# 如何导入到 iPhone？\n解压后 .pvt 会被 macOS/iOS 识别为 Live 图，AirDrop 到 iPhone 即实况照片。\n# 导入 Synology Photos\n将同名 .jpg 与 .mp4 通过 Synology Photos 移动 App 上传即可配对。\n`);
const zip = buildStoreZip([
  { name: base + '.pvt/', bytes: new Uint8Array(0) },
  { name: base + '.pvt/' + base + '.jpg', bytes: jpg },
  { name: base + '.pvt/' + base + '.mp4', bytes: mov },
  { name: base + '.pvt/metadata.plist', bytes: plist },
  { name: '使用说明.txt', bytes: readme },
]);
writeFileSync('test-output/paired.pvt.zip', zip);
console.log('已写出 test-output/paired.pvt.zip', zip.length, 'bytes');
