/**
 * ============================================================================
 * MyClaw Patch Engine
 * ============================================================================
 *
 * 功能：
 *   1. 探测 OpenClaw 安装路径
 *   2. 备份原始 index.html
 *   3. 复制 myclaw-inject.js 到 control-ui 目录
 *   4. 在 index.html 的 </body> 前注入 <script> 标签
 *   5. 幂等：重复执行不会重复注入
 *
 * 使用：
 *   const { patch, unpatch, status } = require('./patch');
 *   patch();     // 执行注入
 *   unpatch();   // 回滚
 *   status();    // 检查状态
 * ============================================================================
 */

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

const INJECT_MARKER = '<!-- myclaw-inject -->';
const INJECT_FILENAME = 'myclaw-inject.js';
const VOICE_SDK_FILENAME = 'voice-input.js';
const VOICE_OUTPUT_SDK_FILENAME = 'voice-output.js';
const TTS_INJECT_FILENAME = 'myclaw-tts.js';
const ARTIFACTS_INJECT_FILENAME = 'myclaw-artifacts.js';
const ITERATION_INJECT_FILENAME = 'myclaw-iteration.js';
const BACKUP_SUFFIX = '.myclaw-backup';

/**
 * 探测 OpenClaw 的 control-ui 目录
 * 按优先级尝试多种路径
 */
function findControlUiDir() {
  const candidates = [];

  // 1. 通过 require.resolve 查找
  const globalPaths = [
    '/usr/lib/node_modules',
    '/usr/local/lib/node_modules',
  ];

  // 加上当前 npm prefix
  try {
    const { execSync } = require('child_process');
    const prefix = execSync('npm config get prefix 2>/dev/null', { encoding: 'utf8' }).trim();
    if (prefix) {
      globalPaths.push(path.join(prefix, 'lib', 'node_modules'));
    }
  } catch {}

  // 加上 pnpm global root（优先用 PNPM_HOME，回退到常见默认路径）
  const os = require('os');
  const pnpmHome = process.env.PNPM_HOME || path.join(os.homedir(), '.local', 'share', 'pnpm');
  // pnpm 内部版本号通常为 4~8，枚举兜底，不依赖 pnpm 命令是否在 PATH
  for (let v = 4; v <= 9; v++) {
    globalPaths.push(path.join(pnpmHome, 'global', String(v), 'node_modules'));
  }
  // 也尝试通过命令动态获取（如果 pnpm 在 PATH 里）
  try {
    const { execSync } = require('child_process');
    const pnpmRoot = execSync('pnpm root -g 2>/dev/null', { encoding: 'utf8' }).trim();
    if (pnpmRoot) globalPaths.push(pnpmRoot);
  } catch {}

  for (const globalPath of globalPaths) {
    candidates.push(path.join(globalPath, 'openclaw', 'dist', 'control-ui'));
  }

  // 2. 通过 which openclaw 推断
  try {
    const { execSync } = require('child_process');
    const bin = execSync('which openclaw 2>/dev/null', { encoding: 'utf8' }).trim();
    if (bin) {
      const real = fs.realpathSync(bin);
      // openclaw.mjs → 包根目录
      const pkgRoot = path.dirname(real);
      candidates.push(path.join(pkgRoot, 'dist', 'control-ui'));
      // 也可能 bin 在 dist/ 下
      candidates.push(path.join(path.dirname(pkgRoot), 'dist', 'control-ui'));
    }
  } catch {}

  // 3. 尝试当前目录（开发用）
  candidates.push(path.join(process.cwd(), 'dist', 'control-ui'));

  // 找到第一个有 index.html 的
  console.log('[myclaw-patch] 🔍 开始探测 control-ui 路径，候选列表:');
  for (const dir of candidates) {
    try {
      const exists = fs.existsSync(path.join(dir, 'index.html'));
      console.log('[myclaw-patch]   ' + (exists ? '✅' : '✗ ') + ' ' + dir);
      if (exists) return dir;
    } catch (e) {
      console.log('[myclaw-patch]   ✗  ' + dir + ' (错误: ' + e.message + ')');
    }
  }

  return null;
}

/**
 * 获取 myclaw 版本号
 */
function getMyclawVersion() {
  try {
    return require(path.join(__dirname, '..', 'package.json')).version;
  } catch {
    return 'unknown';
  }
}

/**
 * 执行注入
 */
function patch() {
  const uiDir = findControlUiDir();
  if (!uiDir) {
    console.log('[myclaw-patch] ❌ 未找到 OpenClaw control-ui 目录，跳过注入');
    console.log('[myclaw-patch]   提示: 确保 openclaw 已全局安装 (npm install -g openclaw)');
    return { success: false, reason: 'control-ui-not-found' };
  }

  const indexPath = path.join(uiDir, 'index.html');
  const backupPath = indexPath + BACKUP_SUFFIX;
  const injectSrc = path.join(__dirname, '..', 'assets', INJECT_FILENAME);
  const injectDest = path.join(uiDir, INJECT_FILENAME);
  const voiceSdkSrc = path.join(__dirname, '..', 'voice-input', VOICE_SDK_FILENAME);
  const voiceSdkDest = path.join(uiDir, VOICE_SDK_FILENAME);
  const voiceOutputSdkSrc = path.join(__dirname, '..', 'voice-output', VOICE_OUTPUT_SDK_FILENAME);
  const voiceOutputSdkDest = path.join(uiDir, VOICE_OUTPUT_SDK_FILENAME);
  const ttsInjectSrc = path.join(__dirname, '..', 'assets', TTS_INJECT_FILENAME);
  const ttsInjectDest = path.join(uiDir, TTS_INJECT_FILENAME);
  const artifactsInjectSrc = path.join(__dirname, '..', 'assets', ARTIFACTS_INJECT_FILENAME);
  const artifactsInjectDest = path.join(uiDir, ARTIFACTS_INJECT_FILENAME);
  const iterationInjectSrc = path.join(__dirname, '..', 'assets', ITERATION_INJECT_FILENAME);
  const iterationInjectDest = path.join(uiDir, ITERATION_INJECT_FILENAME);
  const version = getMyclawVersion();

  console.log('[myclaw-patch] → control-ui: ' + uiDir);
  console.log('[myclaw-patch] → MyClaw v' + version + ' (patch-engine: 2026.04.14)');

  // 1. 备份原始 index.html（仅首次）
  if (!fs.existsSync(backupPath)) {
    try {
      fs.copyFileSync(indexPath, backupPath);
      console.log('[myclaw-patch] ✅ 已备份 index.html');
    } catch (err) {
      console.error('[myclaw-patch] ❌ 备份失败: ' + err.message);
      return { success: false, reason: 'backup-failed' };
    }
  }

  // 2. 复制注入脚本（替换版本号占位符）
  try {
    let injectCode = fs.readFileSync(injectSrc, 'utf8');
    injectCode = injectCode.replace(/__MYCLAW_VERSION__/g, version);
    fs.writeFileSync(injectDest, injectCode, 'utf8');
    console.log('[myclaw-patch] ✅ 注入脚本已复制');
  } catch (err) {
    console.error('[myclaw-patch] ❌ 复制注入脚本失败: ' + err.message);
    return { success: false, reason: 'copy-failed' };
  }

  // 3. 复制 VoiceInput SDK
  try {
    fs.copyFileSync(voiceSdkSrc, voiceSdkDest);
    console.log('[myclaw-patch] ✅ 语音 SDK 已复制');
  } catch (err) {
    console.error('[myclaw-patch] ⚠ 语音 SDK 复制失败 (非致命): ' + err.message);
  }

  // 4. 复制 VoiceOutput SDK（TTS）
  try {
    fs.copyFileSync(voiceOutputSdkSrc, voiceOutputSdkDest);
    console.log('[myclaw-patch] ✅ TTS SDK 已复制');
  } catch (err) {
    console.error('[myclaw-patch] ⚠ TTS SDK 复制失败 (非致命): ' + err.message);
  }

  // 5. 复制 TTS 注入脚本
  try {
    fs.copyFileSync(ttsInjectSrc, ttsInjectDest);
    console.log('[myclaw-patch] ✅ TTS 注入脚本已复制');
  } catch (err) {
    console.error('[myclaw-patch] ⚠ TTS 注入脚本复制失败 (非致命): ' + err.message);
  }

  // 5.5 复制 Artifacts 注入脚本
  try {
    fs.copyFileSync(artifactsInjectSrc, artifactsInjectDest);
    console.log('[myclaw-patch] ✅ Artifacts 注入脚本已复制');
  } catch (err) {
    console.error('[myclaw-patch] ⚠ Artifacts 注入脚本复制失败 (非致命): ' + err.message);
  }

  // 5.6 复制 Iteration 注入脚本
  try {
    fs.copyFileSync(iterationInjectSrc, iterationInjectDest);
    console.log('[myclaw-patch] ✅ Iteration 注入脚本已复制');
  } catch (err) {
    console.error('[myclaw-patch] ⚠ Iteration 注入脚本复制失败 (非致命): ' + err.message);
  }

  // 6. Patch index.html（幂等）
  try {
    let html = fs.readFileSync(indexPath, 'utf8');

    // 先移除旧的注入（如果有）
    const markerRegex = new RegExp(
      INJECT_MARKER + '[\\s\\S]*?(?=</body>)',
      'g'
    );
    html = html.replace(markerRegex, '');

    // 在 </body> 前注入（SDK 先加载，inject 后加载）
    const injection = [
      INJECT_MARKER,
      '<script src="./' + VOICE_OUTPUT_SDK_FILENAME + '"></script>',
      '<script src="./' + VOICE_SDK_FILENAME + '"></script>',
      '<script src="./' + TTS_INJECT_FILENAME + '"></script>',
      '<script src="./' + ARTIFACTS_INJECT_FILENAME + '"></script>',
      '<script src="./' + ITERATION_INJECT_FILENAME + '"></script>',
      '<script src="./' + INJECT_FILENAME + '"></script>',
      '',
    ].join('\n');

    if (html.includes('</body>')) {
      html = html.replace('</body>', injection + '</body>');
    } else {
      html += '\n' + injection;
    }

    fs.writeFileSync(indexPath, html, 'utf8');
    console.log('[myclaw-patch] ✅ index.html 已注入');
  } catch (err) {
    console.error('[myclaw-patch] ❌ 注入 index.html 失败: ' + err.message);
    return { success: false, reason: 'inject-failed' };
  }

  // 7. Patch Permissions-Policy 头（允许麦克风）+ CSP frame-src（允许 iframe 加载外部资源）
  // OpenClaw 在 gateway-cli-*.js 或 server-*.js 中硬编码了 microphone=()，需要改为 microphone=(self)
  // 同时 CSP 中 default-src 'self' 导致 iframe 只能加载同源内容，需要添加 frame-src 允许外部资源
  try {
    const distParent = path.resolve(uiDir, '..');  // dist/
    const distFiles = fs.readdirSync(distParent);
    let patched = false;
    let foundTarget = false;

    for (const f of distFiles) {
      // 兼容多版本：gateway-cli-*.js（旧）、server-*.js（中）、control-ui-*.js（新）
      const isTarget = (f.startsWith('gateway-cli-') || f.startsWith('server-') || f.startsWith('control-ui-')) && f.endsWith('.js');
      if (!isTarget) continue;

      foundTarget = true;
      const filePath = path.join(distParent, f);
      let content = fs.readFileSync(filePath, 'utf8');

      // 检查是否需要 patch（分别检测每项，避免部分已 patch 导致整体跳过）
      const needsMicrophonePatch = content.includes('microphone=()');
      const needsFrameSrc = !content.includes('"frame-src *');
      const needsConnectSrc = !content.includes("connect-src *");
      const needsFrameAncestors = !content.includes("frame-ancestors *");
      const needsAnyCspPatch = needsFrameSrc || needsConnectSrc || needsFrameAncestors;

      if (needsMicrophonePatch || needsAnyCspPatch) {
        // 备份
        const backupFile = filePath + BACKUP_SUFFIX;
        if (!fs.existsSync(backupFile)) {
          fs.copyFileSync(filePath, backupFile);
        }

        // Patch 1: microphone
        if (needsMicrophonePatch) {
          content = content.replace(
            'microphone=()',
            'microphone=(self)'
          );
          console.log('[myclaw-patch] ✅ 已修复 Permissions-Policy (microphone): ' + f);
        }

        // Patch 2: CSP - 全部放宽到最宽松模式
        const cspPatches = [];
        // frame-src → 允许所有
        if (needsFrameSrc) {
          if (content.includes('"frame-src')) {
            content = content.replace(/\"frame-src[^"]*\"/g, '"frame-src *"');
          } else {
            content = content.replace(
              '"default-src \'self\'"',
              '"default-src \'self\'",\n\t\t"frame-src *"'
            );
          }
          cspPatches.push('frame-src *');
        }
        // connect-src → 允许所有（含 http://127.0.0.1 本地 API）
        if (needsConnectSrc) {
          content = content.replace(/\"connect-src[^"]*\"/g, '"connect-src *"');
          cspPatches.push('connect-src *');
        }
        // frame-ancestors → 允许所有
        if (needsFrameAncestors) {
          content = content.replace(/\"frame-ancestors[^"]*\"/g, '"frame-ancestors *"');
          cspPatches.push('frame-ancestors *');
        }
        if (cspPatches.length > 0) {
          console.log('[myclaw-patch] ✅ 已修复 CSP (' + cspPatches.join(', ') + '): ' + f);
        }

        fs.writeFileSync(filePath, content, 'utf8');
        patched = true;
      } else {
        console.log('[myclaw-patch] ✅ Permissions-Policy 和 CSP 已是允许状态 (无需修改): ' + f);
        patched = true;
      }
    }

    if (!patched && foundTarget) {
      console.log('[myclaw-patch] ⚠ 找到目标文件但未检测到 microphone/CSP 配置');
    } else if (!patched) {
      console.log('[myclaw-patch] ⚠ 未找到配置文件，麦克风和 iframe 可能受限');
    }
  } catch (err) {
    console.error('[myclaw-patch] ⚠ Permissions-Policy/CSP 修复失败 (非致命): ' + err.message);
  }

  console.log('[myclaw-patch] ✅ 注入完成，重启 Gateway 后生效');
  return { success: true, uiDir: uiDir, version: version };
}

/**
 * 回滚注入
 */
function unpatch() {
  const uiDir = findControlUiDir();
  if (!uiDir) {
    console.log('[myclaw-patch] ❌ 未找到 OpenClaw control-ui 目录');
    return { success: false };
  }

  const indexPath = path.join(uiDir, 'index.html');
  const backupPath = indexPath + BACKUP_SUFFIX;
  const injectDest = path.join(uiDir, INJECT_FILENAME);
  const voiceSdkDest = path.join(uiDir, VOICE_SDK_FILENAME);
  const voiceOutputSdkDest = path.join(uiDir, VOICE_OUTPUT_SDK_FILENAME);
  const ttsInjectDest = path.join(uiDir, TTS_INJECT_FILENAME);
  const artifactsInjectDest = path.join(uiDir, ARTIFACTS_INJECT_FILENAME);
  const iterationInjectDest = path.join(uiDir, ITERATION_INJECT_FILENAME);

  // 恢复备份
  if (fs.existsSync(backupPath)) {
    fs.copyFileSync(backupPath, indexPath);
    fs.unlinkSync(backupPath);
    console.log('[myclaw-patch] ✅ index.html 已恢复');
  }

  // 删除注入脚本
  if (fs.existsSync(injectDest)) {
    fs.unlinkSync(injectDest);
    console.log('[myclaw-patch] ✅ 注入脚本已删除');
  }

  // 删除语音 SDK
  if (fs.existsSync(voiceSdkDest)) {
    fs.unlinkSync(voiceSdkDest);
    console.log('[myclaw-patch] ✅ 语音 SDK 已删除');
  }

  // 删除 TTS SDK
  if (fs.existsSync(voiceOutputSdkDest)) {
    fs.unlinkSync(voiceOutputSdkDest);
    console.log('[myclaw-patch] ✅ TTS SDK 已删除');
  }

  // 删除 TTS 注入脚本
  if (fs.existsSync(ttsInjectDest)) {
    fs.unlinkSync(ttsInjectDest);
    console.log('[myclaw-patch] ✅ TTS 注入脚本已删除');
  }

  // 删除 Artifacts 注入脚本
  if (fs.existsSync(artifactsInjectDest)) {
    fs.unlinkSync(artifactsInjectDest);
    console.log('[myclaw-patch] ✅ Artifacts 注入脚本已删除');
  }

  // 删除 Iteration 注入脚本
  if (fs.existsSync(iterationInjectDest)) {
    fs.unlinkSync(iterationInjectDest);
    console.log('[myclaw-patch] ✅ Iteration 注入脚本已删除');
  }

  // 恢复 gateway-cli-*.js / server-*.js / control-ui-*.js 的 Permissions-Policy + CSP
  try {
    const distParent = path.resolve(uiDir, '..');
    const distFiles = fs.readdirSync(distParent);
    for (const f of distFiles) {
      if ((f.startsWith('gateway-cli-') || f.startsWith('server-') || f.startsWith('control-ui-')) && f.endsWith('.js' + BACKUP_SUFFIX)) {
        const originalFile = path.join(distParent, f.replace(BACKUP_SUFFIX, ''));
        const backupFile = path.join(distParent, f);
        fs.copyFileSync(backupFile, originalFile);
        fs.unlinkSync(backupFile);
        console.log('[myclaw-patch] ✅ Permissions-Policy 已恢复: ' + f.replace(BACKUP_SUFFIX, ''));
      }
    }
  } catch (err) {
    // 非致命
  }

  console.log('[myclaw-patch] ✅ 回滚完成，重启 Gateway 后生效');
  return { success: true };
}

/**
 * 检查注入状态
 */
function status() {
  const uiDir = findControlUiDir();
  if (!uiDir) {
    return { installed: false, patched: false, message: 'OpenClaw control-ui 未找到' };
  }

  const indexPath = path.join(uiDir, 'index.html');
  const backupPath = indexPath + BACKUP_SUFFIX;

  try {
    const html = fs.readFileSync(indexPath, 'utf8');
    const patched = html.includes(INJECT_MARKER);
    const hasBackup = fs.existsSync(backupPath);
    return {
      installed: true,
      patched: patched,
      hasBackup: hasBackup,
      uiDir: uiDir,
      message: patched ? '已注入' : '未注入',
    };
  } catch {
    return { installed: true, patched: false, message: '无法读取 index.html' };
  }
}

module.exports = { patch, unpatch, status, findControlUiDir };
