/**
 * KooKoa Folders — Bulk Organize (Grid & List)
 *
 * Extracted from admin.js (block 11).
 * IIFE that adds methods directly to FO (window.KookoaWPF).
 *
 * Depends on: kookoawpf-admin (FO.data, FO.tree, FO.currentFolder, FO.ajax,
 *             FO.showNotice, FO.refreshTree, FO.goToFolder, FO.getFromFolderId,
 *             FO.getItemId, FO.populateFolderSelect, FO._refreshContentView)
 *
 * @since 1.0.0
 */
(function($) {
    'use strict';

    const FO = window.KookoaWPF;
    if (!FO) return;

    // ========================
    // Bulk Organize
    // ========================

    FO.initBulkOrganize = function() {
        if (this.data.postType === 'attachment' && $('body').hasClass('upload-php')) {
            this._initGridBulkOrganize();
        }
        this._initListBulkOrganize();
    };

    FO._initGridBulkOrganize = function() {
        const self = this;

        const checkInterval = setInterval(function() {
            const $toolbar = $('.media-toolbar-secondary');
            if ($toolbar.length && !$('#kookoawpf-grid-filter').length) {
                clearInterval(checkInterval);
                self._injectGridToolbarElements($toolbar);
            }
        }, 200);

        $(document).on('click', '#kookoawpf-btn-bulk-organize', function() {
            const $wpBulkBtn = $('.select-mode-toggle-button, .button.media-button.select-mode-toggle-button');

            if ($('body').hasClass('mode-select') || $('body').hasClass('kookoawpf-bulk-active') || $('.media-toolbar-mode-select').is(':visible')) {
                $('#kookoawpf-grid-move-wrapper').remove();
                $('body').removeClass('kookoawpf-bulk-active');
                if ($wpBulkBtn.length) {
                    const $cancelBtn = $(
                        '.media-toolbar-mode-select .media-button-secondary, ' +
                        '.deselect-all-button, ' +
                        '.media-toolbar-mode-select .button.media-button:contains("Cancel")'
                    );
                    ($cancelBtn.length ? $cancelBtn.first() : $wpBulkBtn).trigger('click');
                }
                return;
            }

            if ($wpBulkBtn.length) $wpBulkBtn.trigger('click');
            setTimeout(function() { self._injectGridMoveDropdown(); }, 300);
        });

        const observer = new MutationObserver(function() {
            if ($('body').hasClass('mode-select') || $('body').hasClass('kookoawpf-bulk-active') || $('.media-toolbar-mode-select').is(':visible')) {
                self._injectGridMoveDropdown();
                // Re-hide WP native buttons (they can reappear after DOM mutations)
                if ($('body').hasClass('kookoawpf-bulk-active')) {
                    self._hideWpBulkButtons();
                }
            } else {
                $('#kookoawpf-grid-move-wrapper').remove();
                $('body').removeClass('kookoawpf-bulk-active');
                self._showWpBulkButtons();
            }
        });
        observer.observe(document.body, { attributes: true, attributeFilter: ['class'], subtree: false });
    };

    FO._injectGridToolbarElements = function($toolbar) {
        const self = this;
        const currentFolder = self.currentFolder;

        const $filter = $('<select id="kookoawpf-grid-filter" class="attachment-filters kookoawpf-grid-filter"></select>');
        // Build options through .text() — localized strings should never be
        // concatenated into HTML even when we control the surrounding markup.
        $filter.append($('<option>').val('all').text(self.data.strings.allItems));
        $filter.append($('<option>').val('uncategorized').text('(' + self.data.strings.uncategorized + ')'));

        self.populateFolderSelect($filter, currentFolder !== 'all' ? currentFolder : null);
        $filter.on('change', function() { self.goToFolder($(this).val()); });

        // Localized text via .text() so the i18n string never reaches HTML parser.
        const $bulkBtn = $('<button type="button" id="kookoawpf-btn-bulk-organize" class="button media-button kookoawpf-btn-bulk-organize"></button>')
            .text(self.data.strings.bulkOrganize);

        // Hide WP's native "Bulk select" button — we replace it entirely
        const $wpBulkSelect = $toolbar.find('.select-mode-toggle-button');
        if ($wpBulkSelect.length) {
            $wpBulkSelect.before($filter);
            $wpBulkSelect.after($bulkBtn);
            $wpBulkSelect.hide();
        } else {
            $toolbar.append($filter).append($bulkBtn);
        }
    };

    FO._injectGridMoveDropdown = function(retries) {
        const self = this;
        if (typeof retries === 'undefined') retries = 0;
        if ($('#kookoawpf-grid-move-wrapper').length) return;

        let $toolbar = $('.media-toolbar-mode-select .media-toolbar-secondary');
        if (!$toolbar.length) $toolbar = $('.media-toolbar .media-toolbar-secondary');
        if (!$toolbar.length) $toolbar = $('.media-toolbar-mode-select');
        if (!$toolbar.length) {
            // Toolbar not ready yet — retry up to 10 times (2 seconds)
            if (retries < 10) {
                setTimeout(function() { self._injectGridMoveDropdown(retries + 1); }, 200);
            }
            return;
        }

        // Build the bulk toolbar piece-by-piece so every localized string
        // is set via .text() / createTextNode and never interpolated into
        // an HTML literal. Static structural markup (dashicons span, ids,
        // classes) stays the same.
        const $wrapper      = $('<span id="kookoawpf-grid-move-wrapper" class="kookoawpf-grid-move-wrapper"></span>');
        const $btnSelectAll = $('<button type="button" id="kookoawpf-btn-select-all" class="button kookoawpf-btn-bulk-action kookoawpf-btn-select-all"></button>')
            .text(self.data.strings.selectAll);
        let $btnDelete    = $('<button type="button" id="kookoawpf-btn-delete-selected" class="button kookoawpf-btn-bulk-action kookoawpf-btn-delete"></button>')
            .append('<span class="dashicons dashicons-trash" style="vertical-align:middle;margin-top:-2px;margin-right:2px;"></span>')
            .append(document.createTextNode(self.data.strings.deletePermanently));
        const $moveLabel    = $('<label class="kookoawpf-grid-move-label"></label>')
            .text(self.data.strings.moveSelected);
        const $moveSelect   = $('<select id="kookoawpf-grid-move-select" class="kookoawpf-grid-move-select"></select>')
            .append($('<option>').val('').text(self.data.strings.selectFolder))
            .append($('<option>').val('uncategorized').text('(' + self.data.strings.uncategorized + ')'));
        const $btnCancel    = $('<button type="button" id="kookoawpf-btn-cancel-bulk" class="button kookoawpf-btn-bulk-action kookoawpf-btn-cancel"></button>')
            .text(self.data.strings.cancelBulk);
        $wrapper
            .append($btnSelectAll).append(document.createTextNode(' '))
            .append($btnDelete).append(document.createTextNode(' '))
            .append($moveLabel).append(document.createTextNode(' '))
            .append($moveSelect).append(document.createTextNode(' '))
            .append($btnCancel);
        // Extension point: companion plugins can append action buttons to
        // the bulk toolbar before mount.
        $(document).trigger('kookoawpf:bulk-toolbar-build', [$wrapper, self.currentFolder]);

        // On mobile: place toolbar OUTSIDE WP's .media-toolbar-mode-select
        // (that container blocks touch scrolling on mobile browsers).
        // On desktop: keep inside toolbar, scroll fixed via CSS max-height.
        if ($('body').hasClass('kookoawpf-mobile')) {
            const $attachBrowser = $('.attachments-browser');
            if ($attachBrowser.length) {
                $attachBrowser.prepend($wrapper);
            } else {
                $toolbar.append($wrapper);
            }
        } else {
            $toolbar.append($wrapper);
        }
        $('body').addClass('kookoawpf-bulk-active');

        // Remove uncategorized option if we're currently in uncategorized
        if (self.currentFolder === 'uncategorized') {
            $('#kookoawpf-grid-move-select option[value="uncategorized"]').remove();
        }

        self.populateFolderSelect($('#kookoawpf-grid-move-select'), null, self.currentFolder);

        // Hide ALL WP native buttons (Cancel on left, Delete Permanently, etc.)
        self._hideWpBulkButtons();

        // Disable Delete + Download until items are selected.
        $btnDelete = $('#kookoawpf-btn-delete-selected');
        const $btnDownload = $('#kookoawpf-btn-download-selected');
        const $btnMoveSelect = $('#kookoawpf-grid-move-select');

        function updateBulkItemButtons() {
            const count = self._getGridSelectedIds().length;
            const $btnSA = $('#kookoawpf-btn-select-all');
            const hasItems = $('.attachments-browser .attachment').length > 0;
            // Check if any folders exist to move to
            const hasFolders = self.tree && self.tree.jstree(true) && self.tree.jstree(true).get_json('#', { flat: true }).length > 0;
            if (count > 0) {
                $btnDelete.prop('disabled', false);
                $btnDownload.prop('disabled', false);
                if (hasFolders) {
                    $btnMoveSelect.removeClass('kookoawpf-select-disabled').prop('disabled', false);
                } else {
                    $btnMoveSelect.addClass('kookoawpf-select-disabled').prop('disabled', false);
                }
            } else {
                $btnDelete.prop('disabled', true);
                $btnDownload.prop('disabled', true);
                $btnMoveSelect.addClass('kookoawpf-select-disabled').prop('disabled', false);
                if ($btnSA.length) $btnSA.text(self.data.strings.selectAll);
            }
            // Disable Select All if no items in the grid
            if ($btnSA.length) $btnSA.prop('disabled', !hasItems);
        }
        self._updateBulkItemButtons = updateBulkItemButtons;

        // Initial state: disabled
        updateBulkItemButtons();

        // Watch WP Backbone selection model
        if (typeof wp !== 'undefined' && wp.media && wp.media.frame) {
            try {
                const sel = wp.media.frame.state().get('selection');
                if (sel) {
                    sel.on('add remove reset', function() { setTimeout(updateBulkItemButtons, 50); });
                }
            } catch (e) {}
        }

        // Fallback: also watch DOM clicks on attachments
        $(document).off('click.kwpfBulkWatch').on('click.kwpfBulkWatch', '.attachments-browser .attachment', function() {
            setTimeout(updateBulkItemButtons, 100);
        });

        // Select All / Deselect All toggle
        $(document).off('click.kwpfSelectAll').on('click.kwpfSelectAll', '#kookoawpf-btn-select-all', function() {
            const $btn = $(this);
            const $attachments = $('.attachments-browser .attachment');
            const $selectedAtts = $attachments.filter('.selected, .details');
            // Compute wantDeselect from actual DOM state, not button text
            // (button text can drift after WP state transitions, e.g. after
            // opening and closing an attachment modal).
            const wantDeselect = ($attachments.length > 0 && $selectedAtts.length >= $attachments.length);

            let backboneDone = false;
            if (typeof wp !== 'undefined' && wp.media && wp.media.frame) {
                try {
                    const state = wp.media.frame.state();
                    const library = state ? state.get('library') : null;
                    const selection = state ? state.get('selection') : null;

                    if (library && selection) {
                        if (wantDeselect) {
                            selection.reset([]);
                            backboneDone = true;
                        } else if (library.length > 0) {
                            library.each(function(model) { selection.add(model); });
                            backboneDone = true;
                        }
                    }
                } catch (e) { /* fall through */ }
            }

            // DOM fallback: after WP state transitions (attachment modal open/close,
            // WebP Convert, Replace Media) the Backbone library/selection may be
            // disconnected from the visible grid. Re-trigger selection via native
            // clicks on the attachment nodes — WP's own handler toggles state.
            if (!backboneDone || $('.attachments-browser .attachment.selected, .attachments-browser .attachment.details').length === 0 && !wantDeselect) {
                if (wantDeselect) {
                    $attachments.filter('.selected, .details').each(function() { this.click(); });
                } else {
                    $attachments.not('.selected, .details').each(function() { this.click(); });
                }
            }

            // Update button text from final DOM state (truth source).
            setTimeout(function() {
                const $atts2 = $('.attachments-browser .attachment');
                const $sel2 = $atts2.filter('.selected, .details');
                if ($atts2.length > 0 && $sel2.length >= $atts2.length) {
                    $btn.text(self.data.strings.deselectAll);
                } else {
                    $btn.text(self.data.strings.selectAll);
                }
                updateBulkItemButtons();
            }, 150);
        });

        // Delete permanently
        $(document).off('click.kwpfDeleteSel').on('click.kwpfDeleteSel', '#kookoawpf-btn-delete-selected', function() {
            const ids = self._getGridSelectedIds();
            if (!ids.length) {
                self.showNotice(self.data.strings.noItemsSelected, 'warning');
                return;
            }

            if (!confirm(self.data.strings.confirmDeleteItems)) return;

            let deleted = 0;
            const total = ids.length;

            if (typeof wp !== 'undefined' && wp.media && wp.media.attachment) {
                ids.forEach(function(id) {
                    const att = wp.media.attachment(id);
                    if (att) {
                        att.destroy({ wait: true }).done(function() {
                            deleted++;
                            if (deleted >= total) {
                                self.showNotice(self.data.strings.itemsDeleted, 'success');
                                self.refreshTree(function() {
                                    self._refreshContentView();
                                });
                            }
                        }).fail(function() {
                            deleted++;
                            if (deleted >= total) {
                                self.showNotice(self.data.strings.itemsDeleted, 'success');
                                self.refreshTree(function() {
                                    self._refreshContentView();
                                });
                            }
                        });
                    } else {
                        deleted++;
                    }
                });
            } else {
                ids.forEach(function(id) {
                    $.post(self.data.ajaxUrl, {
                        action: 'delete-post',
                        id: id,
                        _wpnonce: self.data.deleteNonce || ''
                    }, function() {
                        deleted++;
                        if (deleted >= total) {
                            self.showNotice(self.data.strings.itemsDeleted, 'success');
                            self.refreshTree(function() {
                                self._refreshContentView();
                            });
                        }
                    });
                });
            }
        });

        // ESC = Cancel. Routing through the existing Cancel button click
        // keeps a single source of truth for the teardown sequence
        // (remove move wrapper, restore WP buttons, clear selection,
        // exit mode-select). Skip when the user is typing somewhere so
        // ESC inside an input/textarea/select/contenteditable still does
        // its normal thing (close a native autocomplete, clear a field).
        //
        // Capture phase + stopImmediatePropagation so WP's own grid
        // mode-select handler doesn't process ESC first and leave our
        // toolbar half-torn-down (mode-select gone, kookoawpf wrapper
        // still painted). Same pattern modals.js uses for the rest of
        // the keyboard shortcuts (CLAUDE.md sec. 7).
        if (!self._kwpfEscBulkAttached) {
            document.addEventListener('keydown', function(e) {
                if (e.keyCode !== 27) return;
                if (!document.body.classList.contains('kookoawpf-bulk-active')) return;
                const t = e.target;
                if (t) {
                    const tag = (t.tagName || '').toLowerCase();
                    if (tag === 'input' || tag === 'textarea' || tag === 'select') return;
                    if (t.isContentEditable) return;
                }
                e.preventDefault();
                e.stopImmediatePropagation();
                const btn = document.getElementById('kookoawpf-btn-cancel-bulk');
                if (btn) {
                    btn.click();
                }
            }, true);
            self._kwpfEscBulkAttached = true;
        }

        // Cancel bulk mode
        $(document).off('click.kwpfCancelBulk').on('click.kwpfCancelBulk', '#kookoawpf-btn-cancel-bulk', function() {
            $('#kookoawpf-grid-move-wrapper').remove();
            $('body').removeClass('kookoawpf-bulk-active');

            self._showWpBulkButtons();

            let exited = false;
            const cancelSelectors = [
                '.media-toolbar-mode-select .media-button-secondary',
                '.media-toolbar-mode-select .button:contains("Cancel")',
                '.deselect-all-button',
                '.media-button.select-mode-toggle-button'
            ];
            for (let i = 0; i < cancelSelectors.length; i++) {
                const $btn = $(cancelSelectors[i]);
                if ($btn.length) {
                    $btn.first().show().trigger('click');
                    exited = true;
                    break;
                }
            }

            if (!exited) {
                $('body').removeClass('mode-select');
            }

            if (typeof wp !== 'undefined' && wp.media && wp.media.frame) {
                try {
                    const state = wp.media.frame.state();
                    if (state && state.get('selection')) {
                        state.get('selection').reset([]);
                    }
                } catch (e) {}
            }
        });

        $(document).off('change.kwpfBulkMove').on('change.kwpfBulkMove', '#kookoawpf-grid-move-select', function() {
            const folderId = $(this).val();
            if (!folderId) return;

            const ids = self._getGridSelectedIds();
            if (!ids.length) {
                self.showNotice(self.data.strings.noItemsSelected, 'warning');
                $(this).val('');
                return;
            }

            if (folderId === 'uncategorized') {
                const fromId = self.getFromFolderId();
                if (fromId) {
                    self.ajax('kookoawpf_unassign_items', {
                        post_ids: ids,
                        folder_id: fromId,
                        post_type: self.data.postType
                    }, function(r) {
                        if (r.success) {
                            self.showNotice(self.data.strings.itemsMoved, 'success');
                            self.refreshTree(function() {
                                self._countsAreFresh = true;
                                self.goToFolder(self.currentFolder);
                            });
                            // dropdown refresh handled by refreshTree debounce
                        } else {
                            self.showNotice((r.data && r.data.message) || self.data.strings.errorGeneric, 'error');
                        }
                    });
                } else {
                    self.showNotice(self.data.strings.removeFromFolder, 'warning');
                }
                $(this).val('');
                return;
            }

            const $dropdown = $(this);
            self.ajax('kookoawpf_move_items', {
                post_ids: ids,
                from_folder_id: self.getFromFolderId(),
                to_folder_id: parseInt(folderId, 10),
                post_type: self.data.postType
            }, function(r) {
                if (r.success) {
                    self.showNotice(self.data.strings.itemsMoved, 'success');
                    self.refreshTree(function() {
                        self._repopulateGridMoveDropdown();
                        if (self._repopulateGridFilter) self._repopulateGridFilter();
                        self._countsAreFresh = true;
                        self.goToFolder(self.currentFolder);
                    });
                } else {
                    self.showNotice((r.data && r.data.message) || self.data.strings.errorGeneric, 'error');
                }
                $dropdown.val('');
            });
        });
    };

    /**
     * Hide ALL WP native bulk-mode buttons.
     */
    FO._hideWpBulkButtons = function() {
        setTimeout(function() {
            $('.media-toolbar .media-button, .media-toolbar-mode-select .media-button').each(function() {
                const $btn = $(this);
                if ($btn.hasClass('kookoawpf-btn-bulk-organize') ||
                    $btn.hasClass('kookoawpf-btn-bulk-action') ||
                    $btn.closest('#kookoawpf-grid-move-wrapper').length) {
                    return;
                }
                $btn.hide();
            });
            $('.select-mode-toggle-button').hide();
        }, 50);
    };

    /**
     * Restore WP native buttons after exiting bulk mode.
     */
    FO._showWpBulkButtons = function() {
        $('.media-toolbar .media-button').show();
        $('.select-mode-toggle-button').hide();
    };

    /**
     * Rebuild the grid filter dropdown (#kookoawpf-grid-filter) from the jsTree state.
     * Reads directly from the tree (already updated optimistically) instead of AJAX,
     * so the dropdown is always in sync even before server round-trips complete.
     */
    FO._repopulateGridFilter = function() {
        const self = this;

        // Blink underscore cursor on any folder structure change
        if (typeof self._blinkUnderscore === 'function') self._blinkUnderscore();

        const $filter = $('#kookoawpf-grid-filter');
        if (!$filter.length) return;

        const currentVal = $filter.val();
        $filter.find('option').slice(2).remove(); // Keep "All Items" + "Uncategorized"

        const inst = self.tree ? self.tree.jstree(true) : null;
        if (!inst) return;

        const nodes = inst.get_json('#', { flat: true });
        $.each(nodes, function(i, n) {
            const data = n.data || {};
            // Smart folders are virtual queries — exclude from selectors.
            if (data.is_dynamic) return;
            let depth = 0;
            let pid = n.parent;
            while (pid && pid !== '#') {
                depth++;
                const pn = inst.get_node(pid);
                pid = pn ? pn.parent : '#';
            }
            const name = data.original_text || inst.get_text(n.id).replace(/<[^>]+>/g, '').trim();
            let prefix = '';
            for (let d = 0; d < depth; d++) prefix += '\u2014\u00A0';
            let label = prefix + name;
            if (data.count !== undefined) label += ' (' + data.count + ')';
            if (data.pinned) label += ' \u2B50';
            if (data.locked) label += ' \uD83D\uDD12';
            const folderId = data.folder_id || n.id.replace('folder_', '');
            $filter.append($('<option></option>').val(folderId).text(label));
        });

        $filter.val(currentVal);
    };

    /**
     * Rebuild the grid "Move to" dropdown with fresh folder counts.
     */
    FO._repopulateGridMoveDropdown = function() {
        const self = this;
        const $select = $('#kookoawpf-grid-move-select');
        if (!$select.length) return;

        // Anti-reentrant: skip if already populating
        if (self._repopulatingMove) return;
        self._repopulatingMove = true;

        // Keep only the first placeholder option, remove everything else
        const placeholder = $select.find('option').first().detach();
        $select.empty().append(placeholder);

        if (self.currentFolder !== 'uncategorized') {
            $select.append($('<option>').val('uncategorized').text('(' + self.data.strings.uncategorized + ')'));
        }

        self.populateFolderSelect($select, null, self.currentFolder);
        $select.val('');

        // Release flag after async populate completes
        setTimeout(function() { self._repopulatingMove = false; }, 500);
    };

    FO._getGridSelectedIds = function() {
        const ids = [];

        if (typeof wp !== 'undefined' && wp.media && wp.media.frame) {
            try {
                const state = wp.media.frame.state();
                if (state && state.get('selection')) {
                    state.get('selection').each(function(model) { ids.push(model.get('id')); });
                }
                if (ids.length) return ids;
            } catch (e) { /* fall through */ }
        }

        $('.attachments-browser .attachment[aria-checked="true"], ' +
          '.attachments-browser .attachment.selected, ' +
          '.attachments-browser .attachment.details').each(function() {
            const id = $(this).data('id');
            if (id) ids.push(parseInt(id, 10));
        });

        if (!ids.length) {
            $('.attachments-browser .attachment :checkbox:checked').each(function() {
                const id = $(this).closest('.attachment').data('id');
                if (id) ids.push(parseInt(id, 10));
            });
        }

        return ids;
    };

    /**
     * Collect all IDs that should be moved during a drag-and-drop.
     */
    FO._getDragIds = function($dragged, mode) {
        const self = this;

        if (mode === 'grid') {
            const draggedId = parseInt($dragged.data('id'), 10) || 0;
            const selectedIds = self._getGridSelectedIds();

            if (draggedId && selectedIds.length > 1 && selectedIds.indexOf(draggedId) !== -1) {
                return selectedIds;
            }
            return draggedId ? [draggedId] : [];
        }

        // List mode
        const draggedId = self.getItemId($dragged);
        const $cb = $dragged.find('.check-column input[type="checkbox"]');
        if ($cb.length && $cb.is(':checked')) {
            const checkedIds = self._getListSelectedIds();
            if (checkedIds.length > 1 && checkedIds.indexOf(draggedId) !== -1) {
                return checkedIds;
            }
        }
        return draggedId ? [draggedId] : [];
    };

    /**
     * Check if a jstree node has any locked descendant.
     */
    FO._hasLockedDescendant = function(node, inst) {
        inst = inst || (this.tree ? this.tree.jstree(true) : null);
        if (!inst || !node || !node.children_d) return false;
        for (let i = 0; i < node.children_d.length; i++) {
            const child = inst.get_node(node.children_d[i]);
            if (child && child.data && child.data.locked) return true;
        }
        return false;
    };

    FO._initListBulkOrganize = function() {
        const self = this;
        // No bulk organize in trash view
        if (window.location.search.indexOf('post_status=trash') !== -1 || $('.subsubsub .trash.current').length) return;
        const $bulkActions = $('.bulkactions');
        if (!$bulkActions.length) return;

        const $btn = $('<button type="button" id="kookoawpf-btn-bulk-organize-list" class="button kookoawpf-btn-bulk-organize"></button>')
            .prop('disabled', true)
            .text(self.data.strings.bulkOrganize);
        $bulkActions.first().after($btn);

        function updateBulkBtnState() {
            const hasChecked = $('.wp-list-table tbody .check-column input[type="checkbox"]:checked').length > 0;
            const inst = FO.tree && FO.tree.jstree(true);
            const folderCount = inst ? inst.get_json('#', { flat: true }).length : 0;
            $btn.prop('disabled', !hasChecked || folderCount === 0);
        }
        self._updateListBulkBtnState = updateBulkBtnState;
        $(document).on('change', '.wp-list-table .check-column input[type="checkbox"]', updateBulkBtnState);
        $(document).on('change', '#cb-select-all-1, #cb-select-all-2', updateBulkBtnState);

        $(document).on('click', '#kookoawpf-btn-bulk-organize-list', function() { self._openBulkModal(); });
        $(document).on('click', '#kookoawpf-bulk-apply', function() { self._applyBulkOrganize(); });
        $(document).on('change', '#kookoawpf-bulk-folder-select', function() {
            $('#kookoawpf-bulk-apply').prop('disabled', !$(this).val());
        });
    };

    FO._openBulkModal = function() {
        const self = this;
        const ids = self._getListSelectedIds();
        if (!ids.length) {
            self.showNotice(self.data.strings.noItemsSelected, 'warning');
            return;
        }

        $('#kookoawpf-bulk-count').text(ids.length + ' item(s) selected');
        const $select = $('#kookoawpf-bulk-folder-select');
        $select.find('option:gt(0)').remove();

        if (self.currentFolder !== 'uncategorized') {
            $select.append($('<option>').val('uncategorized').text('(' + self.data.strings.uncategorized + ')'));
        }

        self.populateFolderSelect($select, null, self.currentFolder);
        $('#kookoawpf-bulk-apply').prop('disabled', true);
        $('#kookoawpf-modal-bulk').show();
    };

    FO._getListSelectedIds = function() {
        const ids = [];
        $('.wp-list-table tbody .check-column input[type="checkbox"]:checked').each(function() {
            const id = parseInt($(this).val(), 10);
            if (id) ids.push(id);
        });
        return ids;
    };

    FO._applyBulkOrganize = function() {
        const self = this;
        const ids = self._getListSelectedIds();
        const folderId = $('#kookoawpf-bulk-folder-select').val();
        if (!ids.length) { self.showNotice(self.data.strings.noItemsSelected, 'warning'); return; }
        if (!folderId) return;

        $('#kookoawpf-modal-bulk').hide();

        function uncheckListItems() {
            $('.wp-list-table .check-column input[type="checkbox"]:checked').prop('checked', false);
            $('#cb-select-all-1, #cb-select-all-2').prop('checked', false);
            $('#kookoawpf-btn-bulk-organize-list').prop('disabled', true);
        }

        if (folderId === 'uncategorized') {
            if (self.currentFolder !== 'all' && self.currentFolder !== 'uncategorized') {
                self.ajax('kookoawpf_unassign_items', {
                    post_ids: ids, folder_id: self.currentFolder, post_type: self.data.postType
                }, function(r) {
                    if (r.success) {
                        uncheckListItems();
                        self.showNotice(self.data.strings.itemsMoved, 'success');
                        self.refreshTree(function() {
                            self._countsAreFresh = true;
                            self.goToFolder(self.currentFolder);
                        });
                    }
                });
            } else {
                self.showNotice(self.data.strings.removeFromFolder, 'warning');
            }
        } else {
            self.ajax('kookoawpf_move_items', {
                post_ids: ids,
                from_folder_id: self.getFromFolderId(),
                to_folder_id: parseInt(folderId, 10),
                post_type: self.data.postType
            }, function(r) {
                if (r.success) {
                    uncheckListItems();
                    self.showNotice(self.data.strings.itemsMoved, 'success');
                    self.refreshTree(function() {
                        self._countsAreFresh = true;
                        self.goToFolder(self.currentFolder);
                    });
                } else {
                    self.showNotice((r.data && r.data.message) || self.data.strings.errorGeneric, 'error');
                }
            });
        }
    };

})(jQuery);
