<?php

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

if ($argc < 3) {
    cli_error("Usage: {$argv[0]} <input.valair> <output.vala> [--no-main] [--activation-function <vala-name>] [--library-init-function <vala-name>] [--library-register-function <vala-name>] [--forbid-runtime-embed] [--extern-function <vala-name>] [--auto-init-function <vala-name>]\n");
    exit(2);
}

function validate_vala_function_name(string $name, string $label): void
{
    if (preg_match('/^[A-Za-z_][A-Za-z0-9_]*$/', $name) !== 1) {
        throw new RuntimeException('Invalid ' . $label . ' function name: ' . $name);
    }
}

function count_text_lines(string $text): int
{
    if ($text === '') {
        return 0;
    }

    return substr_count($text, "\n") + (str_ends_with($text, "\n") ? 0 : 1);
}

function shift_embedded_c_source_line_map(string $vala, int $firstShiftedLine, int $lineDelta): string
{
    if ($lineDelta === 0) {
        return $vala;
    }
    return preg_replace_callback('/PHPEFC-C-SOURCE-LINES-BEGIN\\R(.*?)\\RPHPEFC-C-SOURCE-LINES-END/s', static function (array $match) use ($firstShiftedLine, $lineDelta): string {
        $sourceMap = json_decode($match[1], true);
        if (!is_array($sourceMap) || ($sourceMap['format'] ?? null) !== 'phpefc-c-source-lines-v0') {
            return $match[0];
        }
        if (isset($sourceMap["mappings"]) && is_array($sourceMap["mappings"])) {
            foreach ($sourceMap["mappings"] as &$mapping) {
                if (!is_array($mapping)) {
                    continue;
                }
                $valaLine = isset($mapping['valaLine']) ? (int) $mapping['valaLine'] : 0;
                if ($valaLine >= $firstShiftedLine) {
                    $mapping['valaLine'] = $valaLine + $lineDelta;
                }
            }
            unset($mapping);
        }
        return "PHPEFC-C-SOURCE-LINES-BEGIN\n" . json_encode($sourceMap, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\nPHPEFC-C-SOURCE-LINES-END";
    }, $vala, 1) ?? $vala;
}

$inputFile = $argv[1];
$outputFile = $argv[2];
$emitMainWrapper = true;
$activationFunctionName = null;
$libraryInitFunctionName = null;
$externFunctions = [];
$autoInitFunctions = [];
$libraryRegisterFunctions = [];
$forbidRuntimeEmbed = false;
for ($i = 3; $i < $argc; $i++) {
    if ($argv[$i] === '--no-main') {
        $emitMainWrapper = false;
        continue;
    }
    if ($argv[$i] === '--activation-function') {
        $i++;
        if ($i >= $argc || $argv[$i] === '') {
            cli_error("Missing value for --activation-function\n");
            exit(2);
        }
        $activationFunctionName = $argv[$i];
        continue;
    }
    if ($argv[$i] === '--library-init-function') {
        $i++;
        if ($i >= $argc || $argv[$i] === '') {
            cli_error("Missing value for --library-init-function\n");
            exit(2);
        }
        $libraryInitFunctionName = $argv[$i];
        continue;
    }
    if ($argv[$i] === '--library-register-function') {
        $i++;
        if ($i >= $argc || $argv[$i] === '') {
            cli_error("Missing value for --library-register-function\n");
            exit(2);
        }
        $libraryRegisterFunctions[] = $argv[$i];
        continue;
    }
    if ($argv[$i] === '--forbid-runtime-embed') {
        $forbidRuntimeEmbed = true;
        continue;
    }
    if ($argv[$i] === '--extern-function') {
        $i++;
        if ($i >= $argc || $argv[$i] === '') {
            cli_error("Missing value for --extern-function\n");
            exit(2);
        }
        $externFunctions[] = $argv[$i];
        continue;
    }
    if ($argv[$i] === '--auto-init-function') {
        $i++;
        if ($i >= $argc || $argv[$i] === '') {
            cli_error("Missing value for --auto-init-function\n");
            exit(2);
        }
        $autoInitFunctions[] = $argv[$i];
        continue;
    }
    cli_error("Unknown option: {$argv[$i]}\n");
    exit(2);
}

$serializedValaIr = @file_get_contents($inputFile);
if ($serializedValaIr === false) {
    cli_error("Could not read input file: {$inputFile}\n");
    exit(1);
}

try {
    $valaIr = PhpSerializedBundle::unpack($serializedValaIr);
    $generated = ValaCodeGenerator::fromValaIrWithSourceMap($valaIr, basename($outputFile), $emitMainWrapper, $activationFunctionName);
    $vala = $generated['vala'];

    $externVala = '';
    foreach (array_values(array_unique($externFunctions)) as $externFunction) {
        validate_vala_function_name($externFunction, 'extern');
        $externVala .= '[CCode (cname = "' . $externFunction . '")]' . "\n";
        $externVala .= 'public extern static void ' . $externFunction . '() throws Error;' . "\n";
    }
    if ($externVala !== '') {
        $externLineCount = count_text_lines($externVala);
        if (preg_match('/^(using [^;]+;\n)+/', $vala, $usingMatch) === 1) {
            $firstShiftedLine = count_text_lines($usingMatch[0]) + 1;
            $vala = preg_replace('/^(using [^;]+;\n)+/', '$0' . $externVala, $vala, 1) ?? ($usingMatch[0] . $externVala . substr($vala, strlen($usingMatch[0])));
        } else {
            $firstShiftedLine = 1;
            $vala = $externVala . $vala;
        }
        $vala = shift_embedded_c_source_line_map($vala, $firstShiftedLine, $externLineCount);
    }

    foreach (array_values(array_unique($autoInitFunctions)) as $autoInitFunction) {
        validate_vala_function_name($autoInitFunction, 'auto init');
        $inserted = false;
        if ($activationFunctionName !== null) {
            $stacktraceCall = "    " . $activationFunctionName . "__register_stacktrace_frames();\n";
            if (str_contains($vala, $stacktraceCall)) {
                $vala = str_replace(
                    $stacktraceCall,
                    $stacktraceCall . "    " . $autoInitFunction . "();\n",
                    $vala
                );
                $inserted = true;
            }
        }
        if (!$inserted) {
            $vala = str_replace(
                "public static void phpef_main() throws Error {\n",
                "public static void phpef_main() throws Error {\n    " . $autoInitFunction . "();\n",
                $vala
            );
        }
    }

    if ($libraryInitFunctionName !== null) {
        validate_vala_function_name($libraryInitFunctionName, 'library init');
        if ($activationFunctionName === null) {
            throw new RuntimeException('--library-init-function requires --activation-function');
        }
        validate_vala_function_name($activationFunctionName, 'activation');
        $registerFunctions = $libraryRegisterFunctions !== [] ? $libraryRegisterFunctions : [$activationFunctionName];
        $registerLines = "";
        foreach (array_values(array_unique($registerFunctions)) as $registerFunction) {
            validate_vala_function_name($registerFunction, 'library register');
            $registerLines .= "    " . $registerFunction . "__register_functions();\n";
        }
        $initVala = "\npublic static void " . $libraryInitFunctionName . "() throws Error {\n"
            . $registerLines
            . "    " . $activationFunctionName . "();\n"
            . "}\n";
        $vala = preg_replace('/\n\/\*\nPHPEFC-C-SOURCE-LINES-BEGIN\n/s', $initVala . "\n/*\nPHPEFC-C-SOURCE-LINES-BEGIN\n", $vala, 1) ?? ($vala . $initVala);
    }

    if ($forbidRuntimeEmbed && str_contains($vala, 'PhpefRuntimeEmbed.')) {
        throw new RuntimeException("PHPEF target requires phpef-runtime-embed, but meson.build.pw has phpef-runtime-embed=false. Not all required PHP functions are native yet; set phpef-runtime-embed=true to allow embed fallback, or add native implementations/allowed native runtimes.");
    }
} catch (Throwable $error) {
    cli_error($error->getMessage() . "\n");
    exit(1);
}

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

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