/* DictIA WebAuthn client (B-2.6).
 * No external dependencies. Wraps the navigator.credentials API and
 * exchanges base64url-encoded payloads with the Flask backend at
 * /2fa/passkey/* endpoints.
 *
 * Exports window.DictIAWebAuthn = { wireRegisterButton, wireAuthButton }.
 */
(function (global) {
  'use strict';

  // --- base64url helpers (no padding) -----------------------------------

  function b64urlToBuffer(s) {
    if (!s) return new ArrayBuffer(0);
    const pad = '='.repeat((4 - (s.length % 4)) % 4);
    const b64 = (s + pad).replace(/-/g, '+').replace(/_/g, '/');
    const binary = atob(b64);
    const bytes = new Uint8Array(binary.length);
    for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
    return bytes.buffer;
  }

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

  // --- Options decoding (server sends b64url; navigator.credentials needs ArrayBuffer)

  function decodeRegistrationOptions(o) {
    return Object.assign({}, o, {
      challenge: b64urlToBuffer(o.challenge),
      user: Object.assign({}, o.user, { id: b64urlToBuffer(o.user.id) }),
      excludeCredentials: (o.excludeCredentials || []).map(function (c) {
        return Object.assign({}, c, { id: b64urlToBuffer(c.id) });
      }),
    });
  }

  function decodeAuthenticationOptions(o) {
    return Object.assign({}, o, {
      challenge: b64urlToBuffer(o.challenge),
      allowCredentials: (o.allowCredentials || []).map(function (c) {
        return Object.assign({}, c, { id: b64urlToBuffer(c.id) });
      }),
    });
  }

  // --- Credential encoding (ArrayBuffer fields → b64url for JSON) -------

  function encodeRegistrationCredential(cred) {
    return {
      id: cred.id,
      rawId: bufferToB64url(cred.rawId),
      type: cred.type,
      response: {
        clientDataJSON: bufferToB64url(cred.response.clientDataJSON),
        attestationObject: bufferToB64url(cred.response.attestationObject),
        transports: typeof cred.response.getTransports === 'function'
          ? cred.response.getTransports() : [],
      },
      clientExtensionResults: cred.getClientExtensionResults
        ? cred.getClientExtensionResults() : {},
    };
  }

  function encodeAuthenticationAssertion(cred) {
    return {
      id: cred.id,
      rawId: bufferToB64url(cred.rawId),
      type: cred.type,
      response: {
        clientDataJSON: bufferToB64url(cred.response.clientDataJSON),
        authenticatorData: bufferToB64url(cred.response.authenticatorData),
        signature: bufferToB64url(cred.response.signature),
        userHandle: cred.response.userHandle
          ? bufferToB64url(cred.response.userHandle) : null,
      },
      clientExtensionResults: cred.getClientExtensionResults
        ? cred.getClientExtensionResults() : {},
    };
  }

  // --- HTTP helper ------------------------------------------------------

  async function postJson(url, body, csrfToken) {
    const headers = { 'Content-Type': 'application/json' };
    if (csrfToken) headers['X-CSRFToken'] = csrfToken;
    const r = await fetch(url, {
      method: 'POST',
      headers: headers,
      body: JSON.stringify(body || {}),
      credentials: 'same-origin',
    });
    let data = null;
    try { data = await r.json(); } catch (_) {}
    return { ok: r.ok, status: r.status, data: data };
  }

  // --- Public API: wire enrolment button --------------------------------

  function wireRegisterButton(cfg) {
    const btn = document.getElementById(cfg.buttonId);
    const labelEl = cfg.labelInputId ? document.getElementById(cfg.labelInputId) : null;
    const statusEl = cfg.statusElementId ? document.getElementById(cfg.statusElementId) : null;
    if (!btn) return;
    btn.addEventListener('click', async function () {
      if (statusEl) statusEl.textContent = 'Préparation...';
      btn.disabled = true;
      try {
        if (!('credentials' in navigator) || !navigator.credentials.create) {
          throw new Error('Votre navigateur ne supporte pas les passkeys.');
        }
        const beginRes = await postJson(cfg.beginUrl, {}, cfg.csrfToken);
        if (!beginRes.ok) {
          throw new Error((beginRes.data && beginRes.data.message) || 'Erreur de préparation');
        }
        const opts = decodeRegistrationOptions(beginRes.data);
        if (statusEl) statusEl.textContent = 'Suivez les instructions de votre authentificateur...';
        const cred = await navigator.credentials.create({ publicKey: opts });
        const encoded = encodeRegistrationCredential(cred);
        const finishRes = await postJson(
          cfg.finishUrl,
          { response: encoded, label: labelEl ? labelEl.value : '' },
          cfg.csrfToken
        );
        if (!finishRes.ok) {
          throw new Error((finishRes.data && finishRes.data.message) || 'Échec de la vérification');
        }
        if (statusEl) statusEl.textContent = 'Passkey enregistrée. Rafraîchissement...';
        setTimeout(function () { window.location.reload(); }, 800);
      } catch (e) {
        if (statusEl) statusEl.textContent = 'Erreur : ' + (e.message || e);
        btn.disabled = false;
      }
    });
  }

  // --- Public API: wire login button ------------------------------------

  function wireAuthButton(cfg) {
    const btn = document.getElementById(cfg.buttonId);
    const statusEl = cfg.statusElementId ? document.getElementById(cfg.statusElementId) : null;
    if (!btn) return;
    btn.addEventListener('click', async function () {
      if (statusEl) statusEl.textContent = 'Préparation...';
      btn.disabled = true;
      try {
        if (!('credentials' in navigator) || !navigator.credentials.get) {
          throw new Error('Votre navigateur ne supporte pas les passkeys.');
        }
        const beginRes = await postJson(cfg.beginUrl, {}, cfg.csrfToken);
        if (!beginRes.ok) {
          throw new Error((beginRes.data && beginRes.data.message) || 'Erreur de préparation');
        }
        const opts = decodeAuthenticationOptions(beginRes.data);
        if (statusEl) statusEl.textContent = 'Confirmez avec votre authentificateur...';
        const cred = await navigator.credentials.get({ publicKey: opts });
        const encoded = encodeAuthenticationAssertion(cred);
        const finishRes = await postJson(cfg.finishUrl, { response: encoded }, cfg.csrfToken);
        if (!finishRes.ok) {
          throw new Error((finishRes.data && finishRes.data.message) || 'Échec de la vérification');
        }
        window.location.assign((finishRes.data && finishRes.data.redirect) || '/');
      } catch (e) {
        if (statusEl) statusEl.textContent = 'Erreur : ' + (e.message || e);
        btn.disabled = false;
      }
    });
  }

  global.DictIAWebAuthn = {
    wireRegisterButton: wireRegisterButton,
    wireAuthButton: wireAuthButton,
  };
})(window);
