/* @vitest-environment jsdom */

import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";

import { observeBrowserViewport } from "../viewport_observer";

class MockVisualViewport extends EventTarget {
  height = 760;
  offsetLeft = 0;
  offsetTop = 0;
  scale = 1;
  width = 390;
}

class MockVirtualKeyboard extends EventTarget {
  overlaysContent = false;
  boundingRect = { height: 0 };
}

describe("viewport observer", () => {
  let visualViewport: MockVisualViewport;
  let virtualKeyboard: MockVirtualKeyboard;

  beforeEach(() => {
    visualViewport = new MockVisualViewport();
    virtualKeyboard = new MockVirtualKeyboard();
    Object.defineProperty(window, "visualViewport", {
      configurable: true,
      value: visualViewport,
    });
    Object.defineProperty(navigator, "virtualKeyboard", {
      configurable: true,
      value: virtualKeyboard,
    });
  });

  afterEach(() => {
    vi.restoreAllMocks();
  });

  it("shares one browser listener set across multiple consumers", () => {
    const addWindowListener = vi.spyOn(window, "addEventListener");
    const removeWindowListener = vi.spyOn(window, "removeEventListener");
    const first = vi.fn();
    const second = vi.fn();

    const stopFirst = observeBrowserViewport(first);
    const stopSecond = observeBrowserViewport(second);

    expect(
      addWindowListener.mock.calls.filter(
        ([event]) => String(event) === "resize",
      ),
    ).toHaveLength(1);
    expect(first).toHaveBeenCalledWith(
      expect.objectContaining({ reason: "initial" }),
    );
    expect(second).toHaveBeenCalledWith(
      expect.objectContaining({ reason: "initial" }),
    );

    visualViewport.height = 460;
    visualViewport.dispatchEvent(new Event("resize"));
    expect(first).toHaveBeenLastCalledWith(
      expect.objectContaining({
        reason: "visualViewport.resize",
        metrics: expect.objectContaining({ visualHeight: 460 }),
      }),
    );
    expect(second).toHaveBeenCalledTimes(2);

    stopFirst();
    visualViewport.dispatchEvent(new Event("scroll"));
    expect(first).toHaveBeenCalledTimes(2);
    expect(second).toHaveBeenCalledTimes(3);

    stopSecond();
    expect(
      removeWindowListener.mock.calls.filter(
        ([event]) => String(event) === "resize",
      ),
    ).toHaveLength(1);
  });
});
