// nucleic-updates — a surgical redirect Worker for the Sparkle update host.
//
// `updates.nucleic.blakeslee.xyz` is served *directly* by the `nucleic-updates` R2 bucket
// (custom domain, no Worker in the path — fully CDN-cacheable; docs/CLOUD_INFRA.md §1). This
// Worker is layered on top for ONE job: resolve a channel's "latest" alias to the current
// versioned DMG, so anything elsewhere (the website download button, docs, a chat link) can point
// at a single stable URL that never needs updating:
//
//   GET https://updates.nucleic.blakeslee.xyz/<channel>/latest  →  302  →  …/<channel>/Nucleic-….dmg
//
// Worker routes take precedence over the R2 custom domain on the same hostname, so ONLY the three
// `/<channel>/latest` paths reach this Worker — the appcasts (`appcast-*.xml` at the bucket root)
// and the DMGs (`<channel>/*.dmg`) keep being served straight from R2 and stay fully cacheable.
// "Latest" is read from the channel appcast on each request (the same XML the app's updater
// consumes), and we redirect to that appcast's newest enclosure URL verbatim — which already
// carries the `<channel>/` folder — so there is no per-release config to touch: ship a release,
// the alias follows.

import { latestDmgUrl } from "./appcast";

export interface Env {
  // The `nucleic-updates` R2 bucket — the same one served at updates.nucleic.blakeslee.xyz.
  // Bound directly so we read the appcast without a network hop or a dependency on the public path.
  UPDATES: R2Bucket;
}

// channel → its appcast object key (flat layout, matching scripts/upload-r2.sh).
const APPCAST: Record<string, string> = {
  beta: "appcast-beta.xml",
  rc: "appcast-rc.xml",
  stable: "appcast-stable.xml",
};

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const { pathname } = new URL(request.url);

    const match = /^\/(beta|rc|stable)\/latest$/.exec(pathname);
    if (!match) return new Response("not found\n", { status: 404 });
    if (request.method !== "GET" && request.method !== "HEAD") {
      return new Response("method not allowed\n", { status: 405, headers: { allow: "GET, HEAD" } });
    }

    const appcast = await env.UPDATES.get(APPCAST[match[1]]);
    if (!appcast) return new Response("no appcast for channel\n", { status: 404 });

    const target = latestDmgUrl(await appcast.text());
    if (!target) return new Response("no release in appcast\n", { status: 404 });

    // 302, never 301: the target moves every release, so the alias must not be cached as a
    // permanent redirect. The short max-age matches the appcast's own 5-min TTL (upload-r2.sh) so
    // a freshly published release surfaces here promptly without hammering R2 on every hit.
    return new Response(null, {
      status: 302,
      headers: { location: target, "cache-control": "public, max-age=300" },
    });
  },
} satisfies ExportedHandler<Env>;
