<?php

if (! defined('ABSPATH')) {
    exit;
} class PufferGo_Alpine_SSR
{
    public const CONTENT_BIND_ATTRS = ['src', 'alt', 'href', 'title', 'loading', 'poster', 'fetchpriority'];

    public const UNRESOLVABLE = "\0__pgo_unresolvable__\0";

    public const BLOCK_STYLE_SPACING_KEYS = ['p', 'px', 'py', 'pt', 'pr', 'pb', 'pl', 'm', 'mx', 'my', 'mt', 'mr', 'mb', 'ml'];

    public const BLOCK_STYLE_PREFIXES = ['md:', 'lg:'];

    public const BLOCK_STYLE_CONTAINER_MAX_DEPTH = 5;

    public const BLOCK_STYLE_SURFACE_SEMANTIC = ['surface-base', 'surface-raised', 'surface-dark', 'surface-brand'];

    public const BLOCK_STYLE_BG_NON_COLOR = ['cover', 'contain', 'auto', 'center', 'top', 'bottom', 'left', 'right', 'repeat', 'no-repeat', 'repeat-x', 'repeat-y', 'fixed', 'local', 'scroll', 'clip', 'origin', 'blend', 'gradient', 'none', ];

    public const BLOCK_STYLE_FONT_SIZES = ['xs', 'sm', 'base', 'lg', 'xl', '2xl', '3xl'];

    public const BLOCK_STYLE_TEXT_ALIGNS = ['left', 'center', 'right', 'justify'];

    public const BLOCK_STYLE_TEXT_NON_COLOR = ['wrap', 'nowrap', 'balance', 'pretty', 'ellipsis', 'clip', 'start', 'end', ];

    public static function render(string $template_html, array $config_data, array $block_style = array()): string
    {
        if (empty($template_html) || empty($config_data) || ! PUFFERGO_HAS_DOM) {
            return $template_html;
        } $html = self::normalize_shorthands($template_html);
        $doc = new DOMDocument('1.0', 'UTF-8');
        $prev = libxml_use_internal_errors(true);
        $doc->loadHTML('<?xml encoding="UTF-8"><html><body><div id="pgo-ssr-root">' . $html . '</div></body></html>', LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
        libxml_clear_errors();
        libxml_use_internal_errors($prev);
        $root = $doc->getElementById('pgo-ssr-root');

        if (! $root) {
            return $template_html;
        } $seed_data = $config_data;
        self::pre_compute_getters($html, $config_data);
        self::process_x_if($root, $config_data, $doc);
        self::process_x_for($root, $config_data, $doc);
        self::process_x_text($root, $config_data, $doc);
        self::process_x_show($root, $config_data, $doc);
        self::process_x_bind($root, $config_data);
        self::seed_x_data($root, $doc, $seed_data);
        self::apply_block_style($root, $block_style);
        $result = '';

        foreach ($root->childNodes as $child) {
            $result .= $doc->saveHTML($child);
        }

return self::encode_gt_lt_in_attrs($result);
    }

    public static function inject_config_data(string $template_html, array $config_data, array $block_style = array()): string
    {
        if ('' === trim($template_html) || empty($config_data) || ! PUFFERGO_HAS_DOM) {
            return $template_html;
        } $json = wp_json_encode($config_data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);

        if (false === $json) {
            return $template_html;
        } $html = self::normalize_shorthands($template_html);
        $doc = new DOMDocument('1.0', 'UTF-8');
        $prev = libxml_use_internal_errors(true);
        $doc->loadHTML('<?xml encoding="UTF-8"><html><body><div id="pgo-inject-root">' . $html . '</div></body></html>', LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
        libxml_clear_errors();
        libxml_use_internal_errors($prev);
        $root = $doc->getElementById('pgo-inject-root');

        if (! $root) {
            return $template_html;
        } self::seed_x_data($root, $doc, $config_data);
        self::apply_block_style($root, $block_style);
        $result = '';

        foreach ($root->childNodes as $child) {
            $result .= $doc->saveHTML($child);
        }

return $result;
    }

    private static function class_family_key(string $cls): string
    {
        $prefix = '';
        $bare = $cls;

        foreach (self::BLOCK_STYLE_PREFIXES as $p) {
            if (0 === strpos($cls, $p)) {
                $prefix = $p;
                $bare = substr($cls, strlen($p));
                break;
            }
        }

if (false !== strpos($bare, '[')) {
            return '';
        }

if (preg_match('/^([a-z]{1,2})-(\d+)$/', $bare, $m) && in_array($m[1], self::BLOCK_STYLE_SPACING_KEYS, true)) {
            return $prefix . $m[1];
        }

if (0 === strpos($bare, 'max-w-')) {
            return $prefix . 'max-w';
        }

if (in_array($bare, self::BLOCK_STYLE_SURFACE_SEMANTIC, true)) {
            return $prefix . 'bg';
        }

if (0 === strpos($bare, 'bg-')) {
            $suffix = substr($bare, 3);
            $head = explode('-', $suffix)[0];

            if (in_array($suffix, self::BLOCK_STYLE_BG_NON_COLOR, true) || in_array($head, self::BLOCK_STYLE_BG_NON_COLOR, true)) {
                return '';
            }

return $prefix . 'bg';
        }

if (0 === strpos($bare, 'text-')) {
            $suffix = substr($bare, 5);

            if (in_array($suffix, self::BLOCK_STYLE_FONT_SIZES, true)) {
                return $prefix . 'text-size';
            }

if (in_array($suffix, self::BLOCK_STYLE_TEXT_ALIGNS, true)) {
                return $prefix . 'text-align';
            }

if (in_array($suffix, self::BLOCK_STYLE_TEXT_NON_COLOR, true)) {
                return '';
            }

return $prefix . 'text-color';
        }

return '';
    }

    private static function merge_block_style_classes(string $base_class, array $overrides): string
    {
        $list = array();

        foreach ($overrides as $cls) {
            if (is_string($cls) && '' !== $cls) {
                $list[] = $cls;
            }
        }

if (empty($list)) {
            return $base_class;
        } $families = array();

        foreach ($list as $cls) {
            $key = self::class_family_key($cls);

            if ('' !== $key) {
                $families[ $key ] = true;
            }
        } $kept = array();

        foreach (preg_split('/\s+/', trim($base_class), -1, PREG_SPLIT_NO_EMPTY) ?: array() as $cls) {
            $key = self::class_family_key($cls);

            if ('' === $key || ! isset($families[ $key ])) {
                $kept[] = $cls;
            }
        }

return implode(' ', array_unique(array_merge($kept, $list)));
    }

    private static function normalize_block_style(array $block_style): array
    {
        $out = array('root' => array('classes' => array(), 'bgImage' => ''), 'container' => array('classes' => array()), );

        if (isset($block_style['classes']) || isset($block_style['bgImage'])) {
            $out['root']['classes'] = is_array($block_style['classes'] ?? null) ? $block_style['classes'] : array();
            $out['root']['bgImage'] = is_string($block_style['bgImage'] ?? null) ? $block_style['bgImage'] : '';

            return $out;
        }

if (is_array($block_style['root'] ?? null)) {
            $out['root']['classes'] = is_array($block_style['root']['classes'] ?? null) ? $block_style['root']['classes'] : array();
            $out['root']['bgImage'] = is_string($block_style['root']['bgImage'] ?? null) ? $block_style['root']['bgImage'] : '';
        }

if (is_array($block_style['container'] ?? null) && is_array($block_style['container']['classes'] ?? null)) {
            $out['container']['classes'] = $block_style['container']['classes'];
        }

return $out;
    }

    private static function find_container_element(DOMElement $root): ?DOMElement
    {
        if (self::has_container_class($root)) {
            return $root;
        } $level = self::child_elements($root);
        for ($depth = 0; $depth < self::BLOCK_STYLE_CONTAINER_MAX_DEPTH && ! empty($level); $depth++) {
            foreach ($level as $el) {
                if (self::has_container_class($el)) {
                    return $el;
                }
            } $next = array();

            foreach ($level as $el) {
                $next = array_merge($next, self::child_elements($el));
            } $level = $next;
        }

return null;
    }

    private static function child_elements(DOMElement $el): array
    {
        $out = array();

        foreach ($el->childNodes as $child) {
            if ($child instanceof DOMElement) {
                $out[] = $child;
            }
        }

return $out;
    }

    private static function has_container_class(DOMElement $el): bool
    {
        foreach (preg_split('/\s+/', trim($el->getAttribute('class')), -1, PREG_SPLIT_NO_EMPTY) ?: array() as $cls) {
            if ('mx-auto' === $cls || preg_match('/^(?:sm:|md:|lg:)?max-w-/', $cls)) {
                return true;
            }
        }

return false;
    }

    private static function apply_block_style(DOMElement $root, array $block_style): void
    {
        if (empty($block_style)) {
            return;
        } $el = null;

        foreach ($root->childNodes as $child) {
            if ($child instanceof DOMElement) {
                $el = $child;
                break;
            }
        }

if (null === $el) {
            return;
        } $normalized = self::normalize_block_style($block_style);

        if (! empty($normalized['root']['classes'])) {
            $merged = self::merge_block_style_classes($el->getAttribute('class'), $normalized['root']['classes']);

            if ('' !== $merged) {
                $el->setAttribute('class', $merged);
            }
        }

if (! empty($normalized['container']['classes'])) {
            $container = self::find_container_element($el);

            if (null !== $container) {
                $merged = self::merge_block_style_classes($container->getAttribute('class'), $normalized['container']['classes']);

                if ('' !== $merged) {
                    $container->setAttribute('class', $merged);
                }
            }
        }

if ('' !== $normalized['root']['bgImage']) {
            $url = esc_url_raw($normalized['root']['bgImage']);
            $style = trim($el->getAttribute('style'));
            $style = preg_replace('/background-(image|size|position)\s*:[^;]*;?/i', '', $style);
            $style = trim((string) $style);

            if ('' !== $style && ! str_ends_with($style, ';')) {
                $style .= ';';
            } $el->setAttribute('style', $style . "background-image:url('" . $url . "');background-size:cover;background-position:center;");
        }
    }

    private static function seed_x_data(DOMElement $root, DOMDocument $doc, array $config_data): void
    {
        if (empty($config_data)) {
            return;
        } $json = wp_json_encode($config_data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);

        if (false === $json) {
            return;
        } $merge = 'Object.assign($data, ' . $json . ')';
        $xpath = new DOMXPath($doc);

        foreach (iterator_to_array($xpath->query('.//*[@x-data]', $root)) as $el) {
            if (! $el instanceof DOMElement) {
                continue;
            } $prev_init = $el->getAttribute('x-init');
            $el->setAttribute('x-init', '' !== $prev_init ? $merge . '; ' . $prev_init : $merge);
        }
    }

    private static function encode_gt_lt_in_attrs(string $html): string
    {
        return preg_replace_callback('/="([^"]*)"/', static function ($m) { return '="' . str_replace(['>', '<'], ['&gt;', '&lt;'], $m[1]) . '"'; }, $html);
    }

    private static function process_x_for(DOMElement $root, array $data, DOMDocument $doc): void
    {
        $safety = 0;

        while ($safety++ < 30) {
            $xpath = new DOMXPath($doc);
            $templates = $xpath->query('.//template[@x-for][not(@data-pg-ssr-done)][not(ancestor::template)]', $root);

            if ($templates->length === 0) {
                break;
            } $el = $templates->item(0);
            $directive = $el->getAttribute('x-for');

            if (preg_match('/^\s*\((\w+)(?:\s*,\s*(\w+))?\)\s+in\s+(.+?)\s*$/', $directive, $m)) {
                $item_name = $m[1];
                $index_name = $m[2] ?: null;
                $path_expr = trim($m[3]);
            } elseif (preg_match('/^\s*(\w+)\s+in\s+(.+?)\s*$/', $directive, $m)) {
                $item_name = $m[1];
                $index_name = null;
                $path_expr = trim($m[2]);
            } else {
                $el->setAttribute('data-pg-ssr-done', '1');
                continue;
            } $el->setAttribute('data-pg-ssr-done', '1');
            $items = self::resolve_for_items($path_expr, $data);

            if (! is_array($items)) {
                continue;
            } $template_inner = '';

            foreach ($el->childNodes as $child) {
                $template_inner .= $doc->saveHTML($child);
            } $parent = $el->parentNode;
            $fragment = $doc->createDocumentFragment();

            foreach ($items as $index => $item) {
                $scope = $data;
                $scope[ $item_name ] = $item;

                if ($index_name) {
                    $scope[ $index_name ] = $index;
                } $item_html = self::render_item($template_inner, $scope);
                $tmp = new DOMDocument('1.0', 'UTF-8');
                $prev = libxml_use_internal_errors(true);
                $tmp->loadHTML('<?xml encoding="UTF-8"><html><body>' . $item_html . '</body></html>', LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
                libxml_clear_errors();
                libxml_use_internal_errors($prev);
                $body = $tmp->getElementsByTagName('body')->item(0);

                if ($body) {
                    foreach ($body->childNodes as $child) {
                        $imported = $doc->importNode($child, true);

                        if ($imported instanceof DOMElement) {
                            $imported->setAttribute('data-pg-ssr', '1');
                        } $fragment->appendChild($imported);
                    }
                }
            }

if ($el->nextSibling) {
                $parent->insertBefore($fragment, $el->nextSibling);
            } else {
                $parent->appendChild($fragment);
            }
        } $xpath = new DOMXPath($doc);

        foreach (iterator_to_array($xpath->query('.//template[@data-pg-ssr-done]', $root)) as $tpl) {
            $tpl->removeAttribute('data-pg-ssr-done');
        }
    }

    private static function render_item(string $html, array $scope): string
    {
        $doc = new DOMDocument('1.0', 'UTF-8');
        $prev = libxml_use_internal_errors(true);
        $doc->loadHTML('<?xml encoding="UTF-8"><html><body><div id="pgo-item-root">' . $html . '</div></body></html>', LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
        libxml_clear_errors();
        libxml_use_internal_errors($prev);
        $root = $doc->getElementById('pgo-item-root');

        if (! $root) {
            return $html;
        } self::process_x_if($root, $scope, $doc);
        self::process_x_for($root, $scope, $doc);
        self::process_x_text($root, $scope, $doc);
        self::process_x_show($root, $scope, $doc);
        self::process_x_bind($root, $scope);
        $result = '';

        foreach ($root->childNodes as $child) {
            $result .= $doc->saveHTML($child);
        }

return $result;
    }

    private static function process_x_text(DOMElement $root, array $data, DOMDocument $doc): void
    {
        $xpath = new DOMXPath($doc);
        $nodes = iterator_to_array($xpath->query('.//*[@x-text][not(ancestor::template)]', $root));

        foreach ($nodes as $el) {
            $expr = $el->getAttribute('x-text');
            $value = self::evaluate($expr, $data);

            if ($value === self::UNRESOLVABLE) {
                continue;
            }

if ($value === null || $value === false || $value === '') {
                continue;
            }

while ($el->firstChild) {
                $el->removeChild($el->firstChild);
            } $el->appendChild($doc->createTextNode((string) $value));
            $el->removeAttribute('x-text');
        }
    }

    private static function process_x_show(DOMElement $root, array $data, DOMDocument $doc): void
    {
        $xpath = new DOMXPath($doc);
        $nodes = iterator_to_array($xpath->query('.//*[@x-show][not(ancestor::template)]', $root));

        foreach ($nodes as $el) {
            $expr = $el->getAttribute('x-show');
            $result = self::evaluate($expr, $data);

            if ($result === self::UNRESOLVABLE) {
                continue;
            }

if (! $result) {
                $style = $el->getAttribute('style');
                $el->setAttribute('style', trim(rtrim($style, '; ') . '; display:none', '; '));
            }
        }
    }

    private static function process_x_bind(DOMElement $root, array $data): void
    {
        $xpath = new DOMXPath($root->ownerDocument);
        $all = iterator_to_array($xpath->query('.//*[not(ancestor::template)]', $root));

        foreach ($all as $el) {
            if (! ($el instanceof DOMElement)) {
                continue;
            } $to_remove = [];
            $to_set = [];
            $attrs = [];

            foreach ($el->attributes as $attr) {
                $attrs[] = ['name' => $attr->name, 'value' => $attr->value];
            }

foreach ($attrs as $a) {
                if (strpos($a['name'], 'x-bind:') !== 0) {
                    continue;
                } $attr_name = substr($a['name'], 7);

                if (! in_array($attr_name, self::CONTENT_BIND_ATTRS, true)) {
                    continue;
                } $value = self::evaluate($a['value'], $data);

                if ($value === self::UNRESOLVABLE) {
                    continue;
                }

if ($value === null || $value === false || $value === '') {
                    continue;
                } $to_set[ $attr_name ] = (string) $value;
                $to_remove[] = $a['name'];
            }

foreach ($to_remove as $r) {
                $node = $el->getAttributeNode($r);

                if ($node) {
                    $el->removeAttributeNode($node);
                }
            }

foreach ($to_set as $k => $v) {
                $el->setAttribute($k, $v);
            }
        }
    }

    private static function process_x_if(DOMElement $root, array $data, DOMDocument $doc): void
    {
        $xpath = new DOMXPath($doc);
        $nodes = array_reverse(iterator_to_array($xpath->query('.//*[@x-if][not(ancestor::template)]', $root)));

        foreach ($nodes as $el) {
            $expr = $el->getAttribute('x-if');
            $result = self::evaluate($expr, $data);

            if ($result === self::UNRESOLVABLE) {
                continue;
            }

if (! $result) {
                if ($el->parentNode) {
                    $el->parentNode->removeChild($el);
                }
            } else {
                $el->removeAttribute('x-if');
            }
        }
    }

    private static function evaluate(string $expr, array $scope)
    {
        $expr = trim($expr);

        if ($expr === '') {
            return null;
        }

if ($expr === 'null') {
            return null;
        }

if ($expr === 'true') {
            return true;
        }

if ($expr === 'false') {
            return false;
        }

if (is_numeric($expr)) {
            return $expr + 0;
        }

if (preg_match('/^([\'"])(.+)\1$/s', $expr, $m)) {
            return $m[2];
        }

if (isset($expr[0]) && $expr[0] === '!') {
            $operand = self::evaluate(substr($expr, 1), $scope);

            return $operand === self::UNRESOLVABLE ? self::UNRESOLVABLE : ! $operand;
        }

if (preg_match('/^(.+?)\s*\?\s*(.+?)\s*:\s*(.+)$/s', $expr, $m)) {
            $left_raw = trim($m[1]);

            if (substr_count($left_raw, "'") % 2 === 0 && substr_count($left_raw, '"') % 2 === 0) {
                $cond = self::evaluate($left_raw, $scope);

                if ($cond === self::UNRESOLVABLE) {
                    return self::UNRESOLVABLE;
                }

return $cond ? self::evaluate(trim($m[2]), $scope) : self::evaluate(trim($m[3]), $scope);
            }
        }

if (preg_match('/^(.+?)\s*!==\s*(.+)$/s', $expr, $m)) {
            $left = self::evaluate(trim($m[1]), $scope);
            $right = self::evaluate(trim($m[2]), $scope);

            if ($left === self::UNRESOLVABLE || $right === self::UNRESOLVABLE) {
                return self::UNRESOLVABLE;
            }

return $left !== $right;
        }

if (preg_match('/^(.+?)\s*===\s*(.+)$/s', $expr, $m)) {
            $left = self::evaluate(trim($m[1]), $scope);
            $right = self::evaluate(trim($m[2]), $scope);

            if ($left === self::UNRESOLVABLE || $right === self::UNRESOLVABLE) {
                return self::UNRESOLVABLE;
            }

return $left === $right;
        }

if (preg_match('/^(.+?)\s*(>=|<=|>|<)\s*(.+)$/s', $expr, $m)) {
            $left = self::evaluate(trim($m[1]), $scope);
            $right = self::evaluate(trim($m[3]), $scope);

            if ($left === self::UNRESOLVABLE || $right === self::UNRESOLVABLE) {
                return self::UNRESOLVABLE;
            }

switch ($m[2]) {
                case '>': return $left > $right;
                case '<': return $left < $right;
                case '>=': return $left >= $right;
                case '<=': return $left <= $right;
            }
        }

if (preg_match('/^(.+?)\s*&&\s*(.+)$/s', $expr, $m)) {
            $left = self::evaluate(trim($m[1]), $scope);

            if ($left === self::UNRESOLVABLE) {
                return self::UNRESOLVABLE;
            }

return $left ? self::evaluate(trim($m[2]), $scope) : $left;
        }

if (preg_match('/^(.+?)\s*\|\|\s*(.+)$/s', $expr, $m)) {
            $left = self::evaluate(trim($m[1]), $scope);

            if ($left === self::UNRESOLVABLE) {
                return self::UNRESOLVABLE;
            }

return $left ? $left : self::evaluate(trim($m[2]), $scope);
        }

if (strpos($expr, '?.') !== false) {
            $parts = explode('?.', $expr, 2);
            $obj = self::evaluate(trim($parts[0]), $scope);

            if ($obj === self::UNRESOLVABLE) {
                return self::UNRESOLVABLE;
            }

if ($obj === null || ! is_array($obj)) {
                return null;
            }

return self::evaluate(trim($parts[1]), $obj);
        }

if (preg_match('/^[\w$][\w.$\[\]]*$/', $expr)) {
            return self::resolve_path($scope, $expr);
        }

return self::UNRESOLVABLE;
    }

    private static function resolve_path(array $scope, string $path)
    {
        $path = preg_replace('/\[(\d+)\]/', '.$1', $path);
        $keys = array_values(array_filter(explode('.', $path)));
        $value = $scope;

        foreach ($keys as $key) {
            if ($key === 'length' && is_array($value) && ! array_key_exists('length', $value)) {
                $value = count($value);
                continue;
            }

if (! is_array($value)) {
                return null;
            }

if (array_key_exists($key, $value)) {
                $value = $value[ $key ];
            } elseif (is_numeric($key) && array_key_exists((int) $key, $value)) {
                $value = $value[ (int) $key ];
            } else {
                return null;
            }
        }

return $value;
    }

    private static function resolve_for_items(string $expr, array $data): ?array
    {
        $expr = trim($expr);

        if (preg_match('/^\[(\w+)\[\1\.length\s*-\s*1\],\s*\.\.\.\1,\s*\1\[0\]\]$/', $expr, $m)) {
            $arr = self::resolve_path($data, $m[1]);

            if (! is_array($arr) || count($arr) === 0) {
                return [];
            }

return array_merge([end($arr)], $arr, [reset($arr)]);
        } $result = self::resolve_path($data, $expr);

        return is_array($result) ? $result : null;
    }

    private static function pre_compute_getters(string $template_html, array &$data): void
    {
        if (! preg_match('/\bx-data\s*=\s*"((?:[^"\\\\]|\\\\.)*?)"\s*(?:>|[\w@:])/s', $template_html, $m) && ! preg_match("/\\bx-data\\s*=\\s*'((?:[^'\\\\]|\\\\.)*?)'\\s*(?:>|[\\w@:])/s", $template_html, $m)) {
            if (! preg_match('/\bx-data\s*=\s*"(.+)/s', $template_html, $m)) {
                return;
            }
        } $x_data = html_entity_decode($m[1], ENT_QUOTES | ENT_HTML5, 'UTF-8');
        preg_match_all('/\bget\s+(\w+)\s*\(\s*\)\s*\{/s', $x_data, $getter_starts, PREG_OFFSET_CAPTURE);

        if (empty($getter_starts[1])) {
            return;
        }

foreach ($getter_starts[1] as $idx => $name_match) {
            $name = $name_match[0];

            if (array_key_exists($name, $data)) {
                continue;
            } $keyword_pos = (int) $getter_starts[0][ $idx ][1];
            $brace_start = strpos($x_data, '{', $keyword_pos);

            if ($brace_start === false) {
                continue;
            } $depth = 0;
            $brace_end = $brace_start;
            for ($i = $brace_start; $i < strlen($x_data); $i++) {
                if ($x_data[ $i ] === '{') {
                    $depth++;
                } elseif ($x_data[ $i ] === '}') {
                    $depth--;

                    if ($depth === 0) {
                        $brace_end = $i;
                        break;
                    }
                }
            } $body = substr($x_data, $brace_start + 1, $brace_end - $brace_start - 1);
            $derived = self::eval_getter_body($body, $data);

            if ($derived !== null) {
                $data[ $name ] = $derived;
            }
        }
    }

    private static function eval_getter_body(string $body, array $data)
    {
        $body = trim($body);

        if (preg_match("/if\s*\(\s*this\.(\w+)\s*===\s*['\"]all['\"]\s*\)\s*return\s+this\.(\w+)\s*[;,]/", $body, $m)) {
            $filter_field = $m[1];
            $array_field = $m[2];

            if (isset($data[ $filter_field ]) && $data[ $filter_field ] === 'all' && isset($data[ $array_field ]) && is_array($data[ $array_field ])) {
                return $data[ $array_field ];
            }
        }

if (preg_match('/this\.(\w+)\.forEach\b.*?\bpush\s*\(\s*\w+\.(\w+)\s*\)/s', $body, $m)) {
            $array_field = $m[1];
            $prop = $m[2];

            if (isset($data[ $array_field ]) && is_array($data[ $array_field ])) {
                return array_values(array_unique(array_column($data[ $array_field ], $prop)));
            }
        }

return null;
    }

    public static function normalize_shorthands(string $html): string
    {
        $html = preg_replace('/(?<=[\s])@([\w.:]+)=(?=["\'])/u', 'x-on:$1=', $html);
        $html = preg_replace('/(?<=[\s])(:([\w-]+))=(?=["\'])/u', 'x-bind:$2=', $html);

        return $html;
    }
}
