<?php
/**
 * Name: Media Categories
 * Description: Organize media files into custom categories with drag-and-drop management, filtering, and taxonomy system for better media library organization in large sites
 * Category: Media
 * Icon: -
 * UseCases: [
 *   {
 *     "title": "Organize 5000+ Images",
 *     "description": "Create categories like 'Product Photos', 'Blog Graphics', 'Team Headshots' to organize massive media library instead of endless scrolling."
 *   },
 *   {
 *     "title": "Multi-Site Asset Management",
 *     "description": "Separate client media into categories on agency multisite network so each brand's assets are organized and quickly accessible."
 *   },
 *   {
 *     "title": "Seasonal Content Organization",
 *     "description": "Sort holiday graphics, summer promotions, and year-round assets into seasonal categories for easy reuse each year."
 *   }
 * ]
 * Added: 2025-08-25
 * Tags: media, categories, organization, library, taxonomy
 * WhereToFind: {
 *   "location": "Media Library",
 *   "url": "upload.php",
 *   "instructions": "Media Category filter dropdown will appear in media library",
 *   "newTab": false
 * }
 */

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

class SwitchboardMediaCategories {

    /**
     * Static property to store the AJAX category ID for filtering
     * Used across filter_by_category, filter_where, and ajax_query_attachments_args
     */
    private static $ajax_category_id = null;

    public function __construct() {
        add_action('init', [$this, 'register_taxonomy'], 0); // Priority 0 to register early
        add_action('admin_enqueue_scripts', [$this, 'enqueue_scripts']);
        add_action('admin_footer', [$this, 'render_media_sidebar']);
        add_filter('manage_media_columns', [$this, 'add_category_column']);
        add_action('manage_media_custom_column', [$this, 'display_category_column'], 10, 2);
        add_action('restrict_manage_posts', [$this, 'add_category_filter']);
        add_filter('posts_where', [$this, 'filter_where'], 10, 2);
        add_filter('posts_join', [$this, 'filter_join'], 10, 2);
        add_filter('pre_get_posts', [$this, 'filter_by_category']);
        add_filter('ajax_query_attachments_args', [$this, 'ajax_query_attachments_args'], 10, 1);
        // Also hook into the AJAX action directly as a backup (runs very early)
        add_action('wp_ajax_query-attachments', [$this, 'capture_ajax_category'], 5);
        add_filter('bulk_actions-upload', [$this, 'add_bulk_actions']);
        add_filter('handle_bulk_actions-upload', [$this, 'handle_bulk_actions'], 10, 3);
        add_action('wp_ajax_switchboard_assign_media_category', [$this, 'ajax_assign_category']);
        add_action('wp_ajax_switchboard_get_media_categories', [$this, 'ajax_get_categories']);
        add_action('wp_ajax_switchboard_create_category', [$this, 'ajax_create_category']);
        add_action('wp_ajax_switchboard_update_category', [$this, 'ajax_update_category']);
        add_action('wp_ajax_switchboard_delete_category', [$this, 'ajax_delete_category']);
        add_action('wp_ajax_switchboard_download_category_zip', [$this, 'ajax_download_category_zip']);
        add_action('wp_ajax_switchboard_get_category_zip_status', [$this, 'ajax_get_category_zip_status']);
        add_action('wp_ajax_switchboard_delete_category_zip', [$this, 'ajax_delete_category_zip']);
        add_action('wp_ajax_switchboard_get_existing_category_zips', [$this, 'ajax_get_existing_category_zips']);

        // Upload-to-category: set target category and auto-assign on upload
        add_action('wp_ajax_switchboard_set_upload_category', [$this, 'ajax_set_upload_category']);
        add_action('add_attachment', [$this, 'auto_assign_upload_category']);

        // Custom media order within categories
        add_action('wp_ajax_switchboard_save_media_order', [$this, 'ajax_save_media_order']);
        add_action('delete_attachment', [$this, 'on_delete_attachment']);

        // Register taxonomy immediately if not in init hook yet
        if (did_action('init')) {
            $this->register_taxonomy();
        }
    }

    /**
     * Register data locations for cleanup system
     *
     * @return array Data storage locations
     */
    public function register_data_locations() {
        return [
            'options' => [],
            'option_patterns' => [],
            'tables' => [],
            'post_meta' => [],
            'user_meta' => [],
        ];
    }
    
    public function register_taxonomy() {
        // Check if already registered
        if (taxonomy_exists('media_category')) {
            return;
        }
        
        register_taxonomy('media_category', 'attachment', [
            'labels' => [
                'name' => __('Switchboard Media Categories', 'wpswitchboard'),
                'singular_name' => __('Media Category', 'wpswitchboard'),
                'menu_name' => __('Categories', 'wpswitchboard'),
                'all_items' => __('All Categories', 'wpswitchboard'),
                'edit_item' => __('Edit Category', 'wpswitchboard'),
                'view_item' => __('View Category', 'wpswitchboard'),
                'update_item' => __('Update Category', 'wpswitchboard'),
                'add_new_item' => __('Add New Category', 'wpswitchboard'),
                'new_item_name' => __('New Category Name', 'wpswitchboard'),
                'search_items' => __('Search Categories', 'wpswitchboard'),
            ],
            'hierarchical' => true,
            'show_ui' => true,
            'show_in_menu' => false,
            'show_admin_column' => false,
            'query_var' => true,
            'rewrite' => ['slug' => 'media-category'],
            'show_in_rest' => true,
            'update_count_callback' => '_update_generic_term_count',
        ]);
    }
    
    public function enqueue_scripts($hook) {
        // Only load jQuery UI on pages that use the media library
        $media_pages = ['upload.php', 'post.php', 'post-new.php', 'media-new.php'];
        if (!in_array($hook, $media_pages, true)) {
            return;
        }

        wp_enqueue_script('jquery-ui-draggable');
        wp_enqueue_script('jquery-ui-droppable');
        wp_enqueue_script('jquery-ui-sortable');

        // Localize data for the media filter
        $user_id = get_current_user_id();
        $upload_category = get_transient('switchboard_upload_category_' . $user_id);
        $category_data = $this->get_category_data();
        $category_data['upload_category'] = intval($upload_category ?: 0);
        $category_data['nonce'] = wp_create_nonce('media_categories');
        wp_localize_script('media-views', 'switchboardMediaCategoryData', $category_data);

        // Localize language strings
        wp_localize_script('media-views', 'switchboardMediaCategoryLang', [
            'all_categories' => __('All Categories', 'wpswitchboard'),
            'uncategorized' => __('Uncategorized', 'wpswitchboard'),
        ]);

        // Add inline Backbone.js filter script (after media-views is loaded)
        wp_add_inline_script('media-views', $this->get_media_category_filter_script(), 'after');

        // Add AJAX interceptor script
        wp_add_inline_script('media-views', $this->get_ajax_interceptor_script(), 'after');

        // Add upload-to-category integration for wp.media modal
        wp_add_inline_script('media-views', $this->get_upload_category_script(), 'after');
    }

    /**
     * Get category data for the Backbone.js filter
     *
     * @return array Category data with hierarchical structure
     */
    private function get_category_data() {
        $categories = get_terms([
            'taxonomy' => 'media_category',
            'hide_empty' => false,
        ]);

        if (is_wp_error($categories)) {
            return ['categories' => [], 'children' => []];
        }

        $parent_categories = [];
        $child_categories = [];

        foreach ($categories as $category) {
            if ($category->parent === 0) {
                $parent_categories[] = [
                    'id' => $category->term_id,
                    'name' => $category->name,
                ];
            } else {
                if (!isset($child_categories[$category->parent])) {
                    $child_categories[$category->parent] = [];
                }
                $child_categories[$category->parent][] = [
                    'id' => $category->term_id,
                    'name' => $category->name,
                ];
            }
        }

        return [
            'categories' => $parent_categories,
            'children' => $child_categories,
        ];
    }

    /**
     * Get Backbone.js media category filter script
     *
     * Inline Backbone.js code that extends WordPress media modal with a category filter.
     * Integrates with WordPress media framework's toolbar system.
     */
    private function get_media_category_filter_script() {
        return <<<'JS'
// Switchboard Media Categories - Backbone.js Integration
window.switchboardMedia = window.switchboardMedia || {};

(function() {
    if (typeof wp === 'undefined' || !wp.media) {
        return;
    }

    // Extends wp.media.view.AttachmentFilters to create a proper category filter
    var SwitchboardMediaCategoryFilter = wp.media.view.AttachmentFilters.extend({
        id: 'switchboard-media-category-filter',
        className: 'attachment-filters',

        createFilters: function() {
            var filters = {};

            // Ensure we have category data
            if (!window.switchboardMediaCategoryData || !window.switchboardMediaCategoryData.categories) {
                return;
            }

            var data = window.switchboardMediaCategoryData;
            var categories = data.categories || [];
            var children = data.children || {};

            // Add "All Categories" option
            filters.all = {
                text: switchboardMediaCategoryLang.all_categories,
                priority: 1
            };
            filters.all['props'] = {};
            filters.all['props']['switchboard_media_category'] = '';

            // Add "Uncategorized" option
            filters.uncategorized = {
                text: switchboardMediaCategoryLang.uncategorized,
                priority: 2
            };
            filters.uncategorized['props'] = {};
            filters.uncategorized['props']['switchboard_media_category'] = 'uncategorized';

            // Add parent categories
            categories.forEach(function(category) {
                var key = 'category-' + category.id;
                filters[key] = {
                    text: category.name,
                    priority: 10 + category.id
                };
                filters[key]['props'] = {};
                filters[key]['props']['switchboard_media_category'] = category.id;

                // Add child categories (indented)
                if (children[category.id]) {
                    children[category.id].forEach(function(child) {
                        var childKey = 'category-' + child.id;
                        filters[childKey] = {
                            text: '    ' + child.name,
                            priority: 10 + category.id + 0.1 * (children[category.id].indexOf(child) + 1)
                        };
                        filters[childKey]['props'] = {};
                        filters[childKey]['props']['switchboard_media_category'] = child.id;
                    });
                }
            });

            this.filters = filters;
        },

        onChange: function() {
            wp.media.view.AttachmentFilters.prototype.onChange.apply(this, arguments);
        }
    });

    // Extend AttachmentsBrowser to inject our custom filter into the toolbar
    var AttachmentsBrowser = wp.media.view.AttachmentsBrowser;
    wp.media.view.AttachmentsBrowser = wp.media.view.AttachmentsBrowser.extend({
        createToolbar: function() {
            var that = this;

            // Call the original toolbar creation
            AttachmentsBrowser.prototype.createToolbar.call(this);

            // Add our category filter to the toolbar
            var filter = new SwitchboardMediaCategoryFilter({
                controller: that.controller,
                model: that.collection.props,
                priority: -80
            }).render();

            that.toolbar.set('switchboard-category-filter', filter);
        }
    });

    // Store the filter state globally for debugging and reference
    window.switchboardMedia.categoryFilter = {
        currentValue: '',
        lastUpdated: null,
        getActiveFilter: function() {
            return this.currentValue;
        }
    };

})();
JS;
    }

    /**
     * Get inline script to intercept AJAX and inject media_category parameter
     *
     * This script ensures that when the Backbone.js filter updates the model,
     * the media_category parameter is properly included in the AJAX request.
     */
    private function get_ajax_interceptor_script() {
        return <<<'JS'
/**
 * AJAX Interceptor for Media Category Filter
 *
 * This minimal script ensures the Backbone.js filter's model properties
 * are properly converted to query parameters in the AJAX request.
 * Only activates if no external media category plugin is intercepting.
 */

if (window.wp && window.wp.media) {
    jQuery(document).on('media:frame:ready', function() {
        // Check if an external media category plugin has already hooked ajax
        // Look for signs that another plugin is handling query-attachments
        var externalPluginActive = false;
        if (window._wpmcm || window.wp_media_categories) {
            externalPluginActive = true;
        }

        // Only inject our interceptor if no external plugin is active
        if (externalPluginActive) {
            return;
        }

        var original_ajax_send = window.wp.ajax.send;

        window.wp.ajax.send = function(actionOrOptions, optionsParam) {
            var options = actionOrOptions;

            if (typeof actionOrOptions === 'string') {
                options = optionsParam || {};
                options.data = options.data || {};
                options.data.action = actionOrOptions;
            }

            if (options && options.data && options.data.action === 'query-attachments') {
                if (!options.data.query) {
                    options.data.query = {};
                }

                if (window.wp.media.frame) {
                    try {
                        var state = window.wp.media.frame.state();
                        if (state && state.get) {
                            var props = state.get('props');
                            if (props && props.get) {
                                var categoryVal = props.get('switchboard_media_category');
                                if (categoryVal) {
                                    options.data.query.switchboard_media_category = categoryVal;
                                }
                            }
                        }
                    } catch (e) {
                        // Ignore errors, let normal flow continue
                    }
                }
            }

            return original_ajax_send.call(this, actionOrOptions, optionsParam);
        };
    });
}
JS;
    }

    /**
     * Get inline script for upload-to-category in wp.media modal
     */
    private function get_upload_category_script() {
        return <<<'JS'
// Switchboard Media Categories - Upload-to-Category Modal Integration
(function() {
    if (typeof wp === 'undefined' || !wp.media || !wp.media.view || !wp.media.view.UploaderInline) {
        return;
    }

    var data = window.switchboardMediaCategoryData;
    if (!data || !data.categories || data.categories.length === 0) {
        return;
    }

    var OriginalUploaderInline = wp.media.view.UploaderInline;
    wp.media.view.UploaderInline = wp.media.view.UploaderInline.extend({
        ready: function() {
            OriginalUploaderInline.prototype.ready.apply(this, arguments);

            // Only add once
            if (this.$el.find('#switchboard-modal-upload-category').length) {
                return;
            }

            var categories = data.categories || [];
            var children = data.children || {};
            var uploadCategory = data.upload_category || 0;

            var opts = '<option value="0">No category</option>';
            categories.forEach(function(cat) {
                var sel = (uploadCategory == cat.id) ? ' selected' : '';
                opts += '<option value="' + cat.id + '"' + sel + '>' + cat.name + '</option>';
                var kids = children[cat.id] || [];
                kids.forEach(function(child) {
                    var childSel = (uploadCategory == child.id) ? ' selected' : '';
                    opts += '<option value="' + child.id + '"' + childSel + '>&nbsp;&nbsp;' + child.name + '</option>';
                });
            });

            var $wrapper = jQuery('<div class="switchboard-upload-category-modal" style="text-align:center;margin:10px 0;">' +
                '<label style="margin-right:8px;font-weight:500;font-size:13px;">Upload to category:</label>' +
                '<select id="switchboard-modal-upload-category" style="padding:4px 8px;border:1px solid #d1d5db;border-radius:4px;font-size:13px;">' + opts + '</select>' +
                '</div>');

            var $uploadUI = this.$el.find('.upload-ui');
            if ($uploadUI.length) {
                $uploadUI.after($wrapper);
            }

            $wrapper.find('select').on('change', function() {
                jQuery.post(ajaxurl, {
                    action: 'switchboard_set_upload_category',
                    category_id: jQuery(this).val(),
                    nonce: data.nonce
                });
            });
        }
    });
})();
JS;
    }

    public function add_category_column($columns) {
        $columns['media_category'] = __('Category', 'wpswitchboard');
        return $columns;
    }

    public function display_category_column($column_name, $post_id) {
        if ($column_name === 'media_category') {
            $terms = get_the_terms($post_id, 'media_category');
            if ($terms && !is_wp_error($terms)) {
                $names = wp_list_pluck($terms, 'name');
                echo esc_html(implode(', ', $names));
            } else {
                echo '<span class="text-gray-400">' . esc_html__('Uncategorized', 'wpswitchboard') . '</span>';
            }
        }
    }
    
    public function add_category_filter() {
        global $typenow, $wpdb;

        if (!$wpdb) {
            return;
        }

        if ($typenow !== 'attachment') {
            return;
        }
        
        $categories = get_terms([
            'taxonomy' => 'media_category',
            'hide_empty' => false,
        ]);
        
        if (is_wp_error($categories) || empty($categories)) {
            return;
        }
        
        // Manually count attachments for each category
        foreach ($categories as $category) {
            $count = $wpdb->get_var($wpdb->prepare(
                "SELECT COUNT(DISTINCT tr.object_id)
                FROM {$wpdb->term_relationships} tr
                INNER JOIN {$wpdb->posts} p ON tr.object_id = p.ID
                WHERE tr.term_taxonomy_id = %d
                AND p.post_type = 'attachment'
                AND p.post_status = 'inherit'",
                $category->term_taxonomy_id
            ));
            $category->count = intval($count);
        }

        // Use empty string as default when no filter is set, so "All Categories" is selected
        // Only set to the actual value if media_category param exists in the URL
        $current = isset($_GET['media_category']) ? sanitize_text_field($_GET['media_category']) : '';

        // Organize categories hierarchically
        $filter_top_level = array_filter($categories, function($cat) { return $cat->parent == 0; });
        $filter_children = [];
        foreach ($categories as $cat) {
            if ($cat->parent != 0) {
                if (!isset($filter_children[$cat->parent])) {
                    $filter_children[$cat->parent] = [];
                }
                $filter_children[$cat->parent][] = $cat;
            }
        }

        echo '<select name="media_category" id="media-category-filter" class="sb-sp-select">';
        echo '<option value=""' . selected($current, '', false) . '>' . esc_html__('All Categories', 'wpswitchboard') . '</option>';
        echo '<option value="0"' . selected($current, '0', false) . '>' . esc_html__('Uncategorized', 'wpswitchboard') . '</option>';

        foreach ($filter_top_level as $category) {
            echo '<option value="' . esc_attr($category->term_id) . '"' . selected($current, $category->term_id, false) . '>';
            echo esc_html($category->name) . ' (' . $category->count . ')';
            echo '</option>';

            // Add children with indent
            if (isset($filter_children[$category->term_id])) {
                foreach ($filter_children[$category->term_id] as $child) {
                    echo '<option value="' . esc_attr($child->term_id) . '"' . selected($current, $child->term_id, false) . '>';
                    echo '&nbsp;&nbsp;↳ ' . esc_html($child->name) . ' (' . $child->count . ')';
                    echo '</option>';
                }
            }
        }

        echo '</select>';
    }
    
    /**
     * Check if an external media category plugin is active and handling filtering
     *
     * @return bool True if external plugin should handle filtering
     */
    private function is_external_plugin_handling_categories() {
        // Check for WP Media Category Management plugin
        // This plugin may be handling its own filtering via different parameter names or hooks
        if (class_exists('WPMCM') || class_exists('WP_Media_Category_Management') ||
            defined('WP_MEDIA_CATEGORY_MANAGEMENT_VERSION') ||
            function_exists('wp_media_category_management_init')) {
            // Check if the external plugin is already filtering
            // Look for evidence that external plugin has processed parameters
            if (isset($_POST['query']) && is_array($_POST['query'])) {
                // If there are category-like parameters we don't recognize, external plugin might be handling it
                foreach ($_POST['query'] as $key => $value) {
                    // Skip our own parameter names
                    if ($key === 'switchboard_media_category' || $key === 'media_category') {
                        continue;
                    }
                    // If there are other category-like parameters, external plugin might be using them
                    if (strpos($key, 'category') !== false || strpos($key, 'cat') !== false) {
                        return true;
                    }
                }
            }
            // Apply a filter to allow external plugins to signal they're handling categories
            if (!apply_filters('switchboard_media_categories_handle_filtering', true)) {
                return true;
            }
        }
        return false;
    }

    public function filter_by_category($query) {
        global $pagenow, $typenow;

        // Only run on media library
        if (!is_admin()) {
            return;
        }

        // Check if external plugin should handle filtering instead
        if ($this->is_external_plugin_handling_categories()) {
            return;
        }

        // Check for both upload.php page and AJAX requests
        $is_upload_page = ($pagenow === 'upload.php');
        $is_ajax_query = (defined('DOING_AJAX') && DOING_AJAX && isset($_REQUEST['action']) && $_REQUEST['action'] === 'query-attachments');

        if (!$is_upload_page && !$is_ajax_query) {
            return;
        }

        // For upload.php, check if it's the main query OR if it's querying attachments
        // This handles both list view and grid view on upload.php
        if ($is_upload_page && !$is_ajax_query) {
            // For list view, we need to check if this is querying attachments
            // List view might not be the main query, but it should still be for attachments
            if ($query->get('post_type') !== 'attachment' && $query->get('post_type') !== false) {
            return;
            }
            // Also check if it's the main query or if we're on upload.php (list view)
            if (!$query->is_main_query() && $pagenow !== 'upload.php') {
                return;
            }
        }
        
        // For AJAX queries, always allow (they're for grid view)
        if ($is_ajax_query) {
            // Allow through
        }

        // Check if filtering by category - handle both GET params and AJAX query params
        $category_id = null;

        // For AJAX queries, check multiple sources in order of likelihood
        if ($is_ajax_query) {
            // First check our static property (captured in ajax_query_attachments_args)
            if (static::$ajax_category_id !== null) {
                $category_id = static::$ajax_category_id;
            }
            // Check POST query array (this is where JavaScript adds it)
            elseif (isset($_POST['query']) && is_array($_POST['query']) && isset($_POST['query']['media_category'])) {
                $category_id = intval($_POST['query']['media_category']);
            }
            // Check REQUEST query array
            elseif (isset($_REQUEST['query']) && is_array($_REQUEST['query']) && isset($_REQUEST['query']['media_category'])) {
                $category_id = intval($_REQUEST['query']['media_category']);
            }
            // Check if it's in the query object's query property (WordPress might have processed it)
            elseif ($query->get('query') && is_array($query->get('query')) && isset($query->get('query')['media_category'])) {
                $query_vars = $query->get('query');
                $category_id = intval($query_vars['media_category']);
            }
            // Check POST directly
            elseif (isset($_POST['media_category'])) {
                $category_id = intval($_POST['media_category']);
            }
            // Check query_vars directly (after WordPress has parsed)
            elseif (isset($query->query_vars['media_category'])) {
                $category_id = intval($query->query_vars['media_category']);
            }
        } else {
            // For upload.php page, check GET parameter
            if (isset($_GET['media_category'])) {
                $category_id = intval($_GET['media_category']);
            }
        }

        // If no category filter is set, don't apply any tax query
        if ($category_id === null) {
            return;
        }

        // Ensure we're querying attachments
        $query->set('post_type', 'attachment');
        $query->set('post_status', 'inherit');

        if ($category_id === 0) {
            // Show uncategorized - items without any terms
            // WordPress tax_query doesn't support NOT EXISTS directly,
            // so we use a workaround: query for a term that doesn't exist
            // and then handle it properly in filter_where for AJAX
            // For upload.php, filter_where will handle it with a subquery
            if ($is_ajax_query) {
                // For AJAX, we'll handle uncategorized in filter_where
                // Set a flag so filter_where knows to handle it
                $query->set('switchboard_uncategorized', true);
            } else {
                // For upload.php, filter_where will handle it
                $query->set('switchboard_uncategorized', true);
            }
        } elseif ($category_id > 0) {
            // Apply custom media order if stored — post__in already constrains
            // results to category members, so skip switchboard_category_id
            // to avoid redundant JOINs/subqueries in filter_join/filter_where
            $resolved_order = $this->get_resolved_media_order($category_id);
            if (!empty($resolved_order)) {
                $query->set('post__in', $resolved_order);
                $query->set('orderby', 'post__in');
            } else {
                // No custom order — fall back to filter_join/filter_where for filtering
                $query->set('switchboard_category_id', $category_id);
            }
        }
    }
    
    public function filter_join($join, $query) {
        global $pagenow, $wpdb;

        if (!is_admin()) {
            return $join;
        }

        // Check for both upload.php page and AJAX requests
        $is_upload_page = ($pagenow === 'upload.php');
        $is_ajax_query = (defined('DOING_AJAX') && DOING_AJAX && isset($_REQUEST['action']) && $_REQUEST['action'] === 'query-attachments');

        if (!$is_upload_page && !$is_ajax_query) {
            return $join;
        }

        // For AJAX queries (media modal), skip custom SQL - let tax_query handle it
        // This prevents conflicts with WordPress's automatic tax_query handling
        if ($is_ajax_query) {
            return $join;
        }

        // Get category ID from appropriate source (only for upload.php now)
        $category_id = null;
        if (isset($_GET['media_category'])) {
            $category_id = intval($_GET['media_category']);
        }

        // Also check if query has category ID flag set (from filter_by_category)
        // Use strict check - only if the value is actually a positive integer
        $switchboard_cat = $query->get('switchboard_category_id');
        if ($category_id === null && $switchboard_cat !== '' && $switchboard_cat !== false && $switchboard_cat !== null) {
            $category_id = intval($switchboard_cat);
        }

        // Only add join for specific category filtering (category_id > 0)
        if ($category_id === null || $category_id <= 0) {
            return $join;
        }

        // For specific category, add BOTH joins if not already present
        if ($category_id > 0 && strpos($join, 'switchboard_tr') === false) {
            $join .= " INNER JOIN {$wpdb->term_relationships} AS switchboard_tr ON ({$wpdb->posts}.ID = switchboard_tr.object_id)";
            $join .= " INNER JOIN {$wpdb->term_taxonomy} AS switchboard_tt ON (switchboard_tt.term_taxonomy_id = switchboard_tr.term_taxonomy_id AND switchboard_tt.taxonomy = 'media_category')";
        }

        return $join;
    }

    public function filter_where($where, $query) {
        global $pagenow, $wpdb;

        if (!is_admin()) {
            return $where;
        }

        // Check for both upload.php page and AJAX requests
        $is_upload_page = ($pagenow === 'upload.php');
        $is_ajax_query = (defined('DOING_AJAX') && DOING_AJAX && isset($_REQUEST['action']) && $_REQUEST['action'] === 'query-attachments');

        if (!$is_upload_page && !$is_ajax_query) {
            return $where;
        }

        // Get category ID from appropriate source
        $category_id = null;
        if (isset($_GET['media_category'])) {
            $category_id = intval($_GET['media_category']);
        } elseif (isset($_POST['media_category'])) {
            $category_id = intval($_POST['media_category']);
        } elseif (isset($_REQUEST['query']['media_category'])) {
            $category_id = intval($_REQUEST['query']['media_category']);
        } elseif (isset($_POST['query']['media_category'])) {
            $category_id = intval($_POST['query']['media_category']);
        }

        // Also check if query has category ID flag set (from filter_by_category)
        // Use strict check - only if the value is actually set
        $switchboard_cat = $query->get('switchboard_category_id');
        if ($category_id === null && $switchboard_cat !== '' && $switchboard_cat !== false && $switchboard_cat !== null) {
            $category_id = intval($switchboard_cat);
        }

        // Check if query has uncategorized flag set
        $is_uncategorized = $query->get('switchboard_uncategorized');

        // For AJAX queries, we need to handle both uncategorized and specific categories
        if ($is_ajax_query) {
            // First check static property (most reliable for AJAX)
            if (static::$ajax_category_id !== null) {
                $category_id = static::$ajax_category_id;
            }
            // Also check query flag (set in filter_by_category) - use strict check
            $switchboard_cat_ajax = $query->get('switchboard_category_id');
            if ($category_id === null && $switchboard_cat_ajax !== '' && $switchboard_cat_ajax !== false && $switchboard_cat_ajax !== null) {
                $category_id = intval($switchboard_cat_ajax);
            }
            
            // Remove WordPress's "0 = 1" condition that blocks all results
            // This happens when WordPress's tax_query conflicts with our custom filtering
            $where = preg_replace('/AND\s+\(\s*0\s*=\s*1\s*\)/', '', $where);
            
            if ($is_uncategorized || $category_id === 0) {
                // Uncategorized - exclude posts that have any media_category terms
                $where .= " AND {$wpdb->posts}.ID NOT IN (
                    SELECT DISTINCT object_id FROM {$wpdb->term_relationships} tr
                    INNER JOIN {$wpdb->term_taxonomy} tt ON tr.term_taxonomy_id = tt.term_taxonomy_id
                    WHERE tt.taxonomy = 'media_category'
                )";
            } elseif ($category_id > 0) {
                // For specific categories in AJAX, we need to add a WHERE clause
                // because tax_query might not work correctly in all AJAX contexts
                // We'll use a subquery to ensure it works
                $where .= $wpdb->prepare(
                    " AND {$wpdb->posts}.ID IN (
                        SELECT DISTINCT tr.object_id 
                        FROM {$wpdb->term_relationships} tr
                        INNER JOIN {$wpdb->term_taxonomy} tt ON tr.term_taxonomy_id = tt.term_taxonomy_id
                        WHERE tt.taxonomy = 'media_category' AND tt.term_id = %d
                    )",
                    $category_id
                );
            }
            return $where;
        }

        // For upload.php page, handle both uncategorized and specific categories
        if ($category_id === null && !$is_uncategorized) {
            return $where;
        }

        // Remove WordPress's "0 = 1" condition that blocks all results
        // This happens when WordPress's tax_query conflicts with our custom filtering
        $where = preg_replace('/AND\s+\(\s*0\s*=\s*1\s*\)/', '', $where);

        if ($category_id === 0 || $is_uncategorized) {
            // Uncategorized - exclude posts that have any media_category terms
            $where .= " AND {$wpdb->posts}.ID NOT IN (
                SELECT DISTINCT object_id FROM {$wpdb->term_relationships} tr
                INNER JOIN {$wpdb->term_taxonomy} tt ON tr.term_taxonomy_id = tt.term_taxonomy_id
                WHERE tt.taxonomy = 'media_category'
            )";
        } elseif ($category_id > 0) {
            // Use subquery approach for list view to ensure it works even if join isn't present
            // This is more reliable than relying on the join
            $where .= $wpdb->prepare(
                " AND {$wpdb->posts}.ID IN (
                    SELECT DISTINCT tr.object_id 
                    FROM {$wpdb->term_relationships} tr
                    INNER JOIN {$wpdb->term_taxonomy} tt ON tr.term_taxonomy_id = tt.term_taxonomy_id
                    WHERE tt.taxonomy = 'media_category' AND tt.term_id = %d
                )",
                $category_id
            );
        }

        return $where;
    }
    
    public function add_bulk_actions($actions) {
        $categories = get_terms([
            'taxonomy' => 'media_category',
            'hide_empty' => false,
        ]);

        if (!is_wp_error($categories) && !empty($categories)) {
            $actions['media_cat_divider'] = __('── Media Categories ──', 'wpswitchboard');
            $actions['remove_category'] = __('Remove from Category', 'wpswitchboard');

            foreach ($categories as $category) {
                $actions['assign_category_' . $category->term_id] = sprintf(__('Assign to: %s', 'wpswitchboard'), $category->name);
            }
        }

        return $actions;
    }
    
    public function handle_bulk_actions($redirect_to, $action, $post_ids) {
        if ($action === 'remove_category') {
            foreach ($post_ids as $post_id) {
                // Get current terms before removing, so we can clean up order arrays
                $current_terms = wp_get_object_terms($post_id, 'media_category', ['fields' => 'ids']);
                if (is_array($current_terms)) {
                    foreach ($current_terms as $ct_id) {
                        $this->remove_from_media_order($ct_id, $post_id);
                    }
                }
                wp_set_object_terms($post_id, [], 'media_category');
            }
            // Update term counts
            wp_update_term_count_now(get_terms(['taxonomy' => 'media_category', 'fields' => 'ids', 'hide_empty' => false]), 'media_category');
            $redirect_to = add_query_arg('bulk_categorized', count($post_ids), $redirect_to);
        } elseif (strpos($action, 'assign_category_') === 0) {
            $term_id = intval(str_replace('assign_category_', '', $action));
            foreach ($post_ids as $post_id) {
                wp_set_object_terms($post_id, [$term_id], 'media_category');
                $this->add_to_media_order($term_id, $post_id);
            }
            // Update term counts
            wp_update_term_count_now([$term_id], 'media_category');
            $redirect_to = add_query_arg('bulk_categorized', count($post_ids), $redirect_to);
        }

        return $redirect_to;
    }

    public function render_media_sidebar() {
        global $wpdb, $pagenow;

        if (!$wpdb) {
            return;
        }

        // Only show on media library page
        if ($pagenow !== 'upload.php') {
            return;
        }

        // Get current view mode to handle DOM structure differences
        // Check URL parameter first, then user's saved preference
        if (isset($_GET['mode'])) {
            $mode = sanitize_key($_GET['mode']);
            // Validate mode is one of the allowed values
            $mode = in_array($mode, ['grid', 'list'], true) ? $mode : 'grid';
        } else {
            // Get user's saved media library mode preference
            $user_mode = get_user_option('media_library_mode', get_current_user_id());
            $mode = $user_mode ? $user_mode : 'grid'; // WordPress defaults to grid
        }

        $categories = get_terms([
            'taxonomy' => 'media_category',
            'hide_empty' => false,
        ]);
        
        if (is_wp_error($categories)) {
            $categories = [];
        }
        
        // Manually count attachments for each category
        foreach ($categories as $category) {
            $count = $wpdb->get_var($wpdb->prepare(
                "SELECT COUNT(DISTINCT tr.object_id)
                FROM {$wpdb->term_relationships} tr
                INNER JOIN {$wpdb->posts} p ON tr.object_id = p.ID
                WHERE tr.term_taxonomy_id = %d
                AND p.post_type = 'attachment'
                AND p.post_status = 'inherit'",
                $category->term_taxonomy_id
            ));
            $category->count = intval($count);
        }
        ?>
        <style>
            /* Switchboard badge - self-contained styles with fallback values */
            .switchboard-badge {
                background: linear-gradient(135deg, var(--primary-500, #3b82f6), var(--primary-600, #2563eb));
                color: white;
                border: 1px solid var(--primary-500, #3b82f6);
                padding: 2px 6px;
                font-size: 9px;
                font-weight: 600;
                letter-spacing: 0.5px;
                text-transform: uppercase;
                border-radius: 3px;
                display: inline-block;
                vertical-align: middle;
                margin-right: 6px;
                box-shadow: 0 1px 2px rgba(59, 130, 246, 0.2);
            }

            /* Make wpbody a flex container when sidebar is present */
            #wpbody.has-media-categories-sidebar {
                display: flex;
                flex-direction: row;
                align-items: flex-start;
                gap: 20px;
            }

            /* Sidebar styling - inside wpbody as sibling to wpbody-content */
            #media-categories-sidebar {
                width: 300px;
                flex-shrink: 0;
                background: transparent;
                border: none;
                border-right: 1px solid #e5e5e5;
                border-radius: 0;
                box-shadow: none;
                height: calc(100vh - 32px);
                overflow-y: auto;
                overflow-x: hidden;
                display: flex;
                flex-direction: column;
                position: sticky;
                top: 32px;
                align-self: flex-start;
                margin-right: 0;
                padding-right: 16px;
            }

            /* wpbody-content takes remaining space with left padding */
            #wpbody-content {
                flex: 1;
                min-width: 0;
                padding-left: 0;
            }

            .category-drop-zone {
                padding: 8px 10px;
                margin: 2px 4px;
                background: transparent;
                border: none;
                border-radius: 4px;
                cursor: pointer;
                transition: background-color 0.15s ease;
                position: relative;
                display: flex;
                align-items: center;
                gap: 8px;
                min-height: 28px; /* Reserve space for checkbox */
            }
            .category-drop-zone:hover {
                background: rgba(34, 113, 177, 0.08);
            }
            
            .category-drop-zone:hover .category-actions {
                display: flex !important;
            }
            
            
            .category-actions {
                display: none;
                align-items: center;
            }
            
            .category-edit-btn,
            .category-delete-btn-single,
            .category-duplicate-btn,
            .category-add-subcat-btn {
                background: transparent;
                border: none;
                padding: 4px;
                cursor: pointer;
                color: #6b7280;
                border-radius: 3px;
                display: flex;
                align-items: center;
                justify-content: center;
                transition: all 0.2s ease;
                opacity: 0.7;
            }
            
            .category-edit-btn:hover {
                background: rgba(34, 113, 177, 0.1);
                color: #2271b1;
                opacity: 1;
            }
            
            .category-add-subcat-btn:hover {
                background: rgba(16, 185, 129, 0.1);
                color: #10b981;
                opacity: 1;
            }
            
            .category-duplicate-btn:hover {
                background: rgba(139, 92, 246, 0.1);
                color: #8b5cf6;
                opacity: 1;
            }
            
            .category-delete-btn-single:hover {
                background: rgba(220, 38, 38, 0.1);
                color: #dc2626;
                opacity: 1;
            }

            .category-download-btn {
                background: transparent;
                border: none;
                padding: 4px;
                cursor: pointer;
                color: #6b7280;
                border-radius: 3px;
                display: flex;
                align-items: center;
                justify-content: center;
                transition: all 0.2s ease;
                opacity: 0.7;
            }

            .category-download-btn:hover {
                background: rgba(16, 185, 129, 0.1);
                color: #10b981;
                opacity: 1;
            }

            .category-download-btn.downloading {
                opacity: 0.5;
                pointer-events: none;
            }

            .category-download-btn .spinner {
                animation: spin 1s linear infinite;
            }

            @keyframes spin {
                from { transform: rotate(0deg); }
                to { transform: rotate(360deg); }
            }
            
            #add-new-category-btn:hover {
                background: rgba(34, 113, 177, 0.1);
            }
            
            .category-delete-btn-single.confirming {
                background: rgba(220, 38, 38, 0.15);
                color: #dc2626;
            }

            /* Accordion toggle button for categories with subcategories */
            .category-accordion-toggle {
                background: transparent;
                border: none;
                padding: 4px;
                cursor: pointer;
                color: #9ca3af;
                border-radius: 4px;
                display: flex;
                align-items: center;
                justify-content: center;
                transition: all 0.2s ease;
                flex-shrink: 0;
            }

            .category-accordion-toggle:hover {
                background: rgba(34, 113, 177, 0.1);
                color: #2271b1;
            }

            .category-accordion-toggle .accordion-icon {
                width: 14px;
                height: 14px;
                transition: transform 0.2s ease;
            }

            /* Rotate icon when expanded */
            .category-item.expanded .category-accordion-toggle .accordion-icon {
                transform: rotate(180deg);
            }

            /* Visual indicator that category has children */
            .category-item.has-children > .category-drop-zone::before {
                background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%232271b1' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 20h16a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.93a2 2 0 0 1-1.66-.9l-.82-1.2A2 2 0 0 0 6.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z'%3E%3C/path%3E%3C/svg%3E");
            }

            /* Hide folder icon and show only checkbox in select mode */
            .select-mode-active .category-drop-zone::before {
                display: none !important;
            }
            
            .select-mode-active .category-drop-zone .category-checkbox {
                margin-right: 8px;
            }
            
            .category-name-input {
                background: white;
                border: 1px solid #2271b1;
                border-radius: 3px;
                padding: 2px 6px;
                font-size: 13px;
                font-weight: 500;
                min-width: 100px;
                flex: 1;
            }
            
            .category-name-input:focus,
            .new-category-input:focus {
                outline: none;
                border-color: #135e96;
                box-shadow: 0 0 0 2px rgba(34, 113, 177, 0.1);
            }
            
            .new-category-item,
            .new-subcategory {
                animation: fadeIn 0.2s ease;
            }
            
            @keyframes fadeIn {
                from { opacity: 0; }
                to { opacity: 1; }
            }
            .category-drop-zone.drag-over {
                background: rgba(0, 163, 42, 0.12);
                border-radius: 4px;
            }

            /* Folder icon styling */
            .category-drop-zone::before {
                content: '';
                width: 16px;
                height: 16px;
                flex-shrink: 0;
                display: inline-block;
                background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23666' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 20h16a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.93a2 2 0 0 1-1.66-.9l-.82-1.2A2 2 0 0 0 6.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z'%3E%3C/path%3E%3C/svg%3E");
                background-size: contain;
                background-repeat: no-repeat;
                background-position: center;
                margin-right: 6px;
            }
            
            /* Hide folder icon for new category items (they have input instead) */
            .new-category-item .category-drop-zone::before,
            .new-subcategory::before {
                display: none;
            }

            /* Subcategory styling in sidebar */
            .category-drop-zone.subcategory {
                margin-left: 0;
                margin-right: 4px;
                background: rgba(0, 0, 0, 0.02);
                padding-left: 6px;
                padding-right: 6px;
                padding-top: 6px;
                padding-bottom: 6px;
                min-height: 24px;
            }
            .category-drop-zone.subcategory strong {
                font-size: 12px;
                font-weight: 400;
            }
            .category-drop-zone.subcategory .category-count {
                font-size: 10px;
                padding: 1px 6px;
            }
            .category-drop-zone.subcategory .category-id {
                font-size: 9px;
            }
            .category-drop-zone.subcategory::before {
                content: '';
                width: 14px;
                height: 14px;
                flex-shrink: 0;
                display: inline-block;
                background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23666' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 14 1.45-2.9A2 2 0 0 1 9.24 10H20a2 2 0 0 1 2 2v6a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h2'%3E%3C/path%3E%3Cpath d='M4 10h.01'%3E%3C/path%3E%3C/svg%3E");
                background-size: contain;
                background-repeat: no-repeat;
                background-position: center;
                margin-right: 6px;
            }
            .category-drop-zone.subcategory:hover {
                background: rgba(34, 113, 177, 0.06);
            }

            /* Active category state */
            .category-drop-zone.active {
                background: rgba(34, 113, 177, 0.12);
                border-radius: 4px;
            }
            .category-drop-zone.active strong {
                color: #2271b1;
                font-weight: 500;
            }
            .category-drop-zone.subcategory.active {
                background: rgba(34, 113, 177, 0.1);
            }
            .category-drop-zone.subcategory.active strong {
                color: #2271b1;
            }

            /* Make rows easier to drag */
            .wp-list-table tbody tr.ui-draggable {
                cursor: move !important;
            }
            .wp-list-table tbody tr.ui-draggable:hover {
                background: var(--primary-50) !important;
            }

            /* Grid View - Make attachment items draggable */
            .attachments .attachment.ui-draggable {
                cursor: move !important;
            }
            .attachments .attachment.ui-draggable:hover {
                outline: 2px solid var(--primary-500, #2271b1);
                opacity: 0.9;
                transform: scale(1.02);
                transition: all 0.2s ease;
            }

            /* Grid View - Improve attachment styling */
            .attachments .attachment {
                transition: all 0.2s ease;
                border: 2px solid transparent;
            }

            .attachments .attachment.ui-draggable.ui-dragging {
                opacity: 0.5;
                z-index: 1000;
            }

            /* Ensure proper sizing in grid view */
            .wp-filter {
                flex-shrink: 0;
                width: 100%;
            }

            .attachments-browser {
                flex: 1;
                width: 100%;
                min-width: 0;
            }

            /* Sidebar search and sort styling */
            #category-search {
                box-sizing: border-box;
                transition: border-color 0.2s ease, box-shadow 0.2s ease;
            }
            #category-search:focus {
                outline: none;
                border-color: #2271b1;
                box-shadow: 0 0 0 2px rgba(34, 113, 177, 0.1);
            }

            /* Sort container styling */
            #sort-container {
                padding: 8px;
                background: #f9fafb;
                border: 1px solid #e5e7eb;
                border-radius: 4px;
                margin-bottom: 8px;
                display: flex;
                flex-direction: column;
                gap: 6px;
            }

            #sort-container label {
                font-size: 10px;
                font-weight: 700;
                color: #6b7280;
                text-transform: uppercase;
                letter-spacing: 0.5px;
                display: block;
                margin: 0;
            }

            #category-sort {
                width: 100%;
                box-sizing: border-box;
                padding: 6px 8px;
                border: 1px solid #c3c4c7;
                border-radius: 3px;
                font-size: 11px;
                background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="12" height="8" viewBox="0 0 12 8"><path fill="%23646970" d="M0 0l6 8 6-8z"/></svg>');
                background-repeat: no-repeat;
                background-position: right 8px center;
                padding-right: 28px;
                appearance: none;
                -webkit-appearance: none;
                -moz-appearance: none;
                cursor: pointer;
                transition: border-color 0.2s ease;
                background-color: white;
            }
            #category-sort:hover {
                border-color: #8c8f94;
            }
            #category-sort:focus {
                outline: none;
                border-color: #2271b1;
                box-shadow: 0 0 0 2px rgba(34, 113, 177, 0.1);
            }

            /* Category item styling */
            .category-item {
                display: flex;
                flex-direction: column;
            }

            .category-item .category-drop-zone {
                padding: 8px 12px;
                margin: 2px 8px;
            }

            .category-item .category-drop-zone strong {
                display: block;
            }

            /* Truncate category name when space is limited */
            .category-name {
                overflow: hidden;
                text-overflow: ellipsis;
                white-space: nowrap;
                max-width: 140px;
                display: inline-block !important;
                vertical-align: middle;
            }

            /* Show full name in tooltip on hover */
            .category-drop-zone:hover .category-name {
                max-width: none;
                overflow: visible;
                white-space: normal;
                word-break: break-word;
            }

            .category-item > .category-drop-zone {
                font-weight: 500;
            }

            .category-children {
                margin: 2px 0;
                margin-left: 32px;
            }

            /* Better visual feedback for draggable zones */
            .category-drop-zone {
                user-select: none;
                -webkit-user-select: none;
            }

            .category-drop-zone:active {
                transform: translateY(1px);
            }

            /* Smooth transitions for expand/collapse */
            .category-children {
                overflow: hidden;
                background: transparent;
            }


            /* Category header with title and toolbar */
            .category-header {
                display: flex;
                align-items: center;
                justify-content: space-between;
                margin-bottom: 12px;
                gap: 12px;
            }

            /* Toolbar styling */
            .category-toolbar {
                display: flex;
                gap: 2px;
                flex-shrink: 0;
            }

            .toolbar-btn {
                display: flex;
                align-items: center;
                justify-content: center;
                padding: 4px 6px;
                background: transparent;
                border: 1px solid transparent;
                border-radius: 5px;
                cursor: pointer;
                transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
                color: #6b7280;
                position: relative;
            }

            .toolbar-btn:hover {
                background: #f3f4f6;
                color: #374151;
                border-color: #e5e7eb;
                transform: translateY(-1px);
            }

            .toolbar-btn:active {
                transform: translateY(0);
                background: #e5e7eb;
            }

            .toolbar-btn.active {
                background: #eff6ff;
                border-color: #bfdbfe;
                color: #2271b1;
            }

            .toolbar-btn.active:hover {
                background: #dbeafe;
                border-color: #93c5fd;
                color: #1e40af;
            }

            .toolbar-icon {
                width: 14px;
                height: 14px;
                display: inline-block;
                vertical-align: middle;
                stroke-width: 2.5;
            }

            .toolbar-btn:hover .toolbar-icon,
            .toolbar-btn.active .toolbar-icon {
                color: inherit;
            }

            /* Checkbox styling for select mode */
            .category-checkbox {
                width: 18px;
                height: 18px;
                cursor: pointer;
                flex-shrink: 0;
                margin-right: 4px;
            }

            .category-drop-zone.selected {
                background: rgba(34, 113, 177, 0.12);
            }

            /* Bulk actions container */
            #bulk-actions-container {
                display: flex;
            }

            /* Delete button for selected categories */
            .category-delete-btn {
                background: #ef4444;
                color: white;
                border: none;
                padding: 6px 12px;
                border-radius: 5px;
                cursor: pointer;
                font-size: 11px;
                font-weight: 500;
                transition: all 0.2s ease;
            }

            .category-delete-btn:hover {
                background: #dc2626;
            }
            
            .category-delete-btn.confirming {
                background: #10b981;
            }
            
            .category-delete-btn.confirming:hover {
                background: #059669;
            }
            
            #select-all-categories-btn:hover {
                background: #e5e7eb;
                border-color: #9ca3af;
            }

            /* Category count badge styling */
            .category-count {
                font-size: 11px;
                color: #6b7280;
                background: #f3f4f6;
                padding: 2px 8px;
                border-radius: 10px;
                flex-shrink: 0;
            }

            /* Sort container visibility toggle */
            #sort-container {
                display: none;
            }

            /* Toast Notification Styles */
            .switchboard-toast {
                position: fixed;
                bottom: 20px;
                right: 20px;
                background: #10b981;
                color: white;
                padding: 12px 20px;
                border-radius: 6px;
                box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
                font-size: 13px;
                font-weight: 500;
                z-index: 100000;
                opacity: 0;
                transform: translateY(10px);
                transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
                max-width: 300px;
                pointer-events: none;
            }
            .switchboard-toast.show {
                opacity: 1;
                transform: translateY(0);
            }
            .switchboard-toast.hide {
                opacity: 0;
                transform: translateY(-10px);
            }
        </style>
        
        <script>
        jQuery(document).ready(function($) {
            // Only run on media library page
            if (!$('body').hasClass('upload-php')) return;

            setTimeout(function() {
                const $wpbody = $('#wpbody');
                const $wpbodyContent = $('#wpbody-content');
                const $sidebar = $('#media-categories-sidebar');

                // Detect view mode
                const isListView = $('.wp-list-table').length > 0;
                const isGridView = $('.attachments-browser').length > 0;

                if (!isListView && !isGridView) return;

                // Check if already positioned (prevent duplicate processing)
                if ($sidebar.hasClass('positioned')) {
                    return;
                }

                // Remove sidebar from its current inline position
                $sidebar.detach();

                // Make wpbody a flex container if not already
                if (!$wpbody.hasClass('has-media-categories-sidebar')) {
                    $wpbody.addClass('has-media-categories-sidebar');
                }

                // Insert sidebar as first child of wpbody (before wpbody-content)
                $wpbody.prepend($sidebar);
                $sidebar.addClass('positioned');

                // Add help text (list view only)
                if (isListView) {
                    const $bulkActions = $('.bulkactions').first();
                    if ($bulkActions.length && !$bulkActions.find('.media-categories-help').length) {
                        $bulkActions.append('<span class="media-categories-help" style="margin-left: 10px;">Use bulk actions menu to assign media to categories</span>');
                    }
                }
            }, 100); // Small delay to ensure DOM is ready
        });
        </script>
        
        <div id="media-categories-sidebar">
            <?php
            // Get current category filter from URL
            $current_category = isset($_GET['media_category']) ? intval($_GET['media_category']) : null;
            $is_all_media = !isset($_GET['media_category']);
            $is_uncategorized = $current_category === 0;
            ?>
            <input type="hidden" id="switchboard-nonce" value="<?php echo esc_attr(wp_create_nonce('switchboard_nonce')); ?>">
            <div class="sidebar-content sb-sp-col">
                <div style="padding: 12px;">
                    <!-- Header with title and toolbar -->
                    <div class="category-header">
                        <h3 style="font-size: 13px; font-weight: 600; margin: 0;"><span class="switchboard-badge">Switchboard</span> Categories</h3>
                    <!-- Toolbar with action icons -->
                    <div class="category-toolbar">
                        <button class="toolbar-btn" id="expand-all-btn" title="Expand All">
                            <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="toolbar-icon">
                                    <path d="m7 13 5 5 5-5"></path>
                                    <path d="m7 6 5 5 5-5"></path>
                            </svg>
                        </button>
                        <button class="toolbar-btn" id="select-mode-btn" title="Select Mode">
                            <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="toolbar-icon">
                                <rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
                                    <path d="m9 12 2 2 4-4"></path>
                            </svg>
                        </button>
                        </div>
                    </div>

                    <!-- Search input with integrated sort -->
                    <div style="display: flex; gap: 4px; margin-bottom: 8px;">
                        <input type="text" id="category-search" placeholder="Search..." style="flex: 1; padding: 5px 8px; border: 1px solid #d1d5db; border-radius: 4px; font-size: 11px; box-sizing: border-box; background: #fff; transition: all 0.2s ease;">
                        <select id="category-sort" style="width: auto; min-width: 85px; padding: 5px 20px 5px 8px; border: 1px solid #d1d5db; border-radius: 4px; font-size: 11px; box-sizing: border-box; background: #fff; cursor: pointer; color: #6b7280; transition: all 0.2s ease; appearance: none; background-image: url('data:image/svg+xml,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' width=\'12\' height=\'12\' viewBox=\'0 0 12 12\'%3E%3Cpath fill=\'%236b7280\' d=\'M6 9L1 4h10z\'/%3E%3C/svg%3E'); background-repeat: no-repeat; background-position: right 6px center; background-size: 10px;">
                            <option value="name">Name</option>
                            <option value="count">Count (High to Low)</option>
                            <option value="count-asc">Count (Low to High)</option>
                        </select>
                    </div>
                    <style>
                        #category-search:focus {
                            outline: none;
                            border-color: #2271b1;
                            box-shadow: 0 0 0 2px rgba(34, 113, 177, 0.1);
                        }
                        #category-sort:focus {
                            outline: none;
                            border-color: #2271b1;
                            box-shadow: 0 0 0 2px rgba(34, 113, 177, 0.1);
                        }
                        #category-sort:hover {
                            border-color: #9ca3af;
                        }

                        /* Sortable: drag handle column at end of row */
                        .wp-list-table .switchboard-drag-column {
                            width: 28px;
                            padding: 8px 4px !important;
                            text-align: center;
                            vertical-align: middle;
                        }
                        .wp-list-table .switchboard-drag-handle {
                            cursor: grab !important;
                            color: #9ca3af;
                            display: inline-flex;
                            align-items: center;
                            transition: color 0.15s ease;
                        }
                        .wp-list-table .switchboard-drag-handle:hover {
                            color: #1d2327;
                        }
                        .wp-list-table .switchboard-drag-handle:active {
                            cursor: grabbing !important;
                        }
                        /* Row being dragged */
                        .wp-list-table tbody tr.ui-sortable-helper {
                            background: #fff !important;
                            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
                            border: 1px solid #e5e7eb;
                        }
                        /* Drop placeholder */
                        .wp-list-table tbody tr.sortable-placeholder {
                            visibility: visible !important;
                            background: #eff6ff !important;
                            border: 2px dashed #93c5fd !important;
                            height: 48px;
                        }
                        .wp-list-table tbody tr.sortable-placeholder td {
                            border: none !important;
                        }
                        /* Info notice above table */
                        .switchboard-sortable-notice {
                            background: #eff6ff;
                            border: 1px solid #bfdbfe;
                            border-radius: 4px;
                            padding: 6px 12px;
                            font-size: 12px;
                            color: #1e40af;
                            margin-bottom: 8px;
                            display: flex;
                            align-items: center;
                            gap: 6px;
                        }
                    </style>


                    <!-- Upload to category -->
                    <div id="switchboard-upload-category-section" style="margin-bottom: 8px;">
                        <label style="font-size: 11px; font-weight: 600; color: #646970; display: block; margin-bottom: 4px; text-transform: uppercase;">Upload to</label>
                        <select id="switchboard-upload-category-select" style="width: 100%; padding: 5px 8px; border: 1px solid #d1d5db; border-radius: 4px; font-size: 11px; box-sizing: border-box; background: #fff;">
                            <option value="0"><?php esc_html_e('No category (default)', 'wpswitchboard'); ?></option>
                            <?php
                            $upload_cat_id = get_transient('switchboard_upload_category_' . get_current_user_id());
                            if (!empty($categories)):
                                $ul_top_level = array_filter($categories, function($cat) { return $cat->parent == 0; });
                                $ul_children_map = [];
                                foreach ($categories as $cat) {
                                    if ($cat->parent != 0) {
                                        $ul_children_map[$cat->parent][] = $cat;
                                    }
                                }
                                foreach ($ul_top_level as $cat):
                                    $selected = ($upload_cat_id == $cat->term_id) ? ' selected' : '';
                                    ?>
                                    <option value="<?php echo esc_attr($cat->term_id); ?>"<?php echo $selected; ?>><?php echo esc_html($cat->name); ?></option>
                                    <?php
                                    if (!empty($ul_children_map[$cat->term_id])):
                                        foreach ($ul_children_map[$cat->term_id] as $child):
                                            $child_selected = ($upload_cat_id == $child->term_id) ? ' selected' : '';
                                            ?>
                                            <option value="<?php echo esc_attr($child->term_id); ?>"<?php echo $child_selected; ?>>&nbsp;&nbsp;<?php echo esc_html($child->name); ?></option>
                                            <?php
                                        endforeach;
                                    endif;
                                endforeach;
                            endif;
                            ?>
                        </select>
                    </div>

                    <!-- Bulk actions -->
                    <div style="display: none; gap: 6px; margin-top: 8px;" id="bulk-actions-container">
                        <button id="select-all-categories-btn" style="flex: 1; padding: 6px 10px; background: #f3f4f6; color: #374151; border: 1px solid #d1d5db; border-radius: 4px; cursor: pointer; font-size: 11px; font-weight: 500; transition: all 0.2s ease;">Select All</button>
                        <button id="category-delete-btn" class="category-delete-btn" style="flex: 1; margin: 0;">Delete Selected</button>
                        </div>
                </div>

                <div class="sb-sp-col" style="gap: 0; overflow-y: auto; max-height: calc(100vh - 280px);">
                    <?php
                        // Get total media count (attachments use 'inherit' status, not 'publish')
                        $total_count = (int) $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->posts} WHERE post_type = 'attachment' AND post_status = 'inherit'");

                        // Get uncategorized count - attachments NOT assigned to any media_category term
                        $uncategorized_count = (int) $wpdb->get_var("
                            SELECT COUNT(*)
                            FROM {$wpdb->posts} p
                            WHERE p.post_type = 'attachment'
                            AND p.post_status = 'inherit'
                            AND p.ID NOT IN (
                                SELECT DISTINCT object_id
                                FROM {$wpdb->term_relationships} tr
                                INNER JOIN {$wpdb->term_taxonomy} tt ON tr.term_taxonomy_id = tt.term_taxonomy_id
                                WHERE tt.taxonomy = 'media_category'
                            )
                        ");
                    ?>
                    <div class="category-drop-zone<?php echo $is_all_media ? ' active' : ''; ?>" data-term-id="-1" data-category-name="All Media" data-is-special="true">
                        <div style="flex: 1; display: flex; justify-content: space-between; align-items: center; gap: 12px;">
                            <strong style="font-size: 13px;"><?php _e('All Media', 'switchboard-pro'); ?></strong>
                            <span class="category-count" style="font-size: 11px; color: #6b7280; font-weight: 500;"><?php echo (int) $total_count; ?></span>
                        </div>
                    </div>

                    <div class="category-drop-zone<?php echo $is_uncategorized ? ' active' : ''; ?>" data-term-id="0" data-category-name="Uncategorized" data-is-special="true">
                        <div style="flex: 1; display: flex; justify-content: space-between; align-items: center; gap: 12px;">
                            <strong style="font-size: 13px;"><?php _e('Uncategorized', 'switchboard-pro'); ?></strong>
                            <span class="category-count" style="font-size: 11px; color: #6b7280; font-weight: 500;"><?php echo (int) $uncategorized_count; ?></span>
                        </div>
                    </div>

                    <div style="margin: 12px 0 8px 12px; padding: 0 12px; display: flex; justify-content: space-between; align-items: center; font-size: 10px; font-weight: 600; color: #646970; text-transform: uppercase; border-bottom: 1px solid #e5e5e5; padding-bottom: 8px;">
                        <span>Your Categories</span>
                        <button id="add-new-category-btn" style="background: transparent; border: none; color: #2271b1; font-size: 10px; font-weight: 600; cursor: pointer; padding: 2px 4px; border-radius: 3px; transition: all 0.2s ease; text-transform: uppercase;">ADD NEW</button>
                    </div>
                    <?php if (!empty($categories)):
                        // Organize categories hierarchically for sidebar
                        $sidebar_top_level = array_filter($categories, function($cat) { return $cat->parent == 0; });
                        $sidebar_children = [];
                        foreach ($categories as $cat) {
                            if ($cat->parent != 0) {
                                if (!isset($sidebar_children[$cat->parent])) {
                                    $sidebar_children[$cat->parent] = [];
                                }
                                $sidebar_children[$cat->parent][] = $cat;
                            }
                        }
                    ?>
                        <?php foreach ($sidebar_top_level as $category):
                            $is_active = ($current_category === $category->term_id);
                            $has_children = isset($sidebar_children[$category->term_id]) && !empty($sidebar_children[$category->term_id]);
                        ?>
                            <div class="category-item<?php echo $has_children ? ' has-children' : ''; ?>" data-term-id="<?php echo esc_attr($category->term_id); ?>" data-category-name="<?php echo esc_attr($category->name); ?>" data-count="<?php echo esc_attr($category->count); ?>">
                                <div class="category-drop-zone<?php echo $is_active ? ' active' : ''; ?>" data-term-id="<?php echo esc_attr($category->term_id); ?>">
                                    <div style="flex: 1; display: flex; justify-content: space-between; align-items: center; gap: 12px;">
                                        <strong class="category-name" style="font-size: 13px;"><?php echo esc_html($category->name); ?></strong>
                                        <div style="display: flex; align-items: center; gap: 6px;">
                                            <span class="category-id" style="font-size: 10px; color: #999; display: none;">ID: <?php echo esc_html($category->term_id); ?></span>
                                            <span class="category-count" style="font-size: 11px; color: #6b7280; font-weight: 500;"><?php echo (int) $category->count; ?></span>
                                            <?php if ($has_children): ?>
                                            <button class="category-accordion-toggle" data-term-id="<?php echo esc_attr($category->term_id); ?>" title="Toggle subcategories">
                                                <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="accordion-icon">
                                                    <polyline points="6 9 12 15 18 9"></polyline>
                                                </svg>
                                            </button>
                                            <?php endif; ?>
                                            <div class="category-actions" style="display: none; gap: 4px; margin-left: 4px;">
                                                <button class="category-add-subcat-btn" data-parent-id="<?php echo esc_attr($category->term_id); ?>" title="Add Subcategory">
                                                    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="width: 12px; height: 12px;">
                                                        <circle cx="12" cy="12" r="10"></circle>
                                                        <path d="M12 8v8"></path>
                                                        <path d="m8 12 8 0"></path>
                                                    </svg>
                                                </button>
                                                <button class="category-duplicate-btn" data-term-id="<?php echo esc_attr($category->term_id); ?>" data-category-name="<?php echo esc_attr($category->name); ?>" title="Duplicate">
                                                    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="width: 12px; height: 12px;">
                                                        <rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
                                                        <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
                                                    </svg>
                                                </button>
                                                <button class="category-edit-btn" data-term-id="<?php echo esc_attr($category->term_id); ?>" title="Edit">
                                                    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="width: 12px; height: 12px;">
                                                        <path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path>
                                                        <path d="m18.5 2.5 3 3L12 15l-4 1 1-4 9.5-9.5z"></path>
                                                    </svg>
                                                </button>
                                                <button class="category-delete-btn-single" data-term-id="<?php echo esc_attr($category->term_id); ?>" data-category-name="<?php echo esc_attr($category->name); ?>" title="Delete">
                                                    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="width: 12px; height: 12px;">
                                                        <path d="M3 6h18"></path>
                                                        <path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"></path>
                                                        <path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"></path>
                                                    </svg>
                                                </button>
                                                <?php if ($category->count > 0): ?>
                                                <button class="category-download-btn" data-term-id="<?php echo esc_attr($category->term_id); ?>" data-category-name="<?php echo esc_attr($category->name); ?>" title="Download as ZIP">
                                                    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="width: 12px; height: 12px;">
                                                        <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
                                                        <polyline points="7 10 12 15 17 10"></polyline>
                                                        <line x1="12" y1="15" x2="12" y2="3"></line>
                                                    </svg>
                                                </button>
                                                <?php endif; ?>
                                            </div>
                                        </div>
                                    </div>
                                </div>
                                <?php if ($has_children): ?>
                                    <div class="category-children" style="display: none;">
                                        <?php foreach ($sidebar_children[$category->term_id] as $child):
                                            $child_is_active = ($current_category === $child->term_id);
                                        ?>
                                            <div class="category-drop-zone subcategory<?php echo $child_is_active ? ' active' : ''; ?>" data-term-id="<?php echo esc_attr($child->term_id); ?>" data-category-name="<?php echo esc_attr($child->name); ?>" data-count="<?php echo esc_attr($child->count); ?>">
                                                <div style="flex: 1; display: flex; justify-content: space-between; align-items: center; gap: 12px;">
                                                    <strong class="category-name" style="font-size: 12px;"><?php echo esc_html($child->name); ?></strong>
                                                    <div style="display: flex; align-items: center; gap: 6px;">
                                                        <span class="category-id" style="font-size: 10px; color: #999; display: none;">ID: <?php echo esc_html($child->term_id); ?></span>
                                                        <span class="category-count" style="font-size: 10px; color: #6b7280; font-weight: 500;"><?php echo (int) $child->count; ?></span>
                                                        <div class="category-actions" style="display: none; gap: 4px; margin-left: 4px;">
                                                            <button class="category-duplicate-btn" data-term-id="<?php echo esc_attr($child->term_id); ?>" data-category-name="<?php echo esc_attr($child->name); ?>" title="Duplicate">
                                                                <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="width: 12px; height: 12px;">
                                                                    <rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
                                                                    <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
                                                                </svg>
                                                            </button>
                                                            <button class="category-edit-btn" data-term-id="<?php echo esc_attr($child->term_id); ?>" title="Edit">
                                                                <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="width: 12px; height: 12px;">
                                                                    <path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path>
                                                                    <path d="m18.5 2.5 3 3L12 15l-4 1 1-4 9.5-9.5z"></path>
                                                                </svg>
                                                            </button>
                                                            <button class="category-delete-btn-single" data-term-id="<?php echo esc_attr($child->term_id); ?>" data-category-name="<?php echo esc_attr($child->name); ?>" title="Delete">
                                                                <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="width: 12px; height: 12px;">
                                                                    <path d="M3 6h18"></path>
                                                                    <path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"></path>
                                                                    <path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"></path>
                                                                </svg>
                                                            </button>
                                                            <?php if ($child->count > 0): ?>
                                                            <button class="category-download-btn" data-term-id="<?php echo esc_attr($child->term_id); ?>" data-category-name="<?php echo esc_attr($child->name); ?>" title="Download as ZIP">
                                                                <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="width: 12px; height: 12px;">
                                                                    <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
                                                                    <polyline points="7 10 12 15 17 10"></polyline>
                                                                    <line x1="12" y1="15" x2="12" y2="3"></line>
                                                                </svg>
                                                            </button>
                                                            <?php endif; ?>
                                                        </div>
                                                    </div>
                                                </div>
                                            </div>
                                        <?php endforeach; ?>
                                    </div>
                                <?php endif; ?>
                            </div>
                        <?php endforeach; ?>
                    <?php else: ?>
                        <div id="no-categories-message" style="padding: 12px; font-size: 12px; color: #6b7280;">
                            No categories yet. <a href="#" id="add-category-inline-link" style="color: #2271b1;">Add one</a>
                        </div>
                    <?php endif; ?>
                </div>
            </div>
        </div>

        <script>
        // Ensure category data is available even when media-views isn't enqueued (e.g. list view)
        if (typeof switchboardMediaCategoryData === 'undefined') {
            window.switchboardMediaCategoryData = { nonce: '<?php echo esc_js(wp_create_nonce("media_categories")); ?>' };
        }
        jQuery(document).ready(function($) {
            // Only run on media library page
            if (!$('body').hasClass('upload-php')) return;

            // Toast notification function
            function showToast(message, duration = 3000) {
                // Remove existing toast if any
                $('.switchboard-toast').remove();
                
                // Create toast element
                const $toast = $('<div>', {
                    class: 'switchboard-toast',
                    text: message
                });
                
                // Append to body
                $('body').append($toast);
                
                // Trigger show animation
                setTimeout(function() {
                    $toast.addClass('show');
                }, 10);
                
                // Auto-hide after duration
                setTimeout(function() {
                    $toast.removeClass('show').addClass('hide');
                    setTimeout(function() {
                        $toast.remove();
                    }, 300);
                }, duration);
            }

            // Mark selected category as active when filtering
            function markSelectedCategory() {
                const urlParams = new URLSearchParams(window.location.search);
                const categoryId = urlParams.get('media_category');

                if (categoryId && categoryId !== '0' && categoryId !== '-1') {
                    const $selected = $(`.category-drop-zone[data-term-id="${categoryId}"]`);
                    if ($selected.length) {
                        $('.category-drop-zone').removeClass('active');
                        $selected.addClass('active');
                        
                        // Check if selected category is a subcategory
                        const isSubcategory = $selected.hasClass('subcategory');
                        
                        if (isSubcategory) {
                            // If it's a subcategory, expand its parent to show it
                            const $parentItem = $selected.closest('.category-item');
                            if ($parentItem.length) {
                                const $children = $parentItem.find('> .category-children');
                                if ($children.length && $children.children().length > 0) {
                                    // Only expand if expand all is not active (to avoid conflicts)
                                    if (!$('#expand-all-btn').hasClass('active')) {
                                        $children.slideDown(200);
                                        $parentItem.addClass('expanded');
                                        saveUIState();
                                    }
                                }
                            }
                        } else {
                            // If it's a parent category, expand its children if it has any
                            const $parentItem = $selected.closest('.category-item');
                            if ($parentItem.length) {
                                const $parentChildren = $parentItem.find('> .category-children');
                                if ($parentChildren.length && $parentChildren.children().length > 0) {
                                    // Only expand if expand all is not active (to avoid conflicts)
                                    if (!$('#expand-all-btn').hasClass('active')) {
                                        $parentChildren.slideDown(200);
                                        $parentItem.addClass('expanded');
                                        saveUIState();
                                    }
                                }
                            }
                        }
                    }
                }
            }

            // Restore UI state FIRST (before markSelectedCategory which might interfere)
            // Use a small delay to ensure DOM is fully ready
            setTimeout(function() {
                restoreUIState();
            }, 100);

            // Run on page load - after restoreUIState to avoid conflicts
            setTimeout(function() {
            markSelectedCategory();
            }, 200);

            // Preserve category filter when switching between list and grid view
            $(document).on('click', '.view-switch a', function(e) {
                const urlParams = new URLSearchParams(window.location.search);
                const categoryId = urlParams.get('media_category');
                
                // If there's a category filter, preserve it in the view switch URL
                if (categoryId !== null && categoryId !== '') {
                    const href = $(this).attr('href');
                    if (href) {
                        // Handle both relative and absolute URLs
                        let url;
                        try {
                            url = new URL(href);
                        } catch (e) {
                            // Relative URL, construct from current location
                            url = new URL(href, window.location.href);
                        }
                        url.searchParams.set('media_category', categoryId);
                        $(this).attr('href', url.pathname + url.search);
                    }
                }
            });

            // State persistence functions (session-based)
            function saveUIState() {
                const state = {
                    expandAll: $('#expand-all-btn').hasClass('active'),
                    detailsView: $('#toggle-details-btn').hasClass('active'),
                    expandedCategories: $('.category-item.expanded').map(function() {
                        return $(this).find('> .category-drop-zone').data('term-id');
                    }).get()
                };
                sessionStorage.setItem('switchboard_media_categories_ui_state', JSON.stringify(state));
            }

            function restoreUIState() {
                const stateJson = sessionStorage.getItem('switchboard_media_categories_ui_state');
                if (!stateJson) return;

                try {
                    const state = JSON.parse(stateJson);

                    // Restore details view state first (before expanding categories)
                    if (state.detailsView) {
                        $('.category-id').show();
                        $('#toggle-details-btn').addClass('active');
                    }

                    // Restore expand all state - do this FIRST before markSelectedCategory might interfere
                    if (state.expandAll) {
                        // Find all category items with children and expand them
                        $('.category-item').each(function() {
                            const $categoryItem = $(this);
                            const $children = $categoryItem.find('> .category-children');
                            if ($children.length && $children.children().length > 0) {
                                $categoryItem.addClass('expanded');
                                $children.css('display', 'block'); // Force show, override any inline styles
                            }
                        });
                        // Set button active state
                        $('#expand-all-btn').addClass('active');
                    } else {
                        // Restore individually expanded categories (skip if expandAll is true)
                        if (state.expandedCategories && state.expandedCategories.length > 0) {
                        state.expandedCategories.forEach(termId => {
                            const $categoryItem = $(`.category-drop-zone[data-term-id="${termId}"]`).closest('.category-item');
                            const $children = $categoryItem.find('> .category-children');
                            if ($children.length) {
                                $categoryItem.addClass('expanded');
                                    $children.css('display', 'block'); // Force show, override any inline styles
                            }
                        });
                        }
                    }
                } catch (e) {
                    console.error('Error restoring UI state:', e);
                }
            }


            // Toolbar: Expand All button (toggle between expand/collapse all)
            $('#expand-all-btn').on('click', function() {
                const $btn = $(this);
                const isExpanding = !$btn.hasClass('active');

                if (isExpanding) {
                    // Expand all categories
                    $('.category-item').addClass('expanded');
                    $('.category-children').slideDown(200);
                } else {
                    // Collapse all categories
                    $('.category-item').removeClass('expanded');
                    $('.category-children').slideUp(200);
                }

                $btn.toggleClass('active');
                saveUIState();
            });

            // Toolbar: Toggle Sort visibility - removed, now integrated with search

            // Accordion toggle for individual categories with subcategories
            $(document).on('click', '.category-accordion-toggle', function(e) {
                e.preventDefault();
                e.stopPropagation(); // Prevent triggering category click

                const $btn = $(this);
                const $categoryItem = $btn.closest('.category-item');
                const $children = $categoryItem.find('> .category-children');

                if ($children.length && $children.children().length > 0) {
                    if ($categoryItem.hasClass('expanded')) {
                        // Collapse
                        $children.slideUp(200);
                        $categoryItem.removeClass('expanded');
                    } else {
                        // Expand
                        $children.slideDown(200);
                        $categoryItem.addClass('expanded');
                    }
                    saveUIState();
                }
            });

            // Helper function to create category item HTML
            function createCategoryItemHTML(termId, name, count, isSubcategory, parentId) {
                const fontSize = isSubcategory ? '12px' : '13px';
                const countFontSize = isSubcategory ? '10px' : '11px';
                const itemClass = isSubcategory ? 'category-drop-zone subcategory' : 'category-drop-zone';
                const categoryItemWrapper = isSubcategory ? '' : '<div class="category-item" data-term-id="' + termId + '" data-category-name="' + $('<div>').text(name).html() + '" data-count="' + count + '">';
                const categoryItemClose = isSubcategory ? '' : '</div>';
                
                const addSubcatBtn = isSubcategory ? '' : '<button class="category-add-subcat-btn" data-parent-id="' + termId + '" title="Add Subcategory"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="width: 12px; height: 12px;"><circle cx="12" cy="12" r="10"></circle><path d="M12 8v8"></path><path d="m8 12 8 0"></path></svg></button>';
                
                return categoryItemWrapper + 
                    '<div class="' + itemClass + '" data-term-id="' + termId + '" data-category-name="' + $('<div>').text(name).html() + '" data-count="' + count + '">' +
                        '<div style="flex: 1; display: flex; justify-content: space-between; align-items: center; gap: 12px;">' +
                            '<strong class="category-name" style="font-size: ' + fontSize + ';">' + $('<div>').text(name).html() + '</strong>' +
                            '<div style="display: flex; align-items: center; gap: 6px;">' +
                                '<span class="category-id" style="font-size: 10px; color: #999; display: none;">ID: ' + termId + '</span>' +
                                '<span class="category-count" style="font-size: ' + countFontSize + '; color: #6b7280; font-weight: 500;">' + count + '</span>' +
                                '<div class="category-actions" style="display: none; gap: 4px; margin-left: 4px;">' +
                                    addSubcatBtn +
                                    '<button class="category-duplicate-btn" data-term-id="' + termId + '" data-category-name="' + $('<div>').text(name).html() + '" title="Duplicate"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="width: 12px; height: 12px;"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg></button>' +
                                    '<button class="category-edit-btn" data-term-id="' + termId + '" title="Edit"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="width: 12px; height: 12px;"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path><path d="m18.5 2.5 3 3L12 15l-4 1 1-4 9.5-9.5z"></path></svg></button>' +
                                    '<button class="category-delete-btn-single" data-term-id="' + termId + '" data-category-name="' + $('<div>').text(name).html() + '" title="Delete"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="width: 12px; height: 12px;"><path d="M3 6h18"></path><path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"></path><path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"></path></svg></button>' +
                                '</div>' +
                            '</div>' +
                        '</div>' +
                    '</div>' +
                    categoryItemClose;
            }

            // Add new category - creates inline editable category at bottom
            // Also triggered by inline "Add one" link in empty state
            $('#add-new-category-btn, #add-category-inline-link').on('click', function(e) {
                e.preventDefault();
                const $categoriesContainer = $('.sb-sp-col:has(.category-item)').last();
                const $newItem = $('<div>', {
                    class: 'category-item new-category-item',
                    'data-term-id': 'new',
                    'data-category-name': '',
                    'data-count': '0'
                });
                
                const $dropZone = $('<div>', {
                    class: 'category-drop-zone',
                    'data-term-id': 'new'
                });
                
                const $content = $('<div>', {
                    style: 'flex: 1; display: flex; justify-content: space-between; align-items: center; gap: 12px;'
                });
                
                const $input = $('<input>', {
                    type: 'text',
                    class: 'category-name-input new-category-input',
                    placeholder: 'Category name...',
                    style: 'font-size: 13px; font-weight: 500; flex: 1; border: 1px solid #2271b1; border-radius: 3px; padding: 2px 6px; background: white;'
                });
                
                const $countSpan = $('<span>', {
                    class: 'category-count',
                    style: 'font-size: 11px; color: #6b7280; font-weight: 500;',
                    text: '0'
                });
                
                $content.append($input).append($countSpan);
                $dropZone.append($content);
                $newItem.append($dropZone);
                
                // Insert at the end of categories
                if ($categoriesContainer.length) {
                    $categoriesContainer.append($newItem);
                } else {
                    $('.sb-sp-col:last').append($newItem);
                }
                
                $input.focus().select();
                
                // Handle save on Enter or blur
                const saveNewCategory = function() {
                    const categoryName = $input.val().trim();
                if (!categoryName) {
                        $newItem.remove();
                    return;
                }

                $.ajax({
                    url: ajaxurl,
                    type: 'POST',
                    data: {
                        action: 'switchboard_create_category',
                        name: categoryName,
                            parent: 0,
                            nonce: $('#switchboard-nonce').val()
                    },
                    success: function(response) {
                        if (response.success) {
                            const newTermId = response.data.term_id;
                            const newName = response.data.name || categoryName;
                            const html = createCategoryItemHTML(newTermId, newName, 0, false, 0);
                            const $newCategoryItem = $(html);
                            
                            // Replace the new item with the actual category HTML
                            $newItem.replaceWith($newCategoryItem);

                            // Hide "No categories yet" message
                            $('#no-categories-message').hide();

                            // Animate in
                            $newCategoryItem.hide().fadeIn(200);

                            // Show success toast
                            showToast('Category created');
                        } else {
                            alert('Error: ' + (response.data || 'Failed to create category'));
                            $newItem.remove();
                        }
                    },
                    error: function() {
                        alert('Error creating category');
                            $newItem.remove();
                        }
                    });
                };
                
                $input.on('blur', saveNewCategory);
                $input.on('keydown', function(e) {
                    if (e.key === 'Enter') {
                        e.preventDefault();
                        saveNewCategory();
                    } else if (e.key === 'Escape') {
                        $newItem.remove();
                    }
                });
            });

            // Add subcategory to main category
            $(document).on('click', '.category-add-subcat-btn', function(e) {
                e.stopPropagation();
                const $btn = $(this);
                const parentId = $btn.data('parent-id');
                const $categoryItem = $btn.closest('.category-item');
                const $childrenContainer = $categoryItem.find('.category-children');
                
                // Ensure children container exists and is visible
                let $children = $childrenContainer;
                if (!$children.length) {
                    $children = $('<div>', {
                        class: 'category-children',
                        style: 'display: block;'
                    });
                    $categoryItem.append($children);
                    $categoryItem.addClass('expanded');
                } else {
                    $children.show();
                    $categoryItem.addClass('expanded');
                }
                
                // Create new subcategory item
                const $newSubcat = $('<div>', {
                    class: 'category-drop-zone subcategory new-subcategory',
                    'data-term-id': 'new',
                    'data-category-name': '',
                    'data-count': '0'
                });
                
                const $content = $('<div>', {
                    style: 'flex: 1; display: flex; justify-content: space-between; align-items: center; gap: 12px;'
                });
                
                const $input = $('<input>', {
                    type: 'text',
                    class: 'category-name-input new-category-input',
                    placeholder: 'Subcategory name...',
                    style: 'font-size: 12px; font-weight: 500; flex: 1; border: 1px solid #2271b1; border-radius: 3px; padding: 2px 6px; background: white;'
                });
                
                const $countSpan = $('<span>', {
                    class: 'category-count',
                    style: 'font-size: 10px; color: #6b7280; font-weight: 500;',
                    text: '0'
                });
                
                $content.append($input).append($countSpan);
                $newSubcat.append($content);
                $children.append($newSubcat);
                
                $input.focus().select();
                
                // Handle save on Enter or blur
                const saveNewSubcat = function() {
                    const categoryName = $input.val().trim();
                    if (!categoryName) {
                        $newSubcat.remove();
                        return;
                    }
                    
                    $.ajax({
                        url: ajaxurl,
                        type: 'POST',
                        data: {
                            action: 'switchboard_create_category',
                            name: categoryName,
                            parent: parentId,
                            nonce: $('#switchboard-nonce').val()
                        },
                        success: function(response) {
                            if (response.success) {
                                const newTermId = response.data.term_id;
                                const newName = response.data.name || categoryName;
                                const html = createCategoryItemHTML(newTermId, newName, 0, true, parentId);
                                const $newSubcatItem = $(html);
                                
                                // Replace the input with the actual subcategory HTML
                                $newSubcat.replaceWith($newSubcatItem);
                                
                                // Animate in
                                $newSubcatItem.hide().fadeIn(200);
                                
                                // Show success toast
                                showToast('Subcategory created');
                            } else {
                                alert('Error: ' + (response.data || 'Failed to create subcategory'));
                                $newSubcat.remove();
                            }
                        },
                        error: function() {
                            alert('Error creating subcategory');
                            $newSubcat.remove();
                        }
                    });
                };
                
                $input.on('blur', saveNewSubcat);
                $input.on('keydown', function(e) {
                    if (e.key === 'Enter') {
                        e.preventDefault();
                        saveNewSubcat();
                    } else if (e.key === 'Escape') {
                        $newSubcat.remove();
                    }
                });
            });

            // Toolbar: Select Mode button (not saved to session)
            $('#select-mode-btn').on('click', function() {
                $(this).toggleClass('active');
                const $sidebar = $('#media-categories-sidebar');
                const $bulkActions = $('#bulk-actions-container');
                const isActive = $(this).hasClass('active');

                if (isActive) {
                    // Add checkboxes to ALL categories (parent and child) except special ones
                    $('.category-drop-zone:not([data-is-special="true"])').each(function() {
                        if (!$(this).find('.category-checkbox').length) {
                            $(this).prepend('<input type="checkbox" class="category-checkbox">');
                        }
                    });
                    // Show bulk actions and add select mode class
                    $bulkActions.slideDown(150);
                    $sidebar.addClass('select-mode-active');
                } else {
                    // Remove checkboxes and selected class
                    $('.category-checkbox').remove();
                    $('.category-drop-zone').removeClass('selected');
                    $bulkActions.slideUp(150);
                    $sidebar.removeClass('select-mode-active');
                    // Reset select all button
                    $('#select-all-categories-btn').text('Select All');
                }
            });
            
            // Select All / Deselect All toggle
            $('#select-all-categories-btn').on('click', function() {
                const $btn = $(this);
                const $checkboxes = $('.category-checkbox:not([data-is-special="true"])');
                const allChecked = $checkboxes.length > 0 && $checkboxes.filter(':checked').length === $checkboxes.length;
                
                if (allChecked) {
                    // Deselect all
                    $checkboxes.prop('checked', false).trigger('change');
                    $btn.text('Select All');
                } else {
                    // Select all
                    $checkboxes.prop('checked', true).trigger('change');
                    $btn.text('Deselect All');
                }
            });

            // Handle checkbox selection
            $(document).on('change', '.category-checkbox', function() {
                const $zone = $(this).closest('.category-drop-zone');
                if ($(this).is(':checked')) {
                    $zone.addClass('selected');
                } else {
                    $zone.removeClass('selected');
                }
                
                // Update select all button text
                const $checkboxes = $('.category-checkbox');
                const checkedCount = $checkboxes.filter(':checked').length;
                const totalCount = $checkboxes.length;
                if (checkedCount === totalCount && totalCount > 0) {
                    $('#select-all-categories-btn').text('Deselect All');
                } else {
                    $('#select-all-categories-btn').text('Select All');
                }
            });
            
            // Duplicate category
            $(document).on('click', '.category-duplicate-btn', function(e) {
                e.stopPropagation();
                const $btn = $(this);
                const termId = $btn.data('term-id');
                const categoryName = $btn.data('category-name');
                
                // Get parent ID - check if it's a subcategory by looking at parent
                const $dropZone = $btn.closest('.category-drop-zone');
                const isSubcategory = $dropZone.hasClass('subcategory');
                let parentId = 0;
                
                // If it's a subcategory, find the parent category item
                if (isSubcategory) {
                    const $parentItem = $dropZone.closest('.category-children').siblings('.category-drop-zone');
                    if ($parentItem.length) {
                        parentId = parseInt($parentItem.data('term-id')) || 0;
                    }
                }
                
                $btn.prop('disabled', true);
                
                $.ajax({
                    url: ajaxurl,
                    type: 'POST',
                    data: {
                        action: 'switchboard_create_category',
                        name: categoryName + ' (Copy)',
                        parent: parentId,
                        nonce: $('#switchboard-nonce').val()
                    },
                    success: function(response) {
                        if (response.success) {
                            const newTermId = response.data.term_id;
                            const newName = response.data.name || (categoryName + ' (Copy)');
                            const html = createCategoryItemHTML(newTermId, newName, 0, isSubcategory, parentId);
                            const $newItem = $(html);
                            
                            if (isSubcategory) {
                                // Insert after the original subcategory
                                $dropZone.after($newItem);
                            } else {
                                // Insert after the original category item
                                $dropZone.closest('.category-item').after($newItem);
                            }
                            
                            // Animate in
                            $newItem.hide().fadeIn(200);
                            
                            // Show success toast
                            showToast('Category duplicated');
                        } else {
                            alert('Error: ' + (response.data || 'Failed to duplicate category'));
                            $btn.prop('disabled', false);
                        }
                    },
                    error: function() {
                        alert('Error duplicating category');
                        $btn.prop('disabled', false);
                    }
                });
            });
            

            // Delete selected categories with confirmation
            let bulkDeleteConfirming = false;
            $(document).on('click', '#category-delete-btn', function() {
                const $btn = $(this);
                const selectedIds = $('.category-checkbox:checked').closest('.category-drop-zone').map(function() {
                    return $(this).data('term-id');
                }).get();

                if (selectedIds.length === 0) {
                    alert('Please select at least one category');
                    return;
                }

                if (!bulkDeleteConfirming) {
                    // First click - show confirmation
                    bulkDeleteConfirming = true;
                    $btn.addClass('confirming').text('Confirm Delete');
                    setTimeout(function() {
                        bulkDeleteConfirming = false;
                        $btn.removeClass('confirming').text('Delete Selected');
                    }, 3000);
                    return;
                }

                // Second click - proceed with deletion
                bulkDeleteConfirming = false;
                $btn.prop('disabled', true).text('Deleting...');

                // Delete each category via AJAX
                let completed = 0;
                selectedIds.forEach(function(termId) {
                    $.ajax({
                        url: ajaxurl,
                        type: 'POST',
                        data: {
                            action: 'switchboard_delete_category',
                            term_id: termId,
                            nonce: $('#switchboard-nonce').val()
                        },
                        success: function(response) {
                            if (response.success) {
                            $(`.category-drop-zone[data-term-id="${termId}"]`).closest('.category-item').fadeOut(200, function() {
                                $(this).remove();
                            });
                            } else {
                                alert('Error: ' + (response.data || `Failed to delete category ${termId}`));
                            }
                            completed++;
                            if (completed === selectedIds.length) {
                                // Show success toast
                                showToast(selectedIds.length === 1 ? 'Category deleted' : selectedIds.length + ' categories deleted');
                                
                                $btn.prop('disabled', false).text('Delete Selected');
                                // Exit select mode
                                $('#select-mode-btn').click();
                            }
                        },
                        error: function(xhr, status, error) {
                            let errorMsg = `Failed to delete category ${termId}`;
                            if (xhr.responseJSON && xhr.responseJSON.data) {
                                errorMsg = xhr.responseJSON.data;
                            }
                            alert(errorMsg);
                            completed++;
                            if (completed === selectedIds.length) {
                                $btn.prop('disabled', false).text('Delete Selected');
                            }
                        }
                    });
                });
            });
            
            // Inline edit category
            $(document).on('click', '.category-edit-btn', function(e) {
                e.stopPropagation();
                const $btn = $(this);
                const termId = $btn.data('term-id');
                const $dropZone = $btn.closest('.category-drop-zone');
                const $name = $dropZone.find('.category-name');
                const currentName = $name.text().trim();
                
                // Get current font size from the name element
                const currentFontSize = $name.css('font-size') || ($dropZone.hasClass('subcategory') ? '12px' : '13px');
                
                // Create input field
                const $input = $('<input>', {
                    type: 'text',
                    class: 'category-name-input',
                    value: currentName,
                    css: {
                        'font-size': currentFontSize
                    }
                });
                
                // Replace name with input
                $name.replaceWith($input);
                
                // Prevent clicks on input from triggering category navigation
                $input.on('click', function(e) {
                    e.stopPropagation();
                });
                
                $input.focus().select();
                
                // Handle save on Enter or blur
                const saveEdit = function() {
                    const newName = $input.val().trim();
                    if (!newName || newName === currentName) {
                        // Cancel - restore original
                        $input.replaceWith($('<strong>', {
                            class: 'category-name',
                            style: 'font-size: ' + currentFontSize + ';',
                            text: currentName
                        }));
                        return;
                    }
                    
                    // Save via AJAX
                    $.ajax({
                        url: ajaxurl,
                        type: 'POST',
                        data: {
                            action: 'switchboard_update_category',
                            term_id: termId,
                            name: newName,
                            nonce: $('#switchboard-nonce').val()
                        },
                        success: function(response) {
                            if (response.success) {
                                $input.replaceWith($('<strong>', {
                                    class: 'category-name',
                                    style: 'font-size: ' + currentFontSize + ';',
                                    text: newName
                                }));
                                $dropZone.attr('data-category-name', newName);
                                // Update in category item if it exists
                                $dropZone.closest('.category-item').attr('data-category-name', newName);
                                
                                // Show success toast
                                showToast('Category updated');
                            } else {
                                alert('Error: ' + (response.data || 'Failed to update category'));
                                $input.replaceWith($('<strong>', {
                                    class: 'category-name',
                                    style: 'font-size: ' + currentFontSize + ';',
                                    text: currentName
                                }));
                            }
                        },
                        error: function() {
                            alert('Error updating category');
                            $input.replaceWith($('<strong>', {
                                class: 'category-name',
                                style: 'font-size: ' + currentFontSize + ';',
                                text: currentName
                            }));
                        }
                    });
                };
                
                $input.on('blur', saveEdit);
                $input.on('keydown', function(e) {
                    if (e.key === 'Enter') {
                        e.preventDefault();
                        saveEdit();
                    } else if (e.key === 'Escape') {
                        $input.replaceWith($('<strong>', {
                            class: 'category-name',
                            style: 'font-size: ' + currentFontSize + ';',
                            text: currentName
                        }));
                    }
                });
            });
            
            // Single category delete with confirmation
            let deleteConfirming = {};
            $(document).on('click', '.category-delete-btn-single', function(e) {
                e.stopPropagation();
                const $btn = $(this);
                const termId = $btn.data('term-id');
                const categoryName = $btn.data('category-name');
                
                if (!deleteConfirming[termId]) {
                    // First click - show confirmation
                    deleteConfirming[termId] = true;
                    $btn.addClass('confirming').html('<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="width: 12px; height: 12px;"><path d="M20 6 9 17l-5-5"></path></svg>');
                    setTimeout(function() {
                        deleteConfirming[termId] = false;
                        $btn.removeClass('confirming').html('<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="width: 12px; height: 12px;"><path d="M3 6h18"></path><path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"></path><path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"></path></svg>');
                    }, 3000);
                    return;
                }
                
                // Second click - proceed with deletion
                deleteConfirming[termId] = false;
                $btn.prop('disabled', true);
                
                $.ajax({
                    url: ajaxurl,
                    type: 'POST',
                    data: {
                        action: 'switchboard_delete_category',
                        term_id: termId,
                        nonce: $('#switchboard-nonce').val()
                    },
                    success: function(response) {
                        if (response.success) {
                            // Show success toast
                            showToast('Category deleted');
                            
                            const $deletedItem = $btn.closest('.category-item');
                            const $deletedDropZone = $btn.closest('.category-drop-zone');
                            
                            // Check if this is a main category with subcategories
                            const $childrenContainer = $deletedItem.length ? $deletedItem.find('> .category-children') : $();
                            const $subcategories = $childrenContainer.find('> .category-drop-zone.subcategory');
                            
                            if ($subcategories.length > 0 && $deletedItem.length) {
                                // This is a main category with children - promote them to top-level
                                const $categoriesContainer = $deletedItem.parent(); // The container holding all category items
                                
                                // Process each subcategory
                                $subcategories.each(function() {
                                    const $subcat = $(this);
                                    const subcatTermId = $subcat.data('term-id');
                                    const subcatName = $subcat.data('category-name');
                                    const subcatCount = $subcat.data('count') || $subcat.find('.category-count').text();
                                    
                                    // Remove subcategory class and update styling
                                    $subcat.removeClass('subcategory');
                                    $subcat.find('.category-name').css('font-size', '13px'); // Main category font size
                                    $subcat.find('.category-count').css('font-size', '11px'); // Main category count size
                                    
                                    // Create new category-item wrapper (matching main category structure)
                                    const $newCategoryItem = $('<div>', {
                                        class: 'category-item',
                                        'data-term-id': subcatTermId,
                                        'data-category-name': subcatName,
                                        'data-count': subcatCount
                                    });
                                    
                                    // Move the drop zone into the new wrapper
                                    $subcat.detach();
                                    $newCategoryItem.append($subcat);
                                    
                                    // Add "Add Subcategory" button to actions (main categories have this)
                                    const $actions = $subcat.find('.category-actions');
                                    if ($actions.length) {
                                        // Check if add-subcat-btn already exists
                                        if (!$actions.find('.category-add-subcat-btn').length) {
                                            const $addSubcatBtn = $('<button>', {
                                                class: 'category-add-subcat-btn',
                                                'data-parent-id': subcatTermId,
                                                title: 'Add Subcategory',
                                                html: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="width: 12px; height: 12px;"><circle cx="12" cy="12" r="10"></circle><path d="M12 8v8"></path><path d="m8 12 8 0"></path></svg>'
                                            });
                                            // Insert before duplicate button
                                            $actions.prepend($addSubcatBtn);
                                        }
                                    }
                                    
                                    // Insert the new top-level category after the deleted one
                                    $deletedItem.after($newCategoryItem);
                                });
                                
                                // Remove the children container (now empty)
                                $childrenContainer.remove();
                            }
                            
                            // Remove the deleted category
                            if ($deletedItem.length) {
                                $deletedItem.fadeOut(200, function() {
                                    $(this).remove();
                                });
                            } else if ($deletedDropZone.length) {
                                // If it was just a drop zone (subcategory), remove it
                                $deletedDropZone.fadeOut(200, function() {
                                    $(this).remove();
                                });
                            }
                        } else {
                            alert('Error: ' + (response.data || 'Failed to delete category'));
                            $btn.prop('disabled', false);
                        }
                    },
                    error: function(xhr, status, error) {
                        let errorMsg = 'Failed to delete category';
                        if (xhr.responseJSON && xhr.responseJSON.data) {
                            errorMsg = xhr.responseJSON.data;
                        }
                        alert(errorMsg);
                        $btn.prop('disabled', false);
                    }
                });
            });

            // Download category as ZIP - first check for existing ZIPs
            $(document).on('click', '.category-download-btn', function(e) {
                e.stopPropagation();
                const $btn = $(this);
                const termId = $btn.data('term-id');
                const categoryName = $btn.data('category-name');

                // Prevent double-click
                if ($btn.hasClass('downloading')) {
                    return;
                }

                // First check if there are existing ZIPs for this category
                $.ajax({
                    url: ajaxurl,
                    type: 'POST',
                    data: {
                        action: 'switchboard_get_existing_category_zips',
                        term_id: termId,
                        nonce: $('#switchboard-nonce').val()
                    },
                    success: function(response) {
                        if (response.success && response.data.zips && response.data.zips.length > 0) {
                            // Show modal with existing ZIPs and option to create new
                            showDownloadModal(null, null, null, categoryName, termId, response.data.zips);
                        } else {
                            // No existing ZIPs, show create new modal
                            showDownloadModal(null, null, null, categoryName, termId, []);
                        }
                    },
                    error: function() {
                        // On error, just show create new modal
                        showDownloadModal(null, null, null, categoryName, termId, []);
                    }
                });
            });

            // Create new ZIP function
            function createNewZip(termId, categoryName, autoDelete, callback) {
                $.ajax({
                    url: ajaxurl,
                    type: 'POST',
                    data: {
                        action: 'switchboard_download_category_zip',
                        term_id: termId,
                        auto_delete: autoDelete ? 1 : 0,
                        nonce: $('#switchboard-nonce').val()
                    },
                    success: function(response) {
                        if (response.success) {
                            callback(null, response.data);
                        } else {
                            callback(response.data || 'Failed to create ZIP');
                        }
                    },
                    error: function() {
                        callback('Error creating ZIP file');
                    }
                });
            }

            // Download modal function - now shows existing ZIPs and create new option
            function showDownloadModal(downloadUrl, filename, zipId, categoryName, termId, existingZips) {
                // Remove existing modal
                $('#switchboard-download-modal').remove();

                let existingZipsHtml = '';
                if (existingZips && existingZips.length > 0) {
                    existingZipsHtml = `
                        <div style="margin-bottom: 16px;">
                            <h4 style="margin: 0 0 8px 0; font-size: 13px; font-weight: 600; color: #374151;">Existing Downloads</h4>
                            <div style="max-height: 150px; overflow-y: auto; border: 1px solid #e5e7eb; border-radius: 6px;">
                                ${existingZips.map(zip => `
                                    <div class="existing-zip-item" style="display: flex; align-items: center; gap: 8px; padding: 10px 12px; border-bottom: 1px solid #f3f4f6;">
                                        <div style="flex: 1;">
                                            <div style="font-size: 12px; font-weight: 500;">${zip.file_count} files (${zip.size})</div>
                                            <div style="font-size: 11px; color: #9ca3af;">Created ${zip.created_human}${zip.auto_delete ? (zip.expires_in ? ' • Expires in ' + zip.expires_in : ' • Expired') : ' • Kept until deleted'}</div>
                                        </div>
                                        <a href="${zip.download_url}" download="${zip.filename}" style="padding: 6px 10px; background: #10b981; color: white; text-decoration: none; border-radius: 4px; font-size: 11px; font-weight: 500;">Download</a>
                                        <button class="delete-existing-zip" data-zip-id="${zip.zip_id}" style="padding: 6px 8px; background: #f3f4f6; color: #6b7280; border: 1px solid #d1d5db; border-radius: 4px; cursor: pointer; font-size: 11px;">×</button>
                                    </div>
                                `).join('')}
                            </div>
                        </div>
                    `;
                }

                const modalHtml = `
                    <div id="switchboard-download-modal" style="position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.5); z-index: 100001; display: flex; align-items: center; justify-content: center;">
                        <div style="background: white; border-radius: 8px; padding: 24px; max-width: 450px; width: 90%; box-shadow: 0 4px 20px rgba(0,0,0,0.15); position: relative;">
                            <button class="close-modal-btn" style="position: absolute; top: 12px; right: 12px; background: none; border: none; font-size: 20px; cursor: pointer; color: #9ca3af; padding: 4px; line-height: 1;">&times;</button>

                            <div style="display: flex; align-items: center; justify-content: space-between; margin: 0 0 16px 0; padding-right: 30px;">
                                <h3 style="margin: 0; font-size: 16px; font-weight: 600;">Download: ${$('<div>').text(categoryName).html()}</h3>
                                <label style="display: flex; align-items: center; gap: 6px; cursor: pointer; white-space: nowrap;">
                                    <input type="checkbox" id="auto-delete-checkbox" checked style="margin: 0; width: 14px; height: 14px;">
                                    <span style="font-size: 11px; color: #6b7280;">Auto-delete (1hr)</span>
                                </label>
                            </div>

                            ${existingZipsHtml}

                            <button id="create-new-zip-btn" data-term-id="${termId}" data-category-name="${$('<div>').text(categoryName).html()}" style="width: 100%; padding: 12px 16px; background: #2271b1; color: white; border: none; border-radius: 6px; cursor: pointer; font-size: 13px; font-weight: 500;">
                                Create New ZIP
                            </button>
                            <div id="zip-progress" style="display: none; margin-top: 12px; text-align: center; font-size: 12px; color: #6b7280;">
                                <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width: 16px; height: 16px; animation: spin 1s linear infinite; display: inline-block; vertical-align: middle; margin-right: 6px;"><path d="M21 12a9 9 0 1 1-6.219-8.56"></path></svg>
                                Creating ZIP file...
                            </div>
                        </div>
                    </div>
                `;

                $('body').append(modalHtml);

                // Close modal on background click
                $('#switchboard-download-modal').on('click', function(e) {
                    if (e.target === this) {
                        $(this).remove();
                    }
                });

                // Close button
                $('#switchboard-download-modal .close-modal-btn').on('click', function() {
                    $('#switchboard-download-modal').remove();
                });

                // Delete existing ZIP buttons
                $('#switchboard-download-modal .delete-existing-zip').on('click', function() {
                    const $deleteBtn = $(this);
                    const zipIdToDelete = $deleteBtn.data('zip-id');
                    const $item = $deleteBtn.closest('.existing-zip-item');

                    $deleteBtn.prop('disabled', true).text('...');

                    $.ajax({
                        url: ajaxurl,
                        type: 'POST',
                        data: {
                            action: 'switchboard_delete_category_zip',
                            zip_id: zipIdToDelete,
                            nonce: $('#switchboard-nonce').val()
                        },
                        success: function(response) {
                            if (response.success) {
                                $item.slideUp(200, function() { $(this).remove(); });
                                showToast('ZIP deleted');
                            } else {
                                $deleteBtn.prop('disabled', false).text('×');
                            }
                        },
                        error: function() {
                            $deleteBtn.prop('disabled', false).text('×');
                        }
                    });
                });

                // Create new ZIP button
                $('#create-new-zip-btn').on('click', function() {
                    const $btn = $(this);
                    const termIdVal = $btn.data('term-id');
                    const catName = $btn.data('category-name');
                    const autoDelete = $('#auto-delete-checkbox').is(':checked');

                    $btn.prop('disabled', true);
                    $('#zip-progress').show();

                    createNewZip(termIdVal, catName, autoDelete, function(error, data) {
                        if (error) {
                            $btn.prop('disabled', false);
                            $('#zip-progress').hide();
                            showToast('Error: ' + error);
                        } else {
                            showToast('ZIP ready! ' + data.file_count + ' files');
                            // Refresh modal to show new ZIP
                            $.ajax({
                                url: ajaxurl,
                                type: 'POST',
                                data: {
                                    action: 'switchboard_get_existing_category_zips',
                                    term_id: termIdVal,
                                    nonce: $('#switchboard-nonce').val()
                                },
                                success: function(response) {
                                    $('#switchboard-download-modal').remove();
                                    showDownloadModal(null, null, null, catName, termIdVal, response.data.zips || []);
                                }
                            });
                        }
                    });
                });
            }

            // Add expand state tracking on page load
            $('.category-item').each(function() {
                const $children = $(this).find('> .category-children');
                if ($children.length && $children.children().length > 0) {
                    $(this).addClass('has-children');
                    // Initially collapsed
                    $children.hide();
                }
            });

            // Search categories
            $('#category-search').on('keyup', function() {
                const searchTerm = $(this).val().toLowerCase();
                const $items = $('.category-item, .category-drop-zone:not(.category-item .category-drop-zone)');

                if (searchTerm === '') {
                    $items.show();
                    $('.category-children').hide();
                    $('.category-item').removeClass('expanded');
                } else {
                    $items.each(function() {
                        const $this = $(this);
                        const categoryName = $this.attr('data-category-name') || $this.find('strong').text();
                        const matches = categoryName.toLowerCase().includes(searchTerm);

                        if (matches) {
                            $this.show();
                            // Auto-expand parents of matches
                            const $parent = $this.closest('.category-item');
                            if ($parent.length) {
                                $parent.find('> .category-children').show();
                                $parent.addClass('expanded');
                            }
                        } else {
                            $this.hide();
                        }
                    });
                }
            });

            // Sort categories
            $('#category-sort').on('change', function() {
                const sortBy = $(this).val();
                if (!sortBy || sortBy === '') return;
                
                // Find the container - it's the sb-sp-col that contains category-item elements
                // Look for the one inside media-categories-sidebar that has category-item children
                let $categoriesContainer = $('#media-categories-sidebar .sb-sp-col').filter(function() {
                    return $(this).find('> .category-item').length > 0;
                }).first();
                
                if (!$categoriesContainer.length) {
                    // Fallback: try to find any sb-sp-col with category-item
                    $categoriesContainer = $('.sb-sp-col:has(> .category-item)').first();
                    if (!$categoriesContainer.length) {
                        return;
                    }
                }

                // Get all top-level category items (not special categories, not subcategories)
                const $items = $categoriesContainer.find('> .category-item');

                if ($items.length === 0) return;

                // Convert to array and sort
                const itemsArray = $items.toArray();

                itemsArray.sort(function(a, b) {
                    const $a = $(a);
                    const $b = $(b);
                    
                    const nameA = ($a.attr('data-category-name') || $a.find('.category-name').text().trim() || '').toLowerCase();
                    const nameB = ($b.attr('data-category-name') || $b.find('.category-name').text().trim() || '').toLowerCase();
                    
                    const countA = parseInt($a.attr('data-count') || $a.find('.category-count').text().trim() || 0) || 0;
                    const countB = parseInt($b.attr('data-count') || $b.find('.category-count').text().trim() || 0) || 0;

                    switch(sortBy) {
                        case 'name':
                            return nameA.localeCompare(nameB);
                        case 'count':
                            return countB - countA; // High to low
                        case 'count-asc':
                            return countA - countB; // Low to high
                        default:
                            return 0;
                    }
                });

                // Detach all items from DOM
                itemsArray.forEach(function(item) {
                    $(item).detach();
                });

                // Re-append in sorted order
                itemsArray.forEach(function(item) {
                    $categoriesContainer.append(item);
                });
            });

            // Click to filter - applies to all categories (parents, children, and special items)
            // Upload-to-category: sync dropdown with AJAX
            $('#switchboard-upload-category-select').on('change', function() {
                var categoryId = $(this).val();
                var nonce = typeof switchboardMediaCategoryData !== 'undefined' && switchboardMediaCategoryData.nonce ? switchboardMediaCategoryData.nonce : '';
                if (!nonce) return; // Cannot proceed without valid nonce
                $.ajax({
                    url: ajaxurl,
                    type: 'POST',
                    data: {
                        action: 'switchboard_set_upload_category',
                        category_id: categoryId,
                        nonce: nonce
                    },
                    success: function(response) {
                        if (response.success) {
                            showToast(categoryId > 0 ? 'New uploads will be assigned to this category' : 'Upload category cleared');
                        }
                    }
                });
            });

            $(document).on('click', '.category-drop-zone', function(e) {
                // Don't filter if clicking on checkbox, input field, or any button
                if ($(e.target).is('.category-checkbox, .category-name-input, input, button, .category-actions, .category-actions *')) {
                    return;
                }

                // Get the term ID to filter
                const termId = $(this).data('term-id');

                // Auto-sync: update the upload-to dropdown when clicking a category
                if (termId > 0) {
                    $('#switchboard-upload-category-select').val(termId).trigger('change');
                }

                // Get current view mode from URL, default to 'list' if not present
                const urlParams = new URLSearchParams(window.location.search);
                const currentMode = urlParams.get('mode') || 'list';

                // Apply filter for all categories (parent and child)
                // Don't expand subcategories on click - they will expand automatically when page loads if this category is active
                if (termId === -1) {
                    // Show all media - remove filter (preserve current view mode)
                    saveUIState();
                    window.location.href = 'upload.php?mode=' + currentMode;
                } else if (termId === 0) {
                    // Show uncategorized (preserve current view mode)
                    saveUIState();
                    window.location.href = 'upload.php?mode=' + currentMode + '&media_category=0';
                } else {
                    // Filter by category (works for both parent and child categories, preserve current view mode)
                    saveUIState();
                    window.location.href = 'upload.php?mode=' + currentMode + '&media_category=' + termId;
                }
            });
            
            // Make media items draggable (works for both list and grid views)
            function initDraggable() {
                const isListView = $('.wp-list-table').length > 0;
                const isGridView = $('.attachments-browser').length > 0;

                if (isListView) {
                    // Skip list view row dragging when sortable reorder is active
                    if (sortableActive) {
                        return;
                    }

                    // LIST VIEW: Make table rows draggable
                    const $rows = $('.wp-list-table tbody tr');

                    if (!$rows.length) {
                        return;
                    }

                    // Remove any existing draggable to avoid duplicates
                    $rows.each(function() {
                        if ($(this).hasClass('ui-draggable')) {
                            $(this).draggable('destroy');
                        }
                    });

                    // Initialize draggable
                    $rows.draggable({
                        helper: 'clone',
                        opacity: 0.7,
                        cursor: 'move',
                        revert: 'invalid',
                        revertDuration: 200,
                        zIndex: 1000,
                        start: function() {
                            $(this).css('opacity', '0.5');
                            // Highlight all drop zones (but preserve active state styling)
                            $('.category-drop-zone:not(.active)').css({
                                'border-color': '#2271b1',
                                'background': 'var(--primary-50)'
                            });
                        },
                        stop: function() {
                            $(this).css('opacity', '1');
                            // Reset drop zones (but preserve active state styling)
                            $('.category-drop-zone:not(.active)').css({
                                'border-color': '#ddd',
                                'background': '#f9f9f9'
                            });
                        }
                    });
                } else if (isGridView) {
                    // GRID VIEW: Make attachment items draggable
                    const $attachments = $('.attachments .attachment');

                    if (!$attachments.length) {
                        return;
                    }

                    // Remove any existing draggable to avoid duplicates
                    $attachments.each(function() {
                        if ($(this).hasClass('ui-draggable')) {
                            $(this).draggable('destroy');
                        }
                    });

                    // Initialize draggable for grid items
                    $attachments.draggable({
                        helper: 'clone',
                        opacity: 0.7,
                        cursor: 'move',
                        revert: 'invalid',
                        revertDuration: 200,
                        zIndex: 1000,
                        start: function() {
                            $(this).css('opacity', '0.5');
                            // Highlight all drop zones
                            $('.category-drop-zone:not(.active)').css({
                                'border-color': '#2271b1',
                                'background': 'var(--primary-50)'
                            });
                        },
                        stop: function() {
                            $(this).css('opacity', '1');
                            // Reset drop zones
                            $('.category-drop-zone:not(.active)').css({
                                'border-color': '#ddd',
                                'background': '#f9f9f9'
                            });
                        }
                    });
                }
            }

            // Initialize sortable for media reordering within a category (list view only)
            // Track whether sortable is active so initDraggable can skip rows
            var sortableActive = false;

            function initSortable() {
                var urlParams = new URLSearchParams(window.location.search);
                var categoryId = urlParams.get('media_category');

                // Only enable for specific categories (not All Media or Uncategorized)
                if (!categoryId || parseInt(categoryId) <= 0) {
                    sortableActive = false;
                    return;
                }

                sortableActive = true;

                var $table = $('.wp-list-table');
                if (!$table.length) {
                    return;
                }

                var $tbody = $table.find('tbody#the-list');
                if (!$tbody.length) {
                    $tbody = $table.find('tbody');
                }
                if (!$tbody.length || $tbody.find('tr').length === 0) {
                    return;
                }

                // Add drag-handle header column (once)
                if (!$table.find('thead .switchboard-drag-column').length) {
                    $table.find('thead tr').append('<th class="switchboard-drag-column"></th>');
                    $table.find('tfoot tr').append('<th class="switchboard-drag-column"></th>');
                }

                // Add drag handle td to any rows that don't have them
                $tbody.find('tr').each(function() {
                    var $row = $(this);
                    if ($row.find('.switchboard-drag-handle').length) return;

                    $row.append('<td class="switchboard-drag-column"><span class="switchboard-drag-handle" title="Drag to reorder"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="width:14px;height:14px;"><line x1="4" y1="9" x2="20" y2="9"></line><line x1="4" y1="15" x2="20" y2="15"></line></svg></span></td>');
                });

                // Show info notice above the table
                if (!$('.switchboard-sortable-notice').length) {
                    var $notice = $('<div class="switchboard-sortable-notice"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="width:14px;height:14px;flex-shrink:0;"><line x1="4" y1="9" x2="20" y2="9"></line><line x1="4" y1="15" x2="20" y2="15"></line></svg> Drag rows to reorder media within this category. Changes are saved automatically.</div>');
                    $table.before($notice);
                }

                // Initialize jQuery UI Sortable (only once)
                if (!$tbody.hasClass('ui-sortable')) {
                    $tbody.sortable({
                        handle: '.switchboard-drag-handle',
                        axis: 'y',
                        placeholder: 'sortable-placeholder',
                        helper: function(e, tr) {
                            var $originals = tr.children();
                            var $helper = tr.clone();
                            $helper.children().each(function(index) {
                                $(this).width($originals.eq(index).width());
                            });
                            return $helper;
                        },
                        start: function(e, ui) {
                            ui.placeholder.height(ui.helper.outerHeight());
                            var colCount = ui.helper.children('td').length;
                            ui.placeholder.html('<td colspan="' + colCount + '"></td>');
                        },
                        update: function(e, ui) {
                            var newOrder = [];
                            $tbody.find('tr').each(function() {
                                var rowId = $(this).attr('id');
                                if (rowId && rowId.indexOf('post-') === 0) {
                                    newOrder.push(parseInt(rowId.replace('post-', ''), 10));
                                }
                            });

                            if (newOrder.length === 0) return;

                            var nonce = (typeof switchboardMediaCategoryData !== 'undefined' && switchboardMediaCategoryData.nonce) ? switchboardMediaCategoryData.nonce : '';
                            if (!nonce) return;

                            $.ajax({
                                url: ajaxurl,
                                type: 'POST',
                                data: {
                                    action: 'switchboard_save_media_order',
                                    term_id: parseInt(categoryId),
                                    order: newOrder,
                                    nonce: nonce
                                },
                                success: function(response) {
                                    if (response.success) {
                                        showToast('Media order saved');
                                    } else {
                                        showToast('Error saving order');
                                    }
                                },
                                error: function() {
                                    showToast('Error saving media order');
                                }
                            });
                        }
                    });
                }

                // Destroy existing draggable on rows to prevent conflict with sortable
                // Must run every time since setInterval re-creates draggable
                $tbody.find('tr.ui-draggable').each(function() {
                    try { $(this).draggable('destroy'); } catch(e) {}
                });
            }

            // Initialize draggable on page load
            initDraggable();
            initSortable();

            // Re-initialize on pagination/content change - check every 500ms if items changed
            let lastItemCount = $('.wp-list-table tbody tr, .attachments .attachment').length;
            let isDragging = false;

            // Track dragging state globally
            $(document).on('mousedown', '.wp-list-table tbody tr.ui-draggable, .attachments .attachment.ui-draggable', function() {
                isDragging = true;
            }).on('mouseup', function() {
                isDragging = false;
            });

            setInterval(function() {
                // Don't re-initialize while actively dragging
                if (isDragging) {
                    return;
                }

                const currentItemCount = $('.wp-list-table tbody tr, .attachments .attachment').length;
                const listViewHasDraggable = $('.wp-list-table tbody tr:first').hasClass('ui-draggable');
                const gridViewHasDraggable = $('.attachments .attachment:first').hasClass('ui-draggable');

                // If item count changed or draggable lost, re-init
                if (currentItemCount !== lastItemCount || (currentItemCount > 0 && !listViewHasDraggable && !gridViewHasDraggable)) {
                    lastItemCount = currentItemCount;
                    initDraggable();
                    initSortable();
                }
            }, 500);

            // Make category zones droppable with improved tolerance
            $('.category-drop-zone').droppable({
                accept: '.wp-list-table tbody tr, .attachments .attachment',
                tolerance: 'pointer', // Changed from default to pointer for better detection
                hoverClass: 'drag-over',
                over: function(event, ui) {
                    // Enhanced visual feedback
                    $(this).css({
                        'background': '#d5f4e6',
                        'border-color': '#00a32a',
                        'border-style': 'solid',
                        'transform': 'scale(1.02)',
                        'box-shadow': '0 2px 8px rgba(0,163,42,0.3)'
                    });
                },
                out: function(event, ui) {
                    // Reset when leaving (but preserve active state)
                    if (!$(this).hasClass('active')) {
                        $(this).css({
                            'background': '#f9f9f9',
                            'border-color': '#ddd',
                            'border-style': 'dashed',
                            'transform': 'scale(1)',
                            'box-shadow': 'none'
                        });
                    } else {
                        // Just reset transform and box-shadow for active items
                        $(this).css({
                            'transform': 'scale(1)',
                            'box-shadow': 'none'
                        });
                    }
                },
                drop: function(event, ui) {
                    const $draggable = ui.draggable;
                    const $dropZone = $(this);
                    let attachmentId = null;

                    // Determine if this is a list view row or grid view attachment
                    if ($draggable.hasClass('attachment')) {
                        // GRID VIEW: Extract ID from data-id attribute
                        attachmentId = $draggable.data('id');
                    } else {
                        // LIST VIEW: Extract ID from id attribute (format: post-{ID})
                        const rowId = $draggable.attr('id');
                        if (!rowId || !rowId.startsWith('post-')) {
                            console.warn('Cannot assign category: item missing valid post ID');
                            return;
                        }
                        attachmentId = rowId.replace('post-', '');
                    }

                    if (!attachmentId) {
                        console.warn('Cannot assign category: could not extract attachment ID');
                        return;
                    }

                    const termId = $dropZone.data('term-id');
                    const categoryName = $dropZone.find('strong').text();

                    // Skip "All Media" drop zone (termId === -1)
                    if (termId === -1) {
                        return;
                    }

                    // Visual feedback - pulsing effect
                    $dropZone.css({
                        'background': '#4ade80',
                        'border-color': '#16a34a'
                    });

                    // Assign category (termId === 0 means Uncategorized, send 0 to server)
                    $.post(ajaxurl, {
                        action: 'switchboard_assign_media_category',
                        attachment_id: attachmentId,
                        term_id: termId === 0 ? 0 : termId,
                        nonce: '<?php echo esc_js(wp_create_nonce('media_categories')); ?>'
                    }, function(response) {
                        if (response.success) {
                            // Update category column for list view
                            const $categoryCell = $draggable.find('.column-media_category');
                            if ($categoryCell.length) {
                                if (termId === 0) {
                                    // Uncategorized (termId === 0)
                                    $categoryCell.html('<span class="text-gray-400">Uncategorized</span>');
                                } else {
                                    $categoryCell.text(categoryName);
                                }
                            }

                            // Show success feedback
                            $draggable.css('background', '#d5f4e6');
                            setTimeout(function() {
                                $draggable.css('background', '');
                            }, 1000);
                            
                            // Show success toast
                            showToast('Media assigned to category');

                            // Refresh the page to update category counts in sidebar
                            setTimeout(function() {
                                location.reload();
                            }, 1200);
                        } else {
                            alert('Error assigning category');
                        }
                    });
                }
            });
        });
        </script>
        <?php
    }
    
    public function ajax_update_category() {
        check_ajax_referer('switchboard_nonce', 'nonce');

        if (!current_user_can('manage_categories')) {
            wp_send_json_error(__('Unauthorized', 'wpswitchboard'));
        }

        $term_id = isset($_POST['term_id']) ? intval($_POST['term_id']) : 0;
        $name = isset($_POST['name']) ? trim(sanitize_text_field($_POST['name'])) : '';

        if ($term_id <= 0) {
            wp_send_json_error(__('Invalid category ID.', 'wpswitchboard'));
        }

                if (empty($name)) {
                    wp_send_json_error(__('Category name cannot be empty.', 'wpswitchboard'));
                }

                if (!taxonomy_exists('media_category')) {
            wp_send_json_error(__('Media category taxonomy not registered.', 'wpswitchboard'));
        }

                $term = get_term($term_id, 'media_category');
                if (is_wp_error($term) || !$term) {
                    wp_send_json_error(__('Category not found.', 'wpswitchboard'));
                }

        $result = wp_update_term($term_id, 'media_category', ['name' => $name]);
                if (is_wp_error($result)) {
                    wp_send_json_error($result->get_error_message());
                }

        wp_send_json_success(['name' => $name]);
    }

    // ── Media Order Within Categories ──────────────────────────────────

    /**
     * Get the stored media order for a category.
     *
     * @param int $term_id The term ID
     * @return array Ordered array of attachment IDs
     */
    private function get_media_order($term_id) {
        $order = get_term_meta($term_id, 'switchboard_media_order', true);
        if (!is_array($order)) {
            return [];
        }
        return array_map('intval', $order);
    }

    /**
     * Save the media order for a category.
     *
     * @param int $term_id The term ID
     * @param array $order Ordered array of attachment IDs
     */
    private function save_media_order($term_id, $order) {
        $order = array_map('intval', array_values($order));
        update_term_meta($term_id, 'switchboard_media_order', $order);
    }

    /**
     * Add an attachment to the end of a category's media order.
     * No-op if already present.
     *
     * @param int $term_id The term ID
     * @param int $attachment_id The attachment ID to add
     */
    private function add_to_media_order($term_id, $attachment_id) {
        $order = $this->get_media_order($term_id);
        $attachment_id = intval($attachment_id);
        if (!in_array($attachment_id, $order, true)) {
            $order[] = $attachment_id;
            $this->save_media_order($term_id, $order);
        }
    }

    /**
     * Remove an attachment from a category's media order.
     *
     * @param int $term_id The term ID
     * @param int $attachment_id The attachment ID to remove
     */
    private function remove_from_media_order($term_id, $attachment_id) {
        $order = $this->get_media_order($term_id);
        $attachment_id = intval($attachment_id);
        $filtered = array_values(array_filter($order, function($id) use ($attachment_id) {
            return $id !== $attachment_id;
        }));
        if (count($filtered) !== count($order)) {
            $this->save_media_order($term_id, $filtered);
        }
    }

    /**
     * Remove an attachment from ALL category order arrays.
     * Used when an attachment is permanently deleted.
     *
     * @param int $attachment_id The attachment ID to remove
     */
    private function remove_from_all_media_orders($attachment_id) {
        // Only check categories this attachment actually belongs to (not all categories)
        $terms = wp_get_object_terms($attachment_id, 'media_category', ['fields' => 'ids']);
        if (is_wp_error($terms) || empty($terms)) {
            return;
        }
        foreach ($terms as $term_id) {
            $this->remove_from_media_order($term_id, $attachment_id);
        }
    }

    /**
     * Get the resolved media order for a category, intersected with actual members.
     * Stale IDs are dropped, new members are appended at end.
     *
     * @param int $term_id The term ID
     * @return array Ordered array of attachment IDs
     */
    private function get_resolved_media_order($term_id) {
        global $wpdb;

        $stored_order = $this->get_media_order($term_id);

        // Direct DB query to avoid triggering pre_get_posts recursion
        $actual_ids = $wpdb->get_col($wpdb->prepare(
            "SELECT p.ID FROM {$wpdb->posts} p
            INNER JOIN {$wpdb->term_relationships} tr ON p.ID = tr.object_id
            INNER JOIN {$wpdb->term_taxonomy} tt ON tr.term_taxonomy_id = tt.term_taxonomy_id
            WHERE tt.term_id = %d AND tt.taxonomy = 'media_category'
            AND p.post_type = 'attachment' AND p.post_status = 'inherit'",
            $term_id
        ));
        $actual_ids = array_map('intval', $actual_ids);

        if (empty($actual_ids)) {
            return [];
        }

        if (empty($stored_order)) {
            return $actual_ids;
        }

        $actual_set = array_flip($actual_ids);

        // Keep only stored IDs that still exist in category
        $resolved = [];
        foreach ($stored_order as $id) {
            if (isset($actual_set[$id])) {
                $resolved[] = $id;
                unset($actual_set[$id]);
            }
        }

        // Append any actual IDs not in stored order
        foreach ($actual_set as $id => $ignored) {
            $resolved[] = $id;
        }

        return $resolved;
    }

    public function ajax_assign_category() {
        check_ajax_referer('media_categories', 'nonce');

        if (!current_user_can('upload_files')) {
            wp_send_json_error(__('Unauthorized', 'wpswitchboard'));
        }

        $attachment_id = isset($_POST['attachment_id']) ? intval($_POST['attachment_id']) : 0;
        $term_id = isset($_POST['term_id']) ? intval($_POST['term_id']) : 0;

        // Validate attachment exists and is an attachment
        if ($attachment_id <= 0) {
            wp_send_json_error(__('Invalid attachment ID.', 'wpswitchboard'));
        }

        $post = get_post($attachment_id);
        if (!$post || $post->post_type !== 'attachment') {
            wp_send_json_error(__('Attachment not found.', 'wpswitchboard'));
        }

        // Get previous terms for count update
        $previous_terms = wp_get_object_terms($attachment_id, 'media_category', ['fields' => 'ids']);

        if ($term_id === 0) {
            // Remove all categories
            wp_set_object_terms($attachment_id, [], 'media_category');
            // Remove from all previous categories' order arrays
            if (is_array($previous_terms)) {
                foreach ($previous_terms as $prev_term_id) {
                    $this->remove_from_media_order($prev_term_id, $attachment_id);
                }
            }
        } else {
            // Validate term exists
            $term = get_term($term_id, 'media_category');
            if (is_wp_error($term) || !$term) {
                wp_send_json_error(__('Category not found.', 'wpswitchboard'));
            }
            // Set category
            wp_set_object_terms($attachment_id, [$term_id], 'media_category');
            // Remove from previous categories' order arrays
            if (is_array($previous_terms)) {
                foreach ($previous_terms as $prev_term_id) {
                    if ($prev_term_id !== $term_id) {
                        $this->remove_from_media_order($prev_term_id, $attachment_id);
                    }
                }
            }
            // Add to new category's order array
            $this->add_to_media_order($term_id, $attachment_id);
        }

        // Update term counts for affected terms
        $terms_to_update = is_array($previous_terms) ? $previous_terms : [];
        if ($term_id > 0) {
            $terms_to_update[] = $term_id;
        }
        if (!empty($terms_to_update)) {
            wp_update_term_count_now(array_unique($terms_to_update), 'media_category');
        }

        wp_send_json_success();
    }

    /**
     * AJAX handler for saving custom media order within a category.
     * Pagination-aware: splices page-level reorder into the full stored order.
     */
    public function ajax_save_media_order() {
        check_ajax_referer('media_categories', 'nonce');

        if (!current_user_can('upload_files')) {
            wp_send_json_error(__('Unauthorized', 'wpswitchboard'));
        }

        $term_id = isset($_POST['term_id']) ? intval($_POST['term_id']) : 0;
        $page_order = isset($_POST['order']) ? array_map('intval', (array) $_POST['order']) : [];

        if ($term_id <= 0) {
            wp_send_json_error(__('Invalid category ID.', 'wpswitchboard'));
        }

        $term = get_term($term_id, 'media_category');
        if (is_wp_error($term) || !$term) {
            wp_send_json_error(__('Category not found.', 'wpswitchboard'));
        }

        $page_order = array_values(array_filter($page_order, function($id) { return $id > 0; }));

        // Merge page-level reorder into the full stored order.
        // Find where the page items sit in the full order by matching IDs,
        // remove them, then re-insert in the new order at the same position.
        $full_order = $this->get_resolved_media_order($term_id);
        $page_set = array_flip($page_order);

        // Find the position of the first page item in the full order
        $insert_pos = null;
        $filtered = [];
        foreach ($full_order as $id) {
            if (isset($page_set[$id])) {
                if ($insert_pos === null) {
                    $insert_pos = count($filtered);
                }
                // Skip — will be re-inserted in new order
            } else {
                $filtered[] = $id;
            }
        }

        // Re-insert page items at the original position
        if ($insert_pos === null) {
            $insert_pos = count($filtered);
        }
        array_splice($filtered, $insert_pos, 0, $page_order);

        $this->save_media_order($term_id, $filtered);

        wp_send_json_success(['message' => __('Media order saved.', 'wpswitchboard')]);
    }

    /**
     * When an attachment is permanently deleted, remove it from all category order arrays.
     *
     * @param int $attachment_id The attachment ID being deleted
     */
    public function on_delete_attachment($attachment_id) {
        $this->remove_from_all_media_orders($attachment_id);
    }

    /**
     * Capture category from AJAX request early (before WordPress processes it)
     * This runs as an action hook on the AJAX handler with priority 5 (very early)
     */
    public function capture_ajax_category() {
        // Skip if external plugin is handling categories
        if ($this->is_external_plugin_handling_categories()) {
            static::$ajax_category_id = null;
            return;
        }

        // Capture the category ID from POST data as early as possible
        if (isset($_POST['query']['media_category'])) {
            $category_id = intval($_POST['query']['media_category']);
            static::$ajax_category_id = $category_id;
        } elseif (isset($_REQUEST['query']['media_category'])) {
            $category_id = intval($_REQUEST['query']['media_category']);
            static::$ajax_category_id = $category_id;
        } else {
            static::$ajax_category_id = null;
        }
    }

    /**
     * Capture media_category parameter from AJAX query-attachments requests
     * This filter runs before WordPress processes the query, so we can capture our custom parameter
     */
    public function ajax_query_attachments_args($query) {
        // Skip if external plugin is handling categories
        if ($this->is_external_plugin_handling_categories()) {
            return $query;
        }

        // Check for new Backbone.js filter parameter first (switchboard_media_category)
        $category_id = null;
        if (isset($_POST['query']['switchboard_media_category'])) {
            $category_id = intval($_POST['query']['switchboard_media_category']);
            static::$ajax_category_id = $category_id;
            $query['media_category'] = $category_id;
        }
        // Fallback: Check for old parameter name (media_category) for backward compatibility
        elseif (isset($_POST['query']['media_category'])) {
            $category_id = intval($_POST['query']['media_category']);
            static::$ajax_category_id = $category_id;
            $query['media_category'] = $category_id;
        }
        // Also check if it's already in the query array
        elseif (isset($query['media_category'])) {
            $category_id = intval($query['media_category']);
            static::$ajax_category_id = $category_id;
            $query['media_category'] = $category_id;
        }
        // Check REQUEST as fallback
        elseif (isset($_REQUEST['query']['media_category'])) {
            $category_id = intval($_REQUEST['query']['media_category']);
            static::$ajax_category_id = $category_id;
            $query['media_category'] = $category_id;
        } else {
            static::$ajax_category_id = null;
        }

        // Custom media order is applied in filter_by_category (pre_get_posts)
        // to avoid duplicate get_resolved_media_order() calls

        return $query;
    }

    /**
     * Get media categories for modal filter
     * AJAX handler for fetching category list
     */
    public function ajax_get_categories() {
        check_ajax_referer('switchboard_nonce', 'nonce');

        $categories = get_terms([
            'taxonomy' => 'media_category',
            'hide_empty' => false,
        ]);

        if (is_wp_error($categories) || empty($categories)) {
            wp_send_json_success([
                'categories' => [],
                'hierarchical' => true,
            ]);
            return;
        }

        // Build hierarchical structure
        $hierarchical = [];
        $children_by_parent = [];

        foreach ($categories as $category) {
            if ($category->parent == 0) {
                $hierarchical[] = [
                    'id' => $category->term_id,
                    'name' => $category->name,
                    'parent' => 0,
                ];
            } else {
                if (!isset($children_by_parent[$category->parent])) {
                    $children_by_parent[$category->parent] = [];
                }
                $children_by_parent[$category->parent][] = [
                    'id' => $category->term_id,
                    'name' => $category->name,
                    'parent' => $category->parent,
                ];
            }
        }

        wp_send_json_success([
            'categories' => $hierarchical,
            'children' => $children_by_parent,
            'hierarchical' => true,
        ]);
    }

    /**
     * AJAX handler for creating a new media category
     */
    public function ajax_create_category() {
        check_ajax_referer('switchboard_nonce', 'nonce');

        if (!current_user_can('manage_categories')) {
            wp_send_json_error(__('Unauthorized', 'wpswitchboard'));
        }

        $name = isset($_POST['name']) ? trim(sanitize_text_field($_POST['name'])) : '';
        $parent = isset($_POST['parent']) ? intval($_POST['parent']) : 0;

        if (empty($name)) {
            wp_send_json_error(__('Category name cannot be empty.', 'wpswitchboard'));
        }

        if (!taxonomy_exists('media_category')) {
            wp_send_json_error(__('Media category taxonomy not registered.', 'wpswitchboard'));
        }

        // Create the term
        $args = ['parent' => $parent];
        $result = wp_insert_term($name, 'media_category', $args);

        if (is_wp_error($result)) {
            wp_send_json_error($result->get_error_message());
        }

        wp_send_json_success([
            'term_id' => $result['term_id'],
            'name' => $name,
        ]);
    }

    /**
     * AJAX handler for deleting a media category
     */
    public function ajax_delete_category() {
        check_ajax_referer('switchboard_nonce', 'nonce');

        if (!current_user_can('manage_categories')) {
            wp_send_json_error(__('Unauthorized', 'wpswitchboard'));
        }

        $term_id = isset($_POST['term_id']) ? intval($_POST['term_id']) : 0;

        if ($term_id <= 0) {
            wp_send_json_error(__('Invalid category ID.', 'wpswitchboard'));
        }

        if (!taxonomy_exists('media_category')) {
            wp_send_json_error(__('Media category taxonomy not registered.', 'wpswitchboard'));
        }

        $term = get_term($term_id, 'media_category');
        if (is_wp_error($term) || !$term) {
            wp_send_json_error(__('Category not found.', 'wpswitchboard'));
        }

        // Get child categories before deletion
        $children = get_terms([
            'taxonomy' => 'media_category',
            'parent' => $term_id,
            'hide_empty' => false,
        ]);

        // Move child categories to top level (parent = 0) before deleting parent
        // This ensures subcategories are preserved when parent is deleted
        // Note: Media files are NOT deleted, only the category term is deleted
        if (!is_wp_error($children) && !empty($children)) {
            foreach ($children as $child) {
                wp_update_term($child->term_id, 'media_category', ['parent' => 0]);
            }
        }

        // Clean up media order term meta
        delete_term_meta($term_id, 'switchboard_media_order');

        // Now delete the category (media files are NOT deleted, only the category term)
        $result = wp_delete_term($term_id, 'media_category');
        if (is_wp_error($result)) {
            wp_send_json_error($result->get_error_message());
        }

        wp_send_json_success([
            'message' => __('Category deleted. Subcategories moved to top level. Media files were not deleted, but are now uncategorized.', 'wpswitchboard')
        ]);
    }

    /**
     * AJAX handler for downloading media category as ZIP
     * Creates a ZIP file with all media in the category and returns download URL
     */
    public function ajax_download_category_zip() {
        check_ajax_referer('switchboard_nonce', 'nonce');

        if (!current_user_can('upload_files')) {
            wp_send_json_error(__('Unauthorized', 'wpswitchboard'));
        }

        $term_id = isset($_POST['term_id']) ? intval($_POST['term_id']) : 0;
        $auto_delete = isset($_POST['auto_delete']) ? (bool) $_POST['auto_delete'] : true;

        if ($term_id <= 0) {
            wp_send_json_error(__('Invalid category ID.', 'wpswitchboard'));
        }

        // Verify term exists
        $term = get_term($term_id, 'media_category');
        if (is_wp_error($term) || !$term) {
            wp_send_json_error(__('Category not found.', 'wpswitchboard'));
        }

        // Get all attachments in this category
        $attachments = get_posts([
            'post_type' => 'attachment',
            'post_status' => 'inherit',
            'posts_per_page' => -1,
            'tax_query' => [
                [
                    'taxonomy' => 'media_category',
                    'field' => 'term_id',
                    'terms' => $term_id,
                ],
            ],
        ]);

        // Filter to unique attachments with valid files
        $filtered_attachments = [];
        $seen_ids = [];

        foreach ($attachments as $attachment) {
            // Skip duplicate attachment IDs
            if (isset($seen_ids[$attachment->ID])) {
                continue;
            }
            $seen_ids[$attachment->ID] = true;

            // Verify the file exists
            $file_path = get_attached_file($attachment->ID);
            if (!$file_path || !file_exists($file_path)) {
                continue;
            }

            $filtered_attachments[] = $attachment;
        }

        if (empty($filtered_attachments)) {
            wp_send_json_error(__('No media files found in this category.', 'wpswitchboard'));
        }

        // Create ZIP file
        $result = $this->create_category_zip($filtered_attachments, $term->name, $term_id, $auto_delete);

        if (is_wp_error($result)) {
            wp_send_json_error($result->get_error_message());
        }

        wp_send_json_success([
            'download_url' => $result['url'],
            'filename' => $result['filename'],
            'file_count' => count($filtered_attachments),
            'zip_id' => $result['zip_id'],
            'auto_delete' => $auto_delete,
        ]);
    }

    /**
     * AJAX handler for checking ZIP status
     */
    public function ajax_get_category_zip_status() {
        check_ajax_referer('switchboard_nonce', 'nonce');

        if (!current_user_can('upload_files')) {
            wp_send_json_error(__('Unauthorized', 'wpswitchboard'));
        }

        $zip_id = isset($_POST['zip_id']) ? sanitize_text_field($_POST['zip_id']) : '';

        if (empty($zip_id)) {
            wp_send_json_error(__('Invalid ZIP ID.', 'wpswitchboard'));
        }

        // Security: Validate zip_id format to prevent path traversal
        if (!preg_match('/^[a-zA-Z0-9_-]+$/', $zip_id)) {
            wp_send_json_error(__('Invalid ZIP ID format.', 'wpswitchboard'));
        }

        $upload_dir = wp_upload_dir();
        $zip_dir = trailingslashit($upload_dir['basedir']) . 'switchboard-downloads';
        $zip_file = $zip_dir . '/' . $zip_id . '.zip';

        if (!file_exists($zip_file)) {
            wp_send_json_error(__('ZIP file not found or expired.', 'wpswitchboard'));
        }

        wp_send_json_success([
            'exists' => true,
            'size' => size_format(filesize($zip_file)),
        ]);
    }

    /**
     * AJAX handler for deleting a ZIP file
     */
    public function ajax_delete_category_zip() {
        check_ajax_referer('switchboard_nonce', 'nonce');

        if (!current_user_can('upload_files')) {
            wp_send_json_error(__('Unauthorized', 'wpswitchboard'));
        }

        $zip_id = isset($_POST['zip_id']) ? sanitize_text_field($_POST['zip_id']) : '';

        if (empty($zip_id)) {
            wp_send_json_error(__('Invalid ZIP ID.', 'wpswitchboard'));
        }

        // Security: Validate zip_id format to prevent path traversal
        // Only allow alphanumeric characters, hyphens, and underscores
        if (!preg_match('/^[a-zA-Z0-9_-]+$/', $zip_id)) {
            wp_send_json_error(__('Invalid ZIP ID format.', 'wpswitchboard'));
        }

        $upload_dir = wp_upload_dir();
        $zip_dir = trailingslashit($upload_dir['basedir']) . 'switchboard-downloads';
        $zip_file = $zip_dir . '/' . $zip_id . '.zip';
        $meta_file = $zip_dir . '/' . $zip_id . '.json';

        // Security: Verify the file is within the expected directory
        $real_zip_dir = realpath($zip_dir);
        $real_zip_file = realpath(dirname($zip_file)) . '/' . basename($zip_file);
        if ($real_zip_dir && strpos($real_zip_file, $real_zip_dir) !== 0) {
            wp_send_json_error(__('Invalid file path.', 'wpswitchboard'));
        }

        if (file_exists($zip_file)) {
            @unlink($zip_file);
        }
        if (file_exists($meta_file)) {
            @unlink($meta_file);
        }

        wp_send_json_success(['deleted' => true]);
    }

    /**
     * Create ZIP file containing all media from a category
     *
     * @param array $attachments Array of attachment posts
     * @param string $category_name Category name for ZIP filename
     * @param int $term_id Term ID for unique identification
     * @param bool $auto_delete Whether to auto-delete after 1 hour
     * @return array|WP_Error Array with url, filename, zip_id on success, WP_Error on failure
     */
    private function create_category_zip($attachments, $category_name, $term_id, $auto_delete = true) {
        // Check for ZIP support
        $use_ziparchive = extension_loaded('zip') && class_exists('ZipArchive');
        $use_pclzip = !$use_ziparchive;

        if (!$use_ziparchive && !class_exists('PclZip')) {
            require_once(ABSPATH . 'wp-admin/includes/class-pclzip.php');
            $use_pclzip = class_exists('PclZip');
        }

        if (!$use_ziparchive && !$use_pclzip) {
            return new WP_Error(
                'zip_not_available',
                __('ZIP extension is not available on this server.', 'wpswitchboard')
            );
        }

        // Increase limits
        @ini_set('memory_limit', '512M');
        @set_time_limit(300);

        // Create downloads directory
        $upload_dir = wp_upload_dir();
        $zip_dir = trailingslashit($upload_dir['basedir']) . 'switchboard-downloads';

        if (!file_exists($zip_dir)) {
            wp_mkdir_p($zip_dir);
            // Add index.php and .htaccess for security
            file_put_contents($zip_dir . '/index.php', '<?php // Silence is golden');
            file_put_contents($zip_dir . '/.htaccess', "Options -Indexes\n");
        }

        // Clean up old ZIPs (older than 1 hour) - only auto-delete ones
        $this->cleanup_old_category_zips($zip_dir);

        // Generate unique ZIP ID and filename
        $safe_name = sanitize_file_name($category_name);
        $zip_id = $safe_name . '-' . $term_id . '-' . wp_generate_password(8, false);
        $zip_file = $zip_dir . '/' . $zip_id . '.zip';
        $zip_filename = $safe_name . '-media.zip';

        // Store ZIP metadata (for tracking auto-delete preference)
        $meta_file = $zip_dir . '/' . $zip_id . '.json';
        $meta_data = [
            'created' => time(),
            'auto_delete' => $auto_delete,
            'category_name' => $category_name,
            'term_id' => $term_id,
            'file_count' => count($attachments),
        ];
        file_put_contents($meta_file, json_encode($meta_data));

        // Calculate total size
        $total_size = 0;
        foreach ($attachments as $attachment) {
            $file_path = get_attached_file($attachment->ID);
            if ($file_path && file_exists($file_path)) {
                $total_size += filesize($file_path);
            }
        }

        // Check size limit (500MB)
        if ($total_size > 500 * 1024 * 1024) {
            return new WP_Error(
                'file_too_large',
                sprintf(
                    __('Total file size (%s) exceeds maximum allowed (500MB).', 'wpswitchboard'),
                    size_format($total_size)
                )
            );
        }

        // Check disk space using centralized handler
        if (!Switchboard_Zip_Handler::get_instance()->has_disk_space($total_size)) {
            return new WP_Error(
                'insufficient_space',
                __('Insufficient disk space to create ZIP file.', 'wpswitchboard')
            );
        }

        // Create ZIP
        if ($use_ziparchive) {
            $result = $this->create_category_zip_ziparchive($zip_file, $attachments);
        } else {
            $result = $this->create_category_zip_pclzip($zip_file, $attachments);
        }

        if (is_wp_error($result)) {
            return $result;
        }

        // Verify ZIP was created
        if (!file_exists($zip_file) || filesize($zip_file) === 0) {
            return new WP_Error(
                'zip_creation_failed',
                __('Failed to create ZIP file.', 'wpswitchboard')
            );
        }

        // Generate download URL
        $download_url = trailingslashit($upload_dir['baseurl']) . 'switchboard-downloads/' . $zip_id . '.zip';

        return [
            'url' => $download_url,
            'filename' => $zip_filename,
            'zip_id' => $zip_id,
        ];
    }

    /**
     * Create category ZIP using ZipArchive
     */
    private function create_category_zip_ziparchive($zip_file, $attachments) {
        $zip = new ZipArchive();

        if ($zip->open($zip_file, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== true) {
            return new WP_Error(
                'zip_create_failed',
                __('Failed to create ZIP archive.', 'wpswitchboard')
            );
        }

        $file_count = 0;
        $used_names = [];

        foreach ($attachments as $attachment) {
            $file_path = get_attached_file($attachment->ID);

            if (!$file_path || !file_exists($file_path) || !is_readable($file_path)) {
                continue;
            }

            // Get original filename
            $filename = basename($file_path);

            // Handle duplicate filenames
            if (isset($used_names[$filename])) {
                $used_names[$filename]++;
                $ext = pathinfo($filename, PATHINFO_EXTENSION);
                $name = pathinfo($filename, PATHINFO_FILENAME);
                $filename = $name . '-' . $used_names[$filename] . '.' . $ext;
            } else {
                $used_names[$filename] = 1;
            }

            if (@$zip->addFile($file_path, $filename)) {
                $file_count++;
            }
        }

        if ($file_count === 0) {
            $zip->close();
            @unlink($zip_file);
            return new WP_Error(
                'no_files_added',
                __('No files could be added to the ZIP archive.', 'wpswitchboard')
            );
        }

        $zip->close();
        return true;
    }

    /**
     * Create category ZIP using PclZip
     */
    private function create_category_zip_pclzip($zip_file, $attachments) {
        if (!class_exists('PclZip')) {
            require_once(ABSPATH . 'wp-admin/includes/class-pclzip.php');
        }

        $zip = new PclZip($zip_file);
        $files_to_add = [];
        $used_names = [];
        $temp_files = [];

        foreach ($attachments as $attachment) {
            $file_path = get_attached_file($attachment->ID);

            if (!$file_path || !file_exists($file_path) || !is_readable($file_path)) {
                continue;
            }

            // Get original filename
            $filename = basename($file_path);

            // Handle duplicate filenames by creating temp copies
            if (isset($used_names[$filename])) {
                $used_names[$filename]++;
                $ext = pathinfo($filename, PATHINFO_EXTENSION);
                $name = pathinfo($filename, PATHINFO_FILENAME);
                $new_filename = $name . '-' . $used_names[$filename] . '.' . $ext;

                // Create temp file with new name
                $temp_dir = sys_get_temp_dir();
                $temp_file = $temp_dir . '/' . $new_filename;
                if (@copy($file_path, $temp_file)) {
                    $files_to_add[] = $temp_file;
                    $temp_files[] = $temp_file;
                }
            } else {
                $used_names[$filename] = 1;
                $files_to_add[] = $file_path;
            }
        }

        if (empty($files_to_add)) {
            return new WP_Error(
                'no_files_added',
                __('No files could be added to the ZIP archive.', 'wpswitchboard')
            );
        }

        $result = $zip->create($files_to_add, PCLZIP_OPT_REMOVE_ALL_PATH);

        // Clean up temp files
        foreach ($temp_files as $temp_file) {
            @unlink($temp_file);
        }

        if ($result == 0) {
            return new WP_Error(
                'zip_create_failed',
                sprintf(__('Failed to create ZIP: %s', 'wpswitchboard'), $zip->errorInfo(true))
            );
        }

        return true;
    }

    /**
     * Clean up old ZIP files (only those with auto_delete enabled)
     */
    private function cleanup_old_category_zips($zip_dir) {
        $files = @glob($zip_dir . '/*.zip');
        if (!$files) {
            return;
        }

        foreach ($files as $file) {
            // Check if file is older than 1 hour
            if (!file_exists($file) || (time() - filemtime($file)) <= Switchboard_Zip_Handler::EXPIRY_TIME) {
                continue;
            }

            // Check metadata file for auto_delete preference
            $zip_id = basename($file, '.zip');
            $meta_file = $zip_dir . '/' . $zip_id . '.json';

            $should_delete = true; // Default to auto-delete for legacy files without metadata

            if (file_exists($meta_file)) {
                $meta = json_decode(file_get_contents($meta_file), true);
                if (isset($meta['auto_delete']) && !$meta['auto_delete']) {
                    $should_delete = false;
                }
            }

            if ($should_delete) {
                @unlink($file);
                if (file_exists($meta_file)) {
                    @unlink($meta_file);
                }
            }
        }
    }

    /**
     * Get list of existing ZIP files for a category (or all)
     */
    public function ajax_get_existing_category_zips() {
        check_ajax_referer('switchboard_nonce', 'nonce');

        if (!current_user_can('upload_files')) {
            wp_send_json_error(__('Unauthorized', 'wpswitchboard'));
        }

        $term_id = isset($_POST['term_id']) ? intval($_POST['term_id']) : 0;

        $upload_dir = wp_upload_dir();
        $zip_dir = trailingslashit($upload_dir['basedir']) . 'switchboard-downloads';

        if (!file_exists($zip_dir)) {
            wp_send_json_success(['zips' => []]);
        }

        $zips = [];
        $files = @glob($zip_dir . '/*.zip');

        if ($files) {
            foreach ($files as $file) {
                $zip_id = basename($file, '.zip');
                $meta_file = $zip_dir . '/' . $zip_id . '.json';

                // Skip if no metadata
                if (!file_exists($meta_file)) {
                    continue;
                }

                $meta = json_decode(file_get_contents($meta_file), true);

                // Filter by term_id if specified
                if ($term_id > 0 && isset($meta['term_id']) && $meta['term_id'] != $term_id) {
                    continue;
                }

                // Calculate expiry info
                $file_time = $meta['created'] ?? filemtime($file);
                $auto_delete = $meta['auto_delete'] ?? true;
                $expiry_time = Switchboard_Zip_Handler::EXPIRY_TIME;
                $time_remaining = ($file_time + $expiry_time) - time();
                $is_expired = $auto_delete && $time_remaining <= 0;

                // Skip expired files with auto_delete enabled
                if ($is_expired) {
                    continue;
                }

                $zips[] = [
                    'zip_id' => $zip_id,
                    'download_url' => trailingslashit($upload_dir['baseurl']) . 'switchboard-downloads/' . $zip_id . '.zip',
                    'filename' => sanitize_file_name($meta['category_name'] ?? 'media') . '-media.zip',
                    'category_name' => $meta['category_name'] ?? 'Unknown',
                    'term_id' => $meta['term_id'] ?? 0,
                    'file_count' => $meta['file_count'] ?? 0,
                    'created' => $file_time,
                    'created_human' => human_time_diff($file_time) . ' ago',
                    'size' => size_format(filesize($file)),
                    'auto_delete' => $auto_delete,
                    'expires_in' => $auto_delete && $time_remaining > 0 ? human_time_diff(time(), $file_time + $expiry_time) : null,
                    'is_expired' => $is_expired,
                ];
            }
        }

        // Sort by creation time (newest first)
        usort($zips, function($a, $b) {
            return $b['created'] - $a['created'];
        });

        wp_send_json_success(['zips' => $zips]);
    }

    /**
     * AJAX handler to set the upload target category
     * Stores selection in a per-user transient (expires after 1 hour)
     */
    public function ajax_set_upload_category() {
        check_ajax_referer('media_categories', 'nonce');

        if (!current_user_can('upload_files')) {
            wp_send_json_error('Unauthorized');
        }

        $category_id = isset($_POST['category_id']) ? intval($_POST['category_id']) : 0;
        $user_id = get_current_user_id();

        if ($category_id > 0) {
            $term = get_term($category_id, 'media_category');
            if (is_wp_error($term) || !$term) {
                wp_send_json_error('Invalid category');
            }
            set_transient('switchboard_upload_category_' . $user_id, $category_id, HOUR_IN_SECONDS);
        } else {
            delete_transient('switchboard_upload_category_' . $user_id);
        }

        wp_send_json_success();
    }

    /**
     * Auto-assign newly uploaded media to the selected upload category
     *
     * @param int $attachment_id The attachment ID
     */
    public function auto_assign_upload_category($attachment_id) {
        $user_id = get_current_user_id();
        if (!$user_id) {
            return;
        }

        $category_id = get_transient('switchboard_upload_category_' . $user_id);

        if ($category_id && intval($category_id) > 0) {
            $term = get_term(intval($category_id), 'media_category');
            if ($term && !is_wp_error($term)) {
                wp_set_object_terms($attachment_id, [intval($category_id)], 'media_category');
                $this->add_to_media_order(intval($category_id), $attachment_id);
            }
        }
    }

}

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