/**
 * Characterization ("golden") tests — freeze the observable API surface of
 * Popover. Added to guard the swap from @floating-ui/react to a local
 * implementation; retained as a long-lived contract test.
 *
 * Pixel math (flip/shift/offset values) is NOT asserted here — jsdom has no
 * layout. Those are verified manually in Storybook.
 */
import { fireEvent, render, screen } from "@testing-library/react";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { Popover } from ".";

describe("Popover — characterization (API contract)", () => {
  describe("trigger element", () => {
    it("renders the trigger as the cloned child with merged class names", () => {
      render(
        <Popover
          trigger={
            <button type="button" className="my-trigger">
              Toggle
            </button>
          }
        >
          Body
        </Popover>,
      );

      const trigger = screen.getByText("Toggle");
      expect(trigger.tagName).toBe("BUTTON");
      expect(trigger).toHaveClass("my-trigger", "mobius-popover__toggle");
    });

    it("does not wrap the trigger in an extra element", () => {
      const { container } = render(
        <Popover trigger={<button type="button">Toggle</button>}>Body</Popover>,
      );
      expect(container.firstElementChild?.tagName).toBe("BUTTON");
    });
  });

  describe("floating container (when open)", () => {
    const open = () => {
      render(
        <Popover trigger={<button type="button">Toggle</button>}>Body</Popover>,
      );
      fireEvent.click(screen.getByText("Toggle"));
      return document.querySelector(
        ".mobius-popover__container",
      ) as HTMLDivElement;
    };

    it("is not in the DOM when closed", () => {
      render(
        <Popover trigger={<button type="button">Toggle</button>}>Body</Popover>,
      );
      expect(document.querySelector(".mobius-popover__container")).toBeNull();
    });

    it("is portalled to document.body when open", () => {
      const floating = open();
      expect(floating).toHaveClass("mobius", "mobius-popover__container");
      expect(floating.parentElement).toBe(document.body);
    });

    it("uses a viewport-anchored positioning strategy", () => {
      // Local implementation uses position: absolute with document-relative
      // top/left (viewport rect + window.scrollY/X). Because the popover is
      // portalled to document.body, the initial containing block is the
      // document, so absolute coords pin it to the trigger and scroll
      // natively with the page.
      const floating = open();
      expect(["fixed", "absolute"]).toContain(floating.style.position);
    });

    it("has inline top/left styles applied", () => {
      const floating = open();
      expect(floating).not.toHaveStyle({ top: "" });
      expect(floating).not.toHaveStyle({ left: "" });
    });
  });

  describe("arrow element", () => {
    it("renders an SVG with the arrow class and aria-hidden", () => {
      render(
        <Popover trigger={<button type="button">Toggle</button>}>Body</Popover>,
      );
      fireEvent.click(screen.getByText("Toggle"));
      const arrow = document.querySelector("svg.mobius-popover__arrow-icon");
      expect(arrow).not.toBeNull();
      expect(arrow).toHaveAttribute("aria-hidden", "true");
      expect(arrow).toHaveStyle({ position: "absolute" });
    });
  });
});

describe("Popover — characterization (dismiss semantics)", () => {
  it("closes and fires onClose when pressing outside via pointerdown", () => {
    const onClose = vi.fn();
    render(
      <div>
        <Popover
          trigger={<button type="button">Toggle</button>}
          onClose={onClose}
        >
          Body
        </Popover>
        <button type="button" data-testid="outside">
          Outside
        </button>
      </div>,
    );

    fireEvent.click(screen.getByText("Toggle"));
    expect(screen.getByText("Body")).toBeInTheDocument();

    fireEvent.pointerDown(screen.getByTestId("outside"));

    expect(screen.queryByText("Body")).not.toBeInTheDocument();
    expect(onClose).toHaveBeenCalledTimes(1);
  });

  it("does not fire onClose when pointerdown lands on the toggle itself", () => {
    const onClose = vi.fn();
    render(
      <Popover
        trigger={<button type="button">Toggle</button>}
        onClose={onClose}
      >
        Body
      </Popover>,
    );

    fireEvent.click(screen.getByText("Toggle"));
    expect(screen.getByText("Body")).toBeInTheDocument();

    fireEvent.pointerDown(screen.getByText("Toggle"));
    fireEvent.click(screen.getByText("Toggle"));

    expect(screen.queryByText("Body")).not.toBeInTheDocument();
    expect(onClose).toHaveBeenCalledTimes(1);
  });

  it("does not close when pointerdown lands inside the floating container", () => {
    const onClose = vi.fn();
    render(
      <Popover
        trigger={<button type="button">Toggle</button>}
        onClose={onClose}
      >
        <span>Body</span>
      </Popover>,
    );

    fireEvent.click(screen.getByText("Toggle"));
    fireEvent.pointerDown(screen.getByText("Body"));

    expect(screen.getByText("Body")).toBeInTheDocument();
    expect(onClose).not.toHaveBeenCalled();
  });

  it("closes on Escape and fires onClose", () => {
    const onClose = vi.fn();
    render(
      <Popover
        trigger={<button type="button">Toggle</button>}
        onClose={onClose}
      >
        Body
      </Popover>,
    );

    fireEvent.click(screen.getByText("Toggle"));
    expect(screen.getByText("Body")).toBeInTheDocument();

    fireEvent.keyDown(window, { key: "Escape" });

    expect(screen.queryByText("Body")).not.toBeInTheDocument();
    expect(onClose).toHaveBeenCalledTimes(1);
  });
});

describe("Popover — characterization (auto-update subscriptions)", () => {
  let windowAddSpy: ReturnType<typeof vi.spyOn>;
  let windowRemoveSpy: ReturnType<typeof vi.spyOn>;
  let resizeObserverInstances: Array<{
    observe: ReturnType<typeof vi.fn>;
    unobserve: ReturnType<typeof vi.fn>;
    disconnect: ReturnType<typeof vi.fn>;
  }>;

  beforeEach(() => {
    resizeObserverInstances = [];
    class MockResizeObserver {
      observe = vi.fn();
      unobserve = vi.fn();
      disconnect = vi.fn();
      constructor() {
        resizeObserverInstances.push(this);
      }
    }
    vi.stubGlobal("ResizeObserver", MockResizeObserver);
    windowAddSpy = vi.spyOn(window, "addEventListener");
    windowRemoveSpy = vi.spyOn(window, "removeEventListener");
  });

  afterEach(() => {
    vi.unstubAllGlobals();
    windowAddSpy.mockRestore();
    windowRemoveSpy.mockRestore();
  });

  const scrollListeners = () =>
    windowAddSpy.mock.calls.filter((c: unknown[]) => c[0] === "scroll");
  const resizeListeners = () =>
    windowAddSpy.mock.calls.filter((c: unknown[]) => c[0] === "resize");

  it("subscribes to window scroll when opened", () => {
    render(
      <Popover trigger={<button type="button">Toggle</button>}>Body</Popover>,
    );
    expect(scrollListeners().length).toBe(0);

    fireEvent.click(screen.getByText("Toggle"));

    expect(scrollListeners().length).toBeGreaterThan(0);
  });

  it("subscribes to window resize when opened", () => {
    render(
      <Popover trigger={<button type="button">Toggle</button>}>Body</Popover>,
    );
    fireEvent.click(screen.getByText("Toggle"));
    expect(resizeListeners().length).toBeGreaterThan(0);
  });

  it("creates a ResizeObserver that observes reference + floating elements", () => {
    render(
      <Popover trigger={<button type="button">Toggle</button>}>Body</Popover>,
    );
    fireEvent.click(screen.getByText("Toggle"));

    const totalObserves = resizeObserverInstances.reduce(
      (n, ro) => n + ro.observe.mock.calls.length,
      0,
    );
    expect(totalObserves).toBeGreaterThanOrEqual(2);
  });

  it("removes scroll/resize listeners when closed", () => {
    render(
      <Popover trigger={<button type="button">Toggle</button>}>Body</Popover>,
    );
    fireEvent.click(screen.getByText("Toggle"));
    const scrollAdded: number = scrollListeners().length;
    const resizeAdded: number = resizeListeners().length;

    fireEvent.click(screen.getByLabelText("Close"));

    const scrollRemoved: number = windowRemoveSpy.mock.calls.filter(
      (c: unknown[]) => c[0] === "scroll",
    ).length;
    const resizeRemoved: number = windowRemoveSpy.mock.calls.filter(
      (c: unknown[]) => c[0] === "resize",
    ).length;

    expect(scrollRemoved).toBeGreaterThanOrEqual(scrollAdded);
    expect(resizeRemoved).toBeGreaterThanOrEqual(resizeAdded);
  });
});
