import { act, renderHook } from "@testing-library/react";
import assert from "node:assert/strict";
import {
  afterEach, describe, it, mock, 
} from "node:test";
import { useIO } from "./useIO";
import { useListeners } from "./useListeners";
import { useRefs } from "./useRefs";
import { EventBus } from "../../plugins/eventBus";

describe("React utility hook lifecycle", () => {
  const originalIntersectionObserver = globalThis.IntersectionObserver;

  afterEach(() => {
    globalThis.IntersectionObserver = originalIntersectionObserver;
  });

  it("keeps refs stable while length is unchanged", () => {
    const { result, rerender } = renderHook(({ length }) => useRefs(length), {
      initialProps: { length: 2 },
    });
    const firstRefs = result.current;

    rerender({ length: 2 });

    assert.strictEqual(result.current[0], firstRefs[0]);
    assert.strictEqual(result.current[1], firstRefs[1]);
  });

  it("disconnects IntersectionObserver on unmount", () => {
    const disconnect = mock.fn();
    class IntersectionObserverMock {

      disconnect = disconnect;

      observe() {}

      takeRecords() {
        return []; 
      }

      unobserve() {}

      root = null;

      rootMargin = "0px";

      scrollMargin = "0px";

      thresholds = [ 0 ];
    
    }
    globalThis.IntersectionObserver = IntersectionObserverMock as typeof IntersectionObserver;
    const ref = { current: document.createElement("div") };

    const { unmount } = renderHook(() => useIO({ ref, callback: mock.fn() }));
    unmount();

    assert.strictEqual(disconnect.mock.calls.length, 1);
  });

  it("replaces event callbacks after rerender", () => {
    const eventBus = new EventBus();
    const target = document.createElement("div");
    const ref = { current: target };
    const first = mock.fn();
    const second = mock.fn();
    const { rerender } = renderHook(({ listener }) => useListeners({
      EventBusInstance: eventBus,
      listeners: { change: listener },
      ref,
    }), { initialProps: { listener: first } });

    act(() => target.dispatchEvent(new window.Event("change")));
    rerender({ listener: second });
    act(() => target.dispatchEvent(new window.Event("change")));

    assert.strictEqual(first.mock.calls.length, 1);
    assert.strictEqual(second.mock.calls.length, 1);
  });
});
