#!/usr/bin/env node

const fs = require('fs');
const path = require('path');

const SKILL_DIR = path.resolve(__dirname, '..');
const DEFAULT_TEMPLATE = path.join(SKILL_DIR, 'assets', 'template.html');

function usage() {
  console.log(`Usage:
  node skills/audio-driven-video/scripts/create_project.js \\
    --title "项目标题" \\
    --audio "../input/voice.mp3" \\
    --srt path/to/subtitle.srt \\
    --text path/to/transcript.txt \\
    --out path/to/output.html \\
    [--bgm "../input/bgm.mp3" | --no-bgm]`);
}

function parseArgs(argv) {
  const args = {};
  for (let i = 0; i < argv.length; i++) {
    const key = argv[i];
    if (!key.startsWith('--')) continue;
    const name = key.slice(2);
    if (name === 'help') args.help = true;
    else if (name === 'no-bgm') args.noBgm = true;
    else args[name] = argv[++i];
  }
  return args;
}

function fail(message) {
  console.error(`Error: ${message}`);
  process.exit(1);
}

function readText(file) {
  return fs.readFileSync(path.resolve(file), 'utf8').replace(/^\uFEFF/, '');
}

function htmlEscape(value) {
  return String(value)
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;');
}

function attrEscape(value) {
  return htmlEscape(value).replace(/'/g, '&#39;');
}

function secondsFromSrtTime(value) {
  const m = String(value).trim().match(/^(\d{2}):(\d{2}):(\d{2})[,.](\d{3})$/);
  if (!m) fail(`Invalid SRT time: ${value}`);
  return Number(m[1]) * 3600 + Number(m[2]) * 60 + Number(m[3]) + Number(m[4]) / 1000;
}

function parseSrt(srtText) {
  const blocks = srtText.replace(/\r/g, '').split(/\n\s*\n/).filter(Boolean);
  const cues = [];
  for (const block of blocks) {
    const lines = block.split('\n').map((line) => line.trim()).filter(Boolean);
    const timeLine = lines.find((line) => line.includes('-->'));
    if (!timeLine) continue;
    const [start, end] = timeLine.split('-->').map((part) => part.trim());
    const textStart = lines.indexOf(timeLine) + 1;
    const text = lines.slice(textStart).join(' ').trim();
    cues.push({ s: secondsFromSrtTime(start), e: secondsFromSrtTime(end), text });
  }
  if (!cues.length) fail('No cues found in SRT.');
  return cues;
}

function splitTranscript(text) {
  const normalized = text
    .replace(/\r/g, '\n')
    .replace(/[ \t]+/g, ' ')
    .replace(/\n+/g, '\n')
    .trim();
  if (!normalized) return [];
  const parts = normalized.match(/[^。！？!?；;\n]+[。！？!?；;]?/g) || [normalized];
  return parts.map((part) => part.trim()).filter(Boolean);
}

function highlight(text) {
  let safe = htmlEscape(text);
  safe = safe.replace(/(执行|现实|反馈|错误|拒绝|攻击|交易|行动|关键|核心|同步|结果|不确定性|模型)/g, '<span class="sub-key">$1</span>');
  return safe;
}

function mapCueText(srtCues, transcriptParts) {
  const useTranscript = transcriptParts.length >= Math.ceil(srtCues.length * 0.65);
  return srtCues.map((cue, index) => ({
    s: cue.s,
    e: cue.e,
    h: highlight(useTranscript ? (transcriptParts[index] || cue.text) : cue.text),
  }));
}

function trimNumber(value) {
  return Number(value.toFixed(3)).toString();
}

function jsString(value) {
  return JSON.stringify(value);
}

function jsValue(value) {
  if (Array.isArray(value)) return `[${value.map(jsValue).join(', ')}]`;
  if (typeof value === 'number') return trimNumber(value);
  if (typeof value === 'boolean') return value ? 'true' : 'false';
  return jsString(value);
}

function formatCues(cues) {
  return `const CUES = [\n${cues
    .map((cue) => `  { s: ${trimNumber(cue.s)}, e: ${trimNumber(cue.e)}, h: ${jsString(cue.h)} }`)
    .join(',\n')}\n];`;
}

function formatStringArray(name, values) {
  return `const ${name} = [\n${values.map((value) => `  ${jsString(value)}`).join(',\n')}\n];`;
}

function chooseStarts(cues, count) {
  const duration = cues[cues.length - 1].e;
  const starts = cues.map((cue) => cue.s);
  const result = [0];
  let previous = 0;
  for (let i = 1; i < count; i++) {
    const target = (duration * i) / count;
    let best = target;
    let bestDist = Infinity;
    for (const start of starts) {
      if (start <= previous + 1.2) continue;
      const dist = Math.abs(start - target);
      if (dist < bestDist) {
        best = start;
        bestDist = dist;
      }
    }
    best = Math.max(best, previous + 1.2);
    result.push(Number(best.toFixed(3)));
    previous = best;
  }
  return result;
}

function buildScenes(cues, _parts, _title) {
  const count = Math.max(3, Math.min(17, Math.ceil(cues[cues.length - 1].e / 4)));
  const starts = chooseStarts(cues, count);
  const fns = ['sceneBigText', 'sceneTwo'];
  const scenes = starts.map((t, i) => {
    const fn = i === starts.length - 1 ? 'sceneClose' : fns[i % fns.length];
    return { id: `s${i + 1}`, t, fn, cut: i % 3 === 1 ? 'red' : 'blue' };
  });
  const names = scenes.map((_, i) => `SHOT ${String(i + 1).padStart(2, '0')}`);
  return { scenes, names };
}
function formatScenes(scenes) {
  return `const SCENES = [\n${scenes.map((scene) => {
    const parts = Object.entries(scene).map(([key, value]) => {
      if (key === 'fn') return `fn: ${value}`;
      return `${key}: ${jsValue(value)}`;
    });
    return `  { ${parts.join(', ')} }`;
  }).join(',\n')}\n];`;
}

function replaceConstArray(html, name, replacement) {
  const re = new RegExp(`const\\s+${name}\\s*=\\s*\\[[\\s\\S]*?\\];`);
  if (!re.test(html)) fail(`Cannot find ${name} array in template.`);
  return html.replace(re, replacement);
}

function htmlAudioPath(value, outputDir) {
  if (!value) return '';
  if (path.isAbsolute(value)) return path.relative(outputDir, value).replace(/\\/g, '/');
  return value.replace(/\\/g, '/');
}

function main() {
  const args = parseArgs(process.argv.slice(2));
  if (args.help) {
    usage();
    return;
  }
  for (const required of ['title', 'audio', 'srt', 'text', 'out']) {
    if (!args[required]) {
      usage();
      fail(`Missing --${required}`);
    }
  }
  if (!args.noBgm && !args.bgm) fail('Pass --bgm path or --no-bgm explicitly.');

  const outputPath = path.resolve(args.out);
  const outputDir = path.dirname(outputPath);
  const title = args.title;
  const srtCues = parseSrt(readText(args.srt));
  const parts = splitTranscript(readText(args.text));
  const cues = mapCueText(srtCues, parts);
  const audioSrc = htmlAudioPath(args.audio, outputDir);
  const bgmSrc = args.noBgm ? '' : htmlAudioPath(args.bgm, outputDir);
  const { scenes, names } = buildScenes(cues, parts, title);

  let html = fs.readFileSync(args.template ? path.resolve(args.template) : DEFAULT_TEMPLATE, 'utf8');
  html = html.replace('<title>[CHANGE ME] Dynamic Web Video</title>', `<title>${htmlEscape(title)} Dynamic Web Video</title>`);
  html = html.replace('<audio id="audio" src="YOUR_VOICE.mp3" preload="auto"></audio>', `<audio id="audio" src="${attrEscape(audioSrc)}" preload="auto"></audio>`);
  html = html.replace("const PROJECT_TITLE = '00:00';", `const PROJECT_TITLE = ${jsString(title)};`);
  html = html.replace('[项目主标题]<br>[动态网页视频]', `${htmlEscape(title)}<br>动态网页视频`);

  if (args.noBgm) {
    html = html.replace(/\n  <audio id="bgm" preload="auto" loop>[\s\S]*?<\/audio>/, '');
    html = html.replace('const HAS_BGM = true;', 'const HAS_BGM = false;');
  } else {
    html = html.replace(/<source src="YOUR_BGM\.mp3" type="audio\/mpeg">/, `<source src="${attrEscape(bgmSrc)}" type="audio/mpeg">`);
    html = html.replace(/<source src="YOUR_BGM\.wav" type="audio\/wav">/, `<source src="${attrEscape(bgmSrc)}" type="audio/wav">`);
  }

  html = replaceConstArray(html, 'CUES', formatCues(cues));
  html = replaceConstArray(html, 'CHAPTER_NAMES', formatStringArray('CHAPTER_NAMES', names));
  html = replaceConstArray(html, 'SCENES', formatScenes(scenes));

  fs.mkdirSync(outputDir, { recursive: true });
  fs.writeFileSync(outputPath, html, 'utf8');

  console.log(`Created ${path.relative(process.cwd(), outputPath)}`);
  console.log(`Cues: ${cues.length}; Scenes: ${scenes.length}`);
  if (parts.length < Math.ceil(srtCues.length * 0.65)) {
    console.log('Warning: transcript has fewer chunks than SRT cues; some cue text may come from SRT.');
  }
  console.log('★ 这是结构草稿。镜头内容全部为空——必须根据 Step 0 分镜意图表重新设计每个镜头。');
  console.log('Next: run validate_html.js, fill each scene with content-specific imagery.');
}

main();
