# Justfile for cc-libs
# Run `just ci` to verify local tooling and lint Lua code.

# List available recipes.
default:
    @just --list

# Install local development tooling.
install: init-env install-git-hooks check-install

# Create a local environment file when one does not exist.
init-env:
    @if [ ! -f .env ]; then \
        cp .env.sample .env; \
        printf '%s\n' 'Created .env from .env.sample'; \
    fi

# Install Git hooks for this repository.
install-git-hooks:
    @mkdir -p .git/hooks
    @printf '%s\n' '#!/bin/sh' '' 'just ci' > .git/hooks/pre-commit
    @chmod +x .git/hooks/pre-commit
    @printf '%s\n' 'Installed .git/hooks/pre-commit'

# Verify the CraftOS-PC harness is installed and recent enough.
check-craftos:
    @command -v craftos >/dev/null 2>&1 || { \
        printf '%s\n' 'craftos not found on $PATH. See docs/install-craftos-pc.md.' >&2; \
        exit 1; \
    }
    @version="$(craftos --version)"; \
    number="${version##* v}"; \
    case "$number" in \
        *.*.*) \
            ;; \
        *) \
            printf '%s\n' "$version"; \
            printf '%s\n' 'Could not parse CraftOS-PC version. See docs/install-craftos-pc.md.' >&2; \
            exit 1; \
            ;; \
    esac; \
    major="${number%%.*}"; \
    rest="${number#*.}"; \
    minor="${rest%%.*}"; \
    patch="${rest#*.}"; \
    patch="${patch%%[^0-9]*}"; \
    printf '%s\n' "$version"; \
    case "$major.$minor.$patch" in \
        *[!0-9.]*|.*|*..*|*.) \
            printf '%s\n' 'Could not parse CraftOS-PC version. See docs/install-craftos-pc.md.' >&2; \
            exit 1; \
            ;; \
    esac; \
    if ! { [ "${major:-0}" -gt 2 ] || \
        { [ "${major:-0}" -eq 2 ] && [ "${minor:-0}" -gt 8 ]; } || \
        { [ "${major:-0}" -eq 2 ] && [ "${minor:-0}" -eq 8 ] && [ "${patch:-0}" -ge 3 ]; }; }; then \
        printf '%s\n' 'CraftOS-PC v2.8.3 or newer is required. See docs/install-craftos-pc.md.' >&2; \
        exit 1; \
    fi

# Verify jq is installed.
check-jq:
    @command -v jq >/dev/null 2>&1 || { \
        printf '%s\n' 'jq not found on $PATH. See DEVELOPMENT.md.' >&2; \
        exit 1; \
    }

# Verify luacheck is installed.
check-luacheck:
    @command -v luacheck >/dev/null 2>&1 || { \
        printf '%s\n' 'luacheck not found on $PATH. See DEVELOPMENT.md.' >&2; \
        exit 1; \
    }

# Verify tools needed for local installation and CraftOS-PC launch recipes.
check-install: check-craftos check-jq check-luacheck

# Pass args through to `craftos`, for example:
#   just craftos --headless --exec 'print("__TRAPOS_TEST_OK__"); os.shutdown()'
# Launch CraftOS-PC with repo-local data and read-only repo mounts.
[positional-arguments]
craftos *args: check-install
    #!/usr/bin/env bash
    set -euo pipefail
    repo='{{justfile_directory()}}'
    argv=(--directory "$repo/.craftos")
    if [ "$(uname -s)" = "Darwin" ]; then
        argv+=(--rom /Applications/CraftOS-PC.app/Contents/Resources)
    fi
    argv+=(--mount-ro "/trapos=$repo")
    while IFS= read -r dir; do
        argv+=(--mount-ro "/$dir=$repo/$dir")
    done < <(jq -r '.files[] | split("/")[0]' "$repo/manifest.json" | sort -u)
    exec craftos "${argv[@]}" "$@"

# Human-only interactive REPL. LLM agents must not execute this command.
repl:
    @just craftos --cli

# Local CI entry point used by Git hooks. Pass args through to `test`.
ci *args: check-craftos check
    @just test {{args}}
    @just test-timeout

# Run CraftOS-PC headless integration tests. Pass `--pretty` for grouped output.
test *args:
	@if [ -f .env ]; then set -a; . ./.env; set +a; fi; \
	pretty=0; \
	verbose=0; \
	timeout_seconds="${TRAP_CCLIBS_TEST_TIMEOUT_SECONDS:-7}"; \
	case "$timeout_seconds" in ''|*[!0-9]*) printf '%s\n' 'TRAP_CCLIBS_TEST_TIMEOUT_SECONDS must be a positive integer' >&2; exit 1 ;; esac; \
	if [ "$timeout_seconds" -lt 1 ]; then printf '%s\n' 'TRAP_CCLIBS_TEST_TIMEOUT_SECONDS must be >= 1' >&2; exit 1; fi; \
	for a in {{args}}; do case "$a" in --pretty) pretty=1 ;; --verbose|-v) pretty=1; verbose=1 ;; esac; done; \
	rom_arg=""; \
	if [ "$(uname -s)" = "Darwin" ]; then \
		rom_arg="--rom /Applications/CraftOS-PC.app/Contents/Resources"; \
	fi; \
	repo='{{justfile_directory()}}'; \
	mount_arg="--mount-ro /apis=$repo/apis --mount-ro /programs=$repo/programs --mount-ro /tests=$repo/tests"; \
	tmp="$(mktemp)"; \
	data_dir="$(mktemp -d)"; \
	output_path="$data_dir/computer/0/trapos-test-output"; \
	exec_code="shell.run('/programs/runtest.lua', '--shutdown')"; \
	if [ "$pretty" -eq 1 ]; then exec_code="shell.run('/programs/runtest.lua', '--pretty', '--output', '/trapos-test-output', '--shutdown')"; fi; \
	if [ "$verbose" -eq 1 ]; then exec_code="shell.run('/programs/runtest.lua', '--verbose', '--output', '/trapos-test-output', '--shutdown')"; fi; \
	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
    #!/usr/bin/env bash
    set -uo pipefail
    repo='{{justfile_directory()}}'
    rom_arg=""
    if [ "$(uname -s)" = "Darwin" ]; then
        rom_arg="--rom /Applications/CraftOS-PC.app/Contents/Resources"
    fi
    mount_arg="--mount-ro /apis=$repo/apis --mount-ro /programs=$repo/programs --mount-ro /tests=$repo/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')"
    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 at 2s,
# before the generous 5s shell watchdog backstop can fire.
test-timeout-lua: (_timeout-fixture "/tests/harness/slow-case.lua" "5" "'--timeout', '2'," "lua")

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

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

# Lint all Lua source with luacheck.
check: check-luacheck
    luacheck .
