#!/usr/bin/env bash
# Bash PreToolUse gate — routes high-output commands through ft for compression.
# Zero deny rules. Everything either rewrites to ft or passes through.
set -euo pipefail

INPUT=""; IFS= read -rd '' INPUT || true

TOKEN_RE='(find|tree|cat|head|tail|less|more|jq|yq|xmllint|xq|git|npm|pnpm|yarn|bun|npx|jest|vitest|mocha|pytest|phpunit|tsc|curl|wget|httpie|http|journalctl|dmesg|docker|podman|kubectl|ls|cargo|go|node|python|sed|ps|lsof)'

# Fast path: no candidate token appears anywhere in the raw JSON (a superset of
# tool_input.command — normalization strips wrappers, never adds command names),
# so no detection below can match. Skip the jq spawn entirely.
[[ $INPUT =~ $TOKEN_RE ]] || exit 0

CMD=$(printf '%s' "$INPUT" | jq -r '.tool_input.command // empty')
[[ -z "$CMD" ]] && exit 0

# Fast path: no candidate token appears anywhere in the raw command (raw is a
# superset of the normalized form — normalization strips wrappers, never adds
# command names), so no detection below can match. Skip all perl work.
if ! [[ "$CMD" =~ $TOKEN_RE ]]; then
  exit 0
fi

# ft binary must exist — if missing, passthrough everything (never break commands)
command -v ft &>/dev/null || exit 0

# Heredoc / multi-line payload passthrough — the rewrite regexes below match
# token text anywhere in $CMD with no awareness of quoting or heredoc-body
# context, so they corrupt text INSIDE a heredoc or multi-line string instead
# of rewriting the outer command (e.g. a heredoc body containing the literal
# text "git status" gets mangled into "ft git status").
[[ "$CMD" == *$'\n'* || "$CMD" == *'<<'* ]] && exit 0

# Pipe detection — disable hints when command has downstream pipe
# Conservative: any bare | (not part of ||) disables hints
HAS_PIPE=0
if printf '%s' "$CMD" | perl -ne 'exit 0 if /(?<![|])\|(?![|])/; exit 1'; then
  HAS_PIPE=1
fi

# Redirect detection — disable hints when command has stdout redirect (> or >>)
# Avoids writing hints dict into redirect-target files.
# Excludes: 2>&1 (fd redirect), <= (here-string), <<, process sub >(cmd)
HAS_REDIRECT=0
if printf '%s' "$CMD" | perl -ne 'exit 0 if /(?<![0-9<(])>{1,2}(?![>&(])/; exit 1'; then
  HAS_REDIRECT=1
fi

# Stdout redirect → whole-command passthrough. Redirected output never reaches
# model context, so ft filtering saves nothing — and the filtered/truncated
# stream would be what lands in the target file (silent data corruption:
# `cat a b > c` wrote ft's "[showing first 100 of N lines]" rendering into c).
[[ $HAS_REDIRECT -eq 1 ]] && exit 0

# 3-pass Perl normalization (regexes unchanged from battle-tested original;
# the 3 passes run inside ONE perl process instead of three).
# Used ONLY for detection; rewrite operates on original $CMD.
NCMD=$(printf '%s' "$CMD" | perl -CSDA -0777 -pe '
  for my $pass (1..3) {
    s/\b(?:bash|sh|zsh|dash|ash|busybox)\s+-c\s+'\''([^'\'']*)'\''/;$1;/g;
    s/\b(?:bash|sh|zsh|dash|ash|busybox)\s+-c\s+"((?:[^"\\\\]|\\\\.)*)"/;$1;/g;
    s/\beval\s+'\''([^'\'']*)'\''/;$1;/g;
    s/\beval\s+"((?:[^"\\\\]|\\\\.)*)"/;$1;/g;
    s/\$\(([^()]*)\)/;$1;/g;
    s/`([^`]*)`/;$1;/g;
    s/(?<!\w)(?:do|then|else|elif)(?=\s)/;/g;
    s/(?<!\w)(?:nohup|time|xargs(?:\s+-[a-zA-Z0-9]+)*|stdbuf(?:\s+-[ioe][^\s]+)*|ionice|nice|sudo|chronic|unbuffer|setsid|exec|command|builtin)\s+(?=\S)/;/g;
    s/(?<!\w)env(?:\s+[A-Za-z_][A-Za-z0-9_]*=\S*)+\s+/;/g;
    s/(?<![\w=])([A-Za-z_][A-Za-z0-9_]*=\S*\s+)+(?=[a-zA-Z_])/;/g;
    s/[{}()]/;/g;
  }
')

# Statement-start boundary (not after pipe — piped grep/tail is fine as filter)
STMT='(^|[;&]|&&|\|\|)[\s]*'
# Any boundary (including pipe)
B='(^|[|;&(]|\$\(|\n)[\s]*'

# All 14 class detections in ONE perl process (regexes unchanged from the
# battle-tested per-spawn originals). Prints one token per matched class.
# Git show/diff/cat-file deliver source/patch bytes to the agent, so they must
# not cross ft's intentionally lossy rendering; only history navigation does.
FLAGS=$(printf '%s' "$NCMD" | perl -0777 -ne "
  print \"FILESCAN \"  if /${STMT}(find|tree)[\s]/;
  print \"FILEREAD \"  if /${STMT}(cat|head|tail|less|more)[\s]/;
  print \"DATAPROC \"  if /${STMT}(jq|yq|xmllint|xq)[\s]/;
  print \"GITREW \"    if /${B}git[\s]+(log|blame|reflog)/;
  print \"PKGMGR \"    if /${B}(npm|pnpm|yarn|bun|npx)[\s]/;
  print \"TESTRUN \"   if /${B}(jest|vitest|mocha|pytest|phpunit)([\s]|\$)/;
  print \"BUILD \"     if /${B}(tsc)([\s]|\$)/;
  print \"NET \"       if /${B}(curl|wget|httpie|http)[\s]/;
  print \"SYSLOG \"    if /${B}(journalctl|dmesg)[\s]/;
  print \"CTRLOG \"    if /${B}(docker|podman|kubectl)[\s]+logs/;
  print \"RECLS \"     if /ls[\s]+(-[a-zA-Z]*[Rr][a-zA-Z]*|--recursive)/;
  print \"CARGOGO \"   if /${B}(cargo|go)[\s]+(test|build|check|run)/;
  print \"PROC \"      if /${B}(ps|lsof)[\s]/;
  print \"INTERP \"    if /${STMT}(node|python3?)([\s]|\$)/;
  print \"SEDREW \"    if /${STMT}sed\s+-n\s+'[0-9]+(,[0-9]+)?p'\s+\S/;
")

NEED_REWRITE=0
FT_PAT=""
[[ "$FLAGS" == *FILESCAN* ]] && { FT_PAT="find|tree"; NEED_REWRITE=1; }
[[ "$FLAGS" == *FILEREAD* ]] && { FT_PAT="${FT_PAT:+$FT_PAT|}cat|head|tail|less|more"; NEED_REWRITE=1; }
[[ "$FLAGS" == *DATAPROC* ]] && { FT_PAT="${FT_PAT:+$FT_PAT|}jq|yq|xmllint|xq"; NEED_REWRITE=1; }
GIT_REWRITE=0
[[ "$FLAGS" == *GITREW* ]] && GIT_REWRITE=1
[[ "$FLAGS" == *PKGMGR* ]] && { FT_PAT="${FT_PAT:+$FT_PAT|}npm|pnpm|yarn|bun|npx"; NEED_REWRITE=1; }
[[ "$FLAGS" == *TESTRUN* ]] && { FT_PAT="${FT_PAT:+$FT_PAT|}jest|vitest|mocha|pytest|phpunit"; NEED_REWRITE=1; }
[[ "$FLAGS" == *BUILD* ]] && { FT_PAT="${FT_PAT:+$FT_PAT|}tsc"; NEED_REWRITE=1; }
[[ "$FLAGS" == *NET* ]] && { FT_PAT="${FT_PAT:+$FT_PAT|}curl|wget|httpie|http"; NEED_REWRITE=1; }
[[ "$FLAGS" == *SYSLOG* ]] && { FT_PAT="${FT_PAT:+$FT_PAT|}journalctl|dmesg"; NEED_REWRITE=1; }
[[ "$FLAGS" == *CTRLOG* ]] && { FT_PAT="${FT_PAT:+$FT_PAT|}docker|podman|kubectl"; NEED_REWRITE=1; }
[[ "$FLAGS" == *RECLS* ]] && { FT_PAT="${FT_PAT:+$FT_PAT|}ls"; NEED_REWRITE=1; }
[[ "$FLAGS" == *CARGOGO* ]] && { FT_PAT="${FT_PAT:+$FT_PAT|}cargo|go"; NEED_REWRITE=1; }
[[ "$FLAGS" == *PROC* ]] && { FT_PAT="${FT_PAT:+$FT_PAT|}ps|lsof"; NEED_REWRITE=1; }
[[ "$FLAGS" == *INTERP* ]] && { FT_PAT="${FT_PAT:+$FT_PAT|}node|python3|python"; NEED_REWRITE=1; }
SED_REWRITE=0
[[ "$FLAGS" == *SEDREW* ]] && SED_REWRITE=1

# Apply rewrites — prepend FT_FROM_HOOK=1 so ft knows to filter (stdout is pipe under Claude Code)
# FT_HINTS=1 added when no downstream pipe — enables trailing compact-flag hints in ft
REWRITTEN="$CMD"
HINTS_PREFIX=""
[[ $HAS_PIPE -eq 0 && $HAS_REDIRECT -eq 0 ]] && HINTS_PREFIX="FT_HINTS=1 "
# Downstream pipe → consumer is a program, not the agent; ft must not swap
# real bytes for a dedupe marker mid-pipeline.
[[ $HAS_PIPE -eq 1 ]] && HINTS_PREFIX="FT_PIPED=1 "
if [[ $NEED_REWRITE -eq 1 && -n "$FT_PAT" ]]; then
  REWRITTEN=$(printf '%s' "$REWRITTEN" | perl -CSDA -pe "s/((?:^|(?<=[|;&(\n]))\s*(?:[A-Za-z_]\w*=\S*\s+)*)($FT_PAT)(?=\s)/\${1}FT_FROM_HOOK=1 ${HINTS_PREFIX}ft \$2/g")
fi
if [[ $GIT_REWRITE -eq 1 ]]; then
  # diff/show are REVIEW verbs: a missing hunk changes a verdict, so above a size
  # threshold od-review-git withholds the body and hands back a file path instead of
  # quietly returning a fraction of it. Only when the agent is the consumer — a
  # downstream pipe or redirect means a program is reading, and those keep ft's
  # byte-faithful path below. /usr/bin/git is matched too: CLAUDE.md teaches the
  # absolute path for push/fetch/merge, never for review, so closing it here collides
  # with no documented use.
  if [[ $HAS_PIPE -eq 0 && $HAS_REDIRECT -eq 0 ]]; then
    REWRITTEN=$(printf '%s' "$REWRITTEN" | perl -CSDA -pe "s/((?:^|(?<=[|;&(\n]))\s*(?:[A-Za-z_]\w*=\S*\s+)*)(?:\/usr\/bin\/)?git\s+(diff|show)(?=\s|\$)/\${1}od-review-git \$2/g")
  fi
  # log/blame/reflog keep advisory truncation: a clipped log rarely corrupts a judgement
  # the way a clipped hunk does, and these are the bulk of the harmless small-output band.
  REWRITTEN=$(printf '%s' "$REWRITTEN" | perl -CSDA -pe "s/((?:^|(?<=[|;&(\n]))\s*(?:[A-Za-z_]\w*=\S*\s+)*)git\s+(log|diff|show|blame|reflog)/\${1}FT_FROM_HOOK=1 ${HINTS_PREFIX}ft git \$2/g")
fi
if [[ $SED_REWRITE -eq 1 ]]; then
  REWRITTEN=$(printf '%s' "$REWRITTEN" | perl -CSDA -pe "s/((?:^|(?<=[|;&(\n]))\s*(?:[A-Za-z_]\w*=\S*\s+)*)sed(?=\s+-n\s+'[0-9]+(,[0-9]+)?p'\s+)/\${1}FT_FROM_HOOK=1 ${HINTS_PREFIX}ft sed/g")
fi

if [[ "$REWRITTEN" != "$CMD" ]]; then
  jq -n --arg cmd "$REWRITTEN" '{
    hookSpecificOutput: {
      hookEventName: "PreToolUse",
      updatedInput: {command: $cmd}
    }
  }'
  exit 0
fi

# Passthrough
exit 0
