/**
 * 光湖视频AI系统 · 视频合成引擎
 * D135 · 铸渊 ICE-GL-ZY001
 *
 * 用途: 将多个镜头的 mp4 文件按时间线拼接为成品视频。
 *       支持硬切、溶解过渡、截取、加速、静音轨。
 *       底层调用 FFmpeg，零额外成本。
 *
 * 使用方式:
 *   const { compose } = require('./video-composer');
 *   await compose({
 *     shots: [{ file, trim: 2.5 }, ...],
 *     transition: 'fade',     // 'cut' | 'fade'
 *     fadeDuration: 0.3,
 *     output: '/Volumes/JZAO/.../output.mp4',
 *   });
 */

const { execSync } = require('child_process');
const fs = require('fs');
const path = require('path');

// ==================== 默认输出 ====================
const DEFAULT_OUTPUT_DIR = (() => {
  const jzao = '/Volumes/JZAO/铸渊-ICE-GL-ZY001/OUT-输出/视频';
  return fs.existsSync(jzao) ? jzao : path.resolve(__dirname, '../outputs');
})();

// ==================== 核心 ====================

/**
 * 合成视频
 * @param {object[]} opts.shots - 镜头列表 [{ file, trim: 秒数 }, ...]
 * @param {string} [opts.transition] - 'cut' 硬切(默认) | 'fade' 溶解
 * @param {number} [opts.fadeDuration] - 溶解时长秒数 (默认0.3)
 * @param {string} [opts.output] - 输出路径
 * @param {number} [opts.fps] - 帧率 (默认与首镜相同)
 * @returns {Promise<{outputPath: string, duration: number, size: string}>}
 */
async function compose({ shots, transition = 'fade', fadeDuration = 0.3, output, fps }) {
  if (!shots || shots.length === 0) throw new Error('镜头列表不能为空');
  if (shots.length === 1) {
    const src = shots[0].file;
    const dest = output || path.join(DEFAULT_OUTPUT_DIR, `composed-${Date.now()}.mp4`);
    fs.copyFileSync(src, dest);
    const dur = probeSec(dest);
    return { outputPath: dest, duration: dur, size: fmtSize(dest) };
  }

  const tmpDir = path.join(DEFAULT_OUTPUT_DIR, '.composer-tmp');
  fs.mkdirSync(tmpDir, { recursive: true });

  // Step1: 截取每镜到目标时长，统一帧率和编码
  const trimmed = [];
  for (let i = 0; i < shots.length; i++) {
    const s = shots[i];
    const tFile = path.join(tmpDir, `trim_${i}_${Date.now()}.mp4`);
    const trimDur = s.trim || probeSec(s.file);
    const fpsFlag = fps ? `-r ${fps}` : '';
    execSync(
      `ffmpeg -y -i "${s.file}" -t ${trimDur} ${fpsFlag} -c:v libx264 -preset ultrafast -crf 18 -an "${tFile}" 2>/dev/null`,
      { timeout: 30000 }
    );
    if (!fs.existsSync(tFile)) throw new Error(`截取失败: 镜${i + 1}`);
    trimmed.push({ file: tFile, dur: trimDur });
  }

  // Step2: 拼接
  const finalPath = output || path.join(DEFAULT_OUTPUT_DIR, `composed-${Date.now()}.mp4`);

  if (transition === 'fade' && trimmed.length >= 2) {
    await composeXFade(trimmed, finalPath, fadeDuration);
  } else {
    await composeCut(trimmed, finalPath);
  }

  // Step3: faststart（播放器优化，失败不影响）
  try {
    const tmp = finalPath + '.tmp';
    execSync(`ffmpeg -y -i "${finalPath}" -c copy -movflags +faststart "${tmp}" 2>/dev/null`, { timeout: 15000 });
    if (fs.existsSync(tmp)) { fs.unlinkSync(finalPath); fs.renameSync(tmp, finalPath); }
  } catch (_) { /* faststart is optional */ }

  // Step4: 清理临时文件
  trimmed.forEach(t => { try { fs.unlinkSync(t.file); } catch (_) {} });

  const dur = probeSec(finalPath);
  console.log(`[Composer] ✅ ${finalPath}  ${dur.toFixed(1)}s  ${fmtSize(finalPath)}`);
  return { outputPath: finalPath, duration: dur, size: fmtSize(finalPath) };
}

/**
 * 硬切拼接（无过渡）
 */
function composeCut(trimmed, output) {
  const listFile = path.join(path.dirname(output), '.concat-list.txt');
  const list = trimmed.map(t => `file '${t.file}'`).join('\n');
  fs.writeFileSync(listFile, list);
  execSync(
    `ffmpeg -y -f concat -safe 0 -i "${listFile}" -c copy "${output}" 2>/dev/null`,
    { timeout: 30000 }
  );
  try { fs.unlinkSync(listFile); } catch (_) {}
}

/**
 * 溶解过渡拼接
 */
function composeXFade(trimmed, output, fadeDur) {
  if (trimmed.length === 2) {
    // 两镜：直接 xfade
    execSync(
      `ffmpeg -y -i "${trimmed[0].file}" -i "${trimmed[1].file}" ` +
      `-filter_complex "xfade=transition=fade:duration=${fadeDur}:offset=${(trimmed[0].dur - fadeDur).toFixed(1)}" ` +
      `-c:v libx264 -preset fast -crf 23 -an "${output}" 2>/dev/null`,
      { timeout: 60000 }
    );
    return;
  }

  // 多镜：链式 xfade
  let filterLines = '';
  let prevLabel = '0:v';
  let totalDur = 0;
  const offsets = [];

  for (let i = 0; i < trimmed.length; i++) {
    if (i > 0) {
      const offset = (totalDur - fadeDur).toFixed(1);
      const newLabel = i < trimmed.length - 1 ? `vt${i}` : 'vout';
      filterLines += `[${prevLabel}][${i}:v]xfade=transition=fade:duration=${fadeDur}:offset=${offset}[${newLabel}];\n`;
      prevLabel = newLabel;
      totalDur += trimmed[i].dur - fadeDur;
    } else {
      totalDur = trimmed[i].dur;
    }
  }

  const inputFlags = trimmed.map((_, i) => `-i "${trimmed[i].file}"`).join(' ');
  const fullFilter = filterLines.trim();
  const mapOut = trimmed.length > 2 ? 'vout' : '1:v';

  execSync(
    `ffmpeg -y ${inputFlags} -filter_complex "${fullFilter}" ` +
    `-map "[${prevLabel}]" -c:v libx264 -preset fast -crf 23 -an "${output}" 2>/dev/null`,
    { timeout: 120000 }
  );
}

// ==================== 工具 ====================

function probeSec(file) {
  try {
    const out = execSync(
      `ffprobe -v quiet -show_entries format=duration -of csv=p=0 "${file}"`,
      { timeout: 5000, encoding: 'utf8' }
    );
    return parseFloat(out.trim());
  } catch (_) {
    return 4.0; // fallback
  }
}

function fmtSize(file) {
  try {
    const stat = fs.statSync(file);
    return stat.size < 1048576
      ? `${(stat.size / 1024).toFixed(0)}KB`
      : `${(stat.size / 1048576).toFixed(1)}MB`;
  } catch (_) { return '?'; }
}

module.exports = { compose, probeSec };
