<?php
namespace DiviBooster\DiviBooster\Modules\Gallery\ParallaxLayout;

// === Constants ===
const DIVI4_MODULE_SLUG              = 'et_pb_gallery';
const DIVI5_MODULE_SLUG              = 'divi/gallery';
const DIVI4_LAYOUT_SETTING_SLUG      = 'fullwidth';
const DIVI4_PARALLAX_LAYOUT_VALUE    = 'db_mouse_parallax';
const DIVI4_PARALLAX_LAYOUT_LABEL    = 'Mouse Parallax';
const DIVI5_SETTING_SLUG             = 'diviboosterMouseParallax';

// === Divi 4: Add "Mouse Parallax" layout option to Gallery Layout select ===
add_filter('et_pb_all_fields_unprocessed_' . DIVI4_MODULE_SLUG, __NAMESPACE__ . '\add_parallax_layout_option', 30, 1);
function add_parallax_layout_option($fields) {
    if (!isset($fields[DIVI4_LAYOUT_SETTING_SLUG]['options'])) return $fields;
    if (isset($fields[DIVI4_LAYOUT_SETTING_SLUG]['options'][DIVI4_PARALLAX_LAYOUT_VALUE])) return $fields; // avoid duplicate
    $fields[DIVI4_LAYOUT_SETTING_SLUG]['options'][DIVI4_PARALLAX_LAYOUT_VALUE] = DIVI4_PARALLAX_LAYOUT_LABEL;
    asort($fields[DIVI4_LAYOUT_SETTING_SLUG]['options'], SORT_NATURAL | SORT_FLAG_CASE);
    return $fields;
}

// === Divi 4: Apply parallax layout output transformation ===
add_filter('et_module_shortcode_output', __NAMESPACE__ . '\output_gallery_parallax_d4', 20, 3);
function output_gallery_parallax_d4($output, $render_slug, $module) {
    if (!is_string($output) || $render_slug !== DIVI4_MODULE_SLUG || !isset($module->props)) {
        return $output;
    }
    if (($module->props[DIVI4_LAYOUT_SETTING_SLUG] ?? '') !== DIVI4_PARALLAX_LAYOUT_VALUE) {
        return $output;
    }
    list($transformed_html, $order_class) = apply_parallax_transformations($output);
    if (!$order_class) return $output; // couldn't scope, bail safely

    $assets = build_instance_assets($order_class);
    return $transformed_html . $assets['css'] . $assets['js'];
}

// === Divi 5: Register custom attribute (PHP fallback) ===
add_filter('divi_module_library_register_module_attrs', __NAMESPACE__ . '\register_divi5_attrs', 10, 2);
function register_divi5_attrs($module_attrs, $filter_args) {
    // Toggle removed: do not register a custom attribute; rely on the Layout dropdown option only
    return $module_attrs;
}

// === Divi 5: Add VB-only JS to register attribute and conditional conversion mapping ===
add_action('wp_enqueue_scripts', __NAMESPACE__ . '\enqueue_divi5_attr_and_conversion_js');
function enqueue_divi5_attr_and_conversion_js() {
    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('divibooster-d5-gallery-parallax-layout');
    \dbdb_enqueue_inline_script_if_dependencies_available($handle, get_divi5_inline_js(), array('lodash', array('divi-vendor-wp-hooks', 'wp-hooks')));
}

// === Divi 5: PHP conversion outline (auto migration) ===
add_filter('divi.moduleLibrary.conversion.moduleConversionOutline', __NAMESPACE__ . '\register_divi5_conversion_outline', 10, 2);
function register_divi5_conversion_outline($conversion_outline, $module_name) {
    if ($module_name !== DIVI5_MODULE_SLUG) {
        return $conversion_outline;
    }
    if (!isset($conversion_outline['module']) || !is_array($conversion_outline['module'])) {
        $conversion_outline['module'] = array();
    }
    // Map D4 fullwidth -> D5 fullwidth path so the value `db_mouse_parallax` carries across
    if (empty($conversion_outline['module'][DIVI4_LAYOUT_SETTING_SLUG])) {
        $conversion_outline['module'][DIVI4_LAYOUT_SETTING_SLUG] = 'module.advanced.fullwidth.*';
    }
    return $conversion_outline;
}

function convert_fullwidth_value_for_parallax($value) {
    // If Divi 4 fullwidth equals our special value, enable the toggle (on). Otherwise set off.
    return ($value === DIVI4_PARALLAX_LAYOUT_VALUE) ? 'on' : 'off';
}

// === Divi 5: Render filter to apply the parallax layout ===
add_filter('render_block_' . DIVI5_MODULE_SLUG, __NAMESPACE__ . '\render_divi5_gallery_parallax', 10, 3);
function render_divi5_gallery_parallax($block_content, $parsed_block, $block) {
    if (!is_string($block_content)) {
        return $block_content;
    }
    $attrs = $parsed_block['attrs'] ?? array();
    if (!should_enable_parallax($attrs)) {
        return $block_content;
    }
    list($transformed_html, $order_class) = apply_parallax_transformations($block_content);
    if (!$order_class) return $block_content;

    $assets = build_instance_assets($order_class);
    return $transformed_html . $assets['css'] . $assets['js'];
}

// === Shared: Determine if parallax should be enabled (supports D4 and D5 props structures) ===
function should_enable_parallax($props) {
    // Divi 4 flat props (used only in D4 filter, but harmless here)
    if (isset($props[DIVI4_LAYOUT_SETTING_SLUG]) && $props[DIVI4_LAYOUT_SETTING_SLUG] === DIVI4_PARALLAX_LAYOUT_VALUE) {
        return true;
    }
    // Divi 5 layout dropdown (nested path)
    if (isset($props['module']['advanced']['fullwidth']['desktop']['value'])) {
        $v = $props['module']['advanced']['fullwidth']['desktop']['value'];
        if ($v === DIVI4_PARALLAX_LAYOUT_VALUE) return true;
    } elseif (isset($props['module']['advanced']['fullwidth']['value'])) {
        $v = $props['module']['advanced']['fullwidth']['value'];
        if ($v === DIVI4_PARALLAX_LAYOUT_VALUE) return true;
    }
    return false;
}

// === Shared: Transform gallery HTML to parallax layout and extract order class ===
function apply_parallax_transformations($html) {
    // Extract module order class (e.g. et_pb_gallery_2 or et_pb_gallery_2_tb_header)
    $order_class = extract_order_class($html);
    if (!$order_class) {
        return array($html, '');
    }

    // Replace grid class with slider classes (keeps consistency across versions)
    $html = preg_replace('/\bet_pb_gallery_grid\b/', 'et_pb_slider et_pb_gallery_fullwidth', $html);

    // Remove the et_pb_grid_item class from gallery items (if present)
    $html = preg_replace('/\bet_pb_grid_item\b/', '', $html);

    // Unwrap links around images to disable lightbox while preserving overlay and image
    $html = preg_replace_callback(
        '/<div\s+class="et_pb_gallery_image[^"]*">\s*<a\b[^>]*>(.*?)<\/a>\s*<\/div>/is',
        function ($matches) {
            return '<div class="et_pb_gallery_image">' . $matches[1] . '</div>';
        },
        $html
    );

    return array($html, $order_class);
}

// === Shared: Build per-instance CSS & JS ===
function build_instance_assets($order_class) {
    $safe_order_class = preg_replace('/[^a-zA-Z0-9_\-]/', '', $order_class);

    $css = <<<EOCSS
<style>
.{$safe_order_class} { position: relative; overflow: hidden !important; }
.{$safe_order_class} .et_pb_gallery_items { position: relative; }
/* Default to relative so the container retains natural height until JS initializes */
.{$safe_order_class} .et_pb_gallery_item { position: relative !important; display: block !important; }
.{$safe_order_class} .et_pb_gallery_title,
.{$safe_order_class} .et-pb-slider-arrows,
.{$safe_order_class} .et-pb-controllers { display: none !important; }
</style>
EOCSS;

    $js = <<<EOJS
<script>
(function($){
    $(function(){
        var selector = '.{$safe_order_class}';
        var \$gallery = $(selector);
        if(!\$gallery.length) return;

        // Disable motion when hover isn't available (e.g., touch devices)
        var canHover = window.matchMedia ? window.matchMedia('(hover: hover)').matches : true;
        // Even if we don't animate on non-hover devices, keep items relative (CSS) so height is preserved

        var maxDisplacement = 50; // px

        function getRequiredScale(\$el, maxDisp){
            var w = \$el.width();
            var h = \$el.height();
            var scaleX = w > 0 ? w / Math.max(1, (w - 2 * maxDisp)) : 1;
            var scaleY = h > 0 ? h / Math.max(1, (h - 2 * maxDisp)) : 1;
            return Math.max(scaleX, scaleY);
        }

        // Compute and lock container height before switching items to absolute positioning
        function lockContainerHeight(){
            var \$itemsWrap = \$gallery.children('.et_pb_gallery_items');
            if (!\$itemsWrap.length) return false;

            // Measure natural height while items are relative
            var naturalHeight = \$itemsWrap.height();

            // Fallback: compute from images' natural sizes if height seems too small (e.g., 0/1px)
            if (!naturalHeight || naturalHeight < 10) {
                var galleryW = \$gallery.width();
                var est = 0;
                \$gallery.find('.et_pb_gallery_image img').each(function(){
                    var nw = this.naturalWidth || 0;
                    var nh = this.naturalHeight || 0;
                    if (nw > 0 && nh > 0 && galleryW > 0) {
                        est = Math.max(est, nh * (galleryW / nw));
                    }
                });
                if (est > 0) naturalHeight = est;
            }

            if (!naturalHeight || naturalHeight < 10) return false;

            // Lock heights on both wrapper and items container
            \$gallery.css('height', naturalHeight);
            \$itemsWrap.css({ position: 'relative', height: naturalHeight });
            return true;
        }

        function absolutizeItems(){
            \$gallery.find('.et_pb_gallery_item').each(function(){
                $(this).css({ position: 'absolute', top: 0, left: '50%', width: '100%', transform: 'translateX(-50%)' });
            });
        }

        // Initialize layout: lock height then absolutize
        function initLayout(){
            var locked = lockContainerHeight();
            if (!locked) return false;
            absolutizeItems();
            return true;
        }

        // Try to init immediately, then after a short delay (for late image/layout paints)
        var inited = initLayout();
        if (!inited) setTimeout(initLayout, 100);
        if (!inited) setTimeout(initLayout, 250);

        // Recalculate on window load and resize
        $(window).on('load resize', function(){
            // Temporarily reset to relative to re-measure on resize
            \$gallery.find('.et_pb_gallery_item').css({ position: 'relative', top: '', left: '', width: '', transform: '' });
            \$gallery.css('height', '');
            \$gallery.children('.et_pb_gallery_items').css('height', '');
            initLayout();
        });

        // Recalculate when images load (covers async loading)
        \$gallery.find('img').each(function(){
            if (this.complete) return; // already loaded
            $(this).one('load', function(){
                \$gallery.find('.et_pb_gallery_item').css({ position: 'relative', top: '', left: '', width: '', transform: '' });
                \$gallery.css('height', '');
                \$gallery.children('.et_pb_gallery_items').css('height', '');
                initLayout();
            });
        });

        if (!canHover) return; // don't animate on non-hover devices

        $(document).on('mousemove', function(e){
            if (!\$gallery.is(':visible')) return;
            var off = \$gallery.offset();
            if (!off) return;
            var centerX = off.left + \$gallery.width() / 2;
            var centerY = off.top + \$gallery.height() / 2;
            var \$items = \$gallery.find('.et_pb_gallery_item');
            var total = \$items.length;
            var mid = (total - 1) / 2;
            var scale = getRequiredScale(\$gallery, maxDisplacement);

            \$items.each(function(index){
                var rel = index - mid;
                var dir = (rel > 0) ? 1 : -1;
                var factor = mid > 0 ? Math.abs(rel) / mid : 0;
                var dx = ((e.pageX - centerX) / Math.max(1, centerX)) * maxDisplacement * factor * dir;
                var dy = ((e.pageY - centerY) / Math.max(1, centerY)) * maxDisplacement * factor * dir;
                $(this).css({ transform: 'translate(-50%, ' + dy + 'px) translate3d(' + dx + 'px, 0, 0) scale(' + scale + ')' });
            });
        });

        \$gallery.on('mouseleave', function(){
            \$gallery.find('.et_pb_gallery_item').css({ transform: 'translate(-50%, 0) scale(1)' });
        });
    });
})(jQuery);
</script>
EOJS;

    return array('css' => $css, 'js' => $js);
}

// === Utility: Extract module order class from HTML ===
function extract_order_class($html) {
    if (preg_match('/\b(et_pb_gallery_(\d+(?:_[a-z0-9_]+)?))\b/i', $html, $m)) {
        return $m[1];
    }
    return '';
}

// === Divi 5: Attribute definition ===
function get_divi5_attribute_definition() {
    return [
        'type'     => 'object',
        'settings' => [
            'innerContent' => [
                'groupType' => 'group-items',
                'items'     => [
                    'mouseParallaxToggle' => [
                        'groupSlug'   => 'designLayout',
                        'attrName'    => DIVI5_SETTING_SLUG,
                        'label'       => __('Mouse Parallax', 'divi-gallery-booster'),
                        'description' => __('Enable a mouse-move parallax layout for the gallery. Images layer and move subtly with the cursor; titles, captions, arrows and dots are hidden, and image links/lightbox are disabled.', 'divi-gallery-booster'),
                        'features'    => [
                            'hover'      => false,
                            'sticky'     => false,
                            'responsive' => false,
                            'preset'     => 'advanced',
                        ],
                        'options'     => [ 'off' => __('Off', 'et_builder'), 'on' => __('On', 'et_builder') ],
                        'defaultAttr' => [ 'desktop' => [ 'value' => 'off' ] ],
                        'render'      => true,
                        'priority'    => 99,
                        'component'   => [ 'type' => 'field', 'name' => 'divi/toggle' ],
                    ],
                ],
            ],
        ],
    ];
}

// === Divi 5: VB inline JS (attribute registration + conditional D4->D5 conversion) ===
function get_divi5_inline_js() {
    $d4slug = wp_json_encode(DIVI4_MODULE_SLUG);
    $d4key  = wp_json_encode(DIVI4_LAYOUT_SETTING_SLUG);
    $parallaxValue = wp_json_encode(DIVI4_PARALLAX_LAYOUT_VALUE);
    $parallaxLabel = wp_json_encode(__('Mouse Parallax', 'divi-gallery-booster'));
    return <<<JS
// Modify Gallery attribute schema: add Mouse Parallax to the Layout (fullwidth) dropdown
window.vendor.wp.hooks.addFilter('divi.moduleLibrary.moduleAttributes.divi.gallery', 'divi', (attributes) => {
  try {
      const fullwidth =
          attributes?.module?.settings?.advanced?.items?.fullwidth ||
          attributes?.module?.advanced?.fullwidth ||
          attributes?.module?.settings?.advanced?.fullwidth;

      let optionsObj;
      if (fullwidth?.item?.component?.props?.options) {
          optionsObj = fullwidth.item.component.props.options;
      }
      if (optionsObj && !optionsObj[{$parallaxValue}]) {
          optionsObj[{$parallaxValue}] = { label: {$parallaxLabel} };
      }
      if (optionsObj && typeof optionsObj === 'object') {
          const sorted = Object.keys(optionsObj)
              .sort((a,b) => String(optionsObj[a].label).localeCompare(String(optionsObj[b].label)))
              .reduce((o,k) => (o[k]=optionsObj[k], o), {});
          fullwidth.item.component.props.options = sorted;
      }
  } catch(e) {}
  return attributes;
});
// Ensure D4 -> D5 conversion maps fullwidth so the `db_mouse_parallax` value persists
window.vendor.wp.hooks.addFilter('divi.moduleLibrary.conversion.moduleConversionOutline', 'divi', (conversionOutline, name) => {
    if (name !== {$d4slug}) return conversionOutline;
    if (!conversionOutline.module) conversionOutline.module = {};
    if (!conversionOutline.module[{$d4key}]) conversionOutline.module[{$d4key}] = 'module.advanced.fullwidth.*';
    return conversionOutline;
});
JS;
}

// Created at 1759319692

