// WebAuthn ceremony bridge: converts the JSON options from the server into the
// ArrayBuffers navigator.credentials needs, runs the create/get ceremony, and posts
// the (base64url-encoded) result back to the Livewire component.
// Exposed as window.clusevWebauthn for the Settings + 2FA-challenge views.

const b64uToBuf = (s) => {
    const pad = s.length % 4 === 0 ? '' : '='.repeat(4 - (s.length % 4));
    const bin = atob(s.replace(/-/g, '+').replace(/_/g, '/') + pad);
    const buf = new Uint8Array(bin.length);
    for (let i = 0; i < bin.length; i++) buf[i] = bin.charCodeAt(i);
    return buf.buffer;
};

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

// Map server creation options (base64url strings) → PublicKeyCredentialCreationOptions.
const toCreateOptions = (o) => {
    const pk = { ...o };
    pk.challenge = b64uToBuf(o.challenge);
    pk.user = { ...o.user, id: b64uToBuf(o.user.id) };
    pk.excludeCredentials = (o.excludeCredentials || []).map((c) => ({ ...c, id: b64uToBuf(c.id) }));
    return pk;
};

const toRequestOptions = (o) => {
    const pk = { ...o };
    pk.challenge = b64uToBuf(o.challenge);
    pk.allowCredentials = (o.allowCredentials || []).map((c) => ({ ...c, id: b64uToBuf(c.id) }));
    return pk;
};

const encodeAttestation = (cred) => ({
    id: cred.id,
    type: cred.type,
    rawId: bufToB64u(cred.rawId),
    response: {
        clientDataJSON: bufToB64u(cred.response.clientDataJSON),
        attestationObject: bufToB64u(cred.response.attestationObject),
    },
    clientExtensionResults: cred.getClientExtensionResults?.() ?? {},
});

const encodeAssertion = (cred) => ({
    id: cred.id,
    type: cred.type,
    rawId: bufToB64u(cred.rawId),
    response: {
        clientDataJSON: bufToB64u(cred.response.clientDataJSON),
        authenticatorData: bufToB64u(cred.response.authenticatorData),
        signature: bufToB64u(cred.response.signature),
        userHandle: cred.response.userHandle ? bufToB64u(cred.response.userHandle) : null,
    },
    clientExtensionResults: cred.getClientExtensionResults?.() ?? {},
});

window.clusevWebauthn = {
    async register($wire) {
        try {
            const options = await $wire.options();
            const cred = await navigator.credentials.create({ publicKey: toCreateOptions(options) });
            await $wire.register(encodeAttestation(cred));
        } catch (e) {
            // user cancelled or the authenticator failed — surface nothing noisy
            console.warn('WebAuthn registration aborted', e);
        }
    },

    async login($wire) {
        try {
            const options = await $wire.assertionOptions();
            const cred = await navigator.credentials.get({ publicKey: toRequestOptions(options) });
            await $wire.verifyWebauthn(encodeAssertion(cred));
        } catch (e) {
            console.warn('WebAuthn login aborted', e);
        }
    },
};
