/**
 * Guard Dog Passkeys (WebAuthn) JavaScript Client
 *
 * Handles passkey registration and authentication using the WebAuthn API.
 *
 * @package GuardDog
 * @since 1.9.1
 */

(function($) {
    'use strict';

    /**
     * AJAX helpers for passkey requests.
     */
    const AjaxUtils = {
        /**
         * Run a JSON AJAX request and return a native Promise.
         *
         * @param {Object} options
         * @return {Promise<Object>}
         */
        request: function(options) {
            return new Promise(function(resolve, reject) {
                $.ajax($.extend(true, {
                    dataType: 'json',
                    cache: false,
                    success: function(response, textStatus, jqXHR) {
                        resolve({
                            response: response,
                            textStatus: textStatus,
                            jqXHR: jqXHR
                        });
                    },
                    error: function(jqXHR, textStatus, errorThrown) {
                        reject({
                            jqXHR: jqXHR,
                            textStatus: textStatus,
                            errorThrown: errorThrown
                        });
                    }
                }, options));
            });
        },

        /**
         * Check whether the provided value is a plain-ish object.
         *
         * @param {*} value
         * @return {boolean}
         */
        isObject: function(value) {
            return value !== null && typeof value === 'object';
        },

        /**
         * Check whether an AJAX response looks like an HTML page instead of JSON.
         *
         * This usually means another plugin or access-control layer intercepted
         * the request before Guard Dog could respond.
         *
         * @param {string} responseText
         * @return {boolean}
         */
        isLikelyHtmlResponse: function(responseText) {
            if (!responseText || typeof responseText !== 'string') {
                return false;
            }

            const trimmed = responseText.trim().toLowerCase();

            return trimmed.startsWith('<!doctype html') ||
                trimmed.startsWith('<html') ||
                trimmed.startsWith('<body') ||
                trimmed.includes('<form') ||
                trimmed.includes('wp-login.php');
        },

        /**
         * Extract the most useful message from a JSON response.
         *
         * @param {*} response
         * @param {string} fallback
         * @return {string}
         */
        extractResponseMessage: function(response, fallback) {
            if (this.isObject(response)) {
                if (this.isObject(response.data) && typeof response.data.message === 'string' && response.data.message.length) {
                    return response.data.message;
                }

                if (typeof response.message === 'string' && response.message.length) {
                    return response.message;
                }
            }

            return fallback;
        },

        /**
         * Turn an unexpected or blocked AJAX response into a human-friendly message.
         *
         * @param {Object} error
         * @param {string} fallback
         * @return {string}
         */
        getRequestErrorMessage: function(error, fallback) {
            const jqXHR = error && error.jqXHR ? error.jqXHR : null;
            const textStatus = error && error.textStatus ? error.textStatus : '';
            const responseJSON = jqXHR && this.isObject(jqXHR.responseJSON) ? jqXHR.responseJSON : null;
            const responseText = jqXHR && typeof jqXHR.responseText === 'string' ? jqXHR.responseText : '';
            const statusCode = jqXHR && typeof jqXHR.status === 'number' ? jqXHR.status : 0;

            if (responseJSON) {
                return this.extractResponseMessage(responseJSON, fallback);
            }

            if ('parsererror' === textStatus || this.isLikelyHtmlResponse(responseText) || [401, 403, 404].includes(statusCode)) {
                return guardDogPasskeys.i18n.endpointBlocked || fallback;
            }

            return fallback;
        },

        /**
         * Validate the JSON response shape expected by Guard Dog.
         *
         * @param {*} response
         * @param {string} fallback
         * @return {Object}
         * @throws {Error}
         */
        ensureExpectedResponse: function(response, fallback) {
            if (this.isObject(response) && typeof response.success !== 'undefined') {
                return response;
            }

            if (guardDogPasskeys.debug) {
                console.error('Guard Dog Passkeys: Unexpected AJAX response shape.', response);
            }

            throw new Error(guardDogPasskeys.i18n.unexpectedResponse || fallback);
        }
    };

    /**
     * WebAuthn Utilities
     */
    const WebAuthnUtils = {
        /**
         * Check if WebAuthn is supported in this browser.
         *
         * @return {boolean}
         */
        isSupported: function() {
            return window.PublicKeyCredential !== undefined &&
                   typeof window.PublicKeyCredential === 'function';
        },

        /**
         * Check if platform authenticator is available.
         *
         * @return {Promise<boolean>}
         */
        isPlatformAuthenticatorAvailable: async function() {
            if (!this.isSupported()) {
                return false;
            }
            try {
                return await PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable();
            } catch (e) {
                return false;
            }
        },

        /**
         * Encode ArrayBuffer to base64url string (no padding).
         *
         * WebAuthn requires base64url encoding (RFC 4648) without padding.
         * This uses - and _ instead of + and /, and strips = padding.
         *
         * @param {ArrayBuffer} buffer
         * @return {string}
         */
        arrayBufferToBase64: function(buffer) {
            const bytes = new Uint8Array(buffer);
            let binary = '';
            for (let i = 0; i < bytes.byteLength; i++) {
                binary += String.fromCharCode(bytes[i]);
            }
            // Convert to base64, then to base64url (replace + with -, / with _)
            // and remove padding (=)
            return btoa(binary)
                .replace(/\+/g, '-')
                .replace(/\//g, '_')
                .replace(/=+$/, '');
        },

        /**
         * Decode base64 string to ArrayBuffer.
         *
         * @param {string} base64
         * @return {ArrayBuffer}
         */
        base64ToArrayBuffer: function(base64) {
            // Handle base64url encoding
            base64 = base64.replace(/-/g, '+').replace(/_/g, '/');
            // Add padding if needed
            while (base64.length % 4) {
                base64 += '=';
            }
            const binary = atob(base64);
            const bytes = new Uint8Array(binary.length);
            for (let i = 0; i < binary.length; i++) {
                bytes[i] = binary.charCodeAt(i);
            }
            return bytes.buffer;
        },

        /**
         * Convert credential to JSON for server.
         *
         * @param {PublicKeyCredential} credential
         * @return {string}
         */
        credentialToJSON: function(credential) {
            const response = credential.response;
            const json = {
                id: this.arrayBufferToBase64(credential.rawId),
                rawId: this.arrayBufferToBase64(credential.rawId),
                type: credential.type
            };

            if (response instanceof AuthenticatorAttestationResponse) {
                // Registration response
                json.response = {
                    attestationObject: this.arrayBufferToBase64(response.attestationObject),
                    clientDataJSON: this.arrayBufferToBase64(response.clientDataJSON)
                };
                if (response.getTransports) {
                    json.response.transports = response.getTransports();
                }
            } else if (response instanceof AuthenticatorAssertionResponse) {
                // Authentication response
                json.response = {
                    authenticatorData: this.arrayBufferToBase64(response.authenticatorData),
                    clientDataJSON: this.arrayBufferToBase64(response.clientDataJSON),
                    signature: this.arrayBufferToBase64(response.signature)
                };
                if (response.userHandle) {
                    json.response.userHandle = this.arrayBufferToBase64(response.userHandle);
                }
            }

            if (credential.authenticatorAttachment) {
                json.authenticatorAttachment = credential.authenticatorAttachment;
            }

            return JSON.stringify(json);
        }
    };

    /**
     * Passkey Registration Handler
     */
    const PasskeyRegistration = {
        /**
         * Add device cleanup guidance after a browser/device credential was created.
         *
         * @param {string} message
         * @return {string}
         */
        withDeviceCleanupMessage: function(message) {
            const cleanupMessage = guardDogPasskeys.i18n.deviceCleanup || '';
            if (!cleanupMessage) {
                return message;
            }

            return `${message} ${cleanupMessage}`;
        },

        /**
         * Start the registration process.
         *
         * @param {string} friendlyName
         * @param {string} nonce
         * @param {function} onSuccess
         * @param {function} onError
         */
        register: async function(friendlyName, nonce, onSuccess, onError) {
            if (typeof nonce === 'function') {
                onError = onSuccess;
                onSuccess = nonce;
                nonce = '';
            }

            if (!WebAuthnUtils.isSupported()) {
                onError(guardDogPasskeys.i18n.notSupported);
                return;
            }

            let credentialCreated = false;

            try {
                // Get registration options from server
                const optionsRequest = await AjaxUtils.request({
                    url: guardDogPasskeys.ajaxUrl,
                    type: 'POST',
                    data: {
                        action: 'guard_dog_passkey_register_options',
                        nonce: nonce || $('#guard-dog-passkey-nonce').val()
                    }
                });
                const optionsResponse = AjaxUtils.ensureExpectedResponse(optionsRequest.response, guardDogPasskeys.i18n.error);

                if (!optionsResponse.success) {
                    onError(AjaxUtils.extractResponseMessage(optionsResponse, guardDogPasskeys.i18n.error));
                    return;
                }

                const options = optionsResponse.data.options;
                const regStateToken = optionsResponse.data.state_token;

                // Convert base64 values to ArrayBuffer
                const publicKeyOptions = {
                    challenge: WebAuthnUtils.base64ToArrayBuffer(options.challenge),
                    rp: options.rp,
                    user: {
                        id: WebAuthnUtils.base64ToArrayBuffer(options.user.id),
                        name: options.user.name,
                        displayName: options.user.displayName
                    },
                    pubKeyCredParams: options.pubKeyCredParams,
                    timeout: options.timeout,
                    authenticatorSelection: options.authenticatorSelection,
                    attestation: options.attestation
                };

                // Add excluded credentials if present
                if (options.excludeCredentials && options.excludeCredentials.length > 0) {
                    publicKeyOptions.excludeCredentials = options.excludeCredentials.map(cred => ({
                        type: cred.type,
                        id: WebAuthnUtils.base64ToArrayBuffer(cred.id)
                    }));
                }

                // Create credential
                const credential = await navigator.credentials.create({
                    publicKey: publicKeyOptions
                });
                credentialCreated = true;

                // Verify with server
                const verifyRequest = await AjaxUtils.request({
                    url: guardDogPasskeys.ajaxUrl,
                    type: 'POST',
                    data: {
                        action: 'guard_dog_passkey_register_verify',
                        nonce: nonce || $('#guard-dog-passkey-nonce').val(),
                        credential: WebAuthnUtils.credentialToJSON(credential),
                        friendly_name: friendlyName,
                        state_token: regStateToken
                    }
                });
                const verifyResponse = AjaxUtils.ensureExpectedResponse(verifyRequest.response, guardDogPasskeys.i18n.error);

                if (verifyResponse.success) {
                    onSuccess(verifyResponse.data);
                } else {
                    onError(this.withDeviceCleanupMessage(AjaxUtils.extractResponseMessage(verifyResponse, guardDogPasskeys.i18n.error)));
                }

            } catch (error) {
                if (guardDogPasskeys.debug) { console.error('Passkey registration error:', error); }
                if (error.name === 'NotAllowedError') {
                    onError(guardDogPasskeys.i18n.userCancelled);
                } else if (error instanceof Error && error.message) {
                    onError(credentialCreated ? this.withDeviceCleanupMessage(error.message) : error.message);
                } else {
                    const errorMessage = AjaxUtils.getRequestErrorMessage(error, guardDogPasskeys.i18n.error);
                    onError(credentialCreated ? this.withDeviceCleanupMessage(errorMessage) : errorMessage);
                }
            }
        }
    };

    /**
     * Passkey Authentication Handler
     */
    const PasskeyAuthentication = {
        /**
         * Start the authentication process.
         *
         * @param {string} username Optional username to restrict credentials
         * @param {function} onSuccess
         * @param {function} onError
         * @param {string} redirectTo Optional redirect target for this login instance
         */
        authenticate: async function(username, onSuccess, onError, redirectTo) {
            if (!WebAuthnUtils.isSupported()) {
                onError(guardDogPasskeys.i18n.notSupported);
                return;
            }

            try {
                // Get authentication options from server.
                // cache:false appends _={timestamp} to bust browser/CDN caching.
                // Each request must generate a fresh challenge; stale challenges
                // cause "Authentication session expired" failures.
                const optionsRequest = await AjaxUtils.request({
                    url: guardDogPasskeys.ajaxUrl,
                    type: 'POST',
                    data: {
                        action: 'guard_dog_passkey_auth_options',
                        username: username || ''
                    }
                });
                const optionsResponse = AjaxUtils.ensureExpectedResponse(optionsRequest.response, guardDogPasskeys.i18n.error);

                if (!optionsResponse.success) {
                    onError(AjaxUtils.extractResponseMessage(optionsResponse, guardDogPasskeys.i18n.error));
                    return;
                }

                const options = optionsResponse.data.options;
                const authStateToken = optionsResponse.data.state_token;

                // Convert base64 values to ArrayBuffer
                const publicKeyOptions = {
                    challenge: WebAuthnUtils.base64ToArrayBuffer(options.challenge),
                    timeout: options.timeout,
                    rpId: options.rpId,
                    userVerification: options.userVerification
                };

                // Add allowed credentials if present
                if (options.allowCredentials && options.allowCredentials.length > 0) {
                    publicKeyOptions.allowCredentials = options.allowCredentials.map(cred => ({
                        type: cred.type,
                        id: WebAuthnUtils.base64ToArrayBuffer(cred.id),
                        transports: cred.transports || []
                    }));
                }

                // Get credential
                const credential = await navigator.credentials.get({
                    publicKey: publicKeyOptions
                });

                // Verify with server — cache:false prevents CDN from serving a
                // stale response that contains another user's auth cookies.
                const verifyRequest = await AjaxUtils.request({
                    url: guardDogPasskeys.ajaxUrl,
                    type: 'POST',
                    data: {
                        action: 'guard_dog_passkey_auth_verify',
                        credential: WebAuthnUtils.credentialToJSON(credential),
                        state_token: authStateToken,
                        redirect_to: redirectTo || guardDogPasskeys.redirectTo || $('input[name="redirect_to"]').val() || ''
                    }
                });
                const verifyResponse = AjaxUtils.ensureExpectedResponse(verifyRequest.response, guardDogPasskeys.i18n.error);

                if (verifyResponse.success) {
                    onSuccess(verifyResponse.data);
                } else {
                    onError(AjaxUtils.extractResponseMessage(verifyResponse, guardDogPasskeys.i18n.error));
                }

            } catch (error) {
                if (guardDogPasskeys.debug) { console.error('Passkey authentication error:', error); }
                if (error.name === 'NotAllowedError') {
                    onError(guardDogPasskeys.i18n.setupAfterPassword || guardDogPasskeys.i18n.userCancelled, { reason: 'not_allowed' });
                } else if (error instanceof Error && error.message) {
                    onError(error.message);
                } else {
                    onError(AjaxUtils.getRequestErrorMessage(error, guardDogPasskeys.i18n.error));
                }
            }
        }
    };

    /**
     * Initialize on document ready
     */
    $(function() {
        if (typeof guardDogPasskeys === 'undefined' || !AjaxUtils.isObject(guardDogPasskeys)) {
            console.error('Guard Dog Passkeys: localized configuration is missing.');
            return;
        }

        // Check WebAuthn support
        if (!WebAuthnUtils.isSupported()) {
            $('[data-guard-dog-passkey-browser-support], #guard-dog-passkey-browser-support').show();
            $('[data-guard-dog-passkey-register-form], #guard-dog-passkey-register-form').hide();
            $('[data-guard-dog-passkey-login-button], #guard-dog-passkey-login-btn').hide();
            return;
        }

        // Login page functionality
        if (guardDogPasskeys.isLoginPage) {
            initLoginPage();
        } else {
            // Profile page functionality
            initProfilePage();
        }
    });

    /**
     * Initialize login page passkey functionality
     */
    function initLoginPage() {
        const $instances = $('[data-guard-dog-passkey-login]');

        if ($instances.length) {
            $instances.each(function() {
                initLoginInstance($(this));
            });
            return;
        }

        initLegacyLoginInstance();
    }

    /**
     * Initialize one instance-safe passkey login button.
     *
     * @param {jQuery} $instance
     */
    function initLoginInstance($instance) {
        const $loginBtn = $instance.find('[data-guard-dog-passkey-login-button]').first();
        const $status = $instance.find('[data-guard-dog-passkey-login-status]').first();

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

        bindLoginButton(
            $loginBtn,
            $status,
            $instance.data('usernameSelector') || guardDogPasskeys.usernameSelector || '#user_login',
            $instance.data('redirectTo') || guardDogPasskeys.redirectTo || ''
        );
    }

    /**
     * Initialize legacy passkey login markup that still uses fixed IDs.
     */
    function initLegacyLoginInstance() {
        const $loginBtn = $('#guard-dog-passkey-login-btn');
        const $status = $('#guard-dog-passkey-status');

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

        bindLoginButton(
            $loginBtn,
            $status,
            guardDogPasskeys.usernameSelector || '#user_login',
            guardDogPasskeys.redirectTo || ''
        );
    }

    /**
     * Bind a passkey login button to its closest status region and settings.
     *
     * @param {jQuery} $loginBtn
     * @param {jQuery} $status
     * @param {string} usernameSelector
     * @param {string} redirectTo
     */
    function bindLoginButton($loginBtn, $status, usernameSelector, redirectTo) {
        const $label = $loginBtn.find('[data-guard-dog-passkey-login-label]').first();
        const originalLabel = $label.length ? $.trim($label.text()) : $.trim($loginBtn.text());
        const originalHtml = $loginBtn.html();

        function setButtonLabel(text) {
            if ($label.length) {
                $label.text(text);
                return;
            }

            $loginBtn.text(text);
        }

        function setButtonBusy(text) {
            setButtonLabel(text);
            $loginBtn.prop('disabled', true).attr('aria-busy', 'true');
        }

        function resetButton() {
            if ($label.length) {
                $label.text(originalLabel);
            } else {
                $loginBtn.html(originalHtml);
            }

            $loginBtn.prop('disabled', false).removeAttr('aria-busy');
        }

        function hideStatus() {
            if (!$status.length) {
                return;
            }

            $status.text('').removeClass('is-success is-error').hide();
        }

        function showStatus(message, tone) {
            if (!$status.length) {
                return;
            }

            $status
                .text(message)
                .removeClass('is-success is-error')
                .addClass(tone ? 'is-' + tone : '')
                .show();
        }

        function markSetupIntent() {
            const fieldName = guardDogPasskeys.setupIntentField || 'guard_dog_passkey_setup_intent';
            const forms = [];

            function addForm($form) {
                if (!$form.length || forms.includes($form[0])) {
                    return;
                }

                forms.push($form[0]);
            }

            addForm($loginBtn.closest('form'));
            addForm($(usernameSelector).first().closest('form'));
            addForm($('#loginform'));
            addForm($('.guard-dog-login-form__form').first());

            $(forms).each(function() {
                const $form = $(this);
                let $field = $form.find('input[name="' + fieldName + '"]').first();

                if (!$field.length) {
                    $field = $('<input>', {
                        type: 'hidden',
                        name: fieldName
                    }).appendTo($form);
                }

                $field.val('1');
            });
        }

        $loginBtn.on('click', function(e) {
            e.preventDefault();

            if ($loginBtn.prop('disabled')) {
                return;
            }

            hideStatus();
            setButtonBusy(guardDogPasskeys.i18n.authenticating);

            // Get username from the configured login field if entered.
            const username = $(usernameSelector).val() || '';

            PasskeyAuthentication.authenticate(
                username,
                function(data) {
                    setButtonLabel(guardDogPasskeys.i18n.success);
                    $loginBtn.removeAttr('aria-busy');
                    // Redirect to admin or specified URL.
                    window.location.href = data.redirect || redirectTo || '/wp-admin/';
                },
                function(error, meta) {
                    if (meta && meta.reason === 'not_allowed') {
                        markSetupIntent();
                    }

                    resetButton();
                    showStatus(error, 'error');
                },
                redirectTo
            );
        });
    }

    /**
     * Initialize profile page passkey functionality
     */
    function initProfilePage() {
        const $instances = $('[data-guard-dog-passkeys-management]');

        if (!$instances.length) {
            initManagementInstance($(document));
            return;
        }

        $instances.each(function() {
            initManagementInstance($(this));
        });
    }

    /**
     * Initialize one passkey management UI instance.
     *
     * @param {jQuery} $instance
     */
    function initManagementInstance($instance) {
        const $registerBtn = findInInstance($instance, '[data-guard-dog-register-passkey]', '#guard-dog-register-passkey');
        const $nameInput = findInInstance($instance, '[data-guard-dog-passkey-name]', '#guard-dog-passkey-name');
        const $registerStatus = findInInstance($instance, '[data-guard-dog-passkey-register-status]', '#guard-dog-passkey-register-status');
        const $managementStatus = findInInstance($instance, '[data-guard-dog-passkey-management-status]', '#guard-dog-passkey-management-status');
        const $nonce = findInInstance($instance, '[data-guard-dog-passkey-nonce]', '#guard-dog-passkey-nonce');
        const successRedirect = $instance.data('successRedirect') || '';
        const managedUserId = $instance.data('userId') || '';
        const feedbackToneClasses = 'is-pending is-success is-error';

        function clearFeedback($element) {
            if (!$element.length) {
                return;
            }

            $element
                .removeClass(feedbackToneClasses)
                .text('')
                .prop('hidden', true);
        }

        function showFeedback($element, message, tone) {
            if (!$element.length) {
                return;
            }

            $element
                .removeClass(feedbackToneClasses)
                .toggleClass('is-pending', tone === 'pending')
                .toggleClass('is-success', tone === 'success')
                .toggleClass('is-error', tone === 'error')
                .text(message || '')
                .prop('hidden', !message);
        }

        function renderEmptyStateIfNeeded() {
            const $list = findInInstance($instance, '[data-guard-dog-passkeys-list]', '#guard-dog-passkeys-list');

            if (!$list.length || $list.find('tbody tr').length > 0) {
                return;
            }

            $list.find('.guard-dog-passkeys-management__count, .guard-dog-passkeys-management__table-wrap').remove();

            if (!$list.find('.guard-dog-passkeys-management__empty').length) {
                $('<p>', {
                    class: 'guard-dog-passkeys-management__empty',
                    text: guardDogPasskeys.i18n.emptyState
                }).insertBefore($managementStatus);
            }
        }

        // Register new passkey
        if ($registerBtn.length) {
            $registerBtn.on('click', function(e) {
                e.preventDefault();

                const friendlyName = (($nameInput.val() || '').trim()) || 'My Passkey';

                clearFeedback($managementStatus);
                showFeedback($registerStatus, guardDogPasskeys.i18n.registering, 'pending');
                $registerBtn.prop('disabled', true);

                PasskeyRegistration.register(
                    friendlyName,
                    $nonce.val(),
                    function(data) {
                        showFeedback($registerStatus, data.message || guardDogPasskeys.i18n.success, 'success');
                        $nameInput.val('');
                        $registerBtn.prop('disabled', false);
                        setTimeout(function() {
                            if (successRedirect) {
                                window.location.href = successRedirect;
                                return;
                            }

                            location.reload();
                        }, 1000);
                    },
                    function(error) {
                        showFeedback($registerStatus, error, 'error');
                        $registerBtn.prop('disabled', false);
                    }
                );
            });
        }

        // Delete passkey
        $instance.on('click', '.guard-dog-delete-passkey', function(e) {
            e.preventDefault();

            if (!confirm(guardDogPasskeys.i18n.confirmDelete)) {
                return;
            }

            const $btn = $(this);
            const $row = $btn.closest('tr');
            const passkeyId = $btn.data('passkey-id');

            clearFeedback($registerStatus);
            $btn.prop('disabled', true);

            AjaxUtils.request({
                url: guardDogPasskeys.ajaxUrl,
                type: 'POST',
                data: {
                    action: 'guard_dog_passkey_delete',
                    nonce: $nonce.val() || $('#guard-dog-passkey-nonce').val(),
                    passkey_id: passkeyId,
                    user_id: managedUserId
                }
            }).then(function(result) {
                const response = AjaxUtils.ensureExpectedResponse(result.response, guardDogPasskeys.i18n.error);
                const successMessage = AjaxUtils.extractResponseMessage(response, guardDogPasskeys.i18n.deleted);

                if (response.success) {
                    $row.fadeOut(function() {
                        $(this).remove();
                        renderEmptyStateIfNeeded();
                        showFeedback($managementStatus, successMessage, 'success');
                    });
                    return;
                }

                showFeedback($managementStatus, AjaxUtils.extractResponseMessage(response, guardDogPasskeys.i18n.error), 'error');
                $btn.prop('disabled', false);
            }).catch(function(error) {
                if (guardDogPasskeys.debug) { console.error('Passkey delete error:', error); }
                showFeedback($managementStatus, AjaxUtils.getRequestErrorMessage(error, guardDogPasskeys.i18n.error), 'error');
                $btn.prop('disabled', false);
            });
        });

        // Rename passkey
        $instance.on('click', '.guard-dog-rename-passkey', function(e) {
            e.preventDefault();

            const $btn = $(this);
            const $row = $btn.closest('tr');
            const $nameSpan = $row.find('.passkey-name');
            const passkeyId = $btn.data('passkey-id');
            const currentName = $nameSpan.text();

            const newName = prompt(guardDogPasskeys.i18n.enterName, currentName);
            if (!newName || newName === currentName) {
                return;
            }

            clearFeedback($registerStatus);
            $btn.prop('disabled', true);

            AjaxUtils.request({
                url: guardDogPasskeys.ajaxUrl,
                type: 'POST',
                data: {
                    action: 'guard_dog_passkey_rename',
                    nonce: $nonce.val() || $('#guard-dog-passkey-nonce').val(),
                    passkey_id: passkeyId,
                    friendly_name: newName,
                    user_id: managedUserId
                }
            }).then(function(result) {
                const response = AjaxUtils.ensureExpectedResponse(result.response, guardDogPasskeys.i18n.error);

                if (response.success) {
                    $nameSpan.text(newName);
                    showFeedback($managementStatus, AjaxUtils.extractResponseMessage(response, guardDogPasskeys.i18n.renamed), 'success');
                } else {
                    showFeedback($managementStatus, AjaxUtils.extractResponseMessage(response, guardDogPasskeys.i18n.error), 'error');
                }

                $btn.prop('disabled', false);
            }).catch(function(error) {
                if (guardDogPasskeys.debug) { console.error('Passkey rename error:', error); }
                showFeedback($managementStatus, AjaxUtils.getRequestErrorMessage(error, guardDogPasskeys.i18n.error), 'error');
                $btn.prop('disabled', false);
            });
        });
    }

    /**
     * Find an element in an instance-safe container, falling back to legacy IDs.
     *
     * @param {jQuery} $instance
     * @param {string} dataSelector
     * @param {string} legacySelector
     * @returns {jQuery}
     */
    function findInInstance($instance, dataSelector, legacySelector) {
        const $dataElement = $instance.find(dataSelector).first();

        if ($dataElement.length) {
            return $dataElement;
        }

        const $legacyElement = $instance.find(legacySelector).first();

        if ($legacyElement.length) {
            return $legacyElement;
        }

        return $instance[0] === document ? $(legacySelector).first() : $();
    }

    // Expose for external use if needed
    window.GuardDogPasskeys = {
        Utils: WebAuthnUtils,
        Registration: PasskeyRegistration,
        Authentication: PasskeyAuthentication
    };

})(jQuery);
