/* global ADVAPAFOLogin */
(function () {
    'use strict';

    var LAST_USED_STORAGE_KEY = 'advapafo_passkey_last_used_at';
    var LAST_USED_FRESHNESS_SECONDS = 90 * 24 * 60 * 60;

    // ── Base64url helpers ───────────────────────────────────────────────────

    function b64urlToBuffer(input) {
        if (input instanceof ArrayBuffer) {
            return input;
        }

        if (ArrayBuffer.isView(input)) {
            return input.buffer.slice(input.byteOffset, input.byteOffset + input.byteLength);
        }

        if (Array.isArray(input)) {
            return new Uint8Array(input).buffer;
        }

        if (input && typeof input === 'object' && Array.isArray(input.data)) {
            return new Uint8Array(input.data).buffer;
        }

        if (typeof input !== 'string') {
            throw new Error('Invalid credential format. Please refresh and try again.');
        }

        var raw = input.trim().replace(/\s+/g, '');
        if (!raw) {
            throw new Error('Invalid credential format. Please refresh and try again.');
        }

        // Accept both base64url and base64 encodings from varying server/client implementations.
        var base64 = raw.replace(/-/g, '+').replace(/_/g, '/');
        var pad = base64.length % 4;
        if (pad) base64 += '='.repeat(4 - pad);

        var binary;
        try {
            binary = atob(base64);
        } catch (err) {
            throw new Error('Invalid passkey data received. Please refresh and try again.');
        }

        var bytes = new Uint8Array(binary.length);
        for (var i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
        return bytes.buffer;
    }

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

    // ── DOM helpers ─────────────────────────────────────────────────────────

    function getMessageNode(btn) {
        var root = btn && btn.closest ? btn.closest('.advapafo-login-passkey-wrap, .advapafo-shortcode-login-wrap') : null;
        if (!root) {
            return document.getElementById('advapafo-passkey-login-message');
        }
        return root.querySelector('.advapafo-login-message') || document.getElementById('advapafo-passkey-login-message');
    }

    function isWpLoginContext(btn) {
        var form = document.getElementById('loginform');
        if (!form) {
            return false;
        }

        if (!btn || !btn.closest) {
            return true;
        }

        return !!btn.closest('#advapafo-login-passkey-block') || !!btn.closest('#loginform');
    }

    function getOrCreateWpLoginNoticeNode() {
        var loginWrap = document.getElementById('login');
        var form = document.getElementById('loginform');
        if (!loginWrap || !form || !form.parentNode) {
            return null;
        }

        var node = document.getElementById('advapafo-login-notice');
        if (!node) {
            node = document.createElement('div');
            node.id = 'advapafo-login-notice';
            node.className = 'notice notice-error';
            node.setAttribute('role', 'alert');
            node.setAttribute('aria-live', 'assertive');
            form.parentNode.insertBefore(node, form);
        }

        return node;
    }

    function setMessage(btn, text) {
        if (isWpLoginContext(btn)) {
            var wpNotice = getOrCreateWpLoginNoticeNode();
            if (!wpNotice) return;

            wpNotice.textContent = text || '';
            wpNotice.style.display = text ? '' : 'none';

            // Keep inline node empty/hidden on wp-login so messages only appear
            // in the native notice region above the form.
            var inlineNode = getMessageNode(btn);
            if (inlineNode) {
                inlineNode.textContent = '';
                inlineNode.classList.add('advapafo-is-hidden');
            }

            return;
        }

        var node = getMessageNode(btn);
        if (!node) return;
        node.textContent = text;
        node.classList.toggle('advapafo-is-hidden', !text);
    }

    function relocateLoginPasskeyBlock() {
        var form = document.getElementById('loginform');
        var block = document.getElementById('advapafo-login-passkey-block');
        if (!form || !block) return;

        var submitRow = form.querySelector('p.submit');
        if (submitRow && submitRow.parentNode === form) {
            submitRow.insertAdjacentElement('afterend', block);
            return;
        }

        form.appendChild(block);
    }

    function setButtonState(btn, busy) {
        if (!btn) return;
        btn.disabled = busy;
        btn.classList.toggle('advapafo-btn-busy', busy);
        if (busy) {
            btn.setAttribute('data-original-html', btn.innerHTML);
            btn.textContent = ADVAPAFOLogin.messages.signingIn || 'Signing in…';
        } else {
            var orig = btn.getAttribute('data-original-html');
            if (orig) btn.innerHTML = orig;
        }
    }

    function hydrateGetOptions(options) {
        var pk = options && options.publicKey ? options.publicKey : options;
        if (!pk || !pk.challenge) {
            throw new Error('Passkey options are incomplete. Please refresh and try again.');
        }

        pk.challenge = b64urlToBuffer(pk.challenge);

        if (Array.isArray(pk.allowCredentials)) {
            var hydratedAllow = [];
            pk.allowCredentials.forEach(function (item) {
                if (!item || !item.id) return;
                try {
                    item.id = b64urlToBuffer(item.id);
                    hydratedAllow.push(item);
                } catch (e) {
                    // Skip malformed credential IDs so a single legacy/bad record does not break all passkey sign-ins.
                }
            });

            if (hydratedAllow.length) {
                pk.allowCredentials = hydratedAllow;
            } else {
                delete pk.allowCredentials;
            }
        }

        options.publicKey = pk;
        return options;
    }

    function getLoginIdentifier() {
        var node = document.getElementById('user_login');
        return node ? (node.value || '').trim() : '';
    }

    function applyUsernameWebauthnAutocomplete() {
        var node = document.getElementById('user_login');
        if (!node) {
            return;
        }

        var current = (node.getAttribute('autocomplete') || '').trim();
        if (current.toLowerCase() === 'username webauthn') {
            return;
        }

        node.setAttribute('autocomplete', 'username webauthn');
    }

    function getRedirectTarget() {
        var redirectNode = document.getElementById('redirect_to');
        if (!redirectNode) {
            return '';
        }

        var value = typeof redirectNode.value === 'string' ? redirectNode.value.trim() : '';
        return value;
    }

    function getLastUsedPill(btn) {
        return btn ? btn.querySelector('.advapafo-passkey-last-used-pill') : null;
    }

    function setLastUsedPill(btn, shouldShow, label) {
        var pill = getLastUsedPill(btn);
        if (!pill) return;

        if (label) {
            pill.textContent = label;
        }

        pill.hidden = !shouldShow;
    }

    function toFriendlyErrorMessage(err) {
        var msg = (err && err.message) ? String(err.message) : '';
        if (msg && /resident credentials|discoverable credentials|empty ['"]?allowCredentials['"]?|allowCredentials lists are not supported/i.test(msg)) {
            return ADVAPAFOLogin.messages.usernamelessUnsupported || ADVAPAFOLogin.messages.genericError;
        }
        if (msg && /did not match the expected pattern/i.test(msg)) {
            return 'Your passkey request data was invalid. Please refresh this page and try again.';
        }
        return msg || ADVAPAFOLogin.messages.genericError;
    }

    function markLocalLastUsedNow() {
        try {
            window.localStorage.setItem(LAST_USED_STORAGE_KEY, String(Math.floor(Date.now() / 1000)));
        } catch (e) {
            // Ignore storage failures (private mode/restrictions).
        }
    }

    function hasRecentLocalLastUsed() {
        try {
            var raw = window.localStorage.getItem(LAST_USED_STORAGE_KEY);
            if (!raw) return false;

            var ts = parseInt(raw, 10);
            if (!Number.isFinite(ts) || ts <= 0) return false;

            var now = Math.floor(Date.now() / 1000);
            return (now - ts) <= LAST_USED_FRESHNESS_SECONDS;
        } catch (e) {
            return false;
        }
    }

    // ── AJAX ────────────────────────────────────────────────────────────────

    function parseJsonPayload(rawText) {
        try {
            return JSON.parse(rawText);
        } catch (e) {
            // Some hosts can prepend warnings/notices before JSON; recover by slicing object payload.
            var start = rawText.indexOf('{');
            var end = rawText.lastIndexOf('}');
            if (start !== -1 && end !== -1 && end > start) {
                return JSON.parse(rawText.slice(start, end + 1));
            }
            throw e;
        }
    }

    async function refreshLoginNonce() {
        try {
            var resp = await fetch(window.location.href, {
                method: 'GET',
                credentials: 'same-origin',
                cache: 'no-store',
            });

            var html = await resp.text();
            var match = html.match(/"nonce":"([a-f0-9]+)"/i);
            if (!match || !match[1]) {
                return false;
            }

            ADVAPAFOLogin.nonce = match[1];
            return true;
        } catch (e) {
            return false;
        }
    }

    async function postForm(data) {
        var resp = await fetch(ADVAPAFOLogin.ajaxUrl, {
            method: 'POST',
            credentials: 'same-origin',
            body: data,
        });

        var rawText = await resp.text();
        var payload;

        try {
            payload = parseJsonPayload(rawText);
        } catch (e) {
            throw new Error('Server returned non-JSON response. Check PHP/server logs and verify admin-ajax.php is reachable.');
        }

        if (!resp.ok) {
            throw new Error((payload && payload.data && payload.data.message) || ADVAPAFOLogin.messages.genericError);
        }

        return payload;
    }

    async function lookupLastUsedHint(identifier) {
        var data = new FormData();
        data.append('action', 'advapafo_get_last_used_hint');
        data.append('nonce', ADVAPAFOLogin.nonce);
        data.append('login', identifier);
        return postForm(data);
    }

    async function beginLoginRequest(identifier) {
        var beginData = new FormData();
        beginData.append('action', 'advapafo_begin_login');
        beginData.append('nonce',  ADVAPAFOLogin.nonce);

        if (identifier) {
            beginData.append('login', identifier);
        }

        var beginResp;
        try {
            beginResp = await postForm(beginData);
        } catch (e) {
            // Recover once if a stale login nonce triggered Invalid request.
            if (e && e.message === 'Invalid request.' && await refreshLoginNonce()) {
                beginData.set('nonce', ADVAPAFOLogin.nonce);
                beginResp = await postForm(beginData);
            } else {
                throw e;
            }
        }

        if (!beginResp || !beginResp.success || !beginResp.data || !beginResp.data.options || !beginResp.data.token) {
            throw new Error((beginResp && beginResp.data && beginResp.data.message) || ADVAPAFOLogin.messages.genericError);
        }

        return beginResp;
    }

    async function finishLoginRequest(credential, token) {
        var finishData = new FormData();
        finishData.append('action',            'advapafo_finish_login');
        finishData.append('nonce',             ADVAPAFOLogin.nonce);
        finishData.append('token',             token);
        finishData.append('id',                bufferToB64url(credential.rawId));
        finishData.append('clientDataJSON',    bufferToB64url(credential.response.clientDataJSON));
        finishData.append('authenticatorData', bufferToB64url(credential.response.authenticatorData));
        finishData.append('signature',         bufferToB64url(credential.response.signature));

        if (credential.response.userHandle) {
            finishData.append('userHandle', bufferToB64url(credential.response.userHandle));
        }

        var redirectTo = getRedirectTarget();
        if (redirectTo) {
            finishData.append('redirect_to', redirectTo);
        }

        var finishResp = await postForm(finishData);
        if (!finishResp || !finishResp.success || !finishResp.data || !finishResp.data.redirect) {
            throw new Error((finishResp && finishResp.data && finishResp.data.message) || ADVAPAFOLogin.messages.genericError);
        }

        markLocalLastUsedNow();
        return finishResp.data.redirect;
    }

    // ── Sign-in flow ─────────────────────────────────────────────────────────

    async function signInWithPasskey(btn) {
        setMessage(btn, '');

        var identifier = getLoginIdentifier();
        var beginResp = await beginLoginRequest(identifier);

        var options    = hydrateGetOptions(beginResp.data.options);
        var credential = await navigator.credentials.get(options);

        var redirectUrl = await finishLoginRequest(credential, beginResp.data.token);
        try {
            var parsed = new URL(redirectUrl, window.location.origin);
            if (parsed.origin !== window.location.origin) {
                throw new Error('Unexpected redirect origin');
            }
            window.location.href = parsed.href;
        } catch (e) {
            window.location.href = window.location.origin;
        }
    }

    // ── Init ────────────────────────────────────────────────────────────────

    function init() {
        applyUsernameWebauthnAutocomplete();
        relocateLoginPasskeyBlock();

        var buttons = Array.prototype.slice.call(document.querySelectorAll('#advapafo-signin-passkey, [data-advapafo-passkey-login-btn="1"]'));
        if (!buttons.length) return;
        var loginNode = document.getElementById('user_login');
        var passwordNode = document.getElementById('user_pass');
        var submitNode = document.getElementById('wp-submit');
        var loginForm = document.getElementById('loginform');
        var lastLookupRequestId = 0;
        var lookupTimer = 0;
        var conditionalController = null;
        var conditionalFlowActive = false;

        function enforceConditionalUiUiState() {
            if (!ADVAPAFOLogin.conditionalUi || !document.body || !document.body.classList.contains('login')) {
                return;
            }

            var separator = document.querySelector('#advapafo-login-passkey-block .advapafo-login-separator');
            if (separator && separator.parentNode) {
                separator.parentNode.removeChild(separator);
            }

            buttons.forEach(function (btn) {
                btn.classList.add('advapafo-passkey-btn--hidden');
                btn.setAttribute('hidden', 'hidden');
                btn.setAttribute('aria-hidden', 'true');
                btn.style.setProperty('display', 'none', 'important');
                btn.style.setProperty('visibility', 'hidden', 'important');
                btn.style.setProperty('opacity', '0', 'important');
            });
        }

        enforceConditionalUiUiState();

        // Graceful degradation for unsupported browsers
        if (!window.PublicKeyCredential || !navigator.credentials || !navigator.credentials.get) {
            buttons.forEach(function (btn) {
                btn.disabled = true;
                btn.classList.add('advapafo-btn-disabled');
                btn.setAttribute('aria-disabled', 'true');
                btn.title = ADVAPAFOLogin.messages.notSupported;
                setMessage(btn, ADVAPAFOLogin.messages.notSupported);
                setLastUsedPill(btn, false, '');
            });
            return;
        }

        buttons.forEach(function (btn) {
            btn.classList.remove('advapafo-btn-disabled');
            btn.removeAttribute('aria-disabled');
        });

        function updateLastUsedPill() {
            var identifier = getLoginIdentifier();
            var fallbackLabel = 'Last used';

            if (buttons.length) {
                var initialPill = getLastUsedPill(buttons[0]);
                if (initialPill && initialPill.textContent && initialPill.textContent.trim()) {
                    fallbackLabel = initialPill.textContent.trim();
                }
            }

            if (!identifier) {
                buttons.forEach(function (btn) {
                    setLastUsedPill(btn, hasRecentLocalLastUsed(), fallbackLabel);
                });
                return;
            }

            var requestId = ++lastLookupRequestId;
            lookupLastUsedHint(identifier)
                .then(function (resp) {
                    if (requestId !== lastLookupRequestId) return;

                    var data = (resp && resp.success && resp.data) ? resp.data : null;
                    var shouldShow = !!(data && data.showPill);
                    var label = (data && typeof data.label === 'string' && data.label.trim()) ? data.label.trim() : '';

                    buttons.forEach(function (btn) {
                        setLastUsedPill(btn, shouldShow, label);
                    });
                })
                .catch(function () {
                    if (requestId !== lastLookupRequestId) return;
                    buttons.forEach(function (btn) {
                        setLastUsedPill(btn, false, '');
                    });
                });
        }

        function queueLastUsedLookup() {
            if (lookupTimer) {
                window.clearTimeout(lookupTimer);
            }
            lookupTimer = window.setTimeout(updateLastUsedPill, 220);
        }

        if (loginNode) {
            loginNode.addEventListener('input', queueLastUsedLookup);
            loginNode.addEventListener('change', queueLastUsedLookup);
            loginNode.addEventListener('blur', queueLastUsedLookup);
        }

        updateLastUsedPill();
        window.setTimeout(updateLastUsedPill, 400);
        window.setTimeout(updateLastUsedPill, 1400);

        function abortConditionalMediation() {
            if (conditionalController) {
                try {
                    conditionalController.abort();
                } catch (e) {
                    // No-op.
                }
                conditionalController = null;
            }
        }

        async function maybeStartConditionalUi(primaryBtn) {
            var conditionalUiEnabled = !!ADVAPAFOLogin.conditionalUi;
            if (!conditionalUiEnabled || !loginForm || !document.body || !document.body.classList.contains('login')) {
                return;
            }

            if (!window.PublicKeyCredential || !navigator.credentials || !navigator.credentials.get) {
                return;
            }

            if (typeof window.PublicKeyCredential.isConditionalMediationAvailable !== 'function') {
                return;
            }

            if (conditionalFlowActive) {
                return;
            }

            var mediationAvailable = false;
            try {
                mediationAvailable = await window.PublicKeyCredential.isConditionalMediationAvailable();
            } catch (e) {
                mediationAvailable = false;
            }

            if (!mediationAvailable) {
                return;
            }

            conditionalFlowActive = true;
            try {
                var beginResp = await beginLoginRequest('');
                var options = hydrateGetOptions(beginResp.data.options);
                if (!options.publicKey || typeof options.publicKey !== 'object') {
                    return;
                }

                // Conditional UI relies on discoverable credentials only.
                options.publicKey.allowCredentials = [];

                conditionalController = typeof window.AbortController === 'function' ? new window.AbortController() : null;

                var credentialRequest = {
                    publicKey: options.publicKey,
                    mediation: 'conditional',
                };

                if (conditionalController) {
                    credentialRequest.signal = conditionalController.signal;
                }

                var credential = await navigator.credentials.get(credentialRequest);
                if (!credential) {
                    return;
                }

                setButtonState(primaryBtn, true);
                var redirectUrl = await finishLoginRequest(credential, beginResp.data.token);
                var parsed = new URL(redirectUrl, window.location.origin);
                if (parsed.origin !== window.location.origin) {
                    throw new Error('Unexpected redirect origin');
                }
                window.location.href = parsed.href;
            } catch (err) {
                if (err && (err.name === 'AbortError' || err.name === 'NotAllowedError')) {
                    return;
                }
                // Keep manual passkey and password paths available on background failures.
            } finally {
                conditionalFlowActive = false;
                conditionalController = null;
                setButtonState(primaryBtn, false);
            }
        }

        if (passwordNode) {
            passwordNode.addEventListener('focus', abortConditionalMediation);
            passwordNode.addEventListener('input', abortConditionalMediation);
            passwordNode.addEventListener('keydown', abortConditionalMediation);
        }

        if (submitNode) {
            submitNode.addEventListener('click', abortConditionalMediation);
        }

        if (loginForm) {
            loginForm.addEventListener('submit', abortConditionalMediation, true);
        }

        maybeStartConditionalUi(buttons[0]);

        if (loginNode) {
            loginNode.addEventListener('focus', function () {
                maybeStartConditionalUi(buttons[0]);
            });

            loginNode.addEventListener('click', function () {
                maybeStartConditionalUi(buttons[0]);
            });
        }

        buttons.forEach(function (btn) {
            btn.addEventListener('click', function (e) {
                e.preventDefault();
                abortConditionalMediation();
                setButtonState(btn, true);
                signInWithPasskey(btn)
                    .catch(function (err) {
                        // Ignore user-cancelled gestures silently
                        if (err && err.name === 'NotAllowedError') {
                            setMessage(btn, '');
                        } else {
                            setMessage(btn, toFriendlyErrorMessage(err));
                        }
                    })
                    .finally(function () {
                        setButtonState(btn, false);
                    });
                });
        });
    }

    document.addEventListener('DOMContentLoaded', init);
})();
