/**
 * TNC FlipBook 3D — Admin JavaScript
 * Handles tab switching, media uploads, sortable, repeaters, overrides.
 */
/* global jQuery, wp, tncfb3dAdminData */
(function( $ ) {
    'use strict';

    // ── Tab switching ───────────────────────────────────────────────────────
    $( document ).on( 'click', '.tncfb-tab-btn', function() {
        var tab  = $( this ).data( 'tab' );
        var wrap = $( this ).closest( '.tncfb-wizard-wrap' );

        wrap.find( '.tncfb-tab-btn' ).removeClass( 'is-active' ).attr( 'aria-selected', 'false' );
        wrap.find( '.tncfb-tab-panel' ).removeClass( 'is-active' ).attr( 'aria-hidden', 'true' );

        $( this ).addClass( 'is-active' ).attr( 'aria-selected', 'true' );
        wrap.find( '#tncfb-panel-' + tab ).addClass( 'is-active' ).attr( 'aria-hidden', 'false' );
        $( '#tncfb3d_active_tab' ).val( tab );

        // No localStorage — always start on Content tab

        // Refresh CodeMirror instances in the newly visible panel
        wrap.find( '#tncfb-panel-' + tab + ' .CodeMirror' ).each( function() {
            if ( this.CodeMirror ) { this.CodeMirror.refresh(); }
        } );
    } );

    // Always open Content tab by default (no localStorage restore)

    // ── Source type card selection ──────────────────────────────────────────
    $( document ).on( 'click', '.tncfb-source-card', function() {
        var card = $( this );
        var type = card.data( 'type' );

        card.closest( '.tncfb-source-cards' ).find( '.tncfb-source-card' ).removeClass( 'is-active' );
        card.addClass( 'is-active' );
        card.find( 'input[type="radio"]' ).prop( 'checked', true );

        $( '.tncfb-source-section' ).hide();
        $( '#tncfb-source-' + type ).show();
    } );

    // ── Small card selectors (layout, style, paper type) ───────────────────
    $( document ).on( 'click', '.tncfb-card-sm', function() {
        $( this ).closest( '.tncfb-card-row' ).find( '.tncfb-card-sm' ).removeClass( 'is-active' );
        $( this ).addClass( 'is-active' );
        $( this ).find( 'input[type="radio"]' ).prop( 'checked', true );
    } );

    // ── Toolbar layout cards (illustrated 2x2 picker) ───────────────────────
    $( document ).on( 'click', '.tncfb-layout-card', function() {
        var $input = $( this ).find( 'input[type="radio"]' );
        if ( $input.prop( 'disabled' ) ) { return; }
        $( this ).closest( '.tncfb-layout-cards' ).find( '.tncfb-layout-card' ).removeClass( 'is-active' );
        $( this ).addClass( 'is-active' );
        $input.prop( 'checked', true );
    } );

    // ── Paper type conditional fields ────────────────────────────────────────
    $( document ).on( 'click', '.tncfb-paper-type-cards .tncfb-card-sm', function() {
        var paperType = $( this ).find( 'input[type="radio"]' ).val();
        $( '.tncfb-paper-field' ).each( function() {
            var forType = $( this ).data( 'paper-type' );
            if ( forType === paperType ) {
                $( this ).slideDown( 200 );
            } else {
                $( this ).slideUp( 200 );
            }
        } );
    } );

    // ── Tri-state section controls (Global / On / Off) ──────────────────────
    $( document ).on( 'change', '.tncfb-tristate input[type="radio"]', function() {
        var val = $( this ).val();
        var $tristate = $( this ).closest( '.tncfb-tristate' );
        var $accordion = $( this ).closest( '.tncfb-accordion-item' );
        var overrideName = $tristate.data( 'override' );
        var valueName = $tristate.data( 'value' );

        // Update visual active state
        $tristate.find( 'label' ).removeClass( 'is-active is-on is-off' );
        if ( val === 'global' ) {
            $( this ).closest( 'label' ).addClass( 'is-active' );
        } else if ( val === 'on' ) {
            $( this ).closest( 'label' ).addClass( 'is-on' );
        } else {
            $( this ).closest( 'label' ).addClass( 'is-off' );
        }

        // Update hidden inputs
        var $overrideInput = $tristate.closest( '.tncfb-accordion-header-toggle' ).find( 'input[name="' + overrideName + '"]' );
        var $valueInput = $tristate.closest( '.tncfb-accordion-header-toggle' ).find( 'input[name="' + valueName + '"]' );
        if ( val === 'global' ) {
            $overrideInput.val( '' );
            $valueInput.val( '' );
        } else {
            $overrideInput.val( '1' );
            $valueInput.val( val === 'on' ? '1' : '' );
        }

        // Toggle accordion disabled state
        $accordion.toggleClass( 'is-disabled', val === 'off' );
    } );

    // ── Accordion section toggles (for global settings page which uses simple toggles) ─
    $( document ).on( 'change', '.tncfb-section-toggle', function() {
        var $accordion = $( this ).closest( '.tncfb-accordion-item' );
        var $label = $( this ).closest( 'label' ).find( '.tncfb-toggle-label' );
        if ( $( this ).is( ':checked' ) ) {
            $accordion.removeClass( 'is-disabled' );
            $label.text( 'On' );
        } else {
            $accordion.addClass( 'is-disabled' );
            $label.text( 'Off' );
        }
    } );

    // ── Chrome cards (surface palette picker) ──────────────────────────────
    $( document ).on( 'click', '.tncfb-chrome-card', function() {
        var $input = $( this ).find( 'input[type="radio"]' );
        if ( $input.is( ':disabled' ) ) { return; }
        $( this ).closest( '.tncfb-chrome-cards' ).find( '.tncfb-chrome-card' ).removeClass( 'is-active' );
        $( this ).addClass( 'is-active' );
        $input.prop( 'checked', true ).trigger( 'change' );
    } );

    // ── Color scheme swatches ───────────────────────────────────────────────
    $( document ).on( 'click', '.tncfb-swatch', function() {
        var $input = $( this ).find( 'input[type="radio"]' );
        var name = $input.attr( 'name' );
        if ( name ) {
            $( 'input[type="radio"][name="' + name.replace( /"/g, '\\"' ) + '"]' )
                .closest( '.tncfb-swatch' ).removeClass( 'is-active' );
        } else {
            $( this ).closest( '.tncfb-scheme-swatches' ).find( '.tncfb-swatch' ).removeClass( 'is-active' );
        }
        $( this ).addClass( 'is-active' );
        $( this ).find( 'input[type="radio"]' ).prop( 'checked', true );
        // Show/hide custom color fields
        var val = $( this ).data( 'value' );
        var $customFields = $( this ).closest( '.tncfb-override-body' ).find( '.tncfb-custom-colors' );
        if ( $customFields.length ) {
            if ( val === 'custom' ) {
                $customFields.slideDown( 200 );
            } else {
                $customFields.slideUp( 200 );
            }
        }
    } );

    // ── Custom color fields — sync color ↔ text ─────────────────────────────
    $( document ).on( 'input', '.tncfb-custom-colors input[type="color"]', function() {
        $( this ).closest( '.tncfb-color-wrap' ).find( '.tncfb-color-text' ).val( $( this ).val() );
    } );
    $( document ).on( 'input', '.tncfb-custom-colors .tncfb-color-text', function() {
        var hex = $( this ).val();
        if ( /^#[0-9a-fA-F]{6}$/.test( hex ) ) {
            $( this ).closest( '.tncfb-color-wrap' ).find( 'input[type="color"]' ).val( hex );
        }
    } );

    // ── Color pickers — sync color input ↔ text input ──────────────────────
    $( document ).on( 'input', 'input[type="color"][data-target]', function() {
        var targetId = $( this ).data( 'target' );
        $( '#' + targetId ).val( $( this ).val() );
    } );
    $( document ).on( 'input', '.tncfb-color-text', function() {
        var val = $( this ).val();
        if ( /^#[0-9a-fA-F]{6}$/.test( val ) ) {
            $( '#' + $( this ).attr( 'id' ) + '_picker' ).val( val );
        }
    } );

    // ── Range slider — live value display ───────────────────────────────────
    $( document ).on( 'input', 'input[type="range"][data-display]', function() {
        var displayId = $( this ).data( 'display' );
        var unit      = $( this ).data( 'unit' ) || '';
        $( '#' + displayId ).text( $( this ).val() + unit );
    } );

    // ── Global override toggles ─────────────────────────────────────────────
    $( document ).on( 'change', '.tncfb-override-checkbox', function() {
        var checked = $( this ).is( ':checked' );
        var label   = $( this ).siblings( '.tncfb-override-toggle-label' );

        // Standard override-wrap (appearance, protection, advanced tabs)
        var wrap = $( this ).closest( '.tncfb-override-wrap' );
        if ( wrap.length ) {
            var body = wrap.find( '.tncfb-override-body' );
            body.toggleClass( 'is-disabled', ! checked );
            body.find( 'input, select, textarea, button' ).prop( 'disabled', ! checked );
            body.find( '.tncfb-field' ).toggleClass( 'tncfb-field-disabled', ! checked );
        }

        // Toolbar item override (compact toggle in toolbar grid)
        var toolbarItem = $( this ).closest( '.tncfb-toolbar-item' );
        if ( toolbarItem.length ) {
            var fields = toolbarItem.find( '.tncfb-field' );
            fields.toggleClass( 'tncfb-field-disabled', ! checked );
            fields.find( 'input' ).prop( 'disabled', ! checked );
        }

        if ( label.length ) {
            label.text( checked
                ? tncfb3dAdminData.strings.customValue
                : tncfb3dAdminData.strings.globalDefault
            );
        }
    } );

    // ── Disabled-field hint tooltip ─────────────────────────────────────────
    // Any control that's locked (because its section is on "global default" or
    // the master toggle is off) surfaces an explanatory tooltip at the click
    // point so users don't think the UI is broken.
    var $hintEl = null;
    var hintHideTimer = null;
    function showHintAt( clickX, clickY, message ) {
        if ( ! $hintEl ) {
            $hintEl = $( '<div class="tncfb-override-hint" role="status" aria-live="polite"></div>' );
            $( document.body ).append( $hintEl );
        }
        $hintEl.text( message );
        // Measure after content set
        $hintEl.css({ top: '-9999px', left: '0px' }).addClass( 'is-measuring' );
        var hintW = $hintEl.outerWidth();
        var hintH = $hintEl.outerHeight();
        $hintEl.removeClass( 'is-measuring' );
        // Place below the click point, centered horizontally on the cursor.
        var top  = clickY + 14;
        var left = clickX - hintW / 2;
        var minLeft = 8;
        var maxLeft = window.innerWidth - hintW - 8;
        if ( left < minLeft ) left = minLeft;
        if ( left > maxLeft ) left = maxLeft;
        // Arrow position relative to tooltip's left edge so it points at click.
        var arrowLeft = clickX - left;
        if ( arrowLeft < 12 ) arrowLeft = 12;
        if ( arrowLeft > hintW - 12 ) arrowLeft = hintW - 12;
        $hintEl.css({
            top:  ( top + window.scrollY ) + 'px',
            left: ( left + window.scrollX ) + 'px'
        });
        $hintEl[0].style.setProperty( '--arrow-left', arrowLeft + 'px' );
        // Force reflow so transition runs on rapid re-show.
        $hintEl[0].offsetWidth;
        $hintEl.addClass( 'is-visible' );
        clearTimeout( hintHideTimer );
        hintHideTimer = setTimeout( function() {
            if ( $hintEl ) $hintEl.removeClass( 'is-visible' );
        }, 3500 );
    }

    function hintMessageFor( $disabledTarget ) {
        // Accordion section turned off via header toggle (e.g. watermark master)
        if ( $disabledTarget.closest( '.tncfb-accordion-item.is-disabled' ).length ) {
            return 'This section is turned off. Use the On/Off toggle in the section header to enable it.';
        }
        // Per-flipbook override toggle is on "Using global default"
        if ( $disabledTarget.closest( '.tncfb-override-body.is-disabled' ).length ) {
            return 'Using global default. Switch the toggle at the top of this section to "Custom value" to override for this FlipBook.';
        }
        // Toolbar grid section-level override
        if ( $disabledTarget.closest( '.tncfb-toolbar-item' ).length ) {
            return 'Using global default. Switch this group to "Custom" at the top to override for this FlipBook.';
        }
        // Generic disabled field
        return 'This field is locked. Enable the override toggle for its section to edit.';
    }

    $( document ).on( 'click', '.tncfb-override-body.is-disabled, .tncfb-accordion-item.is-disabled .tncfb-accordion-body, .tncfb-field-disabled, .tncfb-toolbar-item .tncfb-field-disabled', function( e ) {
        // Only react when click is on locked content (not on the override
        // toggle itself, which lives outside the body).
        var msg = hintMessageFor( $( this ) );
        showHintAt( e.clientX, e.clientY, msg );
        e.stopPropagation();
    } );

    // ── Toolbar section-level override toggle ───────────────────────────────
    $( document ).on( 'change', '.tncfb-section-override-checkbox', function() {
        var checked = $( this ).is( ':checked' );
        var group   = $( this ).closest( '.tncfb-toolbar-group' );

        // Update label text
        $( this ).siblings( '.tncfb-section-override-label' ).text( checked ? 'Custom' : 'Global' );

        // Sync all hidden per-item override fields in this section
        group.find( '.tncfb-item-override-hidden' ).val( checked ? '1' : '' );

        // Enable/disable all switcher fields in this section
        group.find( '.tncfb-toolbar-item .tncfb-field' ).toggleClass( 'tncfb-field-disabled', ! checked );
        group.find( '.tncfb-toolbar-item .tncfb-field input' ).prop( 'disabled', ! checked );
    } );

    // ── Media upload (PDF + images) ─────────────────────────────────────────
    var pdfFrame;
    $( document ).on( 'click', '.tncfb-media-upload', function( e ) {
        e.preventDefault();
        var btn      = $( this );
        var mimeType = btn.data( 'media-type' ) || '';

        // New PDF card/dropzone layout
        var isNewLayout = btn.hasClass( 'tncfb-pdf-dropzone' ) || btn.closest( '.tncfb-pdf-card' ).length > 0;
        var section, idInput;

        if ( isNewLayout ) {
            section = btn.closest( '.tncfb-source-inner' );
            idInput = section.find( 'input[type="hidden"]' );
            mimeType = mimeType || 'application/pdf';
        } else {
            // Legacy field-media layout
            var wrap = btn.closest( '.tncfb-field-media' );
            idInput  = wrap.find( 'input[type="hidden"]' );
            mimeType = mimeType || wrap.data( 'media-type' ) || '';
        }

        var strings = tncfb3dAdminData && tncfb3dAdminData.strings ? tncfb3dAdminData.strings : {};
        var isAudio = mimeType === 'audio';
        pdfFrame = wp.media({
            title:    mimeType === 'application/pdf' ? ( strings.choosePDF || 'Choose PDF' ) : isAudio ? ( strings.chooseAudio || 'Choose Audio File' ) : ( strings.chooseImage || 'Choose Image' ),
            button:   { text: mimeType === 'application/pdf' ? ( strings.usePDF || 'Use this PDF' ) : isAudio ? ( strings.useAudio || 'Use this audio' ) : ( strings.useImage || 'Use this image' ) },
            multiple: false,
            library:  { type: mimeType },
        });

        pdfFrame.on( 'select', function() {
            var attachment = pdfFrame.state().get( 'selection' ).first().toJSON();
            idInput.val( attachment.id );

            if ( isNewLayout ) {
                // Build file size string
                var fileSize = '';
                if ( attachment.filesizeHumanReadable ) {
                    fileSize = attachment.filesizeHumanReadable;
                }

                // Replace dropzone/card with a new card
                var target = section.find( '.tncfb-pdf-dropzone, .tncfb-pdf-card' );
                var cardHtml =
                    '<div class="tncfb-pdf-card" id="tncfb-pdf-card">' +
                        '<div class="tncfb-pdf-card-icon">' +
                            '<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="#e74c3c" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>' +
                        '</div>' +
                        '<div class="tncfb-pdf-card-info">' +
                            '<strong class="tncfb-pdf-card-name">' + $( '<span>' ).text( attachment.title || attachment.filename ).html() + '</strong>' +
                            ( fileSize ? '<span class="tncfb-pdf-card-meta">' + $( '<span>' ).text( fileSize ).html() + '</span>' : '' ) +
                        '</div>' +
                        '<div class="tncfb-pdf-card-actions">' +
                            '<button type="button" class="tncfb-media-upload tncfb-pdf-card-btn" data-media-type="application/pdf">' +
                                '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg> ' +
                                'Replace' +
                            '</button>' +
                            '<button type="button" class="tncfb-media-remove tncfb-pdf-card-btn tncfb-pdf-card-btn-danger">' +
                                '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg> ' +
                                'Remove' +
                            '</button>' +
                        '</div>' +
                    '</div>';
                target.replaceWith( cardHtml );
            } else {
                // Legacy layout
                var wrap     = idInput.closest( '.tncfb-field-media' );
                var preview  = wrap.find( '.tncfb-media-preview' );
                var filename = wrap.find( '.tncfb-media-filename' );
                var thumb    = wrap.find( '.tncfb-media-thumb' );
                filename.text( attachment.title || attachment.filename );
                if ( attachment.sizes && attachment.sizes.thumbnail ) {
                    thumb.attr( 'src', attachment.sizes.thumbnail.url ).show();
                }
                preview.removeClass( 'tncfb-hidden' ).css( 'display', 'flex' );
                wrap.find( '.tncfb-audio-dropzone' ).addClass( 'tncfb-hidden' );
            }
        } );

        pdfFrame.open();
    } );

    $( document ).on( 'click', '.tncfb-media-remove', function() {
        // New PDF card layout: replace card with dropzone
        var card = $( this ).closest( '.tncfb-pdf-card' );
        if ( card.length ) {
            var section = card.closest( '.tncfb-source-inner' );
            section.find( 'input[type="hidden"]' ).val( '' );
            card.replaceWith(
                '<div class="tncfb-pdf-dropzone tncfb-media-upload" id="tncfb-pdf-dropzone" data-media-type="application/pdf">' +
                    '<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="#2271b1" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>' +
                    '<p class="tncfb-pdf-dropzone-text">Select PDF</p>' +
                    '<span class="tncfb-pdf-dropzone-hint">Upload or choose from Media Library</span>' +
                '</div>'
            );
            return;
        }

        // Legacy media field layout
        var wrap = $( this ).closest( '.tncfb-field-media' );
        wrap.find( 'input[type="hidden"]' ).val( '' );
        wrap.find( '.tncfb-media-preview' ).addClass( 'tncfb-hidden' ).css( 'display', 'none' );
        wrap.find( '.tncfb-audio-dropzone' ).removeClass( 'tncfb-hidden' );
    } );

    // ── Multiple PDFs — add PDF row ─────────────────────────────────────────
    $( document ).on( 'click', '#tncfb-add-pdf-btn', function() {
        var list  = $( '#tncfb-multi-pdf-rows' );
        var idx   = list.find( '.tncfb-multi-pdf-row' ).length;
        var row   = $( '<div class="tncfb-repeater-row tncfb-multi-pdf-row" />' )
            .attr( 'data-index', idx )
            .append(
                '<span class="tncfb-row-handle"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><circle cx="9" cy="5" r="1"/><circle cx="15" cy="5" r="1"/><circle cx="9" cy="12" r="1"/><circle cx="15" cy="12" r="1"/><circle cx="9" cy="19" r="1"/><circle cx="15" cy="19" r="1"/></svg></span>' +
                '<div class="tncfb-row-fields">' +
                    '<input type="hidden" name="_tncfb3d_pdf_ids[' + idx + '][id]" value="" class="tncfb-pdf-id-input" />' +
                    '<span class="tncfb-pdf-filename"></span>' +
                    '<input type="text" name="_tncfb3d_pdf_ids[' + idx + '][name]" value="" placeholder="' + 'Chapter name (optional)' + '" class="regular-text" />' +
                    '<button type="button" class="button tncfb-choose-pdf-btn">' + tncfb3dAdminData.strings.choosePDF + '</button>' +
                '</div>' +
                '<button type="button" class="tncfb-row-remove tncfb-btn-pill tncfb-btn-pill--danger"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg></button>'
            );
        $( '.tncfb-repeater-empty' ).hide();
        list.append( row );
    } );

    $( document ).on( 'click', '.tncfb-choose-pdf-btn', function() {
        var btn      = $( this );
        var row      = btn.closest( '.tncfb-multi-pdf-row' );
        var idInput  = row.find( '.tncfb-pdf-id-input' );
        var filename = row.find( '.tncfb-pdf-filename' );

        var frame = wp.media({
            title:   tncfb3dAdminData.strings.choosePDF,
            button:  { text: tncfb3dAdminData.strings.usePDF },
            multiple: false,
            library: { type: 'application/pdf' },
        });
        frame.on( 'select', function() {
            var att = frame.state().get( 'selection' ).first().toJSON();
            idInput.val( att.id );
            filename.text( att.title || att.filename );
        } );
        frame.open();
    } );

    // ── Image gallery ───────────────────────────────────────────────────────
    $( document ).on( 'click', '#tncfb-add-images-btn', function() {
        var gallery = $( '#tncfb-image-gallery' );

        var frame = wp.media({
            title:    tncfb3dAdminData.strings.addImages,
            button:   { text: tncfb3dAdminData.strings.addImages },
            multiple: 'add',
            library:  { type: 'image' },
        });
        frame.on( 'select', function() {
            frame.state().get( 'selection' ).each( function( att ) {
                var id   = att.get( 'id' );
                var url  = att.get( 'sizes' ) && att.get( 'sizes' ).thumbnail ? att.get( 'sizes' ).thumbnail.url : att.get( 'url' );
                var item = $( '<div class="tncfb-gallery-thumb" data-id="' + id + '">' +
                    '<input type="hidden" name="_tncfb3d_image_ids[]" value="' + id + '" />' +
                    ( url ? '<img src="' + url + '" alt="" />' : '' ) +
                    '<button type="button" class="tncfb-gallery-remove"><span class="dashicons dashicons-no-alt"></span></button>' +
                    '</div>' );
                gallery.append( item );
            } );
        } );
        frame.open();
    } );

    $( document ).on( 'click', '.tncfb-gallery-remove', function() {
        $( this ).closest( '.tncfb-gallery-thumb' ).remove();
    } );

    // ── Sortable (gallery and multi-PDF) ────────────────────────────────────
    $( function() {
        if ( $( '#tncfb-image-gallery' ).length ) {
            $( '#tncfb-image-gallery' ).sortable( { items: '.tncfb-gallery-thumb', tolerance: 'pointer' } );
        }
        if ( $( '#tncfb-multi-pdf-rows' ).length ) {
            $( '#tncfb-multi-pdf-rows' ).sortable( { items: '.tncfb-multi-pdf-row', handle: '.tncfb-row-handle', tolerance: 'pointer' } );
        }

        // Generic sortable repeaters (TOC, etc.)
        $( '.tncfb-repeater-sortable .tncfb-repeater-rows' ).each( function() {
            $( this ).sortable( {
                items: '> .tncfb-repeater-row',
                handle: '.tncfb-row-handle',
                tolerance: 'pointer',
                placeholder: 'tncfb-sortable-placeholder',
                update: function() {
                    // Re-index names after reorder
                    $( this ).find( '> .tncfb-repeater-row' ).each( function( i ) {
                        $( this ).attr( 'data-index', i );
                        $( this ).find( '[name]' ).each( function() {
                            var name = $( this ).attr( 'name' );
                            $( this ).attr( 'name', name.replace( /\[\d+\]/, '[' + i + ']' ) );
                        } );
                    } );
                }
            } );
        } );
    } );

    // ── Generic repeater remove ─────────────────────────────────────────────
    $( document ).on( 'click', '.tncfb-row-remove', function() {
        $( this ).closest( '.tncfb-repeater-row' ).remove();
    } );

    // ── Video chooser ───────────────────────────────────────────────────────
    $( document ).on( 'click', '.tncfb-choose-video-btn', function() {
        var btn      = $( this );
        var row      = btn.closest( '.tncfb-repeater-row, .tncfb-row' );
        var idInput  = row.find( '.tncfb-video-id-input' );
        var filename = row.find( '.tncfb-video-filename' );

        var frame = wp.media({
            title:   tncfb3dAdminData.strings.chooseVideo,
            button:  { text: tncfb3dAdminData.strings.useVideo },
            multiple: false,
            library: { type: 'video' },
        });
        frame.on( 'select', function() {
            var att = frame.state().get( 'selection' ).first().toJSON();
            idInput.val( att.id );
            filename.text( att.title || att.filename );
        } );
        frame.open();
    } );

    // ── Generic repeater — add row (for page_videos / hotspots) ────────────
    $( document ).on( 'click', '.tncfb-repeater-add', function() {
        var repeaterId = $( this ).data( 'repeater' );
        var rows       = $( '#' + repeaterId + '_rows' );
        var idx        = rows.find( '.tncfb-repeater-row' ).length;
        var template   = $( '#' + repeaterId + '_template' );
        if ( ! template.length ) { return; }

        var html = template.html().replace( /\[__INDEX__\]/g, '[' + idx + ']' );
        var row  = $( '<div class="tncfb-repeater-row" data-index="' + idx + '">' + html + '</div>' );
        $( '.tncfb-repeater-empty' ).hide();
        rows.append( row );
    } );

    // ── Shortcode copy button ───────────────────────────────────────────────
    $( document ).on( 'click', '.tncfb-copy-shortcode-btn, .tncfb-copy-btn, .tncfb-copy-permalink-btn', function() {
        var text = $( this ).data( 'clipboard' );
        var btn  = $( this );
        // Save original HTML (icon + text) and restore after feedback
        var originalHtml = btn.html();
        var copiedText = ( tncfb3dAdminData && tncfb3dAdminData.strings && tncfb3dAdminData.strings.copied ) ? tncfb3dAdminData.strings.copied : 'Copied!';
        var showCopied = function() {
            btn.html( '<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg> ' + copiedText );
            btn.css( { background: '#00a32a', borderColor: '#00a32a', color: '#fff' } );
            setTimeout( function() {
                btn.html( originalHtml );
                btn.css( { background: '', borderColor: '', color: '' } );
            }, 1500 );
        };
        if ( navigator.clipboard && window.isSecureContext ) {
            navigator.clipboard.writeText( text ).then( showCopied );
        } else {
            var el = $( '<input type="text" />' ).val( text ).appendTo( 'body' ).select();
            document.execCommand( 'copy' );
            el.remove();
            showCopied();
        }
    } );

    // ── Dynamic Shortcode Generator ──────────────────────────────────────────

    function updateShortcodeVisibility() {
        var type = $( '#tncfb-shortcode-type' ).val();

        $( '.tncfb-sc-group' ).each( function() {
            var $g       = $( this );
            var forTypes = ( $g.data( 'for' ) || '' ).split( ' ' );
            $g.toggle( forTypes.indexOf( type ) !== -1 );
        } );
    }

    function buildShortcode() {
        var postId = $( '#tncfb-sc-post-id' ).val();
        if ( ! postId ) return;

        var type = $( '#tncfb-shortcode-type' ).val();
        var tag  = 'tnc-flipbook-3d-' + type;
        var attrs = 'id="' + postId + '"';

        $( '.tncfb-sc-opt' ).each( function() {
            var $el   = $( this );
            var group = $el.closest( '.tncfb-sc-group' );

            if ( group.length ) {
                var forTypes = ( group.attr( 'data-for' ) || '' ).split( ' ' );
                if ( forTypes.indexOf( type ) === -1 ) return;
            }

            var attr = $el.data( 'attr' );
            if ( ! attr ) return;

            var val;
            if ( $el.is( ':checkbox' ) ) {
                val = $el.is( ':checked' ) ? ( $el.data( 'val' ) || 'true' ) : '';
            } else {
                val = $el.val();
            }

            if ( val && val !== '' ) {
                if ( attr === 'image' ) {
                    attrs += ' cover="false"';
                }
                attrs += ' ' + attr + '="' + val + '"';
            }
        } );

        var sc = '[' + tag + ' ' + attrs + ']';
        $( '#tncfb-shortcode-text' ).text( sc );
        $( '.tncfb-copy-shortcode-btn' ).data( 'clipboard', sc );
    }

    // Rebuild + retoggle on type change
    $( document ).on( 'change', '#tncfb-shortcode-type', function() {
        updateShortcodeVisibility();
        buildShortcode();
    } );

    // Rebuild on any option change; retoggle when a visibility-driving field changes
    $( document ).on( 'change input', '.tncfb-sc-opt', function() {
        buildShortcode();
    } );

    // Image browse button for shortcode generator
    $( document ).on( 'click', '.tncfb-sc-upload-btn', function( e ) {
        e.preventDefault();
        var $input = $( this ).siblings( 'input.tncfb-sc-opt' );
        var frame = wp.media( { multiple: false, library: { type: 'image' } } );
        frame.on( 'select', function() {
            var url = frame.state().get( 'selection' ).first().toJSON().url;
            $input.val( url ).trigger( 'change' );
        } );
        frame.open();
    } );

    // Initial build on page load
    $( function() {
        if ( $( '#tncfb-sc-post-id' ).length ) {
            $( '#tncfb-shortcode-type' ).trigger( 'change' );
        }
    } );

    // ── Settings page tabs ──────────────────────────────────────────────────
    $( document ).on( 'click', '.tncfb-settings-nav-btn', function() {
        var tab = $( this ).data( 'tab' );
        $( '.tncfb-settings-nav-btn' ).removeClass( 'is-active' );
        $( '.tncfb-settings-section' ).removeClass( 'is-active' );
        $( this ).addClass( 'is-active' );
        $( '#tncfb-settings-' + tab ).addClass( 'is-active' );
        $( '#tncfb3d_settings_tab' ).val( tab );
        try { localStorage.setItem( 'tncfb3d_settings_tab', tab ); } catch(e) {}

        // Refresh CodeMirror instances in the newly visible tab
        $( '#tncfb-settings-' + tab ).find( '.CodeMirror' ).each( function() {
            if ( this.CodeMirror ) { this.CodeMirror.refresh(); }
        } );
    } );

    $( function() {
        try {
            var saved = localStorage.getItem( 'tncfb3d_settings_tab' );
            if ( saved && $( '.tncfb-settings-nav-btn[data-tab="' + saved + '"]' ).length ) {
                $( '.tncfb-settings-nav-btn[data-tab="' + saved + '"]' ).trigger( 'click' );
            } else {
                $( '.tncfb-settings-nav-btn:first' ).trigger( 'click' );
            }
        } catch(e) {
            $( '.tncfb-settings-nav-btn:first' ).trigger( 'click' );
        }
    } );

    // ── Secret field eye toggle ──────────────────────────────────────────────
    $( document ).on( 'click', '.tncfb-eye-toggle', function() {
        var $wrap = $( this ).closest( '.tncfb-secret-wrap' );
        var $input = $wrap.find( 'input' );
        var $on  = $( this ).find( '.tncfb-eye-on' );
        var $off = $( this ).find( '.tncfb-eye-off' );
        if ( $input.attr( 'type' ) === 'password' ) {
            $input.attr( 'type', 'text' );
            $on.addClass( 'is-hidden' );
            $off.removeClass( 'is-hidden' );
        } else {
            $input.attr( 'type', 'password' );
            $off.addClass( 'is-hidden' );
            $on.removeClass( 'is-hidden' );
        }
    } );

    // ── Settings page media upload (logo fields) ────────────────────────
    $( document ).on( 'click', '.tncfb-settings-media-upload', function( e ) {
        e.preventDefault();
        var $btn     = $( this );
        var $field   = $btn.closest( '.tncfb-settings-media-field' );
        var $input   = $field.find( '.tncfb-settings-media-input' );
        var $preview = $field.find( '.tncfb-settings-media-preview' );
        var mediaType = $btn.data( 'media-type' ) || 'image';

        var frame = wp.media( { title: mediaType === 'audio' ? 'Choose Audio' : 'Choose Image', multiple: false, library: { type: mediaType } } );
        frame.on( 'select', function() {
            var attachment = frame.state().get( 'selection' ).first().toJSON();
            $input.val( attachment.id );
            if ( mediaType === 'audio' ) {
                $preview.find( 'span' ).first().text( attachment.title || attachment.filename );
            } else {
                var url = attachment.sizes && attachment.sizes.medium ? attachment.sizes.medium.url : attachment.url;
                $preview.find( 'img' ).attr( 'src', url );
            }
            $preview.show();
        } );
        frame.open();
    } );

    $( document ).on( 'click', '.tncfb-settings-media-remove', function( e ) {
        e.preventDefault();
        var $field   = $( this ).closest( '.tncfb-settings-media-field' );
        $field.find( '.tncfb-settings-media-input' ).val( '' );
        $field.find( '.tncfb-settings-media-preview' ).hide();
    } );

    // ── Watermark type toggle (text vs image fields) ─────────────────────
    function tncfb3dUpdateWatermarkFields( context ) {
        // Metabox + Settings: find watermark type selects by ID suffix or class
        context.find( '[id$="protection_watermark_type"], .tncfb-wm-type-select' ).each( function() {
            var val = $( this ).val();
            var parent = $( this ).closest( '.tncfb-accordion-body, .tncfb-settings-section, #tncfb-settings-protection' );
            if ( ! parent.length ) { parent = $( this ).closest( 'form' ); }
            parent.find( '.tncfb-watermark-text-fields' ).toggle( val === 'text' );
            parent.find( '.tncfb-watermark-image-fields' ).toggle( val === 'image' );
            // Settings page rows
            parent.find( '.tncfb-wm-text-row' ).toggle( val === 'text' );
            parent.find( '.tncfb-wm-image-row' ).toggle( val === 'image' );
        } );
    }

    // Run on load and on change
    $( function() { tncfb3dUpdateWatermarkFields( $( document ) ); } );
    $( document ).on( 'change', '[id$="protection_watermark_type"], .tncfb-wm-type-select', function() {
        tncfb3dUpdateWatermarkFields( $( document ) );
    } );


    // ── Promo code copy button ─────────────────────────────────────────────
    $( document ).on( 'click', '.tncfb-promo-copy-btn', function() {
        var btn = $( this );
        var code = btn.data( 'code' );
        if ( ! code ) return;

        var doCopy = function( text ) {
            // Fallback for non-HTTPS
            var ta = document.createElement( 'textarea' );
            ta.value = text;
            ta.style.cssText = 'position:fixed;left:-9999px;';
            document.body.appendChild( ta );
            ta.select();
            document.execCommand( 'copy' );
            document.body.removeChild( ta );
        };

        if ( navigator.clipboard && window.isSecureContext ) {
            navigator.clipboard.writeText( code );
        } else {
            doCopy( code );
        }

        btn.addClass( 'copied' );
        btn.find( 'span' ).text( tncfb3dAdminData && tncfb3dAdminData.strings && tncfb3dAdminData.strings.copied ? tncfb3dAdminData.strings.copied : 'Copied!' );
        // Visual feedback for inline style buttons
        btn.css( { background: '#00a32a', color: '#fff', borderColor: '#00a32a' } );
        btn.find( '.tncfb-copy-icon' ).css( 'stroke', '#fff' );
        setTimeout( function() {
            btn.removeClass( 'copied' );
            btn.find( 'span' ).text( tncfb3dAdminData && tncfb3dAdminData.strings && tncfb3dAdminData.strings.copy ? tncfb3dAdminData.strings.copy : 'Copy' );
            btn.css( { background: '', color: '', borderColor: '' } );
            btn.find( '.tncfb-copy-icon' ).css( 'stroke', '' );
        }, 2000 );
    } );

    // ── Searchable Select (lightweight select2 alternative) ─────��────────
    // Converts a <select> into a text-searchable dropdown.
    // Usage: $('select').tncfbSearchable();
    $.fn.tncfbSearchable = function() {
        return this.each( function() {
            var $select = $( this );
            if ( $select.data( 'tncfb-searchable' ) ) return;
            $select.data( 'tncfb-searchable', true );

            // Build option data (flatten optgroups)
            var items = [];
            $select.find( 'option, optgroup' ).each( function() {
                if ( this.tagName === 'OPTGROUP' ) {
                    items.push( { type: 'group', label: this.label } );
                } else {
                    var $opt = $( this );
                    items.push( {
                        type: 'option',
                        value: $opt.val(),
                        text: $opt.text(),
                        search: $opt.text().toLowerCase(),
                        selected: $opt.is( ':selected' )
                    } );
                }
            } );

            // Get initial selected text
            var selectedText = $select.find( 'option:selected' ).text() || '';

            // Hide original select
            $select.hide();

            // Build wrapper
            var $wrap = $( '<div class="tncfb-searchable-wrap"></div>' );
            var $display = $( '<div class="tncfb-searchable-display">' +
                '<span class="tncfb-searchable-text"></span>' +
                '<svg class="tncfb-searchable-arrow" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>' +
                '</div>' );
            var $dropdown = $( '<div class="tncfb-searchable-dropdown"></div>' );
            var $search = $( '<input type="text" class="tncfb-searchable-input" placeholder="' +
                ( tncfb3dAdminData && tncfb3dAdminData.strings && tncfb3dAdminData.strings.search_placeholder ? tncfb3dAdminData.strings.search_placeholder : 'Type to search...' ) +
                '" autocomplete="off" />' );
            var $list = $( '<div class="tncfb-searchable-list"></div>' );

            $dropdown.append( $search ).append( $list );
            $wrap.append( $display ).append( $dropdown );
            $select.after( $wrap );

            $display.find( '.tncfb-searchable-text' ).text( selectedText );

            // Sync disabled state from the underlying select
            function syncDisabled() {
                var isDisabled = $select.prop( 'disabled' );
                $wrap.toggleClass( 'tncfb-searchable-disabled', isDisabled );
                if ( isDisabled ) {
                    $wrap.removeClass( 'tncfb-searchable-open' );
                }
            }
            syncDisabled();

            // Watch for disabled attribute changes (from override toggle)
            var observer = new MutationObserver( syncDisabled );
            observer.observe( $select[0], { attributes: true, attributeFilter: ['disabled'] } );

            // Also sync when the select value changes externally
            $select.on( 'change', function() {
                $display.find( '.tncfb-searchable-text' ).text( $select.find( 'option:selected' ).text() || '' );
            } );

            function renderList( filter ) {
                var html = '';
                var f = ( filter || '' ).toLowerCase();
                var visibleGroup = false;
                var count = 0;

                for ( var i = 0; i < items.length; i++ ) {
                    var item = items[i];
                    if ( item.type === 'group' ) {
                        // We'll add the group header only if it has visible children
                        visibleGroup = item;
                        continue;
                    }
                    if ( f && item.search.indexOf( f ) === -1 ) continue;

                    if ( visibleGroup ) {
                        html += '<div class="tncfb-searchable-group">' + $( '<span>' ).text( visibleGroup.label ).html() + '</div>';
                        visibleGroup = false;
                    }
                    var cls = 'tncfb-searchable-item';
                    if ( item.value === $select.val() ) cls += ' tncfb-searchable-selected';
                    html += '<div class="' + cls + '" data-value="' + $( '<span>' ).text( item.value ).html() + '">' + $( '<span>' ).text( item.text ).html() + '</div>';
                    count++;
                    if ( count >= 200 ) {
                        html += '<div class="tncfb-searchable-more">' + ( items.length - count ) + ' more &mdash; type to filter</div>';
                        break;
                    }
                }

                if ( ! count ) {
                    html = '<div class="tncfb-searchable-empty">No results</div>';
                }

                $list.html( html );
            }

            // Toggle dropdown
            $display.on( 'click', function( e ) {
                e.stopPropagation();
                if ( $wrap.hasClass( 'tncfb-searchable-disabled' ) ) return;
                var wasOpen = $wrap.hasClass( 'tncfb-searchable-open' );
                $( '.tncfb-searchable-wrap' ).removeClass( 'tncfb-searchable-open' );
                if ( ! wasOpen ) {
                    $wrap.addClass( 'tncfb-searchable-open' );
                    $search.val( '' );
                    renderList( '' );
                    $search.focus();
                    // Scroll selected into view
                    setTimeout( function() {
                        var $sel = $list.find( '.tncfb-searchable-selected' );
                        if ( $sel.length ) {
                            $list.scrollTop( $sel[0].offsetTop - $list.height() / 2 );
                        }
                    }, 10 );
                }
            } );

            // Search filter
            $search.on( 'input', function() {
                renderList( $( this ).val() );
            } );

            // Select item
            $list.on( 'click', '.tncfb-searchable-item', function() {
                var val = $( this ).data( 'value' );
                $select.val( val ).trigger( 'change' );
                $display.find( '.tncfb-searchable-text' ).text( $( this ).text() );
                $wrap.removeClass( 'tncfb-searchable-open' );
            } );

            // Keyboard navigation
            $search.on( 'keydown', function( e ) {
                var $items = $list.find( '.tncfb-searchable-item' );
                var $active = $list.find( '.tncfb-searchable-item.tncfb-searchable-hover' );
                var idx = $items.index( $active );

                if ( e.keyCode === 40 ) { // Down
                    e.preventDefault();
                    idx = Math.min( idx + 1, $items.length - 1 );
                    $items.removeClass( 'tncfb-searchable-hover' );
                    $items.eq( idx ).addClass( 'tncfb-searchable-hover' );
                    $items.eq( idx )[0].scrollIntoView( { block: 'nearest' } );
                } else if ( e.keyCode === 38 ) { // Up
                    e.preventDefault();
                    idx = Math.max( idx - 1, 0 );
                    $items.removeClass( 'tncfb-searchable-hover' );
                    $items.eq( idx ).addClass( 'tncfb-searchable-hover' );
                    $items.eq( idx )[0].scrollIntoView( { block: 'nearest' } );
                } else if ( e.keyCode === 13 ) { // Enter
                    e.preventDefault();
                    if ( $active.length ) {
                        $active.trigger( 'click' );
                    }
                } else if ( e.keyCode === 27 ) { // Escape
                    $wrap.removeClass( 'tncfb-searchable-open' );
                }
            } );

            // Close on outside click
            $( document ).on( 'click', function( e ) {
                if ( ! $( e.target ).closest( '.tncfb-searchable-wrap' ).length ) {
                    $( '.tncfb-searchable-wrap' ).removeClass( 'tncfb-searchable-open' );
                }
            } );
        } );
    };

    // Auto-init on language selects (global settings + metabox)
    $( function() {
        $( '#tncfb3d_language, #_tncfb3d_language' ).tncfbSearchable();
    } );

}( jQuery ) );
