type StubGlobal = (name: PropertyKey, value: unknown) => void;

interface GlobalMockParams {
  fn: <T extends (...args: never[]) => unknown>(implementation?: T) => T;
  stubGlobal?: StubGlobal;
}

const defaultStubGlobal: StubGlobal = (name, value) => {
  Object.defineProperty(globalThis, name, {
    value,
    writable: true,
    enumerable: true,
    configurable: true,
  });
};

export function mockMatchMedia({ fn, stubGlobal = defaultStubGlobal }: GlobalMockParams) {
  stubGlobal(
    'matchMedia',
    fn<Window['matchMedia']>((query) => {
      const matches = /^\(min-width: (\d+)px\)$/u.exec(query);
      const minWidth = matches != null ? Number(matches[1]) : undefined;
      return {
        matches: minWidth != null ? window.innerWidth >= minWidth : false,
        media: query,
        onchange: null,
        addListener: fn(),
        removeListener: fn(),
        addEventListener: fn(),
        removeEventListener: fn(),
        dispatchEvent: fn(),
      };
    }),
  );
}

export function mockResizeObserver({ fn, stubGlobal = defaultStubGlobal }: GlobalMockParams) {
  stubGlobal(
    'ResizeObserver',
    fn(
      (): ResizeObserver => ({
        observe: fn(),
        unobserve: fn(),
        disconnect: fn(),
      }),
    ),
  );
}
