#!/usr/bin/env bash
# Fast Haskell-on-SX test runner — pipes directly to sx_server.exe.
# No MCP, no Docker. All tests live in lib/haskell/tests/*.sx and
# produce a summary dict at the end of each file.
#
# Usage:
#   bash lib/haskell/test.sh              # run all tests
#   bash lib/haskell/test.sh -v           # verbose — show each file's pass/fail
#   bash lib/haskell/test.sh tests/parse.sx   # run one file

set -euo pipefail
cd "$(git rev-parse --show-toplevel)"

SX_SERVER="hosts/ocaml/_build/default/bin/sx_server.exe"
if [ ! -x "$SX_SERVER" ]; then
  # Fall back to the main-repo build if we're in a worktree.
  MAIN_ROOT=$(git worktree list | awk 'NR==1{print $1}')
  if [ -x "$MAIN_ROOT/$SX_SERVER" ]; then
    SX_SERVER="$MAIN_ROOT/$SX_SERVER"
  else
    echo "ERROR: sx_server.exe not found. Run: cd hosts/ocaml && dune build"
    exit 1
  fi
fi

VERBOSE=""
FILES=()
for arg in "$@"; do
  case "$arg" in
    -v|--verbose) VERBOSE=1 ;;
    *) FILES+=("$arg") ;;
  esac
done

if [ ${#FILES[@]} -eq 0 ]; then
  mapfile -t FILES < <(find lib/haskell/tests -maxdepth 2 -name '*.sx' | sort)
fi

TOTAL_PASS=0
TOTAL_FAIL=0
FAILED_FILES=()

for FILE in "${FILES[@]}"; do
  [ -f "$FILE" ] || { echo "skip $FILE (not found)"; continue; }
  # Load infer.sx only for infer/typecheck test files (it adds ~6s overhead).
  INFER_LOAD=""
  case "$FILE" in *infer*|*typecheck*) INFER_LOAD='(load "lib/haskell/infer.sx")' ;; esac
  TMPFILE=$(mktemp)
  cat > "$TMPFILE" <<EPOCHS
(epoch 1)
(load "lib/haskell/tokenizer.sx")
(load "lib/haskell/layout.sx")
(load "lib/haskell/parser.sx")
(load "lib/haskell/desugar.sx")
(load "lib/haskell/runtime.sx")
(load "lib/haskell/match.sx")
(load "lib/haskell/eval.sx")
(load "lib/haskell/map.sx")
(load "lib/haskell/set.sx")
$INFER_LOAD
(load "lib/haskell/testlib.sx")
(epoch 2)
(load "$FILE")
(epoch 3)
(eval "(list hk-test-pass hk-test-fail)")
EPOCHS

  OUTPUT=$(timeout 360 "$SX_SERVER" < "$TMPFILE" 2>&1 || true)
  rm -f "$TMPFILE"

  # Output format: either "(ok 3 (P F))" on one line (short result) or
  # "(ok-len 3 N)\n(P F)" where the value appears on the following line.
  LINE=$(echo "$OUTPUT" | awk '/^\(ok-len 3 / {getline; print; exit}')
  if [ -z "$LINE" ]; then
    LINE=$(echo "$OUTPUT" | { grep -E '^\(ok 3 \([0-9]+ [0-9]+\)\)' || true; } | tail -1 \
            | sed -E 's/^\(ok 3 //; s/\)$//')
  fi
  if [ -z "$LINE" ]; then
    echo "✗ $FILE: could not extract summary"
    echo "$OUTPUT" | tail -20
    TOTAL_FAIL=$((TOTAL_FAIL + 1))
    FAILED_FILES+=("$FILE")
    continue
  fi
  P=$(echo "$LINE" | sed -E 's/^\(([0-9]+) ([0-9]+)\).*/\1/')
  F=$(echo "$LINE" | sed -E 's/^\(([0-9]+) ([0-9]+)\).*/\2/')
  TOTAL_PASS=$((TOTAL_PASS + P))
  TOTAL_FAIL=$((TOTAL_FAIL + F))
  if [ "$F" -gt 0 ]; then
    FAILED_FILES+=("$FILE")
    printf '✗ %-40s %d/%d\n' "$FILE" "$P" "$((P+F))"
    # Print failure names
    TMPFILE2=$(mktemp)
    cat > "$TMPFILE2" <<EPOCHS
(epoch 1)
(load "lib/haskell/tokenizer.sx")
(load "lib/haskell/layout.sx")
(load "lib/haskell/parser.sx")
(load "lib/haskell/desugar.sx")
(load "lib/haskell/runtime.sx")
(load "lib/haskell/match.sx")
(load "lib/haskell/eval.sx")
(load "lib/haskell/map.sx")
(load "lib/haskell/set.sx")
$INFER_LOAD
(load "lib/haskell/testlib.sx")
(epoch 2)
(load "$FILE")
(epoch 3)
(eval "(map (fn (f) (get f \"name\")) hk-test-fails)")
EPOCHS
    FAILS=$(timeout 360 "$SX_SERVER" < "$TMPFILE2" 2>&1 | grep -E '^\(ok 3 ' || true)
    rm -f "$TMPFILE2"
    echo "    $FAILS"
  elif [ "$VERBOSE" = "1" ]; then
    printf '✓ %-40s %d passed\n' "$FILE" "$P"
  fi
done

TOTAL=$((TOTAL_PASS + TOTAL_FAIL))
if [ $TOTAL_FAIL -eq 0 ]; then
  echo "✓ $TOTAL_PASS/$TOTAL haskell-on-sx tests passed"
else
  echo "✗ $TOTAL_PASS/$TOTAL passed, $TOTAL_FAIL failed in: ${FAILED_FILES[*]}"
fi

[ $TOTAL_FAIL -eq 0 ]
