<?php
declare(strict_types=1);
namespace Aen\MarkdownLab;

use League\CommonMark\Environment\Environment;
use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
use League\CommonMark\Extension\ExternalLink\ExternalLinkExtension;
use League\CommonMark\Extension\GithubFlavoredMarkdownExtension;
use League\CommonMark\Extension\HeadingPermalink\HeadingPermalinkExtension;
use League\CommonMark\Extension\TableOfContents\TableOfContentsExtension;
use League\CommonMark\MarkdownConverter;

final class Converter
{
    public const MAX_INPUT = 8192;
    public const MAX_OUTPUT = 131072;
    public const PROFILES = ['core', 'gfm', 'docs', 'comments', 'links', 'notes'];

    public function convert(string $markdown, string $profile = 'core', string $htmlPolicy = 'escape'): array
    {
        if (!in_array($profile, self::PROFILES, true)) { throw new \InvalidArgumentException('Неизвестный профиль.'); }
        if (!in_array($htmlPolicy, ['escape', 'strip'], true)) { throw new \InvalidArgumentException('HTML-политика: escape или strip.'); }
        if (!mb_check_encoding($markdown, 'UTF-8')) { throw new \InvalidArgumentException('Текст должен быть UTF-8.'); }
        if (strlen($markdown) > self::MAX_INPUT) { throw new \LengthException('Лимит Markdown — 8192 байта UTF-8.'); }
        $config = [
            'html_input' => $htmlPolicy, 'allow_unsafe_links' => false,
            'max_nesting_level' => 20, 'max_delimiters_per_line' => 200,
            'heading_permalink' => ['id_prefix' => 'doc', 'fragment_prefix' => 'doc',
                'apply_id_to_heading' => true, 'title' => 'Ссылка на раздел', 'symbol' => '#'],
            'table_of_contents' => ['position' => 'placeholder', 'placeholder' => '[TOC]',
                'min_heading_level' => 2, 'max_heading_level' => 3, 'max_placeholder_entries' => 100],
            'external_link' => ['internal_hosts' => ['ae-nekrasov.ru'], 'open_in_new_window' => true,
                'noopener' => 'external', 'noreferrer' => 'external', 'nofollow' => 'external'],
        ];
        $environment = new Environment($config);
        $environment->addExtension(new CommonMarkCoreExtension());
        if (in_array($profile, ['gfm', 'docs', 'links', 'notes'], true)) {
            $environment->addExtension(new GithubFlavoredMarkdownExtension());
        }
        if ($profile === 'docs') {
            $environment->addExtension(new HeadingPermalinkExtension());
            $environment->addExtension(new TableOfContentsExtension());
        }
        if ($profile === 'links') { $environment->addExtension(new ExternalLinkExtension()); }
        if ($profile === 'notes') { $environment->addExtension(new NoteExtension()); }
        $started = hrtime(true);
        $html = (string) (new MarkdownConverter($environment))->convert($markdown);
        if (strlen($html) > self::MAX_OUTPUT) { throw new \LengthException('HTML превышает лимит 128 КиБ.'); }
        return ['html' => $html, 'inputBytes' => strlen($markdown), 'outputBytes' => strlen($html),
            'elapsedMs' => round((hrtime(true) - $started) / 1e6, 3), 'profile' => $profile,
            'htmlPolicy' => $htmlPolicy, 'engine' => 'league/commonmark 2.10.7'];
    }

    public function request(array $data): array
    {
        foreach (array_keys($data) as $key) {
            if (!in_array($key, ['markdown', 'documents', 'profile', 'htmlPolicy'], true)) {
                throw new \InvalidArgumentException('Неизвестное поле запроса.');
            }
        }
        $profile = array_key_exists('profile', $data) ? $data['profile'] : 'core';
        $policy = array_key_exists('htmlPolicy', $data) ? $data['htmlPolicy'] : 'escape';
        if (!is_string($profile) || !is_string($policy)) { throw new \InvalidArgumentException('Профиль и политика должны быть строками.'); }
        if (array_key_exists('documents', $data)) {
            if (array_key_exists('markdown', $data) || !is_array($data['documents']) || !array_is_list($data['documents']) || count($data['documents']) < 1 || count($data['documents']) > 5) {
                throw new \InvalidArgumentException('Пакет: от 1 до 5 строк Markdown без поля markdown.');
            }
            $bytes = 0;
            foreach ($data['documents'] as $document) {
                if (!is_string($document)) { throw new \InvalidArgumentException('Каждый документ должен быть строкой.'); }
                $bytes += strlen($document);
            }
            if ($bytes > self::MAX_INPUT) { throw new \LengthException('Суммарный лимит пакета — 8192 байта.'); }
            $results = array_map(fn(string $m): array => $this->convert($m, $profile, $policy), $data['documents']);
            if (array_sum(array_column($results, 'outputBytes')) > self::MAX_OUTPUT) { throw new \LengthException('Пакет HTML превышает лимит 128 КиБ.'); }
            return ['ok' => true, 'mode' => 'batch', 'results' => $results];
        }
        if (!isset($data['markdown']) || !is_string($data['markdown'])) { throw new \InvalidArgumentException('Поле markdown должно быть строкой.'); }
        return ['ok' => true, 'mode' => 'single', 'results' => [$this->convert($data['markdown'], $profile, $policy)]];
    }
}
