<?php

namespace LoftyIDX\includes\common;

defined('ABSPATH') || exit;

/**
 * parse raw custom css/js/html and extract valid content
 */
class LoftyIDXCustomizationHelper
{
    const CUSTOM_CSS_KEY = 'lofty_idx_custom_css';
    const CUSTOM_JS_KEY = 'lofty_idx_custom_js';
    const CUSTOM_HTML_KEY = 'lofty_idx_custom_html';

    const allowedJsAttrs = [
        'type' => true,
        'defer' => true,
        'async' => true,
        'id' => true,
        'class' => true
    ];

    const allowedHeadTags = [
        'meta'=> ['name'=> [], 'content'=> [], 'itemprop'=>[], 'property'=>[]],
        'title' => [],
        'link'=> ['rel'=>[], 'type'=>[], 'href'=>[]],
        'script'=>['type'=>[], 'defer'=>[], 'async'=>[], 'id'=>[], 'class'=>[], 'src'=>[]]
    ];

    protected $custom_css = '';
    protected $custom_js = '';
    protected $custom_html = '';

    // flags for custom data injection
    private $injected = [
        'css' => false,
        'js' => false,
        'head' => false,
        'html' => [
            'css' => false,
            'js' => false,
            'html' => false
        ]
    ];

    public function __construct()
    {
    }

    public static function getCustomData()
    {
        return [
            'custom_css' => get_option(self::CUSTOM_CSS_KEY),
            'custom_js' => get_option(self::CUSTOM_JS_KEY),
            'custom_html' => get_option(self::CUSTOM_HTML_KEY)
        ];
    }

    public static function setCustomData($config = ['css' => '', 'js' => '', 'html' => ''])
    {
        foreach ($config as $key => $val) {
            $val = trim($val);
            if ($key === 'css') {
                // sanitize and strip style tag
                $val = wp_strip_all_tags($val);
                $val = wp_kses($val, []);
                update_option(self::CUSTOM_CSS_KEY, $val);
            }
            if ($key === 'js') {
                // disallow script tag with src attr
                $val = wp_kses($val, ['script' => self::allowedJsAttrs]);
                update_option(self::CUSTOM_JS_KEY, $val);
            }
            if ($key === 'html') {
                $clean_html = wp_kses($val, self::allowedHeadTags);
                update_option(self::CUSTOM_HTML_KEY, $clean_html);
            }
        }
    }

    /**
     * inject custom css/js from global customization
     */
    public function injectCustomCss($data = [])
    {
        if (!$this->injected['css']) {
            $raw_style = $data['custom_css'];
            $clean_style = sprintf("<style>\n%s\n</style>", wp_strip_all_tags(trim($raw_style)));
            echo wp_kses($clean_style, ['style' => []]);
            $this->injected['css'] = true;
        }
    }

    public function injectCustomJs($data = [])
    {
        if (!$this->injected['js']) {
            // always defer script
            $clean_scripts = $this->extractScripts($data['custom_js']);
            echo wp_kses($clean_scripts, ['script' => self::allowedJsAttrs]);
            $this->injected['js'] = true;
        }
    }

    public function injectCustomHead($data = [])
    {
        if (!$this->injected['head']) {
            $head_content = $data['custom_html'] ?? '';
            echo wp_kses($head_content, self::allowedHeadTags);
            $this->injected['head'] = true;
        }
    }

    /**
     * inject custom html from global customization
     */
    public function injectCustomHtml($data = [])
    {
        $html_content = $data['custom_html'] ?? '';

        // Create a DOMDocument instance
        $dom = new \DOMDocument();

        // Preserve spaces and format
        $dom->preserveWhiteSpace = true;
        $dom->formatOutput = true;

        // Load HTML content with UTF-8 encoding
        $dom->loadHTML('<?xml encoding="UTF-8">' . $html_content, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);

        // Initialize arrays to store different types of content
        $styles = [];
        $scripts = [];
        $body_content = [];

        // Get all elements from the document
        $xpath = new \DOMXPath($dom);
        $nodes = $xpath->query('/*');

        foreach ($nodes as $node) {
            if ($node->nodeName === 'style') {
                // Store style tags
                $styles[] = $dom->saveHTML($node);
            } else if ($node->nodeName === 'script') {
                // Add defer attribute to script tag if it doesn't exist
                if (!$node->hasAttribute('defer')) {
                    $node->setAttribute('defer', '');
                }
                $scripts[] = $dom->saveHTML($node);
            } else {
                // Store other elements for body
                $body_content[] = $dom->saveHTML($node);
            }
        }

        // Add styles as late as possible using wp_footer with high priority
        add_action('wp_head', function () use ($styles) {
            if (!empty($styles) && !$this->injected['html']['css']) {
                echo "\n<!-- Begin Custom Styles -->\n";
                echo wp_kses(implode("\n", $styles), ['style' => []]);
                echo "\n<!-- End Custom Styles -->\n";

                // prevent duplicate injection
                $this->injected['html']['css'] = true;
            }
        }, 999); // High priority number ensures it runs late

        // Add scripts to head with defer attribute
        add_action('wp_head', function () use ($scripts) {
            if (!empty($scripts) && !$this->injected['html']['js']) {
                echo "\n<!-- Begin Custom Scripts -->\n";
                echo wp_kses(implode("\n", $scripts), ['script' => self::allowedJsAttrs]);
                echo "\n<!-- End Custom Scripts -->\n";

                $this->injected['html']['js'] = true;
            }
        }, 999);

        add_action('lofty_idx_inject_custom_html_body', function () use ($body_content) {
            if (!empty($body_content) && !$this->injected['html']['html']) {
                $this->injected['html']['html'] = true;

                $custom_content = "\n<!-- Begin Custom Body Content -->\n";
                $custom_content .= implode("\n", $body_content);
                $custom_content .= "\n<!-- End Custom Body Content -->\n";

                // direct echo result, used when display template
                echo wp_kses_post($custom_content);
                // when apply filter, get final result, used when return template string
                return wp_kses_post($custom_content);
            }
        });
    }

    /**
     * Extract and clean only script tags from raw HTML/JS
     *
     * @param string $raw_content Raw content that might contain script tags and other HTML
     * @return string Cleaned script tags only
     */
    public function extractScripts($raw_content)
    {
        $raw_content = trim($raw_content);

        // Initialize empty array for clean scripts
        $clean_scripts = [];

        // Pattern to match script tags and their content
        $pattern = '/<script[^>]*>(.*?)<\/script>/is';

        // Find all script tags
        if (preg_match_all($pattern, $raw_content, $matches)) {
            foreach ($matches[0] as $index => $full_script) {
                // Create new script element
                $script = $this->createCleanScript($full_script, $matches[1][$index]);

                if ($script) {
                    $clean_scripts[] = $script;
                }
            }
        }

        return implode("\n", $clean_scripts);
    }

    /**
     * Create a clean script tag from dirty input
     *
     * @param string $full_script Complete script tag including content
     * @param string $content Script content only
     * @return string|null Clean script tag or null if invalid
     */
    private function createCleanScript($full_script, $content)
    {
        // Extract attributes from original script tag
        $attributes = [];
        if (preg_match('/<script([^>]*)>/i', $full_script, $attr_matches)) {
            $attr_string = $attr_matches[1];

            // Extract valid attributes
            preg_match_all('/(\w+)\s*=\s*["\']([^"\']*)["\']/', $attr_string, $attr_matches);
            for ($i = 0; $i < count($attr_matches[1]); $i++) {
                $attr_name = strtolower($attr_matches[1][$i]);
                $attr_value = $attr_matches[2][$i];

                // Whitelist of allowed attributes
                if (in_array($attr_name, ['type', 'id', 'class', 'defer', 'async'])) {
                    $attributes[$attr_name] = $attr_value;
                }
            }
        }

        // Clean the content
        $clean_content = $this->sanitizeJavaScript($content);

        // If no content or has src attribute, skip this script
        if (empty($clean_content) || !empty($attributes['src'])) {
            return null;
        }

        // Build attribute string
        $attr_str = '';
        foreach ($attributes as $name => $value) {
            $attr_str .= " $name=\"" . htmlspecialchars($value, ENT_QUOTES) . "\"";
        }

        // Add defer attribute if not present
        if (!isset($attributes['defer'])) {
            $attr_str .= " defer";
        }

        // Construct final script tag
        return "<script$attr_str>" . ("\n$clean_content\n") . "</script>";
    }

    /**
     * Sanitize JavaScript content
     *
     * @param string $js JavaScript content
     * @return string Sanitized JavaScript
     */
    private function sanitizeJavaScript($js)
    {
        // Remove HTML comments
        $js = preg_replace('/<!--.*?-->/s', '', $js);

        // Remove any script tags in content
        $js = preg_replace('/<\/?script[^>]*>/i', '', $js);

        // Basic XSS prevention
        $js = str_replace(['</script>', '<!--', '-->'], '', $js);

        return trim($js);
    }
}
