# Local CI entry point used by Git hooks. Pass args through to CraftOS tests.
ci *args: check-craftos check check-packages npm-build npm-test
    @just _craftos-test {{args}}
    @just test-integration

# Run all standard tests. Pass `--pretty` for grouped CraftOS output.
[positional-arguments]
test *args: build npm-test
    @just _craftos-test {{args}}

# Run end-to-end tests that span the trapos-server gateway / MCP endpoint and headless CraftOS-PC.
e2e: npm-test-integration

# Run integration and harness tests that are too broad for unit test targets.
test-integration: e2e test-timeout

# Run CraftOS-PC headless integration tests. Pass `--pretty` for grouped output.
[positional-arguments]
_craftos-test *args: stage
    #!/usr/bin/env bash
    set -uo pipefail

    if [ -f env.sample ]; then set -a; . ./env.sample; set +a; fi

    pretty=0
    has_output=0
    timeout_seconds="${TRAPOS_TEST_TIMEOUT_SECONDS:-3}"
    case "$timeout_seconds" in ''|*[!0-9]*) printf '%s\n' 'TRAPOS_TEST_TIMEOUT_SECONDS must be a positive integer' >&2; exit 1 ;; esac
    if [ "$timeout_seconds" -lt 1 ]; then printf '%s\n' 'TRAPOS_TEST_TIMEOUT_SECONDS must be >= 1' >&2; exit 1; fi

    runtest_args=("$@")
    for a in "$@"; do
        case "$a" in
            --pretty|--verbose|-v) pretty=1 ;;
            --output) has_output=1 ;;
        esac
    done
    if [ "$pretty" -eq 1 ] && [ "$has_output" -eq 0 ]; then
        runtest_args+=(--output /trapos-test-output)
    fi
    runtest_args+=(--shutdown)

    lua_quote() {
        local value="$1"
        value="${value//\\/\\\\}"
        value="${value//\'/\\\'}"
        printf "'%s'" "$value"
    }

    exec_code="shell.run('/programs/runtest.lua'"
    for arg in "${runtest_args[@]}"; do
        exec_code+=", $(lua_quote "$arg")"
    done
    exec_code+=")"

    rom_arg=()
    if [ "$(uname -s)" = "Darwin" ]; then
        rom_arg=(--rom /Applications/CraftOS-PC.app/Contents/Resources)
    fi
    repo='{{justfile_directory()}}'
    mount_arg=(--mount-ro "/trapos=$repo" --mount-ro "/apis=$repo/.stage/apis" --mount-ro "/programs=$repo/.stage/programs" --mount-ro "/startup=$repo/.stage/startup" --mount-ro "/tests=$repo/.stage/tests")
    tmp="$(mktemp)"
    data_dir="$(mktemp -d)"
    output_path="$data_dir/computer/0/trapos-test-output"

    craftos --directory "$data_dir" --headless "${rom_arg[@]}" "${mount_arg[@]}" --exec "$exec_code" >"$tmp" 2>&1 &
    pid="$!"
    ( sleep "$timeout_seconds"; kill -TERM "$pid" >/dev/null 2>&1 ) &
    watchdog="$!"
    wait "$pid" >/dev/null 2>&1
    status="$?"
    kill "$watchdog" >/dev/null 2>&1 || true
    wait "$watchdog" >/dev/null 2>&1 || true
    if grep -q __TRAPOS_TEST_OK__ "$tmp"; then
        if [ "$pretty" -eq 1 ] && [ -f "$output_path" ]; then cat "$output_path"; fi
        rm -f "$tmp"
        rm -rf "$data_dir"
    else
        red=$(printf '\033[31m'); reset=$(printf '\033[0m')
        if [ "$status" -eq 143 ]; then
            printf '%s\n' "${red}FAIL${reset} CraftOS integration tests timed out after ${timeout_seconds}s" >&2
        else
            printf '%s\n' "${red}FAIL${reset} CraftOS integration tests did not print __TRAPOS_TEST_OK__" >&2
        fi
        if [ -f "$output_path" ]; then cat "$output_path" >&2; fi
        cat "$tmp" >&2
        rm -f "$tmp"
        rm -rf "$data_dir"
        exit 1
    fi
    printf '%s\n' 'OK: CraftOS integration tests passed'

# Harness self-test: run a tests/harness fixture and assert which timeout layer
# caught it. `expect` is "lua" (libtest cancels the case) or "shell" (the shell
# watchdog kills the whole process). Not part of `ci`/`test`: these exercise the
# failure paths on purpose.
_timeout-fixture script shell_timeout extra_flag expect: check-install stage
    #!/usr/bin/env bash
    set -uo pipefail
    repo='{{justfile_directory()}}'
    if [ -f "$repo/env.sample" ]; then set -a; . "$repo/env.sample"; set +a; fi
    rom_arg=""
    if [ "$(uname -s)" = "Darwin" ]; then
        rom_arg="--rom /Applications/CraftOS-PC.app/Contents/Resources"
    fi
    mount_arg="--mount-ro /apis=$repo/.stage/apis --mount-ro /programs=$repo/.stage/programs --mount-ro /tests=$repo/.stage/tests"
    tmp="$(mktemp)"
    data_dir="$(mktemp -d)"
    output_path="$data_dir/computer/0/trapos-test-output"
    exec_code="shell.run('/programs/runtest.lua', '{{script}}', '--verbose', '--output', '/trapos-test-output', {{extra_flag}} '--shutdown')"
    shell_timeout="{{shell_timeout}}"
    craftos --directory "$data_dir" --headless $rom_arg $mount_arg --exec "$exec_code" >"$tmp" 2>&1 &
    pid="$!"
    ( sleep "$shell_timeout"; kill -TERM "$pid" >/dev/null 2>&1 ) &
    watchdog="$!"
    wait "$pid" >/dev/null 2>&1
    status="$?"
    kill "$watchdog" >/dev/null 2>&1 || true
    wait "$watchdog" >/dev/null 2>&1 || true
    combined="$(cat "$tmp"; [ -f "$output_path" ] && cat "$output_path")"
    red=$(printf '\033[31m'); green=$(printf '\033[32m'); reset=$(printf '\033[0m')
    rc=0
    case "{{expect}}" in
        lua)
            if printf '%s\n' "$combined" | grep -q 'libtest timeout' && [ "$status" -ne 143 ]; then
                printf '%s\n' "${green}OK${reset} libtest cancelled the case (shell watchdog not needed)"; \
            else
                printf '%s\n' "${red}FAIL${reset} expected a libtest timeout before the shell watchdog (status=$status)" >&2
                rc=1
            fi
            ;;
        shell)
            if [ "$status" -eq 143 ]; then
                printf '%s\n' "${green}OK${reset} shell watchdog killed the run after ${shell_timeout}s (status 143; libtest timeout bypassed)"; \
            else
                printf '%s\n' "${red}FAIL${reset} expected the shell watchdog to kill the run (status=$status)" >&2
                rc=1
            fi
            ;;
        *)
            printf '%s\n' "${red}FAIL${reset} unknown expectation '{{expect}}'" >&2
            rc=1
            ;;
    esac
    if [ "$rc" -ne 0 ]; then printf '%s\n' "$combined" >&2; fi
    rm -f "$tmp"
    rm -rf "$data_dir"
    exit "$rc"

# Prove the libtest (Lua) timeout layer: libtest cancels the slow case quickly,
# before the shell watchdog backstop can fire.
test-timeout-lua: (_timeout-fixture "/tests/harness/slow-case.lua" "${TRAPOS_TEST_TIMEOUT_WATCHDOG_SECONDS:-1}" "'--timeout', '0'," "lua")

# Prove the shell watchdog backstop: the slow case runs with the libtest timeout
# bypassed (--no-timeout), so the shell watchdog kills the whole process.
test-timeout-shell: (_timeout-fixture "/tests/harness/slow-case.lua" "${TRAPOS_TEST_TIMEOUT_WATCHDOG_SECONDS:-1}" "'--no-timeout'," "shell")

# Fast regression guard for both timeout layers. Wired into `ci`.
test-timeout: test-timeout-lua test-timeout-shell
