<?php

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

function stub_identifier(string $name): string
{
    return preg_match('/^[A-Za-z_][A-Za-z0-9_]*$/', $name) === 1 ? $name : 'unknown';
}

function stub_param(PhpParser\Node\Param $param): string
{
    $name = $param->var instanceof PhpParser\Node\Expr\Variable && is_string($param->var->name) ? $param->var->name : 'arg';
    $out = '';
    if ($param->byRef) {
        $out .= '&';
    }
    if ($param->variadic) {
        $out .= '...';
    }
    $out .= '$' . stub_identifier($name);
    if ($param->default !== null && !$param->variadic) {
        $out .= ' = null';
    }
    return $out;
}

function stub_function_line(string $name, array $params, string $indent = ''): string
{
    $renderedParams = array_map(static fn($param): string => $param instanceof PhpParser\Node\Param ? stub_param($param) : '$arg', $params);
    return $indent . 'function ' . stub_identifier($name) . '(' . implode(', ', $renderedParams) . ') {}';
}

function stub_class_lines(PhpParser\Node\Stmt\Class_ $class): array
{
    if ($class->name === null) {
        return [];
    }
    $lines = ['class ' . stub_identifier($class->name->toString()) . ' {'];
    foreach ($class->stmts as $stmt) {
        if (!$stmt instanceof PhpParser\Node\Stmt\ClassMethod) {
            continue;
        }
        $name = $stmt->name->toString();
        $renderedParams = array_map(static fn(PhpParser\Node\Param $param): string => stub_param($param), $stmt->params);
        $prefix = $stmt->isStatic() ? 'public static function ' : 'public function ';
        $lines[] = '    ' . $prefix . stub_identifier($name) . '(' . implode(', ', $renderedParams) . ') {}';
    }
    $lines[] = '}';
    return $lines;
}

function collect_stub_lines(array $ast): array
{
    $lines = [];
    foreach ($ast as $stmt) {
        if ($stmt instanceof PhpParser\Node\Stmt\Function_) {
            $lines[] = stub_function_line($stmt->name->toString(), $stmt->params);
            continue;
        }
        if ($stmt instanceof PhpParser\Node\Stmt\Class_) {
            foreach (stub_class_lines($stmt) as $line) {
                $lines[] = $line;
            }
            continue;
        }
        if ($stmt instanceof PhpParser\Node\Stmt\Namespace_) {
            $namespace = $stmt->name !== null ? $stmt->name->toString() : '';
            $inner = collect_stub_lines($stmt->stmts);
            if ($inner !== []) {
                $lines[] = 'namespace ' . $namespace . ' {';
                foreach ($inner as $line) {
                    $lines[] = '    ' . $line;
                }
                $lines[] = '}';
            }
        }
    }
    return $lines;
}

if ($argc < 3) {
    cli_error("Usage: {$argv[0]} <input.php> [input2.php ...] <output.php>\n");
    exit(2);
}

$outputFile = $argv[$argc - 1];
$inputFiles = array_slice($argv, 1, -1);
$allLines = [
    '<?php',
    '// Generated PHPEF library stub. Runtime ABI lives in the matching .so.',
];

try {
    foreach ($inputFiles as $inputFile) {
        $code = @file_get_contents($inputFile);
        if ($code === false) {
            throw new RuntimeException("Could not read input file: {$inputFile}");
        }
        $lines = collect_stub_lines(PhpAstProcessor::phpSource2ast($code));
        foreach ($lines as $line) {
            if (!in_array($line, $allLines, true)) {
                $allLines[] = $line;
            }
        }
    }
} catch (Throwable $error) {
    cli_error($error->getMessage() . "\n");
    exit(1);
}

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

echo "Wrote PHPEF library stub to {$outputFile}\n";
