import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import path from "node:path";
import { PAGES_FIXTURE_DIR } from "./helpers.js";
import { isExternalUrl, isHashOnlyChange } from "../packages/vinext/src/shims/router.js";

const FIXTURE_DIR = PAGES_FIXTURE_DIR;

describe("next/navigation shim", () => {
  it("exports usePathname, useSearchParams, useParams, useRouter", async () => {
    const nav = await import(
      "../packages/vinext/src/shims/navigation.js"
    );
    expect(typeof nav.usePathname).toBe("function");
    expect(typeof nav.useSearchParams).toBe("function");
    expect(typeof nav.useParams).toBe("function");
    expect(typeof nav.useRouter).toBe("function");
  });

  it("exports redirect, notFound, permanentRedirect", async () => {
    const nav = await import(
      "../packages/vinext/src/shims/navigation.js"
    );
    expect(typeof nav.redirect).toBe("function");
    expect(typeof nav.notFound).toBe("function");
    expect(typeof nav.permanentRedirect).toBe("function");
  });

  it("redirect() throws with correct digest", async () => {
    const { redirect } = await import(
      "../packages/vinext/src/shims/navigation.js"
    );
    try {
      redirect("/login");
      expect.unreachable("should have thrown");
    } catch (e: any) {
      expect(e.digest).toContain("NEXT_REDIRECT");
      expect(e.digest).toContain("/login");
    }
  });

  it("notFound() throws with correct digest", async () => {
    const { notFound } = await import(
      "../packages/vinext/src/shims/navigation.js"
    );
    try {
      notFound();
      expect.unreachable("should have thrown");
    } catch (e: any) {
      expect(e.digest).toBe("NEXT_HTTP_ERROR_FALLBACK;404");
    }
  });

  it("forbidden() throws with correct digest", async () => {
    const { forbidden } = await import(
      "../packages/vinext/src/shims/navigation.js"
    );
    try {
      forbidden();
      expect.unreachable("should have thrown");
    } catch (e: any) {
      expect(e.digest).toBe("NEXT_HTTP_ERROR_FALLBACK;403");
    }
  });

  it("unauthorized() throws with correct digest", async () => {
    const { unauthorized } = await import(
      "../packages/vinext/src/shims/navigation.js"
    );
    try {
      unauthorized();
      expect.unreachable("should have thrown");
    } catch (e: any) {
      expect(e.digest).toBe("NEXT_HTTP_ERROR_FALLBACK;401");
    }
  });

  it("isHTTPAccessFallbackError detects all HTTP access fallback errors", async () => {
    const { notFound, forbidden, unauthorized, isHTTPAccessFallbackError, getAccessFallbackHTTPStatus } = await import(
      "../packages/vinext/src/shims/navigation.js"
    );

    // Test notFound
    try { notFound(); } catch (e) {
      expect(isHTTPAccessFallbackError(e)).toBe(true);
      expect(getAccessFallbackHTTPStatus(e)).toBe(404);
    }

    // Test forbidden
    try { forbidden(); } catch (e) {
      expect(isHTTPAccessFallbackError(e)).toBe(true);
      expect(getAccessFallbackHTTPStatus(e)).toBe(403);
    }

    // Test unauthorized
    try { unauthorized(); } catch (e) {
      expect(isHTTPAccessFallbackError(e)).toBe(true);
      expect(getAccessFallbackHTTPStatus(e)).toBe(401);
    }

    // Test non-access error
    expect(isHTTPAccessFallbackError(new Error("random"))).toBe(false);
    expect(isHTTPAccessFallbackError(null)).toBe(false);

    // Test legacy NEXT_NOT_FOUND format
    const legacyErr = new Error("old");
    (legacyErr as any).digest = "NEXT_NOT_FOUND";
    expect(isHTTPAccessFallbackError(legacyErr)).toBe(true);
    expect(getAccessFallbackHTTPStatus(legacyErr)).toBe(404);
  });

  it("setNavigationContext / useParams works on server side", async () => {
    const { setNavigationContext, useParams } = await import(
      "../packages/vinext/src/shims/navigation.js"
    );
    setNavigationContext({
      pathname: "/blog/test",
      searchParams: new URLSearchParams(""),
      params: { slug: "test" },
    });
    const params = useParams();
    expect(params).toEqual({ slug: "test" });
    setNavigationContext(null);
  });

  it("setClientParams provides referential stability for identical params", async () => {
    const { setClientParams, getClientParams } = await import(
      "../packages/vinext/src/shims/navigation.js"
    );
    // Set params initially
    setClientParams({ slug: "hello" });
    const first = getClientParams();
    // Set params with same values — should return same object reference
    setClientParams({ slug: "hello" });
    const second = getClientParams();
    expect(first).toBe(second); // referential equality

    // Set params with different values — should return new object
    setClientParams({ slug: "world" });
    const third = getClientParams();
    expect(third).not.toBe(first);
    expect(third).toEqual({ slug: "world" });

    // Clean up
    setClientParams({});
  });

  it("exports useSelectedLayoutSegment and useSelectedLayoutSegments", async () => {
    const nav = await import(
      "../packages/vinext/src/shims/navigation.js"
    );
    expect(typeof nav.useSelectedLayoutSegment).toBe("function");
    expect(typeof nav.useSelectedLayoutSegments).toBe("function");
  });

  it("useSelectedLayoutSegments returns path segments from server context", async () => {
    const { setNavigationContext, useSelectedLayoutSegments } = await import(
      "../packages/vinext/src/shims/navigation.js"
    );
    setNavigationContext({
      pathname: "/dashboard/settings/profile",
      searchParams: new URLSearchParams(""),
      params: {},
    });
    const segments = useSelectedLayoutSegments();
    expect(segments).toEqual(["dashboard", "settings", "profile"]);
    setNavigationContext(null);
  });

  it("useSelectedLayoutSegment returns first segment or null", async () => {
    const { setNavigationContext, useSelectedLayoutSegment } = await import(
      "../packages/vinext/src/shims/navigation.js"
    );
    setNavigationContext({
      pathname: "/blog/my-post",
      searchParams: new URLSearchParams(""),
      params: {},
    });
    expect(useSelectedLayoutSegment()).toBe("blog");

    setNavigationContext({
      pathname: "/",
      searchParams: new URLSearchParams(""),
      params: {},
    });
    expect(useSelectedLayoutSegment()).toBeNull();
    setNavigationContext(null);
  });
});

describe("next/headers shim", () => {
  it("exports cookies, headers, draftMode", async () => {
    const mod = await import(
      "../packages/vinext/src/shims/headers.js"
    );
    expect(typeof mod.cookies).toBe("function");
    expect(typeof mod.headers).toBe("function");
    expect(typeof mod.draftMode).toBe("function");
  });

  it("headers() returns request headers from context", async () => {
    const { setHeadersContext, headers } = await import(
      "../packages/vinext/src/shims/headers.js"
    );
    const reqHeaders = new Headers({ "x-custom": "test-value" });
    setHeadersContext({
      headers: reqHeaders,
      cookies: new Map(),
    });

    const h = await headers();
    expect(h.get("x-custom")).toBe("test-value");
    setHeadersContext(null);
  });

  it("cookies() returns parsed cookies from context", async () => {
    const { setHeadersContext, cookies } = await import(
      "../packages/vinext/src/shims/headers.js"
    );
    setHeadersContext({
      headers: new Headers(),
      cookies: new Map([
        ["session", "abc123"],
        ["theme", "dark"],
      ]),
    });

    const c = await cookies();
    expect(c.get("session")).toEqual({ name: "session", value: "abc123" });
    expect(c.get("theme")).toEqual({ name: "theme", value: "dark" });
    expect(c.has("session")).toBe(true);
    expect(c.has("missing")).toBe(false);
    expect(c.size).toBe(2);
    setHeadersContext(null);
  });

  it("headersContextFromRequest parses cookies from Request", async () => {
    const { headersContextFromRequest } = await import(
      "../packages/vinext/src/shims/headers.js"
    );
    const req = new Request("https://example.com", {
      headers: { cookie: "a=1; b=2" },
    });
    const ctx = headersContextFromRequest(req);

    expect(ctx.cookies.get("a")).toBe("1");
    expect(ctx.cookies.get("b")).toBe("2");
    expect(ctx.headers.get("cookie")).toBe("a=1; b=2");
  });

  it("throws when called outside request context", async () => {
    const { headers, cookies } = await import(
      "../packages/vinext/src/shims/headers.js"
    );
    // Ensure context is cleared
    const { setHeadersContext } = await import(
      "../packages/vinext/src/shims/headers.js"
    );
    setHeadersContext(null);

    await expect(headers()).rejects.toThrow("Server Component");
    await expect(cookies()).rejects.toThrow("Server Component");
  });

  it("draftMode() returns isEnabled=false when no bypass cookie", async () => {
    const { setHeadersContext, draftMode } = await import(
      "../packages/vinext/src/shims/headers.js"
    );
    setHeadersContext({
      headers: new Headers(),
      cookies: new Map(),
    });
    const dm = await draftMode();
    expect(dm.isEnabled).toBe(false);
    setHeadersContext(null);
  });

  it("draftMode() returns isEnabled=false for arbitrary cookie values (not signed)", async () => {
    const { setHeadersContext, draftMode } = await import(
      "../packages/vinext/src/shims/headers.js"
    );
    // An arbitrary cookie value should NOT enable draft mode — only the
    // server-generated secret is valid.
    setHeadersContext({
      headers: new Headers(),
      cookies: new Map([["__prerender_bypass", "1"]]),
    });
    const dm = await draftMode();
    expect(dm.isEnabled).toBe(false);
    setHeadersContext(null);
  });

  it("draftMode().enable() sets the bypass cookie in context", async () => {
    const { setHeadersContext, draftMode, getDraftModeCookieHeader } = await import(
      "../packages/vinext/src/shims/headers.js"
    );
    setHeadersContext({
      headers: new Headers(),
      cookies: new Map(),
    });
    const dm = await draftMode();
    expect(dm.isEnabled).toBe(false);

    dm.enable();
    // After enabling, the cookie should be set on the context
    const dm2 = await draftMode();
    expect(dm2.isEnabled).toBe(true);

    // The Set-Cookie header should be generated with a non-predictable secret (UUID)
    const cookieHeader = getDraftModeCookieHeader();
    expect(cookieHeader).toContain("__prerender_bypass=");
    const bypassMatch = cookieHeader!.match(/__prerender_bypass=([^;]+)/);
    expect(bypassMatch).not.toBeNull();
    expect(bypassMatch![1]).toMatch(
      /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/,
    );
    expect(cookieHeader).toContain("HttpOnly");
    setHeadersContext(null);
  });

  it("draftMode().disable() clears the bypass cookie", async () => {
    const { setHeadersContext, draftMode, getDraftModeCookieHeader } = await import(
      "../packages/vinext/src/shims/headers.js"
    );
    setHeadersContext({
      headers: new Headers(),
      cookies: new Map(),
    });
    // Enable first so the cookie is set to the server secret
    const dm = await draftMode();
    dm.enable();
    // Consume the enable Set-Cookie header
    getDraftModeCookieHeader();

    const dm1 = await draftMode();
    expect(dm1.isEnabled).toBe(true);

    dm1.disable();
    const dm2 = await draftMode();
    expect(dm2.isEnabled).toBe(false);

    const cookieHeader = getDraftModeCookieHeader();
    expect(cookieHeader).toContain("Max-Age=0");
    setHeadersContext(null);
  });
});

describe("next/headers writable cookies", () => {
  it("cookies().set() updates the cookie map and accumulates Set-Cookie headers", async () => {
    const { setHeadersContext, cookies, getAndClearPendingCookies } = await import(
      "../packages/vinext/src/shims/headers.js"
    );
    setHeadersContext({
      headers: new Headers(),
      cookies: new Map(),
    });

    const c = await cookies();
    c.set("token", "xyz", { path: "/", httpOnly: true, secure: true });

    // Cookie should now be readable
    expect(c.get("token")).toEqual({ name: "token", value: "xyz" });
    expect(c.has("token")).toBe(true);

    // Pending Set-Cookie headers should be accumulated
    const pending = getAndClearPendingCookies();
    expect(pending.length).toBe(1);
    expect(pending[0]).toContain("token=xyz");
    expect(pending[0]).toContain("Path=/");
    expect(pending[0]).toContain("HttpOnly");
    expect(pending[0]).toContain("Secure");

    // After clearing, should be empty
    expect(getAndClearPendingCookies().length).toBe(0);
    setHeadersContext(null);
  });

  it("cookies().delete() removes from map and adds Max-Age=0 header", async () => {
    const { setHeadersContext, cookies, getAndClearPendingCookies } = await import(
      "../packages/vinext/src/shims/headers.js"
    );
    setHeadersContext({
      headers: new Headers(),
      cookies: new Map([["session", "abc"]]),
    });

    const c = await cookies();
    expect(c.has("session")).toBe(true);
    c.delete("session");
    expect(c.has("session")).toBe(false);

    const pending = getAndClearPendingCookies();
    expect(pending.length).toBe(1);
    expect(pending[0]).toContain("session=");
    expect(pending[0]).toContain("Max-Age=0");
    setHeadersContext(null);
  });

  it("cookies().set() with object syntax works", async () => {
    const { setHeadersContext, cookies, getAndClearPendingCookies } = await import(
      "../packages/vinext/src/shims/headers.js"
    );
    setHeadersContext({
      headers: new Headers(),
      cookies: new Map(),
    });

    const c = await cookies();
    c.set({ name: "pref", value: "dark", sameSite: "Lax" });
    expect(c.get("pref")?.value).toBe("dark");

    const pending = getAndClearPendingCookies();
    expect(pending[0]).toContain("pref=dark");
    expect(pending[0]).toContain("SameSite=Lax");
    setHeadersContext(null);
  });
});

describe("next/server shim", () => {
  it("NextRequest wraps a standard Request with nextUrl and cookies", async () => {
    const { NextRequest } = await import(
      "../packages/vinext/src/shims/server.js"
    );
    const req = new NextRequest("https://example.com/blog?page=2", {
      headers: { cookie: "session=abc123; theme=dark" },
    });

    expect(req.nextUrl.pathname).toBe("/blog");
    expect(req.nextUrl.searchParams.get("page")).toBe("2");
    expect(req.cookies.get("session")).toEqual({ name: "session", value: "abc123" });
    expect(req.cookies.get("theme")).toEqual({ name: "theme", value: "dark" });
    expect(req.cookies.has("session")).toBe(true);
    expect(req.cookies.has("missing")).toBe(false);
  });

  it("NextResponse.json() creates a JSON response", async () => {
    const { NextResponse } = await import(
      "../packages/vinext/src/shims/server.js"
    );
    const res = NextResponse.json({ message: "hello" }, { status: 201 });

    expect(res.status).toBe(201);
    expect(res.headers.get("content-type")).toBe("application/json");
    const body = await res.json();
    expect(body).toEqual({ message: "hello" });
  });

  it("NextResponse.redirect() creates a redirect response", async () => {
    const { NextResponse } = await import(
      "../packages/vinext/src/shims/server.js"
    );
    const res = NextResponse.redirect("https://example.com/new", 308);

    expect(res.status).toBe(308);
    expect(res.headers.get("Location")).toBe("https://example.com/new");
  });

  it("NextResponse.rewrite() sets x-middleware-rewrite header", async () => {
    const { NextResponse } = await import(
      "../packages/vinext/src/shims/server.js"
    );
    const res = NextResponse.rewrite("https://example.com/internal");

    expect(res.headers.get("x-middleware-rewrite")).toBe("https://example.com/internal");
  });

  it("NextResponse.next() sets x-middleware-next header", async () => {
    const { NextResponse } = await import(
      "../packages/vinext/src/shims/server.js"
    );
    const res = NextResponse.next();

    expect(res.headers.get("x-middleware-next")).toBe("1");
  });

  it("ResponseCookies set/get/delete work", async () => {
    const { NextResponse } = await import(
      "../packages/vinext/src/shims/server.js"
    );
    const res = new NextResponse();
    res.cookies.set("token", "xyz", { path: "/", httpOnly: true });

    const cookie = res.cookies.get("token");
    expect(cookie).toBeTruthy();
    expect(cookie!.value).toBe("xyz");

    // Verify the Set-Cookie header was set
    const setCookie = res.headers.getSetCookie();
    expect(setCookie.length).toBeGreaterThan(0);
    expect(setCookie[0]).toContain("token=xyz");
    expect(setCookie[0]).toContain("HttpOnly");
  });

  it("userAgentFromString detects bots", async () => {
    const { userAgentFromString } = await import(
      "../packages/vinext/src/shims/server.js"
    );
    const bot = userAgentFromString("Googlebot/2.1");
    expect(bot.isBot).toBe(true);

    const human = userAgentFromString("Mozilla/5.0");
    expect(human.isBot).toBe(false);
  });

  it("after() runs a callback asynchronously without throwing", async () => {
    const { after } = await import(
      "../packages/vinext/src/shims/server.js"
    );
    let called = false;
    after(() => {
      called = true;
    });
    // after() schedules as a microtask, so await a tick
    await new Promise((r) => setTimeout(r, 10));
    expect(called).toBe(true);
  });

  it("after() handles a promise argument", async () => {
    const { after } = await import(
      "../packages/vinext/src/shims/server.js"
    );
    let resolved = false;
    const p = new Promise<void>((resolve) => {
      setTimeout(() => {
        resolved = true;
        resolve();
      }, 5);
    });
    after(p);
    await new Promise((r) => setTimeout(r, 20));
    expect(resolved).toBe(true);
  });

  it("after() swallows errors from failing tasks", async () => {
    const { after } = await import(
      "../packages/vinext/src/shims/server.js"
    );
    const consoleError = vi.spyOn(console, "error").mockImplementation(() => {});
    after(() => {
      throw new Error("task failed");
    });
    await new Promise((r) => setTimeout(r, 10));
    expect(consoleError).toHaveBeenCalledWith(
      "[vinext] after() task failed:",
      expect.any(Error),
    );
    consoleError.mockRestore();
  });

  it("connection() returns a resolved promise", async () => {
    const { connection } = await import(
      "../packages/vinext/src/shims/server.js"
    );
    const result = connection();
    expect(result).toBeInstanceOf(Promise);
    await expect(result).resolves.toBeUndefined();
  });

  it("URLPattern is exported and available in Node 20+", async () => {
    const { URLPattern } = await import(
      "../packages/vinext/src/shims/server.js"
    );
    // Node 22+ has URLPattern globally; if available, test it works
    if (globalThis.URLPattern) {
      expect(URLPattern).toBe(globalThis.URLPattern);
      const pattern = new URLPattern({ pathname: "/blog/:slug" });
      const match = pattern.exec({ pathname: "/blog/hello-world" });
      expect(match).toBeTruthy();
      expect(match!.pathname.groups.slug).toBe("hello-world");
    } else {
      // URLPattern not available — our export should be a fallback that throws
      expect(typeof URLPattern).toBe("function");
    }
  });
});

describe("next/config shim", () => {
  it("getConfig returns default empty config", async () => {
    const { default: getConfig } = await import(
      "../packages/vinext/src/shims/config.js"
    );
    const config = getConfig();
    expect(config).toEqual({
      serverRuntimeConfig: {},
      publicRuntimeConfig: {},
    });
  });

  it("setConfig updates the runtime config", async () => {
    const { default: getConfig, setConfig } = await import(
      "../packages/vinext/src/shims/config.js"
    );
    setConfig({
      serverRuntimeConfig: { secret: "s3cr3t" },
      publicRuntimeConfig: { appName: "test-app" },
    });
    const config = getConfig();
    expect(config.serverRuntimeConfig.secret).toBe("s3cr3t");
    expect(config.publicRuntimeConfig.appName).toBe("test-app");

    // Reset for other tests
    setConfig({ serverRuntimeConfig: {}, publicRuntimeConfig: {} });
  });
});

describe("next/cache shim", () => {
  it("exports revalidateTag, revalidatePath, unstable_cache", async () => {
    const mod = await import(
      "../packages/vinext/src/shims/cache.js"
    );
    expect(typeof mod.revalidateTag).toBe("function");
    expect(typeof mod.revalidatePath).toBe("function");
    expect(typeof mod.unstable_cache).toBe("function");
  });

  it("exports setCacheHandler and getCacheHandler", async () => {
    const mod = await import(
      "../packages/vinext/src/shims/cache.js"
    );
    expect(typeof mod.setCacheHandler).toBe("function");
    expect(typeof mod.getCacheHandler).toBe("function");
  });

  it("default handler is MemoryCacheHandler", async () => {
    const { getCacheHandler, MemoryCacheHandler } = await import(
      "../packages/vinext/src/shims/cache.js"
    );
    const handler = getCacheHandler();
    expect(handler).toBeInstanceOf(MemoryCacheHandler);
  });

  it("unstable_cache caches function results", async () => {
    const { unstable_cache, setCacheHandler, MemoryCacheHandler } =
      await import("../packages/vinext/src/shims/cache.js");

    // Fresh handler for isolation
    setCacheHandler(new MemoryCacheHandler());

    let callCount = 0;
    const expensive = async (x: number) => {
      callCount++;
      return x * 2;
    };

    const cached = unstable_cache(expensive, ["test-fn"], {
      tags: ["test-tag"],
    });

    const r1 = await cached(5);
    expect(r1).toBe(10);
    expect(callCount).toBe(1);

    const r2 = await cached(5);
    expect(r2).toBe(10);
    expect(callCount).toBe(1); // Should NOT call the function again

    const r3 = await cached(10);
    expect(r3).toBe(20);
    expect(callCount).toBe(2); // Different args = different cache key

    // Reset
    setCacheHandler(new MemoryCacheHandler());
  });

  it("revalidateTag invalidates cached entries", async () => {
    const {
      unstable_cache,
      revalidateTag,
      setCacheHandler,
      MemoryCacheHandler,
    } = await import("../packages/vinext/src/shims/cache.js");

    setCacheHandler(new MemoryCacheHandler());

    let callCount = 0;
    const fn = async () => {
      callCount++;
      return "result-" + callCount;
    };

    const cached = unstable_cache(fn, ["revalidate-test"], {
      tags: ["my-tag"],
    });

    const r1 = await cached();
    expect(r1).toBe("result-1");
    expect(callCount).toBe(1);

    // Revalidate the tag
    await revalidateTag("my-tag");

    // Next call should re-execute the function
    const r2 = await cached();
    expect(r2).toBe("result-2");
    expect(callCount).toBe(2);

    // Reset
    setCacheHandler(new MemoryCacheHandler());
  });

  it("revalidateTag accepts optional cacheLife profile (Next.js 16)", async () => {
    const {
      revalidateTag,
      setCacheHandler,
      MemoryCacheHandler,
    } = await import("../packages/vinext/src/shims/cache.js");

    setCacheHandler(new MemoryCacheHandler());

    // Should not throw with profile argument
    await revalidateTag("my-tag", "max");
    await revalidateTag("my-tag", "hours");
    await revalidateTag("my-tag", { expire: 3600 });

    // Should still work without profile (deprecated single-arg form)
    await revalidateTag("my-tag");

    setCacheHandler(new MemoryCacheHandler());
  });

  it("exports updateTag function (Next.js 16)", async () => {
    const mod = await import("../packages/vinext/src/shims/cache.js");
    expect(typeof mod.updateTag).toBe("function");
  });

  it("updateTag invalidates cached entries", async () => {
    const {
      unstable_cache,
      updateTag,
      setCacheHandler,
      MemoryCacheHandler,
    } = await import("../packages/vinext/src/shims/cache.js");

    setCacheHandler(new MemoryCacheHandler());

    let callCount = 0;
    const fn = async () => {
      callCount++;
      return "result-" + callCount;
    };

    const cached = unstable_cache(fn, ["update-tag-test"], {
      tags: ["user-1"],
    });

    const r1 = await cached();
    expect(r1).toBe("result-1");

    // updateTag expires the cache
    await updateTag("user-1");

    const r2 = await cached();
    expect(r2).toBe("result-2");
    expect(callCount).toBe(2);

    setCacheHandler(new MemoryCacheHandler());
  });

  it("exports refresh function (Next.js 16)", async () => {
    const mod = await import("../packages/vinext/src/shims/cache.js");
    expect(typeof mod.refresh).toBe("function");
    // refresh() is a no-op on the server but should not throw
    mod.refresh();
  });

  it("setCacheHandler swaps the active handler", async () => {
    const { setCacheHandler, getCacheHandler, unstable_cache } = await import(
      "../packages/vinext/src/shims/cache.js"
    );

    // Create a custom handler that tracks calls
    const calls: string[] = [];
    const customHandler = {
      async get(key: string) {
        calls.push(`get:${key}`);
        return null;
      },
      async set(key: string) {
        calls.push(`set:${key}`);
      },
      async revalidateTag(tags: string | string[]) {
        const tagList = Array.isArray(tags) ? tags : [tags];
        calls.push(`revalidateTag:${tagList.join(",")}`);
      },
      resetRequestCache() {
        calls.push("reset");
      },
    };

    const originalHandler = getCacheHandler();
    setCacheHandler(customHandler);

    const cached = unstable_cache(async () => 42, ["custom-test"]);
    await cached();

    expect(calls.some((c) => c.startsWith("get:"))).toBe(true);
    expect(calls.some((c) => c.startsWith("set:"))).toBe(true);

    // Restore
    setCacheHandler(originalHandler);
  });

  it("MemoryCacheHandler.get/set round-trips values", async () => {
    const { MemoryCacheHandler } = await import(
      "../packages/vinext/src/shims/cache.js"
    );

    const handler = new MemoryCacheHandler();

    await handler.set("test-key", {
      kind: "FETCH",
      data: { headers: {}, body: '{"x":1}', url: "test" },
      tags: ["t1"],
      revalidate: 3600,
    });

    const result = await handler.get("test-key");
    expect(result).not.toBeNull();
    expect(result!.value).not.toBeNull();
    expect(result!.value!.kind).toBe("FETCH");
    if (result!.value!.kind === "FETCH") {
      expect(result!.value!.data.body).toBe('{"x":1}');
    }
  });

  it("MemoryCacheHandler respects tag invalidation", async () => {
    const { MemoryCacheHandler } = await import(
      "../packages/vinext/src/shims/cache.js"
    );

    const handler = new MemoryCacheHandler();

    await handler.set(
      "tagged-entry",
      {
        kind: "FETCH",
        data: { headers: {}, body: '"cached"', url: "test" },
        tags: ["fresh-tag"],
        revalidate: 3600,
      },
      { tags: ["fresh-tag"] },
    );

    // Should return the entry
    let result = await handler.get("tagged-entry");
    expect(result).not.toBeNull();

    // Invalidate the tag
    await handler.revalidateTag("fresh-tag");

    // Should now return null (invalidated)
    result = await handler.get("tagged-entry");
    expect(result).toBeNull();
  });

  it("exports unstable_noStore and noStore as no-ops", async () => {
    const { unstable_noStore, noStore } = await import(
      "../packages/vinext/src/shims/cache.js"
    );
    expect(typeof unstable_noStore).toBe("function");
    expect(typeof noStore).toBe("function");
    // Both should run without throwing
    expect(() => unstable_noStore()).not.toThrow();
    expect(() => noStore()).not.toThrow();
  });

  it("exports cacheLife with built-in profiles", async () => {
    const { cacheLife, cacheLifeProfiles } = await import(
      "../packages/vinext/src/shims/cache.js"
    );
    expect(typeof cacheLife).toBe("function");
    expect(typeof cacheLifeProfiles).toBe("object");

    // Built-in profiles should exist
    expect(cacheLifeProfiles).toHaveProperty("default");
    expect(cacheLifeProfiles).toHaveProperty("seconds");
    expect(cacheLifeProfiles).toHaveProperty("minutes");
    expect(cacheLifeProfiles).toHaveProperty("hours");
    expect(cacheLifeProfiles).toHaveProperty("days");
    expect(cacheLifeProfiles).toHaveProperty("weeks");
    expect(cacheLifeProfiles).toHaveProperty("max");

    // Profile shapes
    expect(cacheLifeProfiles.seconds).toEqual({ stale: 30, revalidate: 1, expire: 60 });
    expect(cacheLifeProfiles.max).toEqual({ stale: 300, revalidate: 2592000, expire: 31536000 });

    // Should run without throwing with valid inputs
    expect(() => cacheLife("default")).not.toThrow();
    expect(() => cacheLife("hours")).not.toThrow();
    expect(() => cacheLife({ stale: 60, revalidate: 300, expire: 3600 })).not.toThrow();
  });

  it("cacheLife warns on unknown profile", async () => {
    const { cacheLife } = await import(
      "../packages/vinext/src/shims/cache.js"
    );
    const consoleWarn = vi.spyOn(console, "warn").mockImplementation(() => {});
    cacheLife("nonexistent-profile");
    expect(consoleWarn).toHaveBeenCalledWith(
      expect.stringContaining("unknown profile"),
    );
    consoleWarn.mockRestore();
  });

  it("cacheLife warns when expire < revalidate", async () => {
    const { cacheLife } = await import(
      "../packages/vinext/src/shims/cache.js"
    );
    const consoleWarn = vi.spyOn(console, "warn").mockImplementation(() => {});
    cacheLife({ revalidate: 3600, expire: 60 });
    expect(consoleWarn).toHaveBeenCalledWith(
      expect.stringContaining("expire must be >= revalidate"),
    );
    consoleWarn.mockRestore();
  });

  it("exports cacheTag as a no-op function", async () => {
    const { cacheTag } = await import(
      "../packages/vinext/src/shims/cache.js"
    );
    expect(typeof cacheTag).toBe("function");
    // Should accept multiple tags without throwing
    expect(() => cacheTag("tag1", "tag2", "tag3")).not.toThrow();
  });
});

// ---------------------------------------------------------------------------
// "use cache" runtime tests
// ---------------------------------------------------------------------------

describe('"use cache" runtime', () => {
  it("registerCachedFunction caches return values", async () => {
    const { registerCachedFunction } = await import(
      "../packages/vinext/src/shims/cache-runtime.js"
    );
    // Reset state
    const { setCacheHandler, MemoryCacheHandler } = await import(
      "../packages/vinext/src/shims/cache.js"
    );
    setCacheHandler(new MemoryCacheHandler());

    let callCount = 0;
    const fn = async (x: number) => {
      callCount++;
      return { result: x * 2 };
    };

    const cached = registerCachedFunction(fn, "test:double");

    const r1 = await cached(5);
    expect(r1).toEqual({ result: 10 });
    expect(callCount).toBe(1);

    // Second call with same args — should be cached
    const r2 = await cached(5);
    expect(r2).toEqual({ result: 10 });
    expect(callCount).toBe(1); // Not called again

    // Different args — cache miss
    const r3 = await cached(7);
    expect(r3).toEqual({ result: 14 });
    expect(callCount).toBe(2);
  });

  it("registerCachedFunction respects cacheLife inside cached function", async () => {
    const { registerCachedFunction } = await import(
      "../packages/vinext/src/shims/cache-runtime.js"
    );
    const { setCacheHandler, MemoryCacheHandler, cacheLife } = await import(
      "../packages/vinext/src/shims/cache.js"
    );
    setCacheHandler(new MemoryCacheHandler());

    let callCount = 0;
    const fn = async () => {
      cacheLife("seconds"); // revalidate: 1s
      callCount++;
      return { ts: Date.now() };
    };

    const cached = registerCachedFunction(fn, "test:cachelife");

    await cached();
    expect(callCount).toBe(1);

    // Immediate second call — cached
    await cached();
    expect(callCount).toBe(1);
  });

  it("registerCachedFunction collects cacheTag", async () => {
    const { registerCachedFunction } = await import(
      "../packages/vinext/src/shims/cache-runtime.js"
    );
    const { setCacheHandler, MemoryCacheHandler, cacheTag } = await import(
      "../packages/vinext/src/shims/cache.js"
    );
    const handler = new MemoryCacheHandler();
    setCacheHandler(handler);

    const fn = async () => {
      cacheTag("my-tag", "another-tag");
      return { data: "tagged" };
    };

    const cached = registerCachedFunction(fn, "test:tags");
    await cached();

    // The cache entry should have tags
    const entry = await handler.get("use-cache:test:tags");
    expect(entry).not.toBeNull();
    expect(entry?.value).toHaveProperty("kind", "FETCH");
    if (entry?.value && entry.value.kind === "FETCH") {
      expect(entry.value.tags).toContain("my-tag");
      expect(entry.value.tags).toContain("another-tag");
    }
  });

  it("revalidateTag invalidates cached entries", async () => {
    const { registerCachedFunction } = await import(
      "../packages/vinext/src/shims/cache-runtime.js"
    );
    const { setCacheHandler, MemoryCacheHandler, cacheTag, revalidateTag } = await import(
      "../packages/vinext/src/shims/cache.js"
    );
    setCacheHandler(new MemoryCacheHandler());

    let callCount = 0;
    const fn = async () => {
      cacheTag("invalidate-me");
      callCount++;
      return { count: callCount };
    };

    const cached = registerCachedFunction(fn, "test:invalidate");

    const r1 = await cached();
    expect(r1).toEqual({ count: 1 });
    expect(callCount).toBe(1);

    // Cached
    const r2 = await cached();
    expect(r2).toEqual({ count: 1 });
    expect(callCount).toBe(1);

    // Invalidate the tag
    await revalidateTag("invalidate-me");

    // Should re-execute
    const r3 = await cached();
    expect(r3).toEqual({ count: 2 });
    expect(callCount).toBe(2);
  });

  it("private variant uses per-request cache", async () => {
    const { registerCachedFunction, clearPrivateCache } = await import(
      "../packages/vinext/src/shims/cache-runtime.js"
    );

    let callCount = 0;
    const fn = async () => {
      callCount++;
      return { count: callCount };
    };

    const cached = registerCachedFunction(fn, "test:private", "private");

    const r1 = await cached();
    expect(r1).toEqual({ count: 1 });

    // Same request — cached
    const r2 = await cached();
    expect(r2).toEqual({ count: 1 });

    // Clear private cache (simulates new request)
    clearPrivateCache();

    // Should re-execute
    const r3 = await cached();
    expect(r3).toEqual({ count: 2 });
  });

  it("cacheLife minimum-wins rule applies", async () => {
    const { registerCachedFunction } = await import(
      "../packages/vinext/src/shims/cache-runtime.js"
    );
    const { setCacheHandler, MemoryCacheHandler, cacheLife } = await import(
      "../packages/vinext/src/shims/cache.js"
    );
    const handler = new MemoryCacheHandler();
    setCacheHandler(handler);

    const fn = async () => {
      cacheLife("hours");   // revalidate: 3600
      cacheLife("seconds"); // revalidate: 1  — this should win
      return { data: "min-wins" };
    };

    const cached = registerCachedFunction(fn, "test:min-wins");
    await cached();

    // The entry should have the minimum revalidate (1 second from "seconds" profile)
    const entry = await handler.get("use-cache:test:min-wins");
    expect(entry).not.toBeNull();
    if (entry?.value && entry.value.kind === "FETCH") {
      expect(entry.value.revalidate).toBe(1);
    }
  });

  it("getCacheContext returns null outside cache function", async () => {
    const { getCacheContext } = await import(
      "../packages/vinext/src/shims/cache-runtime.js"
    );
    expect(getCacheContext()).toBeNull();
  });

  it("consistent cache keys for same objects regardless of key order", async () => {
    const { registerCachedFunction } = await import(
      "../packages/vinext/src/shims/cache-runtime.js"
    );
    const { setCacheHandler, MemoryCacheHandler } = await import(
      "../packages/vinext/src/shims/cache.js"
    );
    setCacheHandler(new MemoryCacheHandler());

    let callCount = 0;
    const fn = async (_opts: Record<string, unknown>) => {
      callCount++;
      return { result: "ok" };
    };

    const cached = registerCachedFunction(fn, "test:stable-key");

    // Different key order, same content — should be same cache key
    await cached({ b: 2, a: 1 });
    expect(callCount).toBe(1);

    await cached({ a: 1, b: 2 });
    expect(callCount).toBe(1); // Same cache key, still cached
  });

  it("cached function with no args works correctly", async () => {
    const { registerCachedFunction } = await import(
      "../packages/vinext/src/shims/cache-runtime.js"
    );
    const { setCacheHandler, MemoryCacheHandler } = await import(
      "../packages/vinext/src/shims/cache.js"
    );
    setCacheHandler(new MemoryCacheHandler());

    let callCount = 0;
    const fn = async () => {
      callCount++;
      return { hello: "world" };
    };

    const cached = registerCachedFunction(fn, "test:no-args");
    const r1 = await cached();
    const r2 = await cached();
    expect(r1).toEqual({ hello: "world" });
    expect(r2).toEqual({ hello: "world" });
    expect(callCount).toBe(1);
  });

  it("falls back to JSON when RSC module is unavailable (test environment)", async () => {
    // In vitest, @vitejs/plugin-rsc/react/rsc is not available (no Vite RSC
    // environment). The runtime should gracefully fall back to JSON.stringify
    // for cache values and stableStringify for cache keys.
    const { registerCachedFunction } = await import(
      "../packages/vinext/src/shims/cache-runtime.js"
    );
    const { setCacheHandler, MemoryCacheHandler } = await import(
      "../packages/vinext/src/shims/cache.js"
    );
    const handler = new MemoryCacheHandler();
    setCacheHandler(handler);

    const fn = async (x: number) => ({ doubled: x * 2 });
    const cached = registerCachedFunction(fn, "test:json-fallback");

    const r1 = await cached(3);
    expect(r1).toEqual({ doubled: 6 });

    // Verify the stored value is JSON (no x-vinext-rsc header)
    // stableStringify wraps args as an array: [3]
    const entry = await handler.get("use-cache:test:json-fallback:[3]");
    expect(entry).not.toBeNull();
    if (entry?.value && entry.value.kind === "FETCH") {
      expect(entry.value.data.headers["x-vinext-rsc"]).toBeUndefined();
      expect(JSON.parse(entry.value.data.body)).toEqual({ doubled: 6 });
    }
  });

  it("skips caching for non-serializable args (functions)", async () => {
    const { registerCachedFunction } = await import(
      "../packages/vinext/src/shims/cache-runtime.js"
    );
    const { setCacheHandler, MemoryCacheHandler } = await import(
      "../packages/vinext/src/shims/cache.js"
    );
    setCacheHandler(new MemoryCacheHandler());

    let callCount = 0;
    const fn = async (_cb: () => void) => {
      callCount++;
      return { called: true };
    };

    const cached = registerCachedFunction(fn, "test:fn-arg");

    // Functions can't be serialized — should execute every time (no caching)
    await cached(() => {});
    await cached(() => {});
    expect(callCount).toBe(2);
  });

  it("produces different cache entries for Promise-augmented params with different values", async () => {
    // Regression test: Next.js 16 params are created via
    // Object.assign(Promise.resolve(params), params) — a "thenable object".
    // encodeReply with temporaryReferences treats Promises as temp refs,
    // which excluded the actual param values from the cache key.
    // This caused all dynamic route pages with "use cache" to share one
    // cache entry (e.g., /layouts/sports showed /layouts/electronics data).
    const { registerCachedFunction } = await import(
      "../packages/vinext/src/shims/cache-runtime.js"
    );
    const { setCacheHandler, MemoryCacheHandler } = await import(
      "../packages/vinext/src/shims/cache.js"
    );
    setCacheHandler(new MemoryCacheHandler());

    let callCount = 0;
    // Simulates a page component: async function Page({ params }) { ... }
    const fn = async (props: { params: any }) => {
      callCount++;
      const p = typeof props.params.then === "function"
        ? await props.params
        : props.params;
      return { section: p.section, data: `data-for-${p.section}` };
    };

    const cached = registerCachedFunction(fn, "test:thenable-params");

    // Create Promise-augmented params (same pattern as app-dev-server.ts)
    const electronicsParams = { section: "electronics" };
    const asyncElectronics = Object.assign(
      Promise.resolve(electronicsParams),
      electronicsParams,
    );

    const sportsParams = { section: "sports" };
    const asyncSports = Object.assign(
      Promise.resolve(sportsParams),
      sportsParams,
    );

    // First call — electronics
    const r1 = await cached({ params: asyncElectronics });
    expect(r1).toEqual({ section: "electronics", data: "data-for-electronics" });
    expect(callCount).toBe(1);

    // Second call with SAME params — should be cached
    const asyncElectronics2 = Object.assign(
      Promise.resolve({ section: "electronics" }),
      { section: "electronics" },
    );
    const r2 = await cached({ params: asyncElectronics2 });
    expect(r2).toEqual({ section: "electronics", data: "data-for-electronics" });
    expect(callCount).toBe(1); // Cache hit

    // Third call with DIFFERENT params — must be a cache MISS
    const r3 = await cached({ params: asyncSports });
    expect(r3).toEqual({ section: "sports", data: "data-for-sports" });
    expect(callCount).toBe(2); // Must have called the function again!
  });
});

describe("replyToCacheKey deterministic hashing", () => {
  it("returns string replies as-is", async () => {
    const { replyToCacheKey } = await import(
      "../packages/vinext/src/shims/cache-runtime.js"
    );
    expect(await replyToCacheKey("hello")).toBe("hello");
    expect(await replyToCacheKey("")).toBe("");
  });

  it("produces stable hash for FormData with string entries", async () => {
    const { replyToCacheKey } = await import(
      "../packages/vinext/src/shims/cache-runtime.js"
    );

    const fd1 = new FormData();
    fd1.append("a", "1");
    fd1.append("b", "2");

    const fd2 = new FormData();
    fd2.append("a", "1");
    fd2.append("b", "2");

    const key1 = await replyToCacheKey(fd1);
    const key2 = await replyToCacheKey(fd2);
    expect(key1).toBe(key2);
  });

  it("produces stable hash regardless of entry insertion order", async () => {
    const { replyToCacheKey } = await import(
      "../packages/vinext/src/shims/cache-runtime.js"
    );

    const fd1 = new FormData();
    fd1.append("b", "2");
    fd1.append("a", "1");

    const fd2 = new FormData();
    fd2.append("a", "1");
    fd2.append("b", "2");

    const key1 = await replyToCacheKey(fd1);
    const key2 = await replyToCacheKey(fd2);
    expect(key1).toBe(key2);
  });

  it("produces stable hash for FormData with Blob entries", async () => {
    const { replyToCacheKey } = await import(
      "../packages/vinext/src/shims/cache-runtime.js"
    );

    const blob = new Blob([new Uint8Array([1, 2, 3])], { type: "application/octet-stream" });

    const fd1 = new FormData();
    fd1.append("data", blob);

    const fd2 = new FormData();
    fd2.append("data", blob);

    const key1 = await replyToCacheKey(fd1);
    const key2 = await replyToCacheKey(fd2);
    expect(key1).toBe(key2);
  });

  it("produces different hashes for different FormData content", async () => {
    const { replyToCacheKey } = await import(
      "../packages/vinext/src/shims/cache-runtime.js"
    );

    const fd1 = new FormData();
    fd1.append("a", "1");

    const fd2 = new FormData();
    fd2.append("a", "2");

    const key1 = await replyToCacheKey(fd1);
    const key2 = await replyToCacheKey(fd2);
    expect(key1).not.toBe(key2);
  });
});

describe("middleware runner", () => {
  it("findMiddlewareFile finds middleware.ts at project root", async () => {
    const { findMiddlewareFile } = await import(
      "../packages/vinext/src/server/middleware.js"
    );
    // pages-basic fixture has middleware.ts
    const result = findMiddlewareFile(FIXTURE_DIR);
    expect(result).not.toBeNull();
    expect(result).toContain("middleware.ts");
  });

  it("findMiddlewareFile returns null when no middleware exists", async () => {
    const { findMiddlewareFile } = await import(
      "../packages/vinext/src/server/middleware.js"
    );
    const result = findMiddlewareFile("/tmp/nonexistent-dir-" + Date.now());
    expect(result).toBeNull();
  });

  it("findMiddlewareFile prefers proxy.ts over middleware.ts (Next.js 16)", async () => {
    const fs = await import("node:fs");
    const path = await import("node:path");
    const os = await import("node:os");
    const { findMiddlewareFile } = await import(
      "../packages/vinext/src/server/middleware.js"
    );

    // Create a temp directory with both proxy.ts and middleware.ts
    const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "vinext-proxy-test-"));
    try {
      fs.writeFileSync(path.join(tmpDir, "proxy.ts"), "export default function proxy() {}");
      fs.writeFileSync(path.join(tmpDir, "middleware.ts"), "export function middleware() {}");
      const result = findMiddlewareFile(tmpDir);
      expect(result).not.toBeNull();
      expect(result).toContain("proxy.ts");
    } finally {
      fs.rmSync(tmpDir, { recursive: true, force: true });
    }
  });

  it("findMiddlewareFile finds proxy.js", async () => {
    const fs = await import("node:fs");
    const path = await import("node:path");
    const os = await import("node:os");
    const { findMiddlewareFile } = await import(
      "../packages/vinext/src/server/middleware.js"
    );

    const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "vinext-proxy-test-"));
    try {
      fs.writeFileSync(path.join(tmpDir, "proxy.js"), "module.exports = function proxy() {}");
      const result = findMiddlewareFile(tmpDir);
      expect(result).not.toBeNull();
      expect(result).toContain("proxy.js");
    } finally {
      fs.rmSync(tmpDir, { recursive: true, force: true });
    }
  });
});

// ---------------------------------------------------------------------------
// matchPattern / matchesMiddleware unit tests

describe("middleware matcher patterns", () => {
  it("matchPattern: exact path match", async () => {
    const { matchPattern } = await import(
      "../packages/vinext/src/server/middleware.js"
    );
    expect(matchPattern("/about", "/about")).toBe(true);
    expect(matchPattern("/about", "/other")).toBe(false);
    expect(matchPattern("/", "/")).toBe(true);
  });

  it("matchPattern: named parameter (:param)", async () => {
    const { matchPattern } = await import(
      "../packages/vinext/src/server/middleware.js"
    );
    expect(matchPattern("/user/123", "/user/:id")).toBe(true);
    expect(matchPattern("/user/abc", "/user/:id")).toBe(true);
    expect(matchPattern("/user/", "/user/:id")).toBe(false);
    expect(matchPattern("/user/123/posts", "/user/:id")).toBe(false);
  });

  it("matchPattern: wildcard (:path*) matches zero or more segments", async () => {
    const { matchPattern } = await import(
      "../packages/vinext/src/server/middleware.js"
    );
    expect(matchPattern("/dashboard", "/dashboard/:path*")).toBe(true);
    expect(matchPattern("/dashboard/settings", "/dashboard/:path*")).toBe(true);
    expect(matchPattern("/dashboard/settings/profile", "/dashboard/:path*")).toBe(true);
    expect(matchPattern("/other", "/dashboard/:path*")).toBe(false);
  });

  it("matchPattern: one-or-more (:path+) requires at least one segment", async () => {
    const { matchPattern } = await import(
      "../packages/vinext/src/server/middleware.js"
    );
    expect(matchPattern("/api/users", "/api/:path+")).toBe(true);
    expect(matchPattern("/api/users/123", "/api/:path+")).toBe(true);
    expect(matchPattern("/api", "/api/:path+")).toBe(false);
    // /api/ has no actual segment after the slash
    expect(matchPattern("/api/", "/api/:path+")).toBe(false);
  });

  it("matchPattern: regex patterns with groups", async () => {
    const { matchPattern } = await import(
      "../packages/vinext/src/server/middleware.js"
    );
    // Common Next.js matcher: /((?!api|_next|favicon\.ico).*)
    expect(matchPattern("/about", "/((?!api|_next|favicon\\.ico).*)")).toBe(true);
    expect(matchPattern("/dashboard/settings", "/((?!api|_next|favicon\\.ico).*)")).toBe(true);
    expect(matchPattern("/api/hello", "/((?!api|_next|favicon\\.ico).*)")).toBe(false);
    expect(matchPattern("/_next/static/chunk.js", "/((?!api|_next|favicon\\.ico).*)")).toBe(false);
  });

  it("matchPattern: dots are escaped in paths", async () => {
    const { matchPattern } = await import(
      "../packages/vinext/src/server/middleware.js"
    );
    expect(matchPattern("/files/data.json", "/files/data.json")).toBe(true);
    expect(matchPattern("/files/dataXjson", "/files/data.json")).toBe(false);
  });

  it("matchesMiddleware: no matcher — default exclusions", async () => {
    const { matchesMiddleware } = await import(
      "../packages/vinext/src/server/middleware.js"
    );
    // Default: matches most paths
    expect(matchesMiddleware("/", undefined)).toBe(true);
    expect(matchesMiddleware("/about", undefined)).toBe(true);
    expect(matchesMiddleware("/dashboard/settings", undefined)).toBe(true);

    // Default: excludes /_next, /api, files with dots, /favicon.ico
    expect(matchesMiddleware("/_next/static/chunk.js", undefined)).toBe(false);
    expect(matchesMiddleware("/api/hello", undefined)).toBe(false);
    expect(matchesMiddleware("/favicon.ico", undefined)).toBe(false);
    expect(matchesMiddleware("/image.png", undefined)).toBe(false);
  });

  it("matchesMiddleware: single string matcher", async () => {
    const { matchesMiddleware } = await import(
      "../packages/vinext/src/server/middleware.js"
    );
    expect(matchesMiddleware("/about", "/about")).toBe(true);
    expect(matchesMiddleware("/other", "/about")).toBe(false);
  });

  it("matchesMiddleware: array of string matchers", async () => {
    const { matchesMiddleware } = await import(
      "../packages/vinext/src/server/middleware.js"
    );
    const matcher = ["/about", "/dashboard/:path*"];
    expect(matchesMiddleware("/about", matcher)).toBe(true);
    expect(matchesMiddleware("/dashboard", matcher)).toBe(true);
    expect(matchesMiddleware("/dashboard/settings", matcher)).toBe(true);
    expect(matchesMiddleware("/other", matcher)).toBe(false);
  });

  it("matchesMiddleware: array of object matchers with source", async () => {
    const { matchesMiddleware } = await import(
      "../packages/vinext/src/server/middleware.js"
    );
    const matcher = [
      { source: "/about" },
      { source: "/dashboard/:path*" },
    ];
    expect(matchesMiddleware("/about", matcher)).toBe(true);
    expect(matchesMiddleware("/dashboard/settings", matcher)).toBe(true);
    expect(matchesMiddleware("/other", matcher)).toBe(false);
  });

  it("matchesMiddleware: mixed array of strings and objects", async () => {
    const { matchesMiddleware } = await import(
      "../packages/vinext/src/server/middleware.js"
    );
    const matcher = ["/about", { source: "/api/:path+" }] as any;
    expect(matchesMiddleware("/about", matcher)).toBe(true);
    expect(matchesMiddleware("/api/users", matcher)).toBe(true);
    expect(matchesMiddleware("/api", matcher)).toBe(false);
    expect(matchesMiddleware("/other", matcher)).toBe(false);
  });

  it("matchPattern: rejects pathological ReDoS patterns", async () => {
    const { matchPattern } = await import(
      "../packages/vinext/src/server/middleware.js"
    );
    // Pathological pattern: (a+)+ causes catastrophic backtracking
    // matchPattern should return false (no match) instead of hanging
    expect(matchPattern("/aaaaaaaaaaaaaaaaaaaac", "(a+)+b")).toBe(false);
  });
});

// ---------------------------------------------------------------------------
// RequestCookies comprehensive tests

describe("RequestCookies API", () => {
  it("get() returns cookie by name", async () => {
    const { RequestCookies } = await import(
      "../packages/vinext/src/shims/server.js"
    );
    const headers = new Headers({ cookie: "token=abc123; session=xyz" });
    const cookies = new RequestCookies(headers);

    const token = cookies.get("token");
    expect(token).toEqual({ name: "token", value: "abc123" });

    const session = cookies.get("session");
    expect(session).toEqual({ name: "session", value: "xyz" });
  });

  it("get() returns undefined for missing cookie", async () => {
    const { RequestCookies } = await import(
      "../packages/vinext/src/shims/server.js"
    );
    const headers = new Headers({ cookie: "token=abc123" });
    const cookies = new RequestCookies(headers);

    expect(cookies.get("missing")).toBeUndefined();
  });

  it("getAll() returns all cookies", async () => {
    const { RequestCookies } = await import(
      "../packages/vinext/src/shims/server.js"
    );
    const headers = new Headers({ cookie: "a=1; b=2; c=3" });
    const cookies = new RequestCookies(headers);

    const all = cookies.getAll();
    expect(all).toHaveLength(3);
    expect(all).toContainEqual({ name: "a", value: "1" });
    expect(all).toContainEqual({ name: "b", value: "2" });
    expect(all).toContainEqual({ name: "c", value: "3" });
  });

  it("has() checks cookie existence", async () => {
    const { RequestCookies } = await import(
      "../packages/vinext/src/shims/server.js"
    );
    const headers = new Headers({ cookie: "token=abc" });
    const cookies = new RequestCookies(headers);

    expect(cookies.has("token")).toBe(true);
    expect(cookies.has("missing")).toBe(false);
  });

  it("iterator yields [name, entry] pairs", async () => {
    const { RequestCookies } = await import(
      "../packages/vinext/src/shims/server.js"
    );
    const headers = new Headers({ cookie: "x=1; y=2" });
    const cookies = new RequestCookies(headers);

    const entries = [...cookies];
    expect(entries).toHaveLength(2);
    expect(entries[0][0]).toBe("x");
    expect(entries[0][1]).toEqual({ name: "x", value: "1" });
    expect(entries[1][0]).toBe("y");
    expect(entries[1][1]).toEqual({ name: "y", value: "2" });
  });

  it("handles empty cookie header", async () => {
    const { RequestCookies } = await import(
      "../packages/vinext/src/shims/server.js"
    );
    const headers = new Headers();
    const cookies = new RequestCookies(headers);

    expect(cookies.getAll()).toHaveLength(0);
    expect(cookies.get("any")).toBeUndefined();
    expect(cookies.has("any")).toBe(false);
  });

  it("handles cookies with = in value", async () => {
    const { RequestCookies } = await import(
      "../packages/vinext/src/shims/server.js"
    );
    const headers = new Headers({ cookie: "data=base64=encoded=value" });
    const cookies = new RequestCookies(headers);

    const data = cookies.get("data");
    expect(data).toBeDefined();
    expect(data!.value).toBe("base64=encoded=value");
  });
});

// ---------------------------------------------------------------------------
// ResponseCookies comprehensive tests

describe("ResponseCookies API", () => {
  it("set() creates Set-Cookie header with options", async () => {
    const { ResponseCookies } = await import(
      "../packages/vinext/src/shims/server.js"
    );
    const headers = new Headers();
    const cookies = new ResponseCookies(headers);

    cookies.set("token", "abc123", {
      path: "/",
      httpOnly: true,
      secure: true,
      sameSite: "Lax",
      maxAge: 3600,
    });

    const setCookie = headers.getSetCookie();
    expect(setCookie).toHaveLength(1);
    expect(setCookie[0]).toContain("token=abc123");
    expect(setCookie[0]).toContain("Path=/");
    expect(setCookie[0]).toContain("HttpOnly");
    expect(setCookie[0]).toContain("Secure");
    expect(setCookie[0]).toContain("SameSite=Lax");
    expect(setCookie[0]).toContain("Max-Age=3600");
  });

  it("set() multiple cookies appends multiple Set-Cookie headers", async () => {
    const { ResponseCookies } = await import(
      "../packages/vinext/src/shims/server.js"
    );
    const headers = new Headers();
    const cookies = new ResponseCookies(headers);

    cookies.set("a", "1");
    cookies.set("b", "2");

    const setCookie = headers.getSetCookie();
    expect(setCookie).toHaveLength(2);
    expect(setCookie[0]).toContain("a=1");
    expect(setCookie[1]).toContain("b=2");
  });

  it("get() retrieves a cookie from Set-Cookie headers", async () => {
    const { ResponseCookies } = await import(
      "../packages/vinext/src/shims/server.js"
    );
    const headers = new Headers();
    const cookies = new ResponseCookies(headers);

    cookies.set("token", "xyz");
    const result = cookies.get("token");
    expect(result).toEqual({ name: "token", value: "xyz" });
  });

  it("getAll() returns all set cookies", async () => {
    const { ResponseCookies } = await import(
      "../packages/vinext/src/shims/server.js"
    );
    const headers = new Headers();
    const cookies = new ResponseCookies(headers);

    cookies.set("a", "1");
    cookies.set("b", "2");
    cookies.set("c", "3");

    const all = cookies.getAll();
    expect(all).toHaveLength(3);
    expect(all).toContainEqual({ name: "a", value: "1" });
    expect(all).toContainEqual({ name: "b", value: "2" });
    expect(all).toContainEqual({ name: "c", value: "3" });
  });

  it("delete() sets Max-Age=0", async () => {
    const { ResponseCookies } = await import(
      "../packages/vinext/src/shims/server.js"
    );
    const headers = new Headers();
    const cookies = new ResponseCookies(headers);

    cookies.delete("session");

    const setCookie = headers.getSetCookie();
    expect(setCookie).toHaveLength(1);
    expect(setCookie[0]).toContain("session=");
    expect(setCookie[0]).toContain("Max-Age=0");
    expect(setCookie[0]).toContain("Path=/");
  });

  it("set() URL-encodes cookie values", async () => {
    const { ResponseCookies } = await import(
      "../packages/vinext/src/shims/server.js"
    );
    const headers = new Headers();
    const cookies = new ResponseCookies(headers);

    cookies.set("data", "hello world; special=chars");

    const setCookie = headers.getSetCookie();
    expect(setCookie[0]).toContain("data=hello%20world%3B%20special%3Dchars");

    // get() should decode it back
    const result = cookies.get("data");
    expect(result?.value).toBe("hello world; special=chars");
  });

  it("iterator yields [name, entry] pairs", async () => {
    const { ResponseCookies } = await import(
      "../packages/vinext/src/shims/server.js"
    );
    const headers = new Headers();
    const cookies = new ResponseCookies(headers);

    cookies.set("x", "1");
    cookies.set("y", "2");

    const entries = [...cookies];
    expect(entries).toHaveLength(2);
    expect(entries[0][0]).toBe("x");
    expect(entries[0][1]).toEqual({ name: "x", value: "1" });
    expect(entries[1][0]).toBe("y");
    expect(entries[1][1]).toEqual({ name: "y", value: "2" });
  });

  it("set() with domain option includes Domain directive", async () => {
    const { ResponseCookies } = await import(
      "../packages/vinext/src/shims/server.js"
    );
    const headers = new Headers();
    const cookies = new ResponseCookies(headers);

    cookies.set("token", "abc", { domain: ".example.com" });

    const setCookie = headers.getSetCookie();
    expect(setCookie[0]).toContain("Domain=.example.com");
  });

  it("set() with expires option includes Expires directive", async () => {
    const { ResponseCookies } = await import(
      "../packages/vinext/src/shims/server.js"
    );
    const headers = new Headers();
    const cookies = new ResponseCookies(headers);

    const expires = new Date("2030-01-01T00:00:00Z");
    cookies.set("token", "abc", { expires });

    const setCookie = headers.getSetCookie();
    expect(setCookie[0]).toContain("Expires=");
    expect(setCookie[0]).toContain("2030");
  });
});

// ---------------------------------------------------------------------------
// Cookie name/value injection prevention (RFC 6265)

describe("cookie name validation", () => {
  it("RequestCookies.set() rejects names with = (injection)", async () => {
    const headersModule = await import("../packages/vinext/src/shims/headers.js");
    headersModule.setHeadersContext({ headers: new Headers(), cookies: new Map() });
    const jar = await headersModule.cookies();
    expect(() => jar.set("foo=bar; Path=/; Domain=evil.com", "val")).toThrow("Invalid cookie name");
  });

  it("RequestCookies.set() rejects names with semicolons", async () => {
    const headersModule = await import("../packages/vinext/src/shims/headers.js");
    headersModule.setHeadersContext({ headers: new Headers(), cookies: new Map() });
    const jar = await headersModule.cookies();
    expect(() => jar.set("foo; HttpOnly", "val")).toThrow("Invalid cookie name");
  });

  it("RequestCookies.set() rejects names with newlines", async () => {
    const headersModule = await import("../packages/vinext/src/shims/headers.js");
    headersModule.setHeadersContext({ headers: new Headers(), cookies: new Map() });
    const jar = await headersModule.cookies();
    expect(() => jar.set("foo\r\nSet-Cookie: evil=1", "val")).toThrow("Invalid cookie name");
  });

  it("RequestCookies.set() rejects empty names", async () => {
    const headersModule = await import("../packages/vinext/src/shims/headers.js");
    headersModule.setHeadersContext({ headers: new Headers(), cookies: new Map() });
    const jar = await headersModule.cookies();
    expect(() => jar.set("", "val")).toThrow("Invalid cookie name");
  });

  it("RequestCookies.set() accepts valid cookie names", async () => {
    const headersModule = await import("../packages/vinext/src/shims/headers.js");
    headersModule.setHeadersContext({ headers: new Headers(), cookies: new Map() });
    const jar = await headersModule.cookies();
    // These should not throw
    jar.set("valid-name", "value");
    jar.set("__Host-token", "value");
    jar.set("session_id", "value");
    jar.set("CSRF.Token", "value");
  });

  it("RequestCookies.delete() rejects invalid names", async () => {
    const headersModule = await import("../packages/vinext/src/shims/headers.js");
    headersModule.setHeadersContext({ headers: new Headers(), cookies: new Map() });
    const jar = await headersModule.cookies();
    expect(() => jar.delete("foo=bar")).toThrow("Invalid cookie name");
  });

  it("RequestCookies.set() rejects path with semicolons", async () => {
    const headersModule = await import("../packages/vinext/src/shims/headers.js");
    headersModule.setHeadersContext({ headers: new Headers(), cookies: new Map() });
    const jar = await headersModule.cookies();
    expect(() => jar.set("name", "val", { path: "/; Domain=evil.com" })).toThrow("Invalid cookie Path");
  });

  it("ResponseCookies.set() rejects names with = (injection)", async () => {
    const { ResponseCookies } = await import("../packages/vinext/src/shims/server.js");
    const headers = new Headers();
    const cookies = new ResponseCookies(headers);
    expect(() => cookies.set("foo=bar; Path=/", "val")).toThrow("Invalid cookie name");
  });

  it("ResponseCookies.set() rejects domain with control chars", async () => {
    const { ResponseCookies } = await import("../packages/vinext/src/shims/server.js");
    const headers = new Headers();
    const cookies = new ResponseCookies(headers);
    expect(() => cookies.set("name", "val", { domain: "evil.com\r\nSet-Cookie: hack=1" })).toThrow("Invalid cookie Domain");
  });

  it("ResponseCookies.set() accepts valid cookie names and options", async () => {
    const { ResponseCookies } = await import("../packages/vinext/src/shims/server.js");
    const headers = new Headers();
    const cookies = new ResponseCookies(headers);
    // These should not throw
    cookies.set("valid-name", "value", { path: "/", domain: ".example.com" });
    cookies.set("__Secure-token", "abc", { secure: true, httpOnly: true });
  });
});

// ---------------------------------------------------------------------------
// NextRequest API tests

describe("NextRequest API", () => {
  it("cookies reads request cookies", async () => {
    const { NextRequest } = await import(
      "../packages/vinext/src/shims/server.js"
    );
    const req = new NextRequest("http://localhost/test", {
      headers: { cookie: "session=abc; theme=dark" },
    });

    expect(req.cookies.get("session")).toEqual({ name: "session", value: "abc" });
    expect(req.cookies.get("theme")).toEqual({ name: "theme", value: "dark" });
    expect(req.cookies.has("session")).toBe(true);
    expect(req.cookies.has("missing")).toBe(false);
  });

  it("nextUrl provides URL properties", async () => {
    const { NextRequest } = await import(
      "../packages/vinext/src/shims/server.js"
    );
    const req = new NextRequest("http://localhost:3000/api/test?key=value#hash");

    expect(req.nextUrl.pathname).toBe("/api/test");
    expect(req.nextUrl.search).toBe("?key=value");
    expect(req.nextUrl.searchParams.get("key")).toBe("value");
    expect(req.nextUrl.host).toBe("localhost:3000");
    expect(req.nextUrl.hostname).toBe("localhost");
    expect(req.nextUrl.protocol).toBe("http:");
    expect(req.nextUrl.hash).toBe("#hash");
  });

  it("nextUrl.clone() creates independent copy", async () => {
    const { NextRequest } = await import(
      "../packages/vinext/src/shims/server.js"
    );
    const req = new NextRequest("http://localhost/test");
    const cloned = req.nextUrl.clone();

    cloned.pathname = "/other";
    expect(req.nextUrl.pathname).toBe("/test");
    expect(cloned.pathname).toBe("/other");
  });

  it("ip reads x-forwarded-for header", async () => {
    const { NextRequest } = await import(
      "../packages/vinext/src/shims/server.js"
    );
    const req = new NextRequest("http://localhost/", {
      headers: { "x-forwarded-for": "1.2.3.4, 5.6.7.8" },
    });
    expect(req.ip).toBe("1.2.3.4");
  });

  it("ip returns undefined when no header", async () => {
    const { NextRequest } = await import(
      "../packages/vinext/src/shims/server.js"
    );
    const req = new NextRequest("http://localhost/");
    expect(req.ip).toBeUndefined();
  });

  it("geo reads Cloudflare headers", async () => {
    const { NextRequest } = await import(
      "../packages/vinext/src/shims/server.js"
    );
    const req = new NextRequest("http://localhost/", {
      headers: {
        "cf-ipcountry": "US",
        "cf-ipcity": "San Francisco",
      },
    });
    expect(req.geo?.country).toBe("US");
    expect(req.geo?.city).toBe("San Francisco");
  });

  it("geo returns undefined when no geo headers", async () => {
    const { NextRequest } = await import(
      "../packages/vinext/src/shims/server.js"
    );
    const req = new NextRequest("http://localhost/");
    expect(req.geo).toBeUndefined();
  });
});

// ---------------------------------------------------------------------------
// NextResponse.next() with request header forwarding

describe("NextResponse.next() request header forwarding", () => {
  it("forwards request headers as x-middleware-request-* headers", async () => {
    const { NextResponse } = await import(
      "../packages/vinext/src/shims/server.js"
    );
    const res = NextResponse.next({
      request: {
        headers: new Headers({
          "x-custom-header": "custom-value",
          "authorization": "Bearer token123",
        }),
      },
    });

    expect(res.headers.get("x-middleware-next")).toBe("1");
    expect(res.headers.get("x-middleware-request-x-custom-header")).toBe("custom-value");
    expect(res.headers.get("x-middleware-request-authorization")).toBe("Bearer token123");
  });
});

// ---------------------------------------------------------------------------
// NextResponse.redirect() with different status codes

describe("NextResponse.redirect() status codes", () => {
  it("defaults to 307 Temporary Redirect", async () => {
    const { NextResponse } = await import(
      "../packages/vinext/src/shims/server.js"
    );
    const res = NextResponse.redirect("https://example.com");
    expect(res.status).toBe(307);
  });

  it("supports 301 Permanent Redirect", async () => {
    const { NextResponse } = await import(
      "../packages/vinext/src/shims/server.js"
    );
    const res = NextResponse.redirect("https://example.com", 301);
    expect(res.status).toBe(301);
    expect(res.headers.get("Location")).toBe("https://example.com");
  });

  it("supports 302 Found", async () => {
    const { NextResponse } = await import(
      "../packages/vinext/src/shims/server.js"
    );
    const res = NextResponse.redirect("https://example.com", 302);
    expect(res.status).toBe(302);
  });

  it("supports 308 Permanent Redirect", async () => {
    const { NextResponse } = await import(
      "../packages/vinext/src/shims/server.js"
    );
    const res = NextResponse.redirect("https://example.com", 308);
    expect(res.status).toBe(308);
  });

  it("accepts URL object", async () => {
    const { NextResponse } = await import(
      "../packages/vinext/src/shims/server.js"
    );
    const url = new URL("https://example.com/target");
    const res = NextResponse.redirect(url);
    expect(res.headers.get("Location")).toBe("https://example.com/target");
  });
});

// ---------------------------------------------------------------------------
// matchConfigPattern unit tests (next.config.js redirects/rewrites)

describe("matchConfigPattern", () => {
  it("matches exact paths", async () => {
    const { matchConfigPattern } = await import(
      "../packages/vinext/src/index.js"
    );
    expect(matchConfigPattern("/about", "/about")).toEqual({});
    expect(matchConfigPattern("/", "/")).toEqual({});
    expect(matchConfigPattern("/about", "/other")).toBeNull();
  });

  it("matches single :param segments", async () => {
    const { matchConfigPattern } = await import(
      "../packages/vinext/src/index.js"
    );
    const result = matchConfigPattern("/blog/hello-world", "/blog/:slug");
    expect(result).toEqual({ slug: "hello-world" });
  });

  it("matches multiple :param segments", async () => {
    const { matchConfigPattern } = await import(
      "../packages/vinext/src/index.js"
    );
    const result = matchConfigPattern("/blog/2024/my-post", "/blog/:year/:slug");
    expect(result).toEqual({ year: "2024", slug: "my-post" });
  });

  it("rejects when segment count differs for non-wildcard patterns", async () => {
    const { matchConfigPattern } = await import(
      "../packages/vinext/src/index.js"
    );
    expect(matchConfigPattern("/blog/a/b", "/blog/:slug")).toBeNull();
    expect(matchConfigPattern("/blog", "/blog/:slug")).toBeNull();
  });

  it("matches :path* catch-all (zero or more segments)", async () => {
    const { matchConfigPattern } = await import(
      "../packages/vinext/src/index.js"
    );
    // Zero segments
    expect(matchConfigPattern("/docs", "/docs/:path*")).toEqual({ path: "" });
    // One segment
    expect(matchConfigPattern("/docs/intro", "/docs/:path*")).toEqual({ path: "intro" });
    // Multiple segments
    expect(matchConfigPattern("/docs/guide/getting-started", "/docs/:path*")).toEqual({
      path: "guide/getting-started",
    });
  });

  it("matches :path+ catch-all (one or more segments)", async () => {
    const { matchConfigPattern } = await import(
      "../packages/vinext/src/index.js"
    );
    // One segment
    expect(matchConfigPattern("/api/users", "/api/:path+")).toEqual({ path: "users" });
    // Multiple segments
    expect(matchConfigPattern("/api/users/123", "/api/:path+")).toEqual({ path: "users/123" });
    // Zero segments — should NOT match
    expect(matchConfigPattern("/api", "/api/:path+")).toBeNull();
  });

  it("matches regex group patterns", async () => {
    const { matchConfigPattern } = await import(
      "../packages/vinext/src/index.js"
    );
    // Common Next.js pattern: /:path(\\d+) for numeric paths
    const result = matchConfigPattern("/123", "/:id(\\d+)");
    if (result) {
      expect(result.id).toBe("123");
    }
    // Non-numeric should not match
    expect(matchConfigPattern("/abc", "/:id(\\d+)")).toBeNull();
  });

  it("handles dots in patterns", async () => {
    const { matchConfigPattern } = await import(
      "../packages/vinext/src/index.js"
    );
    expect(matchConfigPattern("/feed.xml", "/feed.xml")).toEqual({});
    // Dot should not match any character
    expect(matchConfigPattern("/feedXxml", "/feed.xml")).toBeNull();
  });

  it("matches :path* with literal suffix (e.g. /:path*.md)", async () => {
    const { matchConfigPattern } = await import(
      "../packages/vinext/src/index.js"
    );
    // Should match URLs ending in .md
    expect(matchConfigPattern("/article.md", "/:path*.md")).toEqual({ path: "article" });
    expect(matchConfigPattern("/news/my-article.md", "/:path*.md")).toEqual({ path: "news/my-article" });
    expect(matchConfigPattern("/docs/guide/intro.md", "/:path*.md")).toEqual({ path: "docs/guide/intro" });
    // Should NOT match URLs without .md suffix
    expect(matchConfigPattern("/", "/:path*.md")).toBeNull();
    expect(matchConfigPattern("/about", "/:path*.md")).toBeNull();
    expect(matchConfigPattern("/news", "/:path*.md")).toBeNull();
    expect(matchConfigPattern("/article.txt", "/:path*.md")).toBeNull();
  });

  it("matches :path+ with literal suffix (e.g. /:path+.json)", async () => {
    const { matchConfigPattern } = await import(
      "../packages/vinext/src/index.js"
    );
    // Should match URLs ending in .json with at least one path segment
    expect(matchConfigPattern("/data.json", "/:path+.json")).toEqual({ path: "data" });
    expect(matchConfigPattern("/api/users.json", "/:path+.json")).toEqual({ path: "api/users" });
    // Should NOT match bare .json (zero segments before suffix)
    expect(matchConfigPattern("/.json", "/:path+.json")).toBeNull();
    // Should NOT match URLs without .json suffix
    expect(matchConfigPattern("/data", "/:path+.json")).toBeNull();
    expect(matchConfigPattern("/", "/:path+.json")).toBeNull();
  });

  it("matches :path* with prefix and suffix (e.g. /docs/:path*.md)", async () => {
    const { matchConfigPattern } = await import(
      "../packages/vinext/src/index.js"
    );
    expect(matchConfigPattern("/docs/intro.md", "/docs/:path*.md")).toEqual({ path: "intro" });
    expect(matchConfigPattern("/docs/guide/getting-started.md", "/docs/:path*.md")).toEqual({
      path: "guide/getting-started",
    });
    // Should NOT match without .md
    expect(matchConfigPattern("/docs/intro", "/docs/:path*.md")).toBeNull();
    // Should NOT match different prefix
    expect(matchConfigPattern("/blog/intro.md", "/docs/:path*.md")).toBeNull();
  });

  it("still matches plain :path* catch-all (no suffix) correctly", async () => {
    const { matchConfigPattern } = await import(
      "../packages/vinext/src/index.js"
    );
    // Ensure the fix doesn't regress existing catch-all behavior
    expect(matchConfigPattern("/docs", "/docs/:path*")).toEqual({ path: "" });
    expect(matchConfigPattern("/docs/intro", "/docs/:path*")).toEqual({ path: "intro" });
    expect(matchConfigPattern("/docs/guide/getting-started", "/docs/:path*")).toEqual({
      path: "guide/getting-started",
    });
  });
});

// ---------------------------------------------------------------------------
// isSafeRegex / safeRegExp unit tests (ReDoS prevention)

describe("isSafeRegex", () => {
  it("accepts simple patterns", async () => {
    const { isSafeRegex } = await import(
      "../packages/vinext/src/config/config-matchers.js"
    );
    expect(isSafeRegex("^/about$")).toBe(true);
    expect(isSafeRegex("^/blog/[^/]+$")).toBe(true);
    expect(isSafeRegex("^/docs/(.*)$")).toBe(true);
    expect(isSafeRegex("^/api/(.+)$")).toBe(true);
    expect(isSafeRegex("\\d+")).toBe(true);
    expect(isSafeRegex("^/feed\\.xml$")).toBe(true);
  });

  it("accepts non-nested quantifiers inside groups", async () => {
    const { isSafeRegex } = await import(
      "../packages/vinext/src/config/config-matchers.js"
    );
    // A single quantifier inside a group without a quantifier on the group itself
    expect(isSafeRegex("(a+)")).toBe(true);
    expect(isSafeRegex("([^/]+)")).toBe(true);
    expect(isSafeRegex("(\\d{2,4})")).toBe(true);
  });

  it("rejects nested quantifiers: (a+)+", async () => {
    const { isSafeRegex } = await import(
      "../packages/vinext/src/config/config-matchers.js"
    );
    expect(isSafeRegex("(a+)+")).toBe(false);
  });

  it("rejects nested quantifiers: (a+)*", async () => {
    const { isSafeRegex } = await import(
      "../packages/vinext/src/config/config-matchers.js"
    );
    expect(isSafeRegex("(a+)*")).toBe(false);
  });

  it("rejects nested quantifiers: (.*)*", async () => {
    const { isSafeRegex } = await import(
      "../packages/vinext/src/config/config-matchers.js"
    );
    expect(isSafeRegex("(.*)*")).toBe(false);
  });

  it("rejects nested quantifiers: (a*)+", async () => {
    const { isSafeRegex } = await import(
      "../packages/vinext/src/config/config-matchers.js"
    );
    expect(isSafeRegex("(a*)+")).toBe(false);
  });

  it("rejects nested quantifiers: ([^/]+)+", async () => {
    const { isSafeRegex } = await import(
      "../packages/vinext/src/config/config-matchers.js"
    );
    expect(isSafeRegex("([^/]+)+")).toBe(false);
  });

  it("rejects nested quantifiers with braces: (a+){2,}", async () => {
    const { isSafeRegex } = await import(
      "../packages/vinext/src/config/config-matchers.js"
    );
    expect(isSafeRegex("(a+){2,}")).toBe(false);
  });

  it("accepts quantifier on group without inner quantifier", async () => {
    const { isSafeRegex } = await import(
      "../packages/vinext/src/config/config-matchers.js"
    );
    // (ab)+ is fine — no inner quantifier
    expect(isSafeRegex("(ab)+")).toBe(true);
    expect(isSafeRegex("(foo|bar)*")).toBe(true);
  });

  it("treats escaped characters as safe", async () => {
    const { isSafeRegex } = await import(
      "../packages/vinext/src/config/config-matchers.js"
    );
    // \\+ is a literal +, not a quantifier
    expect(isSafeRegex("(a\\+)+")).toBe(true);
  });

  it("treats quantifiers inside character classes as safe", async () => {
    const { isSafeRegex } = await import(
      "../packages/vinext/src/config/config-matchers.js"
    );
    // [+*] is a character class, not a quantifier
    expect(isSafeRegex("([+*])+")).toBe(true);
  });

  it("rejects nested optional quantifiers: (a?)+", async () => {
    const { isSafeRegex } = await import(
      "../packages/vinext/src/config/config-matchers.js"
    );
    // '?' inside group + quantifier on group = catastrophic backtracking
    expect(isSafeRegex("(a?)+")).toBe(false);
    expect(isSafeRegex("(a?)+b")).toBe(false);
  });

  it("rejects nested optional quantifiers: (.?)+", async () => {
    const { isSafeRegex } = await import(
      "../packages/vinext/src/config/config-matchers.js"
    );
    expect(isSafeRegex("(.?)+")).toBe(false);
  });

  it("rejects nested optional quantifiers: (a?)*", async () => {
    const { isSafeRegex } = await import(
      "../packages/vinext/src/config/config-matchers.js"
    );
    expect(isSafeRegex("(a?)*")).toBe(false);
  });

  it("accepts outer '?' on group (zero-or-one is not unbounded repetition)", async () => {
    const { isSafeRegex } = await import(
      "../packages/vinext/src/config/config-matchers.js"
    );
    // '?' means zero or one — only 2 paths, not exponential backtracking
    // This is safe even with inner quantifiers (e.g. URL patterns like (?:/.*)?  )
    expect(isSafeRegex("(a+)?")).toBe(true);
    expect(isSafeRegex("(?:/.*)?")).toBe(true);
  });

  it("treats non-greedy modifier as safe, not as quantifier", async () => {
    const { isSafeRegex } = await import(
      "../packages/vinext/src/config/config-matchers.js"
    );
    // a+? is non-greedy '+', not a nested quantifier
    expect(isSafeRegex("(a+?)")).toBe(true);
    // (a*?) is non-greedy '*', still just one quantifier
    expect(isSafeRegex("(a*?)")).toBe(true);
  });
});

describe("safeRegExp", () => {
  it("returns RegExp for safe patterns", async () => {
    const { safeRegExp } = await import(
      "../packages/vinext/src/config/config-matchers.js"
    );
    const re = safeRegExp("^/about$");
    expect(re).toBeInstanceOf(RegExp);
    expect(re!.test("/about")).toBe(true);
  });

  it("returns null for pathological patterns", async () => {
    const { safeRegExp } = await import(
      "../packages/vinext/src/config/config-matchers.js"
    );
    const re = safeRegExp("(a+)+b");
    expect(re).toBeNull();
  });

  it("returns null for invalid regex syntax", async () => {
    const { safeRegExp } = await import(
      "../packages/vinext/src/config/config-matchers.js"
    );
    const re = safeRegExp("(?P<name>");
    expect(re).toBeNull();
  });
});

describe("matchConfigPattern rejects ReDoS patterns", () => {
  it("returns null for pathological source patterns", async () => {
    const { matchConfigPattern } = await import(
      "../packages/vinext/src/config/config-matchers.js"
    );
    // This pattern has nested quantifiers: the compiled regex would be (a+)+b
    // which causes catastrophic backtracking. matchConfigPattern should return
    // null (no match) rather than hanging.
    const result = matchConfigPattern(
      "/aaaaaaaaaaaaaaaaaaaac",
      "/:id((a+)+b)",
    );
    expect(result).toBeNull();
  });
});

describe("matchConfigPattern handles parameterized suffix patterns", () => {
  it("matches :path* with literal suffix (e.g. /:path*.md)", async () => {
    const { matchConfigPattern } = await import(
      "../packages/vinext/src/config/config-matchers.js"
    );
    // Should match URLs ending in .md
    expect(matchConfigPattern("/article.md", "/:path*.md")).toEqual({ path: "article" });
    expect(matchConfigPattern("/news/my-article.md", "/:path*.md")).toEqual({ path: "news/my-article" });
    // Should NOT match URLs without .md suffix
    expect(matchConfigPattern("/", "/:path*.md")).toBeNull();
    expect(matchConfigPattern("/about", "/:path*.md")).toBeNull();
    expect(matchConfigPattern("/news", "/:path*.md")).toBeNull();
  });

  it("matches :path+ with literal suffix (e.g. /:path+.json)", async () => {
    const { matchConfigPattern } = await import(
      "../packages/vinext/src/config/config-matchers.js"
    );
    expect(matchConfigPattern("/data.json", "/:path+.json")).toEqual({ path: "data" });
    expect(matchConfigPattern("/api/users.json", "/:path+.json")).toEqual({ path: "api/users" });
    // Zero segments before suffix — should NOT match for :path+
    expect(matchConfigPattern("/.json", "/:path+.json")).toBeNull();
    expect(matchConfigPattern("/", "/:path+.json")).toBeNull();
  });

  it("does not regress plain :path* catch-all (no suffix)", async () => {
    const { matchConfigPattern } = await import(
      "../packages/vinext/src/config/config-matchers.js"
    );
    expect(matchConfigPattern("/docs", "/docs/:path*")).toEqual({ path: "" });
    expect(matchConfigPattern("/docs/intro", "/docs/:path*")).toEqual({ path: "intro" });
    expect(matchConfigPattern("/docs/guide/getting-started", "/docs/:path*")).toEqual({
      path: "guide/getting-started",
    });
  });
});

// ---------------------------------------------------------------------------
// has/missing condition matching unit tests (next.config.js redirects/rewrites)

describe("parseCookies", () => {
  it("parses standard cookie header", async () => {
    const { parseCookies } = await import(
      "../packages/vinext/src/config/config-matchers.js"
    );
    expect(parseCookies("a=1; b=2; c=three")).toEqual({ a: "1", b: "2", c: "three" });
  });

  it("returns empty object for null", async () => {
    const { parseCookies } = await import(
      "../packages/vinext/src/config/config-matchers.js"
    );
    expect(parseCookies(null)).toEqual({});
  });

  it("returns empty object for empty string", async () => {
    const { parseCookies } = await import(
      "../packages/vinext/src/config/config-matchers.js"
    );
    expect(parseCookies("")).toEqual({});
  });

  it("handles cookies with = in value", async () => {
    const { parseCookies } = await import(
      "../packages/vinext/src/config/config-matchers.js"
    );
    expect(parseCookies("token=abc=def")).toEqual({ token: "abc=def" });
  });

  it("trims whitespace around keys and values", async () => {
    const { parseCookies } = await import(
      "../packages/vinext/src/config/config-matchers.js"
    );
    expect(parseCookies("  a = 1 ;  b = 2 ")).toEqual({ a: "1", b: "2" });
  });
});

describe("checkHasConditions", () => {
  function makeCtx(overrides: Partial<{
    headers: Record<string, string>;
    cookies: Record<string, string>;
    query: Record<string, string>;
    host: string;
  }> = {}) {
    const headers = new Headers(overrides.headers ?? {});
    if (overrides.cookies) {
      headers.set("cookie", Object.entries(overrides.cookies).map(([k, v]) => `${k}=${v}`).join("; "));
    }
    const query = new URLSearchParams(overrides.query ?? {});
    return {
      headers,
      cookies: overrides.cookies ?? {},
      query,
      host: overrides.host ?? "localhost",
    };
  }

  it("returns true when no conditions", async () => {
    const { checkHasConditions } = await import(
      "../packages/vinext/src/config/config-matchers.js"
    );
    expect(checkHasConditions(undefined, undefined, makeCtx())).toBe(true);
  });

  // -- header conditions --
  it("has header: passes when header present", async () => {
    const { checkHasConditions } = await import(
      "../packages/vinext/src/config/config-matchers.js"
    );
    const ctx = makeCtx({ headers: { "x-custom": "yes" } });
    expect(checkHasConditions([{ type: "header", key: "x-custom" }], undefined, ctx)).toBe(true);
  });

  it("has header: fails when header absent", async () => {
    const { checkHasConditions } = await import(
      "../packages/vinext/src/config/config-matchers.js"
    );
    const ctx = makeCtx({});
    expect(checkHasConditions([{ type: "header", key: "x-custom" }], undefined, ctx)).toBe(false);
  });

  it("has header with value: matches regex", async () => {
    const { checkHasConditions } = await import(
      "../packages/vinext/src/config/config-matchers.js"
    );
    const ctx = makeCtx({ headers: { "x-auth": "yes" } });
    expect(checkHasConditions([{ type: "header", key: "x-auth", value: "(?:yes|true)" }], undefined, ctx)).toBe(true);
    expect(checkHasConditions([{ type: "header", key: "x-auth", value: "(?:no|false)" }], undefined, ctx)).toBe(false);
  });

  // -- cookie conditions --
  it("has cookie: passes when cookie present", async () => {
    const { checkHasConditions } = await import(
      "../packages/vinext/src/config/config-matchers.js"
    );
    const ctx = makeCtx({ cookies: { "session": "abc" } });
    expect(checkHasConditions([{ type: "cookie", key: "session" }], undefined, ctx)).toBe(true);
  });

  it("has cookie: fails when cookie absent", async () => {
    const { checkHasConditions } = await import(
      "../packages/vinext/src/config/config-matchers.js"
    );
    const ctx = makeCtx({ cookies: {} });
    expect(checkHasConditions([{ type: "cookie", key: "session" }], undefined, ctx)).toBe(false);
  });

  it("has cookie with exact value", async () => {
    const { checkHasConditions } = await import(
      "../packages/vinext/src/config/config-matchers.js"
    );
    const ctx = makeCtx({ cookies: { "authorized": "true" } });
    expect(checkHasConditions([{ type: "cookie", key: "authorized", value: "true" }], undefined, ctx)).toBe(true);
    expect(checkHasConditions([{ type: "cookie", key: "authorized", value: "false" }], undefined, ctx)).toBe(false);
  });

  // -- query conditions --
  it("has query: passes when query param present", async () => {
    const { checkHasConditions } = await import(
      "../packages/vinext/src/config/config-matchers.js"
    );
    const ctx = makeCtx({ query: { page: "home" } });
    expect(checkHasConditions([{ type: "query", key: "page" }], undefined, ctx)).toBe(true);
  });

  it("has query: fails when query param absent", async () => {
    const { checkHasConditions } = await import(
      "../packages/vinext/src/config/config-matchers.js"
    );
    const ctx = makeCtx({ query: {} });
    expect(checkHasConditions([{ type: "query", key: "page" }], undefined, ctx)).toBe(false);
  });

  it("has query with regex value", async () => {
    const { checkHasConditions } = await import(
      "../packages/vinext/src/config/config-matchers.js"
    );
    const ctx = makeCtx({ query: { page: "home" } });
    expect(checkHasConditions([{ type: "query", key: "page", value: "home|about" }], undefined, ctx)).toBe(true);
    expect(checkHasConditions([{ type: "query", key: "page", value: "^settings$" }], undefined, ctx)).toBe(false);
  });

  // -- host conditions --
  it("has host: matches exact value", async () => {
    const { checkHasConditions } = await import(
      "../packages/vinext/src/config/config-matchers.js"
    );
    const ctx = makeCtx({ host: "example.com" });
    expect(checkHasConditions([{ type: "host", key: "", value: "example.com" }], undefined, ctx)).toBe(true);
    expect(checkHasConditions([{ type: "host", key: "", value: "other.com" }], undefined, ctx)).toBe(false);
  });

  it("has host: matches regex value", async () => {
    const { checkHasConditions } = await import(
      "../packages/vinext/src/config/config-matchers.js"
    );
    const ctx = makeCtx({ host: "staging.example.com" });
    expect(checkHasConditions([{ type: "host", key: "", value: ".*\\.example\\.com" }], undefined, ctx)).toBe(true);
  });

  // -- missing conditions --
  it("missing header: passes when header absent", async () => {
    const { checkHasConditions } = await import(
      "../packages/vinext/src/config/config-matchers.js"
    );
    const ctx = makeCtx({});
    expect(checkHasConditions(undefined, [{ type: "header", key: "x-block" }], ctx)).toBe(true);
  });

  it("missing header: fails when header present", async () => {
    const { checkHasConditions } = await import(
      "../packages/vinext/src/config/config-matchers.js"
    );
    const ctx = makeCtx({ headers: { "x-block": "1" } });
    expect(checkHasConditions(undefined, [{ type: "header", key: "x-block" }], ctx)).toBe(false);
  });

  it("missing cookie: passes when cookie absent", async () => {
    const { checkHasConditions } = await import(
      "../packages/vinext/src/config/config-matchers.js"
    );
    const ctx = makeCtx({ cookies: {} });
    expect(checkHasConditions(undefined, [{ type: "cookie", key: "stay-here" }], ctx)).toBe(true);
  });

  it("missing cookie: fails when cookie present", async () => {
    const { checkHasConditions } = await import(
      "../packages/vinext/src/config/config-matchers.js"
    );
    const ctx = makeCtx({ cookies: { "stay-here": "1" } });
    expect(checkHasConditions(undefined, [{ type: "cookie", key: "stay-here" }], ctx)).toBe(false);
  });

  // -- combined has + missing --
  it("both has and missing must pass", async () => {
    const { checkHasConditions } = await import(
      "../packages/vinext/src/config/config-matchers.js"
    );
    const ctx = makeCtx({ cookies: { "auth": "yes" } });
    // has: cookie auth present (passes), missing: cookie block absent (passes)
    expect(checkHasConditions(
      [{ type: "cookie", key: "auth" }],
      [{ type: "cookie", key: "block" }],
      ctx,
    )).toBe(true);
    // has: cookie auth present (passes), missing: cookie auth absent (fails — it's present)
    expect(checkHasConditions(
      [{ type: "cookie", key: "auth" }],
      [{ type: "cookie", key: "auth" }],
      ctx,
    )).toBe(false);
  });

  it("all has conditions must match (conjunction)", async () => {
    const { checkHasConditions } = await import(
      "../packages/vinext/src/config/config-matchers.js"
    );
    const ctx = makeCtx({ cookies: { "a": "1" }, query: { "page": "home" } });
    // Both match
    expect(checkHasConditions(
      [{ type: "cookie", key: "a" }, { type: "query", key: "page" }],
      undefined,
      ctx,
    )).toBe(true);
    // One doesn't match
    expect(checkHasConditions(
      [{ type: "cookie", key: "a" }, { type: "query", key: "missing" }],
      undefined,
      ctx,
    )).toBe(false);
  });
});

// ---------------------------------------------------------------------------
// isExternalUrl unit tests (external rewrite detection)

describe("isExternalUrl", () => {
  it("returns true for https:// URLs", async () => {
    const { isExternalUrl } = await import(
      "../packages/vinext/src/config/config-matchers.js"
    );
    expect(isExternalUrl("https://example.com/path")).toBe(true);
    expect(isExternalUrl("https://us.i.posthog.com/decide?v=3")).toBe(true);
  });

  it("returns true for http:// URLs", async () => {
    const { isExternalUrl } = await import(
      "../packages/vinext/src/config/config-matchers.js"
    );
    expect(isExternalUrl("http://example.com/api")).toBe(true);
  });

  it("returns false for relative paths", async () => {
    const { isExternalUrl } = await import(
      "../packages/vinext/src/config/config-matchers.js"
    );
    expect(isExternalUrl("/about")).toBe(false);
    expect(isExternalUrl("/api/test")).toBe(false);
    expect(isExternalUrl("/")).toBe(false);
  });

  it("returns false for protocol-relative URLs", async () => {
    const { isExternalUrl } = await import(
      "../packages/vinext/src/config/config-matchers.js"
    );
    expect(isExternalUrl("//example.com")).toBe(false);
  });
});

// ---------------------------------------------------------------------------
// proxyExternalRequest unit tests

describe("proxyExternalRequest", () => {
  it("proxies request to external URL and returns upstream response", async () => {
    const { proxyExternalRequest } = await import(
      "../packages/vinext/src/config/config-matchers.js"
    );

    // Use a well-known public URL that returns a predictable response
    const request = new Request("http://localhost:3000/test?extra=1", {
      method: "GET",
      headers: { "user-agent": "vinext-test" },
    });

    // We test the function constructs the right request by mocking fetch
    const originalFetch = globalThis.fetch;
    let capturedUrl: string | undefined;
    let capturedInit: any;
    globalThis.fetch = async (url: any, init: any) => {
      capturedUrl = typeof url === "string" ? url : url.toString();
      capturedInit = init;
      return new Response("proxied body", {
        status: 200,
        headers: { "content-type": "text/plain", "x-upstream": "true" },
      });
    };

    try {
      const response = await proxyExternalRequest(request, "https://api.example.com/endpoint");
      expect(capturedUrl).toContain("https://api.example.com/endpoint");
      // Extra query param from original request should be merged
      expect(capturedUrl).toContain("extra=1");
      expect(capturedInit.method).toBe("GET");
      expect(capturedInit.redirect).toBe("manual");
      // Host header should be set to the external target
      expect(capturedInit.headers.get("host")).toBe("api.example.com");
      expect(response.status).toBe(200);
      expect(response.headers.get("x-upstream")).toBe("true");
      const body = await response.text();
      expect(body).toBe("proxied body");
    } finally {
      globalThis.fetch = originalFetch;
    }
  });

  it("preserves query parameters from the rewrite destination", async () => {
    const { proxyExternalRequest } = await import(
      "../packages/vinext/src/config/config-matchers.js"
    );

    const request = new Request("http://localhost:3000/test", {
      method: "GET",
    });

    const originalFetch = globalThis.fetch;
    let capturedUrl: string | undefined;
    globalThis.fetch = async (url: any, _init: any) => {
      capturedUrl = typeof url === "string" ? url : url.toString();
      return new Response("ok", { status: 200 });
    };

    try {
      await proxyExternalRequest(request, "https://api.example.com/v1?key=abc");
      expect(capturedUrl).toContain("key=abc");
    } finally {
      globalThis.fetch = originalFetch;
    }
  });

  it("strips hop-by-hop headers from upstream response", async () => {
    const { proxyExternalRequest } = await import(
      "../packages/vinext/src/config/config-matchers.js"
    );

    const request = new Request("http://localhost:3000/test");

    const originalFetch = globalThis.fetch;
    globalThis.fetch = async (_url: any, _init: any) => {
      return new Response("ok", {
        status: 200,
        headers: {
          "content-type": "text/plain",
          "x-custom": "preserved",
          // Note: "transfer-encoding" and "connection" are hop-by-hop headers
          // that should be stripped. However, the fetch API may not allow
          // setting them on Response, so we test with headers that can be set.
        },
      });
    };

    try {
      const response = await proxyExternalRequest(request, "https://api.example.com/test");
      expect(response.headers.get("content-type")).toBe("text/plain");
      expect(response.headers.get("x-custom")).toBe("preserved");
    } finally {
      globalThis.fetch = originalFetch;
    }
  });

  it("passes through non-200 status codes", async () => {
    const { proxyExternalRequest } = await import(
      "../packages/vinext/src/config/config-matchers.js"
    );

    const request = new Request("http://localhost:3000/test");

    const originalFetch = globalThis.fetch;
    globalThis.fetch = async (_url: any, _init: any) => {
      return new Response("Not Found", { status: 404 });
    };

    try {
      const response = await proxyExternalRequest(request, "https://api.example.com/missing");
      expect(response.status).toBe(404);
    } finally {
      globalThis.fetch = originalFetch;
    }
  });

  it("forwards redirect responses without following them", async () => {
    const { proxyExternalRequest } = await import(
      "../packages/vinext/src/config/config-matchers.js"
    );

    const request = new Request("http://localhost:3000/test");

    const originalFetch = globalThis.fetch;
    globalThis.fetch = async (_url: any, init: any) => {
      // Verify redirect: "manual" was set
      expect(init.redirect).toBe("manual");
      return new Response(null, {
        status: 301,
        headers: { "location": "https://other.example.com/new" },
      });
    };

    try {
      const response = await proxyExternalRequest(request, "https://api.example.com/old");
      expect(response.status).toBe(301);
      expect(response.headers.get("location")).toBe("https://other.example.com/new");
    } finally {
      globalThis.fetch = originalFetch;
    }
  });
});

// ---------------------------------------------------------------------------
// matchRewrite + isExternalUrl integration (config-matchers)

describe("matchRewrite with external URLs", () => {
  it("returns full external URL when destination is external", async () => {
    const { matchRewrite, isExternalUrl } = await import(
      "../packages/vinext/src/config/config-matchers.js"
    );
    const rewrites = [
      { source: "/ph/:path*", destination: "https://us.i.posthog.com/:path*" },
    ];
    const result = matchRewrite("/ph/decide", rewrites);
    expect(result).toBe("https://us.i.posthog.com/decide");
    expect(isExternalUrl(result!)).toBe(true);
  });

  it("returns full external URL for static path rewrites", async () => {
    const { matchRewrite, isExternalUrl } = await import(
      "../packages/vinext/src/config/config-matchers.js"
    );
    const rewrites = [
      { source: "/ph/static/:path*", destination: "https://us-assets.i.posthog.com/static/:path*" },
    ];
    const result = matchRewrite("/ph/static/array.js", rewrites);
    expect(result).toBe("https://us-assets.i.posthog.com/static/array.js");
    expect(isExternalUrl(result!)).toBe(true);
  });

  it("returns internal path for non-external rewrites", async () => {
    const { matchRewrite, isExternalUrl } = await import(
      "../packages/vinext/src/config/config-matchers.js"
    );
    const rewrites = [
      { source: "/posts/:id", destination: "/blog/:id" },
    ];
    const result = matchRewrite("/posts/hello", rewrites);
    expect(result).toBe("/blog/hello");
    expect(isExternalUrl(result!)).toBe(false);
  });
});

describe("next/form shim", () => {
  it("exports default Form component", async () => {
    const mod = await import("../packages/vinext/src/shims/form.js");
    expect(mod.default).toBeDefined();
    expect(typeof mod.default).toBe("object"); // forwardRef returns an object
  });

  it("re-exports useActionState from React", async () => {
    const mod = await import("../packages/vinext/src/shims/form.js");
    expect(typeof mod.useActionState).toBe("function");
  });

  it("renders a form element with string action in SSR", async () => {
    const React = await import("react");
    const { renderToStaticMarkup } = await import("react-dom/server");
    const { default: Form } = await import("../packages/vinext/src/shims/form.js");

    const html = renderToStaticMarkup(
      React.createElement(
        Form,
        { action: "/search" },
        React.createElement("input", { name: "q" }),
        React.createElement("button", { type: "submit" }, "Search"),
      ),
    );
    expect(html).toContain("<form");
    expect(html).toContain('action="/search"');
    expect(html).toContain('name="q"');
    expect(html).toContain("Search");
  });

  it("renders a form with method prop", async () => {
    const React = await import("react");
    const { renderToStaticMarkup } = await import("react-dom/server");
    const { default: Form } = await import("../packages/vinext/src/shims/form.js");

    const html = renderToStaticMarkup(
      React.createElement(
        Form,
        { action: "/api/submit", method: "POST" },
        React.createElement("button", { type: "submit" }, "Submit"),
      ),
    );
    expect(html).toContain("<form");
    expect(html).toContain('method="POST"');
  });

  it("renders children inside the form", async () => {
    const React = await import("react");
    const { renderToStaticMarkup } = await import("react-dom/server");
    const { default: Form } = await import("../packages/vinext/src/shims/form.js");

    const html = renderToStaticMarkup(
      React.createElement(
        Form,
        { action: "/search" },
        React.createElement("label", null, "Query:"),
        React.createElement("input", { name: "q", placeholder: "Search..." }),
        React.createElement("button", null, "Go"),
      ),
    );
    expect(html).toContain("Query:");
    expect(html).toContain('placeholder="Search..."');
    expect(html).toContain("Go");
  });

  it("passes className and id through to form element", async () => {
    const React = await import("react");
    const { renderToStaticMarkup } = await import("react-dom/server");
    const { default: Form } = await import("../packages/vinext/src/shims/form.js");

    const html = renderToStaticMarkup(
      React.createElement(
        Form,
        { action: "/search", className: "search-form", id: "main-search" },
      ),
    );
    expect(html).toContain('class="search-form"');
    expect(html).toContain('id="main-search"');
  });
});

describe("next/font/google shim", () => {
  it("returns className, style, and variable for a Google Font", async () => {
    const { Inter } = await import(
      "../packages/vinext/src/shims/font-google.js"
    );
    const result = Inter({ subsets: ["latin"], weight: ["400", "700"] });

    expect(result.className).toMatch(/^__font_inter_/);
    expect(result.style.fontFamily).toContain("Inter");
    // In Next.js, `variable` returns a CLASS NAME that sets the CSS variable.
    // Users apply this class to set the CSS variable on that element.
    expect(result.variable).toMatch(/^__variable_inter_/);
  });

  it("Proxy returns font loaders for any family", async () => {
    const mod = await import(
      "../packages/vinext/src/shims/font-google.js"
    );
    const googleFonts = mod.default;
    const loader = googleFonts.Poppins;
    expect(typeof loader).toBe("function");

    const result = loader({ weight: "400" });
    expect(result.className).toMatch(/^__font_poppins_/);
    expect(result.style.fontFamily).toContain("Poppins");
  });

  it("converts PascalCase to font family name", async () => {
    const mod = await import(
      "../packages/vinext/src/shims/font-google.js"
    );
    const googleFonts = mod.default;
    const result = googleFonts.RobotoMono({ weight: "400" });

    expect(result.style.fontFamily).toContain("Roboto Mono");
    // In Next.js, `variable` returns a CLASS NAME that sets the CSS variable.
    expect(result.variable).toMatch(/^__variable_roboto_mono_/);
  });

  it("uses custom variable name when provided", async () => {
    const { Inter } = await import(
      "../packages/vinext/src/shims/font-google.js"
    );
    const result = Inter({ variable: "--custom-font" });
    // When custom variable is provided, the generated class still sets that variable
    // The returned value is still a class name, not the CSS variable name itself
    expect(result.variable).toMatch(/^__variable_inter_/);
  });

  it("uses custom fallback fonts", async () => {
    const { Inter } = await import(
      "../packages/vinext/src/shims/font-google.js"
    );
    const result = Inter({ fallback: ["Helvetica", "Arial", "sans-serif"] });
    expect(result.style.fontFamily).toContain("Helvetica");
    expect(result.style.fontFamily).toContain("Arial");
  });

  it("generates CSS rules for className (SSR)", async () => {
    const { Inter, getSSRFontStyles } = await import(
      "../packages/vinext/src/shims/font-google.js"
    );
    // Clear any previously collected styles
    getSSRFontStyles();

    const result = Inter({ subsets: ["latin"], weight: ["400"] });

    // getSSRFontStyles should return CSS rules mapping className to font-family
    const styles = getSSRFontStyles();
    const allCss = styles.join("\n");
    expect(allCss).toContain(`.${result.className}`);
    expect(allCss).toContain("font-family:");
    expect(allCss).toContain("Inter");
  });

  it("generates CSS variable rule when variable is specified", async () => {
    const { Inter, getSSRFontStyles } = await import(
      "../packages/vinext/src/shims/font-google.js"
    );
    getSSRFontStyles(); // clear

    Inter({ variable: "--font-inter" });
    const styles = getSSRFontStyles();
    const allCss = styles.join("\n");
    expect(allCss).toContain("--font-inter:");
  });
});

describe("next/font/local shim", () => {
  it("returns className, style for a local font", async () => {
    const { default: localFont } = await import(
      "../packages/vinext/src/shims/font-local.js"
    );
    const result = localFont({ src: "./my-font.woff2" });

    expect(result.className).toMatch(/^__font_local_/);
    expect(result.style.fontFamily).toMatch(/__local_font_/);
  });

  it("includes variable as generated class name when specified", async () => {
    const { default: localFont } = await import(
      "../packages/vinext/src/shims/font-local.js"
    );
    const result = localFont({
      src: "./my-font.woff2",
      variable: "--font-custom",
    });
    // variable should be a generated class name, not the raw CSS variable
    expect(result.variable).toMatch(/^__variable_local_/);
    expect(result.variable).not.toBe("--font-custom");
  });

  it("accepts array of font sources", async () => {
    const { default: localFont } = await import(
      "../packages/vinext/src/shims/font-local.js"
    );
    const result = localFont({
      src: [
        { path: "./regular.woff2", weight: "400" },
        { path: "./bold.woff2", weight: "700" },
      ],
    });

    expect(result.className).toMatch(/^__font_local_/);
    expect(result.style.fontFamily).toBeTruthy();
  });

  it("does not include variable when not specified", async () => {
    const { default: localFont } = await import(
      "../packages/vinext/src/shims/font-local.js"
    );
    const result = localFont({ src: "./no-var.woff2" });
    expect(result.variable).toBeUndefined();
  });

  it("generates SSR font styles for className rules", async () => {
    const fontLocal = await import(
      "../packages/vinext/src/shims/font-local.js"
    );
    const localFont = fontLocal.default;
    // In test (Node), typeof document === "undefined", so SSR path is used
    const result = localFont({
      src: "./ssr-test.woff2",
      variable: "--font-ssr-test",
    });

    const ssrStyles = fontLocal.getSSRFontStyles();
    expect(ssrStyles.length).toBeGreaterThan(0);

    // Should contain @font-face rule
    const allCSS = ssrStyles.join("\n");
    expect(allCSS).toContain("@font-face");
    expect(allCSS).toContain("ssr-test.woff2");

    // Should contain className rule
    expect(allCSS).toContain(`.${result.className}`);
    expect(allCSS).toContain("font-family:");

    // Should contain variable class rule with :root fallback
    expect(allCSS).toContain(`.${result.variable}`);
    expect(allCSS).toContain("--font-ssr-test");
    expect(allCSS).toContain(":root");
  });

  it("generates unique classNames and variableClassNames", async () => {
    const { default: localFont } = await import(
      "../packages/vinext/src/shims/font-local.js"
    );
    const a = localFont({ src: "./a.woff2", variable: "--font-a" });
    const b = localFont({ src: "./b.woff2", variable: "--font-b" });

    expect(a.className).not.toBe(b.className);
    expect(a.variable).not.toBe(b.variable);
    expect(a.variable).toMatch(/^__variable_local_/);
    expect(b.variable).toMatch(/^__variable_local_/);
  });

  it("exports getSSRFontPreloads function", async () => {
    const fontLocal = await import(
      "../packages/vinext/src/shims/font-local.js"
    );
    expect(typeof fontLocal.getSSRFontPreloads).toBe("function");
  });

  it("collects preload data for fonts with absolute URLs", async () => {
    const fontLocal = await import(
      "../packages/vinext/src/shims/font-local.js"
    );
    const localFont = fontLocal.default;

    // Simulate a font with an absolute URL (as resolved by Vite transform)
    localFont({ src: "/assets/my-font-abc123.woff2" });

    const preloads = fontLocal.getSSRFontPreloads();
    const match = preloads.find(
      (p: any) => p.href === "/assets/my-font-abc123.woff2",
    );
    expect(match).toBeDefined();
    expect(match!.type).toBe("font/woff2");
  });

  it("collects preload data for array font sources with absolute URLs", async () => {
    const fontLocal = await import(
      "../packages/vinext/src/shims/font-local.js"
    );
    const localFont = fontLocal.default;

    localFont({
      src: [
        { path: "/assets/regular-abc.woff2", weight: "400" },
        { path: "/assets/bold-def.woff", weight: "700" },
      ],
    });

    const preloads = fontLocal.getSSRFontPreloads();
    const woff2 = preloads.find(
      (p: any) => p.href === "/assets/regular-abc.woff2",
    );
    const woff = preloads.find(
      (p: any) => p.href === "/assets/bold-def.woff",
    );
    expect(woff2).toBeDefined();
    expect(woff2!.type).toBe("font/woff2");
    expect(woff).toBeDefined();
    expect(woff!.type).toBe("font/woff");
  });

  it("does not collect preload data for relative URLs", async () => {
    const fontLocal = await import(
      "../packages/vinext/src/shims/font-local.js"
    );
    const localFont = fontLocal.default;

    const preloadsBefore = fontLocal.getSSRFontPreloads().length;
    localFont({ src: "./relative-font.woff2" });
    const preloadsAfter = fontLocal.getSSRFontPreloads().length;

    // Relative URLs should NOT be added to preloads
    expect(preloadsAfter).toBe(preloadsBefore);
  });

  it("deduplicates preload entries by href", async () => {
    const fontLocal = await import(
      "../packages/vinext/src/shims/font-local.js"
    );
    const localFont = fontLocal.default;

    // Call twice with the same font URL
    localFont({ src: "/assets/dedup-test.woff2" });
    localFont({ src: "/assets/dedup-test.woff2" });

    const preloads = fontLocal.getSSRFontPreloads();
    const matches = preloads.filter(
      (p: any) => p.href === "/assets/dedup-test.woff2",
    );
    expect(matches.length).toBe(1);
  });
});

describe("next/og shim", () => {
  it("exports ImageResponse class", async () => {
    const og = await import(
      "../packages/vinext/src/shims/og.js"
    );
    expect(og.ImageResponse).toBeDefined();
    expect(typeof og.ImageResponse).toBe("function");
  });

  it("ImageResponse extends Response", async () => {
    const og = await import(
      "../packages/vinext/src/shims/og.js"
    );
    // Check the prototype chain
    expect(og.ImageResponse.prototype instanceof Response).toBe(true);
  });

  it("generates a PNG image from JSX", async () => {
    const React = await import("react");
    const og = await import(
      "../packages/vinext/src/shims/og.js"
    );

    // Simple colored div — no text so no font needed
    const element = React.createElement(
      "div",
      {
        style: {
          display: "flex",
          width: "100%",
          height: "100%",
          backgroundColor: "#ff6600",
          alignItems: "center",
          justifyContent: "center",
        },
      },
    );

    const response = new og.ImageResponse(element, {
      width: 100,
      height: 100,
    });

    expect(response).toBeInstanceOf(Response);
    expect(response.status).toBe(200);
    expect(response.headers.get("content-type")).toBe("image/png");

    // Read the response body — should be valid PNG data
    const buffer = await response.arrayBuffer();
    const bytes = new Uint8Array(buffer);

    // PNG magic bytes: 0x89 0x50 0x4E 0x47
    expect(bytes[0]).toBe(0x89);
    expect(bytes[1]).toBe(0x50); // P
    expect(bytes[2]).toBe(0x4e); // N
    expect(bytes[3]).toBe(0x47); // G
  });

  it("respects custom status and headers", async () => {
    const React = await import("react");
    const og = await import(
      "../packages/vinext/src/shims/og.js"
    );

    const element = React.createElement("div", {
      style: { display: "flex", width: "100%", height: "100%", backgroundColor: "blue" },
    });

    const response = new og.ImageResponse(element, {
      width: 50,
      height: 50,
      status: 201,
      headers: { "x-custom": "test-value" },
    });

    expect(response.status).toBe(201);
    expect(response.headers.get("x-custom")).toBe("test-value");
    expect(response.headers.get("content-type")).toBe("image/png");
  });

  it("uses default dimensions of 1200x630", async () => {
    const React = await import("react");
    const og = await import(
      "../packages/vinext/src/shims/og.js"
    );

    const element = React.createElement("div", {
      style: { display: "flex", width: "100%", height: "100%", backgroundColor: "green" },
    });

    // No width/height specified — should use defaults
    const response = new og.ImageResponse(element);
    expect(response).toBeInstanceOf(Response);

    // Verify it produces valid PNG
    const buffer = await response.arrayBuffer();
    const bytes = new Uint8Array(buffer);
    expect(bytes[0]).toBe(0x89); // PNG magic
  });
});

describe("metadata route serializers", () => {
  it("sitemapToXml converts sitemap entries to valid XML", async () => {
    const { sitemapToXml } = await import(
      "../packages/vinext/src/server/metadata-routes.js"
    );
    const xml = sitemapToXml([
      { url: "https://example.com", lastModified: "2025-01-01", priority: 1 },
      { url: "https://example.com/about", changeFrequency: "monthly" as const },
    ]);
    expect(xml).toContain('<?xml version="1.0" encoding="UTF-8"?>');
    expect(xml).toContain("<urlset");
    expect(xml).toContain("<loc>https://example.com</loc>");
    expect(xml).toContain("<lastmod>2025-01-01</lastmod>");
    expect(xml).toContain("<priority>1</priority>");
    expect(xml).toContain("<loc>https://example.com/about</loc>");
    expect(xml).toContain("<changefreq>monthly</changefreq>");
  });

  it("sitemapToXml handles Date objects", async () => {
    const { sitemapToXml } = await import(
      "../packages/vinext/src/server/metadata-routes.js"
    );
    const xml = sitemapToXml([
      { url: "https://example.com", lastModified: new Date("2025-06-15") },
    ]);
    expect(xml).toContain("<lastmod>2025-06-15T00:00:00.000Z</lastmod>");
  });

  it("robotsToText converts robots config to text", async () => {
    const { robotsToText } = await import(
      "../packages/vinext/src/server/metadata-routes.js"
    );
    const text = robotsToText({
      rules: { userAgent: "*", allow: "/", disallow: "/private/" },
      sitemap: "https://example.com/sitemap.xml",
    });
    expect(text).toContain("User-Agent: *");
    expect(text).toContain("Allow: /");
    expect(text).toContain("Disallow: /private/");
    expect(text).toContain("Sitemap: https://example.com/sitemap.xml");
  });

  it("robotsToText handles multiple rules", async () => {
    const { robotsToText } = await import(
      "../packages/vinext/src/server/metadata-routes.js"
    );
    const text = robotsToText({
      rules: [
        { userAgent: "Googlebot", allow: "/" },
        { userAgent: "Bingbot", disallow: "/secret" },
      ],
    });
    expect(text).toContain("User-Agent: Googlebot");
    expect(text).toContain("User-Agent: Bingbot");
    expect(text).toContain("Disallow: /secret");
  });

  it("manifestToJson converts manifest config to JSON", async () => {
    const { manifestToJson } = await import(
      "../packages/vinext/src/server/metadata-routes.js"
    );
    const json = manifestToJson({
      name: "Test App",
      short_name: "Test",
      start_url: "/",
      display: "standalone",
    });
    const parsed = JSON.parse(json);
    expect(parsed.name).toBe("Test App");
    expect(parsed.display).toBe("standalone");
  });

  it("scanMetadataFiles discovers metadata files in app directory", async () => {
    const { scanMetadataFiles } = await import(
      "../packages/vinext/src/server/metadata-routes.js"
    );
    const appDir = path.resolve(import.meta.dirname, "./fixtures/app-basic/app");
    const routes = scanMetadataFiles(appDir);

    // Should find our test fixture files
    const types = routes.map((r: { type: string }) => r.type);
    expect(types).toContain("sitemap");
    expect(types).toContain("robots");
    expect(types).toContain("manifest");
    expect(types).toContain("favicon");

    // Sitemap should be dynamic (.ts)
    const sitemap = routes.find((r: { type: string }) => r.type === "sitemap");
    expect(sitemap).toBeDefined();
    expect(sitemap!.isDynamic).toBe(true);
    expect(sitemap!.servedUrl).toBe("/sitemap.xml");
    expect(sitemap!.contentType).toBe("application/xml");

    // Favicon should be static (.ico)
    const favicon = routes.find((r: { type: string }) => r.type === "favicon");
    expect(favicon).toBeDefined();
    expect(favicon!.isDynamic).toBe(false);
    expect(favicon!.servedUrl).toBe("/favicon.ico");
    expect(favicon!.contentType).toBe("image/x-icon");
  });
});

describe("next/dynamic shim", () => {
  it("exports a default function", async () => {
    const mod = await import(
      "../packages/vinext/src/shims/dynamic.js"
    );
    expect(typeof mod.default).toBe("function");
  });

  it("exports flushPreloads", async () => {
    const mod = await import(
      "../packages/vinext/src/shims/dynamic.js"
    );
    expect(typeof mod.flushPreloads).toBe("function");
  });

  it("returns a component for SSR-enabled dynamic imports", async () => {
    const { default: dynamic } = await import(
      "../packages/vinext/src/shims/dynamic.js"
    );
    const React = await import("react");
    const { renderToReadableStream } = await import("react-dom/server.edge");

    const FakeComponent = () => React.createElement("div", null, "Hello from dynamic");
    const DynamicComponent = dynamic(() =>
      Promise.resolve({ default: FakeComponent }),
    );

    // renderToReadableStream handles React.lazy + Suspense
    const stream = await renderToReadableStream(React.createElement(DynamicComponent));
    await stream.allReady;
    const html = await new Response(stream).text();
    expect(html).toContain("Hello from dynamic");
  });

  it("renders loading state for ssr: false on server", async () => {
    const { default: dynamic } = await import(
      "../packages/vinext/src/shims/dynamic.js"
    );
    const React = await import("react");
    const { renderToStaticMarkup } = await import("react-dom/server");

    const FakeComponent = () => React.createElement("div", null, "Should not appear");
    const Loading = () => React.createElement("span", null, "Loading...");
    const DynamicComponent = dynamic(
      () => Promise.resolve({ default: FakeComponent }),
      { ssr: false, loading: Loading },
    );

    // On server with ssr: false, should render loading, not the component
    const html = renderToStaticMarkup(React.createElement(DynamicComponent));
    expect(html).toContain("Loading...");
    expect(html).not.toContain("Should not appear");
  });

  it("renders nothing for ssr: false without loading on server", async () => {
    const { default: dynamic } = await import(
      "../packages/vinext/src/shims/dynamic.js"
    );
    const React = await import("react");
    const { renderToStaticMarkup } = await import("react-dom/server");

    const FakeComponent = () => React.createElement("div", null, "Should not appear");
    const DynamicComponent = dynamic(
      () => Promise.resolve({ default: FakeComponent }),
      { ssr: false },
    );

    // On server with ssr: false and no loading component, should render nothing
    const html = renderToStaticMarkup(React.createElement(DynamicComponent));
    expect(html).toBe("");
  });

  it("accepts module without default export (bare component)", async () => {
    const { default: dynamic } = await import(
      "../packages/vinext/src/shims/dynamic.js"
    );
    const React = await import("react");
    const { renderToReadableStream } = await import("react-dom/server.edge");

    const BareComponent = () => React.createElement("p", null, "Bare export");
    const DynamicComponent = dynamic(() => Promise.resolve(BareComponent));

    const stream = await renderToReadableStream(React.createElement(DynamicComponent));
    await stream.allReady;
    const html = await new Response(stream).text();
    expect(html).toContain("Bare export");
  });

  it("forwards props to the underlying component", async () => {
    const { default: dynamic } = await import(
      "../packages/vinext/src/shims/dynamic.js"
    );
    const React = await import("react");
    const { renderToReadableStream } = await import("react-dom/server.edge");

    const Greeter = ({ name }: { name: string }) =>
      React.createElement("span", null, `Hello ${name}`);
    const DynamicGreeter = dynamic(() => Promise.resolve({ default: Greeter }));

    const stream = await renderToReadableStream(
      React.createElement(DynamicGreeter, { name: "World" }),
    );
    await stream.allReady;
    const html = await new Response(stream).text();
    expect(html).toContain("Hello World");
  });

  it("renders loading fallback when component not yet resolved (SSR)", async () => {
    const { default: dynamic } = await import(
      "../packages/vinext/src/shims/dynamic.js"
    );
    const React = await import("react");
    const { renderToReadableStream } = await import("react-dom/server.edge");

    let resolveLoader!: (val: any) => void;
    const loaderPromise = new Promise((r) => { resolveLoader = r; });
    const SlowComponent = () => React.createElement("div", null, "Loaded");
    const Loading = () => React.createElement("span", null, "Please wait...");

    const DynamicSlow = dynamic(() => loaderPromise as any, { loading: Loading });

    // Start streaming — the shell includes the Suspense fallback
    const stream = await renderToReadableStream(React.createElement(DynamicSlow));
    // Resolve the loader so the stream can complete
    resolveLoader({ default: SlowComponent });
    await stream.allReady;
    const html = await new Response(stream).text();
    // The final HTML should contain the resolved component, but the Suspense
    // fallback ("Please wait...") was sent as part of the shell before it resolved
    expect(html).toContain("Loaded");
  });

  it("streaming renderer resolves multiple dynamic components", async () => {
    const { default: dynamic } = await import(
      "../packages/vinext/src/shims/dynamic.js"
    );
    const React = await import("react");
    const { renderToReadableStream } = await import("react-dom/server.edge");

    const CompA = () => React.createElement("div", null, "Component A");
    const CompB = () => React.createElement("div", null, "Component B");

    const DynA = dynamic(() =>
      new Promise<any>((r) => setTimeout(() => r({ default: CompA }), 10)),
    );
    const DynB = dynamic(() =>
      new Promise<any>((r) => setTimeout(() => r({ default: CompB }), 10)),
    );

    // renderToReadableStream handles React.lazy via Suspense
    const streamA = await renderToReadableStream(React.createElement(DynA));
    await streamA.allReady;
    const htmlA = await new Response(streamA).text();

    const streamB = await renderToReadableStream(React.createElement(DynB));
    await streamB.allReady;
    const htmlB = await new Response(streamB).text();

    expect(htmlA).toContain("Component A");
    expect(htmlB).toContain("Component B");
  });

  it("flushPreloads second call resolves immediately (queue drained)", async () => {
    const { flushPreloads } = await import(
      "../packages/vinext/src/shims/dynamic.js"
    );

    // First call should drain whatever's in the queue
    await flushPreloads();

    // Second call should resolve immediately with empty array
    const result = await flushPreloads();
    expect(result).toEqual([]);
  });

  it("loading component receives isLoading and pastDelay props", async () => {
    const { default: dynamic } = await import(
      "../packages/vinext/src/shims/dynamic.js"
    );
    const React = await import("react");
    const { renderToStaticMarkup } = await import("react-dom/server");

    let receivedProps: any = null;
    const Loading = (props: any) => {
      receivedProps = props;
      return React.createElement("span", null, "Loading");
    };

    const FakeComp = () => React.createElement("div", null, "Content");
    const DynComp = dynamic(
      () => Promise.resolve({ default: FakeComp }),
      { ssr: false, loading: Loading },
    );

    renderToStaticMarkup(React.createElement(DynComp));
    expect(receivedProps).not.toBeNull();
    expect(receivedProps.isLoading).toBe(true);
    expect(receivedProps.pastDelay).toBe(true);
    expect(receivedProps.error).toBeNull();
  });

  it("renders loading fallback for ssr: false with props forwarded", async () => {
    const { default: dynamic } = await import(
      "../packages/vinext/src/shims/dynamic.js"
    );
    const React = await import("react");
    const { renderToStaticMarkup } = await import("react-dom/server");

    const HeavyChart = ({ title }: { title: string }) =>
      React.createElement("canvas", null, title);
    const Loading = () => React.createElement("div", null, "Chart loading...");

    const DynamicChart = dynamic(
      () => Promise.resolve({ default: HeavyChart }),
      { ssr: false, loading: Loading },
    );

    // On server: should show loading, not the chart
    const html = renderToStaticMarkup(
      React.createElement(DynamicChart, { title: "Revenue" }),
    );
    expect(html).toContain("Chart loading...");
    expect(html).not.toContain("Revenue");
  });

  it("handles module with both default and named exports", async () => {
    const { default: dynamic } = await import(
      "../packages/vinext/src/shims/dynamic.js"
    );
    const React = await import("react");
    const { renderToReadableStream } = await import("react-dom/server.edge");

    const MainComponent = () => React.createElement("div", null, "Main");
    const namedHelper = () => "helper";

    const DynComp = dynamic(() =>
      Promise.resolve({ default: MainComponent, namedHelper }),
    );

    const stream = await renderToReadableStream(React.createElement(DynComp));
    await stream.allReady;
    const html = await new Response(stream).text();
    expect(html).toContain("Main");
  });

  it("loader rejection does not crash flushPreloads", async () => {
    const { default: dynamic, flushPreloads } = await import(
      "../packages/vinext/src/shims/dynamic.js"
    );

    dynamic(() => Promise.reject(new Error("Module not found")));

    // flushPreloads should not throw (it's now a no-op for the server lazy path,
    // but kept for backward compatibility with Pages Router)
    await expect(flushPreloads()).resolves.not.toThrow();
  });

  it("loader rejection renders loading component with error", async () => {
    const { default: dynamic } = await import(
      "../packages/vinext/src/shims/dynamic.js"
    );
    const React = await import("react");
    const { renderToReadableStream } = await import("react-dom/server.edge");

    const LoadingComp = (props: { error?: Error | null; isLoading?: boolean }) => {
      if (props.error) {
        return React.createElement("div", null, `Error: ${props.error.message}`);
      }
      return React.createElement("div", null, "Loading...");
    };

    const DynComp = dynamic(
      () => Promise.reject(new Error("chunk load fail")),
      { loading: LoadingComp },
    );

    // The error boundary renders the loading component with the error
    const stream = await renderToReadableStream(React.createElement(DynComp));
    await stream.allReady;
    const html = await new Response(stream).text();
    expect(html).toContain("Error: chunk load fail");
  });

  it("loader rejection without loading component propagates via onError", async () => {
    const { default: dynamic } = await import(
      "../packages/vinext/src/shims/dynamic.js"
    );
    const React = await import("react");
    const { renderToReadableStream } = await import("react-dom/server.edge");

    const DynComp = dynamic(
      () => Promise.reject(new Error("fail")),
    );

    // Without a loading component, the Suspense fallback is null.
    // The rejected loader throws during rendering, caught by onError.
    const errors: Error[] = [];
    const stream = await renderToReadableStream(
      React.createElement(DynComp),
      { onError(err: unknown) { if (err instanceof Error) errors.push(err); } },
    );
    await stream.allReady.catch(() => {});
    expect(errors.some((e) => e.message === "fail")).toBe(true);
  });

  it("loader rejection with non-Error value is caught during SSR", async () => {
    const { default: dynamic } = await import(
      "../packages/vinext/src/shims/dynamic.js"
    );
    const React = await import("react");
    const { renderToReadableStream } = await import("react-dom/server.edge");

    const DynComp = dynamic(
      () => Promise.reject("string error"),
    );

    // Non-Error rejection values are caught by React's SSR error handling
    const errors: unknown[] = [];
    const stream = await renderToReadableStream(
      React.createElement(DynComp),
      { onError(err: unknown) { errors.push(err); } },
    );
    await stream.allReady.catch(() => {});
    expect(errors.length).toBeGreaterThan(0);
  });
});

describe("basePath config validation", () => {
  it("resolveNextConfig preserves basePath with leading slash", async () => {
    const { resolveNextConfig } = await import(
      "../packages/vinext/src/config/next-config.js"
    );
    const config = await resolveNextConfig({ basePath: "/my-app" });
    expect(config.basePath).toBe("/my-app");
  });

  it("resolveNextConfig handles nested basePath", async () => {
    const { resolveNextConfig } = await import(
      "../packages/vinext/src/config/next-config.js"
    );
    const config = await resolveNextConfig({ basePath: "/a/b/c" });
    expect(config.basePath).toBe("/a/b/c");
  });

  it("resolveNextConfig defaults to empty string", async () => {
    const { resolveNextConfig } = await import(
      "../packages/vinext/src/config/next-config.js"
    );
    const config = await resolveNextConfig({});
    expect(config.basePath).toBe("");
  });

  it("resolveNextConfig handles undefined basePath", async () => {
    const { resolveNextConfig } = await import(
      "../packages/vinext/src/config/next-config.js"
    );
    const config = await resolveNextConfig({ basePath: undefined });
    expect(config.basePath).toBe("");
  });
});

describe("cacheComponents config (Next.js 16)", () => {
  it("resolveNextConfig defaults cacheComponents to false", async () => {
    const { resolveNextConfig } = await import(
      "../packages/vinext/src/config/next-config.js"
    );
    const config = await resolveNextConfig({});
    expect(config.cacheComponents).toBe(false);
  });

  it("resolveNextConfig reads cacheComponents: true", async () => {
    const { resolveNextConfig } = await import(
      "../packages/vinext/src/config/next-config.js"
    );
    const config = await resolveNextConfig({ cacheComponents: true });
    expect(config.cacheComponents).toBe(true);
  });

  it("resolveNextConfig reads cacheComponents: false", async () => {
    const { resolveNextConfig } = await import(
      "../packages/vinext/src/config/next-config.js"
    );
    const config = await resolveNextConfig({ cacheComponents: false });
    expect(config.cacheComponents).toBe(false);
  });

  it("resolveNextConfig handles null input with cacheComponents default", async () => {
    const { resolveNextConfig } = await import(
      "../packages/vinext/src/config/next-config.js"
    );
    const config = await resolveNextConfig(null);
    expect(config.cacheComponents).toBe(false);
  });

  it("resolveNextConfig defaults mdx to null", async () => {
    const { resolveNextConfig } = await import(
      "../packages/vinext/src/config/next-config.js"
    );
    const config = await resolveNextConfig({});
    expect(config.mdx).toBeNull();
  });

  it("resolveNextConfig returns null mdx for null input", async () => {
    const { resolveNextConfig } = await import(
      "../packages/vinext/src/config/next-config.js"
    );
    const config = await resolveNextConfig(null);
    expect(config.mdx).toBeNull();
  });

  it("resolveNextConfig resolves serverActionsAllowedOrigins from experimental.serverActions", async () => {
    const { resolveNextConfig } = await import(
      "../packages/vinext/src/config/next-config.js"
    );
    const config = await resolveNextConfig({
      experimental: {
        serverActions: {
          allowedOrigins: ["my-proxy.com", "*.my-domain.com"],
        },
      },
    });
    expect(config.serverActionsAllowedOrigins).toEqual(["my-proxy.com", "*.my-domain.com"]);
  });

  it("resolveNextConfig defaults serverActionsAllowedOrigins to empty array", async () => {
    const { resolveNextConfig } = await import(
      "../packages/vinext/src/config/next-config.js"
    );
    const config = await resolveNextConfig({});
    expect(config.serverActionsAllowedOrigins).toEqual([]);
  });

  it("resolveNextConfig handles null input with empty serverActionsAllowedOrigins", async () => {
    const { resolveNextConfig } = await import(
      "../packages/vinext/src/config/next-config.js"
    );
    const config = await resolveNextConfig(null);
    expect(config.serverActionsAllowedOrigins).toEqual([]);
  });
});

describe("loadNextConfig CJS support", () => {
  let tmpDir: string;

  beforeEach(async () => {
    const os = await import("node:os");
    const fsp = await import("node:fs/promises");
    tmpDir = await fsp.mkdtemp(path.join(os.tmpdir(), "vinext-cjs-cfg-"));
  });

  afterEach(async () => {
    const fsp = await import("node:fs/promises");
    await fsp.rm(tmpDir, { recursive: true, force: true });
  });

  it("loads a CJS next.config.js that uses module.exports", async () => {
    const fsp = await import("node:fs/promises");
    const { loadNextConfig } = await import(
      "../packages/vinext/src/config/next-config.js"
    );

    await fsp.writeFile(
      path.join(tmpDir, "next.config.js"),
      `module.exports = { basePath: "/cjs-app", trailingSlash: true };`,
    );

    const config = await loadNextConfig(tmpDir);
    expect(config).not.toBeNull();
    expect(config!.basePath).toBe("/cjs-app");
    expect(config!.trailingSlash).toBe(true);
  });

  it("loads a CJS next.config.js with require() plugin wrapper", async () => {
    const fsp = await import("node:fs/promises");
    const { loadNextConfig } = await import(
      "../packages/vinext/src/config/next-config.js"
    );

    // Simulate a CJS plugin wrapper like nextra/next-intl/etc.
    // Create a fake plugin module that wraps the config
    await fsp.mkdir(path.join(tmpDir, "node_modules", "fake-plugin"), {
      recursive: true,
    });
    await fsp.writeFile(
      path.join(tmpDir, "node_modules", "fake-plugin", "index.js"),
      `module.exports = function fakePlugin(pluginOpts) {
        return function withPlugin(nextConfig) {
          return Object.assign({}, nextConfig, { env: { PLUGIN: "loaded" } });
        };
      };`,
    );
    await fsp.writeFile(
      path.join(tmpDir, "node_modules", "fake-plugin", "package.json"),
      JSON.stringify({ name: "fake-plugin", version: "1.0.0", main: "index.js" }),
    );

    // Write a next.config.js that uses require() — this is the pattern that fails
    await fsp.writeFile(
      path.join(tmpDir, "next.config.js"),
      `const withPlugin = require('fake-plugin')({ theme: 'docs' });
module.exports = withPlugin({ basePath: "/wrapped" });`,
    );

    const config = await loadNextConfig(tmpDir);
    expect(config).not.toBeNull();
    expect(config!.basePath).toBe("/wrapped");
    expect(config!.env).toEqual({ PLUGIN: "loaded" });
  });

  it("loads a CJS function-form next.config.js", async () => {
    const fsp = await import("node:fs/promises");
    const { loadNextConfig } = await import(
      "../packages/vinext/src/config/next-config.js"
    );

    await fsp.writeFile(
      path.join(tmpDir, "next.config.js"),
      `module.exports = function(phase, { defaultConfig }) {
        return { basePath: "/fn-" + phase.split("-")[1] };
      };`,
    );

    const config = await loadNextConfig(tmpDir);
    expect(config).not.toBeNull();
    // phase is "phase-development-server", split("-")[1] = "development"
    expect(config!.basePath).toBe("/fn-development");
  });

  it("loads a .cjs config file", async () => {
    const fsp = await import("node:fs/promises");
    const { loadNextConfig } = await import(
      "../packages/vinext/src/config/next-config.js"
    );

    await fsp.writeFile(
      path.join(tmpDir, "next.config.cjs"),
      `module.exports = { basePath: "/cjs-ext" };`,
    );

    const config = await loadNextConfig(tmpDir);
    expect(config).not.toBeNull();
    expect(config!.basePath).toBe("/cjs-ext");
  });

  it("loads an ESM next.config.mjs normally", async () => {
    const fsp = await import("node:fs/promises");
    const { loadNextConfig } = await import(
      "../packages/vinext/src/config/next-config.js"
    );

    await fsp.writeFile(
      path.join(tmpDir, "next.config.mjs"),
      `export default { basePath: "/esm-app" };`,
    );

    const config = await loadNextConfig(tmpDir);
    expect(config).not.toBeNull();
    expect(config!.basePath).toBe("/esm-app");
  });

  it("returns null when no config file exists", async () => {
    const { loadNextConfig } = await import(
      "../packages/vinext/src/config/next-config.js"
    );

    const config = await loadNextConfig(tmpDir);
    expect(config).toBeNull();
  });
});

describe("extractMdxOptions", () => {
  it("returns null when no webpack function", async () => {
    const { extractMdxOptions } = await import(
      "../packages/vinext/src/config/next-config.js"
    );
    expect(extractMdxOptions({})).toBeNull();
    expect(extractMdxOptions({ webpack: "not a function" })).toBeNull();
  });

  it("extracts remarkPlugins from webpack rule", async () => {
    const { extractMdxOptions } = await import(
      "../packages/vinext/src/config/next-config.js"
    );
    const fakeRemarkPlugin = () => {};
    const config = {
      webpack: (webpackConfig: any) => {
        webpackConfig.module.rules.push({
          test: /\.mdx$/,
          use: [
            {
              loader: "@next/mdx/mdx-js-loader",
              options: {
                remarkPlugins: [[fakeRemarkPlugin, { option: true }]],
                rehypePlugins: [],
              },
            },
          ],
        });
        return webpackConfig;
      },
    };
    const result = extractMdxOptions(config);
    expect(result).not.toBeNull();
    expect(result!.remarkPlugins).toHaveLength(1);
    expect(result!.remarkPlugins![0]).toEqual([fakeRemarkPlugin, { option: true }]);
    expect(result!.rehypePlugins).toBeUndefined();
  });

  it("extracts rehypePlugins from webpack rule", async () => {
    const { extractMdxOptions } = await import(
      "../packages/vinext/src/config/next-config.js"
    );
    const fakeRehypePlugin = () => {};
    const config = {
      webpack: (webpackConfig: any) => {
        webpackConfig.module.rules.push({
          test: /\.mdx$/,
          use: [
            {
              loader: "@next/mdx/mdx-js-loader",
              options: {
                remarkPlugins: [],
                rehypePlugins: [fakeRehypePlugin],
              },
            },
          ],
        });
        return webpackConfig;
      },
    };
    const result = extractMdxOptions(config);
    expect(result).not.toBeNull();
    expect(result!.rehypePlugins).toHaveLength(1);
    expect(result!.remarkPlugins).toBeUndefined();
  });

  it("extracts recmaPlugins from webpack rule", async () => {
    const { extractMdxOptions } = await import(
      "../packages/vinext/src/config/next-config.js"
    );
    const fakeRecmaPlugin = () => {};
    const config = {
      webpack: (webpackConfig: any) => {
        webpackConfig.module.rules.push({
          test: /\.mdx$/,
          use: [
            {
              loader: "@next/mdx/mdx-js-loader",
              options: {
                recmaPlugins: [fakeRecmaPlugin],
              },
            },
          ],
        });
        return webpackConfig;
      },
    };
    const result = extractMdxOptions(config);
    expect(result).not.toBeNull();
    expect(result!.recmaPlugins).toHaveLength(1);
  });

  it("handles oneOf nested rules", async () => {
    const { extractMdxOptions } = await import(
      "../packages/vinext/src/config/next-config.js"
    );
    const fakeRemarkPlugin = () => {};
    const config = {
      webpack: (webpackConfig: any) => {
        webpackConfig.module.rules.push({
          oneOf: [
            {
              test: /\.mdx$/,
              use: [
                {
                  loader: "@next/mdx/mdx-js-loader",
                  options: {
                    remarkPlugins: [fakeRemarkPlugin],
                  },
                },
              ],
            },
          ],
        });
        return webpackConfig;
      },
    };
    const result = extractMdxOptions(config);
    expect(result).not.toBeNull();
    expect(result!.remarkPlugins).toHaveLength(1);
  });

  it("returns null when webpack throws", async () => {
    const { extractMdxOptions } = await import(
      "../packages/vinext/src/config/next-config.js"
    );
    const config = {
      webpack: () => {
        throw new Error("some webpack error");
      },
    };
    expect(extractMdxOptions(config)).toBeNull();
  });

  it("returns null when webpack has no MDX loader", async () => {
    const { extractMdxOptions } = await import(
      "../packages/vinext/src/config/next-config.js"
    );
    const config = {
      webpack: (webpackConfig: any) => {
        webpackConfig.module.rules.push({
          test: /\.css$/,
          use: [{ loader: "css-loader" }],
        });
        return webpackConfig;
      },
    };
    expect(extractMdxOptions(config)).toBeNull();
  });

  it("returns null when MDX loader has empty plugin arrays", async () => {
    const { extractMdxOptions } = await import(
      "../packages/vinext/src/config/next-config.js"
    );
    const config = {
      webpack: (webpackConfig: any) => {
        webpackConfig.module.rules.push({
          test: /\.mdx$/,
          use: [
            {
              loader: "@next/mdx/mdx-js-loader",
              options: {
                remarkPlugins: [],
                rehypePlugins: [],
              },
            },
          ],
        });
        return webpackConfig;
      },
    };
    expect(extractMdxOptions(config)).toBeNull();
  });

  it("resolveNextConfig extracts mdx from webpack closure", async () => {
    const { resolveNextConfig } = await import(
      "../packages/vinext/src/config/next-config.js"
    );
    const fakeRemarkPlugin = () => {};
    const config = await resolveNextConfig({
      webpack: (webpackConfig: any) => {
        webpackConfig.module.rules.push({
          test: /\.mdx$/,
          use: [
            {
              loader: "@next/mdx/mdx-js-loader",
              options: {
                remarkPlugins: [fakeRemarkPlugin],
              },
            },
          ],
        });
        return webpackConfig;
      },
    });
    expect(config.mdx).not.toBeNull();
    expect(config.mdx!.remarkPlugins).toHaveLength(1);
  });
});

describe("next/web-vitals shim", () => {
  it("exports useReportWebVitals as a no-op function", async () => {
    const { useReportWebVitals } = await import(
      "../packages/vinext/src/shims/web-vitals.js"
    );
    expect(typeof useReportWebVitals).toBe("function");
    // Should run without throwing
    expect(() => useReportWebVitals(() => {})).not.toThrow();
  });
});

describe("next/amp shim", () => {
  it("exports useAmp and isInAmpMode as no-op functions", async () => {
    const { useAmp, isInAmpMode } = await import(
      "../packages/vinext/src/shims/amp.js"
    );
    expect(typeof useAmp).toBe("function");
    expect(typeof isInAmpMode).toBe("function");
    // Both always return false
    expect(useAmp()).toBe(false);
    expect(isInAmpMode()).toBe(false);
  });
});

describe("Pages Router router helpers", () => {
  describe("isExternalUrl", () => {
    it("detects https:// as external", () => {
      expect(isExternalUrl("https://example.com")).toBe(true);
      expect(isExternalUrl("https://example.com/path")).toBe(true);
    });

    it("detects http:// as external", () => {
      expect(isExternalUrl("http://example.com")).toBe(true);
    });

    it("detects protocol-relative // as external", () => {
      expect(isExternalUrl("//cdn.example.com/img.png")).toBe(true);
    });

    it("returns false for relative paths", () => {
      expect(isExternalUrl("/about")).toBe(false);
      expect(isExternalUrl("/")).toBe(false);
      expect(isExternalUrl("about")).toBe(false);
    });

    it("returns false for hash-only", () => {
      expect(isExternalUrl("#section")).toBe(false);
    });

    it("returns false for query-only", () => {
      expect(isExternalUrl("?foo=1")).toBe(false);
    });
  });

  describe("isHashOnlyChange", () => {
    it("returns true for hash-only strings starting with #", () => {
      // This works even without window because of the startsWith check
      expect(isHashOnlyChange("#foo")).toBe(true);
      expect(isHashOnlyChange("#")).toBe(true);
      expect(isHashOnlyChange("#section-2")).toBe(true);
    });

    it("returns false for absolute paths without window context", () => {
      // Without a real browser window, URL-based comparison returns false
      // because typeof window === "undefined" → returns false
      expect(isHashOnlyChange("/about")).toBe(false);
      expect(isHashOnlyChange("/about#foo")).toBe(false);
    });

    it("returns false for full URLs without window context", () => {
      expect(isHashOnlyChange("https://example.com#foo")).toBe(false);
    });
  });
});

describe("next/server enhancements", () => {
  it("NextRequest.ip extracts from x-forwarded-for header", async () => {
    const { NextRequest } = await import(
      "../packages/vinext/src/shims/server.js"
    );
    const req = new NextRequest("https://example.com", {
      headers: { "x-forwarded-for": "1.2.3.4, 5.6.7.8" },
    });
    expect(req.ip).toBe("1.2.3.4");
  });

  it("NextRequest.ip returns undefined without forwarded header", async () => {
    const { NextRequest } = await import(
      "../packages/vinext/src/shims/server.js"
    );
    const req = new NextRequest("https://example.com");
    expect(req.ip).toBeUndefined();
  });

  it("NextRequest.geo extracts from Cloudflare/Vercel headers", async () => {
    const { NextRequest } = await import(
      "../packages/vinext/src/shims/server.js"
    );
    const req = new NextRequest("https://example.com", {
      headers: {
        "cf-ipcountry": "US",
        "cf-ipcity": "San Francisco",
      },
    });
    const geo = req.geo;
    expect(geo).toBeDefined();
    expect(geo!.country).toBe("US");
    expect(geo!.city).toBe("San Francisco");
  });

  it("NextRequest.geo returns undefined without geo headers", async () => {
    const { NextRequest } = await import(
      "../packages/vinext/src/shims/server.js"
    );
    const req = new NextRequest("https://example.com");
    expect(req.geo).toBeUndefined();
  });

  it("ResponseCookies.getAll returns all set cookies", async () => {
    const { ResponseCookies } = await import(
      "../packages/vinext/src/shims/server.js"
    );
    const headers = new Headers();
    const cookies = new ResponseCookies(headers);
    cookies.set("a", "1");
    cookies.set("b", "2");
    const all = cookies.getAll();
    expect(all).toHaveLength(2);
    expect(all.find((c: any) => c.name === "a")?.value).toBe("1");
    expect(all.find((c: any) => c.name === "b")?.value).toBe("2");
  });
});

describe("next/image enhancements", () => {
  it("exports StaticImageData type", async () => {
    const imageModule = await import(
      "../packages/vinext/src/shims/image.js"
    );
    // StaticImageData is an interface, so we can't check at runtime
    // but getImageProps uses it — verify that function exists
    expect(typeof imageModule.getImageProps).toBe("function");
  });

  it("getImageProps returns img props from Image props", async () => {
    const { getImageProps } = await import(
      "../packages/vinext/src/shims/image.js"
    );
    const result = getImageProps({
      src: "/photo.jpg",
      alt: "Test",
      width: 800,
      height: 600,
      priority: true,
    });
    // Local images now route through the optimization endpoint
    expect(result.props.src).toContain("/_vinext/image");
    expect(result.props.src).toContain("url=%2Fphoto.jpg");
    expect(result.props.src).toContain("w=800");
    expect(result.props.alt).toBe("Test");
    expect(result.props.width).toBe(800);
    expect(result.props.height).toBe(600);
    expect(result.props.loading).toBe("eager");
  });

  it("getImageProps handles fill mode", async () => {
    const { getImageProps } = await import(
      "../packages/vinext/src/shims/image.js"
    );
    const result = getImageProps({
      src: "/bg.jpg",
      alt: "Background",
      fill: true,
    });
    expect(result.props.width).toBeUndefined();
    expect(result.props.height).toBeUndefined();
    expect(result.props.style?.position).toBe("absolute");
  });

  it("getImageProps handles StaticImageData", async () => {
    const { getImageProps } = await import(
      "../packages/vinext/src/shims/image.js"
    );
    const result = getImageProps({
      src: { src: "/imported.jpg", width: 1200, height: 800, blurDataURL: "data:..." },
      alt: "Imported",
    });
    expect(result.props.src).toContain("/_vinext/image");
    expect(result.props.src).toContain("url=%2Fimported.jpg");
    expect(result.props.src).toContain("w=1200");
    expect(result.props.width).toBe(1200);
    expect(result.props.height).toBe(800);
  });

  it("getImageProps generates srcSet for local images with width", async () => {
    const { getImageProps } = await import(
      "../packages/vinext/src/shims/image.js"
    );
    const result = getImageProps({
      src: "/photo.jpg",
      alt: "Test",
      width: 1200,
      height: 800,
    });
    expect(result.props.srcSet).toBeDefined();
    // srcSet entries point to /_vinext/image optimization endpoint
    expect(result.props.srcSet).toContain("/_vinext/image");
    expect(result.props.srcSet).toContain("url=%2Fphoto.jpg");
    expect(result.props.srcSet).toContain("w");
  });

  it("getImageProps does not generate srcSet for fill images", async () => {
    const { getImageProps } = await import(
      "../packages/vinext/src/shims/image.js"
    );
    const result = getImageProps({
      src: "/bg.jpg",
      alt: "Background",
      fill: true,
    });
    expect(result.props.srcSet).toBeUndefined();
    expect(result.props.sizes).toBe("100vw"); // fill implies 100vw
  });

  it("getImageProps includes fetchPriority for priority images", async () => {
    const { getImageProps } = await import(
      "../packages/vinext/src/shims/image.js"
    );
    const result = getImageProps({
      src: "/hero.jpg",
      alt: "Hero",
      width: 1200,
      height: 800,
      priority: true,
    });
    expect(result.props.fetchPriority).toBe("high");
    expect(result.props.loading).toBe("eager");
  });

  it("getImageProps includes data-nimg attribute", async () => {
    const { getImageProps } = await import(
      "../packages/vinext/src/shims/image.js"
    );
    const result = getImageProps({
      src: "/photo.jpg",
      alt: "Photo",
      width: 800,
      height: 600,
    });
    expect((result.props as any)["data-nimg"]).toBe("1");

    const fillResult = getImageProps({
      src: "/bg.jpg",
      alt: "BG",
      fill: true,
    });
    expect((fillResult.props as any)["data-nimg"]).toBe("fill");
  });

  it("getImageProps includes blur placeholder background styles", async () => {
    const { getImageProps } = await import(
      "../packages/vinext/src/shims/image.js"
    );
    const blurUrl = "data:image/jpeg;base64,/9j/4AAQ";
    const result = getImageProps({
      src: "/photo.jpg",
      alt: "Blurry",
      width: 800,
      height: 600,
      placeholder: "blur",
      blurDataURL: blurUrl,
    });
    expect(result.props.style?.backgroundImage).toContain(blurUrl);
    expect(result.props.style?.backgroundSize).toBe("cover");
  });

  it("getImageProps uses custom loader function", async () => {
    const { getImageProps } = await import(
      "../packages/vinext/src/shims/image.js"
    );
    const result = getImageProps({
      src: "/photo.jpg",
      alt: "Custom",
      width: 800,
      height: 600,
      loader: ({ src, width, quality }) => `https://cdn.example.com${src}?w=${width}&q=${quality}`,
    });
    // Custom loader bypasses the /_vinext/image endpoint
    expect(result.props.src).toBe("https://cdn.example.com/photo.jpg?w=800&q=75");
    expect(result.props.src).not.toContain("/_vinext/image");
  });

  it("unoptimized prop bypasses /_vinext/image endpoint", async () => {
    const { getImageProps } = await import("../packages/vinext/src/shims/image.js");
    const result = getImageProps({
      src: "/photo.jpg",
      alt: "Unoptimized",
      width: 800,
      height: 600,
      unoptimized: true,
    });
    // unoptimized=true should serve the raw src, not the optimization endpoint
    expect(result.props.src).toBe("/photo.jpg");
    expect(result.props.src).not.toContain("/_vinext/image");
  });
});

describe("next/image component rendering", () => {
  it("renders basic image with src, alt, width, height", async () => {
    const React = await import("react");
    const { renderToStaticMarkup } = await import("react-dom/server");
    const Image = (await import("../packages/vinext/src/shims/image.js")).default;

    const html = renderToStaticMarkup(
      React.createElement(Image, { src: "/photo.jpg", alt: "Test photo", width: 800, height: 600 }),
    );
    // Local images route through the optimization endpoint
    expect(html).toContain("/_vinext/image");
    expect(html).toContain("url=%2Fphoto.jpg");
    expect(html).toContain('alt="Test photo"');
    expect(html).toContain('width="800"');
    expect(html).toContain('height="600"');
  });

  it("renders fill image with absolute positioning", async () => {
    const React = await import("react");
    const { renderToStaticMarkup } = await import("react-dom/server");
    const Image = (await import("../packages/vinext/src/shims/image.js")).default;

    const html = renderToStaticMarkup(
      React.createElement(Image, { src: "/bg.jpg", alt: "Background", fill: true }),
    );
    expect(html).toContain("position:absolute");
    expect(html).toContain('data-nimg="fill"');
    // fill images should not have width/height attributes
    expect(html).not.toContain('width=');
    expect(html).not.toContain('height=');
  });

  it("renders priority image with fetchpriority=high and loading=eager", async () => {
    const React = await import("react");
    const { renderToStaticMarkup } = await import("react-dom/server");
    const Image = (await import("../packages/vinext/src/shims/image.js")).default;

    const html = renderToStaticMarkup(
      React.createElement(Image, { src: "/hero.jpg", alt: "Hero", width: 1200, height: 800, priority: true }),
    );
    expect(html).toContain('fetchPriority="high"');
    expect(html).toContain('loading="eager"');
  });

  it("renders lazy loading by default (no priority)", async () => {
    const React = await import("react");
    const { renderToStaticMarkup } = await import("react-dom/server");
    const Image = (await import("../packages/vinext/src/shims/image.js")).default;

    const html = renderToStaticMarkup(
      React.createElement(Image, { src: "/photo.jpg", alt: "Photo", width: 800, height: 600 }),
    );
    expect(html).toContain('loading="lazy"');
    expect(html).not.toContain('fetchPriority');
  });

  it("renders srcSet for local images with width", async () => {
    const React = await import("react");
    const { renderToStaticMarkup } = await import("react-dom/server");
    const Image = (await import("../packages/vinext/src/shims/image.js")).default;

    const html = renderToStaticMarkup(
      React.createElement(Image, { src: "/photo.jpg", alt: "Photo", width: 1200, height: 800 }),
    );
    expect(html).toContain("srcSet");
    // srcSet entries point to /_vinext/image optimization endpoint
    expect(html).toContain("/_vinext/image");
    expect(html).toContain("url=%2Fphoto.jpg");
  });

  it("renders blur placeholder with background-image", async () => {
    const React = await import("react");
    const { renderToStaticMarkup } = await import("react-dom/server");
    const Image = (await import("../packages/vinext/src/shims/image.js")).default;

    const blurUrl = "data:image/jpeg;base64,/9j/4AAQ";
    const html = renderToStaticMarkup(
      React.createElement(Image, {
        src: "/photo.jpg",
        alt: "Blurry",
        width: 800,
        height: 600,
        placeholder: "blur",
        blurDataURL: blurUrl,
      }),
    );
    expect(html).toContain(blurUrl);
    expect(html).toContain("background-image");
    expect(html).toContain("background-size:cover");
  });

  it("renders with custom loader function", async () => {
    const React = await import("react");
    const { renderToStaticMarkup } = await import("react-dom/server");
    const Image = (await import("../packages/vinext/src/shims/image.js")).default;

    const html = renderToStaticMarkup(
      React.createElement(Image, {
        src: "/photo.jpg",
        alt: "Custom",
        width: 800,
        height: 600,
        loader: ({ src, width, quality }: { src: string; width: number; quality?: number }) =>
          `https://cdn.example.com${src}?w=${width}&q=${quality}`,
      }),
    );
    expect(html).toContain('src="https://cdn.example.com/photo.jpg?w=800&amp;q=75"');
  });

  it("renders with custom sizes attribute", async () => {
    const React = await import("react");
    const { renderToStaticMarkup } = await import("react-dom/server");
    const Image = (await import("../packages/vinext/src/shims/image.js")).default;

    const html = renderToStaticMarkup(
      React.createElement(Image, {
        src: "/photo.jpg",
        alt: "Responsive",
        width: 1200,
        height: 800,
        sizes: "(max-width: 768px) 100vw, 50vw",
      }),
    );
    expect(html).toContain('sizes="(max-width: 768px) 100vw, 50vw"');
  });

  it("renders fill image with sizes=100vw by default", async () => {
    const React = await import("react");
    const { renderToStaticMarkup } = await import("react-dom/server");
    const Image = (await import("../packages/vinext/src/shims/image.js")).default;

    const html = renderToStaticMarkup(
      React.createElement(Image, { src: "/bg.jpg", alt: "BG", fill: true }),
    );
    expect(html).toContain('sizes="100vw"');
  });

  it("handles StaticImageData import objects", async () => {
    const React = await import("react");
    const { renderToStaticMarkup } = await import("react-dom/server");
    const Image = (await import("../packages/vinext/src/shims/image.js")).default;

    const staticImport = { src: "/imported.jpg", width: 1200, height: 800, blurDataURL: "data:..." };
    const html = renderToStaticMarkup(
      React.createElement(Image, { src: staticImport, alt: "Imported" }),
    );
    expect(html).toContain("/_vinext/image");
    expect(html).toContain("url=%2Fimported.jpg");
    expect(html).toContain('width="1200"');
    expect(html).toContain('height="800"');
  });

  it("renders with className", async () => {
    const React = await import("react");
    const { renderToStaticMarkup } = await import("react-dom/server");
    const Image = (await import("../packages/vinext/src/shims/image.js")).default;

    const html = renderToStaticMarkup(
      React.createElement(Image, {
        src: "/photo.jpg",
        alt: "Styled",
        width: 800,
        height: 600,
        className: "rounded-lg shadow-md",
      }),
    );
    expect(html).toContain('class="rounded-lg shadow-md"');
  });

  it("includes data-nimg=1 for non-fill images", async () => {
    const React = await import("react");
    const { renderToStaticMarkup } = await import("react-dom/server");
    const Image = (await import("../packages/vinext/src/shims/image.js")).default;

    const html = renderToStaticMarkup(
      React.createElement(Image, { src: "/photo.jpg", alt: "Test", width: 800, height: 600 }),
    );
    expect(html).toContain('data-nimg="1"');
  });

  it("always sets decoding=async", async () => {
    const React = await import("react");
    const { renderToStaticMarkup } = await import("react-dom/server");
    const Image = (await import("../packages/vinext/src/shims/image.js")).default;

    const html = renderToStaticMarkup(
      React.createElement(Image, { src: "/photo.jpg", alt: "Test", width: 800, height: 600 }),
    );
    expect(html).toContain('decoding="async"');
  });
});

describe("image remote pattern matching", () => {
  it("matchRemotePattern matches exact hostname", async () => {
    const { matchRemotePattern } = await import("../packages/vinext/src/shims/image-config.js");
    const pattern = { hostname: "cdn.example.com" };
    expect(matchRemotePattern(pattern, new URL("https://cdn.example.com/img.jpg"))).toBe(true);
    expect(matchRemotePattern(pattern, new URL("https://other.example.com/img.jpg"))).toBe(false);
  });

  it("matchRemotePattern matches wildcard hostname with *", async () => {
    const { matchRemotePattern } = await import("../packages/vinext/src/shims/image-config.js");
    const pattern = { hostname: "*.example.com" };
    expect(matchRemotePattern(pattern, new URL("https://cdn.example.com/img.jpg"))).toBe(true);
    expect(matchRemotePattern(pattern, new URL("https://images.example.com/img.jpg"))).toBe(true);
    // Single * should NOT match nested subdomains
    expect(matchRemotePattern(pattern, new URL("https://a.b.example.com/img.jpg"))).toBe(false);
    // Should not match bare domain
    expect(matchRemotePattern(pattern, new URL("https://example.com/img.jpg"))).toBe(false);
  });

  it("matchRemotePattern matches ** hostname for any depth", async () => {
    const { matchRemotePattern } = await import("../packages/vinext/src/shims/image-config.js");
    const pattern = { hostname: "**.example.com" };
    expect(matchRemotePattern(pattern, new URL("https://cdn.example.com/img.jpg"))).toBe(true);
    expect(matchRemotePattern(pattern, new URL("https://a.b.c.example.com/img.jpg"))).toBe(true);
    // ** requires at least one segment
    expect(matchRemotePattern(pattern, new URL("https://example.com/img.jpg"))).toBe(false);
  });

  it("matchRemotePattern checks protocol when specified", async () => {
    const { matchRemotePattern } = await import("../packages/vinext/src/shims/image-config.js");
    const pattern = { protocol: "https", hostname: "cdn.example.com" };
    expect(matchRemotePattern(pattern, new URL("https://cdn.example.com/img.jpg"))).toBe(true);
    expect(matchRemotePattern(pattern, new URL("http://cdn.example.com/img.jpg"))).toBe(false);
  });

  it("matchRemotePattern checks port when specified", async () => {
    const { matchRemotePattern } = await import("../packages/vinext/src/shims/image-config.js");
    const pattern = { hostname: "localhost", port: "3000" };
    expect(matchRemotePattern(pattern, new URL("http://localhost:3000/img.jpg"))).toBe(true);
    expect(matchRemotePattern(pattern, new URL("http://localhost:8080/img.jpg"))).toBe(false);
  });

  it("matchRemotePattern checks pathname when specified", async () => {
    const { matchRemotePattern } = await import("../packages/vinext/src/shims/image-config.js");
    const pattern = { hostname: "cdn.example.com", pathname: "/images/**" };
    expect(matchRemotePattern(pattern, new URL("https://cdn.example.com/images/photo.jpg"))).toBe(true);
    expect(matchRemotePattern(pattern, new URL("https://cdn.example.com/images/nested/photo.jpg"))).toBe(true);
    expect(matchRemotePattern(pattern, new URL("https://cdn.example.com/other/photo.jpg"))).toBe(false);
  });

  it("matchRemotePattern checks search when specified", async () => {
    const { matchRemotePattern } = await import("../packages/vinext/src/shims/image-config.js");
    const pattern = { hostname: "cdn.example.com", search: "?v=123" };
    expect(matchRemotePattern(pattern, new URL("https://cdn.example.com/img.jpg?v=123"))).toBe(true);
    expect(matchRemotePattern(pattern, new URL("https://cdn.example.com/img.jpg?v=456"))).toBe(false);
    expect(matchRemotePattern(pattern, new URL("https://cdn.example.com/img.jpg"))).toBe(false);
  });

  it("matchRemotePattern defaults pathname to ** when not specified", async () => {
    const { matchRemotePattern } = await import("../packages/vinext/src/shims/image-config.js");
    const pattern = { hostname: "cdn.example.com" };
    expect(matchRemotePattern(pattern, new URL("https://cdn.example.com/any/deep/path/img.jpg"))).toBe(true);
    expect(matchRemotePattern(pattern, new URL("https://cdn.example.com/"))).toBe(true);
  });

  it("matchRemotePattern handles protocol with trailing colon", async () => {
    const { matchRemotePattern } = await import("../packages/vinext/src/shims/image-config.js");
    const pattern = { protocol: "https:", hostname: "cdn.example.com" };
    expect(matchRemotePattern(pattern, new URL("https://cdn.example.com/img.jpg"))).toBe(true);
  });

  it("hasRemoteMatch checks domains list", async () => {
    const { hasRemoteMatch } = await import("../packages/vinext/src/shims/image-config.js");
    const domains = ["cdn.example.com", "images.example.com"];
    expect(hasRemoteMatch(domains, [], new URL("https://cdn.example.com/img.jpg"))).toBe(true);
    expect(hasRemoteMatch(domains, [], new URL("https://images.example.com/img.jpg"))).toBe(true);
    expect(hasRemoteMatch(domains, [], new URL("https://other.example.com/img.jpg"))).toBe(false);
  });

  it("hasRemoteMatch checks remotePatterns when domains don't match", async () => {
    const { hasRemoteMatch } = await import("../packages/vinext/src/shims/image-config.js");
    const patterns = [{ protocol: "https", hostname: "**.cdn.example.com" }];
    expect(hasRemoteMatch([], patterns, new URL("https://us.cdn.example.com/img.jpg"))).toBe(true);
    expect(hasRemoteMatch([], patterns, new URL("http://us.cdn.example.com/img.jpg"))).toBe(false);
  });

  it("matchRemotePattern handles single * in pathname", async () => {
    const { matchRemotePattern } = await import("../packages/vinext/src/shims/image-config.js");
    const pattern = { hostname: "cdn.example.com", pathname: "/images/*" };
    expect(matchRemotePattern(pattern, new URL("https://cdn.example.com/images/photo.jpg"))).toBe(true);
    // Single * should not match nested paths
    expect(matchRemotePattern(pattern, new URL("https://cdn.example.com/images/nested/photo.jpg"))).toBe(false);
  });

  it("matchRemotePattern handles regex special chars in hostname", async () => {
    const { matchRemotePattern } = await import("../packages/vinext/src/shims/image-config.js");
    // Dots in hostname are literal (escaped for regex)
    const pattern = { hostname: "cdn.example.com" };
    expect(matchRemotePattern(pattern, new URL("https://cdn.example.com/img.jpg"))).toBe(true);
    // "cdnXexampleXcom" should not match "cdn.example.com"
    expect(matchRemotePattern(pattern, new URL("https://cdnXexample.com/img.jpg"))).toBe(false);
  });
});

describe("image optimization URL generation", () => {
  it("imageOptimizationUrl generates correct URL", async () => {
    const { imageOptimizationUrl } = await import("../packages/vinext/src/shims/image.js");
    const url = imageOptimizationUrl("/images/hero.webp", 1200, 75);
    expect(url).toBe("/_vinext/image?url=%2Fimages%2Fhero.webp&w=1200&q=75");
  });

  it("imageOptimizationUrl encodes special characters", async () => {
    const { imageOptimizationUrl } = await import("../packages/vinext/src/shims/image.js");
    const url = imageOptimizationUrl("/images/my photo.jpg", 800, 80);
    expect(url).toContain("url=%2Fimages%2Fmy%20photo.jpg");
    expect(url).toContain("w=800");
    expect(url).toContain("q=80");
  });

  it("imageOptimizationUrl uses default quality of 75", async () => {
    const { imageOptimizationUrl } = await import("../packages/vinext/src/shims/image.js");
    const url = imageOptimizationUrl("/img.png", 640);
    expect(url).toContain("q=75");
  });
});

describe("image optimization request parsing", () => {
  it("parseImageParams extracts url, width, quality", async () => {
    const { parseImageParams } = await import("../packages/vinext/src/server/image-optimization.js");
    const url = new URL("http://localhost/_vinext/image?url=%2Fimages%2Fhero.webp&w=1200&q=75");
    const params = parseImageParams(url);
    expect(params).not.toBeNull();
    expect(params!.imageUrl).toBe("/images/hero.webp");
    expect(params!.width).toBe(1200);
    expect(params!.quality).toBe(75);
  });

  it("parseImageParams returns null when url is missing", async () => {
    const { parseImageParams } = await import("../packages/vinext/src/server/image-optimization.js");
    const url = new URL("http://localhost/_vinext/image?w=800&q=75");
    expect(parseImageParams(url)).toBeNull();
  });

  it("parseImageParams blocks absolute http URLs", async () => {
    const { parseImageParams } = await import("../packages/vinext/src/server/image-optimization.js");
    const url = new URL("http://localhost/_vinext/image?url=http%3A%2F%2Fevil.com%2Fimg.jpg&w=800");
    expect(parseImageParams(url)).toBeNull();
  });

  it("parseImageParams blocks absolute https URLs", async () => {
    const { parseImageParams } = await import("../packages/vinext/src/server/image-optimization.js");
    const url = new URL("http://localhost/_vinext/image?url=https%3A%2F%2Fevil.com%2Fimg.jpg&w=800");
    expect(parseImageParams(url)).toBeNull();
  });

  it("parseImageParams blocks protocol-relative URLs", async () => {
    const { parseImageParams } = await import("../packages/vinext/src/server/image-optimization.js");
    const url = new URL("http://localhost/_vinext/image?url=%2F%2Fevil.com%2Fimg.jpg&w=800");
    expect(parseImageParams(url)).toBeNull();
  });

  it("parseImageParams defaults width to 0 and quality to 75", async () => {
    const { parseImageParams } = await import("../packages/vinext/src/server/image-optimization.js");
    const url = new URL("http://localhost/_vinext/image?url=%2Fimg.jpg");
    const params = parseImageParams(url);
    expect(params).not.toBeNull();
    expect(params!.width).toBe(0);
    expect(params!.quality).toBe(75);
  });

  it("parseImageParams blocks data: URIs (exotic scheme bypass)", async () => {
    const { parseImageParams } = await import("../packages/vinext/src/server/image-optimization.js");
    expect(parseImageParams(new URL("http://localhost/_vinext/image?url=data%3Atext%2Fhtml%2C%3Cscript%3Ealert(1)%3C%2Fscript%3E&w=800"))).toBeNull();
  });

  it("parseImageParams blocks javascript: URIs", async () => {
    const { parseImageParams } = await import("../packages/vinext/src/server/image-optimization.js");
    expect(parseImageParams(new URL("http://localhost/_vinext/image?url=javascript%3Aalert(1)&w=800"))).toBeNull();
  });

  it("parseImageParams blocks bare filenames (no leading slash)", async () => {
    const { parseImageParams } = await import("../packages/vinext/src/server/image-optimization.js");
    expect(parseImageParams(new URL("http://localhost/_vinext/image?url=img.jpg&w=800"))).toBeNull();
  });

  it("parseImageParams rejects quality outside 1-100", async () => {
    const { parseImageParams } = await import("../packages/vinext/src/server/image-optimization.js");
    expect(parseImageParams(new URL("http://localhost/_vinext/image?url=%2Fimg.jpg&q=0"))).toBeNull();
    expect(parseImageParams(new URL("http://localhost/_vinext/image?url=%2Fimg.jpg&q=101"))).toBeNull();
  });

  it("negotiateImageFormat prefers AVIF over WebP", async () => {
    const { negotiateImageFormat } = await import("../packages/vinext/src/server/image-optimization.js");
    expect(negotiateImageFormat("image/avif,image/webp,image/jpeg")).toBe("image/avif");
  });

  it("negotiateImageFormat selects WebP when no AVIF", async () => {
    const { negotiateImageFormat } = await import("../packages/vinext/src/server/image-optimization.js");
    expect(negotiateImageFormat("image/webp,image/jpeg")).toBe("image/webp");
  });

  it("negotiateImageFormat falls back to JPEG", async () => {
    const { negotiateImageFormat } = await import("../packages/vinext/src/server/image-optimization.js");
    expect(negotiateImageFormat("image/png,image/jpeg")).toBe("image/jpeg");
    expect(negotiateImageFormat(null)).toBe("image/jpeg");
  });

  it("IMAGE_OPTIMIZATION_PATH is /_vinext/image", async () => {
    const { IMAGE_OPTIMIZATION_PATH } = await import("../packages/vinext/src/server/image-optimization.js");
    expect(IMAGE_OPTIMIZATION_PATH).toBe("/_vinext/image");
  });
});

describe("handleImageOptimization", () => {
  it("returns 400 for invalid params", async () => {
    const { handleImageOptimization } = await import("../packages/vinext/src/server/image-optimization.js");
    const request = new Request("http://localhost/_vinext/image");
    const handlers = {
      fetchAsset: async () => new Response("", { status: 200 }),
    };
    const response = await handleImageOptimization(request, handlers);
    expect(response.status).toBe(400);
  });

  it("returns 404 when fetchAsset fails", async () => {
    const { handleImageOptimization } = await import("../packages/vinext/src/server/image-optimization.js");
    const request = new Request("http://localhost/_vinext/image?url=%2Fimg.jpg&w=800");
    const handlers = {
      fetchAsset: async () => new Response("", { status: 404 }),
    };
    const response = await handleImageOptimization(request, handlers);
    expect(response.status).toBe(404);
  });

  it("returns original image when no transformImage handler", async () => {
    const { handleImageOptimization } = await import("../packages/vinext/src/server/image-optimization.js");
    const request = new Request("http://localhost/_vinext/image?url=%2Fimg.jpg&w=800");
    const handlers = {
      fetchAsset: async () => new Response("original-image-data", {
        status: 200,
        headers: { "Content-Type": "image/jpeg" },
      }),
    };
    const response = await handleImageOptimization(request, handlers);
    expect(response.status).toBe(200);
    expect(await response.text()).toBe("original-image-data");
    expect(response.headers.get("Cache-Control")).toBe("public, max-age=31536000, immutable");
    expect(response.headers.get("Vary")).toBe("Accept");
  });

  it("calls transformImage when provided", async () => {
    const { handleImageOptimization } = await import("../packages/vinext/src/server/image-optimization.js");
    const request = new Request("http://localhost/_vinext/image?url=%2Fimg.jpg&w=800&q=90", {
      headers: { Accept: "image/webp" },
    });
    let capturedOptions: { width: number; format: string; quality: number } | null = null;
    const handlers = {
      fetchAsset: async () => new Response("original", { status: 200 }),
      transformImage: async (_body: ReadableStream, options: { width: number; format: string; quality: number }) => {
        capturedOptions = options;
        return new Response("transformed", { headers: { "Content-Type": options.format } });
      },
    };
    const response = await handleImageOptimization(request, handlers);
    expect(response.status).toBe(200);
    expect(await response.text()).toBe("transformed");
    expect(capturedOptions).toEqual({ width: 800, format: "image/webp", quality: 90 });
  });

  it("falls back to original on transform error", async () => {
    const { handleImageOptimization } = await import("../packages/vinext/src/server/image-optimization.js");
    const request = new Request("http://localhost/_vinext/image?url=%2Fimg.jpg&w=800");
    const handlers = {
      fetchAsset: async () => new Response("original", { status: 200 }),
      transformImage: async () => {
        throw new Error("transform failed");
      },
    };
    const response = await handleImageOptimization(request, handlers);
    expect(response.status).toBe(200);
    expect(await response.text()).toBe("original");
  });
});

describe("next/navigation enhancements", () => {
  it("exports ReadonlyURLSearchParams type alias", async () => {
    // This is a type-only export, we verify the module loads without error
    const nav = await import(
      "../packages/vinext/src/shims/navigation.js"
    );
    // ReadonlyURLSearchParams is a type export, not a runtime value
    // But useServerInsertedHTML should be exported
    expect(typeof nav.useServerInsertedHTML).toBe("function");
  });

  it("useServerInsertedHTML is a no-op function", async () => {
    const { useServerInsertedHTML } = await import(
      "../packages/vinext/src/shims/navigation.js"
    );
    // Should not throw
    expect(() => useServerInsertedHTML(() => null)).not.toThrow();
  });
});

describe("next/legacy/image shim", () => {
  it("renders LegacyImage with layout=fill as modern Image with fill prop", async () => {
    const React = await import("react");
    const { renderToStaticMarkup } = await import("react-dom/server");
    const LegacyImage = (await import("../packages/vinext/src/shims/legacy-image.js")).default;

    const html = renderToStaticMarkup(
      React.createElement(LegacyImage, {
        src: "/photo.jpg",
        alt: "Test",
        layout: "fill",
        objectFit: "cover",
        objectPosition: "center",
      }),
    );
    expect(html).toContain("photo.jpg");
    expect(html).toContain("alt");
    // fill mode should produce absolute positioning styles
    expect(html).toContain("position:absolute");
  });

  it("renders LegacyImage with layout=intrinsic using width/height", async () => {
    const React = await import("react");
    const { renderToStaticMarkup } = await import("react-dom/server");
    const LegacyImage = (await import("../packages/vinext/src/shims/legacy-image.js")).default;

    const html = renderToStaticMarkup(
      React.createElement(LegacyImage, {
        src: "/photo.jpg",
        alt: "Test",
        layout: "intrinsic",
        width: 640,
        height: 480,
      }),
    );
    expect(html).toContain('width="640"');
    expect(html).toContain('height="480"');
  });

  it("renders LegacyImage with string width/height (converts to number)", async () => {
    const React = await import("react");
    const { renderToStaticMarkup } = await import("react-dom/server");
    const LegacyImage = (await import("../packages/vinext/src/shims/legacy-image.js")).default;

    const html = renderToStaticMarkup(
      React.createElement(LegacyImage, {
        src: "/photo.jpg",
        alt: "Test",
        width: "200",
        height: "150",
      }),
    );
    expect(html).toContain('width="200"');
    expect(html).toContain('height="150"');
  });
});

describe("next/error shim", () => {
  it("renders 404 error page", async () => {
    const React = await import("react");
    const { renderToStaticMarkup } = await import("react-dom/server");
    const ErrorComponent = (await import("../packages/vinext/src/shims/error.js")).default;

    const html = renderToStaticMarkup(
      React.createElement(ErrorComponent, { statusCode: 404 }),
    );
    expect(html).toContain("404");
    expect(html).toContain("could not be found");
  });

  it("renders 500 error page", async () => {
    const React = await import("react");
    const { renderToStaticMarkup } = await import("react-dom/server");
    const ErrorComponent = (await import("../packages/vinext/src/shims/error.js")).default;

    const html = renderToStaticMarkup(
      React.createElement(ErrorComponent, { statusCode: 500 }),
    );
    expect(html).toContain("500");
    expect(html).toContain("Internal Server Error");
  });

  it("renders custom title", async () => {
    const React = await import("react");
    const { renderToStaticMarkup } = await import("react-dom/server");
    const ErrorComponent = (await import("../packages/vinext/src/shims/error.js")).default;

    const html = renderToStaticMarkup(
      React.createElement(ErrorComponent, { statusCode: 403, title: "Forbidden" }),
    );
    expect(html).toContain("403");
    expect(html).toContain("Forbidden");
  });
});

describe("next/constants shim", () => {
  it("exports all phase constants", async () => {
    const constants = await import("../packages/vinext/src/shims/constants.js");
    expect(constants.PHASE_PRODUCTION_BUILD).toBe("phase-production-build");
    expect(constants.PHASE_DEVELOPMENT_SERVER).toBe("phase-development-server");
    expect(constants.PHASE_PRODUCTION_SERVER).toBe("phase-production-server");
    expect(constants.PHASE_EXPORT).toBe("phase-export");
    expect(constants.PHASE_INFO).toBe("phase-info");
    expect(constants.PHASE_TEST).toBe("phase-test");
  });
});

describe("next/script SSR rendering", () => {
  it("beforeInteractive renders <script> tag in SSR", async () => {
    const React = await import("react");
    const { renderToStaticMarkup } = await import("react-dom/server");
    const Script = (await import("../packages/vinext/src/shims/script.js")).default;

    const html = renderToStaticMarkup(
      React.createElement(Script, {
        src: "https://example.com/analytics.js",
        strategy: "beforeInteractive",
        id: "analytics",
      }),
    );
    expect(html).toContain("<script");
    expect(html).toContain('src="https://example.com/analytics.js"');
    expect(html).toContain('id="analytics"');
  });

  it("afterInteractive returns null in SSR", async () => {
    const React = await import("react");
    const { renderToStaticMarkup } = await import("react-dom/server");
    const Script = (await import("../packages/vinext/src/shims/script.js")).default;

    const html = renderToStaticMarkup(
      React.createElement(Script, {
        src: "https://example.com/chat.js",
        strategy: "afterInteractive",
      }),
    );
    // afterInteractive should not render anything server-side
    expect(html).toBe("");
  });

  it("lazyOnload returns null in SSR", async () => {
    const React = await import("react");
    const { renderToStaticMarkup } = await import("react-dom/server");
    const Script = (await import("../packages/vinext/src/shims/script.js")).default;

    const html = renderToStaticMarkup(
      React.createElement(Script, {
        src: "https://example.com/tracking.js",
        strategy: "lazyOnload",
      }),
    );
    expect(html).toBe("");
  });

  it("default strategy (no strategy prop) returns null in SSR", async () => {
    const React = await import("react");
    const { renderToStaticMarkup } = await import("react-dom/server");
    const Script = (await import("../packages/vinext/src/shims/script.js")).default;

    const html = renderToStaticMarkup(
      React.createElement(Script, {
        src: "https://example.com/default.js",
      }),
    );
    // Default is afterInteractive → null in SSR
    expect(html).toBe("");
  });

  it("beforeInteractive with dangerouslySetInnerHTML renders inline script", async () => {
    const React = await import("react");
    const { renderToStaticMarkup } = await import("react-dom/server");
    const Script = (await import("../packages/vinext/src/shims/script.js")).default;

    const html = renderToStaticMarkup(
      React.createElement(Script, {
        strategy: "beforeInteractive",
        id: "inline-script",
        dangerouslySetInnerHTML: { __html: "console.log('hello')" },
      }),
    );
    expect(html).toContain("<script");
    expect(html).toContain('id="inline-script"');
    expect(html).toContain("console.log('hello')");
  });

  it("exports handleClientScriptLoad and initScriptLoader", async () => {
    const scriptModule = await import("../packages/vinext/src/shims/script.js");
    expect(typeof scriptModule.handleClientScriptLoad).toBe("function");
    expect(typeof scriptModule.initScriptLoader).toBe("function");
  });
});

describe("next/dist/* internal import shims", () => {
  it("app-router-context exports AppRouterContext and types", async () => {
    const mod = await import(
      "../packages/vinext/src/shims/internal/app-router-context.js"
    );
    expect(mod.AppRouterContext).toBeDefined();
    expect(mod.GlobalLayoutRouterContext).toBeDefined();
    expect(mod.LayoutRouterContext).toBeDefined();
    expect(mod.MissingSlotContext).toBeDefined();
    expect(mod.TemplateContext).toBeDefined();
  });

  it("utils exports NEXT_DATA type helpers", async () => {
    const mod = await import(
      "../packages/vinext/src/shims/internal/utils.js"
    );
    expect(typeof mod.execOnce).toBe("function");
    expect(typeof mod.getLocationOrigin).toBe("function");
    expect(typeof mod.getURL).toBe("function");

    // execOnce should only call the function once
    let count = 0;
    const fn = mod.execOnce(() => ++count);
    fn(); fn(); fn();
    expect(count).toBe(1);
  });

  it("api-utils exports NextApiRequestCookies type", async () => {
    // This module is primarily type-only, but should resolve without errors
    const mod = await import(
      "../packages/vinext/src/shims/internal/api-utils.js"
    );
    expect(mod).toBeDefined();
  });

  it("cookies shim re-exports RequestCookies and ResponseCookies", async () => {
    const mod = await import(
      "../packages/vinext/src/shims/internal/cookies.js"
    );
    expect(mod.RequestCookies).toBeDefined();
    expect(mod.ResponseCookies).toBeDefined();
  });

  it("work-unit-async-storage exports AsyncLocalStorage instances", async () => {
    const mod = await import(
      "../packages/vinext/src/shims/internal/work-unit-async-storage.js"
    );
    expect(mod.workUnitAsyncStorage).toBeDefined();
    expect(mod.requestAsyncStorage).toBeDefined();
    // Both should be the same AsyncLocalStorage instance
    expect(mod.workUnitAsyncStorage).toBe(mod.requestAsyncStorage);
  });

  it("router-context exports RouterContext", async () => {
    const mod = await import(
      "../packages/vinext/src/shims/internal/router-context.js"
    );
    expect(mod.RouterContext).toBeDefined();
  });
});

// ---------------------------------------------------------------------------
// Cloudflare KV CacheHandler
// ---------------------------------------------------------------------------

describe("KVCacheHandler", () => {
  // In-memory mock of Cloudflare KV namespace
  function createMockKV() {
    const store = new Map<string, { value: string; expirationTtl?: number }>();
    return {
      store,
      async get(key: string): Promise<string | null> {
        return store.get(key)?.value ?? null;
      },
      async put(
        key: string,
        value: string,
        options?: { expirationTtl?: number },
      ): Promise<void> {
        store.set(key, { value, expirationTtl: options?.expirationTtl });
      },
      async delete(key: string): Promise<void> {
        store.delete(key);
      },
      async list(options?: { prefix?: string; limit?: number; cursor?: string }) {
        const prefix = options?.prefix ?? "";
        const keys: Array<{ name: string }> = [];
        for (const k of store.keys()) {
          if (k.startsWith(prefix)) keys.push({ name: k });
        }
        return { keys, list_complete: true };
      },
    };
  }

  it("stores and retrieves a cache entry", async () => {
    const { KVCacheHandler } = await import(
      "../packages/vinext/src/cloudflare/kv-cache-handler.js"
    );
    const kv = createMockKV();
    const handler = new KVCacheHandler(kv as any);

    await handler.set("test-key", {
      kind: "PAGES",
      html: "<h1>Hello</h1>",
      pageData: { props: {} },
      headers: undefined,
      status: 200,
    });

    const result = await handler.get("test-key");
    expect(result).not.toBeNull();
    expect(result!.value).not.toBeNull();
    expect(result!.value!.kind).toBe("PAGES");
    if (result!.value!.kind === "PAGES") {
      expect(result!.value!.html).toBe("<h1>Hello</h1>");
      expect(result!.value!.status).toBe(200);
    }
  });

  it("returns null for missing keys", async () => {
    const { KVCacheHandler } = await import(
      "../packages/vinext/src/cloudflare/kv-cache-handler.js"
    );
    const kv = createMockKV();
    const handler = new KVCacheHandler(kv as any);

    const result = await handler.get("nonexistent");
    expect(result).toBeNull();
  });

  it("handles tag-based invalidation", async () => {
    const { KVCacheHandler } = await import(
      "../packages/vinext/src/cloudflare/kv-cache-handler.js"
    );
    const kv = createMockKV();
    const handler = new KVCacheHandler(kv as any);

    await handler.set(
      "tagged-entry",
      {
        kind: "FETCH",
        data: { headers: {}, body: '{"result":1}', url: "test" },
        tags: ["my-tag"],
        revalidate: 0,
      },
      { tags: ["my-tag"] },
    );

    // Before invalidation — entry exists
    const before = await handler.get("tagged-entry");
    expect(before).not.toBeNull();

    // Invalidate the tag
    await handler.revalidateTag("my-tag");

    // After invalidation — entry should be treated as miss
    const after = await handler.get("tagged-entry");
    expect(after).toBeNull();
  });

  it("returns stale entry when past revalidation time", async () => {
    const { KVCacheHandler } = await import(
      "../packages/vinext/src/cloudflare/kv-cache-handler.js"
    );
    const kv = createMockKV();
    const handler = new KVCacheHandler(kv as any);

    // Set with very short revalidation (already expired)
    await handler.set(
      "stale-key",
      {
        kind: "PAGES",
        html: "<h1>Stale</h1>",
        pageData: {},
        headers: undefined,
        status: 200,
      },
      { revalidate: -1 }, // already past
    );

    // Manually fix the revalidateAt to be in the past
    const raw = await kv.get("cache:stale-key");
    const entry = JSON.parse(raw!);
    entry.revalidateAt = Date.now() - 1000;
    await kv.put("cache:stale-key", JSON.stringify(entry));

    const result = await handler.get("stale-key");
    expect(result).not.toBeNull();
    expect(result!.cacheState).toBe("stale");
    expect(result!.value!.kind).toBe("PAGES");
  });

  it("serializes and restores APP_PAGE with rscData ArrayBuffer", async () => {
    const { KVCacheHandler } = await import(
      "../packages/vinext/src/cloudflare/kv-cache-handler.js"
    );
    const kv = createMockKV();
    const handler = new KVCacheHandler(kv as any);

    const originalData = new TextEncoder().encode("RSC payload data");

    await handler.set("app-page-key", {
      kind: "APP_PAGE",
      html: "<div>App page</div>",
      rscData: originalData.buffer as ArrayBuffer,
      headers: { "x-custom": "value" },
      postponed: undefined,
      status: 200,
    });

    const result = await handler.get("app-page-key");
    expect(result).not.toBeNull();
    expect(result!.value!.kind).toBe("APP_PAGE");
    if (result!.value!.kind === "APP_PAGE") {
      expect(result!.value!.html).toBe("<div>App page</div>");
      // rscData should be restored as ArrayBuffer
      expect(result!.value!.rscData).toBeInstanceOf(ArrayBuffer);
      const restored = new TextDecoder().decode(result!.value!.rscData!);
      expect(restored).toBe("RSC payload data");
    }
  });

  it("serializes and restores APP_ROUTE with body ArrayBuffer", async () => {
    const { KVCacheHandler } = await import(
      "../packages/vinext/src/cloudflare/kv-cache-handler.js"
    );
    const kv = createMockKV();
    const handler = new KVCacheHandler(kv as any);

    const body = new TextEncoder().encode('{"ok":true}');

    await handler.set("route-key", {
      kind: "APP_ROUTE",
      body: body.buffer as ArrayBuffer,
      status: 200,
      headers: { "content-type": "application/json" },
    });

    const result = await handler.get("route-key");
    expect(result).not.toBeNull();
    if (result!.value!.kind === "APP_ROUTE") {
      const restored = new TextDecoder().decode(result!.value!.body);
      expect(restored).toBe('{"ok":true}');
    }
  });

  it("sets KV expiration TTL based on revalidation period", async () => {
    const { KVCacheHandler } = await import(
      "../packages/vinext/src/cloudflare/kv-cache-handler.js"
    );
    const kv = createMockKV();
    const handler = new KVCacheHandler(kv as any);

    await handler.set(
      "ttl-key",
      {
        kind: "PAGES",
        html: "<h1>TTL</h1>",
        pageData: {},
        headers: undefined,
        status: 200,
      },
      { revalidate: 60 }, // 60 seconds
    );

    // The KV entry should have an expiration TTL set
    const stored = kv.store.get("cache:ttl-key");
    expect(stored).toBeDefined();
    expect(stored!.expirationTtl).toBeDefined();
    // 10x the revalidation period = 600, but minimum is 60
    expect(stored!.expirationTtl).toBe(600);
  });

  it("handles multiple tag invalidation in parallel", async () => {
    const { KVCacheHandler } = await import(
      "../packages/vinext/src/cloudflare/kv-cache-handler.js"
    );
    const kv = createMockKV();
    const handler = new KVCacheHandler(kv as any);

    await handler.set(
      "multi-tag",
      {
        kind: "FETCH",
        data: { headers: {}, body: "{}", url: "test" },
        tags: ["tag-a", "tag-b"],
        revalidate: 0,
      },
      { tags: ["tag-a", "tag-b"] },
    );

    // Invalidate both tags at once
    await handler.revalidateTag(["tag-a", "tag-b"]);

    const result = await handler.get("multi-tag");
    expect(result).toBeNull();
  });

  it("handles corrupted KV entries gracefully", async () => {
    const { KVCacheHandler } = await import(
      "../packages/vinext/src/cloudflare/kv-cache-handler.js"
    );
    const kv = createMockKV();
    const handler = new KVCacheHandler(kv as any);

    // Put corrupted data directly
    await kv.put("cache:corrupt-key", "not valid json {{{");

    const result = await handler.get("corrupt-key");
    expect(result).toBeNull();
    // The corrupted entry should be cleaned up
    expect(await kv.get("cache:corrupt-key")).toBeNull();
  });
});

// ─── server-only / client-only shims ─────────────────────────────────────────

describe("server-only shim", () => {
  it("can be imported without error", async () => {
    const mod = await import("../packages/vinext/src/shims/server-only.js");
    expect(mod).toBeDefined();
  });

  it("exports nothing (empty marker module)", async () => {
    const mod = await import("../packages/vinext/src/shims/server-only.js");
    // The module should have no named exports (just the default module namespace)
    const keys = Object.keys(mod).filter((k) => k !== "__esModule" && k !== "default");
    expect(keys).toHaveLength(0);
  });
});

describe("client-only shim", () => {
  it("can be imported without error", async () => {
    const mod = await import("../packages/vinext/src/shims/client-only.js");
    expect(mod).toBeDefined();
  });

  it("exports nothing (empty marker module)", async () => {
    const mod = await import("../packages/vinext/src/shims/client-only.js");
    const keys = Object.keys(mod).filter((k) => k !== "__esModule" && k !== "default");
    expect(keys).toHaveLength(0);
  });
});

// ---------------------------------------------------------------------------
// next/link — onNavigate / NavigateEvent (View Transitions support, Issue #38)
// ---------------------------------------------------------------------------

describe("next/link onNavigate / NavigateEvent", () => {
  it("exports Link as default and useLinkStatus as named export", async () => {
    const mod = await import("../packages/vinext/src/shims/link.js");
    expect(typeof mod.default).toBe("object"); // forwardRef returns an object
    expect(typeof mod.useLinkStatus).toBe("function");
  });

  it("NavigateEvent.preventDefault() sets defaultPrevented to true", () => {
    // Mirrors the NavigateEvent construction in the Link click handler
    let prevented = false;
    const navEvent = {
      url: new URL("/about", "http://localhost"),
      preventDefault() { prevented = true; },
      get defaultPrevented() { return prevented; },
    };

    expect(navEvent.defaultPrevented).toBe(false);
    navEvent.preventDefault();
    expect(navEvent.defaultPrevented).toBe(true);
  });

  it("NavigateEvent.defaultPrevented is false when preventDefault is not called", () => {
    let prevented = false;
    const navEvent = {
      url: new URL("/products/1", "http://localhost"),
      preventDefault() { prevented = true; },
      get defaultPrevented() { return prevented; },
    };

    expect(navEvent.defaultPrevented).toBe(false);
    expect(navEvent.url.pathname).toBe("/products/1");
  });

  it("onNavigate callback receives event with correct url", () => {
    // Simulate what the Link component does in its click handler
    const resolvedHref = "/view-transitions/posts/42";
    const navUrl = new URL(resolvedHref, "http://localhost:3000");

    let prevented = false;
    const navEvent = {
      url: navUrl,
      preventDefault() { prevented = true; },
      get defaultPrevented() { return prevented; },
    };

    // Simulated TransitionLink-style callback
    const onNavigate = (event: typeof navEvent) => {
      event.preventDefault();
    };

    onNavigate(navEvent);
    expect(navEvent.defaultPrevented).toBe(true);
    expect(navEvent.url.pathname).toBe("/view-transitions/posts/42");
  });

  it("multiple preventDefault() calls are idempotent", () => {
    let prevented = false;
    const navEvent = {
      url: new URL("/", "http://localhost"),
      preventDefault() { prevented = true; },
      get defaultPrevented() { return prevented; },
    };

    navEvent.preventDefault();
    navEvent.preventDefault();
    navEvent.preventDefault();
    expect(navEvent.defaultPrevented).toBe(true);
  });
});

// ---------------------------------------------------------------------------
// vinext:react-canary — ViewTransition & addTransitionType polyfills (Issue #42)
// ---------------------------------------------------------------------------

describe("vinext:react-canary transform logic", () => {
  // These tests verify the regex patterns used by the vinext:react-canary plugin
  // to detect and rewrite imports of React canary APIs.

  const canaryImportRegex = /import\s*\{[^}]*(ViewTransition|addTransitionType)[^}]*\}\s*from\s*['"]react['"]/;

  it("detects ViewTransition import from react", () => {
    const code = `import { ViewTransition } from "react";`;
    expect(canaryImportRegex.test(code)).toBe(true);
  });

  it("detects addTransitionType import from react", () => {
    const code = `import { addTransitionType } from "react";`;
    expect(canaryImportRegex.test(code)).toBe(true);
  });

  it("detects ViewTransition alongside other React imports", () => {
    const code = `import { useState, ViewTransition, useEffect } from "react";`;
    expect(canaryImportRegex.test(code)).toBe(true);
  });

  it("detects addTransitionType alongside other React imports", () => {
    const code = `import { useRef, addTransitionType } from 'react';`;
    expect(canaryImportRegex.test(code)).toBe(true);
  });

  it("detects both canary APIs in a single import", () => {
    const code = `import { ViewTransition, addTransitionType } from "react";`;
    expect(canaryImportRegex.test(code)).toBe(true);
  });

  it("does NOT match imports from other modules", () => {
    const code = `import { ViewTransition } from "some-other-lib";`;
    expect(canaryImportRegex.test(code)).toBe(false);
  });

  it("does NOT match non-canary React imports", () => {
    const code = `import { useState, useEffect } from "react";`;
    expect(canaryImportRegex.test(code)).toBe(false);
  });

  it("does NOT match default React import", () => {
    const code = `import React from "react";`;
    expect(canaryImportRegex.test(code)).toBe(false);
  });

  it("does NOT match namespace React import", () => {
    const code = `import * as React from "react";`;
    expect(canaryImportRegex.test(code)).toBe(false);
  });

  it("rewrites all 'from react' occurrences in a file with canary imports", () => {
    const code = [
      `import { useState } from "react";`,
      `import { ViewTransition } from "react";`,
      ``,
      `export default function Template({ children }) {`,
      `  return <ViewTransition>{children}</ViewTransition>;`,
      `}`,
    ].join("\n");

    // The transform replaces all `from "react"` in the file
    const result = code.replace(
      /from\s*['"]react['"]/g,
      'from "virtual:vinext-react-canary"',
    );

    expect(result).toContain('from "virtual:vinext-react-canary"');
    expect(result).not.toContain("from \"react\"");
    // Both import lines should be rewritten
    expect(result.match(/virtual:vinext-react-canary/g)?.length).toBe(2);
  });

  it("handles single-quoted imports", () => {
    const code = `import { ViewTransition } from 'react';`;
    expect(canaryImportRegex.test(code)).toBe(true);

    const result = code.replace(
      /from\s*['"]react['"]/g,
      'from "virtual:vinext-react-canary"',
    );
    expect(result).toBe(`import { ViewTransition } from "virtual:vinext-react-canary";`);
  });

  it("handles multiline imports", () => {
    const code = `import {\n  ViewTransition,\n  useState,\n} from "react";`;
    expect(canaryImportRegex.test(code)).toBe(true);
  });
});

describe("ViewTransition polyfill behavior", () => {
  it("provides a passthrough component when React lacks ViewTransition", () => {
    // Simulate the polyfill logic from the virtual module
    const React = { ViewTransition: undefined };
    const ViewTransition = React.ViewTransition || function ViewTransition({ children }: { children: any }) { return children; };

    // ViewTransition should be a function
    expect(typeof ViewTransition).toBe("function");

    // It should pass through children unchanged
    const children = { type: "div", props: {} };
    const result = ViewTransition({ children });
    expect(result).toBe(children);
  });

  it("uses native ViewTransition when React exports it", () => {
    // Simulate React canary that HAS ViewTransition
    const nativeViewTransition = function NativeViewTransition({ children }: { children: any }) {
      return { wrapped: children };
    };
    const React = { ViewTransition: nativeViewTransition };
    const ViewTransition = React.ViewTransition || function ViewTransition({ children }: { children: any }) { return children; };

    expect(ViewTransition).toBe(nativeViewTransition);
  });

  it("provides a no-op addTransitionType when React lacks it", () => {
    const React = { addTransitionType: undefined };
    const addTransitionType = React.addTransitionType || function addTransitionType() {};

    expect(typeof addTransitionType).toBe("function");
    // Should not throw when called
    expect(() => addTransitionType()).not.toThrow();
  });

  it("uses native addTransitionType when React exports it", () => {
    const nativeAddTransitionType = function nativeAddTransitionType(type: string) { return type; };
    const React = { addTransitionType: nativeAddTransitionType };
    const addTransitionType = React.addTransitionType || function addTransitionType() {};

    expect(addTransitionType).toBe(nativeAddTransitionType);
  });
});

// ---------------------------------------------------------------------------
// next/head SSR security tests
// ---------------------------------------------------------------------------

describe("next/head SSR security", () => {
  async function collectHeadHTML(children: React.ReactElement[]) {
    const React = await import("react");
    const { renderToStaticMarkup } = await import("react-dom/server");
    const { default: Head, resetSSRHead, getSSRHeadHTML } = await import(
      "../packages/vinext/src/shims/head.js"
    );

    resetSSRHead();
    // Render Head with children — SSR path collects elements
    renderToStaticMarkup(
      React.createElement(Head, null, ...children),
    );
    return getSSRHeadHTML();
  }

  it("escapes HTML special characters in title children", async () => {
    const React = await import("react");
    const html = await collectHeadHTML([
      React.createElement("title", null, '</title><script>alert("xss")</script>'),
    ]);

    // The injected script tag must be escaped, not raw
    expect(html).not.toContain("<script>");
    expect(html).not.toContain("</title><script>");
    expect(html).toContain("&lt;/title&gt;&lt;script&gt;");
  });

  it("escapes ampersands and angle brackets in children", async () => {
    const React = await import("react");
    const html = await collectHeadHTML([
      React.createElement("title", null, "Tom & Jerry < Friends > Foes"),
    ]);

    expect(html).toContain("Tom &amp; Jerry &lt; Friends &gt; Foes");
    expect(html).not.toContain("Tom & Jerry < Friends > Foes");
  });

  it("still allows dangerouslySetInnerHTML (intentionally raw)", async () => {
    const React = await import("react");
    const html = await collectHeadHTML([
      React.createElement("style", {
        dangerouslySetInnerHTML: { __html: "body { color: red; }" },
      }),
    ]);

    expect(html).toContain("body { color: red; }");
  });

  it("attributes are still properly escaped", async () => {
    const React = await import("react");
    const html = await collectHeadHTML([
      React.createElement("meta", {
        name: "description",
        content: 'He said "hello" & <goodbye>',
      }),
    ]);

    expect(html).toContain("&quot;hello&quot;");
    expect(html).toContain("&amp;");
    expect(html).toContain("&lt;goodbye&gt;");
  });

  it("rejects disallowed tag types (iframe)", async () => {
    const React = await import("react");
    const consoleWarn = vi.spyOn(console, "warn").mockImplementation(() => {});

    const html = await collectHeadHTML([
      React.createElement("iframe" as any, { src: "https://evil.com" }),
    ]);

    expect(html).toBe("");
    expect(consoleWarn).toHaveBeenCalledWith(
      expect.stringContaining("ignoring disallowed tag <iframe>"),
    );
    consoleWarn.mockRestore();
  });

  it("rejects disallowed tag types (object, embed, form)", async () => {
    const React = await import("react");
    const consoleWarn = vi.spyOn(console, "warn").mockImplementation(() => {});

    const html = await collectHeadHTML([
      React.createElement("object" as any, { data: "https://evil.com" }),
      React.createElement("embed" as any, { src: "https://evil.com" }),
      React.createElement("form" as any, { action: "https://evil.com" }),
    ]);

    expect(html).toBe("");
    consoleWarn.mockRestore();
  });

  it("allows all valid head tags", async () => {
    const React = await import("react");
    const { resetSSRHead } = await import(
      "../packages/vinext/src/shims/head.js"
    );

    const allowedTags = ["title", "meta", "link", "style", "script", "base", "noscript"];

    for (const tag of allowedTags) {
      resetSSRHead();
      const selfClosing = ["meta", "link", "base"].includes(tag);
      const el = selfClosing
        ? React.createElement(tag, { name: "test", content: "test" })
        : React.createElement(tag, null, "test content");

      const html = await collectHeadHTML([el]);

      expect(html).toContain(`<${tag}`);
      expect(html).toContain('data-vinext-head="true"');
    }
  });
});
