// Who a notification is for, and which of the two claimants wins.
//
// Two kinds of caller reach POST /_officer/notify and they are told apart by nothing but this header:
//
//   - A producer inside the tailnet (the queue, the email sidecar, the agent) POSTs over loopback. It
//     has no session to speak from, so naming a user in the body is its ONLY option.
//   - A signed-in browser arrives through createSidecarProxy, which injects X-Officer-User from the
//     token the platform just authenticated. The browser cannot know its own numeric id, and must not
//     be trusted with it if it did.
//
// So the header wins wherever it is present. This lives in its own module, away from the Bun.serve
// entrypoint, purely so it can be tested — it is an access-control decision, and the version that read
// `body.userId ?? header` let any account holding the `notify` capability push to any other account's
// devices by naming them in a JSON body.

type ResolveUserParams = {
  /** Raw X-Officer-User header value, or null when absent. */
  header: string | null;
  /** `userId` as it appeared in the request body — unvalidated, may be anything. */
  bodyUserId: unknown;
};

const asUserId = (v: unknown): number | null => (typeof v === 'number' && Number.isInteger(v) && v > 0 ? v : null);

/** The user to notify, or null when neither claimant supplied a usable id. */
export function resolveNotifyUser({ header, bodyUserId }: ResolveUserParams): number | null {
  // PRESENCE of the header is the signal, not its validity. A header that is present but unparseable
  // means a proxied request went wrong, and falling through to the body there would hand the decision
  // straight back to the caller we just declined to trust — send junk, name yourself in the body, win.
  if (header !== null) return asUserId(Number(header));

  // No header at all: a loopback producer. Its body is honoured, but still has to be a real id — a
  // string "13" is a producer bug worth surfacing as a 400 rather than coercing into a silent success.
  return asUserId(bodyUserId);
}
