<?php
/**
 * Name: Custom Taxonomy Order
 * Description: Drag-and-drop custom ordering for taxonomy terms with up to 100 items for performance to control category, tag, or custom taxonomy display order manually
 * Category: Content
 * Icon: -
 * UseCases: [
 *   {
 *     "title": "Navigation Menu Order",
 *     "description": "Reorder product categories to match desired menu structure instead of alphabetical, putting bestselling categories first."
 *   },
 *   {
 *     "title": "Filter Display Priority",
 *     "description": "Arrange attribute filters on shop page to show most important filters (price, brand, size) before less common ones."
 *   },
 *   {
 *     "title": "Archive Organization",
 *     "description": "Order blog categories in sidebar to prioritize popular topics over niche categories regardless of alphabetical order."
 *   }
 * ]
 * Added: 2025-09-06
 * Tags: taxonomy, terms, order, sort, categories, tags, drag-drop
 * WhereToFind: {
 *   "location": "Taxonomy Edit Screen",
 *   "url": "edit-tags.php",
 *   "instructions": "Drag and drop to reorder taxonomy terms",
 *   "newTab": false
 * }
 */

// Prevent direct access
if (!defined('ABSPATH')) exit;

class SwitchboardCustomTaxonomyOrder {
    
    public function __construct() {
        add_action('wp_ajax_update_term_order', [$this, 'update_term_order']);
        add_action('wp_ajax_get_term_order_html', [$this, 'get_term_order_html']);
        add_action('wp_ajax_get_terms_for_ordering', [$this, 'ajax_get_terms_for_ordering']);
        add_action('wp_ajax_save_term_order', [$this, 'ajax_save_term_order']);
        add_filter('get_terms_args', [$this, 'apply_custom_order'], 10, 2);
        add_filter('terms_clauses', [$this, 'terms_clauses'], 10, 3);

        // Apply custom order to admin taxonomy tables and post edit screens
        add_filter('get_terms_args', [$this, 'apply_admin_order'], 10, 2);

        // Apply custom order to ACF taxonomy fields
        add_filter('acf/fields/taxonomy/query', [$this, 'apply_acf_taxonomy_order'], 10, 3);

        // Add Terms Order submenu under custom post types for Editors
        add_action('admin_menu', [$this, 'add_post_type_submenus'], 99);
    }
    
    public function register_data_locations() {
        return [
            'options' => [
                'switchboard_custom-taxonomy-order_settings'
            ],
            'option_patterns' => [],
            'tables' => [],
            'post_meta' => [],
            'user_meta' => [],
            'term_meta' => [
                'term_order'
            ],
            'transients' => []
        ];
    }

    /**
     * Add Terms Order submenu page under each custom post type that has taxonomies
     * This allows Editors to reorder taxonomy terms without Switchboard admin access
     */
    public function add_post_type_submenus() {
        // Get all custom post types (excluding built-in ones that don't need this)
        $post_types = get_post_types(['public' => true, '_builtin' => false], 'objects');

        // Also add for posts and pages if they have custom taxonomies
        $post_types['post'] = get_post_type_object('post');
        $post_types['page'] = get_post_type_object('page');

        foreach ($post_types as $post_type => $pt_obj) {
            if (!$pt_obj) {
                continue;
            }

            // Get taxonomies for this post type
            $taxonomies = get_object_taxonomies($post_type, 'objects');

            // Filter to only public taxonomies with terms
            $valid_taxonomies = [];
            foreach ($taxonomies as $tax_slug => $tax_obj) {
                if ($tax_obj->public && $tax_obj->show_ui) {
                    $valid_taxonomies[$tax_slug] = $tax_obj;
                }
            }

            // Skip if no valid taxonomies
            if (empty($valid_taxonomies)) {
                continue;
            }

            // Determine parent menu slug
            if ($post_type === 'post') {
                $parent_slug = 'edit.php';
            } elseif ($post_type === 'page') {
                $parent_slug = 'edit.php?post_type=page';
            } else {
                $parent_slug = 'edit.php?post_type=' . $post_type;
            }

            // Use manage_categories for taxonomy editing
            add_submenu_page(
                $parent_slug,
                sprintf(__('Taxonomy Terms Order for %s', 'wpswitchboard'), $pt_obj->labels->name),
                __('Terms Order', 'wpswitchboard'),
                'manage_categories',
                'switchboard-' . $post_type . '-terms-order',
                function() use ($post_type, $pt_obj, $valid_taxonomies) {
                    $this->render_post_type_terms_order_page($post_type, $pt_obj, $valid_taxonomies);
                }
            );
        }
    }

    /**
     * Render the Terms Order page for a specific post type
     */
    public function render_post_type_terms_order_page($post_type, $pt_obj, $taxonomies) {
        // Enqueue jQuery UI Sortable
        wp_enqueue_script('jquery-ui-sortable');

        // Get selected taxonomy from URL or default to first
        $selected_taxonomy = isset($_GET['taxonomy']) ? sanitize_key($_GET['taxonomy']) : '';
        if (empty($selected_taxonomy) || !isset($taxonomies[$selected_taxonomy])) {
            $selected_taxonomy = key($taxonomies);
        }

        $selected_tax_obj = $taxonomies[$selected_taxonomy];
        $is_hierarchical = is_taxonomy_hierarchical($selected_taxonomy);

        // Check if any terms have custom order set for this taxonomy
        global $wpdb;
        $has_custom_order = $wpdb->get_var(
            $wpdb->prepare(
                "SELECT COUNT(*) FROM {$wpdb->termmeta} tm
                 INNER JOIN {$wpdb->term_taxonomy} tt ON tm.term_id = tt.term_id
                 WHERE tm.meta_key = %s AND tt.taxonomy = %s",
                'term_order',
                $selected_taxonomy
            )
        );

        // Get terms for selected taxonomy
        // Use term_order if custom order exists, otherwise use WordPress default (name)
        $terms = get_terms([
            'taxonomy' => $selected_taxonomy,
            'hide_empty' => false,
            'orderby' => $has_custom_order > 0 ? 'term_order' : 'name',
            'order' => 'ASC',
            'number' => 100
        ]);

        if (is_wp_error($terms)) {
            $terms = [];
        }

        // Build hierarchical structure if taxonomy is hierarchical
        $terms_by_parent = [];
        if ($is_hierarchical && !empty($terms)) {
            foreach ($terms as $term) {
                $parent_id = $term->parent;
                if (!isset($terms_by_parent[$parent_id])) {
                    $terms_by_parent[$parent_id] = [];
                }
                $terms_by_parent[$parent_id][] = $term;
            }
        }

        $term_count = wp_count_terms(['taxonomy' => $selected_taxonomy, 'hide_empty' => false]);
        if (is_wp_error($term_count)) {
            $term_count = 0;
        }

        ?>
        <div class="wrap">
            <h1><?php echo esc_html(sprintf(__('Taxonomy Terms Order for %s', 'wpswitchboard'), $pt_obj->labels->name)); ?></h1>

            <div style="margin: 20px 0;">
                <?php foreach ($taxonomies as $tax_slug => $tax_obj):
                    $count = wp_count_terms(['taxonomy' => $tax_slug, 'hide_empty' => false]);
                    if (is_wp_error($count)) $count = 0;
                    $is_selected = $tax_slug === $selected_taxonomy;
                    $url = add_query_arg(['taxonomy' => $tax_slug], admin_url('admin.php?page=switchboard-' . $post_type . '-terms-order'));
                ?>
                <label style="display: inline-flex; align-items: center; gap: 6px; margin-right: 20px; cursor: pointer;">
                    <input type="radio" name="taxonomy_select" value="<?php echo esc_attr($tax_slug); ?>" <?php checked($is_selected); ?> onchange="window.location.href='<?php echo esc_url($url); ?>'">
                    <strong><?php echo esc_html($tax_obj->labels->name); ?></strong>
                    <span style="color: #666;">(<?php echo esc_html($tax_obj->name); ?>): <?php echo esc_html($count); ?> terms</span>
                </label>
                <?php endforeach; ?>
            </div>

            <?php if ($term_count > 100): ?>
            <div class="notice notice-warning">
                <p><?php esc_html_e('Showing first 100 terms only for performance.', 'wpswitchboard'); ?></p>
            </div>
            <?php endif; ?>

            <?php if ($is_hierarchical): ?>
            <p class="description" style="margin-bottom: 15px;"><?php esc_html_e('Drag terms to reorder. Child terms stay grouped under their parents.', 'wpswitchboard'); ?></p>
            <?php endif; ?>

            <div id="term-order-list" style="max-width: 800px; margin-top: 20px;">
                <?php if (empty($terms)): ?>
                    <p style="color: #666;"><?php esc_html_e('No terms found for this taxonomy.', 'wpswitchboard'); ?></p>
                <?php elseif ($is_hierarchical): ?>
                    <?php $this->render_hierarchical_terms($terms_by_parent, 0, 0); ?>
                <?php else: ?>
                    <?php foreach ($terms as $term): ?>
                    <div class="sortable-term-item" data-id="<?php echo esc_attr($term->term_id); ?>" data-parent="0" style="display: flex; align-items: center; gap: 12px; padding: 12px 16px; background: #fff; border: 1px solid #ddd; margin-bottom: 4px; cursor: move; transition: all 0.15s ease;">
                        <span class="drag-handle" style="color: #9ca3af; cursor: grab; user-select: none; font-size: 16px;">&#9776;</span>
                        <span style="font-size: 11px; color: #999; font-family: monospace;">#<?php echo esc_html($term->term_id); ?></span>
                        <span style="flex: 1; font-weight: 500; color: #2271b1;"><?php echo esc_html($term->name); ?></span>
                        <span style="font-size: 12px; color: #666;"><?php echo esc_html($term->count); ?> <?php esc_html_e('items', 'wpswitchboard'); ?></span>
                    </div>
                    <?php endforeach; ?>
                <?php endif; ?>
            </div>

            <div id="save-indicator" style="display: none; position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: #2271b1; color: white; padding: 12px 24px; border-radius: 4px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); z-index: 999999; font-weight: 500;">
                <?php esc_html_e('Order saved', 'wpswitchboard'); ?>
            </div>
        </div>

        <script>
        jQuery(document).ready(function($) {
            var taxonomy = '<?php echo esc_js($selected_taxonomy); ?>';
            var isHierarchical = <?php echo $is_hierarchical ? 'true' : 'false'; ?>;

            if (isHierarchical) {
                // For hierarchical taxonomies, make each parent group sortable separately
                // Items can be either direct .sortable-term-item OR .term-parent-wrapper (parent with children)
                $(".term-children-group").sortable({
                    items: "> .sortable-term-item, > .term-parent-wrapper",
                    placeholder: "sortable-placeholder",
                    tolerance: "pointer",
                    cursor: "grabbing",
                    opacity: 0.8,
                    connectWith: false, // Don't allow moving between groups
                    start: function(e, ui) {
                        ui.placeholder.height(ui.item.outerHeight());
                        ui.placeholder.css({
                            "background": "#f0f7ff",
                            "border": "2px dashed #2271b1",
                            "margin-bottom": "8px"
                        });
                        ui.item.css("box-shadow", "0 4px 12px rgba(0,0,0,0.15)");
                    },
                    stop: function(e, ui) {
                        ui.item.css("box-shadow", "");
                        saveOrder();
                    }
                });
            } else {
                // For flat taxonomies, make the whole list sortable
                $("#term-order-list").sortable({
                    items: ".sortable-term-item",
                    placeholder: "sortable-placeholder",
                    tolerance: "pointer",
                    cursor: "grabbing",
                    opacity: 0.8,
                    start: function(e, ui) {
                        ui.placeholder.height(ui.item.height());
                        ui.placeholder.css({
                            "background": "#f0f7ff",
                            "border": "2px dashed #2271b1",
                            "margin-bottom": "4px"
                        });
                        ui.item.css("box-shadow", "0 4px 12px rgba(0,0,0,0.15)");
                    },
                    stop: function(e, ui) {
                        ui.item.css("box-shadow", "");
                        saveOrder();
                    }
                });
            }

            function saveOrder() {
                var order = [];
                $("#term-order-list .sortable-term-item").each(function() {
                    order.push($(this).data("id"));
                });

                $.ajax({
                    url: ajaxurl,
                    type: "POST",
                    data: {
                        action: "save_term_order",
                        taxonomy: taxonomy,
                        order: order,
                        nonce: "<?php echo wp_create_nonce('term_ordering'); ?>"
                    },
                    success: function(response) {
                        if (response.success) {
                            $("#save-indicator").fadeIn(200);
                            setTimeout(function() {
                                $("#save-indicator").fadeOut(200);
                            }, 1500);
                        }
                    }
                });
            }
        });
        </script>

        <style>
        .sortable-term-item:hover {
            background: #f9f9f9 !important;
        }
        .sortable-placeholder {
            visibility: visible !important;
        }
        .term-children-group {
            margin-left: 0;
        }
        .term-parent-wrapper {
            margin-bottom: 8px;
        }
        .term-parent-wrapper .term-children-group {
            margin-left: 24px;
            border-left: 2px solid #e5e7eb;
            padding-left: 12px;
            margin-top: 4px;
        }
        </style>
        <?php
    }

    /**
     * Recursively render hierarchical terms with proper nesting
     */
    private function render_hierarchical_terms($terms_by_parent, $parent_id, $level) {
        if (!isset($terms_by_parent[$parent_id])) {
            return;
        }

        $is_top_level = ($parent_id === 0);

        if ($is_top_level) {
            echo '<div class="term-children-group" data-parent="0">';
        }

        foreach ($terms_by_parent[$parent_id] as $term) {
            $has_children = isset($terms_by_parent[$term->term_id]);

            if ($has_children) {
                // Parent term with children - wrap in container
                echo '<div class="term-parent-wrapper">';
            }

            $indent = $level > 0 ? '' : ''; // Indent handled by CSS
            ?>
            <div class="sortable-term-item" data-id="<?php echo esc_attr($term->term_id); ?>" data-parent="<?php echo esc_attr($parent_id); ?>" style="display: flex; align-items: center; gap: 12px; padding: 12px 16px; background: #fff; border: 1px solid #ddd; margin-bottom: 4px; cursor: move; transition: all 0.15s ease;">
                <span class="drag-handle" style="color: #9ca3af; cursor: grab; user-select: none; font-size: 16px;">&#9776;</span>
                <span style="font-size: 11px; color: #999; font-family: monospace;">#<?php echo esc_html($term->term_id); ?></span>
                <span style="flex: 1; font-weight: 500; color: #2271b1;"><?php echo esc_html($term->name); ?></span>
                <?php if ($has_children): ?>
                <span style="font-size: 11px; color: #666; background: #f0f0f0; padding: 2px 6px; border-radius: 3px;"><?php esc_html_e('parent', 'wpswitchboard'); ?></span>
                <?php endif; ?>
                <span style="font-size: 12px; color: #666;"><?php echo esc_html($term->count); ?> <?php esc_html_e('items', 'wpswitchboard'); ?></span>
            </div>
            <?php

            // Render children if any
            if ($has_children) {
                echo '<div class="term-children-group" data-parent="' . esc_attr($term->term_id) . '">';
                $this->render_hierarchical_terms($terms_by_parent, $term->term_id, $level + 1);
                echo '</div>';
                echo '</div>'; // Close term-parent-wrapper
            }
        }

        if ($is_top_level) {
            echo '</div>';
        }
    }

    public function ajax_get_terms_for_ordering() {
        check_ajax_referer('term_ordering', 'nonce');
        
        if (!current_user_can('manage_categories')) {
            wp_send_json_error(['message' => esc_html__('Permission denied', 'wpswitchboard')]);
        }
        
        $taxonomy = isset($_POST['taxonomy']) ? sanitize_key($_POST['taxonomy']) : 'category';
        
        $terms = get_terms([
            'taxonomy' => $taxonomy,
            'hide_empty' => false,
            'orderby' => 'term_order',
            'order' => 'ASC',
            'number' => 100
        ]);
        
        $terms_data = [];
        $is_hierarchical = is_taxonomy_hierarchical($taxonomy);

        if (!empty($terms) && !is_wp_error($terms)) {
            foreach ($terms as $term) {
                $terms_data[] = [
                    'term_id' => $term->term_id,
                    'name' => $term->name,
                    'count' => $term->count,
                    'parent' => $term->parent
                ];
            }
        }

        wp_send_json_success(['terms' => $terms_data, 'is_hierarchical' => $is_hierarchical]);
    }
    
    public function ajax_save_term_order() {
        check_ajax_referer('term_ordering', 'nonce');
        
        if (!current_user_can('manage_categories')) {
            wp_send_json_error(['message' => esc_html__('Permission denied', 'wpswitchboard')]);
        }
        
        $taxonomy = isset($_POST['taxonomy']) ? sanitize_key($_POST['taxonomy']) : '';
        $order = isset($_POST['order']) ? array_map('intval', $_POST['order']) : [];
        
        if (empty($order)) {
            wp_send_json_error(['message' => esc_html__('No order data', 'wpswitchboard')]);
        }
        
        foreach ($order as $index => $term_id) {
            update_term_meta($term_id, 'term_order', $index);
        }
        
        wp_send_json_success(['message' => esc_html__('Order saved', 'wpswitchboard')]);
    }
    
    /**
     * Render settings panel for Custom Taxonomy Order
     * 
     * Displays an interactive drag-and-drop interface for reordering taxonomy terms:
     * - Click taxonomy badges to load terms (up to 100 items for performance)
     * - jQuery UI Sortable for drag-and-drop reordering
     * - Auto-save functionality via AJAX
     * - Visual feedback with animations
     * - Optional frontend application of custom order
     * 
     * Order is stored in term_meta as 'term_order' for each term
     * 
     * @param array $settings Current module settings (taxonomies, apply_frontend)
     * @return void Outputs HTML directly
     */
    public function render_settings($settings = []) {
        $selected_taxonomies = isset($settings['taxonomies']) ? explode(',', $settings['taxonomies']) : ['category'];
        $selected_taxonomies = array_map('trim', $selected_taxonomies);
        $all_taxonomies = get_taxonomies(['public' => true], 'objects');
        ?>
        <input type="hidden" name="module" value="custom-taxonomy-order">
        <input type="hidden" name="setting_taxonomies" id="setting_taxonomies" value="<?php echo esc_attr(implode(',', $selected_taxonomies)); ?>">
        <input type="hidden" name="setting_term_order" id="setting_term_order" value="">

        <div class="sb-sp-group">
            <label class="sb-sp-label"><?php esc_html_e('Select Taxonomy to Reorder', 'wpswitchboard'); ?></label>
            <p class="sb-sp-description"><?php esc_html_e('Click a taxonomy to reorder its terms. Up to 100 terms will be shown for performance.', 'wpswitchboard'); ?></p>

            <div style="display: flex; gap: 8px; flex-wrap: wrap;">
                <?php foreach ($all_taxonomies as $tax_slug => $tax_obj): ?>
                    <button type="button" class="taxonomy-badge switchboard-tag-option" data-taxonomy="<?php echo esc_attr($tax_slug); ?>">
                        <?php echo esc_html($tax_obj->label); ?>
                    </button>
                <?php endforeach; ?>
            </div>
        </div>

        <div id="term-order-container" style="display: none; margin-bottom: 24px;">
            <div class="sb-sp-group">
                <label class="sb-sp-label"><?php esc_html_e('Drag to Reorder Terms', 'wpswitchboard'); ?></label>
                <p class="sb-sp-description"><?php esc_html_e('Click and drag items to your desired order. Changes save automatically.', 'wpswitchboard'); ?></p>
            </div>
            <div id="term-order-list" class="sb-sp-sortable-container">
                <div style="text-align: center; padding: 16px;">
                    <span style="color: #64748b;"><?php esc_html_e('Select a taxonomy above', 'wpswitchboard'); ?></span>
                </div>
            </div>
        </div>
        
        <script>
        jQuery(document).ready(function($) {
            var termOrderSortable;
            var currentTaxonomy = null;

            $(".taxonomy-badge").on("click", function() {
                var $badge = $(this);
                var taxonomy = $badge.data("taxonomy");
                
                // Don't reload if already active
                if (currentTaxonomy === taxonomy) {
                    return;
                }
                
                currentTaxonomy = taxonomy;
                
                // Update badge styles
                $(".taxonomy-badge").removeClass("selected");
                $badge.addClass("selected");

                $("#term-order-container").show();
                $("#term-order-list").html('<div style="text-align: center; padding: 16px;"><span style="color: #64748b;">Loading terms...</span></div>');
                
                // Destroy existing sortable if it exists
                if (termOrderSortable) {
                    try { $("#term-order-list").sortable("destroy"); } catch(e) {}
                    try { $("#term-order-list .term-children-group").sortable("destroy"); } catch(e) {}
                    termOrderSortable = null;
                }

                $.ajax({
                    url: ajaxurl,
                    type: "POST",
                    data: {
                        action: "get_terms_for_ordering",
                        taxonomy: taxonomy,
                        nonce: "<?php echo wp_create_nonce('term_ordering'); ?>"
                    },
                    success: function(response) {
                        if (response.success && response.data.terms) {
                            var terms = response.data.terms;
                            var isHierarchical = response.data.is_hierarchical;
                            var html = "";

                            if (isHierarchical) {
                                html = buildHierarchicalTermsHtml(terms);
                            } else {
                                $.each(terms, function(i, term) {
                                    html += buildTermItemHtml(term, i);
                                });
                            }

                            $("#term-order-list").html(html);
                            updateTermNumbers();

                            if (isHierarchical) {
                                initHierarchicalSortable(taxonomy);
                            } else {
                                initFlatSortable(taxonomy);
                            }
                        } else {
                            $("#term-order-list").html('<div style="text-align: center; padding: 16px;"><span style="color: #64748b;">No terms found for this taxonomy.</span></div>');
                        }
                    },
                    error: function() {
                        $("#term-order-list").html('<div style="padding: 12px 16px; background: #fef2f2; border: 1px solid #fecaca; border-radius: 6px; color: #dc2626; font-size: 14px; text-align: center;">Error loading terms. Please try again.</div>');
                    }
                });
            });
            
            function buildTermItemHtml(term, index, hasChildren) {
                var html = '<div class="sortable-term-item sb-sp-sortable-item" data-id="' + term.term_id + '">';
                html += '<span class="sb-sp-drag-handle">⋮⋮</span>';
                html += '<span class="sb-sp-badge-number">' + (index + 1) + '</span>';
                html += '<span style="flex: 1;">' + $('<span>').text(term.name).html() + '</span>';
                if (hasChildren) {
                    html += '<span style="font-size: 11px; color: #666; background: #f0f0f0; padding: 2px 8px; border-radius: 3px;">parent</span>';
                }
                html += '<span style="font-size: 13px; color: #64748b;">Count: ' + term.count + '</span>';
                html += '</div>';
                return html;
            }

            function buildHierarchicalTermsHtml(terms) {
                // Group terms by parent
                var byParent = {};
                $.each(terms, function(i, term) {
                    var pid = term.parent || 0;
                    if (!byParent[pid]) byParent[pid] = [];
                    byParent[pid].push(term);
                });

                function renderLevel(parentId) {
                    var children = byParent[parentId] || [];
                    var html = "";
                    var counter = {n: 0};
                    $.each(children, function(i, term) {
                        var hasChildren = !!byParent[term.term_id];
                        if (hasChildren) {
                            html += '<div class="term-parent-wrapper">';
                            html += buildTermItemHtml(term, counter.n++, true);
                            html += '<div class="term-children-group" data-parent="' + term.term_id + '" style="margin-left: 24px; border-left: 2px solid #e5e7eb; padding-left: 12px; margin-top: 4px; margin-bottom: 8px;">';
                            html += renderLevel(term.term_id);
                            html += '</div></div>';
                        } else {
                            html += buildTermItemHtml(term, counter.n++, false);
                        }
                    });
                    return html;
                }

                return renderLevel(0);
            }

            function initFlatSortable(taxonomy) {
                termOrderSortable = $("#term-order-list").sortable({
                    items: "> .sortable-term-item",
                    placeholder: "sortable-placeholder",
                    tolerance: "pointer",
                    cursor: "grabbing",
                    opacity: 0.8,
                    start: function(e, ui) {
                        ui.placeholder.height(ui.item.height());
                        ui.placeholder.css({
                            "background": "var(--primary-100)",
                            "border": "2px dashed var(--primary-500)",
                            "border-radius": "4px",
                            "margin-bottom": "8px"
                        });
                        ui.item.css("box-shadow", "0 4px 12px rgba(0,0,0,0.15)");
                    },
                    stop: function(e, ui) {
                        ui.item.css("box-shadow", "");
                        updateTermNumbers();
                        saveTermOrder(taxonomy);
                    }
                });
            }

            function initHierarchicalSortable(taxonomy) {
                var sortableOpts = {
                    items: "> .sortable-term-item, > .term-parent-wrapper",
                    placeholder: "sortable-placeholder",
                    tolerance: "pointer",
                    cursor: "grabbing",
                    opacity: 0.8,
                    connectWith: false,
                    start: function(e, ui) {
                        ui.placeholder.height(ui.item.height());
                        ui.placeholder.css({
                            "background": "var(--primary-100)",
                            "border": "2px dashed var(--primary-500)",
                            "border-radius": "4px",
                            "margin-bottom": "8px"
                        });
                        ui.item.css("box-shadow", "0 4px 12px rgba(0,0,0,0.15)");
                    },
                    stop: function(e, ui) {
                        ui.item.css("box-shadow", "");
                        updateTermNumbers();
                        saveTermOrder(taxonomy);
                    }
                };

                // Top-level sortable (parents + standalone terms)
                termOrderSortable = $("#term-order-list").sortable(sortableOpts);

                // Each children group is independently sortable
                $("#term-order-list .term-children-group").each(function() {
                    $(this).sortable({
                        items: "> .sortable-term-item",
                        placeholder: "sortable-placeholder",
                        tolerance: "pointer",
                        cursor: "grabbing",
                        opacity: 0.8,
                        connectWith: false,
                        start: function(e, ui) {
                            ui.placeholder.height(ui.item.height());
                            ui.placeholder.css({
                                "background": "var(--primary-100)",
                                "border": "2px dashed var(--primary-500)",
                                "border-radius": "4px",
                                "margin-bottom": "8px"
                            });
                            ui.item.css("box-shadow", "0 4px 12px rgba(0,0,0,0.15)");
                        },
                        stop: function(e, ui) {
                            ui.item.css("box-shadow", "");
                            updateTermNumbers();
                            saveTermOrder(taxonomy);
                        }
                    });
                });
            }

            function updateTermNumbers() {
                var n = 1;
                $("#term-order-list .sortable-term-item").each(function() {
                    $(this).find(".sb-sp-badge-number").text(n++);
                });
            }
            
            function saveTermOrder(taxonomy) {
                var order = [];
                $("#term-order-list .sortable-term-item").each(function() {
                    order.push($(this).data("id"));
                });
                
                $("#setting_term_order").val(JSON.stringify({
                    taxonomy: taxonomy,
                    order: order
                }));
                
                // Auto-save via AJAX
                $.ajax({
                    url: ajaxurl,
                    type: "POST",
                    data: {
                        action: "save_term_order",
                        taxonomy: taxonomy,
                        order: order,
                        nonce: "<?php echo wp_create_nonce('term_ordering'); ?>"
                    },
                    success: function(response) {
                        if (response.success) {
                            // Show brief success indicator
                            var indicator = $('<div style="position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: var(--primary-500); color: white; padding: 12px 24px; border-radius: 6px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); z-index: 999999; font-weight: 500;">Order saved</div>');
                            $('body').append(indicator);
                            setTimeout(function() {
                                indicator.fadeOut(function() { $(this).remove(); });
                            }, 1500);
                        }
                    }
                });
            }

            // Auto-click first taxonomy badge on load (after handlers are attached)
            if ($(".taxonomy-badge").length > 0) {
                $(".taxonomy-badge").first().click();
            }
        });
        </script>

        <div class="sb-module-option-item sb-module-toggle-item">
            <div class="sb-module-option-content">
                <div class="sb-module-toggle-header">
                    <strong class="sb-module-option-title"><?php esc_html_e('Apply on Frontend', 'wpswitchboard'); ?></strong>
                    <label class="sb-module-toggle">
                        <input type="checkbox"
                               name="setting_apply_frontend"
                               value="yes"
                               <?php checked(isset($settings['apply_frontend']) && $settings['apply_frontend'] === 'yes', true); ?>>
                        <span class="sb-module-toggle-slider"></span>
                    </label>
                </div>
                <p class="sb-module-option-text"><?php esc_html_e('Apply custom order to frontend queries (category widgets, tag clouds, etc.)', 'wpswitchboard'); ?></p>
            </div>
        </div>
        <?php
    }
    
    public function get_term_order_html() {
        check_ajax_referer('switchboard_settings', 'nonce');
        
        if (!current_user_can('manage_categories')) {
            wp_send_json_error(['message' => esc_html__('Permission denied', 'wpswitchboard')]);
        }
        
        $taxonomy = isset($_POST['taxonomy']) ? sanitize_key($_POST['taxonomy']) : 'category';
        
        $terms = get_terms([
            'taxonomy' => $taxonomy,
            'hide_empty' => false,
            'orderby' => 'term_order',
            'order' => 'ASC',
            'number' => 100 // Performance limit
        ]);
        
        ob_start();
        if (empty($terms) || is_wp_error($terms)) {
            echo '<p style="color:#666;">No terms found for this taxonomy.</p>';
        } else {
            echo '<ul class="sortable-term-list" data-taxonomy="' . esc_attr($taxonomy) . '" style="list-style:none; padding:0; margin:10px 0;">';
            $this->render_terms_list($terms, 0, 0);
            echo '</ul>';
            
            if (count($terms) >= 100) {
                echo '<p style="color: #dc2626;">Showing first 100 terms only for performance.</p>';
            }
        }
        
        $html = ob_get_clean();
        wp_send_json_success(['html' => $html]);
    }
    
    private function render_terms_list($terms, $parent = 0, $level = 0) {
        foreach ($terms as $term) {
            if ($term->parent != $parent) continue;
            
            $margin = $level > 0 ? 'margin-left:' . ($level * 20) . 'px;' : '';
            echo '<li class="sortable-term-item" style="background: white;" data-id="' . esc_attr($term->term_id) . '">';
            echo '<span style="color:#8c8f94;">☰</span>';
            echo '<span class="sb-sp-label">' . esc_html($term->name) . '</span>';
            echo '<span class="sb-sp-text-xs">' . $term->count . ' items</span>';
            echo '</li>';
            
            // Render children
            $this->render_terms_list($terms, $term->term_id, $level + 1);
        }
    }
    
    public function update_term_order() {
        check_ajax_referer('switchboard_settings', 'nonce');
        
        if (!current_user_can('manage_categories')) {
            wp_send_json_error(['message' => esc_html__('Permission denied', 'wpswitchboard')]);
        }
        
        $order = isset($_POST['order']) ? array_map('intval', wp_unslash($_POST['order'])) : [];

        foreach ($order as $index => $term_id) {
            update_term_meta($term_id, 'term_order', $index);
        }
        
        wp_send_json_success();
    }
    
    /**
     * Apply custom order to admin taxonomy list tables and post edit screens
     * This ensures the custom order shows in wp-admin/edit-tags.php, term.php,
     * post.php, and post-new.php pages
     */
    public function apply_admin_order($args, $taxonomies) {
        // Only apply in admin area
        if (!is_admin()) {
            return $args;
        }

        // Check if we're on a taxonomy edit screen or post edit screen
        global $pagenow;
        $admin_pages = ['edit-tags.php', 'term.php', 'post.php', 'post-new.php'];
        if (!in_array($pagenow, $admin_pages, true)) {
            return $args;
        }

        // Get all public taxonomies (these are the ones we allow ordering)
        $all_taxonomies = get_taxonomies(['public' => true]);

        // Apply custom order to any public taxonomy in admin
        foreach ($taxonomies as $taxonomy) {
            if (in_array($taxonomy, $all_taxonomies)) {
                // Check if any terms have custom order set for THIS SPECIFIC taxonomy (cached per request)
                $cache_key = 'switchboard_tax_order_' . $taxonomy;
                $has_custom_order = wp_cache_get($cache_key, 'switchboard');
                if ($has_custom_order === false) {
                    global $wpdb;
                    $has_custom_order = $wpdb->get_var(
                        $wpdb->prepare(
                            "SELECT COUNT(*) FROM {$wpdb->termmeta} tm
                             INNER JOIN {$wpdb->term_taxonomy} tt ON tm.term_id = tt.term_id
                             WHERE tm.meta_key = %s AND tt.taxonomy = %s",
                            'term_order',
                            $taxonomy
                        )
                    );
                    wp_cache_set($cache_key, $has_custom_order, 'switchboard');
                }

                if ($has_custom_order > 0) {
                    $args['orderby'] = 'term_order';
                    $args['order'] = 'ASC';
                }
                break;
            }
        }

        return $args;
    }

    /**
     * Apply custom order to ACF taxonomy fields
     * This ensures the custom order shows in ACF taxonomy field dropdowns and checkboxes
     *
     * @param array $args The query arguments for get_terms()
     * @param array $field The ACF field settings
     * @param int $post_id The post ID being edited
     * @return array Modified query arguments
     */
    public function apply_acf_taxonomy_order($args, $field, $post_id) {
        // Get the taxonomy from ACF field settings
        $taxonomy = isset($field['taxonomy']) ? $field['taxonomy'] : '';

        if (empty($taxonomy)) {
            return $args;
        }

        // Check if any terms have custom order set for this taxonomy
        global $wpdb;
        $has_custom_order = $wpdb->get_var(
            $wpdb->prepare(
                "SELECT COUNT(*) FROM {$wpdb->termmeta} tm
                 INNER JOIN {$wpdb->term_taxonomy} tt ON tm.term_id = tt.term_id
                 WHERE tm.meta_key = %s AND tt.taxonomy = %s",
                'term_order',
                $taxonomy
            )
        );

        if ($has_custom_order > 0) {
            $args['orderby'] = 'term_order';
            $args['order'] = 'ASC';
        }

        return $args;
    }

    public function apply_custom_order($args, $taxonomies) {
        $settings = get_option('switchboard_custom-taxonomy-order_settings', []);

        if (!isset($settings['apply_frontend']) || $settings['apply_frontend'] !== 'yes') {
            return $args;
        }

        $enabled_taxonomies = isset($settings['taxonomies']) ? explode(',', $settings['taxonomies']) : ['category', 'post_tag'];
        $enabled_taxonomies = array_map('trim', $enabled_taxonomies);

        foreach ($taxonomies as $taxonomy) {
            if (in_array($taxonomy, $enabled_taxonomies)) {
                $args['orderby'] = 'term_order';
                $args['order'] = 'ASC';
                break;
            }
        }

        return $args;
    }
    
    public function terms_clauses($clauses, $taxonomies, $args) {
        global $wpdb;

        if (isset($args['orderby']) && $args['orderby'] === 'term_order') {
            $clauses['join'] .= " LEFT JOIN {$wpdb->termmeta} AS tm ON t.term_id = tm.term_id AND tm.meta_key = 'term_order'";
            // Order by term_order, then by name for terms without custom order
            // Use COALESCE to handle NULL values (terms without term_order set)
            // This ensures terms with custom order come first, then alphabetical fallback
            $clauses['orderby'] = "ORDER BY COALESCE(CAST(tm.meta_value AS UNSIGNED), 999999), t.name";
        }

        return $clauses;
    }
}

// Initialize and register with data system
$switchboard_instance = new SwitchboardCustomTaxonomyOrder();
Switchboard_Data_Registry::get_instance()->auto_register($switchboard_instance, __FILE__);
