<?php

namespace Core\System;

/**
 * HtmlSanitizer
 *
 * Centralized HTML sanitization to prevent Stored XSS attacks.
 * Allows safe formatting tags while stripping dangerous elements and attributes.
 *
 * Usage:
 *   $safeHtml = HtmlSanitizer::sanitize($userHtml);
 *   $safeHtml = HtmlSanitizer::sanitizeStrict($userHtml); // strips ALL tags
 */
class HtmlSanitizer
{
    /**
     * Allowed HTML tags for rich product descriptions and landing page content.
     * These tags are safe for public-facing output.
     */
    private const ALLOWED_TAGS = [
        'a', 'abbr', 'acronym', 'address', 'article', 'aside',
        'b', 'bdi', 'bdo', 'blockquote', 'br',
        'caption', 'cite', 'code', 'col', 'colgroup',
        'data', 'dd', 'del', 'details', 'dfn', 'div', 'dl', 'dt',
        'em',
        'figcaption', 'figure', 'footer',
        'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'hr',
        'i', 'img', 'ins',
        'kbd',
        'legend', 'li',
        'main', 'mark',
        'nav',
        'ol',
        'p', 'picture', 'pre',
        'q',
        's', 'samp', 'section', 'small', 'span', 'strong', 'sub', 'summary', 'sup',
        'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'time', 'tr',
        'u', 'ul',
        'var',
        'wbr',
    ];

    /**
     * Allowed attributes per tag.
     * '*' means allowed on all tags.
     */
    private const ALLOWED_ATTRIBUTES = [
        '*'          => ['class', 'id', 'dir', 'lang', 'title', 'aria-label', 'aria-describedby', 'aria-hidden', 'role', 'tabindex', 'style'],
        'a'          => ['href', 'target', 'rel', 'download'],
        'img'        => ['src', 'srcset', 'alt', 'width', 'height', 'loading', 'decoding'],
        'source'     => ['srcset', 'media', 'type'],
        'col'        => ['span', 'width'],
        'colgroup'   => ['span'],
        'td'         => ['colspan', 'rowspan', 'headers'],
        'th'         => ['colspan', 'rowspan', 'headers', 'scope', 'abbr'],
        'time'       => ['datetime'],
        'data'       => ['value'],
        'details'    => ['open'],
        'ol'         => ['reversed', 'start', 'type'],
        'li'         => ['value'],
        'blockquote' => ['cite'],
        'ins'        => ['cite', 'datetime'],
        'del'        => ['cite', 'datetime'],
        'q'          => ['cite'],
    ];

    /**
     * Dangerous URL schemes that must be blocked in href/src attributes.
     */
    private const BLOCKED_SCHEMES = [
        'javascript', 'vbscript', 'data', 'file', 'about', 'blob',
    ];

    /**
     * Sanitize HTML allowing safe formatting tags.
     * Removes scripts, event handlers, and dangerous attributes.
     *
     * @param  string $html  Raw HTML input
     * @return string        Safe HTML safe for public output
     */
    public static function sanitize(string $html): string
    {
        if (trim($html) === '') {
            return '';
        }

        // 1. Remove all <script> tags and their content
        $html = preg_replace('/<script\b[^>]*>.*?<\/script>/is', '', $html);

        // 2. Remove all <style> tags (can be used for CSS-based attacks / layout hijacking)
        // We allow inline style attributes but not embedded <style> blocks
        $html = preg_replace('/<style\b[^>]*>.*?<\/style>/is', '', $html);

        // 3. Remove all <iframe>, <frame>, <frameset>, <object>, <embed>, <applet>, <base>, <form>, <input>, <button>, <select>, <textarea>
        $dangerousTags = ['iframe', 'frame', 'frameset', 'object', 'embed', 'applet', 'base',
                          'form', 'input', 'button', 'select', 'textarea', 'link', 'meta',
                          'noscript', 'template', 'canvas', 'svg', 'math'];
        foreach ($dangerousTags as $tag) {
            $html = preg_replace('/<' . preg_quote($tag, '/') . '\b[^>]*>.*?<\/' . preg_quote($tag, '/') . '>/is', '', $html);
            $html = preg_replace('/<' . preg_quote($tag, '/') . '\b[^>]*\/?>/is', '', $html);
        }

        // 4. Remove all event handler attributes (on*)
        $html = preg_replace('/\s+on\w+\s*=\s*(?:"[^"]*"|\'[^\']*\'|[^\s>]*)/i', '', $html);

        // 5. Remove javascript: and other dangerous schemes in attributes
        $html = preg_replace_callback(
            '/\b(href|src|action|formaction|data)\s*=\s*(["\']?)([^"\'>\s]+)\2/i',
            function (array $m) {
                $attr  = $m[1];
                $quote = $m[2] ?: '"';
                $value = $m[3];

                $scheme = strtolower(preg_replace('/[\x00-\x1f\x7f\s]+/', '', $value));
                $scheme = explode(':', $scheme)[0] ?? '';

                if (in_array($scheme, self::BLOCKED_SCHEMES, true)) {
                    return $attr . '=' . $quote . '#' . $quote;
                }

                return $m[0];
            },
            $html
        );

        // 6. Remove HTML comments (can contain IE conditional code / server-side includes)
        $html = preg_replace('/<!--.*?-->/s', '', $html);

        // 7. Strip disallowed tags while keeping their inner content
        $allowedTagString = '<' . implode('><', self::ALLOWED_TAGS) . '>';
        $html = strip_tags($html, $allowedTagString);

        // 8. Sanitize inline style attributes to prevent CSS-based attacks
        $html = preg_replace_callback(
            '/style\s*=\s*(?:"([^"]*)"|\'([^\']*)\')/i',
            function (array $m) {
                $css = $m[1] ?? $m[2] ?? '';
                // Remove expression(), url() with data/javascript, and other dangerous CSS
                $css = preg_replace('/expression\s*\(/i', '', $css);
                $css = preg_replace('/javascript\s*:/i', '', $css);
                $css = preg_replace('/vbscript\s*:/i', '', $css);
                $css = preg_replace('/url\s*\(\s*["\']?\s*(javascript|data|vbscript):[^)]*\)/i', '', $css);
                $css = preg_replace('/@import/i', '', $css);
                $css = preg_replace('/behavior\s*:/i', '', $css);
                $css = preg_replace('/-moz-binding\s*:/i', '', $css);
                $css = htmlspecialchars($css, ENT_QUOTES, 'UTF-8', false);
                return 'style="' . $css . '"';
            },
            $html
        );

        return $html;
    }

    /**
     * Strict sanitization — strips ALL HTML tags, returns plain text only.
     * Use for single-line outputs, meta tags, titles, etc.
     *
     * @param  string $input
     * @return string
     */
    public static function sanitizeStrict(string $input): string
    {
        return htmlspecialchars(strip_tags($input), ENT_QUOTES, 'UTF-8');
    }

    /**
     * Sanitize for use inside HTML attributes (escapes HTML entities).
     *
     * @param  string $input
     * @return string
     */
    public static function attr(string $input): string
    {
        return htmlspecialchars($input, ENT_QUOTES | ENT_HTML5, 'UTF-8');
    }
}
