import { test, expect, beforeEach } from "bun:test";
import { Window } from "happy-dom";
import { NAV_RUNTIME } from "../src/nav-runtime.ts";

let win: any;
let fetchCalls: { url: string; opts: any }[];
let nextHtml: string;

function install(bodyHtml: string): void {
  win = new Window({ url: "https://example.test/" });
  win.document.body.innerHTML = bodyHtml;
  fetchCalls = [];
  nextHtml = "";
  const g = globalThis as any;
  g.window = win;
  g.document = win.document;
  g.history = win.history;
  g.location = win.location;
  g.DOMParser = win.DOMParser;
  g.CustomEvent = win.CustomEvent;
  g.Event = win.Event;
  g.fetch = win.fetch = (url: string, opts: any) => {
    fetchCalls.push({ url, opts });
    return Promise.resolve({
      redirected: false,
      url,
      headers: {
        get: (k: string) =>
          k.toLowerCase() === "content-type" ? "text/html; charset=utf-8" : null,
      },
      text: () => Promise.resolve(nextHtml),
    });
  };
  (0, eval)(NAV_RUNTIME);
}

const flush = () => new Promise((r) => setTimeout(r, 0));

beforeEach(() => {
  const g = globalThis as any;
  for (const k of [
    "window",
    "document",
    "history",
    "location",
    "DOMParser",
    "CustomEvent",
    "Event",
    "fetch",
  ]) {
    delete g[k];
  }
});

test("intercepts an internal link click and swaps #app in place", async () => {
  install(`<div id="app"><h1>Home</h1><a href="/about" id="lnk">About</a></div>`);
  nextHtml =
    `<!doctype html><html><head><title>About</title></head>` +
    `<body><div id="app"><h1>About page</h1></div></body></html>`;
  win.document.getElementById("lnk").click();
  await flush();
  expect(fetchCalls.length).toBe(1);
  expect(fetchCalls[0]!.url).toContain("/about");
  expect(fetchCalls[0]!.opts.headers["x-wrnexus-nav"]).toBe("1");
  expect(win.document.getElementById("app").innerHTML).toContain("About page");
  expect(win.document.title).toBe("About");
});

test("prefetches focused routes once and reuses the document during navigation", async () => {
  install(`<div id="app"><a href="/about" id="prefetch">About</a></div>`);
  nextHtml =
    `<!doctype html><html><head><title>About</title></head>` +
    `<body><div id="app"><h1>Prefetched</h1></div></body></html>`;
  const link = win.document.getElementById("prefetch");
  link.dispatchEvent(new win.FocusEvent("focusin", { bubbles: true }));
  await flush();
  expect(fetchCalls).toHaveLength(1);
  expect(fetchCalls[0]?.opts.headers["x-wrnexus-prefetch"]).toBe("1");
  link.click();
  await flush();
  expect(fetchCalls).toHaveLength(1);
  expect(win.document.querySelector("h1")?.textContent).toBe("Prefetched");
});

test("does not prefetch links merely passing under the pointer while scrolling", async () => {
  install(`<div id="app"><a href="/about" id="prefetch">About</a></div>`);
  const link = win.document.getElementById("prefetch");
  link.dispatchEvent(new win.PointerEvent("pointerover", { bubbles: true }));
  await flush();
  expect(fetchCalls).toHaveLength(0);
  link.dispatchEvent(new win.PointerEvent("pointerdown", { bubbles: true }));
  await flush();
  expect(fetchCalls).toHaveLength(1);
  expect(fetchCalls[0]?.opts.headers["x-wrnexus-prefetch"]).toBe("1");
});

test("ignores cross-origin links (full navigation)", async () => {
  install(`<div id="app"><a href="https://other.test/x" id="lnk">x</a></div>`);
  win.document.getElementById("lnk").click();
  await flush();
  expect(fetchCalls.length).toBe(0);
});

test("ignores modified clicks so new-tab still works", async () => {
  install(`<div id="app"><a href="/about" id="lnk">x</a></div>`);
  const MouseEventConstructor = (win as unknown as { MouseEvent: typeof MouseEvent }).MouseEvent;
  win.document.getElementById("lnk").dispatchEvent(
    new MouseEventConstructor("click", {
      bubbles: true,
      cancelable: true,
      button: 0,
      metaKey: true,
    }),
  );
  await flush();
  expect(fetchCalls.length).toBe(0);
});

test("exposes programmatic navigation", async () => {
  install(`<div id="app"><h1>Home</h1></div>`);
  nextHtml = `<html><head><title>Dash</title></head><body><div id="app"><h1>Dashboard</h1></div></body></html>`;
  expect(typeof win.__wrnexusNavigate).toBe("function");
  win.__wrnexusNavigate("/dashboard");
  await flush();
  expect(fetchCalls.length).toBe(1);
  expect(win.document.getElementById("app").innerHTML).toContain("Dashboard");
});

test("rebinds theme controls after swapping the page", async () => {
  install(`<div id="app"><a href="/about" id="lnk">About</a></div>`);
  let boundRoot: unknown;
  win.wireTheme = { bind: (root: unknown) => (boundRoot = root) };
  nextHtml = `<html><body><div id="app"><button data-wire-theme-toggle>Theme</button></div></body></html>`;

  win.document.getElementById("lnk").click();
  await flush();

  expect(boundRoot).toBe(win.document);
  expect(win.document.querySelector("[data-wire-theme-toggle]")).not.toBeNull();
});

test("unmounts and remounts package runtimes during client navigation", async () => {
  install(
    `<div id="app"><div data-wrnexus-runtime="captcha">Old</div><a href="/next" id="lnk">Next</a></div>`,
  );
  let mounts = 0;
  let unmounts = 0;
  win.__wrnexusRuntimes = {
    captcha: {
      mount: () => mounts++,
      unmount: () => unmounts++,
    },
  };
  nextHtml = `<html><body><div id="app"><div data-wrnexus-runtime="captcha">New</div></div></body></html>`;

  win.document.getElementById("lnk").click();
  await flush();

  expect(unmounts).toBe(1);
  expect(mounts).toBe(1);
  expect(win.document.getElementById("app").textContent).toContain("New");
});

test("synchronizes page styles during client navigation and preserves the current nonce", async () => {
  install(
    `<script nonce="current-nonce"></script>` +
      `<style data-wrnexus-style-id="home" data-wrnexus-style-kind="page" nonce="current-nonce">.page{color:red}</style>` +
      `<div id="app"><a href="/about" id="lnk">About</a></div>`,
  );
  nextHtml =
    `<html><head>` +
    `<style data-wrnexus-style-id="about" data-wrnexus-style-owner="About" data-wrnexus-style-kind="page" nonce="response-nonce">.page{color:blue}</style>` +
    `</head><body><div id="app"><h1 class="page">About</h1></div></body></html>`;

  win.document.getElementById("lnk").click();
  await flush();

  expect(win.document.querySelector('style[data-wrnexus-style-id="home"]')).toBeNull();
  const style = win.document.querySelector('style[data-wrnexus-style-id="about"]');
  expect(style).not.toBeNull();
  expect(style.textContent).toContain("color:blue");
  expect(style.getAttribute("nonce")).toBe("current-nonce");
});

test("restores declared form state but never preserves sensitive fields", async () => {
  install(
    `<meta name="wrnexus-preserve" content="forms">` +
      `<div id="app"><input name="query" value="draft"><input name="password" type="password" value="secret"></div>`,
  );
  nextHtml = `<html><head><meta name="wrnexus-preserve" content="forms"></head><body><div id="app">Next</div></body></html>`;
  win.__wrnexusNavigate("/next");
  await flush();

  nextHtml =
    `<html><head><meta name="wrnexus-preserve" content="forms"></head><body><div id="app">` +
    `<input name="query" value=""><input name="password" type="password" value=""></div></body></html>`;
  win.__wrnexusNavigate("/");
  await flush();

  expect(win.document.querySelector('[name="query"]').value).toBe("draft");
  expect(win.document.querySelector('[name="password"]').value).toBe("");
});

test("KeepAlive preserves the same live DOM instance across routes", async () => {
  install(
    `<div id="app"><section data-wrn-keepalive="dashboard"><input value="live"></section></div>`,
  );
  const original = win.document.querySelector("[data-wrn-keepalive]");
  original.runtimeState = { count: 7 };
  nextHtml = `<html><body><div id="app"><section data-wrn-keepalive="dashboard"><input value="new"></section></div></body></html>`;
  win.__wrnexusNavigate("/next");
  await flush();
  const restored = win.document.querySelector("[data-wrn-keepalive]");
  expect(restored).toBe(original);
  expect(restored.runtimeState).toEqual({ count: 7 });
  expect(restored.querySelector("input").value).toBe("live");
});
