/**
 * commit-quality.js — PreToolUse Hook
 * 攔截硬編碼 Secret：
 *   - Bash  `git commit`：掃 staged diff（保留舊行為）
 *   - Edit / Write / MultiEdit：掃 new_string / content / edits[].new_string
 * 輸出遵循官方 PreToolUse 規格：
 *   permissionDecision: "allow" | "deny"
 */
const { spawnSync } = require('child_process');

const SECRET_PATTERNS = [
  [/sk-[a-zA-Z0-9]{20,}/, 'OpenAI API Key'],
  [/ghp_[a-zA-Z0-9]{36}/, 'GitHub PAT'],
  [/AKIA[A-Z0-9]{16}/, 'AWS Access Key'],
  [/AIza[a-zA-Z0-9_-]{35}/, 'Google API Key'],
  [/\b\d{8,12}:[A-Za-z0-9_-]{35}\b/, 'Telegram Bot Token'],
  [/TELEGRAM[_\s]*(?:BOT[_\s]*)?TOKEN\s*=\s*["']?[^\s"']{20,}/, 'Telegram Token 環境變數'],
  [/GEMINI_API_KEY\s*=\s*["']?[A-Za-z0-9_-]{20,}/, 'Gemini API Key'],
  [/sk-ant-api[0-9a-zA-Z_-]{20,}/, 'Anthropic API Key'],
  [/glpat-[a-zA-Z0-9_-]{20}/, 'Gitea/GitLab PAT'],
  [/GITEA[_\s]*TOKEN\s*=\s*["']?[^\s"']{20,}/, 'Gitea Token 環境變數'],
  // Python fallback default: os.getenv('X', '<secret ≥30 chars>')
  [/getenv\s*\(\s*[^,)]+,\s*['"][^'"\s]{30,}['"]\s*\)/, 'Python getenv fallback 含疑似機密'],
  [/os\.environ\.get\s*\(\s*[^,)]+,\s*['"][^'"\s]{30,}['"]\s*\)/, 'os.environ.get fallback 含疑似機密'],
];

function scan(text, fileHint) {
  if (!text) return [];
  const hits = [];
  for (const [pat, label] of SECRET_PATTERNS) {
    const m = text.match(pat);
    if (m) hits.push(`${label}${fileHint ? ` in ${fileHint}` : ''}: ${m[0].slice(0, 24)}...`);
  }
  // debugger 只對 JS 家族
  if (fileHint && /\.(js|jsx|ts|tsx)$/.test(fileHint) && /\bdebugger\b/.test(text)) {
    hits.push(`debugger statement in ${fileHint}`);
  }
  return hits;
}

function allow() {
  // 預設 allow：輸出官方規格 JSON
  const out = {
    hookSpecificOutput: {
      hookEventName: 'PreToolUse',
      permissionDecision: 'allow',
    },
  };
  process.stdout.write(JSON.stringify(out));
  process.exit(0);
}

function deny(reasons) {
  const reason = `[commit-quality] 偵測到機密或禁用內容，已阻擋：\n- ${reasons.join('\n- ')}\n請移除後重試。`;
  process.stderr.write(reason + '\n');
  const out = {
    hookSpecificOutput: {
      hookEventName: 'PreToolUse',
      permissionDecision: 'deny',
      permissionDecisionReason: reason,
    },
  };
  process.stdout.write(JSON.stringify(out));
  process.exit(0);
}

let d = '';
process.stdin.on('data', c => d += c);
process.stdin.on('end', () => {
  try {
    const i = JSON.parse(d || '{}');
    const tool = i.tool_name || '';
    const ti = i.tool_input || {};
    const hits = [];

    if (tool === 'Bash') {
      const cmd = ti.command || '';
      if (!/git\s+commit/.test(cmd) || /--amend/.test(cmd)) return allow();

      const r = spawnSync('git', ['diff', '--cached', '--name-only', '--diff-filter=ACMR'], { encoding: 'utf8' });
      const files = (r.stdout || '').trim().split('\n').filter(Boolean);
      for (const f of files) {
        if (!/\.(js|jsx|ts|tsx|py|sh|json|yaml|yml|env|toml|ini|conf)$/.test(f)) continue;
        const cr = spawnSync('git', ['show', ':' + f], { encoding: 'utf8' });
        hits.push(...scan(cr.stdout || '', f));
      }
    } else if (tool === 'Write') {
      hits.push(...scan(ti.content || '', ti.file_path));
    } else if (tool === 'Edit') {
      hits.push(...scan(ti.new_string || '', ti.file_path));
    } else if (tool === 'MultiEdit') {
      const edits = Array.isArray(ti.edits) ? ti.edits : [];
      for (const e of edits) hits.push(...scan(e.new_string || '', ti.file_path));
    } else {
      return allow();
    }

    if (hits.length) return deny(hits);
    return allow();
  } catch (e) {
    process.stderr.write(`[commit-quality] hook internal error: ${e.message}\n`);
    // 內部錯誤不阻擋，避免誤殺
    return allow();
  }
});
