/**
 * verify-pair.mjs — 校验浏览器导出的 HEIC + MOV 是否可配对为 Live Photo
 *
 * 判定原则：**以 exiftool 为权威**。
 * 历史教训：曾经只用自己的解析器按字节提取 UUID，结果显示"配对成功"，
 * 但 exiftool / Photos / Synology Photos 全都读不到——因为 MOV 的 meta box
 * 多写了 4 字节 version/flags，标准解析器把那 4 个 0 当成 "子 box size=0"
 * 立即结束解析（exiftool 报 "Terminator found in Meta"）。
 * 字节里"有"UUID ≠ 解析器"读得到"UUID。所以必须用第三方权威工具验证。
 *
 * 用法: node verify-pair.mjs [heic路径] [mov路径]
 */
import fs from 'node:fs';
import { execFileSync } from 'node:child_process';
import { pathToFileURL } from 'node:url';

const DIR = 'C:\\Users\\Administrator\\WorkBuddy\\2026-09-03-22-27-47';
const ET = DIR + '\\tools\\exiftool-13.59_64\\exiftool.exe';

const heicPath = process.argv[2] || DIR + '\\livephoto-splitter\\test-output\\hw_livephoto.heic';
const movPath = process.argv[3] || DIR + '\\livephoto-splitter\\test-output\\hw_livephoto.mov';

let fail = 0;
const pass = msg => console.log('  ✓ ' + msg);
const bad = msg => { console.log('  ✗ ' + msg); fail++; };

if (!fs.existsSync(heicPath)) { console.error('HEIC 不存在: ' + heicPath); process.exit(1); }
if (!fs.existsSync(movPath)) { console.error('MOV 不存在: ' + movPath); process.exit(1); }

console.log('文件:');
console.log('  HEIC: ' + heicPath);
console.log('  MOV : ' + movPath);

const hasExiftool = fs.existsSync(ET);
if (!hasExiftool) console.log('\n⚠ 未找到 exiftool，跳过权威校验（仅做结构自检）');

/** 用 exiftool 读取指定文件的 ContentIdentifier */
function exiftoolUuid(file) {
  const out = execFileSync(ET, ['-G1', '-a', '-n', '-ContentIdentifier', file], {
    encoding: 'utf8', env: { ...process.env, LC_ALL: 'C', LANG: 'C' }
  }).trim();
  if (!out) return null;
  const m = out.match(/:\s*(\S+)\s*$/);
  return m ? m[1] : null;
}

console.log('\n[1] exiftool 权威校验（第三方解析器能否读到配对标识）');
let heicUuid = null, movUuid = null;
if (hasExiftool) {
  try {
    heicUuid = exiftoolUuid(heicPath);
    heicUuid ? pass('HEIC: ' + heicUuid) : bad('HEIC: exiftool 读不到 ContentIdentifier');
  } catch (e) { bad('HEIC exiftool 执行失败: ' + e.message); }
  try {
    movUuid = exiftoolUuid(movPath);
    movUuid ? pass('MOV : ' + movUuid) : bad('MOV : exiftool 读不到 ContentIdentifier（meta box 结构有问题）');
  } catch (e) { bad('MOV exiftool 执行失败: ' + e.message); }

  if (heicUuid && movUuid) {
    if (heicUuid === movUuid) pass('两侧 ContentIdentifier 一致 → 可配对为 Live Photo');
    else bad('两侧 ContentIdentifier 不一致，无法配对');
  }
}

console.log('\n[2] 自解析结构自检');
const { verifyLivePhotoMov } = await import(pathToFileURL(DIR + '\\livephoto-splitter\\js\\mov.js').href);
const v = verifyLivePhotoMov(new Uint8Array(fs.readFileSync(movPath)));
if (v.ok) {
  pass(`meta box 正常（handler=${v.handler}, key=${v.key}）`);
} else {
  bad('meta box 异常: ' + v.error);
}
if (v.stillImageTime && v.stillImageTime.present) {
  const s = v.stillImageTime;
  if (s.sampleValue === -1) pass(`still-image-time 轨道正常（静态帧 ${s.seconds}s，样本值 -1）`);
  else bad('still-image-time 样本值异常: ' + s.sampleValue);
} else {
  bad('缺少 still-image-time 轨道');
}

console.log('\n[3] 文件格式');
const hb = fs.readFileSync(heicPath), mb = fs.readFileSync(movPath);
const isJpeg = /\.(jpe?g)$/i.test(heicPath);
if (isJpeg) {
  // JPEG：SOI/EOI 标记 + APP1 Exif 段
  const soi = hb[0] === 0xFF && hb[1] === 0xD8;
  const eoi = hb[hb.length - 2] === 0xFF && hb[hb.length - 1] === 0xD9;
  soi && eoi ? pass('JPEG 结构完整（SOI/EOI）') : bad('JPEG 结构损坏');
  // 扫描段列表找 APP1/Exif（APP0-JFIF 通常在它前面，不能假设固定位置）
  let seg = 2, hasExifSeg = false;
  while (seg + 4 <= hb.length && hb[seg] === 0xFF) {
    const m = hb[seg + 1];
    if (m === 0x01 || (m >= 0xD0 && m <= 0xD9)) { seg += 2; continue; }
    if (m === 0xDA) break;
    const len = (hb[seg + 2] << 8) | hb[seg + 3];
    if (m === 0xE1 && hb.toString('latin1', seg + 4, seg + 10) === 'Exif\0\0') { hasExifSeg = true; break; }
    seg += 2 + len;
  }
  hasExifSeg ? pass('含 Exif APP1 段') : bad('缺少 Exif APP1 段');
} else {
  const hBrand = hb.toString('latin1', 8, 12);
  /^(heic|heix|hevc|mif1|msf1)/.test(hBrand)
    ? pass('HEIC ftyp brand = ' + hBrand) : bad('HEIC 品牌异常: ' + hBrand);
}
const mBrand = mb.toString('latin1', 8, 12);
mBrand.startsWith('qt') ? pass('MOV ftyp brand = ' + mBrand) : bad('MOV 品牌异常: ' + mBrand);

console.log('\n' + (fail === 0 ? '结果: 全部通过 ✅' : `结果: ${fail} 项失败 ❌`));
process.exit(fail === 0 ? 0 : 1);
