import { act, assertEquals, cleanup, render } from "./setup.ts";
import { EditorMode } from "../../islands/Editor.tsx";
import Editor from "../../islands/Editor.tsx";

function setupMocks() {
  const clickSpy: { href: string; download: string }[] = [];
  const createObjectURLCalls: string[] = [];
  let revokeObjectURLCalled = false;

  const originalCreateElement = document.createElement.bind(document);
  document.createElement = (tag: string) => {
    const el = originalCreateElement(tag) as HTMLAnchorElement;
    if (tag === "a") {
      el.click = () => {
        clickSpy.push({ href: el.href, download: el.download });
      };
    }
    return el;
  };

  const origCreateObjectURL = URL.createObjectURL;
  const origRevokeObjectURL = URL.revokeObjectURL;
  URL.createObjectURL = () => {
    createObjectURLCalls.push("called");
    return "blob:fake-url";
  };
  URL.revokeObjectURL = () => {
    revokeObjectURLCalled = true;
  };

  const origFetch = globalThis.fetch;
  globalThis.fetch = (() =>
    Promise.resolve(
      new Response("/* mock css */", {
        status: 200,
        headers: { "Content-Type": "text/css" },
      }),
    )) as typeof fetch;

  const listeners: { type: string; listener: EventListener }[] = [];
  const origAddEventListener = globalThis.addEventListener;
  const origRemoveEventListener = globalThis.removeEventListener;
  globalThis.addEventListener = (
    type: string,
    listener: EventListenerOrEventListenerObject,
  ) => {
    listeners.push({
      type,
      listener: listener as EventListener,
    });
    return origAddEventListener.call(globalThis, type, listener);
  };
  globalThis.removeEventListener = (
    type: string,
    listener: EventListenerOrEventListenerObject,
  ) => {
    const idx = listeners.findIndex((l) =>
      l.type === type && l.listener === (listener as EventListener)
    );
    if (idx !== -1) listeners.splice(idx, 1);
    return origRemoveEventListener.call(globalThis, type, listener);
  };

  const emitEvent = (type: string, detail: string) => {
    const match = listeners.filter((l) => l.type === type);
    for (const { listener } of match) {
      listener({ type, detail } as unknown as Event);
    }
  };

  return {
    clickSpy,
    createObjectURLCalls,
    get revokeObjectURLCalled() {
      return revokeObjectURLCalled;
    },
    emitEvent,
    getListenerCount(type: string) {
      return listeners.filter((l) => l.type === type).length;
    },
    restore() {
      document.createElement = originalCreateElement;
      URL.createObjectURL = origCreateObjectURL;
      URL.revokeObjectURL = origRevokeObjectURL;
      globalThis.fetch = origFetch;
      globalThis.addEventListener = origAddEventListener;
      globalThis.removeEventListener = origRemoveEventListener;
    },
  };
}

Deno.test({
  name:
    "Editor - DownloadRequest event triggers download with correct filename",
  fn() {
    const mocks = setupMocks();

    render(<Editor id="test1" content="# Hello" allowMode={EditorMode.Read} />);

    act(() => {
      mocks.emitEvent("DownloadRequest", "My Post");
    });

    assertEquals(mocks.clickSpy.length, 1);
    assertEquals(mocks.clickSpy[0].download, "My Post.md");
    assertEquals(mocks.createObjectURLCalls.length, 1);
    assertEquals(mocks.revokeObjectURLCalled, true);

    mocks.restore();
    cleanup();
  },
  sanitizeResources: false,
  sanitizeOps: false,
});

Deno.test({
  name: "Editor - DownloadRequest uses Untitled for empty title",
  fn() {
    const mocks = setupMocks();

    render(
      <Editor id="test2" content="Some content" allowMode={EditorMode.Read} />,
    );

    act(() => {
      mocks.emitEvent("DownloadRequest", "");
    });

    assertEquals(mocks.clickSpy.length, 1);
    assertEquals(mocks.clickSpy[0].download, "Untitled.md");

    mocks.restore();
    cleanup();
  },
  sanitizeResources: false,
  sanitizeOps: false,
});

Deno.test({
  name: "Editor - removes DownloadRequest listener on unmount",
  fn() {
    const mocks = setupMocks();

    const { unmount } = render(
      <Editor id="test3" content="test" allowMode={EditorMode.Read} />,
    );

    assertEquals(mocks.getListenerCount("DownloadRequest"), 1);

    act(() => {
      unmount();
    });

    assertEquals(mocks.getListenerCount("DownloadRequest"), 0);

    mocks.restore();
    cleanup();
  },
  sanitizeResources: false,
  sanitizeOps: false,
});
