/**
 * B6 (Phase-1 STAGING) — operator password gate → `POST /auth/mint` → §4.3 capability token.
 *
 * This is the STAGING login the deployed Phase-1 relay actually serves (`main-phase1.ts` enables
 * `POST /auth/mint`, B5/auth-mint.ts). It is a deliberate shortcut: a single shared operator
 * password (constant-time compared server-side) stands in for the full WebAuthn stack (Phase 2 →
 * T7 `login-passkey.ts`). The flow, per mint:
 *
 *   1. mint an EPHEMERAL browser DPoP key (WebCrypto Ed25519) and take its `jkt` thumbprint;
 *   2. `POST /auth/mint {password, jkt, subdomain}` (subdomain comes from the same-origin `location`,
 *      never a caller-supplied accountId — INV3); on 200 receive `{token}` bound to that `jkt`;
 *   3. hand the caller `{token, aud, dpop}` so it can open the WS carrying the token (+ a DPoP proof
 *      signed by the SAME key) — see entry/index-page.ts.
 *
 * Security (mirrors T3 login-password.ts):
 *  - the password is NEVER stored in localStorage and NEVER logged; all error text is set via
 *    `textContent` (never `innerHTML`); empty input disables submit (fail-fast at the boundary);
 *  - the request body carries only `{password, jkt, subdomain}` — a NAME + the client's own DPoP
 *    thumbprint, never an accountId/hostId (INV3, deny-by-default; the CP resolves identity);
 *  - the `{token}` response is Zod-validated before use (never a torn object).
 */
import { z } from 'zod'
import type { RelayWebConfig } from './config'
import { createDpopKey, type DpopKey } from './dpop'

/** Same-origin staging mint endpoint (B5 auth-mint.ts owns `POST /auth/mint`). */
const MINT_PATH = '/auth/mint'

/** The `{token}` response shape (lenient: ignore any additive fields, require a non-empty token). */
const MintResponseSchema = z.object({ token: z.string().min(1) })

/** What a successful staging login yields — everything the connect step needs, nothing more. */
export interface StagingSession {
  /** §4.3 capability token (a bearer secret — pass to the transport, NEVER log or persist it). */
  readonly token: string
  /** Tenant subdomain the token's `aud` is bound to; used to derive the DPoP `htu` (`htuFor`). */
  readonly aud: string
  /** The SAME ephemeral key whose `jkt` the token binds — signs the connect DPoP proof. */
  readonly dpop: DpopKey
}

export interface StagingLogin {
  /** Attempt a mint with `password`. 'ok' fires `onSuccess`; 'rejected' surfaces an error in the UI. */
  submit(password: string): Promise<'ok' | 'rejected'>
}

export interface StagingLoginDeps {
  readonly fetchImpl?: typeof fetch
  /** Invoked once per successful mint so the shell wrapper can open the terminal connection. */
  readonly onSuccess?: (session: StagingSession) => void
  /** DI seam: the DPoP key factory (defaults to WebCrypto); tests inject a deterministic fake. */
  readonly createKey?: () => Promise<DpopKey>
}

export function mountStagingLogin(
  root: HTMLElement,
  cfg: RelayWebConfig,
  deps: StagingLoginDeps = {},
): StagingLogin {
  const fetchImpl = deps.fetchImpl ?? fetch
  const createKey = deps.createKey ?? (() => createDpopKey())

  const form = document.createElement('form')
  form.className = 'login-form'

  const input = document.createElement('input')
  input.type = 'password'
  input.autocomplete = 'current-password'
  input.placeholder = 'Operator password'
  input.className = 'login-input'

  const button = document.createElement('button')
  button.type = 'submit'
  button.textContent = 'Connect'
  button.disabled = true // empty input → disabled (fail-fast)

  const error = document.createElement('p')
  error.className = 'login-error'
  error.setAttribute('role', 'alert')

  input.addEventListener('input', () => {
    button.disabled = input.value.length === 0
    error.textContent = '' // clear stale error on edit
  })

  async function submit(password: string): Promise<'ok' | 'rejected'> {
    if (password.length === 0) return 'rejected'

    let dpop: DpopKey
    try {
      dpop = await createKey()
    } catch {
      error.textContent = 'Secure crypto is unavailable in this browser.'
      return 'rejected'
    }

    let res: Response
    try {
      res = await fetchImpl(`${cfg.apiBase}${MINT_PATH}`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
        // INV3: only a NAME (subdomain) + the client's own DPoP thumbprint — never an accountId.
        body: JSON.stringify({ password, jkt: dpop.jkt, subdomain: cfg.subdomain }),
      })
    } catch {
      error.textContent = 'Network error — please retry.'
      return 'rejected'
    }

    if (!res.ok) {
      error.textContent = res.status === 401 ? 'Invalid operator password.' : 'Sign-in failed.'
      return 'rejected'
    }

    let body: unknown
    try {
      body = await res.json()
    } catch {
      error.textContent = 'Malformed response from the relay.'
      return 'rejected'
    }
    const parsed = MintResponseSchema.safeParse(body)
    if (!parsed.success) {
      error.textContent = 'Malformed response from the relay.'
      return 'rejected'
    }

    error.textContent = ''
    deps.onSuccess?.({ token: parsed.data.token, aud: cfg.subdomain, dpop })
    return 'ok'
  }

  form.addEventListener('submit', (ev) => {
    ev.preventDefault()
    void submit(input.value)
  })

  form.append(input, button, error)
  root.append(form)

  return { submit }
}
