/** Cloudflare Worker entry point for the vinext-starter template. */
import { handleImageOptimization, DEFAULT_DEVICE_SIZES, DEFAULT_IMAGE_SIZES } from "vinext/server/image-optimization";
import handler from "vinext/server/app-router-entry";
import { edgeCache } from "../lib/edge-cache";
import type { FeedEnvironment } from "../lib/liquidity-provider";
import { serveLiquidity } from "../lib/liquidity-response";
import { serveGit } from "../lib/git-http";
import { REPOSITORY_PATH, REPOSITORY_NAME } from "../lib/site-config";
import manifest from "../lib/source-manifest.json";
import { DEFAULT_CHAIN, getChain } from "../lib/chains";

interface Env extends FeedEnvironment {
  ASSETS: Fetcher;
  DB: D1Database;
  IMAGES: {
    input(stream: ReadableStream): {
      transform(options: Record<string, unknown>): {
        output(options: { format: string; quality: number }): Promise<{ response(): Response }>;
      };
    };
  };
}

interface ExecutionContext {
  waitUntil(promise: Promise<unknown>): void;
  passThroughOnException(): void;
}

// Image security config. SVG sources with .svg extension auto-skip the
// optimization endpoint on the client side (served directly, no proxy).
// To route SVGs through the optimizer (with security headers), set
// dangerouslyAllowSVG: true in next.config.js and uncomment below:
// const imageConfig: ImageConfig = { dangerouslyAllowSVG: true };

const worker = {
  async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
    const url = new URL(request.url);

    if (url.pathname === "/api/liquidity" && request.method === "GET") {
      const chain = getChain(url.searchParams.get("chain") ?? DEFAULT_CHAIN.id);
      if (!chain) return Response.json({ error: "Unsupported chain" }, { status: 400 });
      const cache = await edgeCache();
      const key = new Request(new URL(`/__feed_response/${manifest.head}/${chain.id}`, request.url));
      try {
        const hit = await cache?.match(key);
        if (hit) { const headers = new Headers(hit.headers); headers.set("Cache-Control", "no-store"); return new Response(hit.body, { headers }); }
      } catch { /* The handler has independent durable and bundled fallbacks. */ }
      const response = await serveLiquidity(request, ctx, env.DB, env);
      try {
        const copy = response.clone(), headers = new Headers(copy.headers); headers.set("Cache-Control", "public, max-age=10");
        ctx.waitUntil((cache?.put(key, new Response(copy.body, { headers })) ?? Promise.resolve()).catch(() => {}));
      } catch { /* An edge-cache failure does not affect this response. */ }
      return response;
    }
    if (/^\/(source|robinhood-correlated-pairs)\.git\//.test(url.pathname)) {
      url.pathname = url.pathname.replace(/^\/(source|robinhood-correlated-pairs)\.git/, REPOSITORY_PATH);
      return Response.redirect(url.toString(), 308);
    }
    if (url.pathname.startsWith(`${REPOSITORY_PATH}/`)) return serveGit(request, env.ASSETS);
    if (/^\/(source|robinhood-correlated-pairs)\.(tar\.gz|bundle)$/.test(url.pathname)) {
      url.pathname = `/${REPOSITORY_NAME}.${url.pathname.endsWith(".bundle") ? "bundle" : "tar.gz"}`;
      return Response.redirect(url.toString(), 308);
    }

    if (url.pathname === "/_vinext/image") {
      const allowedWidths = [...DEFAULT_DEVICE_SIZES, ...DEFAULT_IMAGE_SIZES];
      return handleImageOptimization(request, {
        fetchAsset: (path) => env.ASSETS.fetch(new Request(new URL(path, request.url))),
        transformImage: async (body, { width, format, quality }) => {
          const result = await env.IMAGES.input(body).transform(width > 0 ? { width } : {}).output({ format, quality });
          return result.response();
        },
      }, allowedWidths);
    }

    // Cache only public HTML documents, never RSC requests or personalized responses.
    const html = request.method === "GET" && /^\/(?:zh|es|ja|ko|fr|pt|de)?$/.test(url.pathname) && [...url.searchParams.keys()].every(key => key === "chain" || key.startsWith("utm_")) && request.headers.get("accept")?.includes("text/html") && !request.headers.has("rsc");
    if (!html) return handler.fetch(request, env, ctx);
    const cache = await edgeCache();
    const key = new Request(new URL(`/__page/${manifest.head}/${getChain(url.searchParams.get("chain") ?? "")?.id ?? DEFAULT_CHAIN.id}${url.pathname}`, request.url));
    try { const hit = await cache?.match(key); if (hit) return hit; } catch { /* Render normally. */ }
    const response = await handler.fetch(request, env, ctx);
    if (response.ok && !response.headers.has("set-cookie")) {
      const headers = new Headers(response.headers); headers.set("Cache-Control", "public, max-age=0, s-maxage=300");
      const result = new Response(response.body, { status: response.status, headers });
      try { ctx.waitUntil((cache?.put(key, result.clone()) ?? Promise.resolve()).catch(() => {})); } catch { /* Optional edge cache. */ }
      return result;
    }
    return response;
  },
};

export default worker;
