/**
 * FontFace registration seam (docs/architecture/02; arch 00 — only
 * infrastructure touches browser APIs).
 *
 * Turns stored FontAsset bytes into live `FontFace`s on `document.fonts`, so the
 * chart renderer's font gate (`ensureFontsLoaded` in chart-renderer.ts) and the
 * Type-panel dropdown resolve user fonts exactly like the @fontsource roster.
 * Registration is keyed by font id (so a delete removes precisely the right
 * face), idempotent (re-registering an id replaces its face), and a no-op where
 * the Font Loading API is absent (tests, SSR).
 *
 * A static file is one weight, registered with default descriptors (normal/400);
 * a config requesting bold falls back to the browser's faux-bold. A **variable**
 * file is registered with its parsed weight/width ranges (`variableFontDescriptors`),
 * so the whole weight axis renders from one upload and the Type panel's weight
 * controls drive it for real.
 */

import { type FontAsset, variableFontDescriptors } from '@core/font-asset';

/** id → the live face we added, so we can remove exactly it on delete/replace. */
const registered = new Map<number, FontFace>();

/** Register (or replace) a stored font as a live `FontFace` on `document.fonts`. */
export function registerFontAsset(asset: FontAsset): void {
  if (typeof document === 'undefined' || !document.fonts) return;
  unregisterFont(asset.id); // replace any prior face for this id (re-import)
  let face: FontFace;
  try {
    // Variable fonts get weight/width ranges so the browser resolves a requested
    // weight against the axis; a static font passes no descriptors (normal/400).
    face = new FontFace(asset.family, asset.data, variableFontDescriptors(asset.axes));
  } catch {
    return; // a malformed family name or unreadable source — nothing to register
  }
  registered.set(asset.id, face);
  document.fonts.add(face);
  // Decode eagerly so the next chart render finds the face ready. A decode
  // failure is non-fatal — the chart renders with fallback metrics — which is
  // arch 02's sanctioned graceful-fallback case, kept explicit, not buried.
  face.load().catch(() => {});
}

/** Register a batch (startup hydration). */
export function registerFontAssets(assets: ReadonlyArray<FontAsset>): void {
  for (const asset of assets) registerFontAsset(asset);
}

/** Remove a previously-registered face by font id. No-op if not registered. */
export function unregisterFont(id: number): void {
  if (typeof document === 'undefined' || !document.fonts) return;
  const face = registered.get(id);
  if (face) {
    document.fonts.delete(face);
    registered.delete(id);
  }
}
