<?php
declare(strict_types=1);

namespace Celena\Core\Support;

/**
 * Минимальный безопасный Markdown-рендерер — без внешних зависимостей.
 * Поддерживает: заголовки #, **bold**, *italic*, `code`, ```fenced```, ссылки [t](u),
 * картинки ![alt](u), списки -/* и 1., цитаты >, параграфы, переносы.
 * HTML из исходника экранируется.
 */
final class Markdown
{
    public static function render(string $source): string
    {
        $src = str_replace(["\r\n", "\r"], "\n", $source);

        // 1. Вырезаем fenced-блоки, чтобы не трогать их содержимое.
        $fences = [];
        $src = preg_replace_callback('/```([a-zA-Z0-9_\-]*)\n([\s\S]*?)```/u', function ($m) use (&$fences) {
            $placeholder = "\x01FENCE" . count($fences) . "\x02";
            $lang = htmlspecialchars($m[1], ENT_QUOTES, 'UTF-8');
            $code = htmlspecialchars($m[2], ENT_QUOTES, 'UTF-8');
            $cls = $lang !== '' ? ' class="language-' . $lang . '"' : '';
            $fences[] = "<pre><code$cls>$code</code></pre>";
            return $placeholder;
        }, $src) ?? $src;

        // 2. Экранируем остальной HTML.
        $src = htmlspecialchars($src, ENT_QUOTES, 'UTF-8');

        // 3. Инлайн: картинки, ссылки, bold, italic, code.
        $src = preg_replace('/!\[([^\]]*)\]\(([^)\s]+)(?:\s+&quot;([^&]+)&quot;)?\)/u', '<img src="$2" alt="$1">', $src) ?? $src;
        $src = preg_replace('/\[([^\]]+)\]\(([^)]+)\)/u', '<a href="$2">$1</a>', $src) ?? $src;
        $src = preg_replace('/\*\*(.+?)\*\*/u', '<strong>$1</strong>', $src) ?? $src;
        $src = preg_replace('/(?<![\w*])\*([^*\n]+)\*(?![\w*])/u', '<em>$1</em>', $src) ?? $src;
        $src = preg_replace('/`([^`\n]+)`/u', '<code>$1</code>', $src) ?? $src;

        // 4. Построчная обработка блоков: заголовки, списки, цитаты, hr, параграфы.
        $lines = explode("\n", $src);
        $out = [];
        $inUl = false; $inOl = false; $inBq = false; $paragraph = [];

        $flushParagraph = function () use (&$out, &$paragraph) {
            if ($paragraph !== []) {
                $out[] = '<p>' . implode("<br>\n", $paragraph) . '</p>';
                $paragraph = [];
            }
        };
        $closeLists = function () use (&$out, &$inUl, &$inOl, &$inBq) {
            if ($inUl) { $out[] = '</ul>'; $inUl = false; }
            if ($inOl) { $out[] = '</ol>'; $inOl = false; }
            if ($inBq) { $out[] = '</blockquote>'; $inBq = false; }
        };

        foreach ($lines as $line) {
            $trim = trim($line);
            if ($trim === '') {
                $flushParagraph();
                $closeLists();
                continue;
            }
            if (preg_match('/^(#{1,6})\s+(.+)$/', $trim, $m)) {
                $flushParagraph();
                $closeLists();
                $level = strlen($m[1]);
                $out[] = "<h$level>" . $m[2] . "</h$level>";
                continue;
            }
            if (preg_match('/^[-*]\s+(.+)$/', $trim, $m)) {
                $flushParagraph();
                if (!$inUl) { $closeLists(); $out[] = '<ul>'; $inUl = true; }
                $out[] = '<li>' . $m[1] . '</li>';
                continue;
            }
            if (preg_match('/^\d+\.\s+(.+)$/', $trim, $m)) {
                $flushParagraph();
                if (!$inOl) { $closeLists(); $out[] = '<ol>'; $inOl = true; }
                $out[] = '<li>' . $m[1] . '</li>';
                continue;
            }
            if (preg_match('/^&gt;\s?(.*)$/', $trim, $m)) {
                $flushParagraph();
                if (!$inBq) { $closeLists(); $out[] = '<blockquote>'; $inBq = true; }
                $out[] = $m[1];
                continue;
            }
            if (preg_match('/^(-{3,}|_{3,}|\*{3,})$/', $trim)) {
                $flushParagraph();
                $closeLists();
                $out[] = '<hr>';
                continue;
            }
            if ($inUl || $inOl || $inBq) {
                $closeLists();
            }
            $paragraph[] = $trim;
        }
        $flushParagraph();
        $closeLists();

        $html = implode("\n", $out);

        // 5. Возвращаем fenced-блоки.
        foreach ($fences as $i => $block) {
            $html = str_replace("\x01FENCE$i\x02", $block, $html);
        }
        return $html;
    }
}
