<?php

/**
 * Fast-path runtime optimizer for structurally proven RuntimeCore dispatch calls.
 *
 * This pass intentionally does not change the PHP/Zval ABI yet. It only skips
 * RuntimeCore registry dispatch when the receiver, class and method target are
 * statically known from the current ValaIR class declaration.
 */
final class ValaIrFastPathRuntimeOptimizer
{
    private int $lastRewriteCount = 0;
    private int $lastRemainingRuntimeDispatchCount = 0;
    private ?Php2ValaFuncResolver $funcResolver;
    private array $externalClassMethodMap;
    private array $externalClassRegisterFunctionMap;

    /**
     * Create the optimizer with an optional runtime function resolver.
     *
     * @param Php2ValaFuncResolver|null $funcResolver Resolver used to detect native typed fast paths; when omitted the default resolver is used when available.
     * @param array $externalValaIrs Other source-unit ValaIR programs from the same build target; used only for include/registration-proven class dispatch.
     */
    public function __construct(?Php2ValaFuncResolver $funcResolver = null, array $externalValaIrs = [])
    {
        $this->externalClassMethodMap = [];
        $this->externalClassRegisterFunctionMap = [];
        foreach ($externalValaIrs as $externalValaIr) {
            if (!$externalValaIr instanceof ValaIrProgram) {
                continue;
            }
            $externalStmts = $this->prop($externalValaIr, "stmts");
            $this->externalClassMethodMap = array_replace($this->externalClassMethodMap, $this->classMethodMap($externalStmts));
            $this->externalClassRegisterFunctionMap = array_replace($this->externalClassRegisterFunctionMap, $this->classRegisterFunctionMap($externalStmts));
        }

        if ($funcResolver !== null) {
            $this->funcResolver = $funcResolver;
            return;
        }
        try {
            $this->funcResolver = Php2ValaFuncResolver::default();
        } catch (Throwable) {
            $this->funcResolver = null;
        }
    }

    /** Apply fast-path runtime rewrites and append a result marker. */
    public function optimize(object $valaIr): object
    {
        $this->lastRewriteCount = 0;
        $optimized = $this->optimizeNode($valaIr, null);
        $this->lastRemainingRuntimeDispatchCount = $this->countRuntimeDispatchCalls($optimized);

        if (!method_exists($optimized, 'withAppendedComment')) {
            return $optimized;
        }

        return $optimized->withAppendedComment(new ValaIrComment(
            'OptimizerMarker',
            '// phpefc optimizer fastpath-runtime result: ' . $this->getLastResultText(),
            0,
            0,
        ));
    }

    /** Return how many RuntimeCore dispatch calls this pass rewrote in the last run. */
    public function getLastRewriteCount(): int
    {
        return $this->lastRewriteCount;
    }

    /** Return how many RuntimeCore dispatch calls remain after the last run. */
    public function getLastRemainingRuntimeDispatchCount(): int
    {
        return $this->lastRemainingRuntimeDispatchCount;
    }

    /** Return the total RuntimeCore dispatch calls seen by the last run. */
    public function getLastTotalRuntimeDispatchCount(): int
    {
        return $this->lastRewriteCount + $this->lastRemainingRuntimeDispatchCount;
    }

    /** Return the human-readable X/Y optimizer result text for warnings and markers. */
    public function getLastResultText(): string
    {
        return $this->lastRemainingRuntimeDispatchCount . '/' . $this->getLastTotalRuntimeDispatchCount() . ' runtime dispatch calls left';
    }

    /** Return whether any RuntimeCore dispatch calls remain after the last run. */
    public function hasRemainingRuntimeDispatchCalls(): bool
    {
        return $this->lastRemainingRuntimeDispatchCount > 0;
    }

    private function optimizeNode(object $node, ?array $classContext): object
    {
        if ($node instanceof ValaIrProgram) {
            $stmts = $this->prop($node, 'stmts');
            $allMethods = array_replace($this->externalClassMethodMap, $this->classMethodMap($stmts));
            $registerFunctions = array_replace($this->externalClassRegisterFunctionMap, $this->classRegisterFunctionMap($stmts));
            $programContext = [
                'phpClassName' => '',
                'methods' => [],
                'allMethods' => $allMethods,
                'classRegisterFunctions' => $registerFunctions,
            ];
            return $this->withProp($node, 'stmts', $this->optimizeFunctionStmtList($stmts, $programContext));
        }

        if ($node instanceof ValaIrClassDeclaration) {
            $classArray = $node->toArray();
            $context = [
                'phpClassName' => (string) ($classArray['phpClassName'] ?? $classArray['phpName'] ?? ''),
                'methods' => $this->methodMap($classArray),
                'allMethods' => is_array($classContext['allMethods'] ?? null) ? $classContext['allMethods'] : [],
                'classRegisterFunctions' => is_array($classContext['classRegisterFunctions'] ?? null) ? $classContext['classRegisterFunctions'] : [],
            ];
            $methods = $this->optimizeStmtList($this->prop($node, 'methods'), $context);
            $methods = $this->addNativeIntMethodHelpers($methods);
            $methods = $this->addStringLiteralDispatchSpecializations($methods);
            return $this->withProp($node, 'methods', $methods);
        }

        if ($node instanceof ValaIrFunctionDeclaration) {
            return $this->withProp($node, 'stmts', $this->optimizeFunctionStmtList($this->prop($node, 'stmts'), $classContext));
        }

        if ($node instanceof ValaIrVariableDeclaration || $node instanceof ValaIrAssignmentStatement) {
            return $this->withProp($node, 'expr', $this->optimizeExpr($this->prop($node, 'expr'), $classContext));
        }

        if ($node instanceof ValaIrReturnStatement) {
            $expr = $this->prop($node, 'expr');
            return is_object($expr) ? $this->withProp($node, 'expr', $this->optimizeExpr($expr, $classContext)) : $node;
        }

        if ($node instanceof ValaIrCallStatement) {
            $optimized = $this->withProp($node, 'args', $this->optimizeExprList($this->prop($node, 'args'), $classContext));
            $dynamicFastPath = $this->rewriteDynamicSelfDispatchCallStatement($optimized, $classContext);
            if ($dynamicFastPath !== $optimized) {
                return $dynamicFastPath;
            }
            $knownSelfFastPath = $this->rewriteKnownSelfDispatchCallStatement($optimized, $classContext);
            if ($knownSelfFastPath !== $optimized) {
                return $knownSelfFastPath;
            }
            return $this->rewriteKnownStaticDispatchCallStatement($optimized, $classContext);
        }

        if ($node instanceof ValaIrThrowStatement) {
            return $node;
        }

        if ($node instanceof ValaIrIfStatement) {
            $optimized = $this->withProp($node, 'condition', $this->optimizeExpr($this->prop($node, 'condition'), $classContext));
            $optimized = $this->withProp($optimized, 'stmts', $this->optimizeStmtList($this->prop($optimized, 'stmts'), $classContext));
            $optimized = $this->withProp($optimized, 'elseifs', $this->optimizeElseifs($this->prop($optimized, 'elseifs'), $classContext));
            return $this->withProp($optimized, 'elseStmts', $this->optimizeStmtList($this->prop($optimized, 'elseStmts'), $classContext));
        }

        if ($node instanceof ValaIrWhileStatement) {
            $optimized = $this->withProp($node, 'condition', $this->optimizeExpr($this->prop($node, 'condition'), $classContext));
            return $this->withProp($optimized, 'stmts', $this->optimizeStmtList($this->prop($optimized, 'stmts'), $classContext));
        }

        if ($node instanceof ValaIrForeachValueStatement) {
            $optimized = $this->withProp($node, 'expr', $this->optimizeExpr($this->prop($node, 'expr'), $classContext));
            return $this->withProp($optimized, 'stmts', $this->optimizeStmtList($this->prop($optimized, 'stmts'), $classContext));
        }

        if ($node instanceof ValaIrTryCatchStatement) {
            $optimized = $this->withProp($node, 'tryStmts', $this->optimizeStmtList($this->prop($node, 'tryStmts'), $classContext));
            return $this->withProp($optimized, 'catchStmts', $this->optimizeStmtList($this->prop($optimized, 'catchStmts'), $classContext));
        }

        return $node;
    }

    private function optimizeCatchArms(array $arms, ?array $classContext): array
    {
        $changed = false;
        $optimized = [];
        foreach ($arms as $arm) {
            if (!is_array($arm)) {
                $optimized[] = $arm;
                continue;
            }
            $newArm = $arm;
            $newArm['stmts'] = $this->optimizeStmtList(is_array($arm['stmts'] ?? null) ? $arm['stmts'] : [], $classContext);
            if ($newArm !== $arm) {
                $changed = true;
            }
            $optimized[] = $newArm;
        }
        return $changed ? $optimized : $arms;
    }

    private function optimizeStmtList(array $stmts, ?array $classContext): array
    {
        $changed = false;
        $optimized = [];
        foreach ($stmts as $stmt) {
            if (!is_object($stmt)) {
                $optimized[] = $stmt;
                continue;
            }
            $newStmt = $this->optimizeNode($stmt, $classContext);
            $changed = $changed || $newStmt !== $stmt;
            $optimized[] = $newStmt;
        }
        return $changed ? $optimized : $stmts;
    }

    private function optimizeFunctionStmtList(array $stmts, ?array $classContext): array
    {
        if ($classContext === null) {
            return $this->optimizeStmtList($stmts, null);
        }

        $changed = false;
        $optimized = [];
        $localObjectClasses = [];
        $registeredClasses = [];
        foreach ($stmts as $stmt) {
            if (!is_object($stmt)) {
                $optimized[] = $stmt;
                continue;
            }

            $stmtContext = $this->withFunctionKnowledge($classContext, $localObjectClasses, $registeredClasses);
            $newStmt = $this->optimizeNode($stmt, $stmtContext);
            $changed = $changed || $newStmt !== $stmt;
            $optimized[] = $newStmt;
            $this->updateLocalObjectClassesFromStmt($newStmt, $classContext, $localObjectClasses);
            $this->updateRegisteredClassesFromStmt($newStmt, $classContext, $registeredClasses);
        }

        return $changed ? $optimized : $stmts;
    }


    private function addNativeIntMethodHelpers(array $methods): array
    {
        $names = $this->functionNameSet($methods);
        $helpers = [];
        foreach ($methods as $method) {
            if (!$method instanceof ValaIrFunctionDeclaration) {
                continue;
            }
            $helper = $this->nativeIntHelperForMethod($method, $names);
            if ($helper !== null) {
                $helpers[(string) ($method->toArray()['name'] ?? '')] = $helper;
            }
        }
        if ($helpers === []) {
            return $methods;
        }

        $out = [];
        foreach ($methods as $method) {
            if (!$method instanceof ValaIrFunctionDeclaration) {
                $out[] = $method;
                continue;
            }
            $name = (string) ($method->toArray()['name'] ?? '');
            if (isset($helpers[$name])) {
                $out[] = $this->wrapNativeIntMethod($method, $this->nativeIntHelperName($name));
                continue;
            }
            $out[] = $method;
        }

        foreach ($helpers as $helper) {
            $out[] = $helper;
            $this->lastRewriteCount++;
        }
        return $out;
    }

    private function functionNameSet(array $methods): array
    {
        $names = [];
        foreach ($methods as $method) {
            if (!$method instanceof ValaIrFunctionDeclaration) {
                continue;
            }
            $methodArray = $method->toArray();
            $name = (string) ($methodArray['name'] ?? '');
            if ($name === '') {
                continue;
            }
            $names[$name] = $name;
            $qualifiedName = (string) ($methodArray['qualifiedName'] ?? '');
            if ($qualifiedName !== '') {
                $names[$qualifiedName] = $name;
            }
        }
        return $names;
    }

    private function nativeIntHelperForMethod(ValaIrFunctionDeclaration $method, array $methodNames): ?ValaIrFunctionDeclaration
    {
        $array = $method->toArray();
        $name = (string) ($array['name'] ?? '');
        if ($name === '' || str_ends_with($name, '__phpefc_int')) {
            return null;
        }
        $valaParams = $array['valaParams'] ?? [];
        if (count($valaParams) !== 2 || ($valaParams[0]['name'] ?? null) !== '__phpefc_this' || ($valaParams[1]['name'] ?? null) !== 'args') {
            return null;
        }

        $stmts = $this->prop($method, 'stmts');
        $argName = $this->singleRuntimeArgumentVariableName($stmts[0] ?? null) ?? $this->singleTypedRuntimeArgumentVariableName($stmts[0] ?? null);
        if ($argName === null) {
            return null;
        }

        $bodyStmts = array_slice($stmts, 1);
        if (!$this->stmtListHasNativeIntEvidence($bodyStmts)) {
            return null;
        }
        if ($this->stmtListRequiresFloatReturn($bodyStmts)) {
            return null;
        }

        $typedStmts = $this->translateIntStmtList($bodyStmts, $methodNames);
        if ($typedStmts === null || $typedStmts === []) {
            return null;
        }

        return new ValaIrFunctionDeclaration(
            (int) ($array['startLine'] ?? 0),
            (int) ($array['endLine'] ?? 0),
            [],
            $this->nativeIntHelperName($name),
            [],
            $typedStmts,
            (($array['phpName'] ?? '') !== '' ? (string) $array['phpName'] : $name) . '#int',
            'int64',
            [['typeName' => 'int64', 'name' => $argName]],
            null,
            'optimizer_helper',
            null,
            true,
            '',
            'private',
        );
    }

    private function stmtListRequiresFloatReturn(array $stmts): bool
    {
        foreach ($stmts as $stmt) {
            if (!$stmt instanceof ValaIrReturnStatement) {
                continue;
            }
            $returnArray = $stmt->toArray();
            $coerceReturnType = strtolower((string) ($returnArray['coerceReturnType'] ?? ''));
            if ($coerceReturnType !== '' && $coerceReturnType !== 'int' && $coerceReturnType !== 'integer') {
                return true;
            }
            if ($this->nodeContainsStaticCall($this->prop($stmt, 'expr'), 'Zval.from_float')) {
                return true;
            }
        }
        return false;
    }

    private function nodeContainsStaticCall(mixed $node, string $callee): bool
    {
        if (!is_object($node)) {
            return false;
        }
        if ($node instanceof ValaIrStaticCallExpression && ((string) ($node->toArray()['callee'] ?? '')) === $callee) {
            return true;
        }
        $ref = new ReflectionObject($node);
        foreach ($ref->getProperties() as $prop) {
            $value = $prop->getValue($node);
            if (is_array($value)) {
                foreach ($value as $item) {
                    if ($this->nodeContainsStaticCall($item, $callee)) {
                        return true;
                    }
                }
                continue;
            }
            if ($this->nodeContainsStaticCall($value, $callee)) {
                return true;
            }
        }
        return false;
    }

    private function stmtListHasNativeIntEvidence(array $stmts): bool
    {
        foreach ($stmts as $stmt) {
            if ($this->nodeHasNativeIntEvidence($stmt)) {
                return true;
            }
        }
        return false;
    }

    private function nodeHasNativeIntEvidence(mixed $node): bool
    {
        if (!is_object($node)) {
            return false;
        }
        if ($node instanceof ValaIrIntLiteral) {
            return true;
        }
        if ($node instanceof ValaIrStaticCallExpression) {
            $callee = (string) ($node->toArray()['callee'] ?? '');
            if ($callee === 'Zval.from_int' || str_ends_with($callee, '__phpefc_int')) {
                return true;
            }
        }
        if ($node instanceof ValaIrMethodCallExpression) {
            $methodName = (string) ($node->toArray()['methodName'] ?? '');
            if (in_array($methodName, ['as_int', 'add', 'subtract', 'multiply', 'less', 'less_or_equal', 'greater', 'greater_or_equal'], true)) {
                return true;
            }
        }

        $ref = new ReflectionObject($node);
        foreach ($ref->getProperties() as $prop) {
            $value = $prop->getValue($node);
            if (is_array($value)) {
                foreach ($value as $item) {
                    if ($this->nodeHasNativeIntEvidence($item)) {
                        return true;
                    }
                }
                continue;
            }
            if ($this->nodeHasNativeIntEvidence($value)) {
                return true;
            }
        }
        return false;
    }

    private function singleRuntimeArgumentVariableName(mixed $stmt): ?string
    {
        if (!$stmt instanceof ValaIrVariableDeclaration) {
            return null;
        }
        $typeName = (string) $this->prop($stmt, 'typeName');
        $expr = $this->prop($stmt, 'expr');
        if ($typeName === 'string') {
            if (!$expr instanceof ValaIrMethodCallExpression || ($expr->toArray()['methodName'] ?? null) !== 'to_php_string' || $this->prop($expr, 'args') !== []) {
                return null;
            }
            $expr = $this->prop($expr, 'receiver');
        } elseif ($typeName !== 'Zval') {
            return null;
        }
        if (!$expr instanceof ValaIrStaticCallExpression || ($expr->toArray()['callee'] ?? null) !== 'PhpefRuntimeCore.argument') {
            return null;
        }
        $args = $this->prop($expr, 'args');
        if (count($args) !== 2 || !$this->isVariableReference($args[0], 'args') || $this->intLiteralValue($args[1]) !== 0) {
            return null;
        }
        $name = (string) $this->prop($stmt, 'name');
        return $name !== '' ? $name : null;
    }

    private function singleTypedRuntimeArgumentVariableName(mixed $stmt): ?string
    {
        if (!$stmt instanceof ValaIrVariableDeclaration || (string) $this->prop($stmt, 'typeName') !== 'int64') {
            return null;
        }
        $expr = $this->prop($stmt, 'expr');
        if (!$expr instanceof ValaIrMethodCallExpression || ($expr->toArray()['methodName'] ?? null) !== 'as_int') {
            return null;
        }
        if ($this->prop($expr, 'args') !== []) {
            return null;
        }
        $receiver = $this->prop($expr, 'receiver');
        if (!$receiver instanceof ValaIrStaticCallExpression || ($receiver->toArray()['callee'] ?? null) !== 'PhpefRuntimeCore.argument') {
            return null;
        }
        $args = $this->prop($receiver, 'args');
        if (count($args) !== 2 || !$this->isVariableReference($args[0], 'args') || $this->intLiteralValue($args[1]) !== 0) {
            return null;
        }
        $name = (string) $this->prop($stmt, 'name');
        return $name !== '' ? $name : null;
    }

    private function nativeIntHelperName(string $methodName): string
    {
        return $methodName . '__phpefc_int';
    }

    private function wrapNativeIntMethod(ValaIrFunctionDeclaration $method, string $helperName): ValaIrFunctionDeclaration
    {
        $line = (int) ($method->toArray()['startLine'] ?? 0);
        $paramName = $this->singleRuntimeArgumentVariableName(($this->prop($method, 'stmts'))[0] ?? null) ?? 'arg0';
        $paramVar = new ValaIrVariableReference($line, $line, [], $paramName);
        $stmts = [
            new ValaIrVariableDeclaration($line, $line, [], 'Zval', $paramName, new ValaIrStaticCallExpression($line, $line, [], 'PhpefRuntimeCore.argument', [new ValaIrVariableReference($line, $line, [], 'args'), new ValaIrIntLiteral($line, $line, [], 0)])),
            new ValaIrReturnStatement($line, $line, [], new ValaIrStaticCallExpression($line, $line, [], 'Zval.from_int', [new ValaIrStaticCallExpression($line, $line, [], $helperName, [new ValaIrMethodCallExpression($line, $line, [], $paramVar, 'as_int', [])])])),
        ];
        $this->lastRewriteCount++;
        return $method->withStmts($stmts);
    }

    private function translateIntStmtList(array $stmts, array $methodNames): ?array
    {
        $out = [];
        foreach ($stmts as $stmt) {
            $translated = $this->translateIntStmt($stmt, $methodNames);
            if ($translated === null) {
                return null;
            }
            foreach ($translated as $item) {
                $out[] = $item;
            }
        }
        return $out;
    }

    private function translateIntStmt(object $stmt, array $methodNames): ?array
    {
        $array = $stmt->toArray();
        $line = (int) ($array['startLine'] ?? 0);
        $endLine = (int) ($array['endLine'] ?? $line);

        if ($stmt instanceof ValaIrVariableDeclaration) {
            $name = (string) $this->prop($stmt, 'name');
            $expr = $this->prop($stmt, 'expr');
            $typeName = (string) $this->prop($stmt, 'typeName');
            if ($typeName === 'int64') {
                $typedExpr = $this->translateIntExpr($expr, $methodNames);
                return $typedExpr !== null ? [new ValaIrVariableDeclaration($line, $endLine, [], 'int64', $name, $typedExpr)] : null;
            }
            if ($typeName !== 'Zval') {
                return null;
            }
            if ($expr instanceof ValaIrStaticCallExpression && ($expr->toArray()['callee'] ?? null) === 'Zval.undefined') {
                return [new ValaIrVariableDeclaration($line, $endLine, [], 'int64', $name, new ValaIrIntLiteral($line, $endLine, [], 0))];
            }
            $typedExpr = $this->translateIntExpr($expr, $methodNames);
            return $typedExpr !== null ? [new ValaIrVariableDeclaration($line, $endLine, [], 'int64', $name, $typedExpr)] : null;
        }

        if ($stmt instanceof ValaIrAssignmentStatement) {
            $expr = $this->translateIntExpr($this->prop($stmt, 'expr'), $methodNames);
            return $expr !== null ? [new ValaIrAssignmentStatement($line, $endLine, [], (string) $this->prop($stmt, 'name'), $expr)] : null;
        }

        if ($stmt instanceof ValaIrIfStatement) {
            $condition = $this->translateBoolExpr($this->prop($stmt, 'condition'), $methodNames);
            $branch = $this->translateIntStmtList($this->prop($stmt, 'stmts'), $methodNames);
            $elseBranch = $this->translateIntStmtList($this->prop($stmt, 'elseStmts'), $methodNames);
            if ($condition === null || $branch === null || $elseBranch === null) {
                return null;
            }
            $elseifs = [];
            foreach ($this->prop($stmt, 'elseifs') as $elseif) {
                if (!is_array($elseif) || !is_object($elseif['condition'] ?? null) || !is_array($elseif['stmts'] ?? null)) {
                    return null;
                }
                $elseifCondition = $this->translateBoolExpr($elseif['condition'], $methodNames);
                $elseifStmts = $this->translateIntStmtList($elseif['stmts'], $methodNames);
                if ($elseifCondition === null || $elseifStmts === null) {
                    return null;
                }
                $elseifs[] = ['condition' => $elseifCondition, 'stmts' => $elseifStmts];
            }
            return [new ValaIrIfStatement($line, $endLine, [], $condition, $branch, $elseifs, $elseBranch)];
        }

        if ($stmt instanceof ValaIrWhileStatement) {
            $condition = $this->translateBoolExpr($this->prop($stmt, 'condition'), $methodNames);
            $body = $this->translateIntStmtList($this->prop($stmt, 'stmts'), $methodNames);
            return $condition !== null && $body !== null ? [new ValaIrWhileStatement($line, $endLine, [], $condition, $body)] : null;
        }

        if ($stmt instanceof ValaIrReturnStatement) {
            $expr = $this->prop($stmt, 'expr');
            if (!is_object($expr)) {
                return null;
            }
            $typedExpr = $this->translateIntExpr($expr, $methodNames);
            return $typedExpr !== null ? [new ValaIrReturnStatement($line, $endLine, [], $typedExpr)] : null;
        }

        return null;
    }

    private function translateBoolExpr(object $expr, array $methodNames): ?object
    {
        if ($expr instanceof ValaIrBoolLiteral) {
            return $expr;
        }
        if ($expr instanceof ValaIrStaticCallExpression && ($expr->toArray()['callee'] ?? null) === 'Zval.from_bool') {
            $args = $this->prop($expr, 'args');
            return count($args) === 1 && $args[0] instanceof ValaIrBoolLiteral ? $args[0] : null;
        }
        if ($expr instanceof ValaIrBinaryExpression) {
            $array = $expr->toArray();
            $op = (string) ($array['operator'] ?? '');
            if (!in_array($op, ['<', '<=', '>', '>=', '==', '!='], true)) {
                return null;
            }
            $left = $this->translateIntExpr($this->prop($expr, 'left'), $methodNames);
            $right = $this->translateIntExpr($this->prop($expr, 'right'), $methodNames);
            return $left !== null && $right !== null ? new ValaIrBinaryExpression((int) ($array['startLine'] ?? 0), (int) ($array['endLine'] ?? 0), [], $op, $left, $right) : null;
        }
        if (!$expr instanceof ValaIrMethodCallExpression) {
            return null;
        }
        $methodName = (string) ($expr->toArray()['methodName'] ?? '');
        if ($methodName === 'to_bool') {
            $args = $this->prop($expr, 'args');
            return $args === [] ? $this->translateBoolExpr($this->prop($expr, 'receiver'), $methodNames) : null;
        }
        $op = match ($methodName) {
            'smaller_or_equal' => '<=',
            'smaller' => '<',
            'greater_or_equal' => '>=',
            'greater' => '>',
            'identical', 'equal' => '==',
            'not_identical', 'not_equal' => '!=',
            default => null,
        };
        if ($op === null) {
            return null;
        }
        $args = $this->prop($expr, 'args');
        if (count($args) !== 1) {
            return null;
        }
        $left = $this->translateIntExpr($this->prop($expr, 'receiver'), $methodNames);
        $right = $this->translateIntExpr($args[0], $methodNames);
        if ($left === null || $right === null) {
            return null;
        }
        $array = $expr->toArray();
        return new ValaIrBinaryExpression((int) ($array['startLine'] ?? 0), (int) ($array['endLine'] ?? 0), [], $op, $left, $right);
    }

    private function translateIntExpr(object $expr, array $methodNames): ?object
    {
        if ($expr instanceof ValaIrVariableReference || $expr instanceof ValaIrIntLiteral) {
            return $expr;
        }
        $array = $expr->toArray();
        $line = (int) ($array['startLine'] ?? 0);
        $endLine = (int) ($array['endLine'] ?? $line);

        if ($expr instanceof ValaIrBinaryExpression) {
            $op = (string) ($array['operator'] ?? '');
            if (!in_array($op, ['+', '-', '*'], true)) {
                return null;
            }
            $left = $this->translateIntExpr($this->prop($expr, 'left'), $methodNames);
            $right = $this->translateIntExpr($this->prop($expr, 'right'), $methodNames);
            return $left !== null && $right !== null ? new ValaIrBinaryExpression($line, $endLine, [], $op, $left, $right) : null;
        }

        if ($expr instanceof ValaIrStaticCallExpression) {
            $callee = (string) ($array['callee'] ?? '');
            $args = $this->prop($expr, 'args');
            if ($callee === 'Zval.from_int' && count($args) === 1) {
                return $this->translateIntExpr($args[0], $methodNames);
            }
            if ($callee === 'PhpefRuntimeCore.call_instance_method') {
                return $this->translateRuntimeSelfCallToIntHelper($expr, $methodNames);
            }
            if (isset($methodNames[$callee])) {
                return $this->translateDirectSelfCallToIntHelper($expr, $methodNames);
            }
            return null;
        }

        if ($expr instanceof ValaIrMethodCallExpression) {
            $methodName = (string) ($array['methodName'] ?? '');
            if ($methodName === 'as_int') {
                $args = $this->prop($expr, 'args');
                return $args === [] ? $this->translateIntExpr($this->prop($expr, 'receiver'), $methodNames) : null;
            }
            $op = match ($methodName) {
                'add' => '+',
                'subtract' => '-',
                'multiply' => '*',
                default => null,
            };
            if ($op === null) {
                return null;
            }
            $args = $this->prop($expr, 'args');
            if (count($args) !== 1) {
                return null;
            }
            $left = $this->translateIntExpr($this->prop($expr, 'receiver'), $methodNames);
            $right = $this->translateIntExpr($args[0], $methodNames);
            return $left !== null && $right !== null ? new ValaIrBinaryExpression($line, $endLine, [], $op, $left, $right) : null;
        }

        return null;
    }

    private function translateDirectSelfCallToIntHelper(ValaIrStaticCallExpression $expr, array $methodNames): ?object
    {
        $array = $expr->toArray();
        $callee = (string) ($array['callee'] ?? '');
        $methodName = (string) ($methodNames[$callee] ?? '');
        if ($methodName === '') {
            return null;
        }
        $line = (int) ($array['startLine'] ?? 0);
        $endLine = (int) ($array['endLine'] ?? $line);
        $args = $this->prop($expr, 'args');
        if (count($args) !== 2 || !$this->isVariableReference($args[0], '__phpefc_this') || !$args[1] instanceof ValaIrArrayCreationExpression) {
            return null;
        }
        $callArgs = $this->zvalArrayCreationValues($args[1]);
        if (count($callArgs) !== 1) {
            return null;
        }
        $typedArg = $this->translateIntExpr($callArgs[0], $methodNames);
        return $typedArg !== null ? new ValaIrStaticCallExpression($line, $endLine, [], $this->nativeIntHelperName($methodName), [$typedArg]) : null;
    }

    private function translateRuntimeSelfCallToIntHelper(ValaIrStaticCallExpression $expr, array $methodNames): ?object
    {
        $array = $expr->toArray();
        $line = (int) ($array['startLine'] ?? 0);
        $endLine = (int) ($array['endLine'] ?? $line);
        $args = $this->prop($expr, 'args');
        if (count($args) !== 4 || !$this->isVariableReference($args[0], '__phpefc_this')) {
            return null;
        }
        $methodName = $this->stringLiteralValue($args[1]);
        if ($methodName === null || !isset($methodNames[$methodName])) {
            return null;
        }
        if (!$args[2] instanceof ValaIrArrayCreationExpression) {
            return null;
        }
        $callArgs = $this->zvalArrayCreationValues($args[2]);
        if (count($callArgs) !== 1) {
            return null;
        }
        $typedArg = $this->translateIntExpr($callArgs[0], $methodNames);
        return $typedArg !== null ? new ValaIrStaticCallExpression($line, $endLine, [], $this->nativeIntHelperName($methodName), [$typedArg]) : null;
    }

    private function zvalArrayCreationValues(ValaIrArrayCreationExpression $array): array
    {
        $values = [];
        foreach ($this->prop($array, 'items') as $item) {
            $value = $this->arrayItemValue($item);
            if ($value === null && is_object($item)) {
                $value = $item;
            }
            if ($value === null) {
                return [];
            }
            $values[] = $value;
        }
        return $values;
    }
    private function addStringLiteralDispatchSpecializations(array $methods): array
    {
        $dispatchers = [];
        foreach ($methods as $method) {
            if (!$method instanceof ValaIrFunctionDeclaration) {
                continue;
            }
            $stmts = $this->prop($method, 'stmts');
            $argName = $this->singleRuntimeArgumentVariableName($stmts[0] ?? null);
            $methodArray = $method->toArray();
            $name = (string) ($methodArray['name'] ?? '');
            if ($name !== '' && $argName !== null && $this->nodeHasStringLiteralDispatch($stmts, $argName)) {
                $dispatcher = ['function' => $method, 'argName' => $argName, 'baseName' => $name];
                $dispatchers[$name] = $dispatcher;
                $qualifiedName = (string) ($methodArray['qualifiedName'] ?? '');
                if ($qualifiedName !== '' && $qualifiedName !== $name) {
                    $dispatchers[$qualifiedName] = $dispatcher;
                }
            }
        }
        if ($dispatchers === []) {
            return $methods;
        }

        $literals = [];
        foreach ($methods as $method) {
            $this->collectStringLiteralDispatchCalls($method, array_keys($dispatchers), $literals);
        }
        if ($literals === []) {
            return $methods;
        }

        $wrapperMap = $this->singleIntHelperWrapperMap($methods, $this->functionNameSet($methods));
        $map = [];
        $specialized = [];
        $specializedKeys = [];
        foreach ($literals as $dispatcherName => $dispatcherLiterals) {
            ksort($dispatcherLiterals);
            $baseName = (string) ($dispatchers[$dispatcherName]['baseName'] ?? $dispatcherName);
            foreach (array_keys($dispatcherLiterals) as $literal) {
                $name = $baseName . '__phpefc_fast_' . $this->safeSymbolSuffix($literal);
                $map[$dispatcherName][$literal] = $name;
                $specializedKey = $baseName . "\0" . $literal;
                if (!isset($specializedKeys[$specializedKey])) {
                    $specialized[] = $this->specializeStringDispatchFunctionForLiteral($dispatchers[$dispatcherName]['function'], $dispatchers[$dispatcherName]['argName'], $name, $literal, $wrapperMap);
                    $specializedKeys[$specializedKey] = true;
                }
            }
        }

        $rewritten = [];
        foreach ($methods as $method) {
            if (!$method instanceof ValaIrFunctionDeclaration) {
                $rewritten[] = $method;
                continue;
            }
            $rewritten[] = $method->withStmts($this->rewriteStringLiteralDispatchCallsInStmtList($this->prop($method, 'stmts'), $map));
        }
        return array_merge($rewritten, $specialized);
    }

    private function nodeHasStringLiteralDispatch(mixed $node, string $argName): bool
    {
        if (is_object($node)) {
            if ($node instanceof ValaIrIfStatement && $this->conditionIsVariableIdenticalToString($this->prop($node, 'condition'), $argName, '') !== null) {
                return true;
            }
            $ref = new ReflectionObject($node);
            foreach ($ref->getProperties() as $property) {
                if ($this->nodeHasStringLiteralDispatch($property->getValue($node), $argName)) {
                    return true;
                }
            }
            return false;
        }
        if (!is_array($node)) {
            return false;
        }
        foreach ($node as $value) {
            if ($this->nodeHasStringLiteralDispatch($value, $argName)) {
                return true;
            }
        }
        return false;
    }

    private function collectStringLiteralDispatchCalls(mixed $node, array $dispatcherNames, array &$literals): void
    {
        $dispatcherSet = array_fill_keys($dispatcherNames, true);
        if (is_object($node)) {
            if ($node instanceof ValaIrStaticCallExpression) {
                $callee = (string) ($node->toArray()['callee'] ?? '');
                if (isset($dispatcherSet[$callee])) {
                    $args = $this->prop($node, 'args');
                    if (count($args) === 2 && $args[1] instanceof ValaIrArrayCreationExpression) {
                        $items = $this->prop($args[1], 'items');
                        if (count($items) === 1) {
                            $literal = $this->zvalFromStringLiteralValue($items[0]);
                            if ($literal !== null) {
                                $literals[$callee][$literal] = true;
                            }
                        }
                    }
                }
            }
            $node = method_exists($node, 'toArray') ? $node->toArray() : [];
        }
        if (!is_array($node)) {
            return;
        }
        if (($node['kind'] ?? null) === 'StaticCallExpression') {
            $callee = (string) ($node['callee'] ?? '');
            if (isset($dispatcherSet[$callee])) {
                $args = is_array($node['args'] ?? null) ? $node['args'] : [];
                if (count($args) === 2 && is_array($args[1] ?? null) && ($args[1]['kind'] ?? null) === 'ArrayCreationExpression') {
                    $items = is_array($args[1]['items'] ?? null) ? $args[1]['items'] : [];
                    if (count($items) === 1 && is_array($items[0])) {
                        $literal = $this->zvalFromStringLiteralValueArray($items[0]);
                        if ($literal !== null) {
                            $literals[$callee][$literal] = true;
                        }
                    }
                }
            }
        }
        foreach ($node as $value) {
            $this->collectStringLiteralDispatchCalls($value, $dispatcherNames, $literals);
        }
    }
    private function specializeStringDispatchFunctionForLiteral(ValaIrFunctionDeclaration $dispatcher, string $argName, string $name, string $literal, array $wrapperMap): ValaIrFunctionDeclaration
    {
        $stmts = [];
        foreach ($this->prop($dispatcher, 'stmts') as $stmt) {
            if ($stmt instanceof ValaIrVariableDeclaration && $this->prop($stmt, 'name') === $argName) {
                continue;
            }
            $stmts[] = $this->replaceStringDispatchInStmt($stmt, $argName, $literal, $wrapperMap);
        }
        $stmts = $this->simplifyListResetStmts($stmts);
        $clone = $dispatcher->withStmts($stmts);
        $clone = $this->withProp($clone, 'name', $name);
        $clone = $this->withProp($clone, 'phpName', (($dispatcher->toArray()['phpName'] ?? '') !== '' ? $dispatcher->toArray()['phpName'] : ($dispatcher->toArray()['name'] ?? 'dispatch')) . '#fastpath(' . $literal . ')');
        $clone = $this->withProp($clone, 'role', 'optimizer_helper');
        $clone = $this->withProp($clone, 'visibility', 'private');
        return $clone;
    }

    private function simplifyListResetStmts(array $stmts): array
    {
        $changed = false;
        $out = [];
        foreach ($stmts as $stmt) {
            if ($stmt instanceof ValaIrWhileStatement) {
                $newStmt = $this->withProp($stmt, 'stmts', $this->simplifyListResetStmts($this->prop($stmt, 'stmts')));
                $changed = $changed || $newStmt !== $stmt;
                $out[] = $newStmt;
                continue;
            }
            if ($stmt instanceof ValaIrIfStatement) {
                $newStmt = $this->withProp($stmt, 'stmts', $this->simplifyListResetStmts($this->prop($stmt, 'stmts')));
                $newStmt = $this->withProp($newStmt, 'elseifs', $this->simplifyListResetElseifs($this->prop($newStmt, 'elseifs')));
                $newStmt = $this->withProp($newStmt, 'elseStmts', $this->simplifyListResetStmts($this->prop($newStmt, 'elseStmts')));
                $changed = $changed || $newStmt !== $stmt;
                $out[] = $newStmt;
                continue;
            }
            if ($stmt instanceof ValaIrAssignmentStatement) {
                $replacementExpr = $this->listResetReplacementExpr($stmt);
                if ($replacementExpr !== null) {
                    $out[] = $this->withProp($stmt, 'expr', $replacementExpr);
                    $this->lastRewriteCount++;
                    $changed = true;
                    continue;
                }
            }
            $out[] = $stmt;
        }
        return $changed ? $out : $stmts;
    }

    private function simplifyListResetElseifs(array $elseifs): array
    {
        $changed = false;
        $out = [];
        foreach ($elseifs as $elseif) {
            if (!is_array($elseif)) {
                $out[] = $elseif;
                continue;
            }
            $newElseif = $elseif;
            if (isset($newElseif['stmts']) && is_array($newElseif['stmts'])) {
                $newElseif['stmts'] = $this->simplifyListResetStmts($newElseif['stmts']);
            }
            $changed = $changed || $newElseif !== $elseif;
            $out[] = $newElseif;
        }
        return $changed ? $out : $elseifs;
    }

    private function listResetReplacementExpr(ValaIrAssignmentStatement $stmt): ?object
    {
        $name = (string) $this->prop($stmt, 'name');
        if ($name !== 't' && $name !== 'c') {
            return null;
        }
        $expr = $this->prop($stmt, 'expr');
        $key = $this->arrayGetKeyInt($expr);
        if ($key === null || ($name === 't' && $key !== 0) || ($name === 'c' && $key !== 1)) {
            return null;
        }
        $arrayItems = $this->arrayGetReceiverItems($expr);
        if ($arrayItems === null || count($arrayItems) !== 2) {
            return null;
        }
        $firstValue = $this->arrayItemValue($arrayItems[0]);
        $secondValue = $this->arrayItemValue($arrayItems[1]);
        if ($firstValue === null || $secondValue === null) {
            return null;
        }
        if (!$this->hasNativeTypedFastPathForCall($firstValue) || !$this->isZvalFromIntValue($secondValue, 0)) {
            return null;
        }
        return $name === 't' ? $firstValue : $secondValue;
    }
    private function arrayGetKeyInt(object $expr): ?int
    {
        if (!$expr instanceof ValaIrMethodCallExpression) {
            return null;
        }
        $array = $expr->toArray();
        if (($array['methodName'] ?? null) !== 'array_get_key') {
            return null;
        }
        $args = $this->prop($expr, 'args');
        if (count($args) !== 1) {
            return null;
        }
        return $this->zvalFromIntValue($args[0]);
    }

    private function arrayGetReceiverItems(object $expr): ?array
    {
        if (!$expr instanceof ValaIrMethodCallExpression) {
            return null;
        }
        $receiver = $this->prop($expr, 'receiver');
        if (!$receiver instanceof ValaIrStaticCallExpression || ($receiver->toArray()['callee'] ?? null) !== 'Zval.from_array') {
            return null;
        }
        $args = $this->prop($receiver, 'args');
        if (count($args) !== 1 || !$args[0] instanceof ValaIrArrayCreationExpression) {
            return null;
        }
        return $this->prop($args[0], 'items');
    }

    private function arrayItemValue(object $expr): ?object
    {
        if ($expr instanceof ValaIrArrayItem) {
            $key = $this->prop($expr, "key");
            if ($key !== null) {
                return null;
            }
            $value = $this->prop($expr, "value");
            return is_object($value) ? $value : null;
        }

        if ($expr instanceof ValaIrConstructExpression && ($expr->toArray()["typeName"] ?? null) === "ZvalArrayEntry") {
            $args = $this->prop($expr, "args");
            if (count($args) !== 2 || !$args[0] instanceof ValaIrNullLiteral) {
                return null;
            }
            return is_object($args[1]) ? $args[1] : null;
        }

        return null;
    }

    private function hasNativeTypedFastPathForCall(object $expr): bool
    {
        if ($this->funcResolver === null || !$expr instanceof ValaIrStaticCallExpression) {
            return false;
        }
        $callee = $expr->toArray()['callee'] ?? null;
        if (!is_string($callee) || preg_match('/^phpfunc_([A-Za-z_][A-Za-z0-9_]*)$/', $callee, $match) !== 1) {
            return false;
        }
        $args = $this->prop($expr, 'args');
        $argumentTypes = [];
        foreach ($args as $arg) {
            $type = $this->zvalLiteralType($arg);
            if ($type === null) {
                return false;
            }
            $argumentTypes[] = $type;
        }
        return $this->funcResolver->resolveTypedFastPath($match[1], $argumentTypes, ['native']) !== null;
    }

    private function zvalLiteralType(object $expr): ?string
    {
        if (!$expr instanceof ValaIrStaticCallExpression) {
            return null;
        }
        $callee = $expr->toArray()['callee'] ?? null;
        $args = $this->prop($expr, 'args');
        if (count($args) !== 1) {
            return null;
        }
        if ($callee === 'Zval.from_string' && $args[0] instanceof ValaIrStringLiteral) {
            return 'string';
        }
        if ($callee === 'Zval.from_int' && $args[0] instanceof ValaIrIntLiteral) {
            return 'int';
        }
        if ($callee === 'Zval.from_float' && $args[0] instanceof ValaIrFloatLiteral) {
            return 'double';
        }
        if ($callee === 'Zval.from_bool' && $args[0] instanceof ValaIrBoolLiteral) {
            return 'bool:' . ((($args[0]->toArray()['value'] ?? null) === true) ? 'true' : 'false');
        }
        return null;
    }

    private function isZvalFromBoolValue(object $expr, bool $value): bool
    {
        if (!$expr instanceof ValaIrStaticCallExpression || ($expr->toArray()['callee'] ?? null) !== 'Zval.from_bool') {
            return false;
        }
        $args = $this->prop($expr, 'args');
        return count($args) === 1 && $args[0] instanceof ValaIrBoolLiteral && (($args[0]->toArray()['value'] ?? null) === $value);
    }

    private function isZvalFromIntValue(object $expr, int $value): bool
    {
        return $this->zvalFromIntValue($expr) === $value;
    }

    private function zvalFromIntValue(object $expr): ?int
    {
        if (!$expr instanceof ValaIrStaticCallExpression || ($expr->toArray()['callee'] ?? null) !== 'Zval.from_int') {
            return null;
        }
        $args = $this->prop($expr, 'args');
        if (count($args) !== 1 || !$args[0] instanceof ValaIrIntLiteral) {
            return null;
        }
        return (int) ($args[0]->toArray()['value'] ?? 0);
    }

    private function replaceStringDispatchInStmt(object $stmt, string $argName, string $literal, array $wrapperMap): object
    {
        if ($stmt instanceof ValaIrIfStatement) {
            $direct = $this->stringDispatchBranchForLiteral($stmt, $argName, $literal);
            if ($direct !== null && count($direct) === 1 && is_object($direct[0])) {
                return $this->inlineStringDispatchWrapperCall($direct[0], $wrapperMap);
            }
            $stmt = $this->withProp($stmt, 'stmts', $this->replaceStringDispatchInStmtList($this->prop($stmt, 'stmts'), $argName, $literal, $wrapperMap));
            $stmt = $this->withProp($stmt, 'elseifs', $this->replaceStringDispatchInElseifs($this->prop($stmt, 'elseifs'), $argName, $literal, $wrapperMap));
            return $this->withProp($stmt, 'elseStmts', $this->replaceStringDispatchInStmtList($this->prop($stmt, 'elseStmts'), $argName, $literal, $wrapperMap));
        }
        if ($stmt instanceof ValaIrWhileStatement) {
            return $this->withProp($stmt, 'stmts', $this->replaceStringDispatchInStmtList($this->prop($stmt, 'stmts'), $argName, $literal, $wrapperMap));
        }
        return $stmt;
    }

    private function replaceStringDispatchInStmtList(array $stmts, string $argName, string $literal, array $wrapperMap): array
    {
        $out = [];
        $changed = false;
        foreach ($stmts as $stmt) {
            if (!is_object($stmt)) {
                $out[] = $stmt;
                continue;
            }
            $newStmt = $this->replaceStringDispatchInStmt($stmt, $argName, $literal, $wrapperMap);
            $changed = $changed || $newStmt !== $stmt;
            $out[] = $newStmt;
        }
        return $changed ? $out : $stmts;
    }

    private function replaceStringDispatchInElseifs(array $elseifs, string $argName, string $literal, array $wrapperMap): array
    {
        $out = [];
        $changed = false;
        foreach ($elseifs as $elseif) {
            if (!is_array($elseif)) {
                $out[] = $elseif;
                continue;
            }
            $newElseif = $elseif;
            if (isset($newElseif['stmts']) && is_array($newElseif['stmts'])) {
                $newElseif['stmts'] = $this->replaceStringDispatchInStmtList($newElseif['stmts'], $argName, $literal, $wrapperMap);
            }
            $changed = $changed || $newElseif !== $elseif;
            $out[] = $newElseif;
        }
        return $changed ? $out : $elseifs;
    }

    private function inlineStringDispatchWrapperCall(object $stmt, array $wrapperMap): object
    {
        if (!$stmt instanceof ValaIrCallStatement) {
            return $stmt;
        }
        $array = $stmt->toArray();
        $callee = (string) ($array['callee'] ?? '');
        $target = $wrapperMap[$callee] ?? null;
        if (!is_array($target)) {
            return $stmt;
        }
        $args = $this->prop($stmt, 'args');
        if (count($args) !== 2 || !$this->isVariableReference($args[0], '__phpefc_this')) {
            return $stmt;
        }
        $this->lastRewriteCount++;
        return new ValaIrCallStatement(
            (int) ($array['startLine'] ?? 0),
            (int) ($array['endLine'] ?? 0),
            [],
            (string) $target['helper'],
            $target['args'],
        );
    }
    private function singleIntHelperWrapperMap(array $methods, array $methodNames): array
    {
        $helperNames = [];
        foreach ($methods as $method) {
            if ($method instanceof ValaIrFunctionDeclaration) {
                $name = (string) ($method->toArray()['name'] ?? '');
                if (str_ends_with($name, '__phpefc_int')) {
                    $helperNames[$name] = true;
                }
            }
        }

        $map = [];
        foreach ($methods as $method) {
            if (!$method instanceof ValaIrFunctionDeclaration) {
                continue;
            }
            $methodArray = $method->toArray();
            $methodName = (string) ($methodArray['name'] ?? '');
            $target = $this->singleIntHelperWrapperTarget($method, $helperNames, $methodNames);
            if ($methodName !== '' && $target !== null) {
                $map[$methodName] = $target;
                $qualifiedName = (string) ($methodArray['qualifiedName'] ?? '');
                if ($qualifiedName !== '' && $qualifiedName !== $methodName) {
                    $map[$qualifiedName] = $target;
                }
            }
        }
        return $map;
    }

    private function singleIntHelperWrapperTarget(ValaIrFunctionDeclaration $method, array $helperNames, array $methodNames): ?array
    {
        $stmts = $this->prop($method, 'stmts');
        if (count($stmts) < 1 || count($stmts) > 2 || !$stmts[0] instanceof ValaIrCallStatement) {
            return null;
        }
        if (count($stmts) === 2 && !$this->isReturnNullStatement($stmts[1])) {
            return null;
        }

        $call = $stmts[0];
        $callee = (string) ($call->toArray()['callee'] ?? '');
        $targetMethodName = (string) ($methodNames[$callee] ?? '');
        if ($targetMethodName === '') {
            return null;
        }
        $helper = $this->nativeIntHelperName($targetMethodName);
        if (!isset($helperNames[$helper])) {
            return null;
        }
        $args = $this->prop($call, 'args');
        if (count($args) !== 2 || !$this->isVariableReference($args[0], '__phpefc_this') || !$args[1] instanceof ValaIrArrayCreationExpression) {
            return null;
        }
        $values = $this->zvalArrayCreationValues($args[1]);
        if ($values === []) {
            return null;
        }
        $typedArgs = [];
        foreach ($values as $value) {
            $intValue = $this->zvalFromIntValue($value);
            if ($intValue === null) {
                return null;
            }
            $array = $value->toArray();
            $typedArgs[] = new ValaIrIntLiteral((int) ($array['startLine'] ?? 0), (int) ($array['endLine'] ?? 0), [], $intValue);
        }
        return ['helper' => $helper, 'args' => $typedArgs];
    }

    private function isReturnNullStatement(object $stmt): bool
    {
        if (!$stmt instanceof ValaIrReturnStatement) {
            return false;
        }
        $expr = $this->prop($stmt, 'expr');
        return $expr instanceof ValaIrStaticCallExpression && ($expr->toArray()['callee'] ?? null) === 'Zval.from_null';
    }

    private function stringDispatchBranchForLiteral(ValaIrIfStatement $stmt, string $argName, string $literal): ?array
    {
        if ($this->conditionIsVariableIdenticalToString($this->prop($stmt, 'condition'), $argName, $literal) === true) {
            return $this->prop($stmt, 'stmts');
        }
        foreach ($this->prop($stmt, 'elseifs') as $elseif) {
            if (is_array($elseif) && isset($elseif['condition']) && $this->conditionIsVariableIdenticalToString($elseif['condition'], $argName, $literal) === true) {
                return is_array($elseif['stmts'] ?? null) ? $elseif['stmts'] : null;
            }
        }
        return null;
    }

    private function conditionIsVariableIdenticalToString(object $condition, string $argName, string $literal): ?bool
    {
        if ($condition instanceof ValaIrBinaryExpression && (string) $this->prop($condition, 'operator') === '==') {
            $left = $this->prop($condition, 'left');
            $right = $this->prop($condition, 'right');
            if ($this->isVariableReference($left, $argName) && $right instanceof ValaIrStringLiteral) {
                $found = $this->stringLiteralValue($right);
                return $literal === '' || $found === $literal;
            }
            if ($this->isVariableReference($right, $argName) && $left instanceof ValaIrStringLiteral) {
                $found = $this->stringLiteralValue($left);
                return $literal === '' || $found === $literal;
            }
            return null;
        }

        if (!$condition instanceof ValaIrMethodCallExpression || (string) $this->prop($condition, 'methodName') !== 'to_bool') {
            return null;
        }
        $receiver = $this->prop($condition, 'receiver');
        if (!$receiver instanceof ValaIrMethodCallExpression || !in_array((string) $this->prop($receiver, 'methodName'), ['identical', 'equal'], true)) {
            return null;
        }
        $identReceiver = $this->prop($receiver, 'receiver');
        if (!$this->isVariableReference($identReceiver, $argName)) {
            return null;
        }
        $args = $this->prop($receiver, 'args');
        if (count($args) !== 1 || !is_object($args[0])) {
            return null;
        }
        $found = $this->zvalFromStringLiteralValue($args[0]);
        if ($found === null) {
            return null;
        }
        return $literal === '' || $found === $literal;
    }

    private function rewriteStringLiteralDispatchCallsInStmtList(array $stmts, array $map): array
    {
        $out = [];
        $changed = false;
        foreach ($stmts as $stmt) {
            if (!is_object($stmt)) {
                $out[] = $stmt;
                continue;
            }
            $newStmt = $this->rewriteStringLiteralDispatchCallsInStmt($stmt, $map);
            $changed = $changed || $newStmt !== $stmt;
            $out[] = $newStmt;
        }
        return $changed ? $out : $stmts;
    }

    private function rewriteStringLiteralDispatchCallsInStmt(object $stmt, array $map): object
    {
        if ($stmt instanceof ValaIrVariableDeclaration || $stmt instanceof ValaIrAssignmentStatement) {
            return $this->withProp($stmt, 'expr', $this->rewriteStringLiteralDispatchCallsInExpr($this->prop($stmt, 'expr'), $map));
        }
        if ($stmt instanceof ValaIrReturnStatement) {
            $expr = $this->prop($stmt, 'expr');
            return is_object($expr) ? $this->withProp($stmt, 'expr', $this->rewriteStringLiteralDispatchCallsInExpr($expr, $map)) : $stmt;
        }
        if ($stmt instanceof ValaIrIfStatement) {
            $stmt = $this->withProp($stmt, 'condition', $this->rewriteStringLiteralDispatchCallsInExpr($this->prop($stmt, 'condition'), $map));
            $stmt = $this->withProp($stmt, 'stmts', $this->rewriteStringLiteralDispatchCallsInStmtList($this->prop($stmt, 'stmts'), $map));
            $stmt = $this->withProp($stmt, 'elseifs', $this->rewriteStringLiteralDispatchCallsInElseifs($this->prop($stmt, 'elseifs'), $map));
            return $this->withProp($stmt, 'elseStmts', $this->rewriteStringLiteralDispatchCallsInStmtList($this->prop($stmt, 'elseStmts'), $map));
        }
        if ($stmt instanceof ValaIrWhileStatement) {
            return $this->withProp($stmt, 'stmts', $this->rewriteStringLiteralDispatchCallsInStmtList($this->prop($stmt, 'stmts'), $map));
        }
        return $stmt;
    }

    private function rewriteStringLiteralDispatchCallsInElseifs(array $elseifs, array $map): array
    {
        $out = [];
        $changed = false;
        foreach ($elseifs as $elseif) {
            if (!is_array($elseif)) {
                $out[] = $elseif;
                continue;
            }
            $newElseif = $elseif;
            if (isset($newElseif['condition']) && is_object($newElseif['condition'])) {
                $newElseif['condition'] = $this->rewriteStringLiteralDispatchCallsInExpr($newElseif['condition'], $map);
            }
            if (isset($newElseif['stmts']) && is_array($newElseif['stmts'])) {
                $newElseif['stmts'] = $this->rewriteStringLiteralDispatchCallsInStmtList($newElseif['stmts'], $map);
            }
            $changed = $changed || $newElseif !== $elseif;
            $out[] = $newElseif;
        }
        return $changed ? $out : $elseifs;
    }

    private function rewriteStringLiteralDispatchCallsInExpr(object $expr, array $map): object
    {
        if ($expr instanceof ValaIrStaticCallExpression) {
            $expr = $this->withProp($expr, 'args', $this->rewriteStringLiteralDispatchCallsInExprList($this->prop($expr, 'args'), $map));
            $callee = (string) ($expr->toArray()['callee'] ?? '');
            if (isset($map[$callee])) {
                $args = $this->prop($expr, 'args');
                if (count($args) === 2 && $args[1] instanceof ValaIrArrayCreationExpression) {
                    $items = $this->prop($args[1], 'items');
                    $literal = count($items) === 1 ? $this->zvalFromStringLiteralValue($items[0]) : null;
                    if ($literal !== null && isset($map[$callee][$literal])) {
                        $this->lastRewriteCount++;
                        return new ValaIrStaticCallExpression((int) ($expr->toArray()['startLine'] ?? 0), (int) ($expr->toArray()['endLine'] ?? 0), [], $map[$callee][$literal], [$args[0], $this->withProp($args[1], 'items', [])]);
                    }
                }
            }
            return $expr;
        }
        if ($expr instanceof ValaIrMethodCallExpression) {
            $expr = $this->withProp($expr, 'receiver', $this->rewriteStringLiteralDispatchCallsInExpr($this->prop($expr, 'receiver'), $map));
            return $this->withProp($expr, 'args', $this->rewriteStringLiteralDispatchCallsInExprList($this->prop($expr, 'args'), $map));
        }
        if ($expr instanceof ValaIrArrayCreationExpression || $expr instanceof ValaIrArrayLiteral) {
            return $this->withProp($expr, 'items', $this->rewriteStringLiteralDispatchCallsInExprList($this->prop($expr, 'items'), $map));
        }
        if ($expr instanceof ValaIrBinaryExpression) {
            $expr = $this->withProp($expr, 'left', $this->rewriteStringLiteralDispatchCallsInExpr($this->prop($expr, 'left'), $map));
            return $this->withProp($expr, 'right', $this->rewriteStringLiteralDispatchCallsInExpr($this->prop($expr, 'right'), $map));
        }
        return $expr;
    }

    private function rewriteStringLiteralDispatchCallsInExprList(array $exprs, array $map): array
    {
        $out = [];
        $changed = false;
        foreach ($exprs as $expr) {
            if (!is_object($expr)) {
                $out[] = $expr;
                continue;
            }
            $newExpr = $this->rewriteStringLiteralDispatchCallsInExpr($expr, $map);
            $changed = $changed || $newExpr !== $expr;
            $out[] = $newExpr;
        }
        return $changed ? $out : $exprs;
    }

    private function zvalFromStringLiteralValue(object $expr): ?string
    {
        if (!$expr instanceof ValaIrStaticCallExpression || ($expr->toArray()['callee'] ?? null) !== 'Zval.from_string') {
            return null;
        }
        $args = $this->prop($expr, 'args');
        if (count($args) !== 1 || !$args[0] instanceof ValaIrStringLiteral) {
            return null;
        }
        return (string) ($args[0]->toArray()['value'] ?? '');
    }

    private function zvalFromStringLiteralValueArray(array $array): ?string
    {
        if (($array['kind'] ?? null) !== 'StaticCallExpression' || ($array['callee'] ?? null) !== 'Zval.from_string') {
            return null;
        }
        $args = is_array($array['args'] ?? null) ? $array['args'] : [];
        if (count($args) !== 1) {
            return null;
        }
        if (is_object($args[0]) && $args[0] instanceof ValaIrStringLiteral) {
            return (string) ($args[0]->toArray()['value'] ?? '');
        }
        if (is_array($args[0]) && ($args[0]['kind'] ?? null) === 'StringLiteral') {
            return (string) ($args[0]['value'] ?? '');
        }
        return null;
    }

    private function safeSymbolSuffix(string $literal): string
    {
        $suffix = strtolower(preg_replace('/[^A-Za-z0-9_]+/', '_', $literal) ?? '');
        $suffix = trim($suffix, '_');
        return $suffix !== '' ? $suffix : 'literal';
    }

    private function optimizeElseifs(array $elseifs, ?array $classContext): array
    {
        $changed = false;
        $optimized = [];
        foreach ($elseifs as $elseif) {
            if (!is_array($elseif)) {
                $optimized[] = $elseif;
                continue;
            }
            $newElseif = $elseif;
            if (isset($newElseif['condition']) && is_object($newElseif['condition'])) {
                $newCondition = $this->optimizeExpr($newElseif['condition'], $classContext);
                $changed = $changed || $newCondition !== $newElseif['condition'];
                $newElseif['condition'] = $newCondition;
            }
            if (isset($newElseif['stmts']) && is_array($newElseif['stmts'])) {
                $newStmts = $this->optimizeStmtList($newElseif['stmts'], $classContext);
                $changed = $changed || $newStmts !== $newElseif['stmts'];
                $newElseif['stmts'] = $newStmts;
            }
            $optimized[] = $newElseif;
        }
        return $changed ? $optimized : $elseifs;
    }

    private function optimizeExpr(object $expr, ?array $classContext): object
    {
        if ($expr instanceof ValaIrStaticCallExpression) {
            $optimizedArgsExpr = $this->withProp($expr, "args", $this->optimizeExprList($this->prop($expr, "args"), $classContext));
            $staticFastPath = $this->rewriteKnownStaticDispatchExpression($optimizedArgsExpr, $classContext);
            if ($staticFastPath !== $optimizedArgsExpr) {
                return $staticFastPath;
            }
            return $this->rewriteKnownSelfDispatch($optimizedArgsExpr, $classContext);
        }

        if ($expr instanceof ValaIrMethodCallExpression) {
            $optimized = $this->withProp($expr, 'receiver', $this->optimizeExpr($this->prop($expr, 'receiver'), $classContext));
            return $this->withProp($optimized, 'args', $this->optimizeExprList($this->prop($optimized, 'args'), $classContext));
        }

        if ($expr instanceof ValaIrArrayCreationExpression || $expr instanceof ValaIrArrayLiteral) {
            return $this->withProp($expr, 'items', $this->optimizeExprList($this->prop($expr, 'items'), $classContext));
        }

        if ($expr instanceof ValaIrArrayItem) {
            $optimized = $this->withProp($expr, 'key', $this->maybeOptimizeNullableExpr($this->prop($expr, 'key'), $classContext));
            return $this->withProp($optimized, 'value', $this->optimizeExpr($this->prop($optimized, 'value'), $classContext));
        }

        if ($expr instanceof ValaIrBinaryExpression) {
            $optimized = $this->withProp($expr, 'left', $this->optimizeExpr($this->prop($expr, 'left'), $classContext));
            return $this->withProp($optimized, 'right', $this->optimizeExpr($this->prop($optimized, 'right'), $classContext));
        }

        if ($expr instanceof ValaIrTernaryExpression) {
            $optimized = $this->withProp($expr, 'condition', $this->optimizeExpr($this->prop($expr, 'condition'), $classContext));
            $optimized = $this->withProp($optimized, 'ifExpr', $this->optimizeExpr($this->prop($optimized, 'ifExpr'), $classContext));
            return $this->withProp($optimized, 'elseExpr', $this->optimizeExpr($this->prop($optimized, 'elseExpr'), $classContext));
        }

        return $expr;
    }

    private function maybeOptimizeNullableExpr(mixed $expr, ?array $classContext): mixed
    {
        return is_object($expr) ? $this->optimizeExpr($expr, $classContext) : $expr;
    }

    private function optimizeExprList(array $exprs, ?array $classContext): array
    {
        $changed = false;
        $optimized = [];
        foreach ($exprs as $expr) {
            if (!is_object($expr)) {
                $optimized[] = $expr;
                continue;
            }
            $newExpr = $this->optimizeExpr($expr, $classContext);
            $changed = $changed || $newExpr !== $expr;
            $optimized[] = $newExpr;
        }
        return $changed ? $optimized : $exprs;
    }

    private function rewriteKnownSelfDispatch(ValaIrStaticCallExpression $expr, ?array $classContext): object
    {
        if ($classContext === null) {
            return $expr;
        }

        $array = $expr->toArray();
        if (($array['callee'] ?? null) !== 'PhpefRuntimeCore.call_instance_method') {
            return $expr;
        }
        $args = $this->prop($expr, 'args');
        if (count($args) !== 4) {
            return $expr;
        }
        $receiverClassName = $this->knownReceiverClassName($args[0], $classContext);
        if ($receiverClassName === null) {
            return $expr;
        }
        $methodName = $this->stringLiteralValue($args[1]);
        $className = $this->stringLiteralValue($args[3]);
        if ($methodName === null || $className === null) {
            return $expr;
        }
        if ($className !== '' && $className !== $receiverClassName) {
            return $expr;
        }
        $target = $this->targetInstanceMethod($classContext, $receiverClassName, $methodName);
        if ($target === null || !$this->directKnownInstanceCallIsAllowed($target, $className, $receiverClassName)) {
            return $expr;
        }
        if (!$args[2] instanceof ValaIrArrayCreationExpression) {
            return $expr;
        }

        $directArgs = [$args[0], $args[2]];
        $this->lastRewriteCount++;

        return new ValaIrStaticCallExpression(
            (int) ($array['startLine'] ?? 0),
            (int) ($array['endLine'] ?? 0),
            [],
            (string) ($target['directCallee'] ?? $target['valaName']),
            $directArgs,
        );
    }



    private function rewriteDynamicSelfDispatchCallStatement(ValaIrCallStatement $stmt, ?array $classContext): object
    {
        if ($classContext === null) {
            return $stmt;
        }

        $array = $stmt->toArray();
        if (($array['callee'] ?? null) !== 'PhpefRuntimeCore.call_instance_method') {
            return $stmt;
        }
        $args = $this->prop($stmt, 'args');
        if (count($args) !== 4) {
            return $stmt;
        }
        $receiverClassName = $this->knownReceiverClassName($args[0], $classContext);
        if ($receiverClassName === null) {
            return $stmt;
        }
        $className = $this->stringLiteralValue($args[3]);
        if ($className === null || $className !== $receiverClassName) {
            return $stmt;
        }
        if (!$args[2] instanceof ValaIrArrayCreationExpression) {
            return $stmt;
        }
        $methodVariable = $this->dynamicMethodStringVariable($args[1]);
        if ($methodVariable === null) {
            return $stmt;
        }
        $methodVariableName = $methodVariable['name'];
        $methodVariableIsNativeString = $methodVariable['nativeString'];

        $zvalArgs = $this->prop($args[2], 'items');
        $methodMap = is_array($classContext['methods'] ?? null) ? $classContext['methods'] : [];
        $candidates = [];
        foreach ($methodMap as $methodName => $target) {
            if (!is_array($target) || !($target['instance'] ?? false)) {
                continue;
            }
            $minArgs = (int) ($target['minArgs'] ?? 0);
            $maxArgs = (int) ($target['maxArgs'] ?? $minArgs);
            $argc = count($zvalArgs);
            if ($argc < $minArgs || $argc > $maxArgs) {
                continue;
            }
            $candidates[(string) $methodName] = $target;
        }
        if ($candidates === []) {
            return $stmt;
        }

        ksort($candidates);
        $branches = [];
        foreach ($candidates as $methodName => $target) {
            $branches[] = [
                'condition' => $this->methodVariableEqualsCondition((int) ($array['startLine'] ?? 0), (int) ($array['endLine'] ?? 0), $methodVariableName, $methodName, $methodVariableIsNativeString),
                'stmt' => new ValaIrCallStatement(
                    (int) ($array['startLine'] ?? 0),
                    (int) ($array['endLine'] ?? 0),
                    [],
                    (string) ($target['directCallee'] ?? $target['valaName']),
                    [$args[0], $args[2]],
                ),
            ];
        }

        $first = array_shift($branches);
        if (!is_array($first)) {
            return $stmt;
        }
        $elseifs = array_map(static fn (array $branch): array => [
            'condition' => $branch['condition'],
            'stmts' => [$branch['stmt']],
        ], $branches);

        $this->lastRewriteCount++;
        return new ValaIrIfStatement(
            (int) ($array['startLine'] ?? 0),
            (int) ($array['endLine'] ?? 0),
            [],
            $first['condition'],
            [$first['stmt']],
            $elseifs,
            [$this->undefinedInstanceMethodThrowStatement((int) ($array['startLine'] ?? 0), (int) ($array['endLine'] ?? 0), $className, $methodVariableName, $methodVariableIsNativeString)],
        );
    }

    private function undefinedInstanceMethodThrowStatement(int $startLine, int $endLine, string $className, string $methodVariableName, bool $methodVariableIsNativeString = false): ValaIrCallStatement
    {
        $methodNameExpr = $methodVariableIsNativeString
            ? new ValaIrStaticCallExpression($startLine, $endLine, [], 'Zval.from_string', [new ValaIrVariableReference($startLine, $endLine, [], $methodVariableName)])
            : new ValaIrVariableReference($startLine, $endLine, [], $methodVariableName);
        $message = new ValaIrMethodCallExpression(
            $startLine,
            $endLine,
            [],
            new ValaIrMethodCallExpression(
                $startLine,
                $endLine,
                [],
                new ValaIrStaticCallExpression($startLine, $endLine, [], 'Zval.from_string', [new ValaIrStringLiteral($startLine, $endLine, [], 'Call to undefined method ' . $className . '::')]),
                'concat',
                [$methodNameExpr],
            ),
            'concat',
            [new ValaIrStaticCallExpression($startLine, $endLine, [], 'Zval.from_string', [new ValaIrStringLiteral($startLine, $endLine, [], '()')])],
        );

        return new ValaIrCallStatement(
            $startLine,
            $endLine,
            [],
            'PhpefRuntimeCore.throw_exception',
            [new ValaIrStringLiteral($startLine, $endLine, [], 'Error'), $message],
        );
    }

    /** @return array{name:string,nativeString:bool}|null */
    private function dynamicMethodStringVariable(object $expr): ?array
    {
        if ($expr instanceof ValaIrVariableReference) {
            $name = (string) ($expr->toArray()['name'] ?? '');
            return $name !== '' ? ['name' => $name, 'nativeString' => true] : null;
        }
        if (!$expr instanceof ValaIrMethodCallExpression) {
            return null;
        }
        $array = $expr->toArray();
        if (($array['methodName'] ?? null) !== 'to_php_string') {
            return null;
        }
        $args = is_array($array['args'] ?? null) ? $array['args'] : [];
        if ($args !== []) {
            return null;
        }
        $receiver = $array['receiver'] ?? null;
        if (!is_array($receiver) || ($receiver['kind'] ?? null) !== 'VariableReference') {
            return null;
        }
        $name = (string) ($receiver['name'] ?? '');
        return $name !== '' ? ['name' => $name, 'nativeString' => false] : null;
    }

    private function methodVariableEqualsCondition(int $startLine, int $endLine, string $variableName, string $methodName, bool $methodVariableIsNativeString = false): object
    {
        if ($methodVariableIsNativeString) {
            return new ValaIrBinaryExpression(
                $startLine,
                $endLine,
                [],
                '==',
                new ValaIrVariableReference($startLine, $endLine, [], $variableName),
                new ValaIrStringLiteral($startLine, $endLine, [], $methodName),
            );
        }
        return new ValaIrMethodCallExpression(
            $startLine,
            $endLine,
            [],
            new ValaIrMethodCallExpression(
                $startLine,
                $endLine,
                [],
                new ValaIrVariableReference($startLine, $endLine, [], $variableName),
                'identical',
                [new ValaIrStaticCallExpression($startLine, $endLine, [], 'Zval.from_string', [new ValaIrStringLiteral($startLine, $endLine, [], $methodName)])],
            ),
            'to_bool',
            [],
        );
    }

    private function rewriteKnownSelfDispatchCallStatement(ValaIrCallStatement $stmt, ?array $classContext): ValaIrCallStatement
    {
        if ($classContext === null) {
            return $stmt;
        }

        $array = $stmt->toArray();
        if (($array['callee'] ?? null) !== 'PhpefRuntimeCore.call_instance_method') {
            return $stmt;
        }
        $args = $this->prop($stmt, 'args');
        if (count($args) !== 4) {
            return $stmt;
        }
        $receiverClassName = $this->knownReceiverClassName($args[0], $classContext);
        if ($receiverClassName === null) {
            return $stmt;
        }
        $methodName = $this->stringLiteralValue($args[1]);
        $className = $this->stringLiteralValue($args[3]);
        if ($methodName === null || $className === null) {
            return $stmt;
        }
        if ($className !== '' && $className !== $receiverClassName) {
            return $stmt;
        }
        $target = $this->targetInstanceMethod($classContext, $receiverClassName, $methodName);
        if ($target === null || !$this->directKnownInstanceCallIsAllowed($target, $className, $receiverClassName)) {
            return $stmt;
        }
        if (!$args[2] instanceof ValaIrArrayCreationExpression) {
            return $stmt;
        }

        $directArgs = [$args[0], $args[2]];
        $this->lastRewriteCount++;

        return new ValaIrCallStatement(
            (int) ($array['startLine'] ?? 0),
            (int) ($array['endLine'] ?? 0),
            [],
            (string) ($target['directCallee'] ?? $target['valaName']),
            $directArgs,
        );
    }



    private function rewriteKnownStaticDispatchCallStatement(ValaIrCallStatement $stmt, ?array $classContext): ValaIrCallStatement
    {
        $rewritten = $this->knownStaticDispatchFastPath($stmt, $classContext);
        return $rewritten instanceof ValaIrCallStatement ? $rewritten : $stmt;
    }

    private function rewriteKnownStaticDispatchExpression(ValaIrStaticCallExpression $expr, ?array $classContext): object
    {
        $rewritten = $this->knownStaticDispatchFastPath($expr, $classContext);
        return $rewritten instanceof ValaIrStaticCallExpression ? $rewritten : $expr;
    }

    private function knownStaticDispatchFastPath(object $call, ?array $classContext): ?object
    {
        if ($classContext === null || (!$call instanceof ValaIrCallStatement && !$call instanceof ValaIrStaticCallExpression)) {
            return null;
        }

        $array = $call->toArray();
        if (($array["callee"] ?? null) !== "PhpefRuntimeCore.call_static_method") {
            return null;
        }

        $args = $call->args();
        if (count($args) !== 4) {
            return null;
        }

        $className = $this->stringLiteralValue($args[0]);
        $methodName = $this->stringLiteralValue($args[1]);
        $currentClassScope = $this->stringLiteralValue($args[3]);
        if ($className === null || $methodName === null || $currentClassScope === null) {
            return null;
        }
        if (!$args[2] instanceof ValaIrArrayCreationExpression) {
            return null;
        }

        $target = $this->targetStaticMethod($classContext, $className, $methodName);
        if ($target === null || !$this->directKnownStaticCallIsAllowed($classContext, $target, $className, $currentClassScope)) {
            return null;
        }

        $callArgs = $this->prop($args[2], "items");
        if (count($callArgs) < (int) ($target["minArgs"] ?? -1) || count($callArgs) > (int) ($target["maxArgs"] ?? -1)) {
            return null;
        }
        if (count($callArgs) !== 0) {
            return null;
        }

        $callee = (string) ($target["directCallee"] ?? $target["valaName"] ?? "");
        if ($callee === "") {
            return null;
        }

        $this->lastRewriteCount++;
        return $call->withCalleeAndArgs($callee, []);
    }

    private function withFunctionKnowledge(array $classContext, array $localObjectClasses, array $registeredClasses): array
    {
        $copy = $classContext;
        $copy["localObjectClasses"] = $localObjectClasses;
        $copy["registeredClasses"] = $registeredClasses;
        return $copy;
    }

    private function updateLocalObjectClassesFromStmt(object $stmt, array $classContext, array &$localObjectClasses): void
    {
        if (!$stmt instanceof ValaIrVariableDeclaration && !$stmt instanceof ValaIrAssignmentStatement) {
            return;
        }

        $name = (string) $this->prop($stmt, 'name');
        if ($name === '') {
            return;
        }

        $className = $this->newObjectClassName($this->prop($stmt, 'expr'));
        if ($className !== null && $this->knownClassNameInContext($classContext, $className)) {
            $localObjectClasses[$name] = $className;
            return;
        }

        unset($localObjectClasses[$name]);
    }

    private function updateRegisteredClassesFromStmt(object $stmt, array $classContext, array &$registeredClasses): void
    {
        if (!$stmt instanceof ValaIrCallStatement) {
            return;
        }

        $callee = (string) ($stmt->toArray()["callee"] ?? "");
        if ($callee === "") {
            return;
        }

        $classRegisterFunctions = is_array($classContext["classRegisterFunctions"] ?? null) ? $classContext["classRegisterFunctions"] : [];
        foreach ($classRegisterFunctions as $className => $registerFunctions) {
            if (!is_array($registerFunctions)) {
                continue;
            }
            if (isset($registerFunctions[$callee])) {
                $registeredClasses[(string) $className] = true;
            }
        }
    }

    private function knownClassNameInContext(array $classContext, string $className): bool
    {
        if ($className === '') {
            return false;
        }
        if ($className === (string) ($classContext['phpClassName'] ?? '')) {
            return true;
        }
        $allMethods = is_array($classContext['allMethods'] ?? null) ? $classContext['allMethods'] : [];
        return isset($allMethods[$className]) && is_array($allMethods[$className]);
    }

    private function newObjectClassName(mixed $expr): ?string
    {
        if (!$expr instanceof ValaIrStaticCallExpression) {
            return null;
        }
        $array = $expr->toArray();
        if (($array['callee'] ?? null) !== 'PhpefRuntimeCore.new_object') {
            return null;
        }
        $args = $this->prop($expr, 'args');
        if (count($args) < 1 || !$args[0] instanceof ValaIrStringLiteral) {
            return null;
        }
        return $this->stringLiteralValue($args[0]);
    }

    private function knownReceiverClassName(object $receiver, array $classContext): ?string
    {
        if ($this->isVariableReference($receiver, '__phpefc_this')) {
            return (string) ($classContext['phpClassName'] ?? '');
        }

        $array = method_exists($receiver, 'toArray') ? $receiver->toArray() : [];
        if (($array['kind'] ?? null) !== 'VariableReference') {
            return null;
        }
        $name = (string) ($array['name'] ?? '');
        $localObjectClasses = is_array($classContext['localObjectClasses'] ?? null) ? $classContext['localObjectClasses'] : [];
        $className = $localObjectClasses[$name] ?? null;
        return is_string($className) && $className !== '' ? $className : null;
    }

    private function classMethodMap(array $stmts): array
    {
        $map = [];
        foreach ($stmts as $stmt) {
            if (!$stmt instanceof ValaIrClassDeclaration) {
                continue;
            }
            $classArray = $stmt->toArray();
            $phpClassName = (string) ($classArray['phpClassName'] ?? $classArray['phpName'] ?? '');
            if ($phpClassName === '') {
                continue;
            }
            $map[$phpClassName] = $this->methodMap($classArray);
        }
        return $map;
    }


    private function classRegisterFunctionMap(array $stmts): array
    {
        $map = [];
        foreach ($stmts as $stmt) {
            if (!$stmt instanceof ValaIrClassDeclaration) {
                continue;
            }
            $classArray = $stmt->toArray();
            $phpClassName = (string) ($classArray["phpClassName"] ?? $classArray["phpName"] ?? "");
            $valaClassName = (string) ($classArray["name"] ?? "");
            if ($phpClassName === "" || $valaClassName === "") {
                continue;
            }
            $functions = [$valaClassName . ".register_class_symbols" => true];
            $separator = strpos($valaClassName, "__");
            $sourcePrefix = $separator === false ? "" : substr($valaClassName, 0, $separator);
            if (str_starts_with($sourcePrefix, "phpef_")) {
                $sourceSuffix = substr($sourcePrefix, strlen("phpef_"));
                if ($sourceSuffix !== "") {
                    $functions["phpef_file_" . $sourceSuffix . "__register_functions"] = true;
                }
            }
            $map[$phpClassName] = $functions;
        }
        return $map;
    }

    private function targetStaticMethod(array $classContext, string $className, string $methodName): ?array
    {
        $allMethods = is_array($classContext["allMethods"] ?? null) ? $classContext["allMethods"] : [];
        $methodMap = $allMethods[$className] ?? null;
        if (!is_array($methodMap)) {
            return null;
        }
        $target = $methodMap[$methodName] ?? null;
        return is_array($target) && ($target["static"] ?? false) ? $target : null;
    }

    private function directKnownStaticCallIsAllowed(array $classContext, array $target, string $className, string $classScope): bool
    {
        if ($classScope === $className) {
            return true;
        }
        $registeredClasses = is_array($classContext["registeredClasses"] ?? null) ? $classContext["registeredClasses"] : [];
        if (!isset($registeredClasses[$className])) {
            return false;
        }
        return strtolower((string) ($target["visibility"] ?? "public")) === "public";
    }

    private function targetInstanceMethod(array $classContext, string $receiverClassName, string $methodName): ?array
    {
        $currentClassName = (string) ($classContext['phpClassName'] ?? '');
        if ($currentClassName === $receiverClassName) {
            $methodMap = is_array($classContext['methods'] ?? null) ? $classContext['methods'] : [];
            $target = $methodMap[$methodName] ?? null;
            return is_array($target) && ($target['instance'] ?? false) ? $target : null;
        }
        $allMethods = is_array($classContext['allMethods'] ?? null) ? $classContext['allMethods'] : [];
        $methodMap = $allMethods[$receiverClassName] ?? null;
        if (!is_array($methodMap)) {
            return null;
        }
        $target = $methodMap[$methodName] ?? null;
        return is_array($target) && ($target['instance'] ?? false) ? $target : null;
    }

    private function directKnownInstanceCallIsAllowed(array $target, string $classScope, string $receiverClassName): bool
    {
        if ($classScope === $receiverClassName) {
            return true;
        }
        if ($classScope === '') {
            return strtolower((string) ($target['visibility'] ?? 'public')) === 'public';
        }
        return false;
    }

    private function methodMap(array $classArray): array
    {
        $map = [];
        foreach (($classArray['methodSummaries'] ?? []) as $summary) {
            if (!is_array($summary)) {
                continue;
            }
            $phpMethodName = (string) ($summary['phpMethodName'] ?? '');
            $valaName = (string) ($summary['valaName'] ?? '');
            if ($phpMethodName === '' || $valaName === '') {
                continue;
            }
            $summary['directCallee'] = (string) ($summary['handlerSymbol'] ?? $summary['qualifiedName'] ?? $summary['valaName'] ?? '');
            $map[$phpMethodName] = $summary;
        }
        return $map;
    }

    private function isVariableReference(object $expr, string $name): bool
    {
        $array = method_exists($expr, 'toArray') ? $expr->toArray() : [];
        return ($array['kind'] ?? null) === 'VariableReference' && ($array['name'] ?? null) === $name;
    }

    private function intLiteralValue(object $expr): ?int
    {
        $array = method_exists($expr, 'toArray') ? $expr->toArray() : [];
        return ($array['kind'] ?? null) === 'IntLiteral' ? (int) ($array['value'] ?? 0) : null;
    }

    private function stringLiteralValue(object $expr): ?string
    {
        $array = method_exists($expr, 'toArray') ? $expr->toArray() : [];
        return ($array['kind'] ?? null) === 'StringLiteral' ? (string) ($array['value'] ?? '') : null;
    }

    private function countRuntimeDispatchCalls(mixed $node): int
    {
        if (is_object($node)) {
            return $this->countRuntimeDispatchCalls(method_exists($node, 'toArray') ? $node->toArray() : []);
        }
        if (!is_array($node)) {
            return 0;
        }
        $kind = $node['kind'] ?? null;
        $callee = $node['callee'] ?? null;
        $runtimeDispatchCallees = ['PhpefRuntimeCore.call_function', 'PhpefRuntimeCore.call_instance_method', 'PhpefRuntimeCore.call_static_method'];
        $count = (in_array($kind, ['StaticCallExpression', 'CallStatement'], true) && in_array($callee, $runtimeDispatchCallees, true)) ? 1 : 0;
        foreach ($node as $value) {
            $count += $this->countRuntimeDispatchCalls($value);
        }
        return $count;
    }

    private function prop(object $object, string $name): mixed
    {
        $ref = new ReflectionProperty($object, $name);
        return $ref->getValue($object);
    }

    private function withProp(object $object, string $name, mixed $value): object
    {
        $current = $this->prop($object, $name);
        if ($current === $value) {
            return $object;
        }
        $clone = clone $object;
        $ref = new ReflectionProperty($clone, $name);
        $ref->setValue($clone, $value);
        return $clone;
    }
}
