#!/usr/bin/env bash
set -euo pipefail
runtime_pkg_args=(--pkg phpef-runtime-core --pkg phpef-runtime-embed)
if [ -n "${PHPEF_RUNTIME_NATIVE_PACKAGES:-}" ]; then
    old_ifs=$IFS; IFS=:
    for native_pkg in $PHPEF_RUNTIME_NATIVE_PACKAGES; do
        [ -n "$native_pkg" ] && runtime_pkg_args+=(--pkg "$native_pkg")
    done
    IFS=$old_ifs
elif [ -n "${PHPEF_RUNTIME_NATIVE_VAPIS:-}" ]; then
    old_ifs=$IFS; IFS=:
    for native_vapi in $PHPEF_RUNTIME_NATIVE_VAPIS; do
        [ -n "$native_vapi" ] || continue
        native_pkg=$(basename "$native_vapi" .vapi)
        runtime_pkg_args+=(--pkg "$native_pkg")
    done
    IFS=$old_ifs
fi
build_root=$1
src=$2
valac_bin=$3
cli_dir="$build_root/src/cli"
php2ast="$cli_dir/phpefc-php2ast"
phpast2phpir="$cli_dir/phpefc-phpast2phpir"
phpir2valair="$cli_dir/phpefc-phpir2valair"
valair2vala="$cli_dir/phpefc-valair2vala"
valair_optimize="$cli_dir/phpefc-valair-optimize-typing"
for tool in "$php2ast" "$phpast2phpir" "$phpir2valair" "$valair_optimize" "$valair2vala"; do [ -x "$tool" ] || { echo "missing built tool: $tool" >&2; exit 99; }; done
work=${MESON_TEST_TMPDIR:-}
if [ -z "$work" ]; then work=$(mktemp -d); trap 'rm -rf "$work"' EXIT; fi
mkdir -p "$work"
bundle_field() {
    local bundle=$1 field=$2
    php -r '
        $wrapper = unserialize(file_get_contents($argv[1]));
        $field = $argv[2];
        if (!is_array($wrapper) || !array_key_exists($field, $wrapper) || !is_string($wrapper[$field])) {
            fwrite(STDERR, "missing bundle field: " . $field . "\n");
            exit(2);
        }
        echo $wrapper[$field];
    ' "$bundle" "$field"
}
bundle_assert_three_blocks() {
    local bundle=$1 first_field=$2 label=$3
    php -r '
        $wrapper = unserialize(file_get_contents($argv[1]));
        $first = $argv[2];
        $label = $argv[3];
        if (!is_array($wrapper)) { fwrite(STDERR, $label . ": bundle wrapper is not an array\n"); exit(2); }
        $keys = array_keys($wrapper);
        $expected = [$first, "pretty", "embed_serialized"];
        if ($keys !== $expected) { fwrite(STDERR, $label . ": expected 3 bundle blocks " . implode(",", $expected) . ", got " . implode(",", $keys) . "\n"); exit(2); }
        foreach ($expected as $key) { if (!is_string($wrapper[$key]) || $wrapper[$key] === "") { fwrite(STDERR, $label . ": empty/non-string bundle block: " . $key . "\n"); exit(2); } }
        if (array_key_exists("embed_serialized_encoding", $wrapper)) { fwrite(STDERR, $label . ": serialized payload must be raw, not hidden behind encoding metadata\n"); exit(2); }
        set_error_handler(static function () {});
        $embedded = unserialize($wrapper["embed_serialized"]);
        restore_error_handler();
        if ($embedded === false && $wrapper["embed_serialized"] !== serialize(false)) { fwrite(STDERR, $label . ": raw embed_serialized payload is not unserializable\n"); exit(2); }
    ' "$bundle" "$first_field" "$label"
}

compile_php() {
    local input=$1 vala=$2 bin=$3
    local phpast="$vala.phpast" phpir="$vala.phpir" valair="$vala.valair" opt_valair="$vala.valair.afteropt.typing-zval2static"
    "$php2ast" "$input" "$phpast" >/dev/null
    "$phpast2phpir" "$phpast" "$phpir" >/dev/null
    "$phpir2valair" "$phpir" "$valair" >/dev/null
    "$valair_optimize" "$valair" "$opt_valair" >/dev/null
    "$valair2vala" "$opt_valair" "$vala" >/dev/null
    "$valac_bin" "${valac_extra_args[@]}" "${runtime_pkg_args[@]}" "$vala" -o "$bin"
}
phpast="$work/class.phpast"; phpir="$work/class.phpir"; valair="$work/class.valair"; vala="$work/class.vala"; bin="$work/class-bin"; out="$work/out"
"$php2ast" "$src" "$phpast" >/dev/null
"$phpast2phpir" "$phpast" "$phpir" >/dev/null
bundle_assert_three_blocks "$phpir" pretty_phpir_dumper PHP-IR
phpir_pretty="$work/class.phpir.pretty"
phpir_full="$work/class.phpir.full"
bundle_field "$phpir" pretty_phpir_dumper > "$phpir_pretty"
bundle_field "$phpir" pretty > "$phpir_full"
if ! grep -a -Fq 'Program' "$phpir_pretty" || ! grep -a -Fq 'Class(testclass)' "$phpir_pretty" || ! grep -a -Fq 'Function(testfunc)' "$phpir_pretty" || ! grep -a -Fq 'Function(testinner)' "$phpir_pretty" || ! grep -a -Fq 'StaticCall(testclass)' "$phpir_pretty"; then
    echo 'PHP-IR compact pretty dump does not expose expanded class/method/static-call structure' >&2
    cat "$phpir_pretty" >&2
    exit 1
fi
if grep -a -Fq ' = ' "$phpir_pretty"; then
    echo 'PHP-IR compact pretty dump contains scalar metadata noise' >&2
    cat "$phpir_pretty" >&2
    exit 1
fi
for noisy_field in lines sourceLines mappings phpSourceFile sourceFile startLine endLine phpLine valaLine comments; do
    if grep -a -Fq "$noisy_field" "$phpir_pretty"; then
        echo "PHP-IR pretty dump leaks source/position/comment field: $noisy_field" >&2
        cat "$phpir_pretty" >&2
        exit 1
    fi
done
if ! grep -a -Fq 'startLine' "$phpir_full" || ! grep -a -Fq 'endLine' "$phpir_full"; then
    echo 'PHP-IR full pretty block no longer contains source positions' >&2
    cat "$phpir_full" >&2
    exit 1
fi
if ! grep -a -Fq 'startLine' "$phpir" || ! grep -a -Fq 'endLine' "$phpir"; then
    echo 'PHP-IR raw serialized payload no longer contains source positions' >&2
    cat "$phpir" >&2
    exit 1
fi
"$phpir2valair" "$phpir" "$valair" >/dev/null
"$valair_optimize" "$valair" "$valair.afteropt.typing-zval2static" >/dev/null
bundle_assert_three_blocks "$valair" pretty_valair_dumper Vala-IR
valair_pretty="$work/class.valair.pretty"
valair_full="$work/class.valair.full"
bundle_field "$valair" pretty_valair_dumper > "$valair_pretty"
bundle_field "$valair" pretty > "$valair_full"
if ! grep -a -Fq 'RuntimeVisibilityLiteral("PRIVATE")' "$valair_pretty" || ! grep -a -Fq 'RuntimeVisibilityLiteral("PUBLIC")' "$valair_pretty"; then echo 'Vala-IR compact pretty dump did not keep runtime visibility enum nodes' >&2; cat "$valair_pretty" >&2; exit 1; fi
if ! grep -a -Fq 'ClassDeclaration(testclass => phpef_' "$valair_pretty" || ! grep -a -Fq 'FunctionDeclaration(testclass::testfunc => testfunc)' "$valair_pretty" || ! grep -a -Fq 'FunctionDeclaration(testclass::testinner => testinner)' "$valair_pretty" || ! grep -a -Fq 'ClassMethodSummary(testfunc)' "$valair_pretty" || ! grep -a -Fq 'ClassMethodSummary(testinner)' "$valair_pretty"; then
    echo 'Vala-IR compact pretty dump does not expose class/method structure' >&2
    cat "$valair_pretty" >&2
    exit 1
fi
if grep -a -Fq ' = ' "$valair_pretty"; then
    echo 'Vala-IR compact pretty dump contains scalar metadata noise' >&2
    cat "$valair_pretty" >&2
    exit 1
fi
for noisy_field in lines sourceLines mappings phpSourceFile sourceFile startLine endLine phpLine valaLine comments; do
    if grep -a -Fq "$noisy_field" "$valair_pretty"; then
        echo "Vala-IR pretty dump leaks source/position/comment field: $noisy_field" >&2
        cat "$valair_pretty" >&2
        exit 1
    fi
done
if ! grep -a -Fq 'startLine' "$valair_full" || ! grep -a -Fq 'endLine' "$valair_full"; then
    echo 'Vala-IR full pretty block no longer contains source positions' >&2
    cat "$valair_full" >&2
    exit 1
fi
if ! grep -a -Fq 'startLine' "$valair" || ! grep -a -Fq 'endLine' "$valair"; then
    echo 'Vala-IR raw serialized payload no longer contains source positions' >&2
    cat "$valair" >&2
    exit 1
fi
"$valair2vala" "$valair.afteropt.typing-zval2static" "$vala" >/dev/null
if ! grep -Fq 'PhpefRuntimeCore.call_static_method("testclass", "testfunc"' "$vala"; then echo 'global public static call did not stay on ClassRegistry call_static_method' >&2; cat "$vala" >&2; exit 1; fi
if grep -Fq 'phpef_class_static_call_php__testclass.testfunc();' "$vala"; then echo 'global public static call was incorrectly optimized to a direct method call' >&2; cat "$vala" >&2; exit 1; fi
if grep -Fq 'PhpefRuntimeCore.call_static_method("testclass", "testinner"' "$vala" || ! grep -Fq 'testinner();' "$vala"; then echo 'scope-proven self private static call was not emitted as direct call' >&2; cat "$vala" >&2; exit 1; fi
if ! grep -Eq '^public class phpef_.*__testclass \{' "$vala" || ! grep -Eq '^    public static Zval testfunc\(\) throws Error \{' "$vala" || ! grep -Eq '^    private static Zval testinner\(\) throws Error \{' "$vala"; then echo 'PHP class static-method signatures/visibility were not emitted PHP-near in the Vala class container' >&2; cat "$vala" >&2; exit 1; fi
if grep -Eq '^    (public|private) static Zval test(func|inner)\(Zval\[] args\)' "$vala"; then echo 'PHP-near zero-argument static methods leaked runtime ABI args in their visible Vala signature' >&2; cat "$vala" >&2; exit 1; fi
if ! grep -Eq '^    private static Zval testfunc__handler\(Zval\[] args\) throws Error \{' "$vala" || ! grep -Eq '^    private static Zval testinner__handler\(Zval\[] args\) throws Error \{' "$vala" || ! grep -Fq 'return testfunc();' "$vala" || ! grep -Fq 'return testinner();' "$vala"; then echo 'runtime ABI handler thunks for zero-argument static methods were not emitted' >&2; cat "$vala" >&2; exit 1; fi
if ! grep -Fq 'register_static_method("testclass", "testfunc", testfunc__handler,' "$vala" || ! grep -Fq 'register_static_method("testclass", "testinner", testinner__handler,' "$vala"; then echo 'class registry did not register runtime handler thunks' >&2; cat "$vala" >&2; exit 1; fi
if grep -Eq '^public static Zval phpef_.*__testclass__testfunc\(' "$vala"; then echo 'old flat generated static-method function leaked at top level' >&2; cat "$vala" >&2; exit 1; fi
if ! grep -Fq 'register_class_symbols()' "$vala" || ! grep -Fq 'phpef_' "$vala" || ! grep -Fq '.register_class_symbols();' "$vala"; then echo 'class registration was not routed through the container register method' >&2; cat "$vala" >&2; exit 1; fi
if ! grep -Fq 'PhpefRuntimeCore.register_class("testclass")' "$vala" || ! grep -Fq 'PhpefRuntimeCore.register_static_method("testclass", "testfunc"' "$vala" || ! grep -Fq 'PhpefRuntimeCore.register_static_method("testclass", "testinner"' "$vala" || ! grep -Fq 'PhpefRuntimeCore.Visibility.PRIVATE)' "$vala" || ! grep -Fq 'PhpefRuntimeCore.Visibility.PUBLIC)' "$vala"; then echo 'class/method declarations were not registered through ClassRegistry with enum visibility metadata' >&2; cat "$vala" >&2; exit 1; fi
if grep -Eq 'register_static_method\([^;]*"private"\)' "$vala"; then echo 'runtime static-method visibility was emitted as string literal instead of enum' >&2; cat "$vala" >&2; exit 1; fi
if grep -Fq 'register_function("testclass::testfunc"' "$vala"; then echo 'old flat Class::method function registration leaked' >&2; cat "$vala" >&2; exit 1; fi
valac_extra_args=()
if [ -n "${PHPEF_RUNTIME_DEV_LIBDIR:-}" ]; then valac_extra_args+=("-X" "-Wl,-rpath,$PHPEF_RUNTIME_DEV_LIBDIR"); fi
if [ -n "${PHPEF_RUNTIME_VAPIDIR:-}" ]; then valac_extra_args+=("--vapidir=$PHPEF_RUNTIME_VAPIDIR"); else
    if [ -n "${PHPEF_RUNTIME_CORE_VAPI:-}" ]; then valac_extra_args+=("--vapidir=$(dirname "$PHPEF_RUNTIME_CORE_VAPI")"); fi
    if [ -n "${PHPEF_RUNTIME_EMBED_VAPI:-}" ]; then valac_extra_args+=("--vapidir=$(dirname "$PHPEF_RUNTIME_EMBED_VAPI")"); fi
    if [ -n "${PHPEF_RUNTIME_NATIVE_VAPIS:-}" ]; then old_ifs=$IFS; IFS=:; for native_vapi in $PHPEF_RUNTIME_NATIVE_VAPIS; do [ -n "$native_vapi" ] && valac_extra_args+=("--vapidir=$(dirname "$native_vapi")"); done; IFS=$old_ifs; fi
fi
"$valac_bin" "${valac_extra_args[@]}" "${runtime_pkg_args[@]}" "$vala" -o "$bin"
"$bin" > "$out"
[ "$(cat "$out")" = 'Klasse! Innen!' ] || { echo 'unexpected output' >&2; cat "$out" >&2; exit 1; }
external_src="$work/private_external.php"
cat > "$external_src" <<'PHP'
<?php
class testclass {
    public static function testfunc(){
        echo "public";
    }
    private static function testinner(){
        echo "private";
    }
}
testclass::testinner();
PHP
external_vala="$work/private_external.vala"; external_bin="$work/private_external-bin"; external_out="$work/private_external.out"
compile_php "$external_src" "$external_vala" "$external_bin"
if ! grep -Fq 'PhpefRuntimeCore.call_static_method("testclass", "testinner"' "$external_vala" || grep -Fq 'phpef_private_external_php__testclass.testinner();' "$external_vala"; then
    echo 'external private static call was incorrectly optimized instead of staying on ClassRegistry visibility check' >&2
    cat "$external_vala" >&2
    exit 1
fi
set +e
"$external_bin" >"$external_out" 2>&1
external_rc=$?
set -e
if [ "$external_rc" -eq 0 ] || ! grep -Fq 'Call to private method testclass::testinner() from global scope' "$external_out"; then
    echo 'external private static method call did not fail with visibility error' >&2
    cat "$external_vala" >&2
    cat "$external_out" >&2
    exit 1
fi
printf 'ok 1 - class-scope static calls optimize directly while global/external calls use ClassRegistry visibility
'
