/**
 * `GET /api/v1/offices/:id/presence` — the route that makes signing in mean
 * something.
 *
 * Until this existed, `member` and `anon` were told apart by the client and by
 * nothing else *inside the office*: `access.ts` picked `officeDepth`, the scene
 * built a presence layer at full depth, and then nobody ever called
 * `setPresence`, so both tiers rendered the identical empty building. A tier
 * that changes nothing you can see is not a tier, and — exactly as
 * `routes/markers.ts` argues for the city — a distinction drawn only in the
 * browser is a UI hiding a control over a body the API hands to whoever asks.
 * This is where the refusal actually happens.
 *
 * ### Occupancy always takes a session. Markers only sometimes do.
 *
 * `markers.ts` serves its feed to anonymous callers when no feed is configured,
 * on the grounds that there is nothing there to protect. The same reasoning does
 * **not** transfer, and the difference is worth stating rather than inferring:
 * a marker is a company at an address and a presence is a person at a desk. So
 * the auth check here is unconditional. A box with no roster still answers 200
 * and an empty list — see below — but it answers it to a caller who signed in.
 *
 * ### 401 here, and why that is not the enumeration oracle §6 forbids
 *
 * CONTRACT.md §6 says a private office answers 404 rather than 403 so the id
 * space cannot be walked for a tenant list, and `offices.ts` implements exactly
 * that. This route resolves the viewer **first**, before it has looked at the id
 * at all, so an anonymous caller gets the same 401 for every id in the world —
 * real, private, or invented. Nothing is learned. Only a caller who has already
 * signed in reaches the point where ids differ, and at that point they can
 * already read `/offices/:id`, so this leaks nothing that route does not.
 *
 * Doing it in the other order is the bug this comment exists to prevent: check
 * the office first and an anonymous 404-for-unknown against 401-for-known
 * distinguishes them perfectly, which is the oracle wearing a different status
 * code.
 *
 * ### An office with no roster is 200 and empty, never 404
 *
 * `store.ts` cannot fail, and that is deliberate. "This office does not exist"
 * and "nobody has told me who is in this office" are different facts with
 * different fixes — one is a wrong URL, the other is an unset
 * `TERA_PRESENCE_DIR` — and collapsing them into one 404 sends an operator
 * looking for the wrong problem. The empty building is also the correct render:
 * it is what the office looks like before anyone arrives.
 */

import type { FastifyInstance } from "fastify";
import { bundledOffice } from "../media/index.ts";
import type { ErrorBody } from "../../../src/server/wire.ts";
import type { Services } from "../services.ts";

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

const NOT_FOUND: ErrorBody = { error: "not_found", message: "No such office." };

export function registerPresence(app: FastifyInstance, services: Services): void {
  app.get<{ Params: { id: string } }>("/api/v1/offices/:id/presence", async (req, reply) => {
    // Viewer first, before the id is looked at. See the header — the order is
    // the security property, not an implementation detail.
    const viewer = await services.auth.resolve(req);
    if (!viewer.authenticated) {
      return reply.code(401).header("www-authenticate", "Bearer").send(UNAUTHORIZED);
    }

    // Then the office, by the same rule `offices.ts` applies: an office this
    // viewer may not see is indistinguishable from one that is not there.
    //
    // Served packs win over bundled ones, and a bundled pack is the fallback —
    // exactly as `routes/devices.ts` has always done it, and this route did not.
    // The gap was not theoretical: the reference deployment sets neither
    // `TERA_OFFICES_DIR` nor `TERA_PRESENCE_DIR`, so `offices.get()` answered
    // `null` for every studio the browser was standing in, and a signed-in
    // member got a 404 from the one route that exists to make signing in mean
    // something. A bundled pack is not a leak: it is compiled into the bundle
    // that made the request, so its floorplan is already in the caller's hands
    // — and `presence.get()` below still answers with an empty roster unless an
    // operator mounted one, which is the correct picture of a building nobody
    // has told this box about.
    const doc = await services.offices.get(req.params.id);
    if (doc === null && bundledOffice(req.params.id) === null) {
      return reply.code(404).send(NOT_FOUND);
    }

    // No `publicCache`, ever. This body took a credential to obtain and names
    // people; a shared cache holding it would hand one member's copy to the next
    // caller, which is precisely what the fail-closed default in `cache.ts`
    // exists to prevent. Saying so here rather than staying silent, because the
    // absence of a call is not self-evidently a decision.
    return services.presence.get(req.params.id);
  });
}
