# shellcheck shell=sh
# Read gate (spec-token-sparen CAP-2). Invoked by a PreToolUse hook on Read.
#
# Refuses a WHOLE read of a long text file in the main session, so raw
# input stays out of the expensive context (Spotify Shunt, 2026-09-03).
# A ranged read, a short file, a binary file and every subagent pass: the
# subagent is the worker whose context is meant to absorb the bulk.
#
# Same physics as the write gate: the exit status is ALWAYS 0, never 2
# (AD-20), and denial needs positive knowledge (AD-33). Every doubt --
# unparsable input, unreadable file, odd threshold -- resolves to proceed.
#
# Threshold: BESTAICONFIG_READ_GATE_LINES, default 350 (Shunt's heuristic,
# not a measurement for this repository). 0 switches the gate off.

payload_=$(cat 2>/dev/null) || exit 0
[ -n "${payload_}" ] || exit 0
flat_=$(printf '%s' "${payload_}" | LC_ALL=C tr -d '\n')

field_() {
  # field_ <key> -- first JSON string value for that key, or empty.
  printf '%s' "${flat_}" \
    | LC_ALL=C sed -n "s/.*\"$1\"[[:space:]]*:[[:space:]]*\"\([^\"]*\)\".*/\1/p" \
    | head -n 1
}

has_key_() {
  # has_key_ <key> -- status 0 when the payload names that key.
  printf '%s' "${flat_}" | LC_ALL=C grep -Eq "\"$1\"[[:space:]]*:"
}

limit_="${BESTAICONFIG_READ_GATE_LINES:-350}"
case "${limit_}" in
  ''|*[!0-9]*) exit 0 ;;
esac
[ "${limit_}" -gt 0 ] 2>/dev/null || exit 0

[ "$(field_ tool_name)" = "Read" ] || exit 0

# The subagent is the worker. Gating it would defeat the delegation.
[ -n "$(field_ agent_id)" ] && exit 0

# A range or a page selection is already a targeted read.
has_key_ offset && exit 0
has_key_ limit && exit 0
has_key_ pages && exit 0

file_="$(field_ file_path)"
[ -n "${file_}" ] || exit 0
[ -f "${file_}" ] || exit 0
[ -r "${file_}" ] || exit 0

# Images, PDFs and other binaries have no lines worth counting.
LC_ALL=C grep -Iq . "${file_}" 2>/dev/null || exit 0

lines_=$(LC_ALL=C wc -l < "${file_}" 2>/dev/null | tr -d '[:space:]')
case "${lines_}" in
  ''|*[!0-9]*) exit 0 ;;
esac
[ "${lines_}" -gt "${limit_}" ] || exit 0

# AD-34: the wording lives in the catalogue, never in this script. The
# numbers are data, not a sentence. No systemMessage on purpose: the
# person has nothing to do, and a notice on every long file is noise.
catalogue_="$(dirname "$0")/messages.txt"
[ -r "${catalogue_}" ] || exit 0
reason_="$(LC_ALL=C awk -F'\t' '$1=="read-gate.reason.model" {print $2; exit}' "${catalogue_}" 2>/dev/null)"
[ -n "${reason_}" ] || exit 0

printf '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"%s lines, threshold %s. %s"}}\n' \
  "${lines_}" "${limit_}" "${reason_}"
exit 0
