#!/usr/bin/env node

import process from 'node:process';

function splitShell(command) {
  const segments = [];
  let current = '';
  let quote = null;
  let escaped = false;

  for (let index = 0; index < command.length; index += 1) {
    const char = command[index];
    const next = command[index + 1];

    if (escaped) {
      current += char;
      escaped = false;
      continue;
    }
    if (char === '\\' && quote !== "'") {
      current += char;
      escaped = true;
      continue;
    }
    if (quote) {
      current += char;
      if (char === quote) quote = null;
      continue;
    }
    if (char === "'" || char === '"' || char === '`') {
      quote = char;
      current += char;
      continue;
    }
    if (char === ';' || char === '\n' || char === '|' || char === '&' || char === '(' || char === ')') {
      if (current.trim()) segments.push(current);
      current = '';
      if ((char === '|' || char === '&') && next === char) index += 1;
      continue;
    }
    current += char;
  }

  if (current.trim()) segments.push(current);
  return segments;
}

function tokenize(segment) {
  const tokens = [];
  let current = '';
  let quote = null;
  let escaped = false;

  for (const char of segment) {
    if (escaped) {
      current += char;
      escaped = false;
      continue;
    }
    if (char === '\\' && quote !== "'") {
      escaped = true;
      continue;
    }
    if (quote) {
      if (char === quote) quote = null;
      else current += char;
      continue;
    }
    if (char === "'" || char === '"' || char === '`') {
      quote = char;
      continue;
    }
    if (/\s/.test(char)) {
      if (current) tokens.push(current);
      current = '';
      continue;
    }
    current += char;
  }

  if (current) tokens.push(current);
  return tokens;
}

function basename(token) {
  return token.split('/').at(-1);
}

function hasMaxTime(args) {
  return args.some((arg) =>
    arg === '--max-time' ||
    arg.startsWith('--max-time=') ||
    arg === '-m' ||
    /^-[^-]*m\d/.test(arg)
  );
}

function commandIndex(tokens) {
  let index = 0;
  const shellKeywords = new Set(['!', 'if', 'then', 'elif', 'else', 'do']);
  const transparentWrappers = new Set(['command', 'builtin', 'exec', 'nohup']);

  while (index < tokens.length) {
    const token = tokens[index];
    const name = basename(token);

    if (shellKeywords.has(token) || /^[A-Za-z_][A-Za-z0-9_]*=/.test(token)) {
      index += 1;
      continue;
    }
    if (transparentWrappers.has(name)) {
      index += 1;
      while (tokens[index]?.startsWith('-')) index += 1;
      continue;
    }
    if (name === 'env') {
      index += 1;
      while (tokens[index]?.startsWith('-') || /^[A-Za-z_][A-Za-z0-9_]*=/.test(tokens[index] ?? '')) index += 1;
      continue;
    }
    if (name === 'timeout') {
      index += 1;
      while (tokens[index]?.startsWith('-')) index += 1;
      if (tokens[index]) index += 1;
      continue;
    }
    return index;
  }

  return -1;
}

function hasUnsafeCurl(command) {
  for (const segment of splitShell(command)) {
    const tokens = tokenize(segment);
    const index = commandIndex(tokens);
    if (index < 0) continue;

    const name = basename(tokens[index]);
    if (name === 'curl' && !hasMaxTime(tokens.slice(index + 1))) return true;
    if (
      ['bash', 'sh', 'zsh', 'dash'].includes(name) &&
      tokens[index + 1] === '-c' &&
      tokens[index + 2] &&
      hasUnsafeCurl(tokens[index + 2])
    ) return true;
    if (name === 'eval' && tokens[index + 1] && hasUnsafeCurl(tokens[index + 1])) return true;
  }
  return false;
}

let input = '';
for await (const chunk of process.stdin) input += chunk;

let command = '';
try {
  command = JSON.parse(input).tool_input?.command ?? '';
} catch {
  process.stdout.write(JSON.stringify({
    hookSpecificOutput: {
      hookEventName: 'PreToolUse',
      permissionDecision: 'deny',
      permissionDecisionReason: 'curl timeout gate could not parse Bash tool input.',
    },
  }));
  process.exit(0);
}

if (!hasUnsafeCurl(command)) process.exit(0);

process.stdout.write(JSON.stringify({
  hookSpecificOutput: {
    hookEventName: 'PreToolUse',
    permissionDecision: 'deny',
    permissionDecisionReason: 'curl requires an explicit --max-time <seconds> or -m <seconds> option.',
  },
}));
