#!/usr/bin/env bash
# lib/erlang/test.sh — smoke-test the Erlang runtime layer.
# Uses sx_server.exe epoch protocol.
#
# Usage:
#   bash lib/erlang/test.sh
#   bash lib/erlang/test.sh -v

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

SX_SERVER="${SX_SERVER:-hosts/ocaml/_build/default/bin/sx_server.exe}"
if [ ! -x "$SX_SERVER" ]; then
  SX_SERVER="/root/rose-ash/hosts/ocaml/_build/default/bin/sx_server.exe"
fi
if [ ! -x "$SX_SERVER" ]; then
  echo "ERROR: sx_server.exe not found. Run: cd hosts/ocaml && dune build"
  exit 1
fi

VERBOSE="${1:-}"
PASS=0; FAIL=0; ERRORS=""
TMPFILE=$(mktemp); trap "rm -f $TMPFILE" EXIT

cat > "$TMPFILE" << 'EPOCHS'
(epoch 1)
(load "lib/erlang/runtime.sx")

;; --- Numeric tower ---
(epoch 10)
(eval "(er-is-integer? 42)")
(epoch 11)
(eval "(er-is-integer? 3.14)")
(epoch 12)
(eval "(er-is-float? 3.14)")
(epoch 13)
(eval "(er-is-float? 42)")
(epoch 14)
(eval "(er-is-number? 42)")
(epoch 15)
(eval "(er-is-number? 3.14)")
(epoch 16)
(eval "(er-float 5)")
(epoch 17)
(eval "(er-trunc 3.9)")
(epoch 18)
(eval "(er-round 3.5)")
(epoch 19)
(eval "(er-abs -7)")
(epoch 20)
(eval "(er-max 3 7)")
(epoch 21)
(eval "(er-min 3 7)")

;; --- div + rem ---
(epoch 30)
(eval "(er-div 10 3)")
(epoch 31)
(eval "(er-div -10 3)")
(epoch 32)
(eval "(er-rem 10 3)")
(epoch 33)
(eval "(er-rem -10 3)")
(epoch 34)
(eval "(er-gcd 12 8)")

;; --- Bitwise ---
(epoch 40)
(eval "(er-band 12 10)")
(epoch 41)
(eval "(er-bor 12 10)")
(epoch 42)
(eval "(er-bxor 12 10)")
(epoch 43)
(eval "(er-bnot 0)")
(epoch 44)
(eval "(er-bsl 1 4)")
(epoch 45)
(eval "(er-bsr 16 2)")

;; --- Sets ---
(epoch 50)
(eval "(er-sets-is-set? (er-sets-new))")
(epoch 51)
(eval "(let ((s (er-sets-new))) (do (er-sets-add-element s 1) (er-sets-is-element s 1)))")
(epoch 52)
(eval "(er-sets-is-element (er-sets-new) 42)")
(epoch 53)
(eval "(er-sets-is-element (er-sets-from-list (list 1 2 3)) 2)")
(epoch 54)
(eval "(er-sets-size (er-sets-from-list (list 1 2 3)))")
(epoch 55)
(eval "(len (er-sets-to-list (er-sets-from-list (list 1 2 3))))")

;; --- Regexp ---
(epoch 60)
(eval "(not (= (er-re-run \"hello\" \"ll\") nil))")
(epoch 61)
(eval "(= (er-re-run \"hello\" \"xyz\") nil)")
(epoch 62)
(eval "(get (er-re-run \"hello\" \"ll\") :match)")
(epoch 63)
(eval "(er-re-replace \"hello\" \"l\" \"r\")")
(epoch 64)
(eval "(er-re-replace-all \"hello\" \"l\" \"r\")")
(epoch 65)
(eval "(er-re-match-groups (er-re-run \"hello world\" \"(\\w+)\\s+(\\w+)\"))")
(epoch 66)
(eval "(len (er-re-split \"a,b,c\" \",\"))")

;; --- List BIFs ---
(epoch 70)
(eval "(er-hd (list 1 2 3))")
(epoch 71)
(eval "(er-tl (list 1 2 3))")
(epoch 72)
(eval "(er-length (list 1 2 3))")
(epoch 73)
(eval "(er-lists-member 2 (list 1 2 3))")
(epoch 74)
(eval "(er-lists-member 9 (list 1 2 3))")
(epoch 75)
(eval "(er-lists-reverse (list 1 2 3))")
(epoch 76)
(eval "(er-lists-nth 2 (list 10 20 30))")
(epoch 77)
(eval "(er-lists-foldl + 0 (list 1 2 3 4 5))")
(epoch 78)
(eval "(er-lists-seq 1 5)")
(epoch 79)
(eval "(er-lists-flatten (list 1 (list 2 3) (list 4 (list 5))))")

;; --- Type conversions ---
(epoch 80)
(eval "(er-integer-to-list 42)")
(epoch 81)
(eval "(er-list-to-integer \"42\")")
(epoch 82)
(eval "(er-integer-to-list-radix 255 16)")
(epoch 83)
(eval "(er-atom-to-list (make-symbol \"hello\"))")
(epoch 84)
(eval "(= (type-of (er-list-to-atom \"foo\")) \"symbol\")")

;; --- ok/error tuples ---
(epoch 90)
(eval "(er-is-ok? (er-ok 42))")
(epoch 91)
(eval "(er-is-error? (er-error \"reason\"))")
(epoch 92)
(eval "(er-unwrap (er-ok 42))")
(epoch 93)
(eval "(er-is-ok? (er-error \"bad\"))")

EPOCHS

OUTPUT=$(timeout 30 "$SX_SERVER" < "$TMPFILE" 2>/dev/null)

check() {
  local epoch="$1" desc="$2" expected="$3"
  local actual
  actual=$(echo "$OUTPUT" | grep -A1 "^(ok-len $epoch " | tail -1 || true)
  if echo "$actual" | grep -q "^(ok-len"; then actual=""; fi
  if [ -z "$actual" ]; then
    actual=$(echo "$OUTPUT" | grep "^(ok $epoch " | head -1 || true)
  fi
  if [ -z "$actual" ]; then
    actual=$(echo "$OUTPUT" | grep "^(error $epoch " | head -1 || true)
  fi
  [ -z "$actual" ] && actual="<no output for epoch $epoch>"

  if echo "$actual" | grep -qF -- "$expected"; then
    PASS=$((PASS+1))
    [ "$VERBOSE" = "-v" ] && echo "  ok $desc"
  else
    FAIL=$((FAIL+1))
    ERRORS+="  FAIL [$desc] (epoch $epoch) expected: $expected | actual: $actual
"
  fi
}

# Numeric tower
check 10  "is-integer? 42"        "true"
check 11  "is-integer? float"     "false"
check 12  "is-float? 3.14"        "true"
check 13  "is-float? int"         "false"
check 14  "is-number? int"        "true"
check 15  "is-number? float"      "true"
check 16  "float 5"               "5"
check 17  "trunc 3.9"             "3"
check 18  "round 3.5"             "4"
check 19  "abs -7"                "7"
check 20  "max 3 7"               "7"
check 21  "min 3 7"               "3"

# div + rem
check 30  "div 10 3"              "3"
check 31  "div -10 3"             "-3"
check 32  "rem 10 3"              "1"
check 33  "rem -10 3"             "-1"
check 34  "gcd 12 8"              "4"

# Bitwise
check 40  "band 12 10"            "8"
check 41  "bor 12 10"             "14"
check 42  "bxor 12 10"            "6"
check 43  "bnot 0"                "-1"
check 44  "bsl 1 4"               "16"
check 45  "bsr 16 2"              "4"

# Sets
check 50  "sets-new is-set?"      "true"
check 51  "sets add+member"       "true"
check 52  "member empty"          "false"
check 53  "from-list member"      "true"
check 54  "sets-size"             "3"
check 55  "sets-to-list len"      "3"

# Regexp
check 60  "re-run match"          "true"
check 61  "re-run no match"       "true"
check 62  "re-run match text"     '"ll"'
check 63  "re-replace first"      '"herlo"'
check 64  "re-replace-all"        '"herro"'
check 65  "re-match-groups"       '"hello"'
check 66  "re-split count"        "3"

# List BIFs
check 70  "hd"                    "1"
check 71  "tl"                    "(2 3)"
check 72  "length"                "3"
check 73  "member hit"            "true"
check 74  "member miss"           "false"
check 75  "reverse"               "(3 2 1)"
check 76  "nth 2"                 "20"
check 77  "foldl sum"             "15"
check 78  "seq 1..5"              "(1 2 3 4 5)"
check 79  "flatten"               "(1 2 3 4 5)"

# Type conversions
check 80  "integer-to-list"       '"42"'
check 81  "list-to-integer"       "42"
check 82  "integer-to-list hex"   '"ff"'
check 83  "atom-to-list"          '"hello"'
check 84  "list-to-atom"          "true"

# ok/error
check 90  "ok? ok-tuple"          "true"
check 91  "error? error-tuple"    "true"
check 92  "unwrap ok"             "42"
check 93  "ok? error-tuple"       "false"

TOTAL=$((PASS+FAIL))
if [ $FAIL -eq 0 ]; then
  echo "ok $PASS/$TOTAL lib/erlang tests passed"
else
  echo "FAIL $PASS/$TOTAL passed, $FAIL failed:"
  echo "$ERRORS"
fi
[ $FAIL -eq 0 ]
