class EventTarget {
  addEventListener() { }
  removeEventListener() { }
  dispatchEvent() {
    return true;
  }
}

class Node extends EventTarget { }

class Element extends Node {
  attributes: NamedNodeMap;
  childNodes: NodeListOf<ChildNode>;
  role: string | null = null;
}

class ResizeObserver {
  observe() { }
  unobserve() { }
  disconnect() { }
}

const documentShim = {
  createElement: function () {
    return new globalThisShim.HTMLElement();
  },
  createElementNS: function () {
    return new globalThisShim.HTMLElement();
  },
  addEventListener() { },
  removeEventListener() { },
  dispatchEvent(_event: Event) {
    return false;
  },
} as unknown as typeof globalThis['document'];

const globalThisShim = {
  ResizeObserver,
  document: documentShim,
  Node,
  Element,
  HTMLElement: class HTMLElement extends Element {
    innerHTML: string = '';
    get content() {
      return new globalThisShim.DocumentFragment();
    }
  },
  DocumentFragment: class DocumentFragment extends EventTarget { },
  customElements: {
    get: function () { },
    define: function () { },
    whenDefined: function () { },
  },
  localStorage: {
    getItem(_key: string) {
      return null;
    },
    setItem(_key: string, _value: string) { },
    removeItem(_key: string) { },
  },
  CustomEvent: function CustomEvent() { },
  getComputedStyle: function () { },
  navigator: {
    languages: [],
    get userAgent() {
      return '';
    },
  },
  matchMedia(media: string) {
    return {
      matches: false,
      media,
    };
  },
  DOMParser: class DOMParser {
    parseFromString(string: string, _contentType: string) {
      return {
        body: {
          textContent: string
        }
      };
    }
  },
} as unknown as typeof globalThis;

export const isServer =
  'global' in globalThis && globalThis?.global === globalThis || // node or node-like environments, whether or not there are global polyfills like jsdom
  typeof window === 'undefined' || typeof window.customElements === 'undefined'; // generic check for global window object to account for non-node-like server environements

const isShimmed = Object.keys(globalThisShim).every((key) => key in globalThis);

export const GlobalThis: typeof globalThis =
  isServer && !isShimmed ? globalThisShim : globalThis;
export const Document: typeof globalThis['document'] &
  Partial<{
    webkitExitFullscreen: typeof globalThis['document']['exitFullscreen'];
  }> = isServer && !isShimmed ? documentShim : globalThis.document;

export { GlobalThis as globalThis, Document as document };
