<?php

namespace Ignite\Core\Models;

use HTMLPurifier;
use HTMLPurifier_Config;

/**
 * Provide a flexible HTML purification system that allows rich semantic HTML5 (section, article, details,
 * summary, header, footer, nav, aside, main, button) while maintaining minimal security by blocking scripts,
 * forms, and unsafe URI schemes. Preserve global attributes (class, id, style, data-*, aria-*,
 * role, etc.) across all elements for styling, accessibility, and JavaScript integration.
 *
 * WHY THIS APPROACH:
 * ------------------
 * HTMLPurifier is XHTML 1.0-based and does not support HTML5 natively. This class overcomes that
 * limitation through a dual-pass registration strategy:
 *
 * 1. CONFIG-BASED REGISTRATION (buildAllowedAttributes):
 *    Registers element.attribute pairs in HTMLPurifier config (e.g., "section.class", "button.aria-label").
 *    This is the first pass and handles the vast majority of attributes.
 *
 * 2. PROGRAMMATIC REGISTRATION (applyGlobalAttributes):
 *    Applies the same attributes programmatically via $def->addAttribute() on the HTML definition.
 *    This is the second pass and ensures HTMLPurifier's internal validator recognizes all attributes.
 *
 * Both passes are required: omitting either can cause silent attribute stripping even though the
 * attribute is technically "allowed".
 *
 * @see config('core.html.options') - Central configuration for allowed elements and attributes
 * @see config('core.html.options_custom') - Overrides and custom settings including GlobalAttributes
 */
class Purifier
{
    /**
     * Clean the provided html and return.
     *
     * @param  string $html
     * @return string
     */
    public function clean($html)
    {
        $config = $this->buildConfig();
        $def = $config->getHTMLDefinition(true);

        $this->registerHtml5Elements($def);
        $this->applyGlobalAttributes($def);

        $purifier = new HTMLPurifier($config);

        return $purifier->purify($html);
    }

    /**
     * Build and return HTMLPurifier configuration with global attributes.
     *
     * @return \HTMLPurifier_Config
     */
    protected function buildConfig()
    {
        $options = config('core.html.options', []);
        $options['HTML.AllowedAttributes'] = $this->buildAllowedAttributes();

        $config = HTMLPurifier_Config::createDefault();
        $config->loadArray($options);

        return $config;
    }

    /**
     * Build element.attribute pairs for all global attributes on all allowed elements.
     *
     * @return array
     */
    protected function buildAllowedAttributes()
    {
        $allowedElements = $this->getAllowedElements();
        $globalAttributes = $this->getGlobalAttributes();
        $options = config('core.html.options', []);
        $allowedAttributes = $options['HTML.AllowedAttributes'] ?? [];

        foreach ($allowedElements as $element) {
            foreach ($globalAttributes as $attr) {
                $allowedAttributes[] = "$element.$attr";
            }
        }

        return $allowedAttributes;
    }

    /**
     * Get list of all allowed elements including HTML5 semantic elements.
     *
     * @return array
     */
    protected function getAllowedElements()
    {
        $options = config('core.html.options', []);
        $allowedElements = $options['HTML.AllowedElements'] ?? [];
        $html5Elements = ['section', 'article', 'details', 'summary', 'header', 'footer', 'main', 'aside', 'nav', 'button'];

        return array_unique(array_merge($allowedElements, $html5Elements));
    }

    /**
     * Get list of global attributes from config.
     *
     * @return array
     */
    protected function getGlobalAttributes()
    {
        return config('core.html.options_custom')['HTML.GlobalAttributes'] ?? [];
    }

    /**
     * Register HTML5 semantic elements not supported in XHTML 1.0.
     *
     * @param  \HTMLPurifier_HTMLDefinition $def
     * @return void
     */
    protected function registerHtml5Elements($def)
    {
        $html5Elements = ['section', 'article', 'details', 'summary', 'header', 'footer', 'main', 'aside', 'nav', 'button'];

        foreach ($html5Elements as $element) {
            $def->addElement($element, 'Block', 'Flow', 'Common');
        }
    }

    /**
     * Apply global attributes to all allowed elements programmatically.
     *
     * @param  \HTMLPurifier_HTMLDefinition $def
     * @return void
     */
    protected function applyGlobalAttributes($def)
    {
        $allElements = $this->getAllowedElements();
        $globalAttributes = $this->getGlobalAttributes();

        foreach ($allElements as $element) {
            foreach ($globalAttributes as $attr) {
                $def->addAttribute($element, $attr, 'CDATA');
            }
        }
    }
}

