<?php

namespace DiviBooster\DiviBooster\Modules\Timeline\ScrollStateColors;

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

const TIMELINE_SLUG = 'divi/timeline';
const TIMELINE_ITEM_SLUG = 'divi/timeline-item';
const LEGACY_MODULE_ATTR = 'dbTimelineScrollStateColors';
const LEGACY_ITEM_ATTR = 'dbTimelineItemScrollStateColors';
const HORIZONTAL_CAROUSEL_ATTR = 'dbTimelineHorizontalCarousel';
const GROUP_SLUG = 'designScrollStateColors';

if (function_exists('add_action')) {
    add_action('wp_enqueue_scripts', __NAMESPACE__ . '\\enqueue_builder_fields');
    add_action('wp_footer', __NAMESPACE__ . '\\output_runtime_assets', 99);
}

if (function_exists('add_filter')) {
    add_filter('divi_module_library_register_module_attrs', __NAMESPACE__ . '\\register_module_attrs', 10, 2);
    add_filter('block_type_metadata_settings', __NAMESPACE__ . '\\register_block_attrs');
    add_filter('render_block_' . TIMELINE_SLUG, __NAMESPACE__ . '\\filter_timeline_output', 20, 3);
    add_filter('render_block_' . TIMELINE_ITEM_SLUG, __NAMESPACE__ . '\\filter_timeline_item_output', 20, 3);
}

function scroll_states()
{
    return array('previous', 'current', 'upcoming');
}

function background_elements()
{
    return array('marker', 'card', 'connector');
}

function text_elements()
{
    return array('date', 'title', 'body');
}

function elements()
{
    return array_merge(background_elements(), text_elements());
}

function legacy_fields()
{
    $fields = array();

    foreach (scroll_states() as $state) {
        foreach (background_elements() as $element) {
            $fields[$state . ucfirst($element)] = array(
                'state' => $state,
                'element' => $element,
                'css_property' => 'background-color',
                'var_suffix' => 'bg',
                'label_suffix' => 'Background',
                'description_property' => 'background color',
            );
        }

        foreach (text_elements() as $element) {
            $fields[$state . ucfirst($element)] = array(
                'state' => $state,
                'element' => $element,
                'css_property' => 'color',
                'var_suffix' => 'color',
                'label_suffix' => 'Color',
                'description_property' => 'text color',
            );
        }
    }

    return $fields;
}

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

function is_divi5_visual_builder()
{
    return is_divi5_active() && function_exists('et_core_is_fb_enabled') && \et_core_is_fb_enabled();
}

function enqueue_builder_fields()
{
    if (!is_divi5_visual_builder()) {
        return;
    }

    $handle = sanitize_title('divi-booster-' . TIMELINE_SLUG . '-scroll-state-colors');
    \dbdb_enqueue_inline_script_if_dependencies_available($handle, get_builder_inline_js(), array('lodash', array('divi-vendor-wp-hooks', 'wp-hooks')));
}

function get_builder_inline_js()
{
    $timeline_slug = wp_json_encode(TIMELINE_SLUG);
    $timeline_item_slug = wp_json_encode(TIMELINE_ITEM_SLUG);
    $states = wp_json_encode(scroll_states());
    $module_attr = wp_json_encode(LEGACY_MODULE_ATTR);
    $item_attr = wp_json_encode(LEGACY_ITEM_ATTR);
    $carousel_attr = wp_json_encode(HORIZONTAL_CAROUSEL_ATTR);
    $group_slug = wp_json_encode(GROUP_SLUG);
    $module_definition = wp_json_encode(get_attribute_definition(LEGACY_MODULE_ATTR, false));
    $item_definition = wp_json_encode(get_attribute_definition(LEGACY_ITEM_ATTR, true));
    $breakpoint_settings = wp_json_encode(get_scroll_state_breakpoint_settings());

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

    var hooks = window.vendor.wp.hooks;
    var get = window.lodash.get;
    var set = window.lodash.set;
    var states = {$states};
    var labels = {
        previous: 'Previous',
        current: 'Current',
        upcoming: 'Upcoming'
    };
    var timelineSlug = {$timeline_slug};
    var timelineItemSlug = {$timeline_item_slug};
    var carouselAttr = {$carousel_attr};
    var groupSlug = {$group_slug};
    var breakpointSettings = {$breakpoint_settings};

    function addScrollStateGroup(groups, metadata) {
        var moduleName = get(metadata || {}, ['name'], '');
        if (moduleName && moduleName !== timelineSlug && moduleName !== timelineItemSlug) {
            return groups;
        }

        groups = groups || {};
        groups[groupSlug] = Object.assign({}, groups[groupSlug] || {}, {
            groupName: groupSlug,
            panel: 'design',
            priority: 92,
            multiElements: true,
            component: {
                name: 'divi/composite',
                props: Object.assign({}, get(groups, [groupSlug, 'component', 'props'], {}), {
                    groupLabel: 'Scroll State',
                    visible: function(ctx) {
                        return isScrollStateGroupVisible(ctx);
                    }
                })
            }
        });

        return groups;
    }

    hooks.addFilter('divi.moduleLibrary.moduleMapping', 'divibooster.timelineScrollStateColors', function(modules) {
        var timelinePath = [{$timeline_slug}, 'metadata', 'attributes'];
        var timelineAttrs = get(modules, timelinePath);
        if (timelineAttrs) {
            timelineAttrs[{$module_attr}] = {$module_definition};
        } else {
            set(modules, timelinePath, (timelineAttrs = {}));
            timelineAttrs[{$module_attr}] = {$module_definition};
        }
        applyScrollStateGroupVisibilityCallbacks(get(modules, [timelineSlug], {}));

        var itemPath = [{$timeline_item_slug}, 'metadata', 'attributes'];
        var itemAttrs = get(modules, itemPath);
        if (itemAttrs) {
            itemAttrs[{$item_attr}] = {$item_definition};
        } else {
            set(modules, itemPath, (itemAttrs = {}));
            itemAttrs[{$item_attr}] = {$item_definition};
        }

        return modules;
    });

    hooks.addFilter('divi.moduleLibrary.moduleSettings.groups', 'divibooster.timelineScrollStateColors', addScrollStateGroup);
    hooks.addFilter('divi.moduleLibrary.moduleSettings.groups.divi.timeline', 'divibooster.timelineScrollStateColors', function(groups, metadata) {
        return addScrollStateGroup(groups, Object.assign({}, metadata || {}, { name: timelineSlug }));
    });
    hooks.addFilter('divi.moduleLibrary.moduleSettings.groups.divi.timeline-item', 'divibooster.timelineScrollStateColors', function(groups, metadata) {
        return addScrollStateGroup(groups, Object.assign({}, metadata || {}, { name: timelineItemSlug }));
    });

    function nestedValue(object, path) {
        var value = object;
        for (var i = 0; i < path.length; i++) {
            if (!value || typeof value !== 'object' || !Object.prototype.hasOwnProperty.call(value, path[i])) {
                return null;
            }
            value = value[path[i]];
        }
        return value;
    }

    function hasResponsiveValue(value) {
        if (value === null || value === undefined) {
            return false;
        }
        return !(typeof value === 'string' && value.trim() === '');
    }

    function getResponsiveAttrValue(attr, breakpoint, paths) {
        var orderedPaths = [];
        var i;

        if (breakpoint && breakpoint !== 'desktop') {
            for (i = 0; i < paths.length; i++) {
                orderedPaths.push(paths[i](breakpoint));
            }
        }
        for (i = 0; i < paths.length; i++) {
            orderedPaths.push(paths[i]('desktop'));
        }

        for (i = 0; i < orderedPaths.length; i++) {
            var value = nestedValue(attr, orderedPaths[i]);
            if (hasResponsiveValue(value)) {
                return value;
            }
        }

        return '';
    }

    function contextAttrs(ctx) {
        return (ctx && ctx.attrs) || ctx || {};
    }

    function contextBreakpoint(ctx) {
        var candidates = [
            ctx && ctx.breakpoint,
            ctx && ctx.breakpointName,
            ctx && ctx.activeBreakpoint,
            ctx && ctx.currentBreakpoint,
            ctx && ctx.responsiveState,
            ctx && ctx.device,
            ctx && ctx.state && ctx.state.breakpoint,
            ctx && ctx.state && ctx.state.activeBreakpoint,
            ctx && ctx.context && ctx.context.breakpoint,
            ctx && ctx.context && ctx.context.activeBreakpoint
        ];

        for (var i = 0; i < candidates.length; i++) {
            if (typeof candidates[i] === 'string' && candidates[i]) {
                return candidates[i];
            }
        }

        return getCurrentBreakpointName();
    }

    function getCurrentBreakpointName() {
        var names = Object.keys(breakpointSettings || {});
        var width = window.innerWidth || document.documentElement.clientWidth || 0;
        var fallback = breakpointSettings && breakpointSettings.desktop ? 'desktop' : (names[0] || 'desktop');

        names.sort(function(a, b) {
            return ((breakpointSettings[a] && breakpointSettings[a].order) || 0) - ((breakpointSettings[b] && breakpointSettings[b].order) || 0);
        });

        for (var i = 0; i < names.length; i++) {
            if (breakpointMatchesWidth(breakpointSettings[names[i]], width)) {
                return names[i];
            }
        }

        return fallback;
    }

    function breakpointMatchesWidth(settings, width) {
        if (!settings) {
            return false;
        }

        var min = parsePixelValue(settings.minWidth && settings.minWidth.value);
        var max = parsePixelValue(settings.maxWidth && settings.maxWidth.value);

        if (min !== null && width < min) {
            return false;
        }
        if (max !== null && width > max) {
            return false;
        }

        return true;
    }

    function parsePixelValue(value) {
        value = typeof value === 'number' ? String(value) : (typeof value === 'string' ? value.trim() : '');
        if (!value) {
            return null;
        }
        var match = value.match(/^(-?\d+(?:\.\d+)?)px$/i) || value.match(/^(-?\d+(?:\.\d+)?)$/);
        return match ? parseFloat(match[1]) : null;
    }

    function attrSubValue(attrs, attrName, subName, breakpoint) {
        var attr = attrs && attrs[attrName];
        var value = getResponsiveAttrValue(attr, breakpoint || 'desktop', [
            function(bp) { return [bp, 'value', subName]; },
            function(bp) { return [subName, bp, 'value']; }
        ]);

        if (hasResponsiveValue(value)) {
            return value;
        }

        var legacyPaths = [
            ['value', subName],
            [subName, 'value'],
            [subName]
        ];

        for (var i = 0; i < legacyPaths.length; i++) {
            value = nestedValue(attr, legacyPaths[i]);
            if (hasResponsiveValue(value)) {
                return value;
            }
        }

        return '';
    }

    function isEnabled(value) {
        return value === true || value === 1 || value === '1' || value === 'on' || value === 'yes' || value === 'true';
    }

    function timelineDirection(attrs, breakpoint) {
        var timelineAttr = nestedValue(attrs || {}, ['module', 'advanced', 'timeline']);
        var direction = getResponsiveAttrValue(timelineAttr, breakpoint || 'desktop', [
            function(bp) { return [bp, 'value', 'direction']; }
        ]);

        return direction || 'vertical';
    }

    function isScrollStateGroupVisible(ctx) {
        var attrs = contextAttrs(ctx);
        var breakpoint = contextBreakpoint(ctx);

        if (!nestedValue(attrs, ['module', 'advanced', 'timeline'])) {
            return true;
        }

        return timelineDirection(attrs, breakpoint) !== 'horizontal' || isEnabled(attrSubValue(attrs, carouselAttr, 'enabled', breakpoint));
    }

    function applyScrollStateGroupVisibilityCallbacks(moduleDefinition) {
        if (!moduleDefinition || typeof moduleDefinition !== 'object') {
            return;
        }

        set(moduleDefinition, ['metadata', 'callbacks', 'design', groupSlug, 'visible'], function(ctx) {
            return isScrollStateGroupVisible(ctx);
        });
        set(moduleDefinition, ['callbacks', 'design', groupSlug, 'visible'], function(ctx) {
            return isScrollStateGroupVisible(ctx);
        });
    }

    function getBuilderDocuments() {
        var docs = [document];
        try {
            if (window.top && window.top.document && window.top.document !== document) {
                docs.push(window.top.document);
            }
        } catch (e) {}
        return docs;
    }

    function getPanelState(doc) {
        var state = '';
        try {
            state = window.localStorage ? window.localStorage.getItem('dbtlTimelineScrollStatePanel') : '';
        } catch (e) {
            state = '';
        }
        return states.indexOf(state) !== -1 ? state : 'current';
    }

    function setPanelState(doc, state) {
        if (states.indexOf(state) === -1) {
            return;
        }
        try {
            if (window.localStorage) {
                window.localStorage.setItem('dbtlTimelineScrollStatePanel', state);
            }
        } catch (e) {}
        updateStateFieldVisibility(doc);
    }

    function getFieldState(labelText) {
        var match = labelText.match(/^(Previous|Current|Upcoming)\s+(Marker|Card|Connector|Date|Title|Body)\s+(Background|Color)$/i);
        if (!match) {
            return null;
        }
        return {
            state: match[1].toLowerCase(),
            shortLabel: match[2] + ' ' + match[3]
        };
    }

    function createSwitcher(doc) {
        var switcher = doc.createElement('div');
        switcher.className = 'dbtl-scroll-state-switcher';

        var label = doc.createElement('span');
        label.className = 'dbtl-scroll-state-switcher-label';
        label.textContent = 'Timeline State';
        switcher.appendChild(label);

        var buttons = doc.createElement('div');
        buttons.className = 'dbtl-scroll-state-switcher-buttons';
        switcher.appendChild(buttons);

        states.forEach(function(state) {
            var button = doc.createElement('button');
            button.type = 'button';
            button.className = 'dbtl-scroll-state-switcher-button dbtl-scroll-state-switcher-button-' + state;
            button.textContent = labels[state];
            button.addEventListener('mousedown', function(event) {
                event.preventDefault();
                event.stopPropagation();
                setPanelState(doc, state);
            });
            buttons.appendChild(button);
        });

        return switcher;
    }

    function markStateFields(doc) {
        var firstField = null;
        var labelsEls = doc.querySelectorAll('.et-vb-field-label label');

        labelsEls.forEach(function(labelEl) {
            var fieldInfo = getFieldState((labelEl.textContent || '').trim());
            if (!fieldInfo) {
                return;
            }

            var field = labelEl.closest ? labelEl.closest('.et-vb-field') : null;
            if (!field) {
                return;
            }

            field.classList.add('dbtl-state-field', 'dbtl-state-' + fieldInfo.state);
            labelEl.textContent = fieldInfo.shortLabel;
            if (!firstField) {
                firstField = field;
            }
        });

        if (firstField && firstField.parentNode && !firstField.parentNode.querySelector('.dbtl-scroll-state-switcher')) {
            firstField.parentNode.insertBefore(createSwitcher(doc), firstField);
        }
    }

    function ensureBuilderStyles(doc) {
        if (!doc || !doc.head || doc.getElementById('dbtl-scroll-state-colors-builder-style')) {
            return;
        }

        var style = doc.createElement('style');
        style.id = 'dbtl-scroll-state-colors-builder-style';
        style.textContent = [
            '.dbtl-state-field:not(.dbtl-state-field-active){display:none!important;}',
            '.et-vb-field.dbtl-state-field.dbtl-state-field-active{margin-bottom:14px!important;}',
            '.dbtl-scroll-state-switcher{margin:8px 0 14px;}',
            '.dbtl-scroll-state-switcher-label{color:var(--dark-gray,#6c6f76);display:block;font-size:12px;font-weight:600;margin:0 0 7px;}',
            '.dbtl-scroll-state-switcher-buttons{display:grid;gap:6px;grid-template-columns:repeat(3,minmax(0,1fr));}',
            '.dbtl-scroll-state-switcher-button{background:#f5f6f8;border:1px solid #d9dde3;border-radius:4px;color:#3f444b;cursor:pointer;font-size:12px;font-weight:600;line-height:1.15;min-width:0;padding:7px 6px;white-space:normal;}',
            '.dbtl-scroll-state-switcher-button-active{background:var(--app-color,#2b87da);border-color:var(--app-color,#2b87da);color:#fff;}'
        ].join('');
        doc.head.appendChild(style);
    }

    function updateStateFieldVisibility(doc) {
        var activeState = getPanelState(doc);
        doc.body.setAttribute('data-dbtl-scroll-state-panel', activeState);

        doc.querySelectorAll('.dbtl-state-field').forEach(function(field) {
            var isActive = field.classList.contains('dbtl-state-' + activeState);
            field.classList.toggle('dbtl-state-field-active', isActive);
            field.style.display = isActive ? '' : 'none';
        });

        doc.querySelectorAll('.dbtl-scroll-state-switcher-button').forEach(function(button) {
            var isActive = button.classList.contains('dbtl-scroll-state-switcher-button-' + activeState);
            button.classList.toggle('dbtl-scroll-state-switcher-button-active', isActive);
        });
    }

    function updateBuilderFields() {
        getBuilderDocuments().forEach(function(doc) {
            if (!doc || !doc.body) {
                return;
            }
            ensureBuilderStyles(doc);
            markStateFields(doc);
            if (doc.querySelector('.dbtl-state-field')) {
                updateStateFieldVisibility(doc);
            }
        });
    }

    function observeBuilderFields() {
        var scheduled = false;
        var schedule = function() {
            if (scheduled) {
                return;
            }
            scheduled = true;
            (window.requestAnimationFrame || window.setTimeout)(function() {
                scheduled = false;
                updateBuilderFields();
            }, 16);
        };

        getBuilderDocuments().forEach(function(doc) {
            if (!doc || !doc.body || !window.MutationObserver) {
                return;
            }

            new window.MutationObserver(schedule).observe(doc.body, {
                childList: true,
                subtree: true
            });
            doc.addEventListener('click', schedule, true);
        });

        schedule();
    }

    observeBuilderFields();
})();
JS;
}

function register_module_attrs($module_attrs, $filter_args)
{
    $name = isset($filter_args['name']) ? $filter_args['name'] : '';

    if ($name === TIMELINE_SLUG) {
        $module_attrs[LEGACY_MODULE_ATTR] = get_attribute_definition(LEGACY_MODULE_ATTR, false);
    }

    if ($name === TIMELINE_ITEM_SLUG) {
        $module_attrs[LEGACY_ITEM_ATTR] = get_attribute_definition(LEGACY_ITEM_ATTR, true);
    }

    return $module_attrs;
}

function register_block_attrs($settings)
{
    $name = isset($settings['name']) ? $settings['name'] : '';

    if ($name === TIMELINE_SLUG && isset($settings['attributes']) && is_array($settings['attributes'])) {
        $settings['attributes'][LEGACY_MODULE_ATTR] = get_attribute_definition(LEGACY_MODULE_ATTR, false);
    }

    if ($name === TIMELINE_ITEM_SLUG && isset($settings['attributes']) && is_array($settings['attributes'])) {
        $settings['attributes'][LEGACY_ITEM_ATTR] = get_attribute_definition(LEGACY_ITEM_ATTR, true);
    }

    return $settings;
}

function get_attribute_definition($attr_name, $is_item)
{
    $items = array();
    $element_priorities = array(
        'marker' => 10,
        'card' => 20,
        'connector' => 30,
        'date' => 40,
        'title' => 50,
        'body' => 60,
    );

    foreach (legacy_fields() as $sub_name => $field) {
        $label = ucfirst($field['state']) . ' ' . ucfirst($field['element']) . ' ' . $field['label_suffix'];
        $description_prefix = $is_item ? 'Override' : 'Set';
        $description_subject = $is_item ? 'this item is' : 'timeline items are';

        $items['dbTimelineScrollState' . ucfirst($sub_name)] = array(
            'groupSlug' => GROUP_SLUG,
            'attrName' => $attr_name,
            'subName' => $sub_name,
            'label' => $label,
            'description' => $description_prefix . ' the ' . $field['element'] . ' ' . $field['description_property'] . ' while ' . $description_subject . ' in the ' . $field['state'] . ' scroll state.',
            'render' => true,
            'priority' => (isset($element_priorities[$field['element']]) ? $element_priorities[$field['element']] : 90) + array_search($field['state'], scroll_states(), true),
            'features' => array(
                'responsive' => false,
                'hover' => false,
                'sticky' => false,
                'dynamicContent' => array(
                    'type' => 'color',
                ),
                'preset' => 'advanced',
            ),
            'component' => array(
                'type' => 'field',
                'name' => 'divi/color-picker',
            ),
        );
    }

    return array(
        'type' => 'object',
        'settings' => array(
            'innerContent' => array(
                'groupType' => 'group-items',
                'items' => $items,
            ),
        ),
    );
}

function get_scroll_state_breakpoint_settings()
{
    $class = '\\ET\\Builder\\Framework\\Breakpoint\\Breakpoint';
    if (class_exists($class) && is_callable(array($class, 'get_style_breakpoint_settings'))) {
        $settings = call_user_func(array($class, 'get_style_breakpoint_settings'));
        if (is_array($settings) && !empty($settings)) {
            return $settings;
        }
    }

    return array(
        'phone' => array(
            'order' => 10,
            'maxWidth' => array('value' => '767px'),
        ),
        'tablet' => array(
            'order' => 30,
            'minWidth' => array('value' => '768px'),
            'maxWidth' => array('value' => '980px'),
        ),
        'desktop' => array(
            'order' => 50,
            'baseDevice' => true,
            'minWidth' => array('value' => '981px'),
        ),
    );
}

function filter_timeline_output($block_content, $parsed_block, $block)
{
    if (!is_divi5_active() || !is_string($block_content)) {
        return $block_content;
    }

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

    if (empty($vars)) {
        return $block_content;
    }

    return add_class_and_style_to_first_tag_with_class($block_content, 'et_pb_timeline', array_merge(array('dbtl-scroll-state-colors'), get_css_var_classes($vars)), $vars);
}

function filter_timeline_item_output($block_content, $parsed_block, $block)
{
    if (!is_divi5_active() || !is_string($block_content)) {
        return $block_content;
    }

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

    if (empty($vars)) {
        return $block_content;
    }

    return add_class_and_style_to_first_tag_with_class($block_content, 'et_pb_timeline_item', array_merge(array('dbtl-scroll-state-colors-item'), get_css_var_classes($vars)), $vars);
}

function get_css_vars_from_attrs($attrs, $legacy_attr_name)
{
    $vars = array_merge(
        get_css_vars_from_background_attrs($attrs),
        get_css_vars_from_legacy_attrs($attrs, $legacy_attr_name)
    );

    return $vars;
}

function get_css_vars_from_background_attrs($attrs)
{
    $vars = array();

    foreach (background_elements() as $element) {
        $background_attr = get_nested_array_value($attrs, array($element, 'decoration', 'background'));

        if (!is_array($background_attr)) {
            continue;
        }

        foreach (scroll_states() as $state) {
            $color = sanitize_css_color(get_background_state_color($background_attr, $state));

            if ($color === '' || is_unset_color_placeholder($color)) {
                continue;
            }

            $vars['--dbtl-' . $state . '-' . $element . '-bg'] = $color;
        }
    }

    return $vars;
}

function get_css_vars_from_legacy_attrs($attrs, $legacy_attr_name)
{
    $vars = array();

    foreach (legacy_fields() as $sub_name => $field) {
        $value = get_attr_sub_value($attrs, $legacy_attr_name, $sub_name);
        $color = sanitize_css_color($value);

        if ($color === '' || is_unset_color_placeholder($color)) {
            continue;
        }

        $vars['--dbtl-' . $field['state'] . '-' . $field['element'] . '-' . $field['var_suffix']] = $color;
    }

    return $vars;
}

function get_background_state_color($background_attr, $state)
{
    $paths = array(
        array('desktop', $state, 'color'),
        array('desktop', $state, 'value', 'color'),
        array('desktop', 'value', $state, 'color'),
        array('value', $state, 'color'),
        array($state, 'color'),
        array($state, 'value', 'color'),
    );

    foreach ($paths as $path) {
        $value = get_nested_array_value($background_attr, $path);
        if (is_string($value) && trim($value) !== '') {
            return $value;
        }
    }

    return '';
}

function get_nested_array_value($array, $path)
{
    $value = $array;

    foreach ($path as $key) {
        if (!is_array($value) || !array_key_exists($key, $value)) {
            return null;
        }
        $value = $value[$key];
    }

    return $value;
}

function get_attr_sub_value($attrs, $attr_name, $sub_name)
{
    if (!isset($attrs[$attr_name]) || !is_array($attrs[$attr_name])) {
        return '';
    }

    $attr = $attrs[$attr_name];

    if (isset($attr['desktop']['value'][$sub_name])) {
        return $attr['desktop']['value'][$sub_name];
    }

    if (isset($attr['value'][$sub_name])) {
        return $attr['value'][$sub_name];
    }

    if (isset($attr[$sub_name]['desktop']['value'])) {
        return $attr[$sub_name]['desktop']['value'];
    }

    if (isset($attr[$sub_name]['value'])) {
        return $attr[$sub_name]['value'];
    }

    if (isset($attr[$sub_name]) && is_string($attr[$sub_name])) {
        return $attr[$sub_name];
    }

    return '';
}

function sanitize_css_color($value)
{
    $value = is_string($value) ? trim($value) : '';
    if ($value === '') {
        return '';
    }

    if (function_exists('safecss_filter_attr')) {
        $filtered = \safecss_filter_attr('color:' . $value);
        if (preg_match('/(?:^|;)\\s*color\\s*:\\s*([^;]+)\\s*;?$/i', $filtered, $matches)) {
            return trim($matches[1]);
        }
        return '';
    }

    return preg_match('/^(#[0-9a-f]{3,8}|rgba?\\([0-9.,%\\s]+\\)|hsla?\\([0-9.,%\\s]+\\)|[a-z]+)$/i', $value) ? $value : '';
}

function is_unset_color_placeholder($color)
{
    return is_string($color) && preg_match('/^rgba\\(\\s*255\\s*,\\s*255\\s*,\\s*255\\s*,\\s*0(?:\\.0+)?\\s*\\)$/i', trim($color));
}

function css_vars_to_style($vars)
{
    $style = '';

    foreach ($vars as $name => $value) {
        if (!preg_match('/^--dbtl-[a-z-]+$/', $name)) {
            continue;
        }
        $style .= $name . ':' . $value . ';';
    }

    return $style;
}

function get_css_var_classes($vars)
{
    $classes = array();

    foreach (array_keys($vars) as $name) {
        if (preg_match('/^--dbtl-([a-z-]+)$/', $name, $matches)) {
            $classes[] = 'dbtl-has-' . $matches[1];
        }
    }

    return $classes;
}

function normalize_css_classes($classes)
{
    $classes = is_array($classes) ? $classes : array($classes);
    $normalized = array();

    foreach ($classes as $class) {
        $class = is_string($class) ? trim($class) : '';
        if ($class === '' || !preg_match('/^[a-z0-9_-]+$/i', $class)) {
            continue;
        }
        $normalized[] = $class;
    }

    return array_values(array_unique($normalized));
}

function add_class_and_style_to_first_tag_with_class($html, $target_class, $class_to_add, $vars)
{
    $style = css_vars_to_style($vars);
    $classes_to_add = normalize_css_classes($class_to_add);

    if ($style === '') {
        return $html;
    }

    if (class_exists('\\WP_HTML_Tag_Processor')) {
        $processor = new \WP_HTML_Tag_Processor($html);
        if ($processor->next_tag(array('class_name' => $target_class))) {
            foreach ($classes_to_add as $class) {
                $processor->add_class($class);
            }
            $existing_style = (string) $processor->get_attribute('style');
            $processor->set_attribute('style', trim($existing_style . ';' . $style, ';'));
            return $processor->get_updated_html();
        }
    }

    $class_pattern = preg_quote($target_class, '/');
    $tag_pattern = "~<([a-z0-9]+)\\b([^>]*\\bclass=([\"'])(?=[^\"']*\\b{$class_pattern}\\b)([^\"']*)\\3[^>]*)>~i";
    $class_attr_pattern = "~\\bclass=([\"'])([^\"']*)\\1~i";
    $style_attr_pattern = "~\\bstyle=([\"'])([^\"']*)\\1~i";

    return preg_replace_callback(
        $tag_pattern,
        function ($matches) use ($classes_to_add, $style, $class_attr_pattern, $style_attr_pattern) {
            $tag = $matches[0];
            $missing_classes = array();

            foreach ($classes_to_add as $class) {
                if (!preg_match('/\\b' . preg_quote($class, '/') . '\\b/', $matches[4])) {
                    $missing_classes[] = $class;
                }
            }

            if (!empty($missing_classes)) {
                $tag = preg_replace($class_attr_pattern, 'class=$1$2 ' . esc_attr(implode(' ', $missing_classes)) . '$1', $tag, 1);
            }

            if (preg_match($style_attr_pattern, $tag)) {
                return preg_replace($style_attr_pattern, 'style=$1$2;' . esc_attr($style) . '$1', $tag, 1);
            }

            return preg_replace('/>$/', ' style="' . esc_attr($style) . '">', $tag, 1);
        },
        $html,
        1
    );
}

function output_runtime_assets()
{
    if (!is_divi5_active()) {
        return;
    }
    $timeline_slug = wp_json_encode(TIMELINE_SLUG);
    $timeline_item_slug = wp_json_encode(TIMELINE_ITEM_SLUG);
    $module_attr = wp_json_encode(LEGACY_MODULE_ATTR);
    $item_attr = wp_json_encode(LEGACY_ITEM_ATTR);
    ?>
    <style id="dbtl-scroll-state-colors-style">
        .et_pb_timeline.dbtl-has-previous-marker-bg .et_pb_timeline_item.dbtl-state-previous .et_pb_timeline_marker,
        .et_pb_timeline .et_pb_timeline_item.dbtl-has-previous-marker-bg.dbtl-state-previous .et_pb_timeline_marker {
            background-color: var(--dbtl-previous-marker-bg) !important;
        }
        .et_pb_timeline.dbtl-has-current-marker-bg .et_pb_timeline_item.dbtl-state-current .et_pb_timeline_marker,
        .et_pb_timeline .et_pb_timeline_item.dbtl-has-current-marker-bg.dbtl-state-current .et_pb_timeline_marker {
            background-color: var(--dbtl-current-marker-bg) !important;
        }
        .et_pb_timeline.dbtl-has-upcoming-marker-bg .et_pb_timeline_item.dbtl-state-upcoming .et_pb_timeline_marker,
        .et_pb_timeline .et_pb_timeline_item.dbtl-has-upcoming-marker-bg.dbtl-state-upcoming .et_pb_timeline_marker {
            background-color: var(--dbtl-upcoming-marker-bg) !important;
        }
        .et_pb_timeline.dbtl-has-previous-card-bg .et_pb_timeline_item.dbtl-state-previous .et_pb_timeline_card,
        .et_pb_timeline .et_pb_timeline_item.dbtl-has-previous-card-bg.dbtl-state-previous .et_pb_timeline_card {
            background-color: var(--dbtl-previous-card-bg) !important;
        }
        .et_pb_timeline.dbtl-has-current-card-bg .et_pb_timeline_item.dbtl-state-current .et_pb_timeline_card,
        .et_pb_timeline .et_pb_timeline_item.dbtl-has-current-card-bg.dbtl-state-current .et_pb_timeline_card {
            background-color: var(--dbtl-current-card-bg) !important;
        }
        .et_pb_timeline.dbtl-has-upcoming-card-bg .et_pb_timeline_item.dbtl-state-upcoming .et_pb_timeline_card,
        .et_pb_timeline .et_pb_timeline_item.dbtl-has-upcoming-card-bg.dbtl-state-upcoming .et_pb_timeline_card {
            background-color: var(--dbtl-upcoming-card-bg) !important;
        }
        .et_pb_timeline.dbtl-has-previous-connector-bg .et_pb_timeline_item.dbtl-state-previous .et_pb_timeline_connector,
        .et_pb_timeline .et_pb_timeline_item.dbtl-has-previous-connector-bg.dbtl-state-previous .et_pb_timeline_connector {
            background-color: var(--dbtl-previous-connector-bg) !important;
        }
        .et_pb_timeline.dbtl-has-current-connector-bg .et_pb_timeline_item.dbtl-state-current .et_pb_timeline_connector,
        .et_pb_timeline .et_pb_timeline_item.dbtl-has-current-connector-bg.dbtl-state-current .et_pb_timeline_connector {
            background-color: var(--dbtl-current-connector-bg) !important;
        }
        .et_pb_timeline.dbtl-has-upcoming-connector-bg .et_pb_timeline_item.dbtl-state-upcoming .et_pb_timeline_connector,
        .et_pb_timeline .et_pb_timeline_item.dbtl-has-upcoming-connector-bg.dbtl-state-upcoming .et_pb_timeline_connector {
            background-color: var(--dbtl-upcoming-connector-bg) !important;
        }
        .et_pb_timeline.dbtl-has-previous-date-color .et_pb_timeline_item.dbtl-state-previous .et_pb_timeline_date,
        .et_pb_timeline .et_pb_timeline_item.dbtl-has-previous-date-color.dbtl-state-previous .et_pb_timeline_date {
            color: var(--dbtl-previous-date-color) !important;
        }
        .et_pb_timeline.dbtl-has-current-date-color .et_pb_timeline_item.dbtl-state-current .et_pb_timeline_date,
        .et_pb_timeline .et_pb_timeline_item.dbtl-has-current-date-color.dbtl-state-current .et_pb_timeline_date {
            color: var(--dbtl-current-date-color) !important;
        }
        .et_pb_timeline.dbtl-has-upcoming-date-color .et_pb_timeline_item.dbtl-state-upcoming .et_pb_timeline_date,
        .et_pb_timeline .et_pb_timeline_item.dbtl-has-upcoming-date-color.dbtl-state-upcoming .et_pb_timeline_date {
            color: var(--dbtl-upcoming-date-color) !important;
        }
        .et_pb_timeline.dbtl-has-previous-title-color .et_pb_timeline_item.dbtl-state-previous .et_pb_timeline_title,
        .et_pb_timeline .et_pb_timeline_item.dbtl-has-previous-title-color.dbtl-state-previous .et_pb_timeline_title {
            color: var(--dbtl-previous-title-color) !important;
        }
        .et_pb_timeline.dbtl-has-current-title-color .et_pb_timeline_item.dbtl-state-current .et_pb_timeline_title,
        .et_pb_timeline .et_pb_timeline_item.dbtl-has-current-title-color.dbtl-state-current .et_pb_timeline_title {
            color: var(--dbtl-current-title-color) !important;
        }
        .et_pb_timeline.dbtl-has-upcoming-title-color .et_pb_timeline_item.dbtl-state-upcoming .et_pb_timeline_title,
        .et_pb_timeline .et_pb_timeline_item.dbtl-has-upcoming-title-color.dbtl-state-upcoming .et_pb_timeline_title {
            color: var(--dbtl-upcoming-title-color) !important;
        }
        .et_pb_timeline.dbtl-has-previous-body-color .et_pb_timeline_item.dbtl-state-previous :where(.et_pb_timeline_content, .et_pb_timeline_content p, .et_pb_timeline_content li),
        .et_pb_timeline .et_pb_timeline_item.dbtl-has-previous-body-color.dbtl-state-previous :where(.et_pb_timeline_content, .et_pb_timeline_content p, .et_pb_timeline_content li) {
            color: var(--dbtl-previous-body-color) !important;
        }
        .et_pb_timeline.dbtl-has-current-body-color .et_pb_timeline_item.dbtl-state-current :where(.et_pb_timeline_content, .et_pb_timeline_content p, .et_pb_timeline_content li),
        .et_pb_timeline .et_pb_timeline_item.dbtl-has-current-body-color.dbtl-state-current :where(.et_pb_timeline_content, .et_pb_timeline_content p, .et_pb_timeline_content li) {
            color: var(--dbtl-current-body-color) !important;
        }
        .et_pb_timeline.dbtl-has-upcoming-body-color .et_pb_timeline_item.dbtl-state-upcoming :where(.et_pb_timeline_content, .et_pb_timeline_content p, .et_pb_timeline_content li),
        .et_pb_timeline .et_pb_timeline_item.dbtl-has-upcoming-body-color.dbtl-state-upcoming :where(.et_pb_timeline_content, .et_pb_timeline_content p, .et_pb_timeline_content li) {
            color: var(--dbtl-upcoming-body-color) !important;
        }
        .et_pb_timeline.dbtl-scroll-state-colors .et_pb_timeline_marker,
        .et_pb_timeline.dbtl-scroll-state-colors .et_pb_timeline_card,
        .et_pb_timeline.dbtl-scroll-state-colors .et_pb_timeline_connector,
        .et_pb_timeline .dbtl-scroll-state-colors-item .et_pb_timeline_marker,
        .et_pb_timeline .dbtl-scroll-state-colors-item .et_pb_timeline_card,
        .et_pb_timeline .dbtl-scroll-state-colors-item .et_pb_timeline_connector {
            transition: background-color 220ms ease;
        }
        .et_pb_timeline.dbtl-scroll-state-colors .et_pb_timeline_date,
        .et_pb_timeline.dbtl-scroll-state-colors .et_pb_timeline_title,
        .et_pb_timeline.dbtl-scroll-state-colors .et_pb_timeline_content,
        .et_pb_timeline .dbtl-scroll-state-colors-item .et_pb_timeline_date,
        .et_pb_timeline .dbtl-scroll-state-colors-item .et_pb_timeline_title,
        .et_pb_timeline .dbtl-scroll-state-colors-item .et_pb_timeline_content {
            transition: color 220ms ease;
        }
        .dbtl-state-field:not(.dbtl-state-field-active) {
            display: none !important;
        }
        .et-vb-field.dbtl-state-field.dbtl-state-field-active {
            margin-bottom: 14px !important;
        }
        .dbtl-scroll-state-switcher {
            margin: 8px 0 14px;
        }
        .dbtl-scroll-state-switcher-label {
            color: var(--dark-gray, #6c6f76);
            display: block;
            font-size: 12px;
            font-weight: 600;
            margin: 0 0 7px;
        }
        .dbtl-scroll-state-switcher-buttons {
            display: grid;
            gap: 6px;
            grid-template-columns: repeat(3, minmax(0, 1fr));
        }
        .dbtl-scroll-state-switcher-button {
            background: #f5f6f8;
            border: 1px solid #d9dde3;
            border-radius: 4px;
            color: #3f444b;
            cursor: pointer;
            font-size: 12px;
            font-weight: 600;
            line-height: 1.15;
            min-width: 0;
            padding: 7px 6px;
            white-space: normal;
        }
        .dbtl-scroll-state-switcher-button-active {
            background: var(--app-color, #2b87da);
            border-color: var(--app-color, #2b87da);
            color: #fff;
        }
    </style>
    <script id="dbtl-scroll-state-colors-script">
        (function() {
            if (window.__dbtlScrollStateColorsInstalled) {
                return;
            }
            window.__dbtlScrollStateColorsInstalled = true;

            var scheduled = false;
            var observed = false;
            var states = ['dbtl-state-previous', 'dbtl-state-current', 'dbtl-state-upcoming'];
            var scrollStates = ['previous', 'current', 'upcoming'];
            var backgroundElements = ['marker', 'card', 'connector'];
            var styleTargets = [
                { element: 'marker', suffix: 'bg' },
                { element: 'card', suffix: 'bg' },
                { element: 'connector', suffix: 'bg' },
                { element: 'date', suffix: 'color' },
                { element: 'title', suffix: 'color' },
                { element: 'body', suffix: 'color' }
            ];
            var timelineSlug = <?php echo $timeline_slug; ?>;
            var timelineItemSlug = <?php echo $timeline_item_slug; ?>;
            var moduleAttr = <?php echo $module_attr; ?>;
            var itemAttr = <?php echo $item_attr; ?>;
            var baseClasses = ['dbtl-scroll-state-colors', 'dbtl-scroll-state-colors-item'];
            var variableNames = [];
            var variableClasses = [];

            for (var variableStateIndex = 0; variableStateIndex < scrollStates.length; variableStateIndex++) {
                for (var variableElementIndex = 0; variableElementIndex < styleTargets.length; variableElementIndex++) {
                    variableNames.push('--dbtl-' + scrollStates[variableStateIndex] + '-' + styleTargets[variableElementIndex].element + '-' + styleTargets[variableElementIndex].suffix);
                    variableClasses.push('dbtl-has-' + scrollStates[variableStateIndex] + '-' + styleTargets[variableElementIndex].element + '-' + styleTargets[variableElementIndex].suffix);
                }
            }

            function getStoreWindow() {
                var candidates = [window];
                try {
                    if (window.parent && window.parent !== window) {
                        candidates.push(window.parent);
                    }
                    if (window.top && window.top !== window && candidates.indexOf(window.top) === -1) {
                        candidates.push(window.top);
                    }
                } catch (e) {}

                for (var i = 0; i < candidates.length; i++) {
                    var candidate = candidates[i];
                    if (candidate && candidate.divi && candidate.divi.data && typeof candidate.divi.data.select === 'function') {
                        return candidate;
                    }
                    if (candidate && candidate.wp && candidate.wp.data && typeof candidate.wp.data.select === 'function') {
                        return candidate;
                    }
                }

                return null;
            }

            function selectStore(storeName) {
                var storeWindow = getStoreWindow();
                var data = storeWindow && storeWindow.divi && storeWindow.divi.data ? storeWindow.divi.data : (storeWindow && storeWindow.wp ? storeWindow.wp.data : null);
                if (!data || typeof data.select !== 'function') {
                    return null;
                }

                try {
                    return data.select(storeName);
                } catch (e) {
                    return null;
                }
            }

            function hasBuilderStore() {
                return !!selectStore('divi/edit-post');
            }

            function toPlainObject(value) {
                if (!value) {
                    return value;
                }
                if (typeof value.asMutable === 'function') {
                    try {
                        return value.asMutable({ deep: true });
                    } catch (e) {}
                }
                if (typeof value.toJS === 'function') {
                    try {
                        return value.toJS();
                    } catch (e) {}
                }
                return value;
            }

            function getModule(moduleId) {
                var editPost = selectStore('divi/edit-post');
                if (!editPost || typeof editPost.getModule !== 'function') {
                    return null;
                }

                try {
                    return toPlainObject(editPost.getModule(moduleId));
                } catch (e) {
                    return null;
                }
            }

            function getModuleName(moduleId) {
                var editPost = selectStore('divi/edit-post');
                if (!editPost) {
                    return '';
                }

                if (typeof editPost.getModuleName === 'function') {
                    try {
                        return editPost.getModuleName(moduleId) || '';
                    } catch (e) {}
                }

                var module = getModule(moduleId);
                return module && module.name ? module.name : '';
            }

            function getModuleAttrs(moduleId) {
                var editPost = selectStore('divi/edit-post');

                if (editPost && typeof editPost.getModuleAttrs === 'function') {
                    try {
                        return toPlainObject(editPost.getModuleAttrs(moduleId)) || {};
                    } catch (e) {}
                }

                var module = getModule(moduleId);
                return toPlainObject(module && module.props ? module.props.attrs : null) || {};
            }

            function closestModuleIdByName(element, moduleName, stopAt) {
                var current = element;
                while (current && current.nodeType === 1 && current !== stopAt) {
                    if (current.hasAttribute && current.hasAttribute('data-id')) {
                        var moduleId = current.getAttribute('data-id');
                        if (moduleId && getModuleName(moduleId) === moduleName) {
                            return moduleId;
                        }
                    }
                    current = current.parentElement;
                }
                return '';
            }

            function getTimelineItemId(item, timeline, timelineId, index) {
                var itemId = closestModuleIdByName(item, timelineItemSlug, timeline);
                if (itemId) {
                    return itemId;
                }

                var timelineModule = getModule(timelineId);
                var children = timelineModule && timelineModule.children;
                if (Array.isArray(children) && children[index]) {
                    return children[index];
                }

                return '';
            }

            function getNestedValue(object, path) {
                var value = object;
                for (var i = 0; i < path.length; i++) {
                    if (!value || typeof value !== 'object' || !Object.prototype.hasOwnProperty.call(value, path[i])) {
                        return null;
                    }
                    value = value[path[i]];
                }
                return value;
            }

            function getAttrSubValue(attrs, attrName, subName) {
                var attr = attrs && attrs[attrName];
                if (!attr || typeof attr !== 'object') {
                    return '';
                }

                var paths = [
                    ['desktop', 'value', subName],
                    ['value', subName],
                    [subName, 'desktop', 'value'],
                    [subName, 'value'],
                    [subName]
                ];

                for (var i = 0; i < paths.length; i++) {
                    var value = getNestedValue(attr, paths[i]);
                    if (typeof value === 'string' && value.trim() !== '') {
                        return value;
                    }
                }

                return '';
            }

            function getBackgroundStateColor(backgroundAttr, state) {
                var paths = [
                    ['desktop', state, 'color'],
                    ['desktop', state, 'value', 'color'],
                    ['desktop', 'value', state, 'color'],
                    ['value', state, 'color'],
                    [state, 'color'],
                    [state, 'value', 'color']
                ];

                for (var i = 0; i < paths.length; i++) {
                    var value = getNestedValue(backgroundAttr, paths[i]);
                    if (typeof value === 'string' && value.trim() !== '') {
                        return value;
                    }
                }

                return '';
            }

            function sanitizeColor(value) {
                value = typeof value === 'string' ? value.trim() : '';
                if (!value || /^rgba\(\s*255\s*,\s*255\s*,\s*255\s*,\s*0(?:\.0+)?\s*\)$/i.test(value)) {
                    return '';
                }
                return /^(#[0-9a-f]{3,8}|rgba?\([0-9.,%\s]+\)|hsla?\([0-9.,%\s]+\)|[a-z]+)$/i.test(value) ? value : '';
            }

            function getCssVarsFromAttrs(attrs, attrName) {
                var vars = {};

                for (var elementIndex = 0; elementIndex < backgroundElements.length; elementIndex++) {
                    var element = backgroundElements[elementIndex];
                    var backgroundAttr = getNestedValue(attrs, [element, 'decoration', 'background']);

                    if (backgroundAttr && typeof backgroundAttr === 'object') {
                        for (var stateIndex = 0; stateIndex < scrollStates.length; stateIndex++) {
                            var state = scrollStates[stateIndex];
                            var backgroundColor = sanitizeColor(getBackgroundStateColor(backgroundAttr, state));
                            if (backgroundColor) {
                                vars['--dbtl-' + state + '-' + element + '-bg'] = backgroundColor;
                            }
                        }
                    }
                }

                for (var legacyStateIndex = 0; legacyStateIndex < scrollStates.length; legacyStateIndex++) {
                    for (var legacyElementIndex = 0; legacyElementIndex < styleTargets.length; legacyElementIndex++) {
                        var legacyState = scrollStates[legacyStateIndex];
                        var legacyElement = styleTargets[legacyElementIndex].element;
                        var legacySuffix = styleTargets[legacyElementIndex].suffix;
                        var subName = legacyState + legacyElement.charAt(0).toUpperCase() + legacyElement.slice(1);
                        var legacyColor = sanitizeColor(getAttrSubValue(attrs, attrName, subName));
                        if (legacyColor) {
                            vars['--dbtl-' + legacyState + '-' + legacyElement + '-' + legacySuffix] = legacyColor;
                        }
                    }
                }

                return vars;
            }

            function applyCssVars(element, baseClass, vars) {
                var hasVars = false;
                var desiredClasses = {};

                if (!element || !element.classList || !element.style) {
                    return false;
                }

                for (var baseIndex = 0; baseIndex < baseClasses.length; baseIndex++) {
                    if (baseClasses[baseIndex] !== baseClass && element.classList.contains(baseClasses[baseIndex])) {
                        element.classList.remove(baseClasses[baseIndex]);
                    }
                }

                Object.keys(vars).forEach(function(variableName) {
                    var className = 'dbtl-has-' + variableName.replace(/^--dbtl-/, '');
                    hasVars = true;
                    desiredClasses[className] = true;

                    if (element.style.getPropertyValue(variableName) !== vars[variableName]) {
                        element.style.setProperty(variableName, vars[variableName]);
                    }
                });

                for (var classIndex = 0; classIndex < variableClasses.length; classIndex++) {
                    var variableClass = variableClasses[classIndex];
                    if (desiredClasses[variableClass]) {
                        if (!element.classList.contains(variableClass)) {
                            element.classList.add(variableClass);
                        }
                    } else if (element.classList.contains(variableClass)) {
                        element.classList.remove(variableClass);
                    }
                }

                for (var variableIndex = 0; variableIndex < variableNames.length; variableIndex++) {
                    var knownVariableName = variableNames[variableIndex];
                    if (!Object.prototype.hasOwnProperty.call(vars, knownVariableName) && element.style.getPropertyValue(knownVariableName) !== '') {
                        element.style.removeProperty(knownVariableName);
                    }
                }

                if (hasVars && !element.classList.contains(baseClass)) {
                    element.classList.add(baseClass);
                } else if (!hasVars && element.classList.contains(baseClass)) {
                    element.classList.remove(baseClass);
                }

                return hasVars;
            }

            function syncBuilderPreviewStyles() {
                var editPost = selectStore('divi/edit-post');
                if (!editPost || !document.querySelector) {
                    return;
                }

                var timelines = document.querySelectorAll('.et_pb_timeline');
                for (var i = 0; i < timelines.length; i++) {
                    var timeline = timelines[i];
                    var timelineId = closestModuleIdByName(timeline, timelineSlug, null);

                    if (!timelineId) {
                        continue;
                    }

                    applyCssVars(timeline, 'dbtl-scroll-state-colors', getCssVarsFromAttrs(getModuleAttrs(timelineId), moduleAttr));

                    var items = timeline.querySelectorAll('.et_pb_timeline_item');
                    for (var j = 0; j < items.length; j++) {
                        var item = items[j];
                        var itemId = getTimelineItemId(item, timeline, timelineId, j);
                        if (!itemId || getModuleName(itemId) !== timelineItemSlug) {
                            applyCssVars(item, 'dbtl-scroll-state-colors-item', {});
                            continue;
                        }
                        applyCssVars(item, 'dbtl-scroll-state-colors-item', getCssVarsFromAttrs(getModuleAttrs(itemId), itemAttr));
                    }
                }
            }

            function timelineHasColors(timeline) {
                return timeline.classList.contains('dbtl-scroll-state-colors') ||
                    !!timeline.querySelector('.dbtl-scroll-state-colors-item');
            }

            function isVerticalTimeline(timeline) {
                var track = timeline.querySelector('.et_pb_timeline_track');
                if (!track || !window.getComputedStyle) {
                    return true;
                }
                return window.getComputedStyle(track).getPropertyValue('flex-direction').indexOf('column') === 0;
            }

            function setItemState(item, stateClass) {
                var currentState = item.classList.contains('dbtl-state-current') ? 'dbtl-state-current' :
                    (item.classList.contains('dbtl-state-previous') ? 'dbtl-state-previous' :
                    (item.classList.contains('dbtl-state-upcoming') ? 'dbtl-state-upcoming' : ''));

                if (currentState === stateClass) {
                    return;
                }

                for (var i = 0; i < states.length; i++) {
                    if (states[i] !== stateClass && item.classList.contains(states[i])) {
                        item.classList.remove(states[i]);
                    }
                }
                if (stateClass && !item.classList.contains(stateClass)) {
                    item.classList.add(stateClass);
                }
            }

            function isCarouselTimeline(timeline) {
                return timeline && timeline.classList && timeline.classList.contains('dbtl-horizontal-carousel');
            }

            function getCarouselCurrentIndex(timeline, items) {
                var index = parseInt(timeline.getAttribute('data-dbtl-carousel-current-index') || '0', 10);
                if (!isFinite(index)) {
                    index = 0;
                }
                return Math.max(0, Math.min(Math.max(0, items.length - 1), index));
            }

            function setCarouselItemStates(timeline, items) {
                var currentIndex = getCarouselCurrentIndex(timeline, items);
                for (var i = 0; i < items.length; i++) {
                    if (i < currentIndex) {
                        setItemState(items[i], 'dbtl-state-previous');
                    } else if (i > currentIndex) {
                        setItemState(items[i], 'dbtl-state-upcoming');
                    } else {
                        setItemState(items[i], 'dbtl-state-current');
                    }
                }
            }

            function update() {
                scheduled = false;
                if (hasBuilderStore()) {
                    syncBuilderPreviewStyles();
                }
                var mid = window.innerHeight / 2;
                var timelines = document.querySelectorAll('.et_pb_timeline');

                for (var i = 0; i < timelines.length; i++) {
                    var timeline = timelines[i];
                    if (!timelineHasColors(timeline)) {
                        continue;
                    }

                    var items = timeline.querySelectorAll('.et_pb_timeline_item');
                    var isVertical = isVerticalTimeline(timeline);

                    if (!isVertical && isCarouselTimeline(timeline)) {
                        setCarouselItemStates(timeline, items);
                        continue;
                    }

                    for (var j = 0; j < items.length; j++) {
                        var item = items[j];
                        if (!isVertical) {
                            setItemState(item, '');
                            continue;
                        }

                        var rect = item.getBoundingClientRect();
                        if (rect.bottom < mid) {
                            setItemState(item, 'dbtl-state-previous');
                        } else if (rect.top > mid) {
                            setItemState(item, 'dbtl-state-upcoming');
                        } else {
                            setItemState(item, 'dbtl-state-current');
                        }
                    }
                }
            }

            function schedule() {
                if (scheduled) {
                    return;
                }
                scheduled = true;
                if (window.requestAnimationFrame) {
                    window.requestAnimationFrame(update);
                } else {
                    window.setTimeout(update, 16);
                }
            }

            function observeBuilderRenders() {
                if (observed || !hasBuilderStore() || !window.MutationObserver || !document.body) {
                    return;
                }
                observed = true;
                new MutationObserver(function(mutations) {
                    for (var i = 0; i < mutations.length; i++) {
                        if (mutations[i].type === 'childList') {
                            schedule();
                            return;
                        }
                    }
                }).observe(document.body, {
                    childList: true,
                    subtree: true
                });
            }

            window.addEventListener('scroll', schedule, { passive: true });
            window.addEventListener('resize', schedule);
            document.addEventListener('DOMContentLoaded', schedule);
            observeBuilderRenders();
            schedule();
            window.setTimeout(schedule, 250);
        })();
    </script>
    <?php
}
