/**
 * T7 (v0.10) — Passkey/WebAuthn login + step-up-BEFORE-a-session (EXPLORE §4e / §0-5).
 *
 * `login()`: fetch a challenge from P5 → `wa.authenticate` → post the assertion (P5 sets the
 * session). `stepUp(hostId)` runs a FRESH WebAuthn assertion immediately before a session is opened
 * — a stolen cookie alone cannot open a session (INV15 step-up). Passkey is primary and
 * phishing-resistant; there is NO SMS/OTP path (INDEX/EXPLORE §0-5: never SMS).
 *
 * `rpId` is server-authoritative: it arrives inside the challenge from P5 (resolved to the stable
 * ACCOUNT-LEVEL domain per §8 Open Question #5) and is passed straight through — the client never
 * hard-wires it to `location.hostname`, so one passkey spans all of an account's host subdomains.
 *
 * INTEGRATION POINT (BLOCKED item, §8 Q#5): the challenge/verify endpoints + the resolved `rpId`
 * value are owned by P5/P3. This module consumes them through the injected {@link PasskeyAuthApi}
 * seam rather than growing the frozen T2 `ApiClient` surface or guessing the resolution.
 */
import type { ApiClient } from './api-client'
import { WebAuthnError, type AuthenticationResult, type WebAuthnClient } from './webauthn'

/** P5-backed challenge/verify seam (NOT part of the frozen v0.8/v0.9 ApiClient — see header note). */
export interface PasskeyAuthApi {
  getLoginChallenge(): Promise<PublicKeyCredentialRequestOptions>
  verifyLogin(assertion: AuthenticationResult): Promise<'ok' | 'rejected'>
  getStepUpChallenge(hostId: string): Promise<PublicKeyCredentialRequestOptions>
  verifyStepUp(hostId: string, assertion: AuthenticationResult): Promise<'ok' | 'rejected'>
}

export interface PasskeyLogin {
  login(): Promise<'ok' | 'rejected'>
  /** BEFORE opening a session, not just at login — a fresh ceremony every call. */
  stepUp(hostId: string): Promise<'ok' | 'rejected'>
}

export function mountPasskeyLogin(
  root: HTMLElement,
  _api: ApiClient,
  wa: WebAuthnClient,
  authApi: PasskeyAuthApi,
): PasskeyLogin {
  const button = document.createElement('button')
  button.type = 'button'
  button.className = 'passkey-login'
  button.textContent = 'Sign in with a passkey'
  const error = document.createElement('p')
  error.className = 'passkey-error'
  error.setAttribute('role', 'alert')
  root.append(button, error)

  /** Run one assertion ceremony; a user-cancel (NotAllowedError) becomes a clean 'rejected'. */
  async function assert(
    getChallenge: () => Promise<PublicKeyCredentialRequestOptions>,
    verify: (a: AuthenticationResult) => Promise<'ok' | 'rejected'>,
  ): Promise<'ok' | 'rejected'> {
    let challenge: PublicKeyCredentialRequestOptions
    try {
      challenge = await getChallenge()
    } catch {
      error.textContent = 'Could not start the passkey ceremony.'
      return 'rejected'
    }
    let assertion: AuthenticationResult
    try {
      assertion = await wa.authenticate(challenge) // rpId comes from the server challenge, not location
    } catch (err) {
      if (err instanceof WebAuthnError && err.kind === 'cancelled') return 'rejected'
      error.textContent = 'Passkey authentication failed.'
      return 'rejected'
    }
    return verify(assertion)
  }

  const login = (): Promise<'ok' | 'rejected'> =>
    assert(
      () => authApi.getLoginChallenge(),
      (a) => authApi.verifyLogin(a),
    )

  const stepUp = (hostId: string): Promise<'ok' | 'rejected'> =>
    assert(
      () => authApi.getStepUpChallenge(hostId),
      (a) => authApi.verifyStepUp(hostId, a),
    )

  button.addEventListener('click', () => void login())

  // Surface EXACTLY the two capabilities — no phone/SMS/OTP field exists (structural, INV: never SMS).
  return { login, stepUp }
}
