#!/usr/bin/env node
'use strict';

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

const DEFAULT_TARGET = 'bun-linux-x64-baseline';
const DEFAULT_OUTDIR = 'dist-exe';
const DEFAULT_NAME = 'cc-web';

function parseArgs(argv) {
  const options = {
    target: DEFAULT_TARGET,
    outdir: DEFAULT_OUTDIR,
    name: DEFAULT_NAME,
  };

  for (let index = 0; index < argv.length; index += 1) {
    const arg = argv[index];
    const readValue = (name) => {
      const inlinePrefix = `${name}=`;
      if (arg.startsWith(inlinePrefix)) return arg.slice(inlinePrefix.length);
      if (arg === name && index + 1 < argv.length) {
        index += 1;
        return argv[index];
      }
      return null;
    };

    const target = readValue('--target');
    if (target !== null) {
      options.target = target;
      continue;
    }

    const outdir = readValue('--outdir');
    if (outdir !== null) {
      options.outdir = outdir;
      continue;
    }

    const name = readValue('--name');
    if (name !== null) {
      options.name = name;
      continue;
    }

    if (arg === '-h' || arg === '--help') {
      options.help = true;
      continue;
    }

    throw new Error(`未知参数: ${arg}`);
  }

  return options;
}

function printHelp() {
  console.log(`用法:
  npm run build:single-exe
  node scripts/build-single-exe.js --target bun-linux-x64-baseline --outdir dist-exe --name cc-web

说明:
  默认 target 是 ${DEFAULT_TARGET}，用于兼容 CentOS 7 这类老 glibc Linux x64 系统。
  该命令只打包 cc-web 服务本体；Claude/Codex CLI 仍在运行时从宿主机 PATH 或 CLAUDE_PATH/CODEX_PATH 调用。
  构建完成后会同时生成 dist-exe/<target>/ 和 dist-exe/cc-web-<target>.tar.gz。`);
}

function isWindowsTarget(target) {
  return /^bun-windows(?:-|$)/.test(String(target || ''));
}

function resolveBinaryName(target, name) {
  return isWindowsTarget(target) && !name.endsWith('.exe') ? `${name}.exe` : name;
}

function copyFileIfExists(source, destination) {
  if (!fs.existsSync(source)) return false;
  fs.mkdirSync(path.dirname(destination), { recursive: true });
  fs.copyFileSync(source, destination);
  return true;
}

function copyDirIfExists(source, destination) {
  if (!fs.existsSync(source)) return false;
  fs.cpSync(source, destination, { recursive: true, force: true });
  return true;
}

function writeRunningGuide(releaseDir, target, binaryName) {
  const runCommand = isWindowsTarget(target) ? binaryName : `./${binaryName}`;
  const content = `# cc-web single executable 运行说明

这个目录是 Bun single executable 发布包，面向 CentOS 7 / 老 glibc Linux 运行。

## 直接运行

\`\`\`bash
chmod +x ${binaryName}
PORT=8002 CC_WEB_PASSWORD='请改成强密码' ${runCommand}
\`\`\`

## 调用宿主机 Claude/Codex CLI

cc-web 不内置 Claude/Codex CLI。运行时会继续从宿主机 PATH 查找：

\`\`\`bash
export CLAUDE_PATH=/usr/local/bin/claude
export CODEX_PATH=/usr/local/bin/codex
${runCommand}
\`\`\`

如果不设置上述变量，默认命令名仍是 \`claude\` 和 \`codex\`。

## 目录说明

- \`public/\`：前端静态资源
- \`config/\`：运行时配置，首次启动会写入登录和通知配置
- \`sessions/\`：会话数据
- \`logs/\`：运行日志

不要把已有生产环境的 \`config/\`、\`sessions/\` 误删或覆盖。
`;
  fs.writeFileSync(path.join(releaseDir, 'RUNNING.md'), content, 'utf8');
}

function copyRuntimeAssets(projectRoot, releaseDir, target, binaryName) {
  copyDirIfExists(path.join(projectRoot, 'public'), path.join(releaseDir, 'public'));

  for (const file of ['.env.example', 'README.md', 'README.en.md', 'CHANGELOG.md', 'package.json']) {
    copyFileIfExists(path.join(projectRoot, file), path.join(releaseDir, file));
  }

  for (const dir of ['.codex/skills', '.codex/prompts', '.agents/skills', '.agents/prompts']) {
    copyDirIfExists(path.join(projectRoot, dir), path.join(releaseDir, dir));
  }

  for (const dir of ['config', 'sessions', 'logs']) {
    fs.mkdirSync(path.join(releaseDir, dir), { recursive: true });
  }

  writeRunningGuide(releaseDir, target, binaryName);
}

function runBunBuild(projectRoot, target, outfile) {
  const bunCommand = process.versions.bun ? process.execPath : (process.env.BUN_BIN || 'bun');
  const args = [
    'build',
    '--compile',
    '--no-compile-autoload-dotenv',
    `--target=${target}`,
    `--outfile=${outfile}`,
    path.join(projectRoot, 'server.js'),
  ];

  console.log(`[build:single-exe] ${bunCommand} ${args.join(' ')}`);
  const result = spawnSync(bunCommand, args, {
    cwd: projectRoot,
    env: process.env,
    stdio: 'inherit',
  });

  if (result.error && result.error.code === 'ENOENT') {
    throw new Error('未找到 bun。请先在构建机安装 Bun，或通过 BUN_BIN 指定 bun 可执行文件。');
  }
  if (result.error) throw result.error;
  if (result.status !== 0) {
    throw new Error(`Bun 编译失败，退出码: ${result.status}`);
  }
}

function createArchive(outdir, target, name) {
  const archivePath = path.join(outdir, `${name}-${target}.tar.gz`);
  fs.rmSync(archivePath, { force: true });
  const result = spawnSync('tar', ['-C', outdir, '-czf', archivePath, target], {
    stdio: 'inherit',
  });
  if (result.error && result.error.code === 'ENOENT') {
    throw new Error('未找到 tar，无法生成发布压缩包。');
  }
  if (result.error) throw result.error;
  if (result.status !== 0) {
    throw new Error(`生成发布压缩包失败，退出码: ${result.status}`);
  }
  return archivePath;
}

function main() {
  const options = parseArgs(process.argv.slice(2));
  if (options.help) {
    printHelp();
    return;
  }

  const projectRoot = path.resolve(__dirname, '..');
  const target = options.target || DEFAULT_TARGET;
  const binaryName = resolveBinaryName(target, options.name || DEFAULT_NAME);
  const outdir = path.resolve(projectRoot, options.outdir || DEFAULT_OUTDIR);
  const releaseDir = path.join(outdir, target);
  const outfile = path.join(releaseDir, binaryName);

  fs.rmSync(releaseDir, { recursive: true, force: true });
  fs.mkdirSync(releaseDir, { recursive: true });

  runBunBuild(projectRoot, target, outfile);
  if (!isWindowsTarget(target)) fs.chmodSync(outfile, 0o755);
  copyRuntimeAssets(projectRoot, releaseDir, target, binaryName);
  const archivePath = createArchive(outdir, target, options.name || DEFAULT_NAME);

  console.log(`[build:single-exe] 发布目录: ${releaseDir}`);
  console.log(`[build:single-exe] 可执行文件: ${outfile}`);
  console.log(`[build:single-exe] 发布压缩包: ${archivePath}`);
}

try {
  main();
} catch (err) {
  console.error(`[build:single-exe] ERROR: ${err.message || err}`);
  process.exit(1);
}
