'use strict';

// Match a detected approval command against the user's Permission Manager rules
// (the `perm_rules` table — Claude-syntax "Bash(node:*)" allow/deny entries).
//
// These rules otherwise only configure Claude Code's own settings.json, so they
// don't apply to Codex/other providers and the shadow approver never honored
// them. This lets the approver respect the user's explicit allow/deny across ALL
// providers: an allow match → auto-approve (skip the verifier); a deny match →
// escalate. An allow rule marked always_ask is treated as "ask" (no auto-approve).

// Parse a "Tool(spec)" rule into { tool, wildcard|prefix|exact }.
function parseRule(rule) {
  const raw = String(rule || '').trim();
  const m = raw.match(/^([A-Za-z_][\w-]*)\((.*)\)$/);
  if (!m) {
    // Bare tool name (e.g. "Read") = the whole tool is covered.
    return raw ? { tool: raw, wildcard: true } : null;
  }
  const tool = m[1];
  const spec = m[2].trim();
  if (spec === '' || spec === '*') return { tool, wildcard: true };
  if (spec.endsWith(':*')) return { tool, prefix: spec.slice(0, -2).trim() };
  return { tool, exact: spec };
}

// Reduce a displayed tool header ("⏺ Bash command", "Bash(...)", "Edit") to a
// bare tool word ("Bash", "Edit").
function toolOf(toolName) {
  const t = String(toolName || '').replace(/^[^A-Za-z]+/, '').trim();
  return (t.split(/[\s(]/)[0] || '');
}

// Does `command` fall under a parsed rule's spec? Prefix matches require a word
// boundary so "Bash(node:*)" does not match "nodejs ...".
function commandMatchesSpec(command, parsed) {
  if (!parsed) return false;
  if (parsed.wildcard) return true;
  const cmd = String(command || '').trim();
  if (parsed.exact != null) return cmd === parsed.exact;
  const p = parsed.prefix;
  if (!p) return false;
  if (cmd === p) return true;
  if (cmd.startsWith(p)) {
    // Boundary: the next char must not continue a word, so "node" does not match
    // "nodejs" but "node -e require" still matches "node -e require(...)".
    const next = cmd.charAt(p.length);
    return next === '' || !/[A-Za-z0-9_]/.test(next);
  }
  return false;
}

// Evaluate a command against the user's perm_rules.
// Returns { action: 'deny', rule } | { action: 'allow', rule } | null.
// Deny wins over allow. allow rules with always_ask are skipped (the user wants
// to keep being asked).
function matchPermission({ toolName, command } = {}, rules = []) {
  const tool = toolOf(toolName);
  const looksBash = /^bash$/i.test(tool) || /\bbash\b/i.test(String(toolName || ''));
  let allowHit = null;
  for (const r of rules || []) {
    const parsed = parseRule(r.rule);
    if (!parsed) continue;
    const ruleTool = String(parsed.tool || '').toLowerCase();
    const toolMatch = ruleTool === tool.toLowerCase() || (looksBash && ruleTool === 'bash');
    if (!toolMatch) continue;
    if (!commandMatchesSpec(command, parsed)) continue;
    if (r.list_type === 'deny') return { action: 'deny', rule: r.rule };
    if (r.list_type === 'allow' && !(r.always_ask)) {
      if (!allowHit) allowHit = { action: 'allow', rule: r.rule };
    }
  }
  return allowHit;
}

module.exports = { parseRule, toolOf, commandMatchesSpec, matchPermission };
