#!/bin/bash
#
# deftype_test.sh — Regression tests for the four klammer definition modes
# and the 9-combination redefinition transition table.
#
# The four syntactic definition modes (increasing colon count = decreasing
# binding strength, the "conservation of syntax" ladder):
#
#   :     create, defines its own parameters      — error if already defined
#   ::    create, inherits parameters from `.k`    — error if already defined
#   :::   override, inherits parameters from `.k`  — replaces existing (warns)
#   ::::  default, defines its own parameters      — silently replaced by create
#
# At the transition level (mac/deftype.cpp) `:` and `::` are both `def_create`;
# the three redefinition modes are create / override (`:::`) / default (`::::`).
# `defmode_transition(existing, incoming)` governs all 9 (existing x incoming)
# combinations. This test pins down every entry of that table plus the basic
# behavior of each mode, none of which tst/klammer_test.cpp currently covers.
#
# {replace,warn,message} semantics (mac/klammer_registry.cpp):
#   !replace && message  -> Definition_error (nonzero exit)
#   !replace && empty    -> silently keep the existing definition
#   replace  && warn     -> emit a warning, then replace
#   replace  && !warn    -> replace silently
#
# Usage: ./deftype_test.sh   (needs KLAMMERTEXT_HOME set; ktext on PATH)
# Exit code: 0 if all tests pass, 1 otherwise.

PASS=0
FAIL=0
KTEXT=ktext
K=${KLAMMERTEXT_HOME:?KLAMMERTEXT_HOME must be set}
ERR=/tmp/deftype_test_err.$$

red=$'\033[31m'
green=$'\033[32m'
bold=$'\033[1m'
reset=$'\033[0m'

# strip leading/trailing blank lines and trailing whitespace
trim() { awk '{ sub(/[ \t\r]+$/, "") } { line[NR]=$0 } END { f=1; while (f<=NR && line[f]=="") f++; l=NR; while (l>=1 && line[l]=="") l--; for (i=f;i<=l;i++) print line[i] }'; }

# check_eq NAME EXPECTED KTEXT_ARGS... — exit 0, stdout==EXPECTED, and NO warning.
check_eq() {
    local name="$1" expected="$2"; shift 2
    local out status err
    out=$("$KTEXT" "$@" 2>"$ERR"); status=$?
    err=$(cat "$ERR")
    out=$(printf '%s' "$out" | trim)
    if [ $status -ne 0 ]; then
        echo "${red}FAIL${reset} $name — ktext exited $status"
        echo "       stderr: $(echo "$err" | head -2)"; FAIL=$((FAIL+1)); return
    fi
    if printf '%s' "$err" | grep -qiF "warning"; then
        echo "${red}FAIL${reset} $name — unexpected warning"; FAIL=$((FAIL+1)); return
    fi
    if [ "$out" = "$expected" ]; then
        echo "${green}PASS${reset} $name"; PASS=$((PASS+1))
    else
        echo "${red}FAIL${reset} $name"
        echo "       expected: [$expected]"; echo "       got:      [$out]"; FAIL=$((FAIL+1))
    fi
}

# check_warn NAME EXPECTED KTEXT_ARGS... — exit 0, stdout==EXPECTED, the
# override REPORTED at "-v 1" and SILENT at the default verbosity.
#
# It was a warning until 2026-08-15.  ":::" exists to override, and a klammer
# set the user did not write may be overridden by design (TODO #33), so the
# notice fired on the sanctioned use of a feature: a warning nobody can act on
# is not a warning.  It is still worth reporting, because the definition being
# replaced usually lives in another file in another klammerset, which the user
# cannot see from what they wrote -- so it is "-v 1" (see the output policy in
# CLAUDE.md).
check_warn() {
    local name="$1" expected="$2"; shift 2
    local out status err
    out=$("$KTEXT" "$@" 2>"$ERR"); status=$?
    err=$(cat "$ERR")
    out=$(printf '%s' "$out" | trim)
    if [ $status -ne 0 ]; then
        echo "${red}FAIL${reset} $name — ktext exited $status"; FAIL=$((FAIL+1)); return
    fi
    if [ -s "$ERR" ]; then
        echo "${red}FAIL${reset} $name — the default run was not silent: $(head -1 "$ERR")"
        FAIL=$((FAIL+1)); return
    fi
    "$KTEXT" "$@" -v 1 >/dev/null 2>"$ERR"
    err=$(cat "$ERR")
    if ! printf '%s' "$err" | grep -qiF "overridden"; then
        echo "${red}FAIL${reset} $name — no override reported at -v 1"; FAIL=$((FAIL+1)); return
    fi
    if [ "$out" = "$expected" ]; then
        echo "${green}PASS${reset} $name"; PASS=$((PASS+1))
    else
        echo "${red}FAIL${reset} $name"
        echo "       expected: [$expected]"; echo "       got:      [$out]"; FAIL=$((FAIL+1))
    fi
}

# check_error NAME PATTERN KTEXT_ARGS... — nonzero exit and PATTERN in the message.
check_error() {
    local name="$1" pattern="$2"; shift 2
    local out status
    out=$("$KTEXT" "$@" 2>&1); status=$?
    if [ $status -eq 0 ]; then
        echo "${red}FAIL${reset} $name — expected an error but ktext succeeded"; FAIL=$((FAIL+1)); return
    fi
    if echo "$out" | tr '\n' ' ' | tr -s ' ' | grep -qF "$pattern"; then
        echo "${green}PASS${reset} $name"; PASS=$((PASS+1))
    else
        echo "${red}FAIL${reset} $name — expected error to contain [$pattern]"
        echo "       output: $(echo "$out" | head -2)"; FAIL=$((FAIL+1))
    fi
}

echo "${bold}Klammer definition-mode tests${reset}"
echo "============================="
echo

# --- Each mode defines and applies in isolation ---
echo "-- isolated modes --"
check_eq   " 1. :    create defines + applies"          "cq" -s '@@x s : c*s* @@ @x q @' -d
check_eq   " 2. ::   instance inherits from .k"         "iq" -s '@@x.k s : d @@ @@x :: i*s* @@ @x q @' -d
check_error " 3. ::   without a .k declaration is an error" "no declarations" -s '@@x :: i*s* @@ @x q @' -d
check_eq   " 4. :::: default defines + applies"         "eq" -s '@@x s :::: e*s* @@ @x q @' -d
check_eq   " 5. :::  override alone becomes the def"     "oq" -s '@@x.k s : d @@ @@x ::: o*s* @@ @x q @' -d

# --- The 9-entry transition table: (existing x incoming) ---
echo
echo "-- transition table (existing + incoming) --"
check_error " 6. create + create   = error"    "already defined"     -s '@@x s : one*s* @@ @@x s : two*s* @@ @x q @' -d
check_warn  " 7. create + override = warn+repl" "twoq"                -s '@@x.k s : d @@ @@x :: one*s* @@ @@x ::: two*s* @@ @x q @' -d
check_eq    " 8. create + default  = keep exist" "oneq"               -s '@@x s : one*s* @@ @@x s :::: two*s* @@ @x q @' -d
check_error " 9. override + create  = error"    "already overridden"  -s '@@x.k s : d @@ @@x ::: one*s* @@ @@x :: two*s* @@ @x q @' -d
check_warn  "10. override + override = warn+repl" "twoq"              -s '@@x.k s : d @@ @@x ::: one*s* @@ @@x ::: two*s* @@ @x q @' -d
check_eq    "11. override + default  = keep exist" "oneq"             -s '@@x.k s : d @@ @@x ::: one*s* @@ @@x :::: two*s* @@ @x q @' -d
check_eq    "12. default + create   = replace (silent)" "twoq"        -s '@@x s :::: one*s* @@ @@x s : two*s* @@ @x q @' -d
check_warn  "13. default + override  = warn+repl" "twoq"              -s '@@x.k s : d @@ @@x :::: one*s* @@ @@x ::: two*s* @@ @x q @' -d
check_error "14. default + default  = error"    "already defined"     -s '@@x s :::: one*s* @@ @@x s :::: two*s* @@ @x q @' -d

rm -f "$ERR"

echo
echo "============================="
echo "Results: ${green}$PASS passed${reset}, ${red}$FAIL failed${reset}"
[ $FAIL -eq 0 ]
