// NodeInfo discovery (RFC: nodeinfo.diaspora.software).
//
// Both Pixelfed and Mastodon expose `/.well-known/nodeinfo`, which links to
// the actual schema document at a server-defined URL. The discovery flow
// here covers both servers — a single round-trip to `.well-known/nodeinfo`
// followed by a fetch of the linked schema URL works for Pixelfed's
// `/api/nodeinfo/2.0.json` and Mastodon's `/nodeinfo/2.0` alike.
//
// Two consumers today:
//   - `loginPreflightCheck` (login flow, classifies the server before OAuth).
//   - `classifyRemoteHost` (per-author probe used by the Mastodon-active home
//     feed to detect Mobilizon actors and badge them as events).
//
// Each call costs at most two short-timeout round-trips: discovery → linked
// schema URL. A 404 / non-OK / parse error at either step returns null —
// callers translate that into their own failure mode.

import { getJsonWithTimeout } from 'src/requests'

export type NodeInfoResponse = {
  software?: {
    name?: string
    version?: string
  }
  metadata?: {
    config?: {
      features?: {
        halflight?: Record<string, boolean>
      }
    }
    halflight?: {
      features?: Record<string, boolean>
    }
  }
}

type NodeInfoLink = { rel?: string; href?: string }
type NodeInfoDiscovery = { links?: NodeInfoLink[] }

const DISCOVERY_TIMEOUT_MS = 5000

/**
 * Resolve a host's nodeinfo schema document via the well-known discovery doc.
 * Returns null on any failure (network, non-OK response, malformed JSON, no
 * usable schema link). Prefers the highest 2.x schema advertised; falls back
 * to the first link when `rel` is absent.
 */
export async function discoverNodeInfo(host: string): Promise<NodeInfoResponse | null> {
  let discovery: NodeInfoDiscovery
  try {
    const res = await getJsonWithTimeout(
      `https://${host}/.well-known/nodeinfo`,
      undefined,
      false,
      undefined,
      DISCOVERY_TIMEOUT_MS
    )
    if (!res.ok) return null
    discovery = (await res.json()) as NodeInfoDiscovery
  } catch {
    return null
  }

  // Prefer the 2.1 schema, then 2.0; fall back to the first link if `rel` is
  // missing entirely (some servers return a single-link doc without it).
  const link =
    discovery.links?.find((l) => l.rel?.endsWith('/schema/2.1')) ??
    discovery.links?.find((l) => l.rel?.endsWith('/schema/2.0')) ??
    discovery.links?.[0]
  if (!link?.href) return null

  try {
    const res = await getJsonWithTimeout(
      link.href,
      undefined,
      false,
      undefined,
      DISCOVERY_TIMEOUT_MS
    )
    if (!res.ok) return null
    return (await res.json()) as NodeInfoResponse
  } catch {
    return null
  }
}
