/**
 * UltraGuard Pro - Passkeys / WebAuthn client-side handler
 *
 * Handles:
 *  - "Register New Passkey" button on the passkeys dashboard page
 *    (wired through the ultraguard:response:handled event pipeline)
 *  - "Sign in with Passkey" button on the WordPress login page
 *  - Recovery code form on the login page
 *
 * No external dependencies - plain JavaScript + WordPress jQuery (no-conflict safe).
 * Loaded only when passkeys module is active (Pro).
 */

/* global ugPasskeys */
(function ($) {
    'use strict';

    var AJAX_URL = (typeof ugPasskeys !== 'undefined' && ugPasskeys.ajaxurl)
        ? ugPasskeys.ajaxurl
        : '/wp-admin/admin-ajax.php';

    var I18N = (typeof ugPasskeys !== 'undefined' && ugPasskeys.i18n) ? ugPasskeys.i18n : {};

    // -------------------------------------------------------------------------
    // Base64url helpers
    // -------------------------------------------------------------------------

    function base64urlToBuffer(base64url) {
        var padded = base64url.replace(/-/g, '+').replace(/_/g, '/');
        var pad = padded.length % 4;
        if (pad) { padded += '==='.slice(0, 4 - pad); }
        var binary = atob(padded);
        var buf = new Uint8Array(binary.length);
        for (var i = 0; i < binary.length; i++) { buf[i] = binary.charCodeAt(i); }
        return buf.buffer;
    }

    function bufferToBase64url(buffer) {
        var bytes = new Uint8Array(buffer);
        var str = '';
        for (var i = 0; i < bytes.length; i++) { str += String.fromCharCode(bytes[i]); }
        return btoa(str).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
    }

    // -------------------------------------------------------------------------
    // Convert server-sent options (base64url strings) to ArrayBuffers
    // -------------------------------------------------------------------------

    function prepareCreationOptions(opts) {
        var o = JSON.parse(JSON.stringify(opts));
        o.challenge  = base64urlToBuffer(o.challenge);
        o.user.id    = base64urlToBuffer(o.user.id);
        if (Array.isArray(o.excludeCredentials)) {
            o.excludeCredentials = o.excludeCredentials.map(function (c) {
                return { id: base64urlToBuffer(c.id), type: c.type };
            });
        }
        return o;
    }

    function prepareRequestOptions(opts) {
        var o = JSON.parse(JSON.stringify(opts));
        o.challenge = base64urlToBuffer(o.challenge);
        if (Array.isArray(o.allowCredentials)) {
            o.allowCredentials = o.allowCredentials.map(function (c) {
                return { id: base64urlToBuffer(c.id), type: c.type, transports: c.transports || [] };
            });
        }
        return o;
    }

    // -------------------------------------------------------------------------
    // Serialize credential for POST to PHP
    // -------------------------------------------------------------------------

    function serializeRegistrationCredential(cred) {
        return JSON.stringify({
            id:    cred.id,
            type:  cred.type,
            clientDataJSON:    bufferToBase64url(cred.response.clientDataJSON),
            attestationObject: bufferToBase64url(cred.response.attestationObject),
        });
    }

    function serializeAssertionCredential(cred) {
        return JSON.stringify({
            id:   cred.id,
            type: cred.type,
            clientDataJSON:    bufferToBase64url(cred.response.clientDataJSON),
            authenticatorData: bufferToBase64url(cred.response.authenticatorData),
            signature:         bufferToBase64url(cred.response.signature),
            userHandle:        cred.response.userHandle ? bufferToBase64url(cred.response.userHandle) : null,
        });
    }

    // -------------------------------------------------------------------------
    // AJAX helper (used by login page flows — admin flows use fetch via post())
    // -------------------------------------------------------------------------

    function post(action, data, nonce) {
        var params = new FormData();
        params.append('action', action);
        params.append('nonce', nonce || '');
        Object.keys(data).forEach(function (k) { params.append(k, data[k]); });

        return fetch(AJAX_URL, { method: 'POST', body: params })
            .then(function (r) { return r.json(); });
    }

    // -------------------------------------------------------------------------
    // Resolve the best available nonce
    // -------------------------------------------------------------------------

    function getNonce() {
        if (typeof ugPasskeys !== 'undefined' && ugPasskeys.nonce) {
            return ugPasskeys.nonce;
        }
        if (typeof ugAdmin !== 'undefined' && ugAdmin.nonce) {
            return ugAdmin.nonce;
        }
        // Last resort: look for a hidden nonce field the page may have rendered
        var field = document.querySelector('[name="_ultraguard_nonce"], [name="nonce"]');
        return field ? field.value : '';
    }

    // -------------------------------------------------------------------------
    // Button state helpers
    // -------------------------------------------------------------------------

    function setButtonBusy($btn, text) {
        $btn.prop('disabled', true).data('ug-original-html', $btn.html()).html(
            '<span class="dashicons dashicons-update" style="animation:spin 1s linear infinite;font-size:14px;width:14px;height:14px;margin-right:4px;"></span>' +
            (text || 'Please wait...')
        );
    }

    function restoreButton($btn) {
        var orig = $btn.data('ug-original-html');
        if (orig) { $btn.html(orig); }
        $btn.prop('disabled', false);
    }

    function showAdminMessage(msg, type) {
        if (typeof window.ultraGuard !== 'undefined' && window.ultraGuard.showMessage) {
            window.ultraGuard.showMessage(msg, type || 'success');
        } else {
            alert(msg);
        }
    }

    // -------------------------------------------------------------------------
    // Step 2 of registration: complete after the browser returns a credential
    // Called both from the admin dashboard flow and (if needed) from elsewhere.
    // -------------------------------------------------------------------------

    function completeRegistration(cred, nonce, $btn) {
        var deviceName = window.prompt(
            'Give this passkey a name (e.g. "MacBook Touch ID"):',
            'Security Key'
        ) || 'Security Key';

        return post('ultraguard_passkeys_complete_registration', {
            response:    serializeRegistrationCredential(cred),
            device_name: deviceName,
        }, nonce)
        .then(function (res) {
            if ($btn) { restoreButton($btn); }

            if (!res.success) {
                throw new Error(
                    (res.data && res.data.message) || 'Registration failed.'
                );
            }

            var msg = (res.data && res.data.message) || 'Passkey registered successfully!';
            showAdminMessage(msg, 'success');

            // Let the core response handler process any actions (datatable reload, etc.)
            if (typeof window.ultraGuard !== 'undefined' &&
                window.ultraGuard.responseHandler &&
                res.data && Array.isArray(res.data.actions)) {
                window.ultraGuard.responseHandler.processActions(res.data.actions, {});
            }

            return res;
        });
    }

    // -------------------------------------------------------------------------
    // Authentication flow (login page)
    // -------------------------------------------------------------------------

    function startAuthentication(nonce, userId) {
        return post('ultraguard_passkeys_authentication_options', { user_id: userId || 0 }, nonce)
            .then(function (res) {
                if (!res.success) { throw new Error(res.data && res.data.message ? res.data.message : 'Failed to get options.'); }
                var options = prepareRequestOptions(res.data.options);
                return navigator.credentials.get({ publicKey: options });
            })
            .then(function (cred) {
                if (!cred) { throw new Error('Authentication was cancelled.'); }
                return post('ultraguard_passkeys_complete_authentication', {
                    response: serializeAssertionCredential(cred),
                }, nonce);
            })
            .then(function (res) {
                if (!res.success) { throw new Error(res.data && res.data.message ? res.data.message : 'Authentication failed.'); }
                return res;
            });
    }

    // -------------------------------------------------------------------------
    // Admin dashboard: intercept the registration options response
    //
    // The "Register New Passkey" header button is a standard ultraguard-ajax-action
    // button (data-ajax-action="ultraguard_passkeys_registration_options").
    // The core framework fires the request and processes the response, then emits
    // 'ultraguard:response:handled'.  We intercept that event here, detect that
    // the response contains WebAuthn creation options, and continue the ceremony.
    // -------------------------------------------------------------------------

    $(document).on('ultraguard:response:handled', function (e, response, context) {
        // Only act on successful responses that carry WebAuthn creation options.
        if (!response || !response.success) { return; }
        if (!response.data || !response.data.options) { return; }

        var opts = response.data.options;
        // Confirm these look like PublicKeyCredentialCreationOptions
        if (!opts.challenge || !opts.rp || !opts.user) { return; }

        // Confirm the source button was the registration-options action.
        var $btn = context && context.sourceElement ? $(context.sourceElement) : $();
        if ($btn.data('ajax-action') !== 'ultraguard_passkeys_registration_options') { return; }

        // Browser support check
        if (!window.PublicKeyCredential) {
            showAdminMessage(I18N.error_browser || 'Your browser does not support passkeys.', 'error');
            return;
        }

        var nonce = getNonce();
        if (!nonce) {
            showAdminMessage('Security token not available. Please refresh the page.', 'error');
            return;
        }

        // Mark the button as busy during the async ceremony
        setButtonBusy($btn, 'Waiting for authenticator...');

        var creationOptions;
        try {
            creationOptions = prepareCreationOptions(opts);
        } catch (ex) {
            restoreButton($btn);
            showAdminMessage('Failed to prepare credential options: ' + ex.message, 'error');
            return;
        }

        navigator.credentials.create({ publicKey: creationOptions })
            .then(function (cred) {
                if (!cred) { throw new Error('Credential creation was cancelled.'); }
                setButtonBusy($btn, 'Saving...');
                return completeRegistration(cred, nonce, $btn);
            })
            .catch(function (err) {
                restoreButton($btn);
                if (err.name !== 'NotAllowedError') {
                    showAdminMessage(err.message || 'Registration failed.', 'error');
                }
            });
    });

    // -------------------------------------------------------------------------
    // Boot: bind login-page events once DOM is ready
    // -------------------------------------------------------------------------

    document.addEventListener('DOMContentLoaded', function () {

        // ---- Login page: "Sign in with Passkey" button ----
        var loginBtn = document.getElementById('ug-passkey-login-btn');
        if (loginBtn) {
            loginBtn.addEventListener('click', function () {
                if (!window.PublicKeyCredential) {
                    alert(I18N.error_browser || 'Your browser does not support passkeys.');
                    return;
                }
                var nonce = this.dataset.nonce || '';
                loginBtn.disabled = true;
                loginBtn.textContent = 'Verifying...';

                startAuthentication(nonce, 0)
                    .then(function (res) {
                        if (res.data && res.data.redirect_url) {
                            window.location.href = res.data.redirect_url;
                        }
                    })
                    .catch(function (err) {
                        loginBtn.disabled = false;
                        loginBtn.innerHTML = '<span class="dashicons dashicons-fingerprint" style="font-size:18px;width:18px;height:18px;"></span> Sign in with Passkey';
                        if (err.name !== 'NotAllowedError') {
                            alert(err.message || (I18N.error_auth || 'Authentication failed.'));
                        }
                    });
            });
        }

        // ---- Login page: recovery code toggle ----
        var recoveryLink = document.getElementById('ug-passkey-recovery-link');
        var recoveryForm = document.getElementById('ug-passkey-recovery-form');
        if (recoveryLink && recoveryForm) {
            recoveryLink.addEventListener('click', function (e) {
                e.preventDefault();
                recoveryForm.style.display = recoveryForm.style.display === 'none' ? '' : 'none';
            });
        }

        var recoverySubmit = document.getElementById('ug-passkey-recovery-submit');
        if (recoverySubmit) {
            recoverySubmit.addEventListener('click', function () {
                var input = document.getElementById('ug-passkey-recovery-input');
                var code  = input ? input.value.trim() : '';
                var nonce = this.dataset.nonce || '';

                if (!code) { return; }

                recoverySubmit.disabled = true;
                recoverySubmit.textContent = 'Verifying...';

                post('ultraguard_passkeys_verify_recovery_code', { code: code, user_id: 0 }, nonce)
                    .then(function (res) {
                        if (res.success && res.data && res.data.redirect_url) {
                            window.location.href = res.data.redirect_url;
                        } else {
                            recoverySubmit.disabled = false;
                            recoverySubmit.textContent = 'Verify Recovery Code';
                            alert(
                                (res.data && res.data.message)
                                || (I18N.error_recovery || 'Invalid recovery code.')
                            );
                        }
                    })
                    .catch(function () {
                        recoverySubmit.disabled = false;
                        recoverySubmit.textContent = 'Verify Recovery Code';
                        alert(I18N.error_recovery || 'Verification failed.');
                    });
            });
        }

        // ---- Settings page: "Generate Recovery Codes" result handler ----
        //
        // The Generate button is a standard ultraguard-ajax-action button.
        // We listen for 'ultraguard:response:handled' and display an inline
        // reminder alongside the download AjaxResponseBuilder already triggered.
        $(document).on('ultraguard:response:handled', function (e, response) {
            if (!response || !response.success) { return; }
            if (!response.data || !Array.isArray(response.data.codes)) { return; }

            var codes = response.data.codes;
            var $btn  = $('[data-ajax-action="ultraguard_passkeys_generate_recovery_codes"]');
            if (!$btn.length) { return; }

            $btn.closest('.ultraguard-card, .ultraguard-card-body, form')
                .find('.ug-recovery-codes-reminder').remove();

            var html = '<div class="ug-recovery-codes-reminder" '
                     + 'style="font-family:monospace;background:#f0fdf4;border:1px solid #bbf7d0;'
                     + 'border-radius:6px;padding:16px;margin-top:12px;">'
                     + '<p style="font-weight:700;margin:0 0 8px;color:#166534;">'
                     + '&#9989; Recovery codes generated — the JSON file has been downloaded. '
                     + 'Store it somewhere safe. Each code can only be used once.</p>'
                     + codes.map(function (c) {
                         return '<div style="padding:3px 0;color:#15803d;">' + escHtml(c) + '</div>';
                       }).join('')
                     + '<p style="margin:8px 0 0;font-size:12px;color:#6b7280;">'
                     + 'These codes will not be shown again after you leave this page.</p>'
                     + '</div>';

            $btn.after($(html));
        });

    });

    function escHtml(s) {
        return String(s)
            .replace(/&/g, '&amp;')
            .replace(/</g, '&lt;')
            .replace(/>/g, '&gt;')
            .replace(/"/g, '&quot;');
    }

}(jQuery));
