/**
 * KooKoa Folders — Smart Folders (Dynamic)
 *
 * Extracted from admin.js (Blocco 5).
 * Adds initDynamic, _openDynamicModal, _addRuleRow, _renderRuleValue,
 * _refreshRuleValueOptions, _updateDynamicSaveState, _collectRules,
 * _saveDynamic, initDynamicTooltip methods to FO.
 *
 * @since 4.5.74
 */
(function($) {
    'use strict';

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

    // ========================
    // Dynamic (Smart) Folders
    // ========================

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

        // New Smart Folder button
        $(document).on('click', '#kookoawpf-btn-new-dynamic', function() {
            self._openDynamicModal('create', 0, '');
        });

        // Add Rule button
        $(document).on('click', '#kookoawpf-dynamic-add-rule', function() {
            const currentRules = $('#kookoawpf-dynamic-rules-list .kookoawpf-rule-row').length;
            if (currentRules >= 6) {
                self.showNotice('Maximum 6 rules per smart folder.', 'error');
                return;
            }
            self._addRuleRow();
            // Disable button if at limit
            if (currentRules + 1 >= 6) {
                $(this).prop('disabled', true);
            }
        });

        // Rule field change → show value input
        $(document).on('change', '.kookoawpf-rule-field-select', function() {
            const $row = $(this).closest('.kookoawpf-rule-row');
            const field = $(this).val();
            self._renderRuleValue($row, field, '');
        });

        // Remove rule
        $(document).on('click', '.kookoawpf-rule-remove', function() {
            $(this).closest('.kookoawpf-rule-row').remove();
            // Re-enable Add Rule button if under limit
            const currentRules = $('#kookoawpf-dynamic-rules-list .kookoawpf-rule-row').length;
            if (currentRules < 6) {
                $('#kookoawpf-dynamic-add-rule').prop('disabled', false);
            }
            // Refresh available options in remaining rows
            self._refreshRuleValueOptions();
            self._updateDynamicSaveState();
        });

        // Date range change → show custom fields
        $(document).on('change', '.kookoawpf-rule-value-select[data-rule-field="date_range"]', function() {
            const $row = $(this).closest('.kookoawpf-rule-row');
            if ($(this).val() === 'custom') {
                $row.find('.kookoawpf-date-custom-row').show();
            } else {
                $row.find('.kookoawpf-date-custom-row').hide();
            }
        });

        // Save dynamic folder
        $(document).on('click', '#kookoawpf-dynamic-save', function() {
            if ($(this).prop('disabled')) return;
            self._saveDynamic();
        });

        // Enable/disable Save based on name + at least one complete rule
        $(document).on('input', '#kookoawpf-dynamic-name', function() {
            self._updateDynamicSaveState();
        });
        $(document).on('change', '.kookoawpf-rule-field-select, .kookoawpf-rule-value-select, .kookoawpf-rule-value-input, .kookoawpf-rule-value-toggle', function() {
            self._updateDynamicSaveState();
        });
        $(document).on('input', '.kookoawpf-rule-value-input', function() {
            self._updateDynamicSaveState();
        });
    };

    FO._openDynamicModal = function(mode, folderId, name) {
        const self = this;
        $('#kookoawpf-dynamic-mode').val(mode);
        $('#kookoawpf-dynamic-folder-id').val(folderId);
        $('#kookoawpf-dynamic-name').val(name);
        $('#kookoawpf-dynamic-rules-list').empty();

        if (mode === 'create') {
            $('#kookoawpf-dynamic-modal-title').text(self.data.strings.newDynamic);
            self._addRuleRow(); // Start with one empty row
            $('#kookoawpf-dynamic-save').prop('disabled', true);
            $('#kookoawpf-modal-dynamic').show();
            setTimeout(function() { $('#kookoawpf-dynamic-name').focus(); }, 100);
        } else {
            // Edit mode: load existing rules
            $('#kookoawpf-dynamic-modal-title').text(self.data.strings.editRulesTitle || self.data.strings.editRules);
            $('#kookoawpf-dynamic-save').prop('disabled', true);
            self.ajax('kookoawpf_get_dynamic_rules', { folder_id: folderId }, function(r) {
                if (r.success && r.data.rules) {
                    const rules = r.data.rules;
                    if (!rules.length) {
                        self._addRuleRow();
                    } else {
                        for (let i = 0; i < rules.length; i++) {
                            self._addRuleRow(rules[i].field, rules[i].value);
                        }
                    }
                } else {
                    self._addRuleRow();
                }
                self._updateDynamicSaveState();
                $('#kookoawpf-modal-dynamic').show();
            });
        }
    };

    FO._addRuleRow = function(fieldVal, valueVal) {
        const self = this;
        const fields = self.data.dynamicFields || {};
        const $row = $('<div class="kookoawpf-rule-row"></div>');

        // Field select
        const $fieldSelect = $('<select class="kookoawpf-rule-field-select"></select>');
        $fieldSelect.append($('<option>').val('').text(self.data.strings.selectField));
        $.each(fields, function(key, meta) {
            $fieldSelect.append($('<option>').val(key).text(meta.label));
        });
        if (fieldVal) $fieldSelect.val(fieldVal);

        // Value placeholder
        const $valueWrap = $('<span class="kookoawpf-rule-value-wrap"></span>');

        // Remove button
        const $remove = $('<button>', {
            type: 'button',
            'class': 'kookoawpf-rule-remove',
            title: self.data.strings.removeRule
        }).text('×');

        $row.append($fieldSelect).append($valueWrap).append($remove);
        $('#kookoawpf-dynamic-rules-list').append($row);

        // Convert to custom dropdown
        if (typeof KookoawpfCustomSelect !== 'undefined') {
            KookoawpfCustomSelect.replace($fieldSelect);
        }

        if (fieldVal) {
            self._renderRuleValue($row, fieldVal, valueVal || '');
        }
    };

    FO._renderRuleValue = function($row, field, currentVal) {
        const self = this;
        const fields = self.data.dynamicFields || {};
        const options = self.data.dynamicOptions || {};
        const $wrap = $row.find('.kookoawpf-rule-value-wrap');
        $wrap.empty();

        if (!field || !fields[field]) return;

        const meta = fields[field];

        if (meta.type === 'select') {
            const opts = options[field] || {};
            // Collect values already used by other rows for this field
            const usedValues = {};
            $('#kookoawpf-dynamic-rules-list .kookoawpf-rule-row').each(function() {
                if (this === $row[0]) return; // skip current row
                const rowField = $(this).find('.kookoawpf-rule-field-select').val();
                if (rowField === field) {
                    const rowVal = $(this).find('.kookoawpf-rule-value-select').val();
                    if (rowVal) usedValues[rowVal] = true;
                }
            });
            const $sel = $('<select class="kookoawpf-rule-value-select">').attr('data-rule-field', field);
            $sel.append($('<option>').val('').text('—'));
            $.each(opts, function(k, v) {
                if (!usedValues[k] || k === currentVal) {
                    $sel.append($('<option>').val(k).text(v));
                }
            });
            if (currentVal) $sel.val(currentVal);
            $wrap.append($sel);

            // When value changes, refresh other rows with same field to update available options
            $sel.on('change', function() {
                self._refreshRuleValueOptions();
            });

            // Convert to custom dropdown
            if (typeof KookoawpfCustomSelect !== 'undefined') {
                KookoawpfCustomSelect.replace($sel);
            }

            // Date range custom fields
            if (field === 'date_range') {
                const showCustom = (currentVal === 'custom');
                const $rangeRow = $(
                    '<span class="kookoawpf-date-custom-row" style="' + (showCustom ? '' : 'display:none;') + '">' +
                    '<input type="date" class="kookoawpf-rule-date-after" value="" /> — ' +
                    '<input type="date" class="kookoawpf-rule-date-before" value="" />' +
                    '</span>'
                );
                $rangeRow.find('.kookoawpf-rule-date-after').attr('placeholder', self.data.strings.dateFrom);
                $rangeRow.find('.kookoawpf-rule-date-before').attr('placeholder', self.data.strings.dateTo);
                $wrap.append($rangeRow);
            }
        } else if (meta.type === 'number') {
            const $inp = $('<input type="number" class="kookoawpf-rule-value-input" min="0" step="100" />');
            if (currentVal) $inp.val(currentVal);
            const $spinner = $('<div class="kookoawpf-number-spinner"></div>');
            const $btns = $('<div class="kookoawpf-number-spinner-btns">' +
                '<button type="button" class="kookoawpf-number-spinner-btn" data-dir="up">▲</button>' +
                '<button type="button" class="kookoawpf-number-spinner-btn" data-dir="down">▼</button>' +
                '</div>');
            $spinner.append($inp).append($btns);
            $btns.on('click', '.kookoawpf-number-spinner-btn', function(e) {
                e.preventDefault();
                const val = parseInt($inp.val()) || 0;
                const step = parseInt($inp.attr('step')) || 100;
                if ($(this).data('dir') === 'up') {
                    $inp.val(val + step).trigger('change');
                } else {
                    $inp.val(Math.max(0, val - step)).trigger('change');
                }
            });
            $wrap.append($spinner);
        } else if (meta.type === 'toggle') {
            // Build the toggle through DOM API so meta.label (a localized
            // string from FO.data.strings via the rule registry) never
            // travels through an HTML literal.
            const $chk = $('<label class="kookoawpf-rule-toggle"></label>');
            const $chkInput = $('<input type="checkbox" class="kookoawpf-rule-value-toggle" value="1" />');
            if (currentVal) $chkInput.prop('checked', true);
            $chk.append($chkInput).append(document.createTextNode(' ' + (meta.label || '')));
            $wrap.append($chk);
        } else {
            // text
            const $txt = $('<input type="text" class="kookoawpf-rule-value-input" />');
            if (currentVal) $txt.val(currentVal);
            $wrap.append($txt);
        }
    };

    /**
     * Refresh available options in all value selects to hide values already used
     * by sibling rows with the same field. Preserves each row's current selection.
     */
    FO._refreshRuleValueOptions = function() {
        const self = this;
        const options = self.data.dynamicOptions || {};
        const $rows = $('#kookoawpf-dynamic-rules-list .kookoawpf-rule-row');

        // 1) Collect all used field:value pairs
        const usedByField = {};
        $rows.each(function() {
            const f = $(this).find('.kookoawpf-rule-field-select').val();
            const v = $(this).find('.kookoawpf-rule-value-select').val();
            if (f && v) {
                if (!usedByField[f]) usedByField[f] = {};
                usedByField[f][v] = true;
            }
        });

        // 2) For each row, rebuild options hiding values taken by OTHER rows
        $rows.each(function() {
            const $row = $(this);
            const field = $row.find('.kookoawpf-rule-field-select').val();
            const $sel = $row.find('.kookoawpf-rule-value-select');
            if (!field || !$sel.length || !options[field]) return;

            const currentVal = $sel.val();
            const opts = options[field];

            $sel.find('option').not(':first').remove();
            $.each(opts, function(k, v) {
                // Show if: not used by others, OR is this row's own current value
                if (k === currentVal || !usedByField[field] || !usedByField[field][k]) {
                    $sel.append($('<option>').val(k).text(v));
                }
            });
            $sel.val(currentVal);

            // Refresh custom dropdown
            if (typeof KookoawpfCustomSelect !== 'undefined') {
                KookoawpfCustomSelect.refresh($sel);
            }
        });
    };

    /**
     * Enable Save only when name has text AND at least one complete rule exists.
     */
    FO._updateDynamicSaveState = function() {
        const hasName = $('#kookoawpf-dynamic-name').val().trim().length > 0;
        let hasCompleteRule = false;
        $('#kookoawpf-dynamic-rules-list .kookoawpf-rule-row').each(function() {
            const field = $(this).find('.kookoawpf-rule-field-select').val();
            if (!field) return;
            const $valSel = $(this).find('.kookoawpf-rule-value-select');
            const $valInp = $(this).find('.kookoawpf-rule-value-input');
            const $valTog = $(this).find('.kookoawpf-rule-value-toggle');
            let value = '';
            if ($valSel.length) value = $valSel.val();
            else if ($valInp.length) value = $valInp.val().trim();
            else if ($valTog.length) value = $valTog.is(':checked') ? '1' : '';
            if (value) hasCompleteRule = true;
        });
        $('#kookoawpf-dynamic-save').prop('disabled', !(hasName && hasCompleteRule));
    };

    FO._collectRules = function() {
        const rules = [];
        const seen = {};
        $('#kookoawpf-dynamic-rules-list .kookoawpf-rule-row').each(function() {
            const $row = $(this);
            const field = $row.find('.kookoawpf-rule-field-select').val();
            if (!field) return;

            let value = '';
            const $valSel = $row.find('.kookoawpf-rule-value-select');
            const $valInp = $row.find('.kookoawpf-rule-value-input');
            const $valTog = $row.find('.kookoawpf-rule-value-toggle');

            if ($valSel.length) value = $valSel.val();
            else if ($valInp.length) value = $valInp.val().trim();
            else if ($valTog.length) value = $valTog.is(':checked') ? '1' : '';

            if (!value) return;

            // Skip duplicate field+value combinations
            const key = field + '::' + value;
            if (seen[key]) return;
            seen[key] = true;

            rules.push({ field: field, value: value });

            // If date_range=custom, also add date_after / date_before
            if (field === 'date_range' && value === 'custom') {
                const after = $row.find('.kookoawpf-rule-date-after').val();
                const before = $row.find('.kookoawpf-rule-date-before').val();
                if (after) rules.push({ field: 'date_after', value: after });
                if (before) rules.push({ field: 'date_before', value: before });
            }
        });
        return rules;
    };

    FO._saveDynamic = function() {
        const self = this;
        const mode = $('#kookoawpf-dynamic-mode').val();
        const name = $('#kookoawpf-dynamic-name').val().trim();
        const folderId = $('#kookoawpf-dynamic-folder-id').val();
        const rules = self._collectRules();

        if (mode === 'create' && !name) {
            $('#kookoawpf-dynamic-name').focus();
            return;
        }

        if (!rules.length) {
            self.showNotice(self.data.strings.noRules, 'warning');
            return;
        }

        if (mode === 'create') {
            self.ajax('kookoawpf_create_dynamic_folder', {
                name: name,
                post_type: self.data.postType,
                rules: rules
            }, function(r) {
                if (r.success) {
                    const newTermId = r.data && r.data.term_id ? r.data.term_id : null;
                    const newCount  = (r.data && typeof r.data.count !== 'undefined') ? (parseInt(r.data.count, 10) || 0) : 0;
                    const inst = self.tree ? self.tree.jstree(true) : null;
                    if (newTermId && inst) {
                        const defaultColor = (FO.data && FO.data.defaultColor) ? FO.data.defaultColor : '#39FF14';
                        const newNode = {
                            id: 'folder_' + newTermId,
                            text: '<span class="kookoawpf-folder-text">' + $('<span>').text(name).html() + '</span>',
                            data: { folder_id: newTermId, color: defaultColor, locked: false, pinned: false, count: newCount, icon: '⚡', is_dynamic: true, original_text: name },
                            icon: 'kookoawpf-folder-icon',
                            state: { opened: false }
                        };
                        self._toggleEmptyState(1);
                        inst.create_node('#', newNode, 'last', function(n) {
                            self.applyFolderIcons();
                            self._makeTreeNodesDroppable();
                            self._folderListCache = null;
                            if (self._repopulateGridFilter) self._repopulateGridFilter();
                            self._updateFreeLimits();
                            self._updateNoFolderState();
                            // In auto sort, snap the new smart folder to its sorted spot.
                            self._resortChildren('#', inst);
                            const $n = $('#' + n.id);
                            $n.addClass('kookoawpf-just-moved');
                            setTimeout(function() { $n.removeClass('kookoawpf-just-moved'); }, 500);
                        });
                    } else {
                        self.refreshTree();
                    }
                    self.showNotice(self.data.strings.smartFolderCreated, 'success');
                    $('#kookoawpf-modal-dynamic').hide();
                } else {
                    self.showNotice((r.data && r.data.message) || self.data.strings.errorGeneric, 'error');
                }
            });
        } else {
            // Update rules, and rename if changed
            self.ajax('kookoawpf_update_dynamic_rules', {
                folder_id: folderId,
                rules: rules
            }, function(r) {
                if (r.success) {
                    // Also rename if name was changed
                    if (name) {
                        self.ajax('kookoawpf_rename_folder', {
                            folder_id: folderId,
                            name: name,
                            post_type: self.data.postType
                        }, function() {
                            self.refreshTree(function() {
                                // Refresh content if viewing this smart folder
                                if (String(self.currentFolder) === String(folderId)) {
                                    self._refreshContentView();
                                }
                            });
                            self.showNotice(self.data.strings.rulesSaved, 'success');
                        });
                    } else {
                        self.refreshTree(function() {
                            if (String(self.currentFolder) === String(folderId)) {
                                self._refreshContentView();
                            }
                        });
                        self.showNotice(self.data.strings.rulesSaved, 'success');
                    }
                    $('#kookoawpf-modal-dynamic').hide();
                } else {
                    self.showNotice((r.data && r.data.message) || self.data.strings.errorGeneric, 'error');
                }
            });
        }
    };

    // ========================
    // Dynamic Folder Tooltip
    // ========================

    FO.initDynamicTooltip = function() {
        if (this._dynamicTooltipInit) return;
        this._dynamicTooltipInit = true;

        const self = this;
        let _tipAjax = null;
        let _tipCache = {};

        // Create tooltip element (avoid duplicates)
        let $tip = $('#kookoawpf-dynamic-tooltip');
        if (!$tip.length) {
            $tip = $('<div id="kookoawpf-dynamic-tooltip" class="kookoawpf-dynamic-tooltip"></div>').hide().appendTo('body');
        }

        let _tipTimer = null;

        // Click on "RULES" label → show tooltip immediately
        $(document).on('click', '.kookoawpf-rules-label', function(e) {
            e.stopPropagation();
            e.preventDefault();
            if ($tip.is(':visible')) return;

            const $label = $(this);
            const $node = $label.closest('.jstree-node');
            const $treeEl = $label.closest('.jstree');
            const inst = $treeEl.jstree(true);
            const node = inst ? inst.get_node($node.attr('id')) : null;
            if (!node || !node.data || !node.data.is_dynamic) return;

            const folderId = node.data.folder_id;
            clearTimeout(_tipTimer);

            if (_tipCache[folderId]) {
                _showTip($label, _tipCache[folderId], folderId);
                return;
            }

            if (_tipAjax && _tipAjax.abort) _tipAjax.abort();
            _tipAjax = self.ajax('kookoawpf_get_dynamic_rules', { folder_id: folderId }, function(r) {
                _tipAjax = null;
                if (r.success && r.data.rules && r.data.rules.length) {
                    const html = _buildTipHtml(r.data.rules);
                    _tipCache[folderId] = html;
                    _showTip($label, html, folderId);
                }
            });
        });

        // Hover on "RULES" label → show tooltip after delay
        $(document).on('mouseenter', '.kookoawpf-rules-label', function() {
            const $label = $(this);
            const $node = $label.closest('.jstree-node');
            const $treeEl = $label.closest('.jstree');
            const inst = $treeEl.jstree(true);
            const node = inst ? inst.get_node($node.attr('id')) : null;
            if (!node || !node.data || !node.data.is_dynamic) return;

            const folderId = node.data.folder_id;

            clearTimeout(_tipTimer);
            _tipTimer = setTimeout(function() {
                // Check cache first
                if (_tipCache[folderId]) {
                    _showTip($label, _tipCache[folderId], folderId);
                    return;
                }

                if (_tipAjax && _tipAjax.abort) _tipAjax.abort();
                _tipAjax = self.ajax('kookoawpf_get_dynamic_rules', { folder_id: folderId }, function(r) {
                    _tipAjax = null;
                    if (r.success && r.data.rules && r.data.rules.length) {
                        const html = _buildTipHtml(r.data.rules);
                        _tipCache[folderId] = html;
                        if ($label.is(':hover')) _showTip($label, html, folderId);
                    }
                });
            }, 500);
        });

        // Mouse leaves "RULES" label → hide after small delay (allows moving to tooltip)
        $(document).on('mouseleave', '.kookoawpf-rules-label', function() {
            clearTimeout(_tipTimer);
            setTimeout(function() {
                if (!$tip.is(':hover')) $tip.hide();
            }, 100);
        });

        // Mouse leaves the tooltip → hide it
        $(document).on('mouseleave', '#kookoawpf-dynamic-tooltip', function() {
            $tip.hide();
        });

        function _showTip($label, html, folderId) {
            // Move tooltip inside visible .media-modal for z-index stacking (like context menu)
            const $visibleModal = $('.media-modal:visible');
            if ($visibleModal.length && !$visibleModal.find('#kookoawpf-dynamic-tooltip').length) {
                $tip.appendTo($visibleModal);
            }
            $tip.css('z-index', 200002);

            const offset = $label.offset();
            const labelH = $label.outerHeight();
            $tip.html(html).data('folderId', folderId).show();

            // Position below the RULES label
            const tipW = $tip.outerWidth();
            const tipH = $tip.outerHeight();
            const winW = $(window).width();
            const winH = $(window).height() + $(window).scrollTop();
            let left = offset.left;
            let top = offset.top + labelH + 4;

            // If overflows right, shift left
            if (left + tipW > winW - 10) {
                left = winW - tipW - 10;
            }
            // If overflows bottom, show above the label
            if (top + tipH > winH - 10) {
                top = offset.top - tipH - 4;
            }

            $tip.css({ top: top, left: left });
        }

        function _buildTipHtml(rules) {
            const fields = self.data.dynamicFields || {};
            const options = self.data.dynamicOptions || {};
            const strings = self.data.strings || {};
            const titleLabel = $('<span>').text(strings.smartRulesTitle).html();
            let html = '<div class="kookoawpf-tip-title">' + titleLabel + '</div>';
            html += '<ul class="kookoawpf-tip-rules">';

            for (let i = 0; i < rules.length; i++) {
                const r = rules[i];
                const fieldLabel = (fields[r.field] && fields[r.field].label) || r.field;
                let valueLabel = r.value;

                // Resolve select option labels
                if (options[r.field] && options[r.field][r.value]) {
                    valueLabel = options[r.field][r.value];
                }

                // Boolean/toggle fields
                if (fields[r.field] && fields[r.field].type === 'toggle') {
                    valueLabel = r.value == '1' ? strings.yes : strings.no;
                }

                html += '<li><span class="kookoawpf-tip-field">' +
                    $('<span>').text(fieldLabel).html() + ':</span> ' +
                    '<span class="kookoawpf-tip-value">' +
                    $('<span>').text(valueLabel).html() + '</span></li>';
            }

            html += '</ul>';
            return html;
        }

        // Invalidate cache on rule save
        const origSave = self._saveDynamic;
        self._saveDynamic = function() {
            _tipCache = {};
            origSave.call(self);
        };
    };

    // ========================
    // Tree + Context Menu glue
    // ========================
    // Decorate smart-folder tree nodes with a "Rules" label and inject the
    // "Edit Rules" entry into the context menu — handled here so all the
    // smart-folder logic lives in a single file.

    $(document).on('kookoawpf:folder-node-render', function(e, $node, node) {
        if (!$node || !node || !node.data) return;

        $node.toggleClass('kookoawpf-dynamic-node', !!node.data.is_dynamic);

        const $anchor = $node.children('.jstree-anchor');
        $anchor.children('.kookoawpf-rules-label').remove();
        if (node.data.is_dynamic) {
            $('<span>', { 'class': 'kookoawpf-rules-label' })
                .text(FO.data.strings.rulesLabel)
                .appendTo($anchor);
        }
    });

    $(document).on('kookoawpf:context-menu-build', function(e, items, node, ctx) {
        if (!node || !node.data || !node.data.is_dynamic) return;
        // Skip in the media modal — the smart-folder branch of _showContextMenu
        // owns its own menu there.
        if (ctx && ctx.source === 'modal') return;

        const S = FO.data.strings || {};
        const label = S.editRules || 'Edit Rules';
        const isLocked = !!(node.data && node.data.locked);
        const item = (typeof FO._ctxItem === 'function')
            ? FO._ctxItem('edit-rules', 'admin-generic', label, false, isLocked, false, isLocked)
            : { action: 'edit-rules', icon: 'admin-generic', label: label };

        items.unshift(item);
    });

    // Wrap _executeContextAction to dispatch 'edit-rules' to the modal.
    const origExecute = FO._executeContextAction;
    FO._executeContextAction = function(action, fid, node, ctx) {
        if ('edit-rules' === action) {
            if (typeof FO._openDynamicModal === 'function') {
                const name = (node && (node.data.original_text || node.text)) || '';
                FO._openDynamicModal('edit', fid, name);
            }
            return;
        }
        if (typeof origExecute === 'function') {
            origExecute.call(this, action, fid, node, ctx);
        }
    };

    // Auto-init on DOM ready. admin.js runs its own $(function(){...}) first
    // (it is enqueued before this file), so by the time we get here FO.data
    // and the base click handlers are already wired up.
    $(function() {
        if (typeof FO.initDynamic === 'function') {
            FO.initDynamic();
        }
        if (typeof FO.initDynamicTooltip === 'function') {
            FO.initDynamicTooltip();
        }
    });

})(jQuery);
