<?php

namespace DiviBooster\GalleryBooster\GridImageSizes;

// === Constants ===
const DIVI4_MODULE_SLUG = 'et_pb_gallery';
const DIVI5_MODULE_SLUG = 'divi/gallery';

// Divi 4 field slugs (must not change)
const D4_IMAGES_PER_ROW         = 'db_images_per_row';
const D4_IMAGES_PER_ROW_TABLET  = 'db_images_per_row_tablet';
const D4_IMAGES_PER_ROW_PHONE   = 'db_images_per_row_phone';
const D4_IMAGE_MAX_WIDTH        = 'db_image_max_width';
const D4_IMAGE_MAX_WIDTH_TABLET = 'db_image_max_width_tablet';
const D4_IMAGE_MAX_WIDTH_PHONE  = 'db_image_max_width_phone';
const D4_IMAGE_MAX_HEIGHT       = 'db_image_max_height';
const D4_IMAGE_MAX_HEIGHT_TABLET= 'db_image_max_height_tablet';
const D4_IMAGE_MAX_HEIGHT_PHONE = 'db_image_max_height_phone';
const D4_IMAGE_ROW_SPACING      = 'db_image_row_spacing';
const D4_IMAGE_ROW_SPACING_TABLET= 'db_image_row_spacing_tablet';
const D4_IMAGE_ROW_SPACING_PHONE = 'db_image_row_spacing_phone';
const D4_DBDB_VERSION           = 'dbdb_version';

// Divi 5 attribute keys (camelCase, namespaced)
const D5_IMAGES_PER_ROW         = 'diviboosterImagesPerRow';
const D5_IMAGE_MAX_WIDTH        = 'diviboosterImageMaxWidth';
const D5_IMAGE_MAX_HEIGHT       = 'diviboosterImageMaxHeight';
const D5_IMAGE_ROW_SPACING      = 'diviboosterImageRowSpacing';
const D5_DBDB_VERSION           = 'diviboosterDbdbVersion';
const MASONRY_LAYOUT_VALUE      = 'dbdb_masonry';

// Media queries
const MQ_DESKTOP = '(min-width: 981px)';
const MQ_TABLET  = '(min-width: 768px) and (max-width: 980px)';
const MQ_PHONE   = '(max-width: 767px)';

// === Divi 4: Register fields ===
add_filter('et_pb_all_fields_unprocessed_' . DIVI4_MODULE_SLUG, __NAMESPACE__ . '\add_divi4_fields');
function add_divi4_fields($fields) {
    if (!is_array($fields)) {
        return $fields;
    }
    $new = [];
    foreach ($fields as $k => $v) {
        $new[$k] = $v;
        if ($k === 'posts_number') {
            // Images per row
            $new[D4_IMAGES_PER_ROW] = array(
                'label'           => 'Images Per Row',
                'type'            => 'text',
                'option_category' => 'layout',
                'description'     => 'Define the number of images to show per row.',
                'default'         => '',
                'mobile_options'  => true,
                'tab_slug'        => 'advanced',
                'toggle_slug'     => 'layout',
                'show_if'         => array('fullwidth' => 'off'),
                'show_if_not'     => array('fullwidth' => MASONRY_LAYOUT_VALUE),
            );
            $new[D4_IMAGES_PER_ROW_TABLET] = array('type' => 'skip', 'tab_slug' => 'advanced', 'default' => '');
            $new[D4_IMAGES_PER_ROW_PHONE]  = array('type' => 'skip', 'tab_slug' => 'advanced', 'default' => '');

            // Max width
            $new[D4_IMAGE_MAX_WIDTH] = array(
                'label'           => 'Image Area Width',
                'type'            => 'range',
                'range_settings'  => array('min' => '1', 'max' => '100', 'step' => '1'),
                'option_category' => 'layout',
                'description'     => 'Define the width of the area of the box containing the image (as % of available width).',
                'default'         => '83.5',
                'default_unit'    => '%',
                'mobile_options'  => true,
                'tab_slug'        => 'advanced',
                'toggle_slug'     => 'layout',
                'show_if'         => array('fullwidth' => 'off'),
                'show_if_not'     => array('fullwidth' => MASONRY_LAYOUT_VALUE),
            );
            $new[D4_IMAGE_MAX_WIDTH_TABLET] = array('type' => 'skip', 'tab_slug' => 'advanced', 'default' => '');
            $new[D4_IMAGE_MAX_WIDTH_PHONE]  = array('type' => 'skip', 'tab_slug' => 'advanced', 'default' => '');

            // Max height
            $new[D4_IMAGE_MAX_HEIGHT] = array(
                'label'           => 'Image Area Height',
                'type'            => 'range',
                'range_settings'  => array('min' => '1', 'max' => '1000', 'step' => '1'),
                'option_category' => 'layout',
                'description'     => 'Define the height of the area of the box containing the image (as % of box width).',
                'default'         => '',
                'default_unit'    => '%',
                'mobile_options'  => true,
                'tab_slug'        => 'advanced',
                'toggle_slug'     => 'layout',
                'show_if'         => array('fullwidth' => 'off'),
                'show_if_not'     => array('fullwidth' => MASONRY_LAYOUT_VALUE),
            );
            $new[D4_IMAGE_MAX_HEIGHT_TABLET] = array('type' => 'skip', 'tab_slug' => 'advanced', 'default' => '');
            $new[D4_IMAGE_MAX_HEIGHT_PHONE]  = array('type' => 'skip', 'tab_slug' => 'advanced', 'default' => '');

            // Row spacing
            $new[D4_IMAGE_ROW_SPACING] = array(
                'label'           => 'Image Row Spacing',
                'type'            => 'range',
                'range_settings'  => array('min' => '0', 'max' => '100', 'step' => '1'),
                'option_category' => 'layout',
                'description'     => 'Define the space between rows (as % of content width).',
                'default'         => '5.5',
                'default_unit'    => '%',
                'mobile_options'  => true,
                'tab_slug'        => 'advanced',
                'toggle_slug'     => 'layout',
                'show_if'         => array('fullwidth' => 'off'),
            );
            $new[D4_IMAGE_ROW_SPACING_TABLET] = array('type' => 'skip', 'tab_slug' => 'advanced', 'default' => '');
            $new[D4_IMAGE_ROW_SPACING_PHONE]  = array('type' => 'skip', 'tab_slug' => 'advanced', 'default' => '');

            $new[D4_DBDB_VERSION] = array('label' => 'Divi Booster Version', 'type' => 'hidden', 'default' => '');
        }
    }
    return $new;
}

// === Divi 4: Add booster version prop while editing (to keep default nuances) ===
add_filter('et_pb_module_shortcode_attributes', __NAMESPACE__ . '\add_divi4_booster_version_prop', 10, 3);
function add_divi4_booster_version_prop($props, $attrs, $render_slug) {
    if ($render_slug !== DIVI4_MODULE_SLUG) return $props;
    if (isset($_GET['et_fb']) && $_GET['et_fb'] === '1') {
        $props[D4_DBDB_VERSION] = defined('BOOSTER_VERSION') ? BOOSTER_VERSION : '4.6.0';
    }
    return $props;
}

// === Divi 5: Hidden field for dbdb_version ===
function get_d5_attr_definition_dbdb_version() {
    return array(
        'type'     => 'object',
        'settings' => array(
            'innerContent' => array(
                'dbdbVersionField' => array(
                    'groupSlug'   => 'advanced',
                    'attrName'    => D5_DBDB_VERSION,
                    'label'       => __('Divi Booster Version', 'et_builder'),
                    'description' => __('Hidden field for Divi Booster version (used for defaults handling).', 'et_builder'),
                    'features'    => array(
                        'hover'      => false,
                        'sticky'     => false,
                        'responsive' => false,
                        'preset'     => 'advanced',
                    ),
                    'showIf'      => array(),
                    'render'      => false,
                    'priority'    => 999,
                    'component'   => array(
                        'type' => 'field',
                        'name' => 'divi/hidden',
                    ),
                ),
            ),
        ),
    );
}

// === Divi 5: Attribute Registration (PHP fallback) ===
add_filter('divi_module_library_register_module_attrs', __NAMESPACE__ . '\register_divi5_attributes', 10, 2);
function register_divi5_attributes($module_attrs, $filter_args) {
    if (($filter_args['name'] ?? '') !== DIVI5_MODULE_SLUG) {
        return $module_attrs;
    }
    $module_attrs[D5_IMAGES_PER_ROW]    = get_d5_attr_definition_images_per_row();
    $module_attrs[D5_IMAGE_MAX_WIDTH]   = get_d5_attr_definition_image_max_width();
    $module_attrs[D5_IMAGE_MAX_HEIGHT]  = get_d5_attr_definition_image_max_height();
    $module_attrs[D5_IMAGE_ROW_SPACING] = get_d5_attr_definition_image_row_spacing();
    $module_attrs[D5_DBDB_VERSION]      = get_d5_attr_definition_dbdb_version();
    return $module_attrs;
}

// === Divi 5: Attribute Registration & Conversion (JS in VB) ===
add_action('divi_visual_builder_assets_before_enqueue_scripts', __NAMESPACE__ . '\enqueue_divi5_builder_js');
function enqueue_divi5_builder_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('divi-booster-' . DIVI5_MODULE_SLUG . '-grid-image-sizes');
    \dbdb_enqueue_inline_script_if_dependencies_available($handle, get_divi5_inline_js(), array('lodash', array('divi-vendor-wp-hooks', 'wp-hooks')));
}

function get_divi5_inline_js() {
        $attrRow    = wp_json_encode(get_d5_attr_definition_images_per_row());
        $attrW      = wp_json_encode(get_d5_attr_definition_image_max_width());
        $attrH      = wp_json_encode(get_d5_attr_definition_image_max_height());
        $attrSpace  = wp_json_encode(get_d5_attr_definition_image_row_spacing());
        $attrDbdb   = wp_json_encode(get_d5_attr_definition_dbdb_version());

        $d4slug = wp_json_encode(DIVI4_MODULE_SLUG);
    $map = array(
        D4_IMAGES_PER_ROW         => D5_IMAGES_PER_ROW . '.*',
        D4_IMAGES_PER_ROW_TABLET  => D5_IMAGES_PER_ROW . '.*',
        D4_IMAGES_PER_ROW_PHONE   => D5_IMAGES_PER_ROW . '.*',
        D4_IMAGE_MAX_WIDTH        => D5_IMAGE_MAX_WIDTH . '.*',
        D4_IMAGE_MAX_WIDTH_TABLET => D5_IMAGE_MAX_WIDTH . '.*',
        D4_IMAGE_MAX_WIDTH_PHONE  => D5_IMAGE_MAX_WIDTH . '.*',
        D4_IMAGE_MAX_HEIGHT       => D5_IMAGE_MAX_HEIGHT . '.*',
        D4_IMAGE_MAX_HEIGHT_TABLET=> D5_IMAGE_MAX_HEIGHT . '.*',
        D4_IMAGE_MAX_HEIGHT_PHONE => D5_IMAGE_MAX_HEIGHT . '.*',
        D4_IMAGE_ROW_SPACING      => D5_IMAGE_ROW_SPACING . '.*',
        D4_IMAGE_ROW_SPACING_TABLET=> D5_IMAGE_ROW_SPACING . '.*',
        D4_IMAGE_ROW_SPACING_PHONE => D5_IMAGE_ROW_SPACING . '.*',
        D4_DBDB_VERSION           => D5_DBDB_VERSION . '.*',
    );
        $mapJson = wp_json_encode($map);

        $d5Row   = wp_json_encode(D5_IMAGES_PER_ROW);
        $d5W     = wp_json_encode(D5_IMAGE_MAX_WIDTH);
        $d5H     = wp_json_encode(D5_IMAGE_MAX_HEIGHT);
        $d5Space = wp_json_encode(D5_IMAGE_ROW_SPACING);
        $d5Dbdb  = wp_json_encode(D5_DBDB_VERSION);
        $masonry = wp_json_encode(MASONRY_LAYOUT_VALUE);

        return <<<JS
(function(){
    const hooks = window.vendor?.wp?.hooks; if (!hooks) return;
    function getGalleryLayoutValue(attrs) {
        return (
            attrs?.layout?.advanced?.fullwidth?.desktop?.value ??
            attrs?.layout?.advanced?.fullwidth?.value ??
            attrs?.module?.advanced?.fullwidth?.desktop?.value ??
            attrs?.module?.advanced?.fullwidth?.value ??
            attrs?.fullwidth?.desktop?.value ??
            attrs?.fullwidth?.value ??
            attrs?.fullwidth ??
            'off'
        );
    }
    function getSingleFieldItem(attribute) {
        const items = attribute?.settings?.innerContent?.items || {};
        const firstKey = Object.keys(items)[0];
        return firstKey ? (items[firstKey]?.item || items[firstKey]) : null;
    }
    function attachVisibleCallback(item, callback) {
        const targets = [];
        if (item && typeof item === 'object') {
            targets.push(item);
            if (item.item && typeof item.item === 'object' && item.item !== item) {
                targets.push(item.item);
            }
        }
        targets.forEach((target) => {
            const prevVisible = target.visible;
            target.visible = function(ctx) {
                return callback(ctx, prevVisible);
            };
            if (target?.component?.props) {
                const prevComponentVisible = target.component.props.visible;
                target.component.props.visible = function(ctx) {
                    const prev = (typeof prevComponentVisible === 'function') ? prevComponentVisible(ctx) : true;
                    return prev && callback(ctx, prevVisible);
                };
            }
        });
    }
    function hideWhenMasonry(attribute) {
        const item = getSingleFieldItem(attribute);
        if (!item) return;
        attachVisibleCallback(item, function(ctx, prevVisible) {
            const notMasonry = getGalleryLayoutValue(ctx?.attrs) !== {$masonry};
            return (typeof prevVisible === 'function') ? (prevVisible(ctx) && notMasonry) : notMasonry;
        });
    }
    // Register attributes on Gallery module
    hooks.addFilter('divi.moduleLibrary.moduleAttributes.divi.gallery', 'divi-booster-gallery-grid-image-sizes-fields', (attributes) => {
        attributes[{$d5Row}]   = {$attrRow};
        attributes[{$d5W}]     = {$attrW};
        attributes[{$d5H}]     = {$attrH};
        attributes[{$d5Space}] = {$attrSpace};
        attributes[{$d5Dbdb}]  = {$attrDbdb};
        hideWhenMasonry(attributes[{$d5Row}]);
        hideWhenMasonry(attributes[{$d5W}]);
        hideWhenMasonry(attributes[{$d5H}]);
        hideWhenMasonry(attributes[{$d5Space}]);
        return attributes;
    });
    hooks.addFilter('divi.moduleLibrary.moduleSettings.groups.divi.gallery', 'divi-booster-gallery-grid-image-sizes-hide-groups-fields', (groups) => {
        function attachGroupVisibility(node) {
            const targets = [];
            if (node && typeof node === 'object') {
                targets.push(node);
                if (node.item && typeof node.item === 'object' && node.item !== node) {
                    targets.push(node.item);
                }
            }
            targets.forEach((target) => {
                const prevVisible = target.visible;
                target.visible = function(ctx) {
                    const notMasonry = getGalleryLayoutValue(ctx?.attrs) !== {$masonry};
                    return (typeof prevVisible === 'function') ? (prevVisible(ctx) && notMasonry) : notMasonry;
                };
                const prevVisibleCallback = target.visibleCallback;
                target.visibleCallback = function(ctx) {
                    const notMasonry = getGalleryLayoutValue(ctx?.attrs) !== {$masonry};
                    return (typeof prevVisibleCallback === 'function') ? (prevVisibleCallback(ctx) && notMasonry) : notMasonry;
                };
                if (target?.component?.props) {
                    const prevComponentVisible = target.component.props.visible;
                    target.component.props.visible = function(ctx) {
                        const notMasonry = getGalleryLayoutValue(ctx?.attrs) !== {$masonry};
                        return (typeof prevComponentVisible === 'function') ? (prevComponentVisible(ctx) && notMasonry) : notMasonry;
                    };
                }
            });
        }
        const seen = new Set();
        function visit(node) {
            if (!node || typeof node !== 'object' || seen.has(node)) return;
            seen.add(node);
            const field = node.item && typeof node.item === 'object' ? node.item : node;
            const attrName = String(field?.attrName || '');
            const label = String(field?.label || '');
            if (
                attrName === {$d5Row} ||
                attrName === {$d5W} ||
                attrName === {$d5H} ||
                attrName === {$d5Space} ||
                label === 'Images Per Row' ||
                label === 'Image Area Width' ||
                label === 'Image Area Height' ||
                label === 'Image Row Spacing'
            ) {
                attachGroupVisibility(node);
            }
            Object.keys(node).forEach((key) => visit(node[key]));
        }
        visit(groups);
        return groups;
    });
    // Conversion mapping D4 -> D5 (explicit device paths)
    hooks.addFilter('divi.moduleLibrary.conversion.moduleConversionOutline', 'divi', (outline, name) => {
        if (name !== {$d4slug}) return outline;
        if (!outline.module) outline.module = {};
        const map = {$mapJson};
        Object.keys(map).forEach((k)=>{ outline.module[k] = map[k]; });
        return outline;
    });
})();
JS;
}

// === Divi 5: PHP conversion outline fallback ===
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();
    }
    $conversion_outline['module'][D4_IMAGES_PER_ROW]          = D5_IMAGES_PER_ROW . '.*';
    $conversion_outline['module'][D4_IMAGES_PER_ROW_TABLET]   = D5_IMAGES_PER_ROW . '.*';
    $conversion_outline['module'][D4_IMAGES_PER_ROW_PHONE]    = D5_IMAGES_PER_ROW . '.*';
    $conversion_outline['module'][D4_IMAGE_MAX_WIDTH]         = D5_IMAGE_MAX_WIDTH . '.*';
    $conversion_outline['module'][D4_IMAGE_MAX_WIDTH_TABLET]  = D5_IMAGE_MAX_WIDTH . '.*';
    $conversion_outline['module'][D4_IMAGE_MAX_WIDTH_PHONE]   = D5_IMAGE_MAX_WIDTH . '.*';
    $conversion_outline['module'][D4_IMAGE_MAX_HEIGHT]        = D5_IMAGE_MAX_HEIGHT . '.*';
    $conversion_outline['module'][D4_IMAGE_MAX_HEIGHT_TABLET] = D5_IMAGE_MAX_HEIGHT . '.*';
    $conversion_outline['module'][D4_IMAGE_MAX_HEIGHT_PHONE]  = D5_IMAGE_MAX_HEIGHT . '.*';
    $conversion_outline['module'][D4_IMAGE_ROW_SPACING]       = D5_IMAGE_ROW_SPACING . '.*';
    $conversion_outline['module'][D4_IMAGE_ROW_SPACING_TABLET]= D5_IMAGE_ROW_SPACING . '.*';
    $conversion_outline['module'][D4_IMAGE_ROW_SPACING_PHONE] = D5_IMAGE_ROW_SPACING . '.*';
    $conversion_outline['module'][D4_DBDB_VERSION]            = D5_DBDB_VERSION . '.*';

    return $conversion_outline;
}

// === Helpers: Attribute definitions for Divi 5 ===
function base_attr_field($groupSlug, $attrName, $label, $description, $component, $props = array(), $defaultAttr = null) {
    $field = array(
        'groupSlug'   => $groupSlug,
        'attrName'    => $attrName,
        'label'       => __($label, 'et_builder'),
        'description' => __($description, 'et_builder'),
        'features'    => array(
            'hover'      => false,
            'sticky'     => false,
            'responsive' => true,
            'preset'     => 'advanced',
        ),
        'showIf' => array(
            'fullwidth' => array('desktop' => array('value' => 'off')),
        ),
        'render'      => true,
        'priority'    => 99,
        'component'   => array(
            'type' => 'field',
            'name' => $component,
            'props'=> $props,
        ),
    );
    if ($defaultAttr !== null) {
        $field['defaultAttr'] = $defaultAttr;
    }
    return array(
        'groupType'   => 'group-items',
        'items'       => array(
            sanitize_title($label) . 'Field' => $field,
        ),
    );
}
function get_d5_attr_definition_images_per_row() {
    return array(
        'type'     => 'object',
        'settings' => array(
            'innerContent' => base_attr_field(
                'designLayout',
                D5_IMAGES_PER_ROW,
                'Images Per Row',
                'Define the number of images to show per row.',
                'divi/text'
            ),
        ),
    );
}
function get_d5_attr_definition_image_max_width() {
    return array(
        'type'     => 'object',
        'settings' => array(
            'innerContent' => base_attr_field(
                'designLayout',
                D5_IMAGE_MAX_WIDTH,
                'Image Area Width',
                'Define the width of the area of the box containing the image (as % of available width).',
                'divi/range',
                array('min'=>1, 'max'=>100, 'step'=>1, 'unit'=>'%', 'defaultUnit'=>'%'),
                array('desktop' => array('value' => '83.5'))
            ),
        ),
    );
}
function get_d5_attr_definition_image_max_height() {
    return array(
        'type'     => 'object',
        'settings' => array(
            'innerContent' => base_attr_field(
                'designLayout',
                D5_IMAGE_MAX_HEIGHT,
                'Image Area Height',
                'Define the height of the area of the box containing the image (as % of box width).',
                'divi/range',
                array('min'=>1, 'max'=>1000, 'step'=>1, 'unit'=>'%', 'defaultUnit'=>'%'),
                array('desktop' => array('value' => '59.3'))
            ),
        ),
    );
}
function get_d5_attr_definition_image_row_spacing() {
    return array(
        'type'     => 'object',
        'settings' => array(
            'innerContent' => base_attr_field(
                'designLayout',
                D5_IMAGE_ROW_SPACING,
                'Image Row Spacing',
                'Define the space between rows (as % of content width).',
                'divi/range',
                array('min'=>0, 'max'=>100, 'step'=>1, 'unit'=>'%', 'defaultUnit'=>'%'),
                array('desktop' => array('value' => '5.5'))
            ),
        ),
    );
}

// === Shared helpers ===
function is_grid_layout($props) {
    // Divi 4 flat
    if (isset($props['fullwidth'])) {
        return ($props['fullwidth'] === 'off');
    }
    // Divi 5 nested
    if (isset($props['module']['advanced']['fullwidth']['desktop']['value'])) {
        return ($props['module']['advanced']['fullwidth']['desktop']['value'] === 'off');
    }
    if (isset($props['module']['advanced']['fullwidth']['value'])) {
        return ($props['module']['advanced']['fullwidth']['value'] === 'off');
    }
    return true; // default
}

function extract_order_class_from_html($html) {
    if (!is_string($html) || $html === '') return '';
    if (!preg_match('/<div[^>]*class=\"([^\"]*\bet_pb_gallery\b[^\"]*)\"/i', $html, $m)) {
        return '';
    }
    $classes = preg_split('/\s+/', $m[1], -1, PREG_SPLIT_NO_EMPTY);
    foreach ($classes as $c) {
        if (preg_match('/^et_pb_gallery_\d+(?:_[^\s]+)?$/', $c)) {
            return $c;
        }
    }
    if (preg_match('/et_pb_gallery_\d+(?:_[^\s]+)?/', $m[1], $m2)) {
        return $m2[0];
    }
    return '';
}

function set_module_style($module_slug, $style) {
    if (is_callable('ET_Builder_Module::set_style')) {
        \ET_Builder_Module::set_style($module_slug, $style);
    }
}

function sanitize_percent_or_unit($v) {
    $v = is_string($v) ? trim($v) : $v;
    if ($v === '' || $v === null) return '';
    if (is_numeric($v)) {
        return $v . '%';
    }
    return preg_replace('/[^0-9.%a-zA-Z\-]/', '', (string)$v);
}

function values_from_divi4($props, $base_slug) {
    $desktop = isset($props[$base_slug]) ? $props[$base_slug] : '';
    $tablet  = isset($props[$base_slug . '_tablet']) ? $props[$base_slug . '_tablet'] : '';
    $phone   = isset($props[$base_slug . '_phone']) ? $props[$base_slug . '_phone'] : '';
    return array('desktop' => $desktop, 'tablet' => $tablet, 'phone' => $phone);
}

function values_from_divi5($attrs, $key) {
    $out = array('desktop' => '', 'tablet' => '', 'phone' => '');
    if (!isset($attrs[$key])) return $out;
    foreach (array('desktop','tablet','phone') as $dev) {
        if (isset($attrs[$key][$dev]['value'])) {
            $out[$dev] = $attrs[$key][$dev]['value'];
        } elseif (isset($attrs[$key][$dev])) {
            $out[$dev] = $attrs[$key][$dev];
        }
    }
    return $out;
}

// === CSS builders ===
function build_css_images_per_row($order_class, $values) {
    $css = '';
    $map = array('desktop' => MQ_DESKTOP, 'tablet' => MQ_TABLET, 'phone' => MQ_PHONE);
    foreach ($map as $dev => $mq) {
        $v = isset($values[$dev]) ? $values[$dev] : '';
        $n = abs(intval($v));
        if ($n > 0) {
            $width = 100 / $n;
            $sel = '.' . $order_class . ' .et_pb_gallery_item.et_pb_grid_item';
            $selNth = $sel . ":nth-of-type(" . $n . "n+1)";
            $css .= "@media only screen and {$mq}{ {$sel}{margin-right:0!important;width:{$width}%!important;clear:none!important;} {$selNth}{clear:both!important;} }";
        }
    }
    return $css;
}

function build_css_max_width($order_class, $values) {
    $css = '';
    $map = array('desktop' => MQ_DESKTOP, 'tablet' => MQ_TABLET, 'phone' => MQ_PHONE);
    foreach ($map as $dev => $mq) {
        if (!array_key_exists($dev, $values)) continue;
        $num = $values[$dev];
        if ($num === '' && $dev !== 'desktop') {
            // fallback to desktop
            $num = $values['desktop'];
        }
        if ($num === '' && $dev === 'desktop') continue;
        $num = sanitize_percent_or_unit($num);
        if ($num === '') continue;
        $wrap = '.' . $order_class . ' .et_pb_gallery_item.et_pb_grid_item';
        $css .= "@media only screen and {$mq}{ {$wrap} .et_pb_gallery_title, {$wrap} .et_pb_gallery_image{max-width:{$num};margin-left:auto!important;margin-right:auto!important;} {$wrap} .et_pb_gallery_image img{width:100%;} }";
    }
    return $css;
}

function build_css_max_height($order_class, $values) {
    $css = '';
    $map = array('desktop' => MQ_DESKTOP, 'tablet' => MQ_TABLET, 'phone' => MQ_PHONE);
    foreach ($map as $dev => $mq) {
        $num = isset($values[$dev]) ? trim((string)$values[$dev]) : '';
        if ($num === '' && $dev !== 'desktop') {
            $num = isset($values['desktop']) ? trim((string)$values['desktop']) : '';
        }
        $ival = abs(intval($num));
        if ($ival > 0) {
            $wrap = '.' . $order_class . ' .et_pb_gallery_item.et_pb_grid_item';
            $css .= "@media only screen and {$mq}{ {$wrap} .et_pb_gallery_image{position:relative;padding-bottom:{$ival}%;height:0;overflow:hidden;} {$wrap} .et_pb_gallery_image img{position:absolute;top:0;left:0;width:100%;height:100%;object-fit:cover;} }";
        }
    }
    return $css;
}

function build_css_row_spacing($order_class, $values) {
    $css = '';
    $map = array('desktop' => MQ_DESKTOP, 'tablet' => MQ_TABLET, 'phone' => MQ_PHONE);
    foreach ($map as $dev => $mq) {
        if (!array_key_exists($dev, $values)) continue;
        $num = $values[$dev];
        if ($num === '' && $dev !== 'desktop') {
            $num = $values['desktop'];
        }
        if ($num === '' && $dev === 'desktop') continue;
        $val = sanitize_percent_or_unit($num);
        if ($val === '') continue;
        $sel = '.' . $order_class . ' .et_pb_gallery_item.et_pb_grid_item';
        $css .= "@media only screen and {$mq}{ {$sel}{margin-bottom:{$val}!important;} }";
    }
    return $css;
}

function build_uncrop_script($order_class) {
    if ($order_class === '') return '';
    $selector = '.' . $order_class . ' .et_pb_gallery_item.et_pb_grid_item';
    $selector_js = esc_js($selector);
    return '<script>jQuery(function($){var items=$("' . $selector_js . '");items.each(function(){var $a=$(this).find("a");var href=$a.attr("href");if(href){$(this).find("a>img").attr("src",href).attr("srcset","").attr("sizes","");}});});</script>';
}

// === Divi 4 Output Filter ===
add_filter('et_module_shortcode_output', __NAMESPACE__ . '\apply_divi4_output', 10, 3);
function apply_divi4_output($output, $render_slug, $module) {
    if (!is_string($output) || $render_slug !== DIVI4_MODULE_SLUG || !isset($module->props) || !is_array($module->props)) {
        return $output;
    }
    $props = $module->props;

    if (!is_grid_layout($props)) {
        return $output;
    }

    // Merge presets (as in original implementation)
    if (class_exists('ET_Builder_Global_Presets_Settings') && is_callable('ET_Builder_Global_Presets_Settings::instance')) {
        $preset = \ET_Builder_Global_Presets_Settings::instance();
        if (is_callable(array($preset, 'get_module_presets_settings'))) {
            $defaults = $preset->get_module_presets_settings(DIVI4_MODULE_SLUG, $props);
            $props = wp_parse_args($props, $defaults);
        }
    }

    // Per-instance order class
    $order_class = extract_order_class_from_html($output);
    if ($order_class === '') {
        return $output;
    }

    // Defaults nuance (legacy behavior)
    $useNewDefaults = (!empty($props[D4_DBDB_VERSION]) && version_compare($props[D4_DBDB_VERSION], '3.2.6', '>='));
    if (!empty($props[D4_IMAGES_PER_ROW]) && (!isset($props[D4_IMAGE_MAX_WIDTH]) || $props[D4_IMAGE_MAX_WIDTH] === '83.5')) {
        $props[D4_IMAGE_MAX_WIDTH] = $useNewDefaults ? '83.5%' : '100%';
    }
    if (!empty($props[D4_IMAGES_PER_ROW]) && (!isset($props[D4_IMAGE_ROW_SPACING]) || $props[D4_IMAGE_ROW_SPACING] === '5.5')) {
        $props[D4_IMAGE_ROW_SPACING] = $useNewDefaults ? '5.5%' : '0%';
    }

    // Images per row via ET set_style (as per original)
    $ipr = values_from_divi4($props, D4_IMAGES_PER_ROW);
    foreach (array('desktop'=>MQ_DESKTOP,'tablet'=>MQ_TABLET,'phone'=>MQ_PHONE) as $dev => $mq) {
        $n = abs(intval($ipr[$dev]));
        if ($n > 0) {
            $width = 100 / $n;
            set_module_style(DIVI4_MODULE_SLUG, array(
                'selector'    => ".et_pb_column %%order_class%% .et_pb_gallery_item.et_pb_grid_item",
                'declaration' => 'margin-right: 0 !important; width: ' . $width . '% !important; clear: none !important;',
                'media_query' => '@media only screen and ' . $mq,
            ));
            set_module_style(DIVI4_MODULE_SLUG, array(
                'selector'    => ".et_pb_column %%order_class%% .et_pb_gallery_item.et_pb_grid_item:nth-of-type({$n}n+1)",
                'declaration' => 'clear: both !important;',
                'media_query' => '@media only screen and ' . $mq,
            ));
        }
    }

    // Build remaining CSS (max width, height, row spacing)
    $maxwVals = values_from_divi4($props, D4_IMAGE_MAX_WIDTH);
    $maxhVals = values_from_divi4($props, D4_IMAGE_MAX_HEIGHT);
    $spaceVals= values_from_divi4($props, D4_IMAGE_ROW_SPACING);

    $css  = '';
    $css .= build_css_max_width($order_class, $maxwVals);
    $css .= build_css_max_height($order_class, $maxhVals);
    $css .= build_css_row_spacing($order_class, $spaceVals);

    if ($css !== '') {
        $output .= '<style>' . $css . '</style>';
    }

    // Disable cropping if height specified (any device)
    if (!empty($maxhVals['desktop']) || !empty($maxhVals['tablet']) || !empty($maxhVals['phone'])) {
        $output .= build_uncrop_script($order_class);
    }

    return $output;
}

// === Divi 5 Output Filter ===
add_filter('render_block_' . DIVI5_MODULE_SLUG, __NAMESPACE__ . '\apply_divi5_output', 10, 3);
function apply_divi5_output($block_content, $parsed_block, $block) {
    if (!is_string($block_content)) {
        return $block_content;
    }
    $attrs = isset($parsed_block['attrs']) && is_array($parsed_block['attrs']) ? $parsed_block['attrs'] : array();
    if (!is_grid_layout($attrs)) {
        return $block_content;
    }

    $order_class = extract_order_class_from_html($block_content);
    if ($order_class === '') {
        return $block_content;
    }

    // Collect responsive values
    $iprVals  = values_from_divi5($attrs, D5_IMAGES_PER_ROW);
    $maxwVals = values_from_divi5($attrs, D5_IMAGE_MAX_WIDTH);
    $maxhVals = values_from_divi5($attrs, D5_IMAGE_MAX_HEIGHT);
    $spaceVals= values_from_divi5($attrs, D5_IMAGE_ROW_SPACING);

    // Build CSS (include images per row here for D5)
    $css  = '';
    $css .= build_css_images_per_row($order_class, $iprVals);
    $css .= build_css_max_width($order_class, $maxwVals);
    $css .= build_css_max_height($order_class, $maxhVals);
    $css .= build_css_row_spacing($order_class, $spaceVals);

    if ($css !== '') {
        $block_content .= '<style>' . $css . '</style>';
    }

    if (!empty($maxhVals['desktop']) || !empty($maxhVals['tablet']) || !empty($maxhVals['phone'])) {
        $block_content .= build_uncrop_script($order_class);
    }

    return $block_content;
}

// Created at 1759360727.

