/**
 * Remotion 服务端渲染脚本
 * 用于从命令行渲染视频
 *
 * 使用方式:
 * npx ts-node render.ts --video /path/to/video.mp4 --captions /path/to/captions.json --title "视频标题" --output /path/to/output.mp4
 */

import { bundle } from '@remotion/bundler';
import { renderMedia, selectComposition } from '@remotion/renderer';
import path from 'path';
import fs from 'fs';

interface RenderOptions {
  videoPath: string;
  captionsPath?: string;
  title?: string;
  titleDuration?: number;
  titleDisplayMode?: 'short' | 'persistent';
  subtitleStyle?: Record<string, unknown>;
  titleStyle?: Record<string, unknown>;
  secondaryTitle?: string;
  secondaryTitleStyle?: Record<string, unknown>;
  outputPath: string;
  fps?: number;
  enableSubtitles?: boolean;
  width?: number;
  height?: number;
  concurrency?: number;
}

async function parseArgs(): Promise<RenderOptions> {
  const args = process.argv.slice(2);
  const options: Partial<RenderOptions> = {};

  for (let i = 0; i < args.length; i += 2) {
    const key = args[i].replace('--', '');
    const value = args[i + 1];

    switch (key) {
      case 'video':
        options.videoPath = value;
        break;
      case 'captions':
        options.captionsPath = value;
        break;
      case 'title':
        options.title = value;
        break;
      case 'titleDuration':
        options.titleDuration = parseFloat(value);
        break;
      case 'titleDisplayMode':
        if (value === 'short' || value === 'persistent') {
          options.titleDisplayMode = value;
        }
        break;
      case 'output':
        options.outputPath = value;
        break;
      case 'fps':
        options.fps = parseInt(value, 10);
        break;
      case 'enableSubtitles':
        options.enableSubtitles = value === 'true';
        break;
      case 'subtitleStyle':
        try {
          options.subtitleStyle = JSON.parse(value);
        } catch (e) {
          console.warn('Invalid subtitleStyle JSON');
        }
        break;
      case 'titleStyle':
        try {
          options.titleStyle = JSON.parse(value);
        } catch (e) {
          console.warn('Invalid titleStyle JSON');
        }
        break;
      case 'secondaryTitle':
        options.secondaryTitle = value;
        break;
      case 'secondaryTitleStyle':
        try {
          options.secondaryTitleStyle = JSON.parse(value);
        } catch (e) {
          console.warn('Invalid secondaryTitleStyle JSON');
        }
        break;
      case 'concurrency':
        options.concurrency = parseInt(value, 10);
        break;
    }
  }

  if (!options.videoPath || !options.outputPath) {
    console.error('Usage: npx ts-node render.ts --video <path> --output <path> [--captions <path>] [--title <text>] [--fps <number>]');
    process.exit(1);
  }

  return options as RenderOptions;
}

async function main() {
  const options = await parseArgs();
  const fps = options.fps || 25;

  console.log('Starting Remotion render...');
  console.log('Options:', JSON.stringify(options, null, 2));

  // 读取字幕数据
  let captions = undefined;
  if (options.captionsPath && fs.existsSync(options.captionsPath)) {
    const captionsContent = fs.readFileSync(options.captionsPath, 'utf-8');
    captions = JSON.parse(captionsContent);
    console.log(`Loaded captions with ${captions.segments?.length || 0} segments`);
  }

  // 获取视频时长和尺寸
  let durationInFrames = 300; // 默认 12 秒
  let videoWidth = 1280;
  let videoHeight = 720;
  try {
    // 使用 promisified exec 异步获取视频信息，避免阻塞主线程
    const { promisify } = require('util');
    const { exec } = require('child_process');
    const execAsync = promisify(exec);

    // 获取视频时长
    const { stdout: durationOutput } = await execAsync(
      `ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "${options.videoPath}"`
    );
    const durationInSeconds = parseFloat(durationOutput.trim());
    durationInFrames = Math.ceil(durationInSeconds * fps);
    console.log(`Video duration: ${durationInSeconds}s (${durationInFrames} frames at ${fps}fps)`);

    // 获取视频尺寸
    const { stdout: dimensionsOutput } = await execAsync(
      `ffprobe -v error -select_streams v:0 -show_entries stream=width,height -of csv=s=x:p=0 "${options.videoPath}"`
    );
    const [width, height] = dimensionsOutput.trim().split('x').map(Number);
    if (width && height) {
      videoWidth = width;
      videoHeight = height;
      console.log(`Video dimensions: ${videoWidth}x${videoHeight}`);
    }
  } catch (e) {
    console.warn('Could not get video duration, using default:', e);
  }

  // 设置 publicDir 为视频文件所在目录，使用文件名作为 videoSrc
  const publicDir = path.dirname(path.resolve(options.videoPath));
  const videoFileName = path.basename(options.videoPath);
  console.log(`Public dir: ${publicDir}, Video file: ${videoFileName}`);

  // Bundle the Remotion project
  // 修复: 使用 process.cwd() 解析 src/index.ts，确保在 dist/render.js 和 ts-node 下都能找到
  // 假设脚本总是在 remotion 根目录下运行 (由 python service 保证)
  const entryPoint = path.resolve(process.cwd(), 'src/index.ts');

  // Bundle 缓存逻辑：通过 src 目录 mtime hash 判断是否需要重新打包
  const BUNDLE_CACHE_DIR = path.resolve(process.cwd(), '.remotion-bundle-cache');
  const hashFile = path.join(BUNDLE_CACHE_DIR, '.hash');

  function getSourceHash(): string {
    // 收集 src 目录下所有文件的 mtime 作为缓存 key
    const srcDir = path.resolve(process.cwd(), 'src');
    const mtimes: string[] = [];
    function walkDir(dir: string) {
      for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
        const fullPath = path.join(dir, entry.name);
        if (entry.isDirectory()) {
          walkDir(fullPath);
        } else {
          mtimes.push(`${fullPath}:${fs.statSync(fullPath).mtimeMs}`);
        }
      }
    }
    walkDir(srcDir);
    mtimes.sort();
    return mtimes.join('|');
  }

  const currentHash = getSourceHash();
  let bundleLocation: string;

  // 辅助函数: 确保文件在缓存 public 目录中可访问 (硬链接 > 复制)
  function ensureInCachedPublic(cachedPublicDir: string, srcAbsPath: string, fileName: string) {
    const cachedPath = path.join(cachedPublicDir, fileName);
    // 已存在且大小一致，跳过
    try {
      if (fs.existsSync(cachedPath)) {
        const srcStat = fs.statSync(srcAbsPath);
        const cachedStat = fs.statSync(cachedPath);
        if (srcStat.size === cachedStat.size && srcStat.ino === cachedStat.ino) return;
      }
    } catch { /* file doesn't exist or broken, will recreate */ }
    // 移除旧的文件/链接
    try { fs.unlinkSync(cachedPath); } catch { /* doesn't exist, fine */ }
    // 优先硬链接（零拷贝，对应用透明），跨文件系统时回退为复制
    try {
      fs.linkSync(srcAbsPath, cachedPath);
      console.log(`Hardlinked into cached bundle: ${fileName}`);
    } catch {
      fs.copyFileSync(srcAbsPath, cachedPath);
      console.log(`Copied into cached bundle: ${fileName}`);
    }
  }

  if (fs.existsSync(hashFile) && fs.readFileSync(hashFile, 'utf-8') === currentHash) {
    bundleLocation = BUNDLE_CACHE_DIR;
    console.log('Using cached bundle');
    // 确保当前渲染所需的文件在缓存 bundle 的 public 目录中可访问
    const cachedPublicDir = path.join(BUNDLE_CACHE_DIR, 'public');
    if (!fs.existsSync(cachedPublicDir)) {
      fs.mkdirSync(cachedPublicDir, { recursive: true });
    }
    // 1) 视频文件
    ensureInCachedPublic(cachedPublicDir, path.resolve(options.videoPath), videoFileName);
    // 2) 字体文件 (从 subtitleStyle / titleStyle / secondaryTitleStyle 中提取)
    const styleSources = [options.subtitleStyle, options.titleStyle, options.secondaryTitleStyle];
    for (const style of styleSources) {
      const fontFile = (style as Record<string, unknown>)?.font_file as string | undefined;
      if (fontFile) {
        const fontSrcPath = path.join(publicDir, fontFile);
        if (fs.existsSync(fontSrcPath)) {
          ensureInCachedPublic(cachedPublicDir, path.resolve(fontSrcPath), fontFile);
        }
      }
    }
  } else {
    console.log('Bundling Remotion project...');
    console.log(`Entry point: ${entryPoint}`);
    const freshBundle = await bundle({
      entryPoint,
      webpackOverride: (config) => config,
      publicDir,
    });
    // 复制到缓存目录
    if (fs.existsSync(BUNDLE_CACHE_DIR)) {
      fs.rmSync(BUNDLE_CACHE_DIR, { recursive: true });
    }
    fs.cpSync(freshBundle, BUNDLE_CACHE_DIR, { recursive: true });
    fs.writeFileSync(hashFile, currentHash);
    bundleLocation = BUNDLE_CACHE_DIR;
    console.log('Bundle cached for future use');
  }

  // 统一 inputProps，包含视频尺寸供 calculateMetadata 使用
  const inputProps = {
    videoSrc: videoFileName,
    captions,
    title: options.title,
    titleDuration: options.titleDuration || 4,
    titleDisplayMode: options.titleDisplayMode || 'short',
    subtitleStyle: options.subtitleStyle,
    titleStyle: options.titleStyle,
    secondaryTitle: options.secondaryTitle,
    secondaryTitleStyle: options.secondaryTitleStyle,
    enableSubtitles: options.enableSubtitles !== false,
    width: videoWidth,
    height: videoHeight,
  };

  // Select the composition
  const composition = await selectComposition({
    serveUrl: bundleLocation,
    id: 'ViGentVideo',
    inputProps,
  });

  // Override duration and dimensions (保险：确保与 ffprobe 检测值一致)
  composition.durationInFrames = durationInFrames;
  composition.fps = fps;
  composition.width = videoWidth;
  composition.height = videoHeight;

  // Render the video
  const concurrency = options.concurrency || 4;
  console.log(`Rendering video (concurrency=${concurrency})...`);
  await renderMedia({
    composition,
    serveUrl: bundleLocation,
    codec: 'h264',
    outputLocation: options.outputPath,
    inputProps,
    concurrency,
    onProgress: ({ progress }) => {
      const percent = Math.round(progress * 100);
      process.stdout.write(`\rRendering: ${percent}%`);
    },
  });

  console.log('\nRender complete!');
  console.log(`Output: ${options.outputPath}`);
}

main().catch((err) => {
  console.error('Render failed:', err);
  process.exit(1);
});
