/**
 * T6 (v0.9) — add-machine onboarding (pairing) from the browser's side (§4.5 ISSUE).
 *
 * `start()` requests a single-use, short-TTL pairing code (P3), displays the copy-paste command
 * `npx web-terminal-agent pair <code>` and a countdown to `expiresAt`, then polls `listHosts` until
 * a new host flips `●online` → fires `onPaired` ONCE. KPI: first-shell-in-under-2-minutes
 * (EXPLORE §6). After `expiresAt` it shows "expired, generate a new code" and STOPS polling — the
 * client never re-displays a redeemed/expired code, and never persists it (INV5: transient state).
 */
import type { ApiClient } from './api-client'
import { ApiError } from './errors'

const DEFAULT_POLL_MS = 3000
const AGENT_PAIR_CMD = 'npx web-terminal-agent pair'

export interface AddMachine {
  start(): Promise<void>
  dispose(): void
}

export interface AddMachineOpts {
  readonly pollMs?: number
  readonly onPaired?: (hostId: string) => void
  /** Injectable clock for deterministic expiry tests; defaults to `Date.now`. */
  readonly now?: () => number
}

export function mountAddMachine(root: HTMLElement, api: ApiClient, opts: AddMachineOpts = {}): AddMachine {
  const pollMs = opts.pollMs ?? DEFAULT_POLL_MS
  const now = opts.now ?? (() => Date.now())

  const cmd = document.createElement('code')
  cmd.className = 'pair-command'
  const status = document.createElement('p')
  status.className = 'pair-status'
  status.setAttribute('role', 'status')
  const error = document.createElement('p')
  error.className = 'pair-error'
  error.setAttribute('role', 'alert')
  root.append(cmd, status, error)

  let timer: ReturnType<typeof setInterval> | null = null
  let disposed = false
  let paired = false
  let knownHostIds: ReadonlySet<string> = new Set()

  function stopPolling(): void {
    if (timer) {
      clearInterval(timer)
      timer = null
    }
  }

  async function poll(expiresAtMs: number): Promise<void> {
    if (disposed || paired) return
    if (now() >= expiresAtMs) {
      stopPolling()
      status.textContent = 'Code expired — generate a new code.'
      cmd.textContent = '' // never re-display an expired/redeemed code
      return
    }
    try {
      const hosts = await api.listHosts()
      if (disposed || paired) return
      const fresh = hosts.find((h) => h.status === 'online' && !knownHostIds.has(h.hostId))
      if (fresh) {
        paired = true
        stopPolling()
        status.textContent = `Paired: ${fresh.subdomain} is online.`
        opts.onPaired?.(fresh.hostId)
      }
    } catch (err) {
      // A transient poll error is non-fatal; the loop retries until expiry (no infinite hang).
      error.textContent =
        err instanceof ApiError ? `Status check failed (${err.kind}).` : 'Status check failed.'
    }
  }

  async function start(): Promise<void> {
    error.textContent = ''
    // Snapshot existing hosts so we detect the NEW one that pairs in.
    try {
      knownHostIds = new Set((await api.listHosts()).map((h) => h.hostId))
    } catch {
      knownHostIds = new Set()
    }

    let code: string
    let expiresAt: string
    try {
      const issued = await api.requestPairingCode()
      code = issued.code
      expiresAt = issued.expiresAt
    } catch (err) {
      error.textContent =
        err instanceof ApiError ? `Could not issue a code (${err.kind}).` : 'Could not issue a code.'
      return
    }

    cmd.textContent = `${AGENT_PAIR_CMD} ${code}` // textContent-only, no injection
    status.textContent = 'Waiting for the host to pair…'
    const expiresAtMs = Date.parse(expiresAt)

    stopPolling()
    timer = setInterval(() => void poll(expiresAtMs), pollMs)
    void poll(expiresAtMs)
  }

  return {
    start,
    dispose(): void {
      disposed = true
      stopPolling()
    },
  }
}
