#!/bin/bash
# PreToolUse hook: auto-add Bash(<binary>:*) allow rules for frequently used commands.
# Optimized: fast-path grep to skip Node.js when rule already exists.

INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)

if [ -z "$COMMAND" ]; then
  exit 0
fi

BIN=""
for token in $COMMAND; do
  case "$token" in
    *=*) continue ;;
    time|command|env) continue ;;
    '|'|'||'|'&&'|';') break ;;
  esac
  BIN="$token"
  break
done

if [ -z "$BIN" ]; then
  exit 0
fi

if [[ ! "$BIN" =~ ^[a-zA-Z0-9._/-]+$ ]]; then
  exit 0
fi

BIN_BASENAME=$(basename "$BIN")

if [[ "$BIN" =~ ^(rm|sudo|dd|mkfs|shutdown|reboot|halt|poweroff|chown|killall|pkill)$ ]]; then
  exit 0
fi

if [[ "$BIN_BASENAME" =~ ^(bash|sh|zsh|fish|dash|ksh|node|nodejs|bun|deno|python|python3|python2|ruby|perl|php|pwsh|powershell|cmd|wscript|cscript|osascript)$ ]]; then
  exit 0
fi

PROJECT_ROOT="${CLAUDE_PROJECT_DIR:-$(pwd)}"
SETTINGS_LOCAL="$PROJECT_ROOT/.claude/settings.local.json"
USAGE_FILE="$PROJECT_ROOT/.claude/ukit/permission-usage.json"
AUDIT_FILE="$PROJECT_ROOT/.claude/ukit/permission-audit.log"
RULE="Bash(${BIN}:*)"
NOW_UTC=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
MAX_RULES="${CLAUDE_AUTO_ALLOW_MAX_RULES:-150}"

# Fast path: if rule already exists in allow list, just update usage and exit
if [ -f "$SETTINGS_LOCAL" ] && grep -Fq -- "\"$RULE\"" "$SETTINGS_LOCAL" 2>/dev/null; then
  # Only update usage timestamp (lighter than full Node.js logic)
  node -e '
    const fs = require("fs");
    const usagePath = process.argv[1];
    const rule = process.argv[2];
    const nowUtc = process.argv[3];
    if (!fs.existsSync(usagePath)) {
      fs.mkdirSync(require("path").dirname(usagePath), { recursive: true });
      fs.writeFileSync(usagePath, "{}");
    }
    try {
      const usage = JSON.parse(fs.readFileSync(usagePath, "utf8"));
      if (!usage.managedRules) usage.managedRules = {};
      const prev = usage.managedRules[rule] || {};
      usage.managedRules[rule] = { firstSeen: prev.firstSeen || nowUtc, lastSeen: nowUtc };
      fs.writeFileSync(usagePath, JSON.stringify(usage, null, 2) + "\n");
    } catch {}
  ' "$USAGE_FILE" "$RULE" "$NOW_UTC" >/dev/null 2>&1 || true
  exit 0
fi

# Slow path: rule is new, need full Node.js processing
node -e '
const fs = require("fs");
const path = require("path");

const settingsPath = process.argv[1];
const usagePath = process.argv[2];
const auditPath = process.argv[3];
const rule = process.argv[4];
const nowUtc = process.argv[5];
const maxRules = Math.max(1, Number(process.argv[6]) || 150);

function readJson(filePath, fallback) {
  if (!fs.existsSync(filePath)) return fallback;
  try {
    return JSON.parse(fs.readFileSync(filePath, "utf8"));
  } catch {
    return fallback;
  }
}

function appendAudit(event, payload) {
  fs.appendFileSync(auditPath, JSON.stringify({ ts: nowUtc, event, ...payload }) + "\n");
}

const settingsDir = path.dirname(settingsPath);
const usageDir = path.dirname(usagePath);
fs.mkdirSync(settingsDir, { recursive: true });
fs.mkdirSync(usageDir, { recursive: true });

const settings = readJson(settingsPath, {});
if (!settings.permissions || typeof settings.permissions !== "object") {
  settings.permissions = {};
}
if (!Array.isArray(settings.permissions.allow)) {
  settings.permissions.allow = [];
}

const usage = readJson(usagePath, {});
if (!usage.managedRules || typeof usage.managedRules !== "object") {
  usage.managedRules = {};
}

const allow = settings.permissions.allow;
let changed = false;

if (!allow.includes(rule)) {
  allow.push(rule);
  changed = true;
  appendAudit("auto_add", { rule });
}

const prevMeta = usage.managedRules[rule] || {};
usage.managedRules[rule] = {
  firstSeen: prevMeta.firstSeen || nowUtc,
  lastSeen: nowUtc,
};

const managedSorted = Object.entries(usage.managedRules).sort((a, b) => {
  const ta = Date.parse(a[1]?.lastSeen || "") || 0;
  const tb = Date.parse(b[1]?.lastSeen || "") || 0;
  return tb - ta;
});

if (managedSorted.length > maxRules) {
  const evict = managedSorted.slice(maxRules);
  for (const [evictRule] of evict) {
    delete usage.managedRules[evictRule];
    const idx = allow.indexOf(evictRule);
    if (idx !== -1) {
      allow.splice(idx, 1);
      changed = true;
    }
    appendAudit("auto_prune_cap", { rule: evictRule, maxRules });
  }
}

if (changed) {
  fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
}
fs.writeFileSync(usagePath, JSON.stringify(usage, null, 2) + "\n");
' "$SETTINGS_LOCAL" "$USAGE_FILE" "$AUDIT_FILE" "$RULE" "$NOW_UTC" "$MAX_RULES" >/dev/null 2>&1 || true

exit 0
