/**
 * SPA bootstrap + state machine. On load: check the session → render login or dashboard. The
 * dashboard polls the host list on an interval and refreshes after mutations. A 401 from any proxy
 * call bounces the operator back to the login screen (session expired).
 */
import { mount } from './dom.js'
import * as api from './api.js'
import { ApiError } from './api.js'
import { renderLogin, renderDashboard, pairingModal, confirmDialog, toast, type DashboardHandlers } from './views.js'
import type { HostView } from './api.js'

const POLL_INTERVAL_MS = 15_000

function rootEl(): HTMLElement {
  const root = document.getElementById('app')
  if (root === null) throw new Error('#app root not found')
  return root
}

let pollTimer: number | undefined

function stopPolling(): void {
  if (pollTimer !== undefined) {
    clearInterval(pollTimer)
    pollTimer = undefined
  }
}

/** True if an error is an auth failure that should bounce to login. */
function isUnauthorized(err: unknown): boolean {
  return err instanceof ApiError && err.status === 401
}

async function showLogin(): Promise<void> {
  stopPolling()
  const root = rootEl()
  const view = renderLogin(async (password) => {
    const errorNode = (view as HTMLElement & { errorNode?: HTMLElement }).errorNode
    try {
      await api.login(password)
      await showDashboard()
    } catch (err) {
      const msg = err instanceof ApiError && err.status === 429 ? 'Too many attempts. Wait and retry.' : err instanceof ApiError && err.status === 503 ? 'Panel not configured (no password set).' : 'Incorrect password.'
      if (errorNode) errorNode.textContent = msg
    }
  })
  mount(root, view)
}

async function loadHosts(): Promise<readonly HostView[]> {
  return api.getHosts()
}

async function refreshDashboard(root: HTMLElement, handlers: DashboardHandlers): Promise<void> {
  try {
    const hosts = await loadHosts()
    mount(root, renderDashboard(hosts, handlers))
  } catch (err) {
    if (isUnauthorized(err)) {
      await showLogin()
      return
    }
    toast(root, 'Failed to load hosts.', 'error')
  }
}

async function showDashboard(): Promise<void> {
  const root = rootEl()

  const handlers: DashboardHandlers = {
    onRefresh: () => void refreshDashboard(root, handlers),
    onLogout: async () => {
      stopPolling()
      await api.logout()
      await showLogin()
    },
    onNewCode: async () => {
      try {
        const artifacts = await api.createPairingCode()
        document.body.append(pairingModal(artifacts))
      } catch (err) {
        if (isUnauthorized(err)) return void showLogin()
        toast(root, 'Failed to create pairing code.', 'error')
      }
    },
    onRevoke: async (host) => {
      const ok = await confirmDialog(`Revoke host "${host.subdomain}"? This removes its tunnel access.`)
      if (!ok) return
      try {
        await api.revokeHost(host.hostId)
        toast(root, `Revoked ${host.subdomain}.`, 'info')
        await refreshDashboard(root, handlers)
      } catch (err) {
        if (isUnauthorized(err)) return void showLogin()
        toast(root, 'Failed to revoke host.', 'error')
      }
    },
  }

  await refreshDashboard(root, handlers)
  stopPolling()
  pollTimer = window.setInterval(() => void refreshDashboard(root, handlers), POLL_INTERVAL_MS)
}

async function boot(): Promise<void> {
  try {
    const authed = await api.getSession()
    await (authed ? showDashboard() : showLogin())
  } catch {
    await showLogin()
  }
}

void boot()
