#!/bin/bash
#
# alone_test.sh — Regression tests for an argument type's :alone value.
#
# An optional argument has three possible values, not two:
#
#   1. the option name is not written at all   -> the default
#   2. the name is written alone, with no value -> the type's :alone value
#   3. the name is written with a value         -> that value
#
# The :alone value is declared by the argument type (@@@argtype ... :alone),
# never by a klammer's parameter declaration.  A default is what a klammer
# means by silence and is properly per-klammer; a bare option name must read
# the same way in every klammer, or a writer cannot know what it means
# without consulting each signature.
#
# The bool type declares :alone true, which is where the convention that a
# bare boolean option means true comes from.  This is a declaration like any
# other type's, not an engine special case for booleans.
#
# A type whose pattern matches running text cannot declare an :alone value:
# an option's value runs to the next bar or option name, so ":opt some words"
# would take "some words" as the value and the alone value would never be
# reached.  That is a definition-time error.
#
# Engine tier: these tests run with --klammersets none and define their own argtypes
# and klammers inline, so they do not depend on the Standard Klammer Set.
#
# Usage: ./alone_test.sh   (LSan suppressions come from env/runtime.env)
# Exit code: 0 if all tests pass, 1 otherwise.

PASS=0
FAIL=0
KTEXT=ktext
K=${KLAMMERTEXT_HOME:?KLAMMERTEXT_HOME must be set}

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

# strip leading/trailing blank lines and surrounding 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 TEST_NAME EXPECTED KTEXT_ARGS...
#   Runs ktext, expects exit status 0, and compares trimmed stdout to EXPECTED.
check_eq() {
    local test_name="$1"
    local expected="$2"
    shift 2

    local output status
    output=$("$KTEXT" "$@" 2>/tmp/alone_test_err.$$)
    status=$?
    output=$(printf '%s' "$output" | trim)

    if [ $status -ne 0 ]; then
        echo "${red}FAIL${reset} $test_name — ktext exited $status"
        echo "       stderr: $(head -3 /tmp/alone_test_err.$$)"
        FAIL=$((FAIL + 1))
        return
    fi
    if [ "$output" = "$expected" ]; then
        echo "${green}PASS${reset} $test_name"
        PASS=$((PASS + 1))
    else
        echo "${red}FAIL${reset} $test_name"
        echo "       expected: [$expected]"
        echo "       got:      [$output]"
        FAIL=$((FAIL + 1))
    fi
}

# check_error TEST_NAME PATTERN KTEXT_ARGS...
#   Runs ktext, expects a NONZERO exit status and PATTERN in the message.
check_error() {
    local test_name="$1"
    local pattern="$2"
    shift 2

    local output status
    output=$("$KTEXT" "$@" 2>&1)
    status=$?

    if [ $status -eq 0 ]; then
        echo "${red}FAIL${reset} $test_name — expected an error but ktext succeeded"
        FAIL=$((FAIL + 1))
        return
    fi
    if echo "$output" | tr '\n' ' ' | tr -s ' ' | grep -qF "$pattern"; then
        echo "${green}PASS${reset} $test_name"
        PASS=$((PASS + 1))
    else
        echo "${red}FAIL${reset} $test_name — expected error to contain [$pattern]"
        echo "       output: $(echo "$output" | head -5)"
        FAIL=$((FAIL + 1))
    fi
}

# A type with both a default and an alone value, and a klammer using it.
DEPTH='@@@argtype depth | a table of contents depth :pattern \d+ :default 0 :alone 3 @@@'
DK='@@d :n.depth : [*n*] @@'

# A type with an alone value but no default.
MARK='@@@argtype mark | a mark character :pattern [-*+] :alone * @@@'
MK='@@m :c.mark : [*c*] @@'

echo "${bold}Argument type :alone value tests${reset}"
echo "================================"
echo

# --- The three values of an optional argument ---

check_eq \
    " 1. name absent — the default" \
    "[0]" \
    --klammersets none -s "$DEPTH $DK @d@" -d

check_eq \
    " 2. name written alone — the type's alone value" \
    "[3]" \
    --klammersets none -s "$DEPTH $DK @d :n @" -d

check_eq \
    " 3. name written with a value — that value" \
    "[7]" \
    --klammersets none -s "$DEPTH $DK @d :n 7 @" -d

# --- A type with an alone value but no default ---

check_eq \
    " 4. no default declared — absent is empty" \
    "[]" \
    --klammersets none -s "$MARK $MK @m@" -d

check_eq \
    " 5. no default declared — alone still applies" \
    "[*]" \
    --klammersets none -s "$MARK $MK @m :c @" -d

# --- The alone value belongs to the type, so every parameter of that
#     type gets it, and a parameter default does not disturb it ---

check_eq \
    " 6. two parameters of one type share the alone value" \
    "[3][3]" \
    --klammersets none -s "$DEPTH @@d2 :a.depth :b.depth : [*a*][*b*] @@ @d2 :a :b @" -d

check_eq \
    " 7. a parameter default overrides the type default, not the alone value" \
    "[5]|[3]" \
    --klammersets none -s "$DEPTH @@d3 :n.depth 5 : [*n*] @@ @d3@|@d3 :n @" -d

# --- bool: the convention that a bare boolean option means true ---

check_eq \
    " 8. bool written alone is true" \
    "[true]" \
    --klammersets none -s '@@b :f.bool : [*f*] @@ @b :f @' -d

check_eq \
    " 9. bool written with false stays false" \
    "[false]" \
    --klammersets none -s '@@b :f.bool : [*f*] @@ @b :f false @' -d

check_eq \
    "10. bool absent with no default is empty" \
    "[]" \
    --klammersets none -s '@@b :f.bool : [*f*] @@ @b@' -d

check_eq \
    "11. bool absent with a true parameter default" \
    "[true]" \
    --klammersets none -s '@@b :f.bool true : [*f*] @@ @b@' -d

check_eq \
    "12. bool false explicitly against a true default" \
    "[false]" \
    --klammersets none -s '@@b :f.bool true : [*f*] @@ @b :f false @' -d

# --- The value reaching @eval ---

check_eq \
    "13. python value of a bool written alone" \
    "True" \
    --klammersets none -s '@@b :f.bool : @eval repr(K.f) @ @@ @b :f @' -d

check_eq \
    "14. python value of an absent bool" \
    "None" \
    --klammersets none -s '@@b :f.bool : @eval repr(K.f) @ @@ @b@' -d

check_eq \
    "15. python value of a user type written alone" \
    "'3'" \
    --klammersets none -s "$DEPTH @@d4 :n.depth : @eval repr(K.n) @ @@ @d4 :n @" -d

# --- Types that cannot delimit a bare option name ---

check_error \
    "16. :alone refused on a type matching running text" \
    "cannot declare an :alone value" \
    --klammersets none -s '@@@argtype loose | anything at all :alone x @@@' -d

check_error \
    "17. :alone refused on an explicit match-everything pattern" \
    "cannot declare an :alone value" \
    --klammersets none -s '@@@argtype loose | anything :pattern (?:.^|\n)* :alone x @@@' -d

check_error \
    "18. an alone value must match its own type's pattern" \
    "does not match its own pattern" \
    --klammersets none -s '@@@argtype depth | a depth :pattern \d+ :alone many @@@' -d

# --- Delimitation: an option value still runs to the next bar or option
#     name, so text after a bare name is taken as the value and rejected
#     by the type.  This is what makes the guard above necessary.

check_error \
    "19. text after a bare option name is taken as its value" \
    "does not match" \
    --klammersets none -s "$DEPTH $DK @d :n some words @" -d

check_eq \
    "20. a bar separates a bare option name from following text" \
    "[3]two" \
    --klammersets none -s "$DEPTH @@d5 :n.depth | t : [*n*]*t* @@ @d5 :n | two @" -d

# --- A type with no alone value is unchanged: a bare name is empty ---

check_eq \
    "21. bare option of a type with no alone value is empty" \
    "[]" \
    --klammersets none -s '@@s :t.word : [*t*] @@ @s :t @' -d

echo
# --- The pattern itself is validated at definition time (gallery review) ---

check_error \
    "22. an invalid :pattern is a definition-time error" \
    "is not a valid regular expression" \
    --klammersets none -s '@@@argtype bad | d :pattern ([ @@@' -d

echo
echo "================================"
echo "Passed: $PASS  Failed: $FAIL"
rm -f /tmp/alone_test_err.$$
[ $FAIL -eq 0 ]
