// @vitest-environment happy-dom
// Mirror the IS_REACT_ACT_ENVIRONMENT setup from useDrawerUrlState.test.ts
// so React doesn't log act() warnings about the createRoot render/unmount
// and the popstate-driven state updates.
(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;

import React, { act } from "react";
import { createRoot, type Root } from "react-dom/client";
import { describe, expect, it, vi, beforeEach, afterEach } from "vitest";
import { useProviderDrawerUrlState } from "./useProviderDrawerUrlState";

/**
 * Minimal renderHook shim — same pattern as the other hook tests.
 */
function renderHook<TResult>(setup: () => TResult): {
  result: { current: TResult | undefined };
  unmount: () => void;
} {
  const result: { current: TResult | undefined } = { current: undefined };
  const container = document.createElement("div");
  document.body.appendChild(container);

  function Probe() {
    result.current = setup();
    return null;
  }

  const root: Root = createRoot(container);
  act(() => {
    root.render(React.createElement(Probe));
  });

  return {
    result,
    unmount: () => {
      act(() => root.unmount());
      container.remove();
    },
  };
}

/**
 * Point happy-dom's URL at a known value. happy-dom v20 doesn't expose a
 * writable `window.location.search`, but `window.happyDOM.setURL` updates
 * the URL the window reports without triggering a navigation — exactly
 * what we want for mounting the hook at `/providers?provider=NPI` etc.
 */
function setLocation(url: string): void {
  (window as unknown as { happyDOM: { setURL: (u: string) => void } }).happyDOM.setURL(url);
}

describe("useProviderDrawerUrlState", () => {
  type PushState = (state: unknown, unused: string, url?: string | URL | null) => void;
  let pushStateMock: ReturnType<typeof vi.fn<PushState>>;
  let replaceStateMock: ReturnType<typeof vi.fn<PushState>>;

  beforeEach(() => {
    pushStateMock = vi.fn();
    replaceStateMock = vi.fn();

    vi.stubGlobal("history", {
      pushState: pushStateMock,
      replaceState: replaceStateMock,
      state: null,
    });
  });

  afterEach(() => {
    vi.unstubAllGlobals();
    setLocation("http://localhost/");
  });

  it("reads the ?provider= param from window.location.search on mount", () => {
    setLocation("http://localhost/providers?provider=1881068062");

    const { result, unmount } = renderHook(() => useProviderDrawerUrlState());

    expect(result.current?.providerNpi).toBe("1881068062");
    expect(typeof result.current?.open).toBe("function");
    expect(typeof result.current?.close).toBe("function");
    expect(typeof result.current?.setProviderNpi).toBe("function");

    unmount();
  });

  it("returns null providerNpi when no ?provider= param is set", () => {
    setLocation("http://localhost/providers");

    const { result, unmount } = renderHook(() => useProviderDrawerUrlState());

    expect(result.current?.providerNpi).toBeNull();

    unmount();
  });

  it("returns null providerNpi when ?provider= is present but empty", () => {
    setLocation("http://localhost/providers?provider=");

    const { result, unmount } = renderHook(() => useProviderDrawerUrlState());

    expect(result.current?.providerNpi).toBeNull();

    unmount();
  });

  it("open(npi) pushes a new history entry containing ?provider=npi", () => {
    setLocation("http://localhost/providers");

    const { result, unmount } = renderHook(() => useProviderDrawerUrlState());

    act(() => {
      result.current?.open("1881068062");
    });

    expect(pushStateMock).toHaveBeenCalledTimes(1);
    expect(replaceStateMock).not.toHaveBeenCalled();
    const urlArg = pushStateMock.mock.calls[0][2] as string;
    expect(urlArg).toContain("?provider=1881068062");

    unmount();
  });

  it("setProviderNpi(npi) replaces the current history entry (no new entry) and does NOT pushState", () => {
    setLocation("http://localhost/providers?provider=1881068062");

    const { result, unmount } = renderHook(() => useProviderDrawerUrlState());

    act(() => {
      result.current?.setProviderNpi("1881068063");
    });

    expect(replaceStateMock).toHaveBeenCalledTimes(1);
    expect(pushStateMock).not.toHaveBeenCalled();
    const urlArg = replaceStateMock.mock.calls[0][2] as string;
    expect(urlArg).toContain("?provider=1881068063");

    unmount();
  });

  it("open() and close() preserve other query params (only ?provider= is touched)", () => {
    setLocation("http://localhost/providers?sort=name&provider=1881068062");

    const { result, unmount } = renderHook(() => useProviderDrawerUrlState());

    act(() => {
      result.current?.open("1881068063");
    });
    const openUrl = pushStateMock.mock.calls[0][2] as string;
    expect(openUrl).toContain("sort=name");
    expect(openUrl).toMatch(/[?&]provider=1881068063/);

    act(() => {
      result.current?.close();
    });
    const closeUrl = pushStateMock.mock.calls[1][2] as string;
    expect(closeUrl).toContain("sort=name");
    expect(closeUrl).not.toContain("provider=");

    unmount();
  });

  it("close() pushes a new history entry with the ?provider= param stripped", () => {
    setLocation("http://localhost/providers?provider=1881068062");

    const { result, unmount } = renderHook(() => useProviderDrawerUrlState());

    act(() => {
      result.current?.close();
    });

    expect(pushStateMock).toHaveBeenCalledTimes(1);
    expect(replaceStateMock).not.toHaveBeenCalled();
    const urlArg = pushStateMock.mock.calls[0][2] as string;
    expect(urlArg).not.toContain("?provider=");
    expect(result.current?.providerNpi).toBeNull();

    unmount();
  });

  it("updates providerNpi in response to popstate (browser back/forward)", async () => {
    setLocation("http://localhost/providers?provider=1881068062");

    const { result, unmount } = renderHook(() => useProviderDrawerUrlState());

    expect(result.current?.providerNpi).toBe("1881068062");

    setLocation("http://localhost/providers?provider=1881068063");
    await act(async () => {
      window.dispatchEvent(new PopStateEvent("popstate"));
      await Promise.resolve();
    });

    expect(result.current?.providerNpi).toBe("1881068063");

    unmount();
  });

  it("does not collide with the existing ?claim= or ?remit= params (orthogonal keys)", () => {
    // The providers drawer is independent of the claims and remits
    // drawers — all three can be open simultaneously in the future, and
    // the hooks must not stomp each other's URL state. Opening a
    // provider must leave ?claim= and ?remit= intact.
    setLocation("http://localhost/?claim=CLM-1&remit=REM-1");

    const { result, unmount } = renderHook(() => useProviderDrawerUrlState());

    act(() => {
      result.current?.open("1881068062");
    });
    const openUrl = pushStateMock.mock.calls[0][2] as string;
    expect(openUrl).toContain("claim=CLM-1");
    expect(openUrl).toContain("remit=REM-1");
    expect(openUrl).toMatch(/[?&]provider=1881068062/);

    unmount();
  });
});