<?php
namespace DiviBooster\DiviBooster\Features\SocialMediaFollowNetworks\DiviIcons;

/**
 * Divi 5 Compatibility Strategy:
 * 
 * The Divi 5 "Social Media Follow" module uses a hardcoded list of networks inside a closure within its 
 * `onChange` handler. This makes it impossible to simply add new options to the list and have them work, 
 * as the handler won't recognize the new values to retrieve their label and background color.
 * 
 * Additionally, the `wp.data` store is not fully available within the builder iframe context, preventing 
 * us from dispatching standard update actions.
 * 
 * To overcome this, we use a "Prototype Injection" strategy:
 * 1. We inject our custom network options into the module's settings group so they appear in the UI.
 * 2. We patch the module's `onChange` handler for the network selector.
 * 3. When a custom network is selected, our wrapper temporarily injects the network data (label, color) 
 *    into `Object.prototype` using the network slug as the key.
 * 4. We then call the original `onChange` handler. When it tries to look up the network in its hardcoded 
 *    list (e.g., `List[slug]`), it fails, falls back to the prototype chain, and finds our injected data.
 * 5. We immediately clean up `Object.prototype` to avoid side effects.
 */

// Divi 4 and Divi 5 module slugs
const D4_CHILD_SLUG = 'et_pb_social_media_follow_network';
const D5_PARENT_SLUG = 'divi/social-media-follow';
const D5_CHILD_SLUG  = 'divi/social-media-follow-network';

// Register hooks
add_filter('et_pb_all_fields_unprocessed_' . D4_CHILD_SLUG, __NAMESPACE__ . '\\add_divi4_network_options');
add_filter('et_module_shortcode_output', __NAMESPACE__ . '\\divi4_set_frontend_styles', 10, 3);
add_filter('et_module_shortcode_output', __NAMESPACE__ . '\\divi4_replace_network_names_in_output', 11, 3);
add_action('wp_enqueue_scripts', __NAMESPACE__ . '\\enqueue_divi5_builder_script');
add_filter('render_block_' . D5_CHILD_SLUG, __NAMESPACE__ . '\\divi5_child_render_block', 10, 3);


// === High-level helpers ===
function get_networks() {
    // Keys include the dbdb- prefix to avoid collisions with native networks
    return [
        'dbdb-phone' => [
            'name'         => 'Phone',
            'code'         => '\\e090',
            'color'        => '#58a9de',
            'font-family'  => 'ETModules',
        ],
        'dbdb-podcast' => [
            'name'         => 'Podcast',
            'code'         => '\\e01b',
            'color'        => '#58a9de',
            'font-family'  => 'ETModules',
        ],
        'dbdb-website' => [
            'name'         => 'Website',
            'code'         => '\\e0e3',
            'color'        => '#58a9de',
            'font-family'  => 'ETModules',
        ],
    ];
}

function get_network_by_id($id) {
    $networks = get_networks();
    return $networks[$id] ?? null;
}

// === Divi 4: Add our networks to the Social Network selector ===
function add_divi4_network_options($fields) {
    if (!isset($fields['social_network'])) {
        return $fields;
    }

    $fields['social_network'] = (new SocialNetworksField($fields['social_network']))
        ->add_missing_options(get_networks())
        ->sort_alphabetically()
        ->get_field();

    return $fields;
}

// === Divi 4: Per-item icon styling (scoped to child item order class) ===
function divi4_set_frontend_styles($output, $render_slug, $module) {
    if (!is_string($output) || $render_slug !== D4_CHILD_SLUG || !isset($module->props)) {
        return $output;
    }

    $id = $module->props['social_network'] ?? '';
    $network = get_network_by_id($id);
    if (!$network || empty($network['code'])) {
        return $output;
    }

    if (class_exists('ET_Builder_Element')) {
        \ET_Builder_Element::set_style($render_slug, [
            'selector'    => '%%order_class%% a.icon:before',
            'declaration' => 'content: "' . esc_attr($network['code']) . '";font-family: "' . esc_attr($network['font-family']) . '" !important;'
        ]);
    }

    return $output;
}

// === Divi 4: Replace network slugs in title attributes with readable names ===
function divi4_replace_network_names_in_output($output, $render_slug, $module) {
    if (!is_string($output) || $render_slug !== D4_CHILD_SLUG || !isset($module->props)) {
        return $output;
    }

    foreach (get_networks() as $id => $network) {
        $name = $network['name'] ?? '';
        if ($name === '') continue;
        $slug = preg_quote($id, '/');
        // Replace occurrences of the slug inside title attributes with the human name
        $output = preg_replace('/title="([^"]*)' . $slug . '([^"]*)"/i', 'title="$1' . esc_attr($name) . '$2"', $output);
        $output = preg_replace("/title='([^']*)" . $slug . "([^']*)'/i", "title='$1" . esc_attr($name) . "$2'", $output);
    }

    return $output;
}

// === Divi 5: Enqueue builder JS to inject extra network options in the child UI ===
function enqueue_divi5_builder_script() {
    // Check for Divi 5. We'll be less strict about the Visual Builder check for debugging purposes
    // to ensure the script is at least enqueued.
    if (!function_exists('et_builder_d5_enabled') || !\et_builder_d5_enabled()) {
        return;
    }

    $handle = 'divibooster-d5-sfnetworks-divi-icons';
    wp_register_script($handle, '', ['jquery'], null, true);
    wp_enqueue_script($handle);

    $networks = get_networks();
    $js_networks = [];
    foreach ($networks as $id => $n) {
        // Divi 5 expects { label: '...', background: '...' } in the options object
        $js_networks[$id] = [ 
            'label' => $n['name'],
            'background' => $n['color'] ?? '#58a9de'
        ];
    }

    $networks_json = wp_json_encode($js_networks);

    $inline = <<<JS
(function($){
    $(function(){
        var customNetworks = {$networks_json};
        
        var attempts = 0;
        var maxAttempts = 100; // 10 seconds
        
        var interval = setInterval(function() {
            attempts++;
            
            // Check for Divi 5 global
            if (window.divi && window.divi.moduleLibrary) {
                clearInterval(interval);
                initDiviBoosterIcons();
            } else if (attempts >= maxAttempts) {
                clearInterval(interval);
            }
        }, 100);

        function initDiviBoosterIcons() {
            // Locate wp.hooks
            var hooks = window.wp && window.wp.hooks;
            if (!hooks && window.vendor && window.vendor.wp) {
                hooks = window.vendor.wp.hooks;
            }
            
            if (!hooks) {
                return;
            }

            var moduleName = "divi/social-media-follow-network";

            // 1. Attempt to patch via filter (Early binding)
            hooks.addFilter('divi.moduleLibrary.moduleSettings.groups', 'divibooster', function(groups, metadata){
                if(metadata && metadata.name === moduleName){
                    injectOptionsIntoGroups(groups);
                }
                return groups;
            });

            // 2. Attempt to patch existing module (Late binding)
            var patchAttempts = 0;
            var patchInterval = setInterval(function() {
                patchAttempts++;
                if (patchAttempts > 40) { // Stop after 20 seconds
                    clearInterval(patchInterval);
                    return;
                }

                // Strategy A: Check moduleMapping directly (since getModule didn't exist on the library object)
                if (window.divi && window.divi.moduleLibrary && window.divi.moduleLibrary.moduleMapping) {
                    var module = window.divi.moduleLibrary.moduleMapping[moduleName];
                    if (module) {
                        clearInterval(patchInterval);
                        patchModule(module);
                        return;
                    }
                }

                // Strategy B: Check wp.data store
                if (window.wp && window.wp.data) {
                    var libStore = window.wp.data.select('divi/module-library');
                    if (libStore && typeof libStore.getModule === 'function') {
                        var module = libStore.getModule(moduleName);
                        if (module) {
                            clearInterval(patchInterval);
                            patchModule(module);
                            return;
                        }
                    }
                }

            }, 500);

            function injectOptionsIntoGroups(groups) {
                try {
                    // Path: contentNetwork.component.props.fields.socialNetworkTitle.component.props.options
                    var path = ["contentNetwork", "component", "props", "fields", "socialNetworkTitle", "component", "props", "options"];
                    var current = groups;
                    for (var i = 0; i < path.length; i++) {
                        if (current && current[path[i]]) {
                            current = current[path[i]];
                        } else {
                            return; // Path not found
                        }
                    }
                    
                    // Merge options
                    Object.assign(current, customNetworks);

                    // Sort options alphabetically, keeping 'Select a Network' (empty key) at the top
                    var sorted = {};
                    var keys = Object.keys(current).sort(function(a, b) {
                        // Keep empty key (placeholder) at the top
                        if (a === '') return -1;
                        if (b === '') return 1;
                        
                        // Get labels for comparison
                        var labelA = current[a].label || a;
                        var labelB = current[b].label || b;
                        
                        return labelA.localeCompare(labelB);
                    });

                    keys.forEach(function(key) {
                        sorted[key] = current[key];
                    });

                    // Replace the options object content with sorted content
                    // We can't replace the object reference itself, so we clear and refill it
                    for (var key in current) {
                        if (current.hasOwnProperty(key)) {
                            delete current[key];
                        }
                    }
                    Object.assign(current, sorted);

                } catch (e) {
                    // Silent fail
                }
            }

            function patchModule(module) {
                // Prevent double-patching
                if (module._diviBoosterPatched) {
                    return;
                }
                module._diviBoosterPatched = true;

                // 1. Patch settings.content
                // The module uses a function for settings.content which resets the options every time.
                // We wrap it to re-inject our options after it runs.
                if (module.settings && typeof module.settings.content === 'function') {
                    var originalContent = module.settings.content;
                    module.settings.content = function(args) {
                        // Run original function (which might reset options)
                        var result = originalContent(args);
                        
                        // Re-inject our options into the configuration
                        if (args && args.groupConfiguration) {
                            injectOptionsIntoGroups(args.groupConfiguration);
                        }
                        
                        return result;
                    };
                }

                // 2. Patch callbacks.content...onChange
                // The default onChange handler uses a hardcoded list. We replace it.
                try {
                    var callbacks = module.callbacks;
                    if (callbacks && callbacks.content && callbacks.content.contentNetwork && 
                        callbacks.content.contentNetwork.fields && 
                        callbacks.content.contentNetwork.fields.socialNetworkTitle) {
                        
                        var field = callbacks.content.contentNetwork.fields.socialNetworkTitle;
                        
                        // Save original handler
                        var originalOnChange = callbacks.content.contentNetwork.fields.socialNetworkTitle.onChange;

                        // Replace the onChange handler
                        // We use a closure variable to track execution state to prevent recursion
                        var isExecuting = false;

                        field.onChange = function(e) {
                            // Prevent infinite recursion
                            if (isExecuting) {
                                return;
                            }
                            isExecuting = true;
                            
                            var inputValue = (e && e.inputValue) ? e.inputValue : null;
                            var network = inputValue ? customNetworks[inputValue] : null;
                            
                            if (network) {
                                Object.prototype[inputValue] = network;
                            }

                            var result;
                            try {
                                if (typeof originalOnChange === 'function') {
                                    // Call original handler
                                    // We use .call(this, e) to pass the context and the event object
                                    result = originalOnChange.call(this, e);
                                }
                            } catch (err) {
                                // Silent fail
                            } finally {
                                // Cleanup
                                if (network) {
                                    delete Object.prototype[inputValue];
                                }
                                isExecuting = false;
                            }
                            return result;
                        };
                    }
                } catch (err) {
                    // Silent fail
                }
            }
        }
    });
})(jQuery);
JS;

    wp_add_inline_script($handle, $inline);
}

// === Divi 5: Child render filter (ensure any needed adjustments are applied) ===
function divi5_child_render_block($block_content, $parsed_block, $block) {
    // Replace title slugs with readable names in output if present
    foreach (get_networks() as $id => $network) {
        $name = $network['name'] ?? '';
        if ($name === '') continue;
        $slug = '/' . preg_quote($id, '/') . '/i';
        // Replace occurrences inside title attributes
        $block_content = preg_replace('/title="([^"]*)' . preg_quote($id, '/') . '([^"]*)"/i', 'title="$1' . esc_attr($name) . '$2"', $block_content);
        $block_content = preg_replace("/title='([^']*)" . preg_quote($id, '/') . "([^']*)'/i", "title='$1" . esc_attr($name) . "$2'", $block_content);
    }
    return $block_content;
}


// === Utility: Output minimal global icon CSS (frontend & builder) ===
add_action('wp_head', __NAMESPACE__ . '\\output_global_icon_css');
function output_global_icon_css() {
    $rules = [];
    foreach (get_networks() as $id => $network) {
        $content = 'content:"' . esc_attr($network['code']) . '";font-family:"' . esc_attr($network['font-family']) . '" !important;';
        $rules[] = '.et-social-' . esc_attr($id) . ' a.icon:before{' . $content . '}';
        $rules[] = '.et-db #et-boc .et-l .et_pb_social_icon.et-social-' . esc_attr($id) . ' a.icon:before{' . $content . '}';
        // Divi 5 specific selector
        $rules[] = '.et_pb_social_icon.et-social-' . esc_attr($id) . ' a.icon:before{' . $content . '}';
    }
    if ($rules) {
        echo '<style>' . implode('', $rules) . '</style>';
    }
}

// === Helper class for Divi 4 field augmentation ===
class SocialNetworksField {
    private $field;
    public function __construct($field) {
        $this->field = is_array($field) ? $field : [];
        if (!isset($this->field['options']) || !is_array($this->field['options'])) {
            $this->field['options'] = [];
        }
        if (!isset($this->field['value_overwrite']) || !is_array($this->field['value_overwrite'])) {
            $this->field['value_overwrite'] = [];
        }
    }
    public function has_option($network_id) {
        return isset($this->field['options'][$network_id]);
    }
    public function add_option($network_id, $name, $data) {
        $this->field['options'][$network_id] = [
            'value' => $name,
            'data'  => $data,
        ];
        if (isset($data['color'])) {
            $this->field['value_overwrite'][$network_id] = $data['color'];
        }
    }
    public function add_missing_options(array $networks) {
        foreach ($networks as $id => $network) {
            if (!$this->has_option($id)) {
                $this->add_option($id, $network['name'] ?? $id, [ 'color' => $network['color'] ?? '' ]);
            }
        }
        return $this;
    }
    public function sort_alphabetically() {
        if (isset($this->field['options']) && is_array($this->field['options'])) {
            uasort($this->field['options'], function($a, $b){
                $av = isset($a['value']) ? (string)$a['value'] : '';
                $bv = isset($b['value']) ? (string)$b['value'] : '';
                if ($av === '' && $bv === '') return 0;
                if ($av === '') return -1;
                if ($bv === '') return 1;
                return strcasecmp($av, $bv);
            });
        }
        return $this;
    }
    public function get_field() { return $this->field; }
}

// Created at 1765178376
