<?php
namespace DiviBooster\DiviBooster\Core\Fixes\AddNewIconsDivi5;

if (!defined('ABSPATH')) {
    exit();
}

const DIVI5_ICON_MODULE_SLUG = 'divi/icon';
const DIVI4_ICON_MODULE_SLUG = 'et_pb_icon';

// === Divi 5 Builder: add DB014 icons to the icon picker ===
// Ported from the previous implementation in `custom-icons-old/divi5-builder.php`.

if (function_exists('add_action')) {
    // Fires inside the main VB app frame.
    add_action('divi_visual_builder_assets_before_enqueue_scripts', __NAMESPACE__ . '\\enqueue_divi5_vb_icon_picker_script');
    // Divi 5 settings UI can be rendered outside the app iframe (top-level window).
    add_action('wp_enqueue_scripts', __NAMESPACE__ . '\\enqueue_divi5_vb_ui_icon_picker_script');
}

function get_db014_custom_icons_for_divi5_picker()
{
    if (!function_exists('dbdb_option')) {
        return array();
    }

    $urlmax = (int) \dbdb_option('014-add-new-icons', 'urlmax', 0);
    if ($urlmax < 0) {
        return array();
    }

    $icons = array();

    for ($id = 0; $id <= $urlmax; $id++) {
        $url = (string) \dbdb_option('014-add-new-icons', "url$id", '');
        if (empty($url)) {
            continue;
        }

        // Matches DBDBExtendedIcon::unicode_value() (800 + id).
        $code           = 800 + (int) $id;
        $unicode_entity = '&#x' . $code . ';';
        $decoded        = html_entity_decode($unicode_entity, ENT_QUOTES, 'UTF-8');

        $icons[] = array(
            'id'             => (int) $id,
            'searchTerms'    => 'divi-booster custom icon',
            'unicode'        => $unicode_entity,
            'decodedUnicode' => $decoded,
            'name'           => 'wtfdivi014-url' . $id,
            'url'            => $url,
            'styles'         => array('divi', 'solid'),
            'fontWeight'     => 400,
            // Keep legacy keys too (safe for server-side code that expects them)
            'font_weight'    => 400,
            'is_divi_icon'   => true,
        );
    }

    return $icons;
}

function is_divi5_visual_builder_active()
{
    if (!function_exists('et_builder_d5_enabled') || !\et_builder_d5_enabled()) {
        return false;
    }
    if (!function_exists('et_core_is_fb_enabled') || !\et_core_is_fb_enabled()) {
        return false;
    }
    return true;
}

function enqueue_divi5_vb_icon_picker_script()
{
    if (!is_divi5_visual_builder_active()) {
        return;
    }

    $icons = get_db014_custom_icons_for_divi5_picker();
    if (empty($icons)) {
        return;
    }

    $handle = 'divibooster-014-custom-icons-d5';

    $js  = '(function () {';
    $js .= 'var DB014_DEBUG = false;';
    $js .= 'function db014_log(){ if(!DB014_DEBUG||!window.console||!console.log) return; try{ var a=[].slice.call(arguments); a.unshift("[Divi Booster][014]"); console.log.apply(console,a);}catch(e){} }';
    $js .= 'if (window.__db014_divi5_icons_installed) { db014_log("Already installed in this window; skipping."); return; }';
    $js .= 'window.__db014_divi5_icons_installed = true;';
    $js .= 'var db014_custom_icons = ' . wp_json_encode($icons) . ';';
    $js .= get_divi5_vb_inline_js_body();
    $js .= '})();';

    \dbdb_enqueue_inline_script_if_dependencies_available($handle, $js, array(array('divi-vendor-wp-hooks', 'wp-hooks'), 'jquery'), 'after');
}

function enqueue_divi5_vb_ui_icon_picker_script()
{
    if (!is_divi5_visual_builder_active()) {
        return;
    }

    $icons = get_db014_custom_icons_for_divi5_picker();
    if (empty($icons)) {
        return;
    }

    // Thumbnails are DOM-only; no hooks dependency needed.
    $handle = 'divibooster-014-custom-icons-d5-ui';
    wp_register_script($handle, '', array('jquery'), null, true);
    wp_enqueue_script($handle);

    $js  = '(function () {';
    $js .= 'var DB014_DEBUG = false;';
    $js .= 'function db014_log(){ if(!DB014_DEBUG||!window.console||!console.log) return; try{ var a=[].slice.call(arguments); a.unshift("[Divi Booster][014][ui]"); console.log.apply(console,a);}catch(e){} }';
    $js .= 'if (window.__db014_divi5_icons_ui_installed) { return; }';
    $js .= 'window.__db014_divi5_icons_ui_installed = true;';
    $js .= 'var db014_custom_icons = ' . wp_json_encode($icons) . ';';
    $js .= get_divi5_vb_inline_js_body();
    $js .= '})();';

    wp_add_inline_script($handle, $js, 'after');
}

function get_divi5_vb_inline_js_body()
{
    // NOTE: plain JS text; do not wrap in <script> tags.
    return <<<'JS'
    // db014_custom_icons is already defined by the wrapper.
    function db014_dedupe_icon_list(list) {
        var seen = {};
        var out = [];
        (list || []).forEach(function (i) {
            if (!i) return;
            var key = (i.unicode || '') + '|' + (i.fontWeight || i.font_weight || '') + '|' + (i.name || '');
            if (!seen[key]) {
                out.push(i);
                seen[key] = true;
            }
        });
        return out;
    }

    function db014_custom_icon_by_id(id) {
        if (!window.__db014_custom_icon_by_id) {
            window.__db014_custom_icon_by_id = {};
            (db014_custom_icons || []).forEach(function (icon) {
                if (!icon) return;
                window.__db014_custom_icon_by_id[String(icon.id)] = icon;
            });
        }

        return window.__db014_custom_icon_by_id[String(id)] || null;
    }

    function db014_apply_divi5_icon_images(root) {
        if (!root || !root.querySelectorAll) return;

        // Divi 5 icon picker renders buttons with title="wtfdivi014-url{id}".
        var buttons = [];
        if (root.matches && root.matches('.et-vb-icon-picker-list button[title^="wtfdivi014-url"]')) {
            buttons = [root];
        } else {
            buttons = root.querySelectorAll('.et-vb-icon-picker-list button[title^="wtfdivi014-url"]');
        }
        if (!buttons || !buttons.length) return;

        for (var i = 0; i < buttons.length; i++) {
            var btn = buttons[i];
            var title = btn.getAttribute('title') || '';
            if (!title || title.indexOf('wtfdivi014-url') !== 0) continue;

            var idStr = title.substring('wtfdivi014-url'.length);
            var numericId = parseInt(idStr, 10);
            if (!isFinite(numericId) || numericId < 0) continue;

            var icon = db014_custom_icon_by_id(numericId);
            if (!icon || !icon.url) continue;

            // Use a real image thumbnail; avoid a background-image copy underneath it.
            btn.style.position = 'relative';
            btn.style.overflow = 'hidden';

            var existingImg = btn.querySelector('img.db014-icon-thumb');
            if (!existingImg) {
                existingImg = document.createElement('img');
                existingImg.className = 'db014-icon-thumb';
                existingImg.alt = '';
                existingImg.decoding = 'async';
                existingImg.loading = 'lazy';
                existingImg.style.position = 'absolute';
                existingImg.style.left = '50%';
                existingImg.style.top = '50%';
                existingImg.style.transform = 'translate(-50%, -50%)';
                existingImg.style.width = '70%';
                existingImg.style.height = '70%';
                existingImg.style.objectFit = 'contain';
                existingImg.style.pointerEvents = 'none';
                existingImg.style.zIndex = '5';
                btn.appendChild(existingImg);
            }
            if (existingImg.getAttribute('src') !== icon.url) {
                existingImg.src = icon.url;
            }

            // Best-effort: suppress the underlying glyph so it doesn't show through.
            btn.style.color = 'transparent';
            btn.style.fontSize = '0';
            btn.style.removeProperty('background-image');
            btn.style.removeProperty('background-repeat');
            btn.style.removeProperty('background-position');
            btn.style.removeProperty('background-size');
        }
    }

    function db014_watch_divi5_icon_picker_for_images() {
        if (!window.MutationObserver) return;
        if (window.__db014_divi5_icon_picker_observer) return;

        var pending = false;
        var queuedRoots = [];
        var queueRoot = function (root) {
            root = root || document;
            if (root === document) {
                queuedRoots = [document];
                return;
            }
            if (queuedRoots.indexOf(document) !== -1) return;
            if (queuedRoots.indexOf(root) === -1) {
                queuedRoots.push(root);
            }
        };
        var flush = function () {
            pending = false;
            var roots = queuedRoots.slice();
            queuedRoots = [];
            for (var i = 0; i < roots.length; i++) {
                db014_apply_divi5_icon_images(roots[i]);
            }
        };
        var schedule = function (root) {
            queueRoot(root);
            if (pending) return;
            pending = true;
            if (window.requestAnimationFrame) {
                window.requestAnimationFrame(flush);
            } else {
                window.setTimeout(flush, 50);
            }
        };
        var pickerRootFromNode = function (node) {
            if (!node) return null;
            var el = node.nodeType === 1 ? node : node.parentElement;
            if (!el || !el.matches) return null;
            if (el.matches('.et-vb-icon-picker-list')) return el;
            if (el.matches('.et-vb-icon-picker-list button[title^="wtfdivi014-url"]')) return el;
            if (el.closest) {
                var closest = el.closest('.et-vb-icon-picker-list');
                if (closest) return closest;
            }
            if (el.querySelector && el.querySelector('.et-vb-icon-picker-list button[title^="wtfdivi014-url"]')) return el;
            return null;
        };

        schedule(document);

        window.__db014_divi5_icon_picker_observer = new MutationObserver(function (mutations) {
            for (var i = 0; i < mutations.length; i++) {
                var mutation = mutations[i];
                for (var j = 0; j < mutation.addedNodes.length; j++) {
                    var root = pickerRootFromNode(mutation.addedNodes[j]);
                    if (root) {
                        schedule(root);
                    }
                }
            }
        });

        window.__db014_divi5_icon_picker_observer.observe(document.documentElement || document.body, {
            childList: true,
            subtree: true
        });

        if (window.jQuery && !window.__db014_divi5_icon_picker_event_bound) {
            window.__db014_divi5_icon_picker_event_bound = true;
            jQuery(document).on('db_vb_custom_icons_updated', function () {
                schedule(document);
            });
        }
    }

    function db014_css_string_value(value) {
        if (!value || value === 'none' || value === 'normal') return '';

        value = String(value);
        if (value.length >= 2) {
            var first = value.charAt(0);
            var last = value.charAt(value.length - 1);
            if ((first === '"' && last === '"') || (first === "'" && last === "'")) {
                value = value.substring(1, value.length - 1);
            }
        }

        return value
            .replace(/\\([0-9a-f]{1,6})\s?/ig, function (match, hex) {
                return String.fromCodePoint(parseInt(hex, 16));
            })
            .replace(/\\"/g, '"')
            .replace(/\\'/g, "'");
    }

    function db014_find_custom_icon_for_value(value) {
        value = db014_css_string_value(value);
        if (!value) return null;

        if (!window.__db014_custom_icon_by_value) {
            window.__db014_custom_icon_by_value = {};
            for (var i = 0; i < db014_custom_icons.length; i++) {
                var icon = db014_custom_icons[i];
                if (!icon) continue;
                if (icon.decodedUnicode) window.__db014_custom_icon_by_value[db014_css_string_value(icon.decodedUnicode)] = icon;
                if (icon.unicode) window.__db014_custom_icon_by_value[db014_css_string_value(icon.unicode)] = icon;
                if (icon.name) window.__db014_custom_icon_by_value[db014_css_string_value(icon.name)] = icon;
            }
        }

        return window.__db014_custom_icon_by_value[value] || null;
    }

    function db014_normalize_url(url) {
        if (!url) return '';

        try {
            var anchor = document.createElement('a');
            anchor.href = url;
            return anchor.href;
        } catch (e) {
            return String(url);
        }
    }

    function db014_find_custom_icon_for_url(url) {
        url = db014_normalize_url(url);
        if (!url) return null;

        if (!window.__db014_custom_icon_by_url) {
            window.__db014_custom_icon_by_url = {};
            for (var i = 0; i < db014_custom_icons.length; i++) {
                var icon = db014_custom_icons[i];
                if (!icon || !icon.url) continue;
                window.__db014_custom_icon_by_url[db014_normalize_url(icon.url)] = icon;
            }
        }

        return window.__db014_custom_icon_by_url[url] || null;
    }

    function db014_find_divi5_preview_icon(el) {
        if (!el) return null;

        var textValue = (el.textContent || '').trim();
        var textIcon = db014_find_custom_icon_for_value(textValue);
        if (textIcon) return textIcon;
        if (textValue) return null;

        if (el.getAttribute) {
            var dataIconValue = el.getAttribute('data-icon') || '';
            var dataIcon = db014_find_custom_icon_for_value(dataIconValue);
            if (dataIcon) return dataIcon;
            if (db014_css_string_value(dataIconValue)) return null;
        }

        if (window.getComputedStyle) {
            try {
                var beforeValue = window.getComputedStyle(el, '::before').getPropertyValue('content');
                var beforeIcon = db014_find_custom_icon_for_value(beforeValue);
                if (beforeIcon) return beforeIcon;
                if (db014_css_string_value(beforeValue)) return null;
            } catch (e) {
                // Some browsers can throw while an element is being re-rendered.
            }

            try {
                var afterValue = window.getComputedStyle(el, '::after').getPropertyValue('content');
                var afterIcon = db014_find_custom_icon_for_value(afterValue);
                if (afterIcon) return afterIcon;
                if (db014_css_string_value(afterValue)) return null;
            } catch (e2) {
                // ignore
            }
        }

        if (el.querySelector) {
            var img = el.querySelector('img.dbdb-custom-icon-img[src]');
            var imgIcon = img ? db014_find_custom_icon_for_url(img.getAttribute('src') || img.src || '') : null;
            if (imgIcon) return imgIcon;
        }

        if (el.getAttribute) {
            var appliedIcon = db014_find_custom_icon_for_value(el.getAttribute('data-db014-divi5-vb-icon') || '');
            if (appliedIcon) return appliedIcon;
        }

        return null;
    }

    function db014_ensure_divi5_preview_style() {
        if (document.getElementById('db014-divi5-builder-preview-icons-style')) return;

        var style = document.createElement('style');
        style.id = 'db014-divi5-builder-preview-icons-style';
        style.textContent = ''
            + '.et-pb-icon.db014-divi5-vb-custom-icon::before,'
            + '.et-pb-icon.db014-divi5-vb-custom-icon::after{color:transparent !important;display:inline-block !important;width:0 !important;height:0 !important;line-height:0 !important;font-size:0 !important;overflow:hidden !important;}'
            + '.et-pb-icon.db014-divi5-vb-custom-icon img.dbdb-custom-icon-img{height:1em;width:auto;max-width:100%;display:inline-block;vertical-align:baseline;}'
            + '.et_pb_blurb .et_pb_main_blurb_image.db014-divi5-vb-custom-icon-wrap{line-height:0 !important;}'
            + '.et_pb_blurb .et_pb_main_blurb_image.db014-divi5-vb-custom-icon-wrap .et_pb_image_wrap,'
            + '.et_pb_blurb .et-pb-icon.db014-divi5-vb-custom-icon{line-height:0 !important;display: inline-flex;}'
            + '.et_pb_blurb .et-pb-icon.db014-divi5-vb-custom-icon img.dbdb-custom-icon-img{display:block;margin-left:auto;margin-right:auto;}'
            + '.et_pb_blurb_position_left .et_pb_blurb_container{padding-left:15px;}'
            + '.et_pb_blurb_position_left .et-pb-icon.db014-divi5-vb-custom-icon,'
            + '.et_pb_blurb_position_right .et-pb-icon.db014-divi5-vb-custom-icon{width:1em !important;height:1em !important;aspect-ratio:1 / 1 !important;display:block !important;box-sizing:content-box !important;}'
            + '.et_pb_blurb_position_left .et-pb-icon.db014-divi5-vb-custom-icon img.dbdb-custom-icon-img,'
            + '.et_pb_blurb_position_right .et-pb-icon.db014-divi5-vb-custom-icon img.dbdb-custom-icon-img{height:1em !important;width:auto !important;max-width:none !important;vertical-align:top;}'
            + '.et_pb_timeline_marker .et-pb-icon.db-custom-icon,'
            + '.et_pb_timeline_marker .et-pb-icon.db014-divi5-vb-custom-icon{display:inline-flex !important;align-items:center !important;justify-content:center !important;line-height:1 !important;}'
            + '.et_pb_timeline_marker .et-pb-icon.db-custom-icon img.dbdb-custom-icon-img,'
            + '.et_pb_timeline_marker .et-pb-icon.db014-divi5-vb-custom-icon img.dbdb-custom-icon-img{display:block !important;height:1em !important;width:auto !important;max-width:100% !important;margin:0 !important;vertical-align:top !important;}';
        (document.head || document.documentElement).appendChild(style);
    }

    function db014_set_divi5_preview_icon_wrap_class(el, enabled) {
        if (!el || !el.closest) return;

        var wrap = el.closest('.et_pb_main_blurb_image');
        if (!wrap || !wrap.classList) return;

        if (enabled) {
            wrap.classList.add('db014-divi5-vb-custom-icon-wrap');
        } else if (!wrap.querySelector('.et-pb-icon.db014-divi5-vb-custom-icon')) {
            wrap.classList.remove('db014-divi5-vb-custom-icon-wrap');
        }
    }

    function db014_remove_divi5_preview_icon_text_nodes(el) {
        if (!el || !el.childNodes) return;

        for (var i = el.childNodes.length - 1; i >= 0; i--) {
            var child = el.childNodes[i];
            if (!child || child.nodeType !== 3) continue;

            if (db014_find_custom_icon_for_value((child.nodeValue || '').trim())) {
                el.removeChild(child);
            }
        }
    }

    function db014_apply_divi5_builder_preview_icons(root) {
        if (!root || !root.querySelectorAll) return;

        var icons = [];
        if (root.matches && root.matches('.et-pb-icon')) {
            icons = [root];
        } else {
            icons = root.querySelectorAll('.et-vb-module .et-pb-icon, .et_pb_module .et-pb-icon');
        }
        if (!icons || !icons.length) return;

        db014_ensure_divi5_preview_style();

        for (var i = 0; i < icons.length; i++) {
            var el = icons[i];
            var icon = db014_find_divi5_preview_icon(el);

            if (!icon || !icon.url) {
                if (el.classList && el.classList.contains('db014-divi5-vb-custom-icon')) {
                    var staleImg = el.querySelector('img.dbdb-custom-icon-img');
                    if (staleImg) staleImg.parentNode.removeChild(staleImg);
                    el.classList.remove('db014-divi5-vb-custom-icon');
                    el.classList.remove('db-custom-icon');
                    el.removeAttribute('data-db014-divi5-vb-icon');
                    db014_set_divi5_preview_icon_wrap_class(el, false);
                }
                continue;
            }

            el.classList.add('db-custom-icon');
            el.classList.add('db014-divi5-vb-custom-icon');
            el.setAttribute('data-db014-divi5-vb-icon', icon.name || icon.decodedUnicode || '');
            db014_set_divi5_preview_icon_wrap_class(el, true);

            var img = el.querySelector('img.dbdb-custom-icon-img');
            if (!img) {
                img = document.createElement('img');
                img.className = 'dbdb-custom-icon-img';
                img.alt = '';
                img.decoding = 'async';
                img.loading = 'lazy';
                el.appendChild(img);
            }

            if (img.getAttribute('src') !== icon.url) {
                img.src = icon.url;
            }

            db014_remove_divi5_preview_icon_text_nodes(el);
        }
    }

    function db014_watch_divi5_builder_preview_for_images() {
        if (!window.MutationObserver) return;
        if (window.__db014_divi5_preview_icon_watcher_installed) return;
        window.__db014_divi5_preview_icon_watcher_installed = true;

        var queuedRoots = [];
        var pending = false;
        var flush = function () {
            pending = false;
            var roots = queuedRoots.slice();
            queuedRoots = [];
            for (var i = 0; i < roots.length; i++) {
                db014_apply_divi5_builder_preview_icons(roots[i]);
            }
        };
        var queueRoot = function (root) {
            root = root || document;
            if (root === document) {
                queuedRoots = [document];
                return;
            }
            if (queuedRoots.indexOf(document) !== -1) return;

            for (var i = queuedRoots.length - 1; i >= 0; i--) {
                if (queuedRoots[i] === root || (queuedRoots[i].contains && queuedRoots[i].contains(root))) {
                    return;
                }
                if (root.contains && root.contains(queuedRoots[i])) {
                    queuedRoots.splice(i, 1);
                }
            }

            queuedRoots.push(root);
        };
        var schedule = function (root) {
            queueRoot(root);
            if (pending) return;
            pending = true;
            if (window.requestAnimationFrame) {
                window.requestAnimationFrame(flush);
            } else {
                window.setTimeout(flush, 50);
            }
        };
        var scheduleDelayed = function (root, delay) {
            window.setTimeout(function () {
                schedule(root);
            }, delay);
        };
        var nodeMayUpdatePreviewIcon = function (node) {
            if (!node) return false;
            var el = node.nodeType === 1 ? node : node.parentElement;
            if (!el || !el.matches) return false;

            return el.matches('style.et-vb-module-styles, .et-vb-module, .et_pb_module, .et-pb-icon, .db014-divi5-vb-custom-icon, .et_pb_main_blurb_image') ||
                el.matches('img.dbdb-custom-icon-img') ||
                !!(el.querySelector && el.querySelector('style.et-vb-module-styles, .et-pb-icon, .db014-divi5-vb-custom-icon, img.dbdb-custom-icon-img'));
        };
        var previewRootFromNode = function (node) {
            if (!node) return document;
            var el = node.nodeType === 1 ? node : node.parentElement;
            if (!el || !el.closest) return document;

            if (el.matches && el.matches('style.et-vb-module-styles')) {
                return el.closest('.et-vb-module, .et_pb_module') || document;
            }

            return el.closest('.et-vb-module, .et_pb_module') || (nodeMayUpdatePreviewIcon(el) ? el : document);
        };
        var mutationMayUpdatePreviewIcon = function (mutation) {
            if (!mutation) return false;

            if (mutation.type === 'characterData') {
                var parent = mutation.target && mutation.target.parentNode;
                if (!parent || parent.nodeType !== 1 || !parent.matches) return false;
                return parent.matches('style.et-vb-module-styles, .et-pb-icon, .db014-divi5-vb-custom-icon');
            }

            if (mutation.type === 'attributes') {
                var target = mutation.target;
                if (!target || !target.matches) return false;

                if (mutation.attributeName === 'src') {
                    return target.matches('img.dbdb-custom-icon-img') &&
                        !!(target.closest && target.closest('.et_pb_timeline_marker'));
                }

                return target.matches('style.et-vb-module-styles, .et-pb-icon, .db014-divi5-vb-custom-icon, .et_pb_main_blurb_image, img.dbdb-custom-icon-img');
            }

            if (mutation.type === 'childList') {
                for (var i = 0; i < mutation.addedNodes.length; i++) {
                    if (nodeMayUpdatePreviewIcon(mutation.addedNodes[i])) return true;
                }
                for (var j = 0; j < mutation.removedNodes.length; j++) {
                    if (nodeMayUpdatePreviewIcon(mutation.removedNodes[j])) return true;
                }
            }

            return false;
        };
        var scheduleFromPickerEvent = function (event) {
            var target = event && event.target;
            if (!target || !target.closest) return;
            if (!target.closest('.et-vb-icon-picker-list, .et-vb-icon-picker, [class*="icon-picker"]')) return;

            schedule(document);
            scheduleDelayed(document, 50);
            scheduleDelayed(document, 150);
            scheduleDelayed(document, 400);
        };

        schedule(document);

        if (!window.__db014_divi5_preview_icon_observer) {
            window.__db014_divi5_preview_icon_observer = new MutationObserver(function (mutations) {
                for (var i = 0; i < mutations.length; i++) {
                    var mutation = mutations[i];
                    if (mutationMayUpdatePreviewIcon(mutation)) {
                        schedule(previewRootFromNode(mutation.target));
                        return;
                    }
                }
            });

            window.__db014_divi5_preview_icon_observer.observe(document.documentElement || document.body, {
                attributes: true,
                attributeFilter: ['class', 'style', 'data-icon', 'src'],
                childList: true,
                characterData: true,
                subtree: true
            });
        }

        if (window.jQuery && !window.__db014_divi5_preview_icon_event_bound) {
            window.__db014_divi5_preview_icon_event_bound = true;
            jQuery(document).on('db_vb_custom_icons_updated', function () {
                schedule(document);
            });
        }

        if (!window.__db014_divi5_preview_icon_picker_event_bound) {
            window.__db014_divi5_preview_icon_picker_event_bound = true;
            document.addEventListener('click', scheduleFromPickerEvent, true);
            document.addEventListener('pointerup', scheduleFromPickerEvent, true);
            document.addEventListener('keyup', scheduleFromPickerEvent, true);
        }
    }

    function db014_attach_divi5_icons() {
        var hooks = (window.vendor && window.vendor.wp && window.vendor.wp.hooks) || (window.wp && window.wp.hooks);
        if (!hooks || !hooks.addFilter) {
            if (!window.__db014_divi5_icons_attach_retry_pending) {
                window.__db014_divi5_icons_attach_retry_pending = true;
                setTimeout(function () {
                    window.__db014_divi5_icons_attach_retry_pending = false;
                    db014_attach_divi5_icons();
                }, 100);
            }
            return;
        }

        var didAttach = false;
        var filterAttached = window.__db014_divi5_icons_filter_hooks === hooks;

        try {
            if (filterAttached && hooks.hasFilter && !hooks.hasFilter('iconList', 'divibooster.customIcons')) {
                filterAttached = false;
            }
        } catch (e) {
            filterAttached = false;
        }

        if (!filterAttached) {
            try {
                if (hooks.removeFilter) {
                    hooks.removeFilter('iconList', 'divibooster.customIcons');
                }
            } catch (e) {
                // ignore
            }

            // Ensure Divi 5 pickers see the icons.
            hooks.addFilter('iconList', 'divibooster.customIcons', function (existing) {
                existing = existing || [];
                var merged = db014_custom_icons.concat(existing);
                return db014_dedupe_icon_list(merged);
            });

            window.__db014_divi5_icons_filter_hooks = hooks;
            didAttach = true;
        }

        // Best-effort: also push into ETBuilderBackend.fontIconsExtended if present.
        try {
            if (window.ETBuilderBackend && Array.isArray(window.ETBuilderBackend.fontIconsExtended)) {
                var backendIcons = window.ETBuilderBackend.fontIconsExtended;
                var existing = {};
                var addedBackendIcon = false;

                backendIcons.forEach(function (ic) {
                    if (!ic) return;
                    if (ic.name) existing['name|' + ic.name] = true;
                    if (ic.unicode) existing['unicode|' + ic.unicode] = true;
                    if (ic.decodedUnicode) existing['decoded|' + ic.decodedUnicode] = true;
                    if (ic.decoded_unicode) existing['decoded|' + ic.decoded_unicode] = true;
                });

                db014_custom_icons.forEach(function (ic) {
                    if (!ic) return;
                    var keys = [
                        ic.name ? 'name|' + ic.name : '',
                        ic.unicode ? 'unicode|' + ic.unicode : '',
                        ic.decodedUnicode ? 'decoded|' + ic.decodedUnicode : ''
                    ];
                    var alreadyExists = keys.some(function (key) {
                        return key && existing[key];
                    });
                    if (alreadyExists) return;

                    var entry = Object.assign({}, ic);
                    if (entry.decodedUnicode) entry.decoded_unicode = entry.decodedUnicode;
                    if (!entry.search_terms && entry.searchTerms) entry.search_terms = entry.searchTerms;
                    backendIcons.push(entry);
                    addedBackendIcon = true;
                    keys.forEach(function (key) {
                        if (key) existing[key] = true;
                    });
                });

                if (window.__db014_divi5_backend_icons_target !== backendIcons || addedBackendIcon) {
                    window.__db014_divi5_backend_icons_target = backendIcons;
                    didAttach = true;
                }
            }
        } catch (e) {
            // ignore
        }

        if (didAttach && window.jQuery) {
            jQuery(document).trigger('db_vb_custom_icons_updated');
        }
    }

    function db014_boot() {
        db014_attach_divi5_icons();
        db014_watch_divi5_icon_picker_for_images();
        db014_watch_divi5_builder_preview_for_images();
    }

    db014_boot();

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', function () {
            db014_boot();
        }, { once: true });
    }

    // Retry during VB startup.
    if (!window.__db014_divi5_boot_retry_interval) {
        var start = Date.now();
        window.__db014_divi5_boot_retry_interval = setInterval(function () {
            db014_boot();
            if ((Date.now() - start) > 60000) {
                clearInterval(window.__db014_divi5_boot_retry_interval);
                window.__db014_divi5_boot_retry_interval = null;
            }
        }, 500);
    }
JS;
}

// === Divi 5 (compat mode): CSS fixups ===
// In Divi 5 compatibility mode, button pseudo-element icon positioning can be thrown off
// by font-size / transforms being applied differently than Divi 4. We inject a late CSS
// override (previously in the old Divi 4 implementation) so DB014 custom button icons
// stay aligned.
if (function_exists('add_action')) {
    add_action('wp_head', __NAMESPACE__ . '\\output_divi5_compat_custom_icon_css', 99);
}

function output_divi5_compat_custom_icon_css()
{
    // Only apply when Divi 5 is active (including compat mode). This keeps the override
    // from affecting non-Divi sites, while still loading anywhere Divi 5 might render.
    if (!function_exists('et_builder_d5_enabled') || !\et_builder_d5_enabled()) {
        return;
    }

    // Late, highly-specific override so it wins over `db014_shared_css()` (Divi 4 CSS).
    ?>
    <style>
        body #page-container .et_pb_section a.et_pb_button.db-custom-extended-icon[data-icon]:before,
        body #page-container .et_pb_section a.et_pb_button.db-custom-extended-icon[data-icon]:after,
        a.et_pb_button.db-custom-extended-icon[data-icon]:before,
        a.et_pb_button.db-custom-extended-icon[data-icon]:after {
            font-size: inherit !important;
            height: 100% !important;
            top: 0 !important;
        }

        body #page-container .et_pb_section a.et_pb_button.db-custom-extended-icon[data-icon]:before,
        a.et_pb_button.db-custom-extended-icon[data-icon]:before {
            left: 0 !important;
            transform: none !important;
        }

        body #page-container .et_pb_section a.et_pb_button.db-custom-extended-icon[data-icon]:after,
        a.et_pb_button.db-custom-extended-icon[data-icon]:after {
            right: 0 !important;
            transform: none !important;
        }
    </style>
    <?php
}

// === Divi 5: Front-end output filters ===
// Divi 5 doesn't always emit the same markup contract Divi Booster's existing runtime expects.
// We restore that contract by injecting icon entities / data attributes into rendered block HTML.
if (function_exists('add_filter')) {
    add_filter('render_block_divi/timeline-item', __NAMESPACE__ . '\\db014_divi5_timeline_item_output', 13, 3);
    add_filter('render_block_divi/toggle', __NAMESPACE__ . '\\db014_divi5_toggle_output', 13, 3);
    // Apply to any block render so modules other than divi/button that support button icons
    // also receive the legacy data-icon attribute.
    add_filter('render_block', __NAMESPACE__ . '\\db014_divi5_button_output', 13, 3);

    // Divi 5 Icon module front-end uses `.et_pb_icon_wrap` to hold the glyph.
    // DB014's existing JS (from `functions.php`) only processes a configurable set of icon classes.
    // Add `.et_pb_icon_wrap` so custom icons in the Icon module get replaced with images.
    add_filter('dbdb_custom_icon_classes', __NAMESPACE__ . '\\db014_divi5_add_icon_wrap_to_custom_icon_classes', 10, 1);
}

function db014_divi5_add_icon_wrap_to_custom_icon_classes($classes)
{
    if (!db014_is_divi5_frontend()) {
        return $classes;
    }

    if (!is_array($classes)) {
        $classes = array();
    }

    if (!in_array('et_pb_icon_wrap', $classes, true)) {
        $classes[] = 'et_pb_icon_wrap';
    }

    return $classes;
}

function db014_is_divi5_frontend()
{
    // We only want this path for Divi 5's native front-end. In Divi 4 compatibility mode,
    // the legacy Divi 4 output already works with DB014's shared JS/CSS.
    if (!function_exists('et_builder_d5_enabled') || !\et_builder_d5_enabled()) {
        return false;
    }
    // Avoid affecting VB output; builder has its own hooks/DOM and can re-render frequently.
    if (function_exists('et_core_is_fb_enabled') && \et_core_is_fb_enabled()) {
        return false;
    }
    return true;
}

function db014_divi5_extract_icon_entity_from_value($value)
{
    if (is_string($value) && preg_match('/^(&#x[0-9a-f]+;)(?:\|\|divi\|\|400)?$/i', $value, $matches)) {
        return $matches[1];
    }

    if (is_array($value)) {
        foreach (array('unicode', 'content', 0) as $key) {
            if (isset($value[$key])) {
                $entity = db014_divi5_extract_icon_entity_from_value($value[$key]);
                if ($entity !== '') {
                    return $entity;
                }
            }
        }
    }

    return '';
}

function db014_divi5_icon_entity_is_custom($icon_entity)
{
    static $custom_icon_entities = null;

    if ($custom_icon_entities === null) {
        $custom_icon_entities = array();
        foreach (get_db014_custom_icons_for_divi5_picker() as $icon) {
            if (isset($icon['unicode']) && is_string($icon['unicode'])) {
                $custom_icon_entities[strtolower($icon['unicode'])] = true;
            }
        }
    }

    return isset($custom_icon_entities[strtolower($icon_entity)]);
}

function db014_is_divi5_active()
{
    return function_exists('et_builder_d5_enabled') && \et_builder_d5_enabled();
}

function db014_divi5_timeline_item_output($block_content, $parsed_block, $block)
{
    if (!db014_is_divi5_active()) {
        return $block_content;
    }

    if (!is_string($block_content)) {
        return $block_content;
    }

    $attrs = isset($parsed_block['attrs']) && is_array($parsed_block['attrs']) ? $parsed_block['attrs'] : array();
    $marker_icon_value = isset($attrs['marker']['innerContent']['desktop']['value']) ? $attrs['marker']['innerContent']['desktop']['value'] : null;
    $icon_entity = db014_divi5_extract_icon_entity_from_value($marker_icon_value);

    if ($icon_entity === '' || !db014_divi5_icon_entity_is_custom($icon_entity)) {
        return $block_content;
    }

    return preg_replace_callback(
        '/(<div[^>]*class=("|\")[^\"]*\bet_pb_timeline_marker\b[^\"]*\2[^>]*>)(.*?)(<\/div>)/is',
        function ($matches) use ($icon_entity) {
            if (preg_match('/\bet-pb-icon\b/i', $matches[3])) {
                return $matches[0];
            }

            return $matches[1] . $matches[3] . '<span class="et-pb-icon">' . $icon_entity . '</span>' . $matches[4];
        },
        $block_content,
        1
    );
}

/**
 * Divi 5: Add data-icon and data-icon-open attributes to Toggle block titles
 * so the existing JS can attach custom image icons per-instance.
 */
function db014_divi5_toggle_output($block_content, $parsed_block, $block)
{
    if (!db014_is_divi5_frontend()) {
        return $block_content;
    }

    if (!is_string($block_content)) {
        return $block_content;
    }

    $attrs = isset($parsed_block['attrs']) && is_array($parsed_block['attrs']) ? $parsed_block['attrs'] : array();

    $icon      = isset($attrs['module']['icon']['desktop']['value']) ? $attrs['module']['icon']['desktop']['value'] : '';
    $icon_open = isset($attrs['module']['openIcon']['desktop']['value']) ? $attrs['module']['openIcon']['desktop']['value'] : '';

    $data_icon      = '';
    $data_icon_open = '';

    // Icons may be stored as extended icon strings or unicode only.
    if (is_string($icon) && preg_match('/^(&#x[0-9a-f]+;)(?:\|\|divi\|\|400)?$/i', $icon, $m)) {
        $data_icon = $m[1];
    }
    if (is_string($icon_open) && preg_match('/^(&#x[0-9a-f]+;)(?:\|\|divi\|\|400)?$/i', $icon_open, $m2)) {
        $data_icon_open = $m2[1];
    }

    if ($data_icon === '' && $data_icon_open === '') {
        return $block_content;
    }

    $block_content = preg_replace_callback(
        '/<[^>]*class="([^"]*et_pb_toggle_title[^"]*)"[^>]*>/',
        function ($matches) use ($data_icon, $data_icon_open) {
            $tag = $matches[0];

            // Only add attributes once.
            if (strpos($tag, 'data-icon=') !== false || strpos($tag, 'data-icon-open=') !== false) {
                return $tag;
            }

            $replacement = rtrim($tag, '>');

            if ($data_icon !== '') {
                $replacement .= ' data-icon="' . esc_attr($data_icon) . '"';
            }
            if ($data_icon_open !== '') {
                $replacement .= ' data-icon-open="' . esc_attr($data_icon_open) . '"';
            }

            $replacement .= '>';

            return $replacement;
        },
        $block_content,
        1
    );

    return $block_content;
}

/**
 * Divi 5: Ensure blocks that output Divi-style buttons include `data-icon` / icon entities
 * so DB014's existing runtime can replace them with custom images.
 */
function db014_divi5_button_output($block_content, $parsed_block, $block)
{
    if (!db014_is_divi5_frontend()) {
        return $block_content;
    }

    $block_name = '';
    if (is_array($parsed_block) && isset($parsed_block['blockName'])) {
        $block_name = (string) $parsed_block['blockName'];
    }

    // Divi 5 may pass non-string content here in some builds.
    if (!is_string($block_content)) {
        if (is_object($block_content) && method_exists($block_content, '__toString')) {
            $block_content = (string) $block_content;
        } else {
            return $block_content;
        }
    }

    $attrs = isset($parsed_block['attrs']) && is_array($parsed_block['attrs']) ? $parsed_block['attrs'] : array();

    // === Case 1: Divi 5 Button-style icons (attrs.button.decoration...) => inject data-icon onto <a.et_pb_button>
    $icon_value = '';
    if (
        isset($attrs['button']) && is_array($attrs['button']) &&
        isset($attrs['button']['decoration']) && is_array($attrs['button']['decoration']) &&
        isset($attrs['button']['decoration']['button']) && is_array($attrs['button']['decoration']['button']) &&
        isset($attrs['button']['decoration']['button']['desktop']) && is_array($attrs['button']['decoration']['button']['desktop']) &&
        isset($attrs['button']['decoration']['button']['desktop']['value']) && is_array($attrs['button']['decoration']['button']['desktop']['value']) &&
        isset($attrs['button']['decoration']['button']['desktop']['value']['icon']) && is_array($attrs['button']['decoration']['button']['desktop']['value']['icon']) &&
        isset($attrs['button']['decoration']['button']['desktop']['value']['icon']['settings']) && is_array($attrs['button']['decoration']['button']['desktop']['value']['icon']['settings']) &&
        isset($attrs['button']['decoration']['button']['desktop']['value']['icon']['settings']['unicode'])
    ) {
        $icon_value = (string) $attrs['button']['decoration']['button']['desktop']['value']['icon']['settings']['unicode'];
    }

    // Normalize icon to an HTML entity like "&#x800;" (strip any "||divi||400" suffix).
    $data_icon = '';
    if (is_string($icon_value) && preg_match('/^(&#x[0-9a-f]+;)(?:\|\|divi\|\|400)?$/i', $icon_value, $m)) {
        $data_icon = $m[1];
    }

    if ($data_icon !== '') {
        $block_content = preg_replace_callback(
            '/<a([^>]*class=("|\").*?\bet_pb_button\b.*?\2)([^>]*)>/i',
            function ($matches) use ($data_icon) {
                $tag = $matches[0];
                if (preg_match('/\sdata-icon=("|\").*?\1/i', $tag)) {
                    return $tag;
                }
                $replacement = rtrim($tag, '>');
                $replacement .= ' data-icon="' . esc_attr($data_icon) . '">';
                return $replacement;
            },
            $block_content,
            1
        );
    }

    // === Case 2: Divi 5 icon-picker glyphs (icon.innerContent) => inject entity into first empty .et-pb-icon
    $icon_inner = '';
    $icon_inner_raw = null;
    if (
        isset($attrs['icon']) && is_array($attrs['icon']) &&
        isset($attrs['icon']['innerContent']) && is_array($attrs['icon']['innerContent']) &&
        isset($attrs['icon']['innerContent']['desktop']) && is_array($attrs['icon']['innerContent']['desktop']) &&
        isset($attrs['icon']['innerContent']['desktop']['value'])
    ) {
        $icon_inner_raw = $attrs['icon']['innerContent']['desktop']['value'];

        if (is_string($icon_inner_raw)) {
            $icon_inner = $icon_inner_raw;
        } elseif (is_array($icon_inner_raw)) {
            if (isset($icon_inner_raw['content']) && is_string($icon_inner_raw['content'])) {
                $icon_inner = $icon_inner_raw['content'];
            } elseif (isset($icon_inner_raw[0]) && is_string($icon_inner_raw[0])) {
                $icon_inner = $icon_inner_raw[0];
            } else {
                foreach ($icon_inner_raw as $v) {
                    if (is_string($v) && preg_match('/^&#x[0-9a-f]+;(?:\|\|divi\|\|400)?$/i', $v)) {
                        $icon_inner = $v;
                        break;
                    }
                    if (is_array($v) && isset($v['content']) && is_string($v['content']) && preg_match('/^&#x[0-9a-f]+;(?:\|\|divi\|\|400)?$/i', $v['content'])) {
                        $icon_inner = $v['content'];
                        break;
                    }
                }
            }
        }
    }

    $icon_entity = '';
    if ($icon_inner !== '' && preg_match('/^(&#x[0-9a-f]+;)(?:\|\|divi\|\|400)?$/i', $icon_inner, $m_icon)) {
        $icon_entity = $m_icon[1];
    }

    if ($icon_entity !== '') {
        $block_content = preg_replace_callback(
            '/<span([^>]*class=("|\")[^\"]*\bet-pb-icon\b[^\"]*\2)([^>]*)>(.*?)<\/span>/is',
            function ($matches) use ($icon_entity) {
                $inner = trim(wp_strip_all_tags($matches[4]));
                if ($inner !== '') {
                    return $matches[0];
                }
                return '<span' . $matches[1] . $matches[3] . '>' . $icon_entity . '</span>';
            },
            $block_content,
            1
        );
    }

    // === Case 2b: Divi 5 Icon module front-end output uses `.et_pb_icon_wrap` (not `.et-pb-icon`) ===
    // DB014's runtime replaces icons by either:
    // - finding elements whose *text* equals the icon id (for wtfdivi014-urlN), or
    // - finding elements with a matching `data-icon` attribute equal to the decoded unicode glyph.
    // For the Icon module, it's easiest to inject `data-icon` and the entity string.
    if ($block_name === 'divi/icon' && $icon_entity !== '') {
        $decoded_icon = html_entity_decode($icon_entity, ENT_QUOTES, 'UTF-8');
        $block_content = preg_replace_callback(
            '/<span([^>]*class=("|\")[^\"]*\bet_pb_icon_wrap\b[^\"]*\2)([^>]*)>(.*?)<\/span>/is',
            function ($matches) use ($icon_entity, $decoded_icon) {
                $inner = trim(wp_strip_all_tags($matches[4]));

                // Add data-icon if it's not already present.
                $tag_open = '<span' . $matches[1] . $matches[3] . '>';
                if (strpos($tag_open, 'data-icon=') === false) {
                    $tag_open = rtrim($tag_open, '>');
                    $tag_open .= ' data-icon="' . esc_attr($decoded_icon) . '">';
                }

                // Note: Do NOT add `et-pb-icon` here.
                // We add it to the injected <img> instead (post-replacement) so Divi's size rule can
                // target the image directly (see `output_divi5_frontend_icon_module_img_css()`).

                // If Divi already output a glyph (e.g. \u0800), keep it; otherwise inject the entity string.
                $content = ($inner !== '') ? $matches[4] : esc_html($icon_entity);
                return $tag_open . $content . '</span>';
            },
            $block_content,
            1
        );
    }

    // === Case 3: Divi 5 Blurb module icons (imageIcon.innerContent.*) ===
    if ($block_name === 'divi/blurb') {
        $blurb_use_icon   = null;
        $blurb_icon_value = '';

        if (isset($attrs['imageIcon']) && is_array($attrs['imageIcon']) && isset($attrs['imageIcon']['innerContent']) && is_array($attrs['imageIcon']['innerContent'])) {
            $ic = $attrs['imageIcon']['innerContent'];

            if (
                isset($ic['desktop']) && is_array($ic['desktop']) &&
                isset($ic['desktop']['value']) && is_array($ic['desktop']['value'])
            ) {
                $desktop_value = $ic['desktop']['value'];

                if (array_key_exists('useIcon', $desktop_value)) {
                    $blurb_use_icon = $desktop_value['useIcon'];
                }
                if (isset($desktop_value['icon'])) {
                    if (is_string($desktop_value['icon'])) {
                        $blurb_icon_value = $desktop_value['icon'];
                    } elseif (is_array($desktop_value['icon']) && isset($desktop_value['icon']['unicode']) && is_string($desktop_value['icon']['unicode'])) {
                        $blurb_icon_value = $desktop_value['icon']['unicode'];
                    }
                }
            } else {
                if (isset($ic['useIcon'])) {
                    $blurb_use_icon = $ic['useIcon'];
                }
                if (isset($ic['icon'])) {
                    if (is_string($ic['icon'])) {
                        $blurb_icon_value = $ic['icon'];
                    } elseif (is_array($ic['icon']) && isset($ic['icon']['unicode']) && is_string($ic['icon']['unicode'])) {
                        $blurb_icon_value = $ic['icon']['unicode'];
                    }
                }
            }
        }

        $use_icon_enabled = ($blurb_use_icon === null) ? true : in_array($blurb_use_icon, array(true, 1, '1', 'on', 'yes', 'true'), true);

        if ($use_icon_enabled && $blurb_icon_value !== '') {
            $blurb_entity = '';
            if (preg_match('/^(&#x[0-9a-f]+;)(?:\|\|divi\|\|400)?$/i', $blurb_icon_value, $m_blurb)) {
                $blurb_entity = $m_blurb[1];
            }

            if ($blurb_entity !== '') {
                $block_content = preg_replace_callback(
                    '/<div([^>]*class=("|\")[^\"]*\bet_pb_blurb\b[^\"]*\2)([^>]*)>(.*?)<\/div>/is',
                    function ($matches) use ($blurb_entity) {
                        $blurb_html = $matches[0];

                        if (preg_match('/<span[^>]*class=("|\")[^\"]*\bet-pb-icon\b[^\"]*\1[^>]*>\s*[^<\s][^<]*<\/span>/i', $blurb_html)) {
                            return $blurb_html;
                        }

                        $updated = preg_replace_callback(
                            '/<span([^>]*class=("|\")[^\"]*\bet-pb-icon\b[^\"]*\2)([^>]*)>(.*?)<\/span>/is',
                            function ($m2) use ($blurb_entity) {
                                $inner = trim(wp_strip_all_tags($m2[4]));
                                if ($inner !== '') {
                                    return $m2[0];
                                }
                                return '<span' . $m2[1] . $m2[3] . '>' . $blurb_entity . '</span>';
                            },
                            $blurb_html,
                            1
                        );

                        if (is_string($updated) && $updated !== '' && $updated !== $blurb_html) {
                            return $updated;
                        }

                        $icon_markup = '<div class="et_pb_main_blurb_image"><span class="et_pb_image_wrap"><span class="et-pb-icon">' . $blurb_entity . '</span></span></div>';

                        $inserted = preg_replace(
                            '/(<div[^>]*class=("|\")[^\"]*\bet_pb_blurb_content\b[^\"]*\2[^>]*>)/i',
                            '$1' . $icon_markup,
                            $blurb_html,
                            1
                        );

                        return is_string($inserted) && $inserted !== '' ? $inserted : $blurb_html;
                    },
                    $block_content,
                    1
                );
            }
        }
    }

    // === Case 4: Divi 5 Icon List module item icons ===
    // Icon List renders list item icons as: <span class="et-pb-icon">{glyph}</span>
    // In some builds the initial (or some) items can omit the span entirely, or emit a
    // placeholder glyph. Ensure the selected icon entity is present so DB014's shared JS
    // can replace custom glyphs with images on the front-end.
    if ($block_name === 'divi/icon-list-item' && $icon_entity !== '') {
        // If there's no icon span at all, insert it as the first child of the list item.
        if (strpos($block_content, 'class="et-pb-icon"') === false && strpos($block_content, 'class="et-pb-icon ') === false) {
            $block_content = preg_replace(
                '/<li([^>]*class=("|\")[^\"]*\\bet_pb_icon_list_item\\b[^\"]*\2[^>]*)>/i',
                '<li$1><span class="et-pb-icon">' . $icon_entity . '</span>',
                $block_content,
                1
            );
        } else {
            // If the icon span exists but doesn't contain a real glyph yet, inject the entity.
            $block_content = preg_replace_callback(
                '/<span([^>]*class=("|\")[^\"]*\\bet-pb-icon\\b[^\"]*\2[^>]*)>(.*?)<\/span>/is',
                function ($matches) use ($icon_entity) {
                    $inner = trim(wp_strip_all_tags($matches[3]));
                    if ($inner !== '' && $inner !== '!') {
                        return $matches[0];
                    }

                    // Preserve any existing attributes, inject the chosen icon entity.
                    return '<span' . $matches[1] . '>' . $icon_entity . '</span>';
                },
                $block_content,
                1
            );
        }
    }

    return $block_content;
}

// === Divi 5 front-end: ensure Icon module custom <img> inherits icon size ===
// When the Icon module gets replaced with an <img>, size it relative to the font-size Divi assigns
// (which is controlled via module settings), with a 96px fallback.
if (function_exists('add_action')) {
    add_action('wp_head', __NAMESPACE__ . '\\output_divi5_frontend_icon_module_img_css', 99);
}

function output_divi5_frontend_icon_module_img_css()
{
    if (!db014_is_divi5_frontend()) {
        return;
    }
    ?>
    <style>
        /* Default/fallback size */
        .et_pb_icon .et_pb_icon_wrap img.dbdb-custom-icon-img {
            height: 96px;
            width: auto;
        }

        /* Preferred: follow Divi's font-size (Divi applies it to `.et_pb_icon_wrap .et-pb-icon`) */
        .et_pb_icon .et_pb_icon_wrap img.dbdb-custom-icon-img.et-pb-icon {
            height: 1em;
            width: auto;
        }
    </style>
    <?php
}

// Icon-module-specific post-processing: add `et-pb-icon` to the injected <img>.
// The <img> is created by DB014's existing shared JS (`db014_update_custom_icons()`), so we
// hook in from the front-end and patch the DOM whenever icons are updated.
if (function_exists('add_action')) {
    add_action('wp_footer', __NAMESPACE__ . '\\db014_divi5_icon_module_add_img_class_js', 99);
}

function db014_divi5_icon_module_add_img_class_js()
{
    if (!db014_is_divi5_frontend()) {
        return;
    }
    ?>
    <script data-name="db014-divi5-icon-module-img-class">
        (function() {
            function apply() {
                var imgs = document.querySelectorAll('.et_pb_icon .et_pb_icon_wrap img.dbdb-custom-icon-img');
                for (var i = 0; i < imgs.length; i++) {
                    imgs[i].classList.add('et-pb-icon');
                }
            }

            // Run initially and whenever DB014 triggers its internal refresh event.
            if (document.readyState === 'loading') {
                document.addEventListener('DOMContentLoaded', apply, { once: true });
            } else {
                apply();
            }

            if (window.jQuery) {
                window.jQuery(document).on('db_vb_custom_icons_updated', function() {
                    apply();
                });
            }

            // Divi / DB014 can inject the <img> after initial DOMContentLoaded.
            // For front-end use, a short delayed retry is usually enough and avoids
            // needing a MutationObserver.
            window.setTimeout(apply, 200);

        })();
    </script>
    <?php
}


// === Divi 5: Frontend render filter for Icon module ===
if (function_exists('add_filter')) {
    add_filter('render_block_' . DIVI5_ICON_MODULE_SLUG, __NAMESPACE__ . '\\filter_divi5_icon_block_output', 13, 3);
}

function filter_divi5_icon_block_output($block_content, $parsed_block, $block)
{
    if (!is_string($block_content)) {
        return $block_content;
    }

    // The existing custom icon system is driven purely by the presence of specific icon codes
    // in the markup (e.g. wtfdivi014-url...). The Divi 5 Icon block already renders those
    // codes into the HTML, and the existing shared CSS/JS (db014_shared_css / db014_sharedUserJs)
    // operate globally. We just pass the content through unchanged so that the existing
    // front-end logic can work for this module instance as well.
    return $block_content;
}

// === Divi 4: Classic output filter for Icon module ===
if (function_exists('add_filter')) {
    add_filter('et_module_shortcode_output', __NAMESPACE__ . '\\filter_divi4_icon_shortcode_output', 13, 3);
}

function filter_divi4_icon_shortcode_output($output, $render_slug, $module)
{
    if (!is_string($output) || $render_slug !== DIVI4_ICON_MODULE_SLUG || !isset($module->props)) {
        return $output;
    }

    // As with Divi 5, the existing custom icon feature relies on detecting custom icon codes
    // in the rendered markup and replacing them via shared JS. We leave the output unchanged
    // so that this behavior continues to function as before, while ensuring this filter runs
    // at a similar priority to the Divi 5 block filter so both paths share behavior.
    return $output;
}

// === Divi 5: Ensure builder-side hooks for module mapping (JS) only when needed ===
if (function_exists('add_action')) {
    add_action('wp_enqueue_scripts', __NAMESPACE__ . '\\enqueue_divi5_icon_builder_script');
}

function enqueue_divi5_icon_builder_script()
{
    // Only load in Divi 5 visual builder.
    if ((!function_exists('et_builder_d5_enabled') || !\et_builder_d5_enabled()) ||
        (!function_exists('et_core_is_fb_enabled') || !\et_core_is_fb_enabled())) {
        return;
    }

    $handle = sanitize_title('divi-booster-' . DIVI5_ICON_MODULE_SLUG . '-db014-custom-icons');
    \dbdb_enqueue_inline_script_if_dependencies_available($handle, get_divi5_icon_inline_js(), array('lodash', array('divi-vendor-wp-hooks', 'wp-hooks')));
}

function get_divi5_icon_inline_js()
{
    $divi5_slug = wp_json_encode(DIVI5_ICON_MODULE_SLUG);

    return <<<END
(function() {
    if (!window.vendor || !window.vendor.wp || !window.vendor.wp.hooks) {
        return;
    }

    var hooks = window.vendor.wp.hooks;

    // Ensure that the icon module is present in the module mapping. We don't need to
    // modify its attributes here as Divi 5 already defines them; this is primarily to
    // keep the hook structure consistent and allow future extension if required.
    hooks.addFilter('divi.moduleLibrary.moduleMapping', 'divi', function(modules) {
        var path = [{$divi5_slug}, 'metadata', 'attributes'];
        var lodash = window.lodash || {};
        var get = lodash.get || function(obj, pathArr) {
            if (!obj) return undefined;
            var cur = obj;
            for (var i = 0; i < pathArr.length; i++) {
                if (cur[pathArr[i]] === undefined) return undefined;
                cur = cur[pathArr[i]];
            }
            return cur;
        };
        var set = lodash.set || function(obj, pathArr, value) {
            if (!obj) return obj;
            var cur = obj;
            for (var i = 0; i < pathArr.length - 1; i++) {
                if (typeof cur[pathArr[i]] !== 'object' || cur[pathArr[i]] === null) {
                    cur[pathArr[i]] = {};
                }
                cur = cur[pathArr[i]];
            }
            cur[pathArr[pathArr.length - 1]] = value;
            return obj;
        };

        if (!get(modules, path)) {
            set(modules, path, {});
        }
        return modules;
    });

    // The existing DB 014 custom icon feature already attaches a MutationObserver and
    // triggers the 'db_vb_custom_icons_updated' event inside the builder. The builder
    // for Divi 5 still uses the same event to refresh custom icons across modules, so
    // we do not need additional icon-update wiring specifically for the Icon module.
})();
END;
}


// Created at 1767563992.
