import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { render, act, screen, cleanup } from "@testing-library/react";
import { useAutoScroll } from "../useAutoScroll";
// ---------------------------------------------------------------------------
// Test harness — renders actual DOM elements so refs get populated
// ---------------------------------------------------------------------------
let latestResult;
function Harness() {
    const result = useAutoScroll();
    latestResult = result;
    return (_jsxs("div", { ref: result.scrollRef, "data-testid": "scroller", children: [_jsx("div", { ref: result.contentRef, "data-testid": "content", children: _jsx("p", { children: "Hello" }) }), _jsx("div", { ref: result.sentinelRef, "data-testid": "sentinel" })] }));
}
let ioCallback;
let ioDisconnect;
let ioObserve;
let roCallback;
let roDisconnect;
let roObserve;
beforeEach(() => {
    ioDisconnect = vi.fn();
    ioObserve = vi.fn();
    roDisconnect = vi.fn();
    roObserve = vi.fn();
    vi.stubGlobal("IntersectionObserver", vi.fn((cb) => {
        ioCallback = cb;
        return {
            observe: ioObserve,
            unobserve: vi.fn(),
            disconnect: ioDisconnect,
            takeRecords: vi.fn(() => []),
            root: null,
            rootMargin: "",
            thresholds: [0],
        };
    }));
    vi.stubGlobal("ResizeObserver", vi.fn((cb) => {
        roCallback = cb;
        return {
            observe: roObserve,
            unobserve: vi.fn(),
            disconnect: roDisconnect,
        };
    }));
    vi.stubGlobal("requestAnimationFrame", vi.fn((cb) => {
        cb(performance.now());
        return 1;
    }));
    vi.stubGlobal("cancelAnimationFrame", vi.fn());
});
afterEach(() => {
    cleanup();
    vi.restoreAllMocks();
});
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
function fireIO(isIntersecting) {
    act(() => {
        ioCallback([{ isIntersecting }], {});
    });
}
function fireRO() {
    act(() => {
        roCallback([], {});
    });
}
// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------
describe("useAutoScroll", () => {
    it("starts in following state", () => {
        render(_jsx(Harness, {}));
        expect(latestResult.isFollowing).toBe(true);
    });
    it("observes sentinel with IntersectionObserver on mount", () => {
        render(_jsx(Harness, {}));
        expect(ioObserve).toHaveBeenCalledWith(screen.getByTestId("sentinel"));
    });
    it("observes content with ResizeObserver on mount", () => {
        render(_jsx(Harness, {}));
        expect(roObserve).toHaveBeenCalledWith(screen.getByTestId("content"));
    });
    it("configures IO with 80px bottom root margin", () => {
        render(_jsx(Harness, {}));
        const IOConstructor = vi.mocked(IntersectionObserver);
        const options = IOConstructor.mock.calls[0]?.[1];
        expect(options?.rootMargin).toBe("0px 0px 80px 0px");
    });
    it("configures IO with scroll container as root", () => {
        render(_jsx(Harness, {}));
        const IOConstructor = vi.mocked(IntersectionObserver);
        const options = IOConstructor.mock.calls[0]?.[1];
        expect(options?.root).toBe(screen.getByTestId("scroller"));
    });
    it("disengages when sentinel exits viewport", () => {
        render(_jsx(Harness, {}));
        fireIO(false);
        expect(latestResult.isFollowing).toBe(false);
    });
    it("re-engages when sentinel enters viewport", () => {
        render(_jsx(Harness, {}));
        fireIO(false);
        expect(latestResult.isFollowing).toBe(false);
        fireIO(true);
        expect(latestResult.isFollowing).toBe(true);
    });
    it("jumpToLatest re-engages follow mode", () => {
        render(_jsx(Harness, {}));
        fireIO(false);
        expect(latestResult.isFollowing).toBe(false);
        act(() => {
            latestResult.jumpToLatest();
        });
        expect(latestResult.isFollowing).toBe(true);
    });
    it("jumpToLatest sets scrollTop to scrollHeight", () => {
        render(_jsx(Harness, {}));
        fireIO(false);
        const scroller = screen.getByTestId("scroller");
        Object.defineProperty(scroller, "scrollHeight", {
            value: 1000,
            configurable: true,
        });
        act(() => {
            latestResult.jumpToLatest();
        });
        expect(scroller.scrollTop).toBe(1000);
    });
    it("scrolls via rAF on content resize while following", () => {
        render(_jsx(Harness, {}));
        fireIO(true);
        const scroller = screen.getByTestId("scroller");
        Object.defineProperty(scroller, "scrollHeight", {
            value: 500,
            configurable: true,
        });
        fireRO();
        expect(requestAnimationFrame).toHaveBeenCalled();
        expect(scroller.scrollTop).toBe(500);
    });
    it("does not scroll on content resize while disengaged", () => {
        render(_jsx(Harness, {}));
        fireIO(false);
        const scroller = screen.getByTestId("scroller");
        Object.defineProperty(scroller, "scrollHeight", {
            value: 500,
            configurable: true,
        });
        vi.mocked(requestAnimationFrame).mockClear();
        fireRO();
        expect(requestAnimationFrame).not.toHaveBeenCalled();
    });
    it("cancels pending rAF before scheduling a new one", () => {
        render(_jsx(Harness, {}));
        fireIO(true);
        fireRO();
        fireRO();
        expect(cancelAnimationFrame).toHaveBeenCalled();
    });
    it("disconnects IO on unmount", () => {
        const { unmount } = render(_jsx(Harness, {}));
        unmount();
        expect(ioDisconnect).toHaveBeenCalled();
    });
    it("disconnects RO on unmount", () => {
        const { unmount } = render(_jsx(Harness, {}));
        unmount();
        expect(roDisconnect).toHaveBeenCalled();
    });
    it("cancels pending rAF on unmount", () => {
        const { unmount } = render(_jsx(Harness, {}));
        fireIO(true);
        fireRO();
        vi.mocked(cancelAnimationFrame).mockClear();
        unmount();
        expect(cancelAnimationFrame).toHaveBeenCalled();
    });
    it("scrolls to bottom on mount without error", () => {
        // The hook sets scrollTop = scrollHeight in the IO setup effect.
        // In jsdom both values are 0, so scrollTop stays 0. The important
        // thing is that the code path executes without error; real scroll
        // behavior is validated by integration tests.
        render(_jsx(Harness, {}));
        expect(screen.getByTestId("scroller").scrollTop).toBe(0);
    });
});
//# sourceMappingURL=useAutoScroll.test.js.map