/*!
 * Brightery Secure 2FA WebAuthn/passkey browser logic
 * Human-readable distributed source included for plugin review and maintenance.
 */
(function () {
  function decodeBase64Url(input) {
    const base64 = input.replace(/-/g, '+').replace(/_/g, '/');
    const pad = base64.length % 4 ? '='.repeat(4 - (base64.length % 4)) : '';
    const binary = atob(base64 + pad);
    const bytes = new Uint8Array(binary.length);
    for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
    return bytes.buffer;
  }

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

  function findStatus(element) {
    return element && element.closest('.bs2fa-section, .bs2fa-passkey-login, .bs2fa-login-card, .bs2fa-card')
      ? element.closest('.bs2fa-section, .bs2fa-passkey-login, .bs2fa-login-card, .bs2fa-card').querySelector('.bs2fa-passkey-status')
      : document.querySelector('.bs2fa-passkey-status');
  }

  function passkeySupportState() {
    const secureHostnames = ['localhost', '127.0.0.1', '[::1]'];
    const secureContext = !!window.isSecureContext || secureHostnames.indexOf(window.location.hostname) !== -1;
    const hasPublicKey = typeof window.PublicKeyCredential !== 'undefined';
    const hasNavigatorCredentials = !!(navigator.credentials && typeof navigator.credentials.create === 'function' && typeof navigator.credentials.get === 'function');
    return {
      secureContext,
      hasPublicKey,
      hasNavigatorCredentials,
      supported: secureContext && hasPublicKey && hasNavigatorCredentials,
    };
  }

  function passkeySupportMessage() {
    const state = passkeySupportState();
    if (!state.secureContext) return BS2FA.strings.passkeyNeedsSecureContext || 'Passkeys require HTTPS or another secure browser context on this page.';
    if (!state.hasPublicKey || !state.hasNavigatorCredentials) return BS2FA.strings.passkeyContextUnavailable || BS2FA.strings.passkeyNotSupported || 'This browser/device does not support passkeys.';
    return BS2FA.strings.passkeyNotSupported || 'This browser/device does not support passkeys.';
  }

  function trustPreference() {
    const checked = document.querySelector('input[name="bs2fa_trust_device"]:checked');
    const label = document.querySelector('input[name="bs2fa_trust_device_label"]');
    return {
      enabled: !!checked,
      label: label ? (label.value || '').trim() : '',
    };
  }

  async function postForm(action, extra) {
    const body = new URLSearchParams();
    body.set('action', action);
    Object.entries(extra || {}).forEach(([key, val]) => body.set(key, val));
    const response = await fetch(BS2FA.ajaxUrl, {
      method: 'POST',
      credentials: 'same-origin',
      headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
      body: body.toString(),
    });
    return response.json();
  }

  async function postJSON(action, payload, includeNonce = true) {
    const url = BS2FA.ajaxUrl + '?action=' + encodeURIComponent(action) + (includeNonce && BS2FA.nonce ? '&nonce=' + encodeURIComponent(BS2FA.nonce) : '');
    const response = await fetch(url, {
      method: 'POST',
      credentials: 'same-origin',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(payload || {}),
    });

    const text = await response.text();
    try {
      return JSON.parse(text);
    } catch (e) {
      return { success: false, message: BS2FA.strings.genericError || 'Request failed.', raw: text };
    }
  }

  function normalizeRegistrationOptions(options) {
    options.challenge = decodeBase64Url(options.challenge);
    options.user.id = decodeBase64Url(options.user.id);
    if (Array.isArray(options.excludeCredentials)) {
      options.excludeCredentials = options.excludeCredentials.map((item) => ({
        ...item,
        id: decodeBase64Url(item.id),
      }));
    }
    return options;
  }

  function normalizeAssertionOptions(options) {
    options.challenge = decodeBase64Url(options.challenge);
    if (Array.isArray(options.allowCredentials)) {
      options.allowCredentials = options.allowCredentials.map((item) => ({
        ...item,
        id: decodeBase64Url(item.id),
      }));
    }
    return options;
  }

  function serializeRegistration(credential) {
    return {
      id: credential.id,
      type: credential.type,
      rawId: encodeBase64Url(credential.rawId),
      response: {
        clientDataJSON: encodeBase64Url(credential.response.clientDataJSON),
        attestationObject: encodeBase64Url(credential.response.attestationObject),
      },
    };
  }

  function serializeAssertion(credential) {
    return {
      id: credential.id,
      type: credential.type,
      rawId: encodeBase64Url(credential.rawId),
      response: {
        clientDataJSON: encodeBase64Url(credential.response.clientDataJSON),
        authenticatorData: encodeBase64Url(credential.response.authenticatorData),
        signature: encodeBase64Url(credential.response.signature),
        userHandle: credential.response.userHandle ? encodeBase64Url(credential.response.userHandle) : '',
      },
    };
  }

  async function addPasskey(button) {
    const status = findStatus(button);
    if (!passkeySupportState().supported) {
      if (status) status.textContent = passkeySupportMessage();
      return;
    }

    button.disabled = true;
    if (status) status.textContent = BS2FA.strings.preparingPasskey || 'Preparing passkey request...';

    try {
      const optionsResult = await postForm(BS2FA.registerAction, { nonce: BS2FA.nonce });
      if (!optionsResult.success) throw new Error(optionsResult.message || 'Could not get registration options.');

      const publicKey = normalizeRegistrationOptions(optionsResult.options);
      const credential = await navigator.credentials.create({ publicKey });
      const payload = serializeRegistration(credential);
      payload.label = 'Passkey ' + new Date().toLocaleString();
      const verifyResult = await postJSON(BS2FA.registerVerifyAction, payload, true);
      if (!verifyResult.success) throw new Error(verifyResult.message || 'Passkey verification failed.');

      if (status) status.textContent = BS2FA.strings.passkeyCreated || 'Passkey added.';
      window.location.reload();
    } catch (error) {
      if (status) status.textContent = error.message || BS2FA.strings.genericError;
    } finally {
      button.disabled = false;
    }
  }

  async function loginWithPasskey(button) {
    const status = findStatus(button);
    if (!passkeySupportState().supported) {
      if (status) status.textContent = passkeySupportMessage();
      return;
    }

    button.disabled = true;
    if (status) status.textContent = BS2FA.strings.waitingForPasskey || 'Waiting for your passkey...';

    try {
      const optionsResult = await postForm(BS2FA.challengeAction, {});
      if (!optionsResult.success) throw new Error(optionsResult.message || 'Could not get passkey login options.');

      const publicKey = normalizeAssertionOptions(optionsResult.options);
      const assertion = await navigator.credentials.get({ publicKey });
      const payload = serializeAssertion(assertion);
      const trust = trustPreference();
      payload.trustDevice = trust.enabled;
      payload.trustDeviceLabel = trust.label;
      const verifyResult = await postJSON(BS2FA.verifyAction, payload, false);
      if (!verifyResult.success) throw new Error(verifyResult.message || 'Passkey verification failed.');

      window.location.href = verifyResult.redirect || '/wp-admin/';
    } catch (error) {
      if (status) status.textContent = error.message || BS2FA.strings.genericError;
    } finally {
      button.disabled = false;
    }
  }

  async function removePasskey(button) {
    if (!confirm('Remove this passkey?')) return;
    const credentialId = button.getAttribute('data-credential');
    const result = await postForm(BS2FA.removePasskeyAction, { nonce: BS2FA.nonce, credential_id: credentialId });
    if (!result.success) {
      alert(result.message || 'Could not remove passkey.');
      return;
    }
    window.location.reload();
  }

  async function regenerateCodes() {
    const output = document.querySelector('.bs2fa-backup-output');
    const result = await postForm(BS2FA.regenBackupCodesAction, { nonce: BS2FA.nonce });
    if (!result.success) {
      alert(result.message || 'Could not generate backup codes.');
      return;
    }
    output.innerHTML = '<p><strong>Save these codes now. They are shown once.</strong></p><pre>' + result.codes.join('\n') + '</pre>';
  }

  async function copyText(button) {
    let target = null;
    const ref = button.getAttribute('data-copy-target');
    if (ref === 'previousElementSibling') {
      target = button.parentElement.previousElementSibling;
    } else if (ref) {
      target = document.querySelector(ref);
    }

    if (!target) return;

    const value = target.value || target.textContent || '';
    try {
      await navigator.clipboard.writeText(value);
      const original = button.textContent;
      button.textContent = BS2FA.strings.copied || 'Copied.';
      window.setTimeout(() => {
        button.textContent = original;
      }, 1200);
    } catch (e) {
      alert('Copy failed.');
    }
  }

  document.addEventListener('click', function (event) {
    const addButton = event.target.closest('.bs2fa-add-passkey');
    if (addButton) {
      event.preventDefault();
      addPasskey(addButton);
      return;
    }

    const loginButton = event.target.closest('.bs2fa-login-passkey');
    if (loginButton) {
      event.preventDefault();
      loginWithPasskey(loginButton);
      return;
    }

    const removeButton = event.target.closest('.bs2fa-remove-passkey');
    if (removeButton) {
      event.preventDefault();
      removePasskey(removeButton);
      return;
    }

    const regenButton = event.target.closest('.bs2fa-regenerate-codes');
    if (regenButton) {
      event.preventDefault();
      regenerateCodes(regenButton);
      return;
    }

    const copyButton = event.target.closest('.bs2fa-copy-btn');
    if (copyButton) {
      event.preventDefault();
      copyText(copyButton);
    }
  });
})();
