<?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 && $argc !== 4) {
    cli_error("Usage: {$argv[0]} <source.php> <generated.php> [stamp]\n");
    exit(2);
}

$sourcePath = $argv[1];
$generatedPath = $argv[2];
$stampPath = $argv[3] ?? null;

function comparable_php_tokens(string $path): array
{
    $code = file_get_contents($path);
    if ($code === false) {
        throw new RuntimeException("failed to read $path");
    }

    $tokens = [];
    foreach (token_get_all($code) as $token) {
        if (is_array($token)) {
            [$id, $text, $line] = $token;
            if ($id === T_WHITESPACE) {
                continue;
            }

            // T_OPEN_TAG/T_CLOSE_TAG can include adjacent outside-string whitespace.
            if ($id === T_OPEN_TAG || $id === T_OPEN_TAG_WITH_ECHO || $id === T_CLOSE_TAG) {
                $text = trim($text);
            }

            $tokens[] = [token_name($id), $text, $line];
            continue;
        }

        $tokens[] = [$token, $token, null];
    }

    return $tokens;
}

function token_label(array $token): string
{
    [$kind, $text, $line] = $token;
    $encoded = json_encode($text, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
    $where = $line === null ? '' : " line $line";
    return "$kind$where $encoded";
}

try {
    $sourceTokens = comparable_php_tokens($sourcePath);
    $generatedTokens = comparable_php_tokens($generatedPath);
} catch (Throwable $error) {
    cli_error($error->getMessage() . "\n");
    exit(2);
}

$max = max(count($sourceTokens), count($generatedTokens));
for ($i = 0; $i < $max; $i++) {
    $sourceToken = $sourceTokens[$i] ?? null;
    $generatedToken = $generatedTokens[$i] ?? null;

    if ($sourceToken !== null && $generatedToken !== null
        && $sourceToken[0] === $generatedToken[0]
        && $sourceToken[1] === $generatedToken[1]) {
        continue;
    }

    cli_error("roundtrip token mismatch at comparable token #" . ($i + 1) . "\n");
    cli_error("source:    " . ($sourceToken === null ? '[end]' : token_label($sourceToken)) . "\n");
    cli_error("generated: " . ($generatedToken === null ? '[end]' : token_label($generatedToken)) . "\n");

    $from = max(0, $i - 3);
    $to = min($max - 1, $i + 3);
    cli_error("\ncontext:\n");
    for ($j = $from; $j <= $to; $j++) {
        $prefix = $j === $i ? '=> ' : '   ';
        cli_error($prefix . '#' . ($j + 1) . "\n");
        cli_error("   source:    " . (isset($sourceTokens[$j]) ? token_label($sourceTokens[$j]) : '[end]') . "\n");
        cli_error("   generated: " . (isset($generatedTokens[$j]) ? token_label($generatedTokens[$j]) : '[end]') . "\n");
    }

    exit(1);
}

if ($stampPath !== null && file_put_contents($stampPath, "ok\n") === false) {
    cli_error("failed to write stamp: $stampPath\n");
    exit(1);
}

echo "ok - " . basename($sourcePath) . " and " . basename($generatedPath) . " are token-equivalent\n";
