import { JSDOM } from "jsdom";

import { register } from "node:module";

// Register the loader so we can use the / prefix to load files from the src directory
register(new URL("./loader.js", import.meta.url));

// Enable JSDOM
const dom = new JSDOM("", { url: "http://localhost/" });
globalThis.window = dom.window;
globalThis.window.scrollTo = () => {};
globalThis.document = dom.window.document;
globalThis.HTMLElement = dom.window.HTMLElement;
globalThis.customElements = dom.window.customElements;
globalThis.CustomEvent = dom.window.CustomEvent;
globalThis.Node = dom.window.Node;
globalThis.DocumentFragment = dom.window.DocumentFragment;
globalThis.Event = dom.window.Event;
globalThis.KeyboardEvent = dom.window.KeyboardEvent;
globalThis.MouseEvent = dom.window.MouseEvent;
globalThis.MutationObserver = dom.window.MutationObserver;
globalThis.FileReader = dom.window.FileReader;
globalThis.Blob = dom.window.Blob;
globalThis.getComputedStyle = dom.window.getComputedStyle;
// Node has its own global navigator; alias JSDOM's so code under test and
// tests (which mock properties on window.navigator) see the same object.
Object.defineProperty(globalThis, "navigator", {
  value: dom.window.navigator,
  configurable: true,
  writable: true,
});

// Mock requestAnimationFrame
const realSetTimeout = globalThis.setTimeout;
globalThis.requestAnimationFrame = (callback) => realSetTimeout(callback, 0);
globalThis.window.requestAnimationFrame = globalThis.requestAnimationFrame;

// Mock matchMedia
globalThis.window.matchMedia = (query) => ({
  matches: false,
  media: query,
  onchange: null,
  addListener: () => {},
  removeListener: () => {},
  addEventListener: () => {},
  removeEventListener: () => {},
  dispatchEvent: () => {},
});

class IntersectionObserver {
  observe() {}
  unobserve() {}
  disconnect() {}
}
globalThis.IntersectionObserver = IntersectionObserver;

globalThis.window.HTMLDialogElement.prototype.showModal = function () {
  this.setAttribute("open", "");
};
globalThis.window.HTMLDialogElement.prototype.close = function () {
  this.removeAttribute("open");
  this.dispatchEvent(new globalThis.Event("close"));
};

globalThis.window.HTMLElement.prototype.showPopover = function () {};
globalThis.window.HTMLElement.prototype.hidePopover = function () {};

globalThis.window.HTMLMediaElement.prototype.play = function () {
  return Promise.resolve();
};
globalThis.window.HTMLMediaElement.prototype.pause = function () {};
globalThis.window.HTMLMediaElement.prototype.load = function () {};

// Prevent network requests. We can mock this in individual tests as needed.
delete globalThis.fetch;

// JSDOM's window.crypto lacks subtle; swap in node's webcrypto.
if (!globalThis.window.crypto?.subtle) {
  Object.defineProperty(globalThis.window, "crypto", {
    value: globalThis.crypto,
    configurable: true,
    writable: true,
  });
}

class LocalStorageStub {
  constructor() {
    this._store = {};
  }
  getItem(key) {
    return key in this._store ? this._store[key] : null;
  }
  setItem(key, value) {
    this._store[key] = String(value);
  }
  removeItem(key) {
    delete this._store[key];
  }
  get length() {
    return Object.keys(this._store).length;
  }
  key(index) {
    return Object.keys(this._store)[index] ?? null;
  }
  clear() {
    this._store = {};
  }
}

globalThis.localStorage = new LocalStorageStub();

globalThis.Element = globalThis.window.Element;

class HighlightStub {
  constructor(...ranges) {
    this._ranges = new Set(ranges);
  }
  add(range) {
    this._ranges.add(range);
  }
  delete(range) {
    return this._ranges.delete(range);
  }
  clear() {
    this._ranges.clear();
  }
  get size() {
    return this._ranges.size;
  }
}
globalThis.Highlight = HighlightStub;
globalThis.window.Highlight = HighlightStub;
if (typeof globalThis.window.CSS === "undefined") {
  globalThis.window.CSS = {};
}
globalThis.window.CSS.highlights = new Map();
globalThis.CSS = globalThis.window.CSS;

// Real emoji element makes a fetch / uses indexeddb, so stub it
class EmojiPickerStub extends globalThis.window.HTMLElement {}
if (!globalThis.customElements.get("emoji-picker")) {
  globalThis.customElements.define("emoji-picker", EmojiPickerStub);
}
