<?php // https://divibooster.com/changing-the-portfolio-module-project-order/
namespace DiviBooster\DiviBooster\Modules\Portfolio\ProjectOrder;

const DIVI4_MODULE_SLUGS = [
    'et_pb_portfolio',
    'et_pb_filterable_portfolio',
    'et_pb_fullwidth_portfolio'
];
const DIVI5_MODULE_SLUGS = [
    'divi/portfolio',
    'divi/filterable-portfolio',
    'divi/fullwidth-portfolio'
];
const DIVI4_PROJECT_ORDER_SLUG = 'db_project_order';
const DIVI5_PROJECT_ORDER_SLUG = 'diviboosterProjectOrder';
const DIVI4_PROJECT_ORDER_IDS_SLUG = 'db_project_order_ids';
const DIVI5_PROJECT_ORDER_IDS_SLUG = 'diviboosterProjectOrderIds';

// === Divi 4: Register Portfolio Project Order fields ===
foreach (DIVI4_MODULE_SLUGS as $divi4_slug) {
    add_filter('et_pb_all_fields_unprocessed_' . $divi4_slug, __NAMESPACE__ . '\add_divi4_project_order_fields');
}
function add_divi4_project_order_fields($fields) {
    $fields[DIVI4_PROJECT_ORDER_SLUG] = [
        'label'            => 'Project Order',
        'type'             => 'select',
        'option_category'  => 'layout',
        'options'          => divibooster_portfolio_order_options(),
        'default'          => 'default',
        'description'      => 'Adjust the order in which projects are displayed. Note that this feature is not previewable in the visual builder, but will take effect on the live page.',
        'tab_slug'         => 'advanced',
        'toggle_slug'      => 'layout',
    ];
    $fields[DIVI4_PROJECT_ORDER_IDS_SLUG] = [
        'label'            => 'Project IDs',
        'type'             => 'text',
        'option_category'  => 'layout',
        'default'          => '',
        'description'      => 'Enter a comma-separated list of project ids.',
        'tab_slug'         => 'advanced',
        'dynamic_content'  => 'text',
        'toggle_slug'      => 'layout',
        'show_if'          => [
            DIVI4_PROJECT_ORDER_SLUG => 'by_id',
        ],
    ];
    return $fields;
}

if (!function_exists(__NAMESPACE__ . '\\divibooster_portfolio_order_options')) {
    function divibooster_portfolio_order_options() {
        $result = [
            'default' => esc_html__('Default', 'et_builder'),
            'random'  => esc_html__('Random',  'et_builder'),
            'reverse' => esc_html__('Reverse', 'et_builder'),
            'by_id'   => esc_html__('By ID',    'et_builder'),
        ];
        return apply_filters('dbdb_portfolio_order_options', $result);
    }
}

// === Divi 5: Register Attributes and Conversion Script ===
add_action('wp_enqueue_scripts', __NAMESPACE__ . '\enqueue_divi5_booster_script');
function enqueue_divi5_booster_script() {
    if ((!function_exists('et_builder_d5_enabled') || !\et_builder_d5_enabled()) ||
        (!function_exists('et_core_is_fb_enabled') || !\et_core_is_fb_enabled())) {
        return;
    }
    foreach (DIVI5_MODULE_SLUGS as $d5_slug) {
        $handle = sanitize_title('divi-booster-' .$d5_slug . '-' . DIVI5_PROJECT_ORDER_SLUG);
        \dbdb_enqueue_inline_script_if_dependencies_available($handle, get_inline_js($d5_slug), array('lodash', array('divi-vendor-wp-hooks', 'wp-hooks')));
    }
}

function get_inline_js($divi5_slug) {
    $attribute_defs = wp_json_encode(get_attribute_definition());
    $divi5_slug_json = wp_json_encode($divi5_slug);
    $divi4_slug = (
        $divi5_slug === 'divi/fullwidth-portfolio' ? 'et_pb_fullwidth_portfolio' : (
            $divi5_slug === 'divi/filterable-portfolio' ? 'et_pb_filterable_portfolio' : 'et_pb_portfolio'
        )
    );
    $divi4_slug_json = wp_json_encode($divi4_slug);
    $divi5_po_slug = wp_json_encode(DIVI5_PROJECT_ORDER_SLUG);
    $divi4_po_slug = wp_json_encode(DIVI4_PROJECT_ORDER_SLUG);
    $divi5_po_ids_slug = wp_json_encode(DIVI5_PROJECT_ORDER_IDS_SLUG);
    $divi4_po_ids_slug = wp_json_encode(DIVI4_PROJECT_ORDER_IDS_SLUG);
    return <<<END
window.vendor.wp.hooks.addFilter('divi.moduleLibrary.moduleMapping', 'divi', modules => {
    const path = [{$divi5_slug_json}, 'metadata', 'attributes'];
    const { set, get, has } = window.lodash;
    const target = get(modules, path) ? get(modules, path) : set(modules, path, {});
    if (has(modules, path)) {
        target[{$divi5_po_slug}] = {$attribute_defs}.projectOrder;
        target[{$divi5_po_ids_slug}] = {$attribute_defs}.projectOrderIds;
    }
    return modules;
});
window.vendor.wp.hooks.addFilter('divi.moduleLibrary.conversion.moduleConversionOutline', 'divi', (conversionOutline, name) => {
    if (name !== {$divi4_slug_json}) return conversionOutline;
    conversionOutline.module[{$divi4_po_slug}] = {$divi5_po_slug} + '.*';
    conversionOutline.module[{$divi4_po_ids_slug}] = {$divi5_po_ids_slug} + '.*';
    return conversionOutline;
});
END;
}

add_filter('divi_module_library_register_module_attrs', function($module_attrs, $filter_args) {
    $name = $filter_args['name'] ?? '';
    if (!in_array($name, DIVI5_MODULE_SLUGS, true)) {
        return $module_attrs;
    }
    $defs = get_attribute_definition();
    $module_attrs[DIVI5_PROJECT_ORDER_SLUG] = $defs['projectOrder'];
    $module_attrs[DIVI5_PROJECT_ORDER_IDS_SLUG] = $defs['projectOrderIds'];
    return $module_attrs;
}, 10, 2);

// === Divi 5: PHP conversion outline registration ===
function dbdb_register_divi5_project_order_conversion($conversion_outline, $module_name) {
    // Match the JS conversion outline: operate for Divi 4 module slugs
    if (!in_array($module_name, DIVI5_MODULE_SLUGS, true)) {
        return $conversion_outline;
    }
    if (!isset($conversion_outline['module']) || !is_array($conversion_outline['module'])) {
        $conversion_outline['module'] = [];
    }
    $conversion_outline['module'][DIVI4_PROJECT_ORDER_SLUG] = DIVI5_PROJECT_ORDER_SLUG . '.*';
    $conversion_outline['module'][DIVI4_PROJECT_ORDER_IDS_SLUG] = DIVI5_PROJECT_ORDER_IDS_SLUG . '.*';
    return $conversion_outline;
}
add_filter('divi.moduleLibrary.conversion.moduleConversionOutline', __NAMESPACE__ . '\\dbdb_register_divi5_project_order_conversion', 10, 2);

function get_attribute_definition() {
    return [
        'projectOrder' => [
            'type'     => 'object',
            'settings' => [
                'innerContent' => [
                    'groupType' => 'group-items',
                    'items'     => [
                        'diviboosterProjectOrderSelect' => [
                            'groupSlug'   => 'designLayout',
                            'attrName'    => DIVI5_PROJECT_ORDER_SLUG,
                            'label'       => 'Project Order',
                            'description' => 'Adjust the order in which projects are displayed. Note that this feature is not previewable in the visual builder, but will take effect on the live page.',
                            'features'    => [
                                'hover'      => false,
                                'sticky'     => false,
                                'responsive' => false,
                                'preset'     => 'advanced',
                            ],
                            'render'   => true,
                            'priority' => 35,
                            'component' => [
                                'type' => 'field',
                                'name' => 'divi/select',
                                'props' => [
                                    'options' => [
                                        'default' => [ 'label' => esc_html__('Default', 'et_builder') ],
                                        'random'  => [ 'label' => esc_html__('Random', 'et_builder') ],
                                        'reverse' => [ 'label' => esc_html__('Reverse', 'et_builder') ],
                                        'by_id'   => [ 'label' => esc_html__('By ID', 'et_builder') ],
                                    ],
                                ],
                            ],
                            'defaultAttr' => [ 'desktop' => [ 'value' => 'default' ] ],
                        ],
                    ],
                ],
            ],
        ],
        'projectOrderIds' => [
            'type'     => 'object',
            'settings' => [
                'innerContent' => [
                    'groupType' => 'group-items',
                    'items'     => [
                        'diviboosterProjectOrderIdsText' => [
                            'groupSlug'   => 'designLayout',
                            'attrName'    => DIVI5_PROJECT_ORDER_IDS_SLUG,
                            'label'       => 'Project IDs',
                            'description' => 'Enter a comma-separated list of project ids.',
                            'features'    => [
                                'hover'      => false,
                                'sticky'     => false,
                                'responsive' => false,
                                'preset'     => 'advanced',
                            ],
                            'render'   => true,
                            'priority' => 36,
                            'component' => [
                                'type' => 'field',
                                'name' => 'divi/text',
                            ],
                            'defaultAttr' => [ 'desktop' => [ 'value' => '' ] ],
                            'showIf' => [
                                DIVI5_PROJECT_ORDER_SLUG => 'by_id',
                            ],
                        ],
                    ],
                ],
            ],
        ],
    ];
}

// --- Output/Query Filtering: Project Order ---

// For Divi 4: Use existing pre_get_posts logic to adjust order of projects output.
add_filter('et_pb_module_shortcode_attributes', __NAMESPACE__ . '\pre_get_posts_project_order', 10, 3);
add_filter('et_module_shortcode_output', __NAMESPACE__ . '\remove_pre_get_posts_project_order');

function pre_get_posts_project_order($props, $atts, $slug) {
    if (!in_array($slug, DIVI4_MODULE_SLUGS, true)) {
        return $props;
    }
    if (isset($atts[DIVI4_PROJECT_ORDER_SLUG])) {
        $order = $atts[DIVI4_PROJECT_ORDER_SLUG];
        if ($order === 'random') {
            add_action('pre_get_posts', __NAMESPACE__ . '\filter_query_random', 11);
        } elseif ($order === 'reverse') {
            add_action('pre_get_posts', __NAMESPACE__ . '\filter_query_reverse', 11);
        } elseif ($order === 'by_id' && !empty($atts[DIVI4_PROJECT_ORDER_IDS_SLUG])) {
            add_action('pre_get_posts', function($query) use ($atts) {
                $ids = explode(',', $atts[DIVI4_PROJECT_ORDER_IDS_SLUG]);
                $ids = array_filter(array_map('absint', $ids));
                if ($ids) {
                    $query->set('post__in', $ids);
                    $query->set('orderby', 'post__in');
                }
            }, 11);
        }
    }
    return $props;
}
function remove_pre_get_posts_project_order($content) {
    remove_action('pre_get_posts', __NAMESPACE__ . '\filter_query_random', 11);
    remove_action('pre_get_posts', __NAMESPACE__ . '\filter_query_reverse', 11);
    // Remove all runtime dynamic by_id handlers just in case
    global $wp_filter;
    if (isset($wp_filter['pre_get_posts'])) {
        $wp_filter['pre_get_posts']->callbacks[11] = array_filter(
            $wp_filter['pre_get_posts']->callbacks[11] ?? [],
            function($cb){
                if (is_array($cb['function']) && isset($cb['function'][0]) && is_object($cb['function'][0]) && get_class($cb['function'][0]) === __NAMESPACE__){
                    return false;
                }
                if (is_object($cb['function']) && ($cb['function'] instanceof \Closure)) {
                    // skip closures
                    return false;
                }
                return true;
            }
        );
    }
    return $content;
}
function filter_query_random($query) {
    $query->set('orderby', 'rand');
}
function filter_query_reverse($query) {
    $query->set('order', 'ASC');
}

// --- Divi 5 block render filter: subset/ordering project posts ---
foreach (DIVI5_MODULE_SLUGS as $divi5_slug) {
    add_filter('render_block_' . $divi5_slug, function($block_content, $parsed_block, $block) use($divi5_slug) {
        $attrs = $parsed_block['attrs'] ?? [];
        $order = $attrs[DIVI5_PROJECT_ORDER_SLUG]['desktop']['value'] ?? ($attrs[DIVI5_PROJECT_ORDER_SLUG]['value'] ?? 'default');
        $project_ids = $attrs[DIVI5_PROJECT_ORDER_IDS_SLUG]['desktop']['value'] ?? ($attrs[DIVI5_PROJECT_ORDER_IDS_SLUG]['value'] ?? '');
        // Only act if not default order
        if ($order === 'default') {
            return $block_content;
        }
        $block_content = divibooster_sort_portfolio_projects_html($block_content, $order, $project_ids);
        return $block_content;
    }, 10, 3);
}
/**
 * Resort the project items in the portfolio module's HTML output for Divi 5 front-end
 */
function divibooster_sort_portfolio_projects_html($html, $order_type, $project_ids) {
    // Use DOMDocument for robust HTML parsing
    if (trim($html) === '') return $html;

    // Suppress warnings for malformed HTML
    libxml_use_internal_errors(true);
    $dom = new \DOMDocument();
    // Ensure UTF-8 handling
    $dom->loadHTML('<?xml encoding="utf-8" ?>' . $html, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);

    $xpath = new \DOMXPath($dom);

    // Find all containers that may hold portfolio items
    $containers = [];
    // Standard/filterable/fullwidth portfolio
    foreach ([
        "//*[contains(concat(' ', normalize-space(@class), ' '), ' et_pb_portfolio_items ')]",
        "//*[contains(concat(' ', normalize-space(@class), ' '), ' et_pb_ajax_pagination_container ')]"
    ] as $query) {
        foreach ($xpath->query($query) as $node) {
            $containers[] = $node;
        }
    }

    if (empty($containers)) {
        // Try to find .et_pb_fullwidth_portfolio and then .et_pb_portfolio_items inside it
        $fullwidths = $xpath->query("//*[contains(concat(' ', normalize-space(@class), ' '), ' et_pb_fullwidth_portfolio ')]");
        foreach ($fullwidths as $fw) {
            foreach ($xpath->query(".//*[contains(concat(' ', normalize-space(@class), ' '), ' et_pb_portfolio_items ')]", $fw) as $node) {
                $containers[] = $node;
            }
        }
    }

    if (empty($containers)) {
        libxml_clear_errors();
        return $html;
    }

    foreach ($containers as $container) {
        // Find all direct children with class et_pb_portfolio_item
        $items = [];
        foreach ($container->childNodes as $child) {
            if ($child->nodeType === XML_ELEMENT_NODE && preg_match('/\bet_pb_portfolio_item\b/', $child->getAttribute('class'))) {
                $items[] = $child;
            }
        }
        if (count($items) === 0) continue;

        // Key items by ID (from class "post-123" or id attribute)
        $id_to_item = [];
        foreach ($items as $item) {
            $id = null;
            // Try to get from class "post-123"
            if (preg_match('/post-(\d+)/', $item->getAttribute('class'), $mitem)) {
                $id = (int)$mitem[1];
            } elseif ($item->hasAttribute('id') && is_numeric($item->getAttribute('id'))) {
                $id = (int)$item->getAttribute('id');
            }
            if ($id !== null) {
                $id_to_item[$id] = $item;
            } else {
                $id_to_item[] = $item;
            }
        }

        // Determine new order
        $ordered_items = [];
        if ($order_type === 'by_id' && !empty($project_ids)) {
            $ids = array_filter(array_map('absint', explode(',', $project_ids)));
            foreach ($ids as $id) {
                if (isset($id_to_item[$id])) {
                    $ordered_items[] = $id_to_item[$id];
                    unset($id_to_item[$id]);
                }
            }
            // Add remaining (unmatched) at end
            foreach ($id_to_item as $item) {
                $ordered_items[] = $item;
            }
        } elseif ($order_type === 'reverse') {
            $ordered_items = array_reverse($items);
        } elseif ($order_type === 'random') {
            $ordered_items = $items;
            shuffle($ordered_items);
        } else {
            $ordered_items = $items;
        }

        // Remove all current items
        foreach ($items as $item) {
            $container->removeChild($item);
        }
        // Append in new order
        foreach ($ordered_items as $item) {
            $container->appendChild($item);
        }
    }

    // Get the HTML back out, removing the XML encoding hack
    $result = $dom->saveHTML();
    $result = preg_replace('/^<\?xml.*?\?>/', '', $result);
    libxml_clear_errors();
    return $result;
}

