<?php

function cli_error(string $message): void
{
    $stderr = fopen('php://stderr', 'w');
    if ($stderr !== false) {
        fwrite($stderr, $message);
        fclose($stderr);
        return;
    }
    echo $message;
}

function unpack_php_parser_ast_file(string $file): array
{
    $encodedAst = @file_get_contents($file);
    if ($encodedAst === false) {
        throw new RuntimeException("Could not read input file: {$file}");
    }
    $bundle = PhpSerializedBundle::unpack($encodedAst);
    if (is_array($bundle) && ($bundle['format'] ?? null) === 'php-parser-ast-v0') {
        return [
            isset($bundle['ast']) && is_array($bundle['ast']) ? $bundle['ast'] : [],
            isset($bundle['phpSourceFile']) ? (string) $bundle['phpSourceFile'] : '',
        ];
    }
    return [$bundle, ''];
}

function include_lookup_keys(string $path): array
{
    $normalized = str_replace('\\', '/', $path);
    return array_values(array_unique([$path, $normalized, ltrim($normalized, './'), basename($normalized)]));
}

function source_matches_include(string $sourceFile, array $includedPaths): bool
{
    $sourceKeys = include_lookup_keys($sourceFile);
    foreach ($includedPaths as $includedPath) {
        if (array_intersect($sourceKeys, include_lookup_keys($includedPath)) !== []) {
            return true;
        }
    }
    return false;
}

function static_include_paths_from_ast(array $ast): array
{
    $paths = [];
    $walk = static function (mixed $node) use (&$walk, &$paths): void {
        if ($node instanceof PhpParser\Node\Expr\Include_ && $node->expr instanceof PhpParser\Node\Scalar\String_) {
            $paths[] = $node->expr->value;
        }
        if ($node instanceof PhpParser\Node) {
            foreach ($node->getSubNodeNames() as $name) {
                $walk($node->$name);
            }
            return;
        }
        if (is_array($node)) {
            foreach ($node as $child) {
                $walk($child);
            }
        }
    };
    $walk($ast);
    return array_values(array_unique($paths));
}

if ($argc < 3) {
    cli_error("Usage: {$argv[0]} <input.phpast> <output.phpir> [--external-phpast <other.phpast> ...] [--external-stub <stub.phpast> ...]\n");
    exit(2);
}

$inputFile = $argv[1];
$outputFile = $argv[2];
$externalPhpAstFiles = [];
for ($i = 3; $i < $argc; $i++) {
    if ($argv[$i] !== '--external-phpast' && $argv[$i] !== '--external-stub') {
        cli_error("Unknown option: {$argv[$i]}\n");
        exit(2);
    }
    $option = $argv[$i];
    $i++;
    if ($i >= $argc || $argv[$i] === '') {
        cli_error("Missing value for {$option}\n");
        exit(2);
    }
    $externalPhpAstFiles[] = $argv[$i];
}

try {
    [$phpParserAst, $phpSourceFile] = unpack_php_parser_ast_file($inputFile);
    $externalDeclaredFunctionsByInclude = [];
    $externalDeclaredStaticMethodsByInclude = [];
    foreach ($externalPhpAstFiles as $externalPhpAstFile) {
        [$externalAst, $externalPhpSourceFile] = unpack_php_parser_ast_file($externalPhpAstFile);
        $declaredFunctions = PhpIrBuilder::declaredFunctionNames($externalAst);
        $declaredStaticMethods = PhpIrBuilder::declaredStaticMethodNames($externalAst);
        foreach (PhpIrBuilder::includeLookupKeys($externalPhpSourceFile) as $key) {
            $externalDeclaredFunctionsByInclude[$key] = $declaredFunctions;
            $externalDeclaredStaticMethodsByInclude[$key] = $declaredStaticMethods;
        }
    }

    $phpIr = PhpIrBuilder::fromPhpParserAst($phpParserAst, $phpSourceFile, $externalDeclaredFunctionsByInclude, $externalDeclaredStaticMethodsByInclude);
    $serializedIr = PhpSerializedBundle::pack($phpIr);
} catch (Throwable $error) {
    cli_error($error->getMessage() . "\n");
    exit(1);
}

if (file_put_contents($outputFile, $serializedIr) === false) {
    cli_error("Could not write output file: {$outputFile}\n");
    exit(1);
}

echo "Wrote PHP AST IR to {$outputFile}\n";
