<?php

declare(strict_types=1);

namespace App\Domain\Blog;

use DOMDocument;
use DOMElement;
use DOMXPath;
use League\CommonMark\Environment\Environment;
use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
use League\CommonMark\Extension\GithubFlavoredMarkdownExtension;
use League\CommonMark\Extension\HeadingPermalink\HeadingPermalinkExtension;
use League\CommonMark\MarkdownConverter;
use League\HTMLToMarkdown\HtmlConverter;
use Mews\Purifier\Facades\Purifier;

class ContentRenderer
{
    public function __construct(
        protected AttachEmbed $attachEmbed,
    ) {}

    public function toHtml(string $content, string $format, ?int $articleId = null): string
    {
        return $this->render($content, $format, $articleId)['html'];
    }

    /**
     * @return array{html: string, toc: list<array{level: int, id: string, text: string}>}
     */
    public function render(string $content, string $format, ?int $articleId = null): array
    {
        $format = ContentFormat::normalize($format);

        if ($format === ContentFormat::MARKDOWN) {
            $content = $this->attachEmbed->legacyToMarkdownReference($content, $articleId);
            $content = $this->attachEmbed->expandMarkdownAttachProtocol($content);
            $html = $this->markdownToHtml($content);
        } else {
            $html = $content;
        }

        $html = $this->attachEmbed->hydrateHtml($html, $articleId);
        $html = Purifier::clean($html, 'article');

        return $this->applyTocAnchors($html);
    }

    public function markdownToHtml(string $markdown): string
    {
        $environment = new Environment([
            'html_input' => 'strip',
            'allow_unsafe_links' => false,
            'heading_permalink' => [
                'html_class' => 'heading-permalink',
                'id_prefix' => '',
                'fragment_prefix' => '',
                'insert' => 'none',
                'apply_id_to_heading' => true,
                'heading_class' => '',
            ],
        ]);
        $environment->addExtension(new CommonMarkCoreExtension);
        $environment->addExtension(new GithubFlavoredMarkdownExtension);
        $environment->addExtension(new HeadingPermalinkExtension);

        return (string) (new MarkdownConverter($environment))->convert($markdown);
    }

    public function htmlToMarkdown(string $html): string
    {
        $converter = new HtmlConverter([
            'strip_tags' => true,
            'hard_break' => true,
        ]);

        return trim($converter->convert($html));
    }

    /**
     * Convert sablog HTML article body into Markdown storage form.
     * Protects [attach=id] / src="[attach=id]" with placeholders during conversion.
     */
    public function convertImportedHtmlToMarkdown(string $html, ?int $articleId = null): string
    {
        [$protected, $map] = $this->attachEmbed->protectLegacyTokensForHtmlConversion($html, $articleId);
        $markdown = $this->htmlToMarkdown($protected);

        return $this->attachEmbed->restoreProtectedTokensToMarkdown($markdown, $map);
    }

    /**
     * @return array{html: string, toc: list<array{level: int, id: string, text: string}>}
     */
    protected function applyTocAnchors(string $html): array
    {
        if (trim($html) === '') {
            return ['html' => '', 'toc' => []];
        }

        $document = new DOMDocument;
        $previous = libxml_use_internal_errors(true);
        $document->loadHTML(
            '<?xml encoding="UTF-8"><div id="larablog-toc-root">'.$html.'</div>',
            LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD
        );
        libxml_clear_errors();
        libxml_use_internal_errors($previous);

        $xpath = new DOMXPath($document);
        $nodes = $xpath->query('//*[@id="larablog-toc-root"]//*[self::h2 or self::h3 or self::h4]');
        if ($nodes === false) {
            return ['html' => $html, 'toc' => []];
        }

        $toc = [];
        $usedIds = [];

        foreach ($nodes as $node) {
            if (! $node instanceof DOMElement) {
                continue;
            }

            $text = trim(preg_replace('/\s+/u', ' ', $node->textContent ?? '') ?? '');
            if ($text === '') {
                continue;
            }

            $level = (int) substr(strtolower($node->tagName), 1);
            $id = $node->getAttribute('id');
            if ($id === '') {
                $id = $this->slugifyHeading($text);
            }

            $base = $id;
            $i = 2;
            while (isset($usedIds[$id])) {
                $id = $base.'-'.$i;
                $i++;
            }
            $usedIds[$id] = true;
            $node->setAttribute('id', $id);

            $toc[] = [
                'level' => $level,
                'id' => $id,
                'text' => $text,
            ];
        }

        $root = $document->getElementById('larablog-toc-root');
        $out = $html;
        if ($root instanceof DOMElement) {
            $out = '';
            foreach ($root->childNodes as $child) {
                $out .= $document->saveHTML($child);
            }
        }

        return ['html' => $out, 'toc' => $toc];
    }

    protected function slugifyHeading(string $text): string
    {
        $slug = strtolower(trim($text));
        $slug = preg_replace('/[^\p{L}\p{N}\-_]+/u', '-', $slug) ?? '';
        $slug = trim($slug, '-');

        return $slug !== '' ? $slug : 'section';
    }
}
