/**
 * `GET /api/v1/markers` — the one route the `member` tier is about.
 *
 * The public snapshot, and nothing else. Private per-user markers are never
 * proxied through this box — an authenticated browser calls Workie directly with
 * its own token, so a private row never enters this process and cannot leave it.
 * CONTRACT.md §5. Every row served here has been through the provenance gate in
 * `markers/gate.ts`, which is what keeps a public snapshot from quietly becoming
 * a Publicly Used Derivative Database under ODbL.
 *
 * ### Why a configured feed is refused to anonymous callers
 *
 * `src/access.ts` has three tiers and, until this route, the middle one gated
 * nothing at all: `member` was a word in a type union that no server behaviour
 * corresponded to. A tier that never refuses anybody anything is not a tier, and
 * one that lives only in the client is worse — it is a UI hiding a control over
 * a body the API hands to whoever asks. **So: where a marker feed is configured,
 * it takes a session.** That refusal is the whole of what makes membership real,
 * it is enforced here rather than drawn in the browser, and there is
 * deliberately no `TERA_MARKERS_PUBLIC` escape hatch to undo it.
 *
 * Two consequences worth stating out loud:
 *
 * - **A box with no feed still answers 200 and an empty list.** `source: none`
 *   is the zero-config default and there is nothing there to protect; making a
 *   stranger sign in to be told "no markers" would fail the acceptance test in
 *   `boot.test.ts` and gain nobody anything.
 * - **A feed with `TERA_AUTH_MODE=none` is unreachable by everyone**, because
 *   nobody on such a box is ever authenticated. That is the fail-closed
 *   direction, it is the one private offices already take, and `config.ts`
 *   pushes a line into `degraded` saying so rather than letting an operator
 *   discover it from an empty map.
 *
 * ### 401 here, 404 for an office
 *
 * `offices.ts` answers 404 for a private office because a 403 there is an
 * enumeration oracle — walk the id space, read the status codes, learn every
 * tenant. Nothing is enumerable here: there is one feed, at a fixed path, and
 * `/api/v1/health` already publishes `sources.markers`, so whether this
 * deployment has markers is not a secret being kept. What the caller needs to
 * know is "sign in and ask again", which is what 401 with `WWW-Authenticate`
 * says. A 404 would be a lie told to protect nothing.
 */

import type { FastifyInstance } from "fastify";
import { publicCache } from "../cache.ts";
import type { ErrorBody } from "../../../src/server/wire.ts";
import type { Services } from "../services.ts";

const UNAUTHORIZED: ErrorBody = {
  error: "unauthorized",
  message: "The marker feed is for signed-in members.",
};

export function registerMarkers(app: FastifyInstance, services: Services): void {
  app.get("/api/v1/markers", async (req, reply) => {
    const { markers } = services.config;
    // Two ways to be public and they are not the same fact. `source === "none"`
    // is "there is nothing here to protect" — the body is the bundled sample
    // set. `access === "public"` is an operator saying this deployment's real
    // marker set is meant to be read by anyone. Both end up here; only the
    // second one is a decision, and `config.ts` announces it in `degraded[]`
    // so it cannot be made silently.
    if (markers.source === "none" || markers.access === "public") {
      const body = await services.markers.current();
      publicCache(req, reply, markers.ttlSeconds);
      return body;
    }

    const viewer = await services.auth.resolve(req);
    if (!viewer.authenticated) {
      return reply.code(401).header("www-authenticate", "Bearer").send(UNAUTHORIZED);
    }

    // No `publicCache`. This body took a credential to obtain, and a shared
    // cache holding it would hand one member's copy to the next caller — the
    // exact thing the fail-closed default in `cache.ts` exists to prevent.
    return services.markers.current();
  });
}
