<?php

/**
 * Generates Vala source code from Vala-IR objects.
 */
final class ValaCodeGenerator
{
    /**
     * Convert one Vala-IR program object to Vala source code.
     */
    public static function fromValaIr(object $valaIr): string
    {
        return self::fromValaIrWithSourceMap($valaIr)['vala'];
    }

    /**
     * Convert one Vala-IR program object to Vala source code and a PHP source map.
     */
    public static function fromValaIrWithSourceMap(object $valaIr, string $generatedValaFile = '', bool $emitMainWrapper = true, ?string $activationFunctionName = null): array
    {
        if (!method_exists($valaIr, 'toArray')) {
            throw new RuntimeException('Expected a Vala-IR object with toArray(), got ' . get_debug_type($valaIr));
        }

        $array = $valaIr->toArray();
        if (($array['format'] ?? null) !== 'vala-ir-v0' || !isset($array['root']['stmts']) || !is_array($array['root']['stmts'])) {
            throw new RuntimeException('Expected vala-ir-v0 program');
        }

        $sourceFile = (string) ($array['root']['phpSourceFile'] ?? '');
        $lines = [];
        $mappings = [];
        $uses = self::uses($array['root']);
        $usingLine = trim(implode(' ', array_map(static fn (string $useName): string => 'using ' . $useName . ';', $uses)));
        if ($usingLine !== '') {
            $lines[] = $usingLine;
        }

        foreach ($array['root']['stmts'] as $stmt) {
            if (!is_array($stmt)) {
                continue;
            }
            if (!$emitMainWrapper && in_array((string) ($stmt['role'] ?? ''), ['entrypoint', 'c_main'], true)) {
                continue;
            }
            if (($stmt['kind'] ?? null) === 'ClassDeclaration') {
                self::appendClassToLines($lines, $mappings, $stmt, $sourceFile);
                continue;
            }
            self::appendFunctionToLines($lines, $mappings, $stmt, $sourceFile);
        }
        self::appendCommentsToLines($lines, $mappings, self::arrayField($array['root'], 'comments'), 0, $sourceFile);

        $vala = implode("\n", $lines);
        if ($vala !== '') {
            $vala .= "\n";
        }

        $sourceMap = [
            'format' => 'phpefc-c-source-lines-v0',
            'phpSourceFile' => $sourceFile,
            'generatedValaFile' => $generatedValaFile,
            'mappings' => array_values($mappings),
        ];

        return [
            'vala' => $vala . self::sourceMapComment($sourceMap),
            'sourceMap' => $sourceMap,
        ];
    }

    private static function sourceMapComment(array $sourceMap): string
    {
        return "\n/*\n"
            . "PHPEFC-C-SOURCE-LINES-BEGIN\n"
            . json_encode($sourceMap, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\n"
            . "PHPEFC-C-SOURCE-LINES-END\n"
            . "*/\n";
    }

    private static function addSourceMapping(array &$mappings, string $sourceFile, int $phpLine, int $valaLine, string $kind): void
    {
        if ($sourceFile === '' || $phpLine <= 0 || $valaLine <= 0) {
            return;
        }

        $key = $valaLine . ':' . $sourceFile . ':' . $phpLine;
        if (isset($mappings[$key])) {
            return;
        }

        $mappings[$key] = [
            'valaLine' => $valaLine,
            'phpSourceFile' => $sourceFile,
            'phpLine' => $phpLine,
            'kind' => $kind,
        ];
    }

    private static function uses(array $root): array
    {
        $uses = isset($root['uses']) && is_array($root['uses']) ? $root['uses'] : [];
        foreach ($uses as $useName) {
            if (!is_string($useName) || !preg_match('/^[A-Za-z_][A-Za-z0-9_.]*$/', $useName)) {
                throw new RuntimeException('Invalid Vala using namespace: ' . (string) $useName);
            }
        }
        return array_values($uses);
    }

    private static function emitComments(ValaCodeLineEmitter $emitter, array $comments, int $level, array &$mappings, string $sourceFile): void
    {
        foreach ($comments as $comment) {
            if (!is_array($comment)) {
                continue;
            }
            $text = (string) ($comment['text'] ?? '');
            if ($text === '') {
                continue;
            }

            $lineNo = self::line($comment, 'startLine');
            foreach (preg_split('/\R/', $text) ?: [] as $line) {
                $valaLine = $emitter->emitAtSourceLine($lineNo, self::indent($level) . self::commentToVala($line));
                self::addSourceMapping($mappings, $sourceFile, $lineNo, $valaLine, (string) ($comment['kind'] ?? 'Comment'));
                $lineNo++;
            }
        }
    }

    private static function commentToVala(string $line): string
    {
        $trimmed = ltrim($line);
        if (str_starts_with($trimmed, '#')) {
            $indent = substr($line, 0, strlen($line) - strlen($trimmed));
            return $indent . '//' . substr($trimmed, 1);
        }
        return $line;
    }

    private static function stmtToVala(array $stmt, int $level): array
    {
        return match ($stmt['kind'] ?? null) {
            'VariableDeclaration' => [self::indent($level) . self::typeName((string) ($stmt['typeName'] ?? '')) . ' ' . self::identifier((string) ($stmt['name'] ?? '')) . ' = ' . self::exprToVala(self::arrayFieldOne($stmt, 'expr')) . ';'],
            'AssignmentStatement' => [self::indent($level) . self::identifier((string) ($stmt['name'] ?? '')) . ' = ' . self::exprToVala(self::arrayFieldOne($stmt, 'expr')) . ';'],
            'CallStatement' => self::callStatementToVala($stmt, $level),
            'DynamicIncludeDispatchStatement' => self::dynamicIncludeDispatchStatementToVala($stmt, $level),
            'ThrowStatement' => self::throwStatementToVala($stmt, $level),
            'ReturnStatement' => self::returnStatementToVala($stmt, $level),
            'IfStatement' => self::ifStatementToVala($stmt, $level),
            'WhileStatement' => self::whileStatementToVala($stmt, $level),
            'ForeachValueStatement' => self::foreachValueStatementToVala($stmt, $level),
            default => throw new RuntimeException('Unsupported Vala-IR statement for codegen: ' . (string) ($stmt['kind'] ?? 'unknown')),
        };
    }


    private static function appendClassToLines(array &$lines, array &$mappings, array $stmt, string $sourceFile): void
    {
        if (($stmt['kind'] ?? null) !== 'ClassDeclaration') {
            throw new RuntimeException('Unsupported Vala-IR class node for codegen: ' . (string) ($stmt['kind'] ?? 'unknown'));
        }
        $name = self::identifier((string) ($stmt['name'] ?? ''));
        $lines[] = 'public class ' . $name . ' {';
        self::addSourceMapping($mappings, $sourceFile, self::line($stmt, 'startLine'), count($lines), 'ClassDeclaration');
        foreach (self::arrayField($stmt, 'methods') as $method) {
            self::appendFunctionToLines($lines, $mappings, $method, $sourceFile, 1);
        }
        $lines[] = '}';
        $lines[] = '';
    }

    private static function appendFunctionToLines(array &$lines, array &$mappings, array $stmt, string $sourceFile, int $level = 0): void
    {
        if (!in_array(($stmt['kind'] ?? null), ['FunctionDeclaration', 'DeferredHelperFunction'], true)) {
            throw new RuntimeException('Unsupported Vala-IR function node for codegen: ' . (string) ($stmt['kind'] ?? 'unknown'));
        }

        $name = self::identifier((string) ($stmt['name'] ?? ''));
        $returnType = self::typeName((string) ($stmt['returnType'] ?? (($stmt['kind'] ?? null) === 'FunctionDeclaration' ? 'Zval' : 'void')));
        if (!array_key_exists('valaParams', $stmt)) {
            throw new RuntimeException('FunctionDeclaration IR is missing valaParams');
        }
        $valaParams = self::arrayField($stmt, 'valaParams');
        $params = array_map(static function (array $param): string {
            return self::typeName((string) ($param['typeName'] ?? '')) . ' ' . self::identifier((string) ($param['name'] ?? ''));
        }, $valaParams);

        $throws = array_key_exists('throwsError', $stmt) ? (bool) $stmt['throwsError'] : true;
        $visibility = self::visibility((string) ($stmt['visibility'] ?? 'public'));
        $lines[] = self::indent($level) . $visibility . ' static ' . $returnType . ' ' . $name . '(' . implode(', ', $params) . ')' . ($throws ? ' throws Error' : '') . ' {';
        self::addSourceMapping($mappings, $sourceFile, self::line($stmt, 'startLine'), count($lines), (string) ($stmt['kind'] ?? 'FunctionDeclaration'));
        foreach (self::arrayField($stmt, 'stmts') as $innerStmt) {
            self::appendStmtToLines($lines, $mappings, $innerStmt, $sourceFile, $level + 1);
        }
        $defaultReturnExpr = self::arrayFieldOne($stmt, 'defaultReturnExpr');
        if ($defaultReturnExpr !== [] && !self::stmtsAlwaysReturn(self::arrayField($stmt, 'stmts'))) {
            $lines[] = self::indent($level + 1) . 'return ' . self::exprToVala($defaultReturnExpr) . ';';
        }
        $lines[] = self::indent($level) . '}';
        $lines[] = '';
    }

    private static function stmtsAlwaysReturn(array $stmts): bool
    {
        if ($stmts === []) {
            return false;
        }

        $lastStmt = end($stmts);
        if (!is_array($lastStmt)) {
            return false;
        }

        return self::stmtAlwaysReturns($lastStmt);
    }

    private static function stmtAlwaysReturns(array $stmt): bool
    {
        if (($stmt['kind'] ?? null) === 'ReturnStatement') {
            return true;
        }

        if (($stmt['kind'] ?? null) !== 'IfStatement') {
            return false;
        }

        if (!self::stmtsAlwaysReturn(self::arrayField($stmt, 'stmts'))) {
            return false;
        }

        foreach (self::arrayField($stmt, 'elseifs') as $elseif) {
            if (!is_array($elseif) || !self::stmtsAlwaysReturn(self::arrayField($elseif, 'stmts'))) {
                return false;
            }
        }

        $elseStmts = self::arrayField($stmt, 'elseStmts');
        return $elseStmts !== [] && self::stmtsAlwaysReturn($elseStmts);
    }

    private static function appendStmtToLines(array &$lines, array &$mappings, array $stmt, string $sourceFile, int $level): void
    {
        self::appendCommentsToLines($lines, $mappings, self::arrayField($stmt, 'comments'), $level, $sourceFile);
        if (($stmt['kind'] ?? null) === 'Nop') {
            return;
        }

        $kind = (string) ($stmt['kind'] ?? 'Statement');
        $line = self::line($stmt, 'startLine');
        switch ($kind) {
            case 'IfStatement':
                $lines[] = self::indent($level) . 'if (' . self::exprToVala(self::arrayFieldOne($stmt, 'condition')) . ') {';
                self::addSourceMapping($mappings, $sourceFile, $line, count($lines), $kind);
                foreach (self::arrayField($stmt, 'stmts') as $innerStmt) {
                    self::appendStmtToLines($lines, $mappings, $innerStmt, $sourceFile, $level + 1);
                }
                foreach (self::arrayField($stmt, 'elseifs') as $elseif) {
                    if (!is_array($elseif)) {
                        continue;
                    }
                    $lines[] = self::indent($level) . '} else if (' . self::exprToVala(self::arrayFieldOne($elseif, 'condition')) . ') {';
                    self::addSourceMapping($mappings, $sourceFile, self::line($elseif, 'startLine'), count($lines), 'ElseIfStatement');
                    foreach (self::arrayField($elseif, 'stmts') as $innerStmt) {
                        self::appendStmtToLines($lines, $mappings, $innerStmt, $sourceFile, $level + 1);
                    }
                }
                $elseStmts = self::arrayField($stmt, 'elseStmts');
                if ($elseStmts !== []) {
                    $lines[] = self::indent($level) . '} else {';
                    self::addSourceMapping($mappings, $sourceFile, $line, count($lines), 'ElseStatement');
                    foreach ($elseStmts as $innerStmt) {
                        self::appendStmtToLines($lines, $mappings, $innerStmt, $sourceFile, $level + 1);
                    }
                }
                $lines[] = self::indent($level) . '}';
                return;

            case 'WhileStatement':
                $lines[] = self::indent($level) . 'while (' . self::exprToVala(self::arrayFieldOne($stmt, 'condition')) . ') {';
                self::addSourceMapping($mappings, $sourceFile, $line, count($lines), $kind);
                foreach (self::arrayField($stmt, 'stmts') as $innerStmt) {
                    self::appendStmtToLines($lines, $mappings, $innerStmt, $sourceFile, $level + 1);
                }
                $lines[] = self::indent($level) . '}';
                return;

            case 'ForeachValueStatement':
                $entryName = self::identifier((string) ($stmt['entryName'] ?? '__phpefc_foreach_entry'));
                $valueName = self::identifier((string) ($stmt['valueName'] ?? 'value'));
                $lines[] = self::indent($level) . 'foreach (var ' . $entryName . ' in ' . self::exprToVala(self::arrayFieldOne($stmt, 'expr')) . '.as_array_entries()) {';
                self::addSourceMapping($mappings, $sourceFile, $line, count($lines), $kind);
                $lines[] = self::indent($level + 1) . 'Zval ' . $valueName . ' = ' . $entryName . '.value;';
                self::addSourceMapping($mappings, $sourceFile, $line, count($lines), $kind);
                foreach (self::arrayField($stmt, 'stmts') as $innerStmt) {
                    self::appendStmtToLines($lines, $mappings, $innerStmt, $sourceFile, $level + 1);
                }
                $lines[] = self::indent($level) . '}';
                return;

            case 'TryCatchStatement':
                $catchType = self::typeName((string) ($stmt['catchType'] ?? 'Error'));
                $catchName = self::identifier((string) ($stmt['catchName'] ?? 'e'));
                $lines[] = self::indent($level) . 'try {';
                self::addSourceMapping($mappings, $sourceFile, $line, count($lines), $kind);
                foreach (self::arrayField($stmt, 'tryStmts') as $innerStmt) {
                    self::appendStmtToLines($lines, $mappings, $innerStmt, $sourceFile, $level + 1);
                }
                $lines[] = self::indent($level) . '} catch (' . $catchType . ' ' . $catchName . ') {';
                self::addSourceMapping($mappings, $sourceFile, $line, count($lines), $kind);
                foreach (self::arrayField($stmt, 'catchStmts') as $innerStmt) {
                    self::appendStmtToLines($lines, $mappings, $innerStmt, $sourceFile, $level + 1);
                }
                $lines[] = self::indent($level) . '}';
                return;
        }

        foreach (self::stmtToVala($stmt, $level) as $stmtLineText) {
            $lines[] = $stmtLineText;
            self::addSourceMapping($mappings, $sourceFile, $line, count($lines), $kind);
        }
    }



    private static function appendCommentsToLines(array &$lines, array &$mappings, array $comments, int $level, string $sourceFile): void
    {
        foreach ($comments as $comment) {
            if (!is_array($comment)) {
                continue;
            }
            $text = (string) ($comment['text'] ?? '');
            if ($text === '') {
                continue;
            }

            $lineNo = self::line($comment, 'startLine');
            foreach (preg_split('/\R/', $text) ?: [] as $line) {
                $lines[] = self::indent($level) . self::commentToVala($line);
                self::addSourceMapping($mappings, $sourceFile, $lineNo, count($lines), (string) ($comment['kind'] ?? 'Comment'));
                $lineNo++;
            }
        }
    }

    private static function throwStatementToVala(array $stmt, int $level): array
    {
        $hostErrorName = (string) ($stmt['hostErrorName'] ?? '');
        if ($hostErrorName === '') {
            throw new RuntimeException('ThrowStatement Vala-IR must carry hostErrorName; PHP throw must be lowered before codegen');
        }

        return [self::indent($level) . 'throw ' . self::identifier($hostErrorName) . ';'];
    }



    private static function callStatementToVala(array $stmt, int $level): array
    {
        $callee = self::callee($stmt);
        $args = self::arrayField($stmt, 'args');
        return [self::indent($level) . $callee . '(' . implode(', ', array_map(self::exprToVala(...), $args)) . ');'];
    }

    private static function returnStatementToVala(array $stmt, int $level): array
    {
        $expr = self::arrayFieldOne($stmt, 'expr');
        if ($expr === []) {
            return [self::indent($level) . 'return;'];
        }
        $valaExpr = self::exprToVala($expr);
        if ((string) ($stmt['coerceReturnType'] ?? '') === 'float') {
            $valaExpr = '(' . $valaExpr . ').to_float_zval()';
        }
        return [self::indent($level) . 'return ' . $valaExpr . ';'];
    }
    private static function ifStatementToVala(array $stmt, int $level): array
    {
        $lines = [self::indent($level) . 'if (' . self::exprToVala(self::arrayFieldOne($stmt, 'condition')) . ') {'];
        foreach (self::arrayField($stmt, 'stmts') as $innerStmt) {
            foreach (self::stmtToVala($innerStmt, $level + 1) as $stmtLineText) {
                $lines[] = $stmtLineText;
            }
        }
        foreach (self::arrayField($stmt, 'elseifs') as $elseif) {
            if (!is_array($elseif)) {
                continue;
            }
            $lines[] = self::indent($level) . '} else if (' . self::exprToVala(self::arrayFieldOne($elseif, 'condition')) . ') {';
            foreach (self::arrayField($elseif, 'stmts') as $innerStmt) {
                foreach (self::stmtToVala($innerStmt, $level + 1) as $stmtLineText) {
                    $lines[] = $stmtLineText;
                }
            }
        }
        $elseStmts = self::arrayField($stmt, 'elseStmts');
        if ($elseStmts !== []) {
            $lines[] = self::indent($level) . '} else {';
            foreach ($elseStmts as $innerStmt) {
                foreach (self::stmtToVala($innerStmt, $level + 1) as $stmtLineText) {
                    $lines[] = $stmtLineText;
                }
            }
        }
        $lines[] = self::indent($level) . '}';
        return $lines;
    }


    private static function whileStatementToVala(array $stmt, int $level): array
    {
        $lines = [self::indent($level) . 'while (' . self::exprToVala(self::arrayFieldOne($stmt, 'condition')) . ') {'];
        foreach (self::arrayField($stmt, 'stmts') as $innerStmt) {
            foreach (self::stmtToVala($innerStmt, $level + 1) as $stmtLineText) {
                $lines[] = $stmtLineText;
            }
        }
        $lines[] = self::indent($level) . '}';
        return $lines;
    }
    private static function foreachValueStatementToVala(array $stmt, int $level): array
    {
        $entryName = self::identifier((string) ($stmt['entryName'] ?? '__phpefc_foreach_entry'));
        $valueName = self::identifier((string) ($stmt['valueName'] ?? 'value'));
        $lines = [self::indent($level) . 'foreach (var ' . $entryName . ' in ' . self::exprToVala(self::arrayFieldOne($stmt, 'expr')) . '.as_array_entries()) {'];
        $lines[] = self::indent($level + 1) . 'Zval ' . $valueName . ' = ' . $entryName . '.value;';
        foreach (self::arrayField($stmt, 'stmts') as $innerStmt) {
            foreach (self::stmtToVala($innerStmt, $level + 1) as $stmtLineText) {
                $lines[] = $stmtLineText;
            }
        }
        $lines[] = self::indent($level) . '}';
        return $lines;
    }


    private static function dynamicIncludeDispatchStatementToVala(array $stmt, int $level): array
    {
        $pathVariable = self::identifier((string) ($stmt['pathVariable'] ?? '__phpefc_include_path'));
        $pathExpr = self::exprToVala(self::arrayFieldOne($stmt, 'pathExpr'));
        $lines = [
            self::indent($level) . '{',
            self::indent($level + 1) . 'string ' . $pathVariable . ' = ' . $pathExpr . '.to_php_string();',
        ];

        $entries = self::arrayField($stmt, 'entries');
        foreach ($entries as $index => $entry) {
            if (!is_array($entry)) {
                continue;
            }
            $register = self::identifier((string) ($entry['registerCallee'] ?? ''));
            $main = self::identifier((string) ($entry['mainCallee'] ?? ''));
            $condition = self::dynamicIncludeMatchConditionToVala($pathVariable, $entry);
            $prefix = $index === 0 ? 'if' : '} else if';
            $lines[] = self::indent($level + 1) . $prefix . ' (' . $condition . ') {';
            $lines[] = self::indent($level + 2) . $register . '();';
            $lines[] = self::indent($level + 2) . $main . '();';
        }
        $failureStmts = self::arrayField($stmt, 'failureStmts');
        if ($failureStmts === []) {
            throw new RuntimeException('Dynamic include dispatch IR is missing failureStmts');
        }
        $lines[] = self::indent($level + 1) . '} else {';
        foreach ($failureStmts as $failureStmt) {
            if (!is_array($failureStmt)) {
                continue;
            }
            foreach (self::stmtToVala($failureStmt, $level + 2) as $stmtLineText) {
                $lines[] = $stmtLineText;
            }
        }
        $lines[] = self::indent($level + 1) . '}';
        $lines[] = self::indent($level) . '}';
        return $lines;
    }

    private static function dynamicIncludeMatchConditionToVala(string $pathVariable, array $entry): string
    {
        $conditions = [];
        foreach (self::arrayField($entry, 'exactPaths') as $exactPath) {
            if (is_string($exactPath) && $exactPath !== '') {
                $conditions[] = $pathVariable . ' == ' . self::quoteString($exactPath);
            }
        }
        foreach (self::arrayField($entry, 'suffixPaths') as $suffixPath) {
            if (is_string($suffixPath) && $suffixPath !== '') {
                $conditions[] = $pathVariable . '.has_suffix(' . self::quoteString($suffixPath) . ')';
            }
        }
        $conditions = array_values(array_unique($conditions));
        if ($conditions === []) {
            throw new RuntimeException('Dynamic include dispatch entry has no IR match conditions');
        }
        return implode(' || ', $conditions);
    }

    private static function exprToVala(array $expr): string
    {
        return match ($expr['kind'] ?? null) {
            'StringLiteral' => self::quoteString((string) ($expr['value'] ?? '')),
            'IntLiteral' => (string) ((int) ($expr['value'] ?? 0)),
            'FloatLiteral' => self::floatLiteral((float) ($expr['value'] ?? 0.0)),
            'BoolLiteral' => !empty($expr['value']) ? 'true' : 'false',
            'RuntimeVisibilityLiteral' => self::runtimeVisibilityLiteral($expr),
            'NullLiteral' => 'null',
            'VariableReference' => self::callee($expr),
            'StaticCallExpression' => self::staticCallExpressionToVala($expr),
            'MethodCallExpression' => self::methodCallExpressionToVala($expr),
            'ConstructExpression' => self::constructExpressionToVala($expr),
            'ArrayCreationExpression' => self::arrayCreationExpressionToVala($expr),
            'BinaryExpression' => self::binaryExpressionToVala($expr),
            'TernaryExpression' => self::ternaryExpressionToVala($expr),
            default => throw new RuntimeException('Unsupported Vala-IR expression for codegen: ' . (string) ($expr['kind'] ?? 'unknown')),
        };
    }

    private static function floatLiteral(float $value): string
    {
        $literal = json_encode($value, JSON_PRESERVE_ZERO_FRACTION);
        if (!is_string($literal) || $literal === '') {
            $literal = sprintf('%.17G', $value);
        }
        if (!str_contains($literal, '.') && !str_contains($literal, 'e') && !str_contains($literal, 'E')) {
            $literal .= '.0';
        }
        return $literal;
    }

    private static function runtimeVisibilityLiteral(array $expr): string
    {
        $value = strtoupper((string) ($expr['value'] ?? ''));
        if (!in_array($value, ['PUBLIC', 'PROTECTED', 'PRIVATE'], true)) {
            throw new RuntimeException('Invalid runtime visibility literal: ' . $value);
        }
        return 'PhpefRuntimeCore.Visibility.' . $value;
    }

    private static function staticCallExpressionToVala(array $expr): string
    {
        $callee = self::callee($expr);
        $args = array_map(self::exprToVala(...), self::arrayField($expr, 'args'));
        return $callee . '(' . implode(', ', $args) . ')';
    }

    private static function constructExpressionToVala(array $expr): string
    {
        $typeName = self::typeName((string) ($expr['typeName'] ?? ''));
        $args = array_map(self::exprToVala(...), self::arrayField($expr, 'args'));
        return 'new ' . $typeName . '(' . implode(', ', $args) . ')';
    }

    private static function arrayCreationExpressionToVala(array $expr): string
    {
        $elementTypeName = self::typeName((string) ($expr['elementTypeName'] ?? ''));
        $items = array_map(self::exprToVala(...), self::arrayField($expr, 'items'));
        return 'new ' . $elementTypeName . '[] {' . implode(', ', $items) . '}';
    }

    private static function methodCallExpressionToVala(array $expr): string
    {
        $receiver = self::exprToVala(self::arrayFieldOne($expr, 'receiver'));
        $methodName = self::identifier((string) ($expr['methodName'] ?? ''));
        $args = array_map(self::exprToVala(...), self::arrayField($expr, 'args'));
        return $receiver . '.' . $methodName . '(' . implode(', ', $args) . ')';
    }

    private static function binaryExpressionToVala(array $expr): string
    {
        $operator = self::operator((string) ($expr['operator'] ?? ''));
        $left = self::exprToVala(self::arrayFieldOne($expr, 'left'));
        $right = self::exprToVala(self::arrayFieldOne($expr, 'right'));
        return '(' . $left . ' ' . $operator . ' ' . $right . ')';
    }

    private static function ternaryExpressionToVala(array $expr): string
    {
        $condition = self::exprToVala(self::arrayFieldOne($expr, 'condition'));
        $ifExpr = self::exprToVala(self::arrayFieldOne($expr, 'ifExpr'));
        $elseExpr = self::exprToVala(self::arrayFieldOne($expr, 'elseExpr'));
        return '(' . $condition . '.to_bool() ? ' . $ifExpr . ' : ' . $elseExpr . ')';
    }
    private static function callee(array $stmt): string
    {
        $callee = (string) ($stmt['callee'] ?? $stmt['name'] ?? '');
        if (!preg_match('/^[A-Za-z_][A-Za-z0-9_.]*$/', $callee)) {
            throw new RuntimeException('Invalid Vala callee name: ' . $callee);
        }
        return $callee;
    }

    private static function phpExceptionClass(string $className): string
    {
        $className = ltrim($className, '\\');
        if (!preg_match('/^[A-Za-z_][A-Za-z0-9_]*$/', $className)) {
            throw new RuntimeException('Invalid PHP exception class name: ' . $className);
        }
        return $className;
    }

    private static function typeName(string $typeName): string
    {
        if (!preg_match('/^[A-Za-z_][A-Za-z0-9_.]*(?:\[\])?$/', $typeName)) {
            throw new RuntimeException('Invalid Vala type name: ' . $typeName);
        }
        return $typeName;
    }

    private static function identifier(string $identifier): string
    {
        if (!preg_match('/^[A-Za-z_][A-Za-z0-9_]*$/', $identifier)) {
            throw new RuntimeException('Invalid Vala identifier: ' . $identifier);
        }
        return $identifier;
    }

    private static function visibility(string $visibility): string
    {
        if (!in_array($visibility, ['public', 'protected', 'private'], true)) {
            throw new RuntimeException('Invalid Vala visibility: ' . $visibility);
        }
        return $visibility;
    }

    private static function operator(string $operator): string
    {
        if (!in_array($operator, ['+', '-', '*', '/', '<', '<=', '>', '>=', '=='], true)) {
            throw new RuntimeException('Unsupported Vala operator: ' . $operator);
        }
        return $operator;
    }

    private static function arrayFieldOne(array $node, string $key): array
    {
        return isset($node[$key]) && is_array($node[$key]) ? $node[$key] : [];
    }

    private static function arrayField(array $node, string $key): array
    {
        return isset($node[$key]) && is_array($node[$key]) ? $node[$key] : [];
    }

    private static function line(array $node, string $key): int
    {
        return isset($node[$key]) ? (int) $node[$key] : 1;
    }

    private static function quoteString(string $value): string
    {
        return '"' . addcslashes($value, "\\\"\n\r\t") . '"';
    }

    private static function indent(int $level): string
    {
        return str_repeat('    ', $level);
    }
}
