<?php

namespace app\service\AdminConsole;

class MarkdownRenderer
{
    public function render(string $markdown): string
    {
        $lines = preg_split('/\r\n|\n|\r/', $markdown) ?: [];
        $html = [];
        $paragraph = [];
        $listType = null;
        $table = null;
        $inCodeBlock = false;
        $codeLines = [];

        $flushParagraph = function () use (&$paragraph, &$html): void {
            if ($paragraph === []) {
                return;
            }

            $text = implode(' ', array_map('trim', $paragraph));
            $html[] = '<p>' . $this->renderInline($text) . '</p>';
            $paragraph = [];
        };

        $flushList = function () use (&$listType, &$html): void {
            if ($listType !== null) {
                $html[] = '</' . $listType . '>';
                $listType = null;
            }
        };

        $flushTable = function () use (&$table, &$html): void {
            if ($table === null) {
                return;
            }

            $html[] = '<table class="markdown-table"><thead><tr>' .
                implode('', array_map(fn (string $cell): string => '<th>' . $this->renderInline($cell) . '</th>', $table['headers'])) .
                '</tr></thead><tbody>';
            foreach ($table['rows'] as $row) {
                $html[] = '<tr>' .
                    implode('', array_map(fn (string $cell): string => '<td>' . $this->renderInline($cell) . '</td>', $row)) .
                    '</tr>';
            }
            $html[] = '</tbody></table>';
            $table = null;
        };

        foreach ($lines as $line) {
            if (preg_match('/^```/', $line)) {
                $flushParagraph();
                $flushList();
                $flushTable();

                if ($inCodeBlock) {
                    $html[] = '<pre class="markdown-pre"><code>' . htmlspecialchars(implode("\n", $codeLines), ENT_QUOTES, 'UTF-8') . '</code></pre>';
                    $codeLines = [];
                    $inCodeBlock = false;
                } else {
                    $inCodeBlock = true;
                }
                continue;
            }

            if ($inCodeBlock) {
                $codeLines[] = $line;
                continue;
            }

            $trimmed = trim($line);
            if ($trimmed === '') {
                $flushParagraph();
                $flushList();
                $flushTable();
                continue;
            }

            if (preg_match('/^(#{1,6})\s+(.+)$/', $trimmed, $matches)) {
                $flushParagraph();
                $flushList();
                $flushTable();
                $level = strlen($matches[1]);
                $html[] = sprintf('<h%d>%s</h%d>', $level, $this->renderInline($matches[2]), $level);
                continue;
            }

            if (preg_match('/^>\s?(.+)$/', $trimmed, $matches)) {
                $flushParagraph();
                $flushList();
                $flushTable();
                $html[] = '<blockquote>' . $this->renderInline($matches[1]) . '</blockquote>';
                continue;
            }

            if (preg_match('/^---+$/', $trimmed)) {
                $flushParagraph();
                $flushList();
                $flushTable();
                $html[] = '<hr>';
                continue;
            }

            if ($this->isTableDelimiter($trimmed) && $table !== null) {
                continue;
            }

            if (str_contains($trimmed, '|')) {
                $cells = $this->tableCells($trimmed);
                if (count($cells) >= 2) {
                    $flushParagraph();
                    $flushList();
                    if ($table === null) {
                        $table = ['headers' => $cells, 'rows' => []];
                    } else {
                        $table['rows'][] = $cells;
                    }
                    continue;
                }
            }

            if (preg_match('/^[-*]\s+(.+)$/', $trimmed, $matches)) {
                $flushParagraph();
                $flushTable();
                if ($listType !== 'ul') {
                    $flushList();
                    $listType = 'ul';
                    $html[] = '<ul>';
                }
                $html[] = '<li>' . $this->renderInline($matches[1]) . '</li>';
                continue;
            }

            if (preg_match('/^\d+\.\s+(.+)$/', $trimmed, $matches)) {
                $flushParagraph();
                $flushTable();
                if ($listType !== 'ol') {
                    $flushList();
                    $listType = 'ol';
                    $html[] = '<ol>';
                }
                $html[] = '<li>' . $this->renderInline($matches[1]) . '</li>';
                continue;
            }

            $flushList();
            $flushTable();
            $paragraph[] = $trimmed;
        }

        if ($inCodeBlock) {
            $html[] = '<pre class="markdown-pre"><code>' . htmlspecialchars(implode("\n", $codeLines), ENT_QUOTES, 'UTF-8') . '</code></pre>';
        }

        $flushParagraph();
        $flushList();
        $flushTable();

        return implode("\n", $html);
    }

    private function renderInline(string $text): string
    {
        $text = htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
        $text = preg_replace('/`([^`]+)`/', '<code>$1</code>', $text) ?? $text;
        $text = preg_replace('/\*\*([^*]+)\*\*/', '<strong>$1</strong>', $text) ?? $text;
        $text = preg_replace('/\*([^*]+)\*/', '<em>$1</em>', $text) ?? $text;
        $text = preg_replace('/\[(.+?)\]\((.+?)\)/', '<a href="$2" target="_blank" rel="noreferrer">$1</a>', $text) ?? $text;

        return $text;
    }

    private function isTableDelimiter(string $line): bool
    {
        return (bool) preg_match('/^\|?[\s:-]+\|[\s|:-]*$/', $line);
    }

    private function tableCells(string $line): array
    {
        $line = trim($line);
        $line = trim($line, '|');
        return array_map(static fn (string $cell): string => trim($cell), explode('|', $line));
    }
}
