<?php
declare(strict_types=1);

namespace KbMarkdownImporter\Markdown;

use KbMarkdownImporter\Frontend\UrlBuilder;

final class MarkdownRenderer
{
    public function render(string $markdown, array $context = []): string
    {
        $lines = preg_split('/\R/', str_replace(["\r\n", "\r"], "\n", $markdown)) ?: [];
        $html = [];
        $paragraph = [];
        $listType = '';
        $codeFence = false;
        $code = [];

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

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

        $closeList = function () use (&$html, &$listType): void {
            if ('' === $listType) {
                return;
            }

            $html[] = '</' . $listType . '>';
            $listType = '';
        };

        foreach ($lines as $line) {
            if (preg_match('/^\s*```/', $line)) {
                if ($codeFence) {
                    $html[] = '<pre><code>' . esc_html(implode("\n", $code)) . '</code></pre>';
                    $code = [];
                    $codeFence = false;
                } else {
                    $flushParagraph();
                    $closeList();
                    $codeFence = true;
                }
                continue;
            }

            if ($codeFence) {
                $code[] = $line;
                continue;
            }

            if ('' === trim($line)) {
                $flushParagraph();
                $closeList();
                continue;
            }

            if (preg_match('/^(#{1,6})\s+(.+)$/', $line, $matches)) {
                $flushParagraph();
                $closeList();
                $level = strlen($matches[1]);
                $text = trim($matches[2]);
                $id = sanitize_title(wp_strip_all_tags($text));
                $html[] = sprintf('<h%d id="%s">%s</h%d>', $level, esc_attr($id), $this->renderInline($text, $context), $level);
                continue;
            }

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

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

            if (preg_match('/^>\s?(.*)$/', $line, $matches)) {
                $flushParagraph();
                $closeList();
                $html[] = '<blockquote><p>' . $this->renderInline(trim($matches[1]), $context) . '</p></blockquote>';
                continue;
            }

            $paragraph[] = trim($line);
        }

        if ($codeFence) {
            $html[] = '<pre><code>' . esc_html(implode("\n", $code)) . '</code></pre>';
        }

        $flushParagraph();
        $closeList();

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

    private function renderInline(string $text, array $context): string
    {
        $escaped = esc_html($text);

        $escaped = preg_replace_callback('/!\[([^\]]*)\]\(([^)]+)\)/', function (array $matches) use ($context): string {
            $alt = html_entity_decode($matches[1], ENT_QUOTES);
            $src = html_entity_decode($matches[2], ENT_QUOTES);
            $url = $this->resolveImageUrl($src, (array) ($context['images'] ?? []));
            $image = sprintf('<img src="%s" alt="%s">', esc_url($url ?: $src), esc_attr($alt));

            if ($url && ! empty($context['lightbox'])) {
                return sprintf('<a href="%s" class="kb-lightbox">%s</a>', esc_url($url), $image);
            }

            return $image;
        }, $escaped) ?? $escaped;

        $escaped = preg_replace_callback('/(?<!!)\[([^\]]+)\]\(([^)]+)\)/', function (array $matches) use ($context): string {
            $label = $this->renderInline($matches[1], $context);
            $href = html_entity_decode($matches[2], ENT_QUOTES);
            $url = $this->rewriteLink($href, $context) ?: $href;

            return sprintf('<a href="%s">%s</a>', esc_url($url), $label);
        }, $escaped) ?? $escaped;

        $escaped = preg_replace('/`([^`]+)`/', '<code>$1</code>', $escaped) ?? $escaped;
        $escaped = preg_replace('/\*\*([^*]+)\*\*/', '<strong>$1</strong>', $escaped) ?? $escaped;
        $escaped = preg_replace('/\*([^*]+)\*/', '<em>$1</em>', $escaped) ?? $escaped;

        return $escaped;
    }

    private function rewriteLink(string $href, array $context): string
    {
        if ('' === $href || str_starts_with($href, '#') || preg_match('#^(?:https?:|mailto:|tel:)#i', $href)) {
            return '';
        }

        $parts = wp_parse_url($href);
        if (! is_array($parts)) {
            return '';
        }

        $path = (string) ($parts['path'] ?? '');
        if (! preg_match('/\.md$/i', $path)) {
            return '';
        }

        $page = preg_replace('/\.md$/i', '', basename($path)) ?: basename($path);
        $slug = in_array(strtolower($page), ['doku', 'index'], true) ? '' : sanitize_title($page);
        $fragment = isset($parts['fragment']) ? '#' . sanitize_title((string) $parts['fragment']) : '';

        return UrlBuilder::page((string) $context['product_slug'], (string) $context['version_slug'], $slug) . $fragment;
    }

    private function resolveImageUrl(string $src, array $images): string
    {
        if ('' === $src || preg_match('#^(?:https?:|data:)#i', $src)) {
            return '';
        }

        $candidates = array_unique([
            $src,
            ltrim($src, '/'),
            basename($src),
            preg_replace('#^images/#', '', $src) ?: $src,
        ]);

        foreach ($candidates as $candidate) {
            if (isset($images[$candidate])) {
                return (string) $images[$candidate];
            }
        }

        return '';
    }
}
