/**
 * 光湖视频AI系统 · 自动剪辑控制层
 * D140 · 铸渊 ICE-GL-ZY001 · 2026-06-23
 *
 * 职责: 解析分镜脚本 → 选镜头 → 生成时间线 → 调度追踪/分轨/剪辑 → 成片
 *
 * 这是视频AI系统的"导演"——不直接处理像素和音频，
 * 而是协调 planar-tracker / audio-stem-splitter / video-editor 三个引擎。
 *
 * 流水线:
 *   1. 解析分镜脚本 (HLDP分镜格式 / JSON)
 *   2. 对每镜执行 OpenCV 追踪 → 导出运动数据
 *   3. 对源音频执行分轨 → 对白/BGM/SFX三轨
 *   4. 基于脚本+运动数据生成剪辑时间线
 *   5. 调用 video-editor 完成最终合成
 *
 * 使用方式:
 *   const { autoCut } = require('./auto-cut-director');
 *   const result = await autoCut({
 *     script: './project/storyboard.json',
 *     shots: './project/shots/',        // 镜头素材目录
 *     audio: './project/audio.wav',     // 可选·源音频
 *     output: './output/final.mp4',
 *   });
 *
 * 铸渊 ICE-GL-ZY001 · D140
 */

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

// 引入现有引擎
const videoEditor = require('./video-editor');
const { splitAudioStems, suggestMixLevels } = require('./audio-stem-splitter');

const PYTHON = process.env.PYTHON_BIN || process.env.PYTHON || 'python3';
const TRACKER_SCRIPT = path.resolve(__dirname, 'planar-tracker.py');

// 输出路径
const JZAO_VIDEO_ROOT = '/Volumes/JZAO/铸渊-ICE-GL-ZY001/OUT-输出/视频';
const DEFAULT_OUTPUT = fs.existsSync(JZAO_VIDEO_ROOT) ? JZAO_VIDEO_ROOT : path.resolve(__dirname, '../outputs');

// ==================== 主入口 ====================

/**
 * 自动剪辑 — 从分镜脚本到成片的完整流水线
 *
 * @param {object} opts
 * @param {string|object} opts.script - 分镜脚本路径(HLDP/JSON) 或已解析的脚本对象
 * @param {string} opts.shots - 镜头素材目录
 * @param {string} [opts.audio] - 源音频文件（视频的原声）
 * @param {string} [opts.voiceover] - 配音文件（外部录制的人声）
 * @param {string} [opts.bgm] - BGM文件
 * @param {string} [opts.subtitle] - SRT字幕文件
 * @param {string} [opts.output] - 输出路径
 * @param {object} [opts.resolution] - {w, h} 默认1920x1080
 * @param {number} [opts.fps=24] - 帧率
 * @param {boolean} [opts.stabilize=false] - 是否对每镜做稳定化
 * @param {boolean} [opts.autoMix=true] - 是否自动混音
 * @returns {Promise<{outputPath: string, duration: number, timeline: array, stemsMeta: object}>}
 */
async function autoCut(opts) {
  const {
    script: scriptInput,
    shots: shotsDir,
    audio: sourceAudio,
    voiceover,
    bgm: externalBgm,
    subtitle,
    output: outputArg,
    resolution = { w: 1920, h: 1080 },
    fps = 24,
    stabilize = false,
    autoMix = true,
  } = opts;

  console.log('[Director] ═══ 自动剪辑流水线启动 ═══');

  // ── Step 0: 解析分镜脚本 ──
  console.log('[Director] 0/6 解析分镜脚本...');
  const script = typeof scriptInput === 'string' ? loadScript(scriptInput) : scriptInput;
  const timeline_spec = buildTimelineSpec(script);
  console.log(`[Director]   分镜数: ${timeline_spec.length}`);

  // ── Step 1: 匹配镜头文件 ──
  console.log('[Director] 1/6 匹配镜头素材...');
  const matched = matchShots(timeline_spec, shotsDir);
  const missing = matched.filter(m => !m.file);
  if (missing.length > 0) {
    console.warn(`[Director] ⚠️ ${missing.length}个镜未找到素材:`);
    missing.forEach(m => console.warn(`    镜${m.index}: ${m.shotId || m.description}`));
  }
  const available = matched.filter(m => m.file);
  console.log(`[Director]   可用: ${available.length}/${matched.length}`);

  // ── Step 2: OpenCV追踪（可选·生成运动数据）──
  console.log('[Director] 2/6 平面追踪分析...');
  const workDir = path.join(DEFAULT_OUTPUT, `.director-${Date.now()}`);
  fs.mkdirSync(workDir, { recursive: true });

  for (const shot of available) {
    if (stabilize) {
      console.log(`  [Tracker] 稳定化: ${path.basename(shot.file)}`);
      const stabilizedPath = path.join(workDir, `stab_${path.basename(shot.file)}`);
      await runPython(TRACKER_SCRIPT, ['stabilize', shot.file, '-o', stabilizedPath]);
      if (fs.existsSync(stabilizedPath)) {
        shot.originalFile = shot.file;
        shot.file = stabilizedPath;
      }
    }

    // 追踪运动数据（用于生成keyframes）
    const trackJson = path.join(workDir, `track_${shot.index}.json`);
    await runPython(TRACKER_SCRIPT, ['track', shot.file, '-o', trackJson]);

    if (fs.existsSync(trackJson)) {
      const motionJson = path.join(workDir, `motion_${shot.index}.json`);
      await runPython(TRACKER_SCRIPT, ['motion_data', shot.file, '--track-json', trackJson, '-o', motionJson]);

      if (fs.existsSync(motionJson)) {
        const motionData = JSON.parse(fs.readFileSync(motionJson, 'utf8'));
        shot.motionData = motionData;
        shot.keyframes = motionData.keyframes || {};
        console.log(`  [Tracker] 镜${shot.index}: 稳定性=${motionData.stability?.toFixed(2) || '?'} 强度=${motionData.motion_intensity || '?'}`);
      }
    }
  }

  // ── Step 3: 音频分轨（如果有源音频）──
  console.log('[Director] 3/6 音频分轨...');
  let stemsMeta = null;
  let mixParams = { voiceVolume: 1.0, bgmVolume: 0.3, sfxVolume: 0.5 };

  if (sourceAudio && fs.existsSync(sourceAudio)) {
    const stemsDir = path.join(workDir, 'stems');
    const stems = await splitAudioStems(sourceAudio, stemsDir);

    if (autoMix) {
      const suggestion = suggestMixLevels(stems.voice, stems.bgm, stems.sfx);
      mixParams = {
        voiceVolume: suggestion.voiceVolume,
        bgmVolume: suggestion.bgmVolume,
        sfxVolume: suggestion.sfxVolume,
      };
      console.log(`  [Audio] 自动混音: voice=${mixParams.voiceVolume} bgm=${mixParams.bgmVolume} sfx=${mixParams.sfxVolume}`);
      suggestion.reasoning.forEach(r => console.log(`    → ${r}`));
    }

    stemsMeta = stems;
  }

  // ── Step 4: 构建剪辑时间线 ──
  console.log('[Director] 4/6 构建剪辑时间线...');
  const timeline = buildEditorTimeline(available, { fps, resolution });

  // ── Step 5: 构建音频参数 ──
  console.log('[Director] 5/6 构建音频参数...');
  const audioConfig = buildAudioConfig({
    voiceover,
    sourceVoice: stemsMeta?.voice,
    externalBgm,
    sourceBgm: stemsMeta?.bgm,
    sourceSfx: stemsMeta?.sfx,
    mixParams,
  });

  // ── Step 6: 调用 video-editor 合成 ──
  console.log('[Director] 6/6 调用剪辑引擎合成...');
  const outputPath = outputArg || path.join(DEFAULT_OUTPUT, `autocut-${Date.now()}.mp4`);

  const editorResult = await videoEditor.edit({
    timeline,
    audio: audioConfig,
    subtitle,
    resolution,
    fps,
    output: outputPath,
  });

  // 清理工作目录
  try {
    fs.rmSync(workDir, { recursive: true });
  } catch (_) {}

  console.log(`[Director] ═══ 自动剪辑完成 ═══`);
  console.log(`[Director] 成片: ${editorResult.outputPath}`);
  console.log(`[Director] 时长: ${editorResult.duration?.toFixed(1)}s`);

  return {
    outputPath: editorResult.outputPath,
    duration: editorResult.duration,
    size: editorResult.size,
    timeline,
    stemsMeta,
    mixParams,
  };
}

// ==================== 分镜脚本解析 ====================

/**
 * 加载分镜脚本
 * 支持: JSON文件 / HLDP格式（简化的key:value解析）
 */
function loadScript(scriptPath) {
  if (!fs.existsSync(scriptPath)) {
    throw new Error(`分镜脚本不存在: ${scriptPath}`);
  }

  const content = fs.readFileSync(scriptPath, 'utf8');
  const ext = path.extname(scriptPath).toLowerCase();

  if (ext === '.json') {
    return JSON.parse(content);
  }

  // HLDP/文本格式解析: 每行 "key: value" 或 "field = value"
  const lines = content.split('\n');
  const result = { shots: [] };
  let currentShot = null;

  for (const line of lines) {
    const trimmed = line.trim();
    if (!trimmed || trimmed.startsWith('#') || trimmed.startsWith('>') || trimmed.startsWith('⊢')) continue;

    // 镜头分隔
    if (trimmed.match(/^(shot|镜|分镜)\s*[:：]/i) || trimmed.match(/^---\s*shot/i)) {
      if (currentShot) result.shots.push(currentShot);
      currentShot = { index: result.shots.length + 1 };
      continue;
    }

    // key: value 格式
    const match = trimmed.match(/^(\w+)\s*[:：=]\s*(.+)$/);
    if (match && currentShot) {
      const key = match[1].toLowerCase();
      const value = match[2].trim();
      currentShot[key] = value;
    } else if (match) {
      result[match[1].toLowerCase()] = match[2].trim();
    }
  }

  if (currentShot) result.shots.push(currentShot);
  return result;
}

/**
 * 从脚本中提取时间线规格
 */
function buildTimelineSpec(script) {
  const shots = script.shots || script.timeline || [];
  return shots.map((s, i) => ({
    index: i + 1,
    shotId: s.id || s.shotId || s.shot_id || `shot${String(i + 1).padStart(2, '0')}`,
    description: s.description || s.desc || s.prompt || '',
    duration: parseFloat(s.duration || s.trim || s.dur || 5),
    transition: s.transition || 'fade',
    file: s.file || s.path || s.video || null,
  }));
}

// ==================== 镜头匹配 ====================

/**
 * 将时间线规格与素材目录中的文件匹配
 */
function matchShots(timelineSpec, shotsDir) {
  if (!fs.existsSync(shotsDir)) {
    console.warn(`[Director] 素材目录不存在: ${shotsDir}`);
    return timelineSpec.map(t => ({ ...t, file: null }));
  }

  // 列出目录中所有视频文件
  const videoExts = ['.mp4', '.mov', '.avi', '.mkv', '.webm'];
  const files = fs.readdirSync(shotsDir)
    .filter(f => videoExts.includes(path.extname(f).toLowerCase()))
    .sort();

  return timelineSpec.map(spec => {
    let matchedFile = null;

    // 1. 直接指定文件
    if (spec.file && fs.existsSync(path.resolve(shotsDir, spec.file))) {
      matchedFile = path.resolve(shotsDir, spec.file);
    }

    // 2. 按shotId匹配
    if (!matchedFile && spec.shotId) {
      const match = files.find(f =>
        f.toLowerCase().includes(spec.shotId.toLowerCase()) ||
        f.toLowerCase().includes(spec.shotId.toLowerCase().replace('shot', ''))
      );
      if (match) matchedFile = path.resolve(shotsDir, match);
    }

    // 3. 按序号匹配
    if (!matchedFile) {
      const numMatch = files.find(f => {
        const nums = f.match(/\d+/g);
        return nums && nums.some(n => parseInt(n) === spec.index);
      });
      if (numMatch) matchedFile = path.resolve(shotsDir, numMatch);
    }

    // 4. 按顺序匹配（如果文件数与镜数一致）
    if (!matchedFile && spec.index <= files.length) {
      matchedFile = path.resolve(shotsDir, files[spec.index - 1]);
    }

    return { ...spec, file: matchedFile };
  });
}

// ==================== 时间线构建 ====================

function buildEditorTimeline(matchedShots, { fps, resolution }) {
  return matchedShots
    .filter(s => s.file)
    .map(s => {
      const entry = {
        shot: s.file,
        trim: s.duration || 5,
        transition: s.transition || 'fade',
      };

      // 注入运动数据生成的keyframes
      if (s.keyframes && Object.keys(s.keyframes).length > 0) {
        entry.keyframes = s.keyframes;
      }

      // 如果运动强度高但稳定化未开启，加入轻微缩放补偿
      if (s.motionData && s.motionData.stability < 0.5 && !entry.keyframes) {
        entry.keyframes = {
          zoom: { from: 1.05, to: 1.05 },  // 固定5%放大，补偿抖动
        };
      }

      return entry;
    });
}

// ==================== 音频配置构建 ====================

function buildAudioConfig({
  voiceover, sourceVoice, externalBgm, sourceBgm, sourceSfx, mixParams,
}) {
  const config = {
    voice: voiceover || sourceVoice || null,
    bgm: externalBgm || sourceBgm || null,
    sfx: [],
    bgmVolume: mixParams.bgmVolume ?? 0.3,
    voiceVolume: mixParams.voiceVolume ?? 1.0,
  };

  // SFX: 如果有分轨的音效轨，作为单个sfx加入
  if (sourceSfx && fs.existsSync(sourceSfx)) {
    config.sfx.push({ at: 0, file: sourceSfx });
  }

  // 如果没有任何音频，返回null
  if (!config.voice && !config.bgm && config.sfx.length === 0) {
    return null;
  }

  return config;
}

// ==================== Python调用工具 ====================

function runPython(script, args, timeout = 120000) {
  return new Promise((resolve, reject) => {
    console.log(`  [Python] ${path.basename(script)} ${args.join(' ')}`);

    const proc = spawnSync(PYTHON, [script, ...args], {
      encoding: 'utf8',
      timeout,
      cwd: path.dirname(script),
    });

    if (proc.status === 0) {
      if (proc.stdout) console.log(proc.stdout.trim());
      resolve(proc.stdout);
    } else {
      const errMsg = proc.stderr || proc.stdout || `退出码 ${proc.status}`;
      console.error(`  [Python] ❌ ${errMsg.substring(0, 300)}`);
      // 不reject——追踪失败不应该中断整个流水线
      resolve(null);
    }
  });
}

// ==================== 导出 ====================

module.exports = {
  autoCut,
  loadScript,
  buildTimelineSpec,
  matchShots,
  buildEditorTimeline,
  buildAudioConfig,
};
