<?php
declare(strict_types=1);
require dirname(__DIR__) . '/vendor/autoload.php';
use Aen\MarkdownLab\Converter;
$converter = new Converter();
$checks = [];
function check(string $name, bool $ok): void {
    global $checks; $checks[] = ['name' => $name, 'pass' => $ok];
    if (!$ok) { fwrite(STDERR, "FAIL $name\n"); }
}
function rejects(string $name, callable $fn, string $class): void {
    try { $fn(); check($name, false); } catch (Throwable $e) { check($name, $e instanceof $class); }
}
$fixtures = json_decode(file_get_contents(dirname(__DIR__) . '/public/fixtures.json'), true, 32, JSON_THROW_ON_ERROR);
foreach ($fixtures as $i => $fixture) {
    $input = ['profile' => $fixture['profile'], 'htmlPolicy' => 'escape'];
    if ($fixture['batch'] ?? false) { $input['documents'] = preg_split('/^---DOC---\s*$/m', $fixture['markdown']); }
    else { $input['markdown'] = $fixture['markdown']; }
    $result = $converter->request($input);
    check('fixture_' . ($i + 1), $result['ok'] && count($result['results']) > 0 && $result['results'][0]['outputBytes'] > 0);
}
$html = $converter->convert('# Привет **мир**')['html'];
check('unicode_and_heading', str_contains($html, 'Привет <strong>мир</strong>'));
$unsafe = '<script>alert(1)</script>' . "\n\n" . '[x](javascript:alert%281%29)' . "\n\n" . '[x](file:///etc/passwd)';
$escaped = $converter->convert($unsafe, 'comments')['html'];
check('escape_html', !str_contains($escaped, '<script>') && str_contains($escaped, '&lt;script&gt;'));
check('unsafe_protocols_removed', !str_contains($escaped, 'href="javascript:') && !str_contains($escaped, 'href="file:'));
check('strip_html', !str_contains($converter->convert('<b>x</b>', 'core', 'strip')['html'], '&lt;b&gt;'));
check('safe_https_remains', str_contains($converter->convert('[ok](https://example.com)')['html'], 'href="https://example.com"'));
$gfm = $converter->convert($fixtures[1]['markdown'], 'gfm')['html'];
check('gfm_table_tasks_del', str_contains($gfm, '<table>') && str_contains($gfm, 'type="checkbox"') && str_contains($gfm, '<del>'));
check('core_not_gfm', !str_contains($converter->convert($fixtures[1]['markdown'], 'core')['html'], '<table>'));
$code = $converter->convert($fixtures[2]['markdown'])['html'];
check('code_escaped', str_contains($code, 'language-php') && str_contains($code, '&lt;?php') && !str_contains($code, '<button>'));
$toc = $converter->convert($fixtures[3]['markdown'], 'docs')['html'];
preg_match_all('/id="([^"]+)"/', $toc, $ids);
check('toc_unique_ids', str_contains($toc, 'table-of-contents') && count($ids[1]) === count(array_unique($ids[1])) && count($ids[1]) >= 5);
preg_match_all('/href="#([^"]+)"/', $toc, $anchors);
check('toc_valid_anchors', count(array_diff($anchors[1], $ids[1])) === 0);
$links = $converter->convert($fixtures[5]['markdown'], 'links')['html'];
check('external_link_rel', str_contains($links, 'noopener') && str_contains($links, 'noreferrer') && str_contains($links, 'assets/diagram.svg'));
$notes = $converter->convert('{{note:<img src=x onerror=alert(1)>}}', 'notes')['html'];
check('custom_note_escapes', str_contains($notes, 'role="note"') && str_contains($notes, '&lt;img') && !str_contains($notes, '<img'));
check('custom_note_unicode', str_contains($converter->convert('{{note:Привет 🌿}}', 'notes')['html'], 'Привет 🌿'));
check('custom_note_limit', !str_contains($converter->convert('{{note:' . str_repeat('a', 161) . '}}', 'notes')['html'], 'role="note"'));
$deep = $converter->convert(str_repeat('> ', 80) . 'end')['html'];
check('nesting_bounded', substr_count($deep, '<blockquote>') <= 20 && str_contains($deep, 'end'));
check('delimiter_bounded', strlen($converter->convert(str_repeat('*x* ', 500))['html']) < Converter::MAX_OUTPUT);
check('empty_input', $converter->convert('')['html'] === '');
check('exact_byte_boundary', $converter->convert(str_repeat('a', 8192))['inputBytes'] === 8192);
rejects('over_byte_boundary', fn() => $converter->convert(str_repeat('a', 8193)), LengthException::class);
rejects('bad_utf8', fn() => $converter->convert("\xC3\x28"), InvalidArgumentException::class);
rejects('bad_profile', fn() => $converter->convert('x', 'unknown'), InvalidArgumentException::class);
rejects('unsafe_html_policy', fn() => $converter->convert('x', 'core', 'allow'), InvalidArgumentException::class);
rejects('wrong_data_type', fn() => $converter->request(['markdown' => ['x']]), InvalidArgumentException::class);
rejects('unknown_field', fn() => $converter->request(['markdown' => 'x', 'path' => '/etc/passwd']), InvalidArgumentException::class);
rejects('batch_too_large', fn() => $converter->request(['documents' => array_fill(0, 6, 'x')]), InvalidArgumentException::class);
rejects('batch_total_bytes', fn() => $converter->request(['documents' => [str_repeat('a', 5000), str_repeat('b', 5000)]]), LengthException::class);
rejects('ambiguous_input', fn() => $converter->request(['documents' => ['x'], 'markdown' => 'x']), InvalidArgumentException::class);
rejects('null_documents_not_absent', fn() => $converter->request(['documents' => null, 'markdown' => 'x']), InvalidArgumentException::class);
rejects('null_profile_not_absent', fn() => $converter->request(['markdown' => 'x', 'profile' => null]), InvalidArgumentException::class);
rejects('null_policy_not_absent', fn() => $converter->request(['markdown' => 'x', 'htmlPolicy' => null]), InvalidArgumentException::class);
$report = ['runtime' => PHP_VERSION, 'engine' => Composer\InstalledVersions::getPrettyVersion('league/commonmark'), 'checks' => $checks, 'passed' => count(array_filter($checks, fn($c) => $c['pass'])), 'total' => count($checks)];
echo json_encode($report, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) . PHP_EOL;
exit($report['passed'] === $report['total'] ? 0 : 1);
