#!/usr/bin/env bash
# Reproducible grix vs ripgrep benchmark.
#
#   bench/run.sh <corpus-dir>
#
# Requires: hyperfine, ripgrep, a release grix build.
# Override binaries with GRIX=/path/to/grix RG=/path/to/rg.
#
# Every pattern is parity-checked (identical matched-line counts) before it
# is timed; a benchmark against wrong results would be meaningless.

set -euo pipefail

CORPUS=${1:?usage: bench/run.sh <corpus-dir>}
GRIX=${GRIX:-grix}
RG=${RG:-rg}
RUNS=${RUNS:-10}

command -v hyperfine >/dev/null || { echo "error: hyperfine not found" >&2; exit 1; }
command -v "$RG" >/dev/null || { echo "error: ripgrep not found" >&2; exit 1; }

# hyperfine runs with --shell=none, so binaries need plain native paths
# (and forward slashes survive its argument splitting on Windows).
if command -v cygpath >/dev/null; then
  GRIX_CMD=$(cygpath -m "$(command -v "$GRIX")")
  RG_CMD=$(cygpath -m "$(command -v "$RG")")
else
  GRIX_CMD=$GRIX
  RG_CMD=$RG
fi

cd "$CORPUS"

echo "## corpus"
files=$("$RG" --files | wc -l | tr -d ' ')
echo "- files (as rg sees them): $files"
echo

echo "## index build"
"$GRIX" forget . >/dev/null 2>&1 || true
start=$(date +%s%N)
"$GRIX" index .
end=$(date +%s%N)
echo "- cold build: $(( (end - start) / 1000000 )) ms"
start=$(date +%s%N)
"$GRIX" index .
end=$(date +%s%N)
echo "- refresh (no changes): $(( (end - start) / 1000000 )) ms"
echo

# pattern | flags (applied to both tools) | description
# NOTE: patterns must not contain spaces or '|' (hyperfine -N word splitting
# / the field separator below).
benchmarks=(
  'PageTransHuge||rare literal'
  'EXPORT_SYMBOL||common literal (tens of thousands of hits)'
  'static\s+int\s+\w+_probe||regex with literal core'
  'spinlock|-i|case-insensitive literal'
  'zzqqxx_does_not_exist||no match'
)

for spec in "${benchmarks[@]}"; do
  IFS='|' read -r pattern flags desc <<<"$spec"
  echo "## $desc: $pattern ${flags:+($flags)}"

  # Parity gate. --no-auto-index so grix is compared on the just-built index
  # (and timed the same way below).
  rg_count=$("$RG" $flags "$pattern" . --no-heading 2>/dev/null | wc -l | tr -d ' ' || true)
  grix_count=$("$GRIX" $flags "$pattern" . --no-heading --color never --no-auto-index 2>/dev/null | wc -l | tr -d ' ' || true)
  if [ "$rg_count" != "$grix_count" ]; then
    echo "PARITY FAILURE: rg=$rg_count grix=$grix_count -- skipping timing" >&2
    echo "--- rg debug ---" >&2
    "$RG" $flags "$pattern" . --no-heading 2>&1 | head -5 >&2 || true
    echo "--- grix debug ---" >&2
    "$GRIX" $flags "$pattern" . --no-heading --color never --stats 2>&1 | tail -8 >&2 || true
    exit 1
  fi
  echo "- matched lines (both tools): $rg_count"

  # --no-auto-index isolates query speed (the index lookup + confirm scan),
  # without the per-search incremental refresh walk. This is the speed you
  # get on an unchanged tree, and what watch mode will make the default.
  hyperfine --warmup 3 --runs "$RUNS" --shell=none --ignore-failure --style basic \
    -n "rg" "$RG_CMD $flags '$pattern' . --no-heading" \
    -n "grix" "$GRIX_CMD $flags '$pattern' . --no-heading --color never --no-auto-index"
  echo
done
