#!/usr/bin/env bash
set -euo pipefail

build_root=$1

cli_dir="$build_root/src/cli"
php2ast="$cli_dir/phpefc-php2ast"
phpast2phpir="$cli_dir/phpefc-phpast2phpir"
phpir2valair="$cli_dir/phpefc-phpir2valair"
valair_optimize="$cli_dir/phpefc-valair-optimize-typing"
valair_optimize_library="$cli_dir/phpefc-valair-optimize-library"
valair2vala="$cli_dir/phpefc-valair2vala"

for tool in "$php2ast" "$phpast2phpir" "$phpir2valair" "$valair_optimize" "$valair_optimize_library" "$valair2vala"; do
    if [ ! -x "$tool" ]; then
        echo "missing built tool: $tool" >&2
        exit 99
    fi
done

script_dir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
project_root=$(dirname "$script_dir")

work=${MESON_TEST_TMPDIR:-}
if [ -z "$work" ]; then
    work=$(mktemp -d)
    trap 'rm -rf "$work"' EXIT
fi
mkdir -p "$work"

src="$work/typing-phpfunc-fast-path.php"
phpast="$work/typing-phpfunc-fast-path.phpast"
phpir="$work/typing-phpfunc-fast-path.phpir"
valair="$work/typing-phpfunc-fast-path.valair"
afteropt="$work/typing-phpfunc-fast-path.valair.afteropt.typing-zval2static"
vala="$work/typing-phpfunc-fast-path.vala"
stderr_file="$work/typing-phpfunc-fast-path.stderr"
marker_regex='// phpefc optimizer typing-zval2static result: [0-9]+/[0-9]+ zvals left'

cat >"$src" <<'PHP'
<?php
echo "Hallo";
strlen("abcdef");
gethostbyname("localhost");
microtime();
microtime(false);
microtime(true);
echo microtime();
echo microtime(false);
echo microtime(true);
PHP

php -d detect_unicode=0 <<PHP
<?php
require '${project_root}/src/valair/php2vala_func_resolver.php';
\$resolver = Php2ValaFuncResolver::default();
\$expected = [
    'strlen' => ['runtime' => 'phpfunc_strlen_typed_int_arg1_string', 'return' => 'int', 'args' => ['string'], 'kind' => 'static_php_call'],
    'gethostbyname' => ['runtime' => 'phpfunc_gethostbyname_typed_string_arg1_string', 'return' => 'string', 'args' => ['string'], 'kind' => 'static_php_call'],
    'echo' => ['runtime' => 'phpfunc_echo_typed_void_arg1_string', 'return' => 'void', 'args' => ['string'], 'kind' => 'native'],
];
foreach (\$expected as \$name => \$expect) {
    \$found = null;
    foreach (\$resolver->resolveRuntimeFunctionCandidates(\$name, 1) as \$candidate) {
        if ((\$candidate['runtimeName'] ?? null) === \$expect['runtime']) {
            \$found = \$candidate;
            break;
        }
    }
    if (\$found === null) {
        fwrite(STDERR, "resolver did not expose typed candidate for {\$name}\n");
        exit(1);
    }
    \$signature = \$found['typedSignature'] ?? null;
    \$metadata = \$found['runtimeMetadata'] ?? [];
    if (!is_array(\$signature) || (\$signature['returnType'] ?? null) !== \$expect['return'] || (\$signature['argumentTypes'] ?? null) !== \$expect['args']) {
        fwrite(STDERR, "resolver typed signature mismatch for {\$name}: " . json_encode(\$signature) . "\n");
        exit(1);
    }
    if (!is_array(\$metadata) || (\$metadata['typed_fast_path'] ?? null) !== \$expect['kind']) {
        fwrite(STDERR, "resolver RuntimeEmbed metadata mismatch for {\$name}: " . json_encode(\$metadata) . "\n");
        exit(1);
    }
}
if (!\$resolver->hasRuntimeFunction('microtime', 0) || !\$resolver->hasRuntimeFunction('microtime', 1)) {
    fwrite(STDERR, "resolver did not expose Zval-compatible microtime wrapper for 0/1 args\n");
    exit(1);
}
\$microtimeFastPaths = [
    'default string' => [[], 'string', 'PhpefRuntimeEmbed.phpfunc_microtime_typed_string', false],
    'false string' => [['bool:false'], 'string', 'PhpefRuntimeEmbed.phpfunc_microtime_typed_string_arg1_bool_false', false],
    'true double' => [['bool:true'], 'double', 'PhpefRuntimeEmbed.phpfunc_microtime_typed_double_arg1_bool_true', false],
];
foreach (\$microtimeFastPaths as \$label => \$expect) {
    [\$args, \$returnType, \$callee, \$passArguments] = \$expect;
    \$found = \$resolver->resolveTypedFastPath('microtime', \$args, ['native'], \$returnType);
    if (!is_array(\$found) || (\$found['callee'] ?? null) !== \$callee || (\$found['passArguments'] ?? null) !== \$passArguments) {
        fwrite(STDERR, "resolver did not expose microtime {\$label}: " . json_encode(\$found) . "\n");
        exit(1);
    }
}
if (getenv('PHPEF_RUNTIME_NATIVE_VAPIS') !== false && getenv('PHPEF_RUNTIME_NATIVE_VAPIS') !== '') {
    \$constantFastPath = \$resolver->resolveTypedFastPath('constant', ['string'], ['native'], 'zval');
    if (!is_array(\$constantFastPath) || (\$constantFastPath['callee'] ?? null) !== 'PhpefRuntimeNativeStandard.phpfunc_constant_typed_zval_arg1_string') {
        fwrite(STDERR, "resolver did not expose NativeStandard typed constant fast path: " . json_encode(\$constantFastPath) . "\n");
        exit(1);
    }
}
PHP

"$php2ast" "$src" "$phpast" >/dev/null
"$phpast2phpir" "$phpast" "$phpir" >/dev/null
"$phpir2valair" "$phpir" "$valair" >/dev/null
if ! "$valair_optimize" "$valair" "$afteropt" >/dev/null 2>"$stderr_file"; then
    echo "typing optimizer failed" >&2
    cat "$stderr_file" >&2
    exit 1
fi
if ! "$valair2vala" "$afteropt" "$vala" >/dev/null; then
    echo "valair2vala failed" >&2
    exit 1
fi

if ! grep -a -Fq 'phpfunc_echo_typed_void_arg1_string' "$afteropt"; then
    echo 'expected typed echo fast path in optimized ValaIR bundle' >&2
    cat "$afteropt" >&2
    exit 1
fi
if ! grep -Fq 'PhpefRuntimeEmbed.phpfunc_echo_typed_void_arg1_string("Hallo");' "$vala"; then
    echo 'expected typed echo fast path in generated Vala' >&2
    cat "$vala" >&2
    exit 1
fi
for expected_typed_call in     'phpfunc_strlen_typed_int_arg1_string("abcdef");'     'phpfunc_gethostbyname_typed_string_arg1_string("localhost");'     'PhpefRuntimeEmbed.phpfunc_microtime_typed_string();'     'PhpefRuntimeEmbed.phpfunc_microtime_typed_string_arg1_bool_false();'     'PhpefRuntimeEmbed.phpfunc_microtime_typed_double_arg1_bool_true();'     'PhpefRuntimeEmbed.phpfunc_echo_typed_void_arg1_string(PhpefRuntimeEmbed.phpfunc_microtime_typed_string());'     'PhpefRuntimeEmbed.phpfunc_echo_typed_void_arg1_string(PhpefRuntimeEmbed.phpfunc_microtime_typed_string_arg1_bool_false());'
do
    if ! grep -Fq "$expected_typed_call" "$vala"; then
        echo "expected typed RuntimeEmbed call in generated Vala: $expected_typed_call" >&2
        cat "$vala" >&2
        exit 1
    fi
done
if grep -Fq 'phpfunc_echo(Zval.from_string("Hallo"));' "$vala"; then
    echo 'generic Zval echo call was not rewritten in generated Vala' >&2
    cat "$vala" >&2
    exit 1
fi
for forbidden_call in     'PhpefRuntimeEmbed.phpfunc_microtime();'     'PhpefRuntimeEmbed.phpfunc_microtime(Zval.from_bool(false));'
do
    if grep -Fq "$forbidden_call" "$vala"; then
        echo "generic Zval microtime call was not rewritten in generated Vala: $forbidden_call" >&2
        cat "$vala" >&2
        exit 1
    fi
done
if ! grep -Fq 'PhpefRuntimeEmbed.phpfunc_echo(PhpefRuntimeEmbed.phpfunc_microtime_typed_double_arg1_bool_true());' "$vala"; then
    echo 'expected echo microtime(true) to stay Zval-compatible until a typed double echo exists' >&2
    cat "$vala" >&2
    exit 1
fi
if grep -a -Fq 'Zval.from_string' "$afteropt"; then
    echo 'optimized ValaIR bundle still contains Zval.from_string for echo literal' >&2
    cat "$afteropt" >&2
    exit 1
fi
marker=$(grep -E "$marker_regex" "$vala" | tail -n 1 || true)
counts=$(printf '%s\n' "$marker" | sed -n 's#.* result: \([0-9][0-9]*\)/\([0-9][0-9]*\) zvals left#\1 \2#p')
if [ -z "$counts" ]; then
    echo "could not parse typing optimizer marker count: $marker" >&2
    cat "$vala" >&2
    exit 1
fi
set -- $counts
after=$1
before=$2
if [ "$after" -ge "$before" ]; then
    echo "expected typed fast path to reduce zval count, got $after/$before" >&2
    cat "$vala" >&2
    exit 1
fi
if [ "$after" -gt 0 ]; then
    expected_warning="WARNING: PERFORMANCE NOT OPTIMIZED THROUGH DYNAMIC ZVALS ($after/$before zvals left)"
    if ! grep -Fq "$expected_warning" "$stderr_file"; then
        echo "expected typing optimizer warning: $expected_warning" >&2
        cat "$stderr_file" >&2
        exit 1
    fi
else
    if grep -Fq 'WARNING: PERFORMANCE NOT OPTIMIZED THROUGH DYNAMIC ZVALS' "$stderr_file"; then
        echo 'did not expect dynamic-zval warning when no zvals remain' >&2
        cat "$stderr_file" >&2
        exit 1
    fi
fi

native_src="$work/typing-native-standard-class-concat.php"
native_phpast="$work/typing-native-standard-class-concat.phpast"
native_phpir="$work/typing-native-standard-class-concat.phpir"
native_valair="$work/typing-native-standard-class-concat.valair"
native_after_first="$work/typing-native-standard-class-concat.valair.afteropt.typing-zval2static"
native_after_library="$work/typing-native-standard-class-concat.valair.afteropt.library-embed2native"
native_after_final="$work/typing-native-standard-class-concat.valair.afteropt.typing-zval2static-final"
native_vala="$work/typing-native-standard-class-concat.vala"

cat >"$native_src" <<'PHP'
<?php
class NativeStandardConcatProbe
{
    public static function run()
    {
        echo "Tests - Success." . constant("PHP_EOL");
    }
}
NativeStandardConcatProbe::run();
PHP

"$php2ast" "$native_src" "$native_phpast" >/dev/null
"$phpast2phpir" "$native_phpast" "$native_phpir" >/dev/null
"$phpir2valair" "$native_phpir" "$native_valair" >/dev/null
"$valair_optimize" --suppress-warning "$native_valair" "$native_after_first" >/dev/null
"$valair_optimize_library" --allowed-native-runtimes standard "$native_after_first" "$native_after_library" >/dev/null
"$valair_optimize" "$native_after_library" "$native_after_final" >/dev/null 2>"$work/typing-native-standard-class-concat.stderr"
"$valair2vala" "$native_after_final" "$native_vala" >/dev/null

expected_native_constant='PhpefRuntimeNativeStandard.phpfunc_constant_typed_zval_arg1_string("PHP_EOL")'
if ! grep -Fq "$expected_native_constant" "$native_vala"; then
    echo "expected typed NativeStandard constant inside class-method concat in generated Vala" >&2
    cat "$native_vala" >&2
    exit 1
fi
if grep -Fq 'PhpefRuntimeNativeStandard.phpfunc_constant(Zval.from_string("PHP_EOL"))' "$native_vala"; then
    echo 'generic NativeStandard constant remained inside class-method concat in generated Vala' >&2
    cat "$native_vala" >&2
    exit 1
fi

printf 'ok - typing-zval2static recognizes RuntimeEmbed typed names and final NativeStandard class-method concat constant (%s/%s zvals left)\n' "$after" "$before"
