<?php

declare(strict_types=1);

namespace PrettyLinks\Pro\Replacements;

/**
 * Emits the admin-supplied CSS blocks that style replaced links
 * site-wide — both keyword replacements (anchors with class
 * `prli-keyword`) and URL replacements (`prli-url`). The stored CSS is
 * admin-authored but not trusted: each emit passes through `sanitize()`
 * to strip known injection vectors before it's wrapped in a `<style>`
 * block on `wp_head`.
 */
class ReplacementStyles
{
    /**
     * Fetch the admin CSS for `$baseKey` / `$hoverKey` from
     * `prlipro_options`, sanitize, and echo a single `<style>` block
     * scoped to `$selector`. Nothing is emitted if both values are
     * empty post-sanitization.
     *
     * @param string $elementId The id attribute for the emitted style block.
     * @param string $selector  The CSS selector the rules are scoped to.
     * @param string $baseKey   The option key holding the base-state CSS.
     * @param string $hoverKey  The option key holding the hover-state CSS.
     *
     * @return void
     */
    public static function emit(string $elementId, string $selector, string $baseKey, string $hoverKey): void
    {
        $opts  = (array) (get_option('prlipro_options', []) ?: []);
        $base  = self::sanitize((string) ($opts[$baseKey] ?? ''));
        $hover = self::sanitize((string) ($opts[$hoverKey] ?? ''));
        if ($base === '' && $hover === '') {
            return;
        }
        echo "\n<style id=\"" . esc_attr($elementId) . "\">\n";
        if ($base !== '') {
            // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- $selector is internal; $base is passed through self::sanitize() which strips CSS-injection vectors.
            echo $selector . '{' . $base . "}\n";
        }
        if ($hover !== '') {
            // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- $selector is internal; $hover is passed through self::sanitize() which strips CSS-injection vectors.
            echo $selector . ':hover{' . $hover . "}\n";
        }
        echo "</style>\n";
    }

    /**
     * Strip the common CSS-injection vectors: `expression()`, script
     * protocols, CSS behaviors, `-moz-binding`, `@import`, and `data:`
     * URLs inside `url(...)`. The returned string is trimmed — if the
     * admin wrote whitespace only, the caller gets `''`.
     *
     * @param string $css The admin-authored CSS to sanitize.
     *
     * @return string
     */
    private static function sanitize(string $css): string
    {
        $css = trim($css);
        if ($css === '') {
            return '';
        }
        $clean = (string) preg_replace(
            [
                '/expression\s*\(/i',
                '/javascript\s*:/i',
                '/vbscript\s*:/i',
                '/behavior\s*:/i',
                '/-moz-binding\s*:/i',
                '/@import/i',
                '/url\s*\(\s*["\']?\s*data:/i',
            ],
            '',
            $css
        );
        return trim($clean);
    }
}
