type PublicKeyCredentialConstructorWithJsonHelpers =
  typeof PublicKeyCredential & {
    parseCreationOptionsFromJSON?: (
      options: unknown,
    ) => PublicKeyCredentialCreationOptions;
    parseRequestOptionsFromJSON?: (
      options: unknown,
    ) => PublicKeyCredentialRequestOptions;
  };

export type PasskeyAuthenticatorAttachment = "platform" | "cross-platform";

function assertRecord(value: unknown, label: string): Record<string, unknown> {
  if (!value || typeof value !== "object" || Array.isArray(value)) {
    throw new Error(`${label} must be an object.`);
  }
  return value as Record<string, unknown>;
}

function decodeBase64Url(input: string): Uint8Array {
  const normalized = input.replace(/-/g, "+").replace(/_/g, "/");
  const padding = normalized.length % 4;
  const padded =
    padding === 0 ? normalized : normalized + "=".repeat(4 - padding);
  const binary = atob(padded);
  const bytes = new Uint8Array(binary.length);
  for (let index = 0; index < binary.length; index += 1) {
    bytes[index] = binary.charCodeAt(index);
  }
  return bytes;
}

function encodeBase64Url(value: ArrayBuffer | null): string | null {
  if (!value) return null;
  const bytes = new Uint8Array(value);
  let binary = "";
  for (const byte of bytes) {
    binary += String.fromCharCode(byte);
  }
  return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
}

function parseCreationOptions(
  optionsJson: unknown,
): PublicKeyCredentialCreationOptions {
  const credentialCtor =
    PublicKeyCredential as PublicKeyCredentialConstructorWithJsonHelpers;
  if (typeof credentialCtor.parseCreationOptionsFromJSON === "function") {
    return credentialCtor.parseCreationOptionsFromJSON(optionsJson);
  }

  const raw = assertRecord(optionsJson, "Passkey registration options");
  const user = assertRecord(raw.user, "Passkey registration user");
  const mapped: Record<string, unknown> = {
    ...raw,
    challenge: decodeBase64Url(String(raw.challenge)).buffer,
    user: {
      ...user,
      id: decodeBase64Url(String(user.id)).buffer,
    },
  };

  if (Array.isArray(raw.excludeCredentials)) {
    mapped.excludeCredentials = raw.excludeCredentials.map((descriptor) => {
      const value = assertRecord(descriptor, "excludeCredentials entry");
      return {
        ...value,
        id: decodeBase64Url(String(value.id)).buffer,
      };
    });
  }

  return mapped as unknown as PublicKeyCredentialCreationOptions;
}

function parseRequestOptions(optionsJson: unknown): PublicKeyCredentialRequestOptions {
  const credentialCtor =
    PublicKeyCredential as PublicKeyCredentialConstructorWithJsonHelpers;
  if (typeof credentialCtor.parseRequestOptionsFromJSON === "function") {
    return credentialCtor.parseRequestOptionsFromJSON(optionsJson);
  }

  const raw = assertRecord(optionsJson, "Passkey authentication options");
  const mapped: Record<string, unknown> = {
    ...raw,
    challenge: decodeBase64Url(String(raw.challenge)).buffer,
  };

  if (Array.isArray(raw.allowCredentials)) {
    mapped.allowCredentials = raw.allowCredentials.map((descriptor) => {
      const value = assertRecord(descriptor, "allowCredentials entry");
      return {
        ...value,
        id: decodeBase64Url(String(value.id)).buffer,
      };
    });
  }

  return mapped as unknown as PublicKeyCredentialRequestOptions;
}

function serializeRegistrationCredential(
  credential: PublicKeyCredential,
): Record<string, unknown> {
  const response = credential.response as AuthenticatorAttestationResponse;
  const transports =
    typeof response.getTransports === "function"
      ? response.getTransports()
      : undefined;

  return {
    id: credential.id,
    rawId: encodeBase64Url(credential.rawId),
    type: credential.type,
    response: {
      clientDataJSON: encodeBase64Url(response.clientDataJSON),
      attestationObject: encodeBase64Url(response.attestationObject),
      ...(Array.isArray(transports) && transports.length > 0
        ? { transports }
        : {}),
    },
    clientExtensionResults: credential.getClientExtensionResults(),
  };
}

function serializeAuthenticationCredential(
  credential: PublicKeyCredential,
): Record<string, unknown> {
  const response = credential.response as AuthenticatorAssertionResponse;
  return {
    id: credential.id,
    rawId: encodeBase64Url(credential.rawId),
    type: credential.type,
    response: {
      clientDataJSON: encodeBase64Url(response.clientDataJSON),
      authenticatorData: encodeBase64Url(response.authenticatorData),
      signature: encodeBase64Url(response.signature),
      userHandle: encodeBase64Url(response.userHandle),
    },
    clientExtensionResults: credential.getClientExtensionResults(),
  };
}

export function isPasskeyAvailableInBrowser(): boolean {
  return typeof window !== "undefined" && typeof PublicKeyCredential !== "undefined";
}

export async function createPasskeyCredential(
  optionsJson: unknown,
  authenticatorAttachment?: PasskeyAuthenticatorAttachment,
): Promise<Record<string, unknown>> {
  if (!isPasskeyAvailableInBrowser()) {
    throw new Error("Passkeys are not available in this browser.");
  }

  const publicKey = parseCreationOptions(optionsJson);
  if (authenticatorAttachment) {
    publicKey.authenticatorSelection = {
      ...(publicKey.authenticatorSelection ?? {}),
      authenticatorAttachment,
    };
  }

  const credential = await navigator.credentials.create({ publicKey });
  if (!(credential instanceof PublicKeyCredential)) {
    throw new Error("Passkey creation did not return a public-key credential.");
  }
  return serializeRegistrationCredential(credential);
}

export async function getPasskeyCredential(
  optionsJson: unknown,
): Promise<Record<string, unknown>> {
  if (!isPasskeyAvailableInBrowser()) {
    throw new Error("Passkeys are not available in this browser.");
  }

  const publicKey = parseRequestOptions(optionsJson);
  const credential = await navigator.credentials.get({ publicKey });
  if (!(credential instanceof PublicKeyCredential)) {
    throw new Error("Passkey authentication did not return a public-key credential.");
  }
  return serializeAuthenticationCredential(credential);
}
