<?php

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

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_phpir(object $phpIr): array
{
    if (!method_exists($phpIr, 'toArray')) {
        return [];
    }
    $array = $phpIr->toArray();
    $stmts = isset($array['root']['stmts']) && is_array($array['root']['stmts']) ? $array['root']['stmts'] : [];
    $paths = [];
    $walk = static function (mixed $node) use (&$walk, &$paths): void {
        if (!is_array($node)) {
            return;
        }
        if (($node['kind'] ?? null) === 'FuncCall' && ($node['functionName'] ?? null) === '__phpefc_include') {
            $args = isset($node['args']) && is_array($node['args']) ? $node['args'] : [];
            $arg = isset($args[0]) && is_array($args[0]) ? $args[0] : [];
            if (($arg['kind'] ?? null) === 'StringLiteral' && isset($arg['value']) && is_string($arg['value'])) {
                $paths[] = $arg['value'];
            }
        }
        foreach ($node as $child) {
            $walk($child);
        }
    };
    $walk($stmts);
    return array_values(array_unique($paths));
}

function phpir_source_file(object $phpIr): string
{
    if (!method_exists($phpIr, 'toArray')) {
        return '';
    }
    $array = $phpIr->toArray();
    return (string) ($array['root']['phpSourceFile'] ?? '');
}

function include_activation_for_source(string $sourceFile, array $includeActivationFunctions): ?string
{
    foreach ($includeActivationFunctions as $path => $callee) {
        if (source_matches_include($sourceFile, [$path])) {
            return $callee;
        }
    }
    return null;
}

function normalized_source_path(string $path): string
{
    $normalized = str_replace('\\', '/', $path);
    $real = @realpath($normalized);
    if ($real !== false) {
        $normalized = str_replace('\\', '/', $real);
    }
    return preg_replace('#/+#', '/', $normalized) ?? $normalized;
}

function source_function_prefix_for(string $sourceFile, array $sourceFunctionPrefixes): ?string
{
    $normalizedSourceFile = normalized_source_path($sourceFile);
    foreach ($sourceFunctionPrefixes as $path => $prefix) {
        if (normalized_source_path((string) $path) === $normalizedSourceFile) {
            return $prefix;
        }
    }
    foreach ($sourceFunctionPrefixes as $path => $prefix) {
        if (source_matches_include($sourceFile, [(string) $path])) {
            return $prefix;
        }
    }
    return null;
}

function phpefc_common_source_root(array $sourceFiles): string
{
    $dirs = [];
    foreach ($sourceFiles as $sourceFile) {
        if (!is_string($sourceFile) || $sourceFile === '') {
            continue;
        }
        $dirs[] = dirname(normalized_source_path($sourceFile));
    }
    $dirs = array_values(array_unique($dirs));
    if ($dirs === []) {
        return '';
    }
    if (count($dirs) === 1) {
        return $dirs[0];
    }
    $split = static fn (string $dir): array => explode('/', trim($dir, '/'));
    $common = $split($dirs[0]);
    foreach (array_slice($dirs, 1) as $dir) {
        $parts = $split($dir);
        $limit = min(count($common), count($parts));
        $next = [];
        for ($i = 0; $i < $limit; $i++) {
            if ($common[$i] !== $parts[$i]) {
                break;
            }
            $next[] = $common[$i];
        }
        $common = $next;
    }
    return '/' . implode('/', $common);
}

function phpefc_relative_source_path(string $sourceFile, string $sourceRoot): string
{
    $source = normalized_source_path($sourceFile);
    $root = rtrim(normalized_source_path($sourceRoot), '/');
    if ($root !== '' && ($source === $root || str_starts_with($source, $root . '/'))) {
        return ltrim(substr($source, strlen($root)), '/');
    }
    return basename($source);
}

function phpefc_source_prefix_from_root(string $sourceFile, string $sourceRoot): string
{
    return phpefc_identifier_part(phpefc_relative_source_path($sourceFile, $sourceRoot));
}

function phpefc_portable_include_keys_for_source(string $sourceFile, string $sourceRoot): array
{
    return include_lookup_keys(phpefc_relative_source_path($sourceFile, $sourceRoot));
}

function phpefc_identifier_part(string $value): string
{
    $part = preg_replace('/[^A-Za-z0-9_]+/', '_', $value) ?? '';
    $part = trim($part, '_');
    if ($part === '') {
        return 'unknown_php';
    }
    if (preg_match('/^[A-Za-z_]/', $part) !== 1) {
        $part = '_' . $part;
    }
    return $part;
}

function phpefc_source_prefix(string $sourceFile): string
{
    if ($sourceFile === '') {
        return 'unknown_php';
    }
    return phpefc_identifier_part(str_replace('\\', '/', $sourceFile));
}

function phpefc_source_main_function(string $sourceFile): string
{
    return 'phpef_' . phpefc_source_prefix($sourceFile) . '__main';
}

function phpefc_source_main_function_from_prefix(string $prefix): string
{
    return 'phpef_file_' . phpefc_identifier_part($prefix !== '' ? $prefix : 'unknown_php');
}

if ($argc < 3) {
    cli_error("Usage: {$argv[0]} <input.phpir> <output.valair> [--external-phpir <other.phpir> ...] [--include-function <path> <vala-callee> ...] [--function-prefix <prefix>] [--user-function-abi-prefix <prefix>] [--source-function-prefix <path> <prefix> ...]\n");
    exit(2);
}

$inputFile = $argv[1];
$outputFile = $argv[2];
$externalPhpIrFiles = [];
$includeActivationFunctions = [];
$localFunctionPrefix = '';
$userFunctionAbiPrefix = 'phpef';
$sourceFunctionPrefixes = [];
for ($i = 3; $i < $argc; $i++) {
    if ($argv[$i] === '--external-phpir') {
        $i++;
        if ($i >= $argc || $argv[$i] === '') {
            cli_error("Missing value for --external-phpir\n");
            exit(2);
        }
        $externalPhpIrFiles[] = $argv[$i];
        continue;
    }
    if ($argv[$i] === '--include-function') {
        if ($i + 2 >= $argc || $argv[$i + 1] === '' || $argv[$i + 2] === '') {
            cli_error("Missing value for --include-function <path> <vala-callee>\n");
            exit(2);
        }
        $includeActivationFunctions[$argv[$i + 1]] = $argv[$i + 2];
        $i += 2;
        continue;
    }
    if ($argv[$i] === '--function-prefix') {
        $i++;
        if ($i >= $argc || $argv[$i] === '') {
            cli_error("Missing value for --function-prefix\n");
            exit(2);
        }
        $localFunctionPrefix = $argv[$i];
        continue;
    }
    if ($argv[$i] === '--user-function-abi-prefix') {
        $i++;
        if ($i >= $argc || $argv[$i] === '') {
            cli_error("Missing value for --user-function-abi-prefix\n");
            exit(2);
        }
        $userFunctionAbiPrefix = $argv[$i];
        continue;
    }
    if ($argv[$i] === '--source-function-prefix') {
        if ($i + 2 >= $argc || $argv[$i + 1] === '' || $argv[$i + 2] === '') {
            cli_error("Missing value for --source-function-prefix <path> <prefix>\n");
            exit(2);
        }
        $sourceFunctionPrefixes[$argv[$i + 1]] = $argv[$i + 2];
        $i += 2;
        continue;
    }
    cli_error("Unknown option: {$argv[$i]}\n");
    exit(2);
}
$serializedPhpIr = @file_get_contents($inputFile);
if ($serializedPhpIr === false) {
    cli_error("Could not read input file: {$inputFile}\n");
    exit(1);
}

try {
    $phpIr = PhpSerializedBundle::unpack($serializedPhpIr);
    $sourceFile = phpir_source_file($phpIr);
    $externalPhpIrs = [];
    $sourceFiles = $sourceFile !== '' ? [$sourceFile] : [];
    foreach ($externalPhpIrFiles as $externalPhpIrFile) {
        $externalSerializedPhpIr = @file_get_contents($externalPhpIrFile);
        if ($externalSerializedPhpIr === false) {
            throw new RuntimeException("Could not read external PHP-IR file: {$externalPhpIrFile}");
        }
        $externalPhpIr = PhpSerializedBundle::unpack($externalSerializedPhpIr);
        $externalSourceFile = phpir_source_file($externalPhpIr);
        $externalPhpIrs[] = [$externalPhpIr, $externalSourceFile];
        if ($externalSourceFile !== '') {
            $sourceFiles[] = $externalSourceFile;
        }
    }

    $sourceRoot = phpefc_common_source_root($sourceFiles);
    $autoSourceFunctionPrefixes = [];
    foreach ($sourceFiles as $sourceFileForPrefix) {
        $autoSourceFunctionPrefixes[normalized_source_path($sourceFileForPrefix)] = phpefc_source_prefix_from_root($sourceFileForPrefix, $sourceRoot);
    }

    $sourcePrefixFor = static function (string $candidateSourceFile) use ($sourceFunctionPrefixes, $autoSourceFunctionPrefixes): ?string {
        $explicit = source_function_prefix_for($candidateSourceFile, $sourceFunctionPrefixes);
        if ($explicit !== null) {
            return $explicit;
        }
        return $autoSourceFunctionPrefixes[normalized_source_path($candidateSourceFile)] ?? null;
    };

    $externalUserFunctionsByInclude = [];
    foreach ($externalPhpIrs as [$externalPhpIr, $externalSourceFile]) {
        $externalPrefix = $sourcePrefixFor($externalSourceFile);
        $externalFunctions = ValaIrBuilder::userFunctionMapFromPhpIr($externalPhpIr, $externalPrefix);
        $preferredIncludeActivation = include_activation_for_source($externalSourceFile, $includeActivationFunctions) ?? phpefc_source_main_function_from_prefix($externalPrefix ?? phpefc_source_prefix($externalSourceFile));
        foreach (ValaIrBuilder::includeLookupKeys($externalSourceFile) as $key) {
            $externalUserFunctionsByInclude[$key] = $externalFunctions;
        }
        foreach (phpefc_portable_include_keys_for_source($externalSourceFile, $sourceRoot) as $key) {
            $externalUserFunctionsByInclude[$key] = $externalFunctions;
            if (!isset($includeActivationFunctions[$key])) {
                $includeActivationFunctions[$key] = $preferredIncludeActivation;
            }
        }
    }

    $effectiveLocalFunctionPrefix = $localFunctionPrefix !== '' ? $localFunctionPrefix : ($sourcePrefixFor($sourceFile) ?? '');
    $valaIr = ValaIrBuilder::fromPhpIr($phpIr, $externalUserFunctionsByInclude, $includeActivationFunctions, $effectiveLocalFunctionPrefix, $userFunctionAbiPrefix);
    $serializedValaIr = PhpSerializedBundle::pack($valaIr);
} catch (Throwable $error) {
    cli_error($error->getMessage() . "\n");
    exit(1);
}

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

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