<?php

declare(strict_types=1);

/**
 * Deterministic rich-text sanitizer for operator-authored HTML fragments.
 *
 * @param array{singleLine?:bool,tables?:bool} $options
 */
function myronRichtextSanitize(string $html, array $options = []): string
{
    $singleLine = (bool) ($options['singleLine'] ?? false);
    $value = myronRichtextNormalizeUtf8($html);
    if (trim($value) === '') {
        return '';
    }

    $value = preg_replace('~<!--.*?-->~s', '', $value) ?? $value;
    $value = preg_replace('~<!\[CDATA\[.*?\]\]>~s', '', $value) ?? $value;
    $value = preg_replace('~<![^>]*>~s', '', $value) ?? $value;
    $value = preg_replace('~<\s*(script|style|iframe|object|embed|form)\b[^>]*>.*?<\s*/\s*\1\s*>~is', '', $value) ?? $value;

    $allowed = $singleLine
        ? ['strong' => true, 'em' => true, 'b' => true, 'i' => true, 'code' => true, 's' => true, 'del' => true, 'sub' => true, 'sup' => true, 'a' => true]
        : ['strong' => true, 'em' => true, 'b' => true, 'i' => true, 'ul' => true, 'ol' => true, 'li' => true, 'code' => true, 'pre' => true, 's' => true, 'del' => true, 'sub' => true, 'sup' => true, 'p' => true, 'br' => true, 'hr' => true, 'a' => true, 'h2' => true, 'h3' => true, 'h4' => true, 'blockquote' => true, 'figure' => true, 'figcaption' => true, 'img' => true, 'table' => true, 'thead' => true, 'tbody' => true, 'tr' => true, 'th' => true, 'td' => true];
    $void = ['br' => true, 'hr' => true, 'img' => true];
    $open = [];
    $parts = preg_split('~(<[^>]*>)~u', $value, -1, PREG_SPLIT_DELIM_CAPTURE);
    if ($parts === false) {
        $parts = [$value];
    }

    $out = [];
    foreach ($parts as $part) {
        if ($part === '') {
            continue;
        }
        if ($part[0] !== '<') {
            $out[] = myronRichtextEscapeText($part);
            continue;
        }

        if (preg_match('~^<\s*/\s*([a-zA-Z0-9]+)\s*>$~', $part, $closing) === 1) {
            $tag = strtolower($closing[1]);
            if (!isset($allowed[$tag]) || isset($void[$tag])) {
                continue;
            }
            $matchIndex = array_search($tag, array_reverse($open, true), true);
            if ($matchIndex === false) {
                continue;
            }
            while ($open !== []) {
                $popped = array_pop($open);
                $out[] = '</' . $popped . '>';
                if ($popped === $tag) {
                    break;
                }
            }
            continue;
        }

        if (preg_match('~^<\s*([a-zA-Z0-9]+)\b([^>]*)>$~', $part, $opening) !== 1) {
            $out[] = myronRichtextEscapeText($part);
            continue;
        }

        $tag = strtolower($opening[1]);
        if (!isset($allowed[$tag])) {
            if (str_contains((string) $opening[2], '<')) {
                $out[] = myronRichtextEscapeText($part);
            }
            continue;
        }
        if (isset($void[$tag])) {
            if ($tag === 'img') {
                $src = myronRichtextAllowedSrcAttribute((string) $opening[2]);
                if ($src === '') {
                    continue;
                }
                $alt = myronRichtextAllowedAltAttribute((string) $opening[2]);
                $out[] = '<img src="' . htmlspecialchars($src, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') . '" alt="' . htmlspecialchars($alt, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') . '">';
                continue;
            }
            $out[] = '<' . $tag . '>';
            continue;
        }

        $attribute = '';
        if ($tag === 'p') {
            $class = myronRichtextAllowedAlignmentClass((string) $opening[2]);
            if ($class !== '') {
                $attribute = ' class="' . $class . '"';
            }
        } elseif ($tag === 'a') {
            $href = myronRichtextAllowedHrefAttribute((string) $opening[2]);
            if ($href === '') {
                continue;
            }
            $attribute = ' href="' . htmlspecialchars($href, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') . '" rel="noopener"';
        }
        $out[] = '<' . $tag . $attribute . '>';
        $open[] = $tag;
    }

    while ($open !== []) {
        $out[] = '</' . array_pop($open) . '>';
    }

    return trim(implode('', $out));
}

function myronRichtextAllowedSrcAttribute(string $attributes): string
{
    if (preg_match('~\bsrc\s*=\s*(["\'])(.*?)\1~i', $attributes, $quoted) === 1) {
        $raw = html_entity_decode((string) $quoted[2], ENT_QUOTES | ENT_HTML5, 'UTF-8');
    } elseif (preg_match('~\bsrc\s*=\s*([^\s>]+)~i', $attributes, $bare) === 1) {
        $raw = html_entity_decode((string) $bare[1], ENT_QUOTES | ENT_HTML5, 'UTF-8');
    } else {
        return '';
    }

    $src = trim($raw);
    if ($src === '' || str_starts_with($src, '//') || preg_match('/[\x00-\x1F\x7F]/', $src) === 1) {
        return '';
    }
    if (preg_match('~^(https?://|/)~i', $src) === 1) {
        return $src;
    }
    if (preg_match('~^[a-z][a-z0-9+.-]*:~i', $src) === 1) {
        return '';
    }

    return $src;
}

function myronRichtextAllowedAltAttribute(string $attributes): string
{
    if (preg_match('~\balt\s*=\s*(["\'])(.*?)\1~i', $attributes, $quoted) === 1) {
        return trim(html_entity_decode((string) $quoted[2], ENT_QUOTES | ENT_HTML5, 'UTF-8'));
    }
    if (preg_match('~\balt\s*=\s*([^\s>]+)~i', $attributes, $bare) === 1) {
        return trim(html_entity_decode((string) $bare[1], ENT_QUOTES | ENT_HTML5, 'UTF-8'));
    }

    return '';
}

function myronRichtextNormalizeUtf8(string $value): string
{
    if (preg_match('//u', $value) === 1) {
        return $value;
    }
    if (function_exists('iconv')) {
        $clean = @iconv('UTF-8', 'UTF-8//IGNORE', $value);
        if (is_string($clean)) {
            return $clean;
        }
    }

    return preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F]/', '', $value) ?? '';
}

function myronRichtextEscapeText(string $value): string
{
    $decoded = html_entity_decode($value, ENT_QUOTES | ENT_HTML5, 'UTF-8');
    return htmlspecialchars($decoded, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}

function myronRichtextAllowedAlignmentClass(string $attributes): string
{
    if (preg_match('~\bclass\s*=\s*(["\'])(.*?)\1~i', $attributes, $quoted) === 1) {
        $raw = (string) $quoted[2];
    } elseif (preg_match('~\bclass\s*=\s*([^\s>]+)~i', $attributes, $bare) === 1) {
        $raw = (string) $bare[1];
    } else {
        return '';
    }

    foreach (preg_split('/\s+/', trim($raw)) ?: [] as $class) {
        if (in_array($class, ['myron-align--start', 'myron-align--center', 'myron-align--end'], true)) {
            return $class;
        }
    }

    return '';
}

function myronRichtextAllowedHrefAttribute(string $attributes): string
{
    if (preg_match('~\bhref\s*=\s*(["\'])(.*?)\1~i', $attributes, $quoted) === 1) {
        $raw = html_entity_decode((string) $quoted[2], ENT_QUOTES | ENT_HTML5, 'UTF-8');
    } elseif (preg_match('~\bhref\s*=\s*([^\s>]+)~i', $attributes, $bare) === 1) {
        $raw = html_entity_decode((string) $bare[1], ENT_QUOTES | ENT_HTML5, 'UTF-8');
    } else {
        return '';
    }

    $href = trim($raw);
    if ($href === '' || str_starts_with($href, '//') || preg_match('/[\x00-\x1F\x7F]/', $href) === 1) {
        return '';
    }
    if (preg_match('~^(https?://|mailto:|tel:|/|#)~i', $href) === 1) {
        return $href;
    }
    if (preg_match('~^[a-z][a-z0-9+.-]*:~i', $href) === 1) {
        return '';
    }

    return $href;
}
