import { createPopper } from "@popperjs/core";
import { mount } from "@vue/test-utils";
import { afterEach, describe, expect, test, vi } from "vitest";

import PopperComponent from "./Popper.vue";

// value from usePopper.ts
const DELAY_CLOSE = 50;

vi.mock("@popperjs/core", () => ({
    createPopper: vi.fn(() => ({
        destroy: vi.fn(),
        update: vi.fn(),
    })),
}));

function mountTarget(trigger = "click", interactive = false) {
    return mount(PopperComponent, {
        propsData: {
            title: "Test Title",
            placement: "bottom",
            interactive,
            trigger,
        },
        slots: {
            reference: "<button>Reference</button>",
            default: "<p>Popper Content</p>",
        },
    });
}

describe("PopperComponent.vue", () => {
    afterEach(() => {
        vi.clearAllMocks();
    });

    test("renders component with default props", async () => {
        const wrapper = mountTarget();
        expect(wrapper.find(".popper-element").exists()).toBe(true);
        expect(wrapper.find(".popper-element").isVisible()).toBe(false);
        const reference = wrapper.find("button");
        await reference.trigger("click");
        expect(wrapper.find(".popper-header").exists()).toBe(true);
        expect(wrapper.find(".popper-header").text()).toContain("Test Title");
    });

    test("opens and closes popper on click trigger", async () => {
        const wrapper = mountTarget();
        const reference = wrapper.find("button");
        expect(wrapper.find(".popper-element").isVisible()).toBe(false);
        await reference.trigger("click");
        expect(wrapper.find(".popper-element").isVisible()).toBe(true);
        await wrapper.find(".popper-close").trigger("click");
        expect(wrapper.find(".popper-element").isVisible()).toBe(false);
    });

    test("disables popper when `disabled` prop is true", async () => {
        const wrapper = mountTarget();
        await wrapper.setProps({ disabled: true });
        const reference = wrapper.find("button");
        await reference.trigger("click");
        expect(wrapper.find(".popper-element").isVisible()).toBe(false);
    });

    test("renders the arrow when `arrow` prop is true", () => {
        const wrapper = mountTarget();
        expect(wrapper.find(".popper-arrow").exists()).toBe(true);
    });

    test("does not render the arrow when `arrow` prop is false", async () => {
        const wrapper = mountTarget();
        await wrapper.setProps({ arrow: false });
        expect(wrapper.find(".popper-arrow").exists()).toBe(false);
    });

    test("applies correct mode class", async () => {
        const wrapper = mountTarget();
        await wrapper.setProps({ mode: "light" });
        expect(wrapper.find(".popper-element").classes()).toContain("popper-element-light");
        await wrapper.setProps({ mode: "dark" });
        expect(wrapper.find(".popper-element").classes()).toContain("popper-element-dark");
    });

    test("uses correct placement prop", () => {
        mountTarget();
        expect(createPopper).toHaveBeenCalledWith(
            expect.anything(),
            expect.anything(),
            expect.objectContaining({ placement: "bottom" }),
        );
    });

    test("updates visibility when props or watchers change", async () => {
        const wrapper = mountTarget();
        await wrapper.setProps({ disabled: false });
        const reference = wrapper.find("button");
        await reference.trigger("click");
        expect(wrapper.find(".popper-element").isVisible()).toBe(true);
        await wrapper.setProps({ disabled: true });
        expect(wrapper.find(".popper-element").isVisible()).toBe(false);
    });

    test("shows and hides popper on hover trigger over reference", async () => {
        const wrapper = mountTarget("hover");
        const reference = wrapper.find("button");
        const popperElement = wrapper.find(".popper-element");
        expect(popperElement.isVisible()).toBe(false);
        await reference.trigger("mouseover");
        expect(popperElement.isVisible()).toBe(true);
        await reference.trigger("mouseout");
        await new Promise((r) => setTimeout(r, 0));
        expect(popperElement.isVisible()).toBe(false);
    });

    test("popper remains visible when hovering over popper", async () => {
        const wrapper = mountTarget("hover", true);
        const reference = wrapper.find("button");
        const popperElement = wrapper.find(".popper-element");
        expect(popperElement.isVisible()).toBe(false);
        await reference.trigger("mouseover");
        expect(popperElement.isVisible()).toBe(true);
        await reference.trigger("mouseout");
        await new Promise((r) => setTimeout(r, DELAY_CLOSE / 2));
        expect(popperElement.isVisible()).toBe(true);
        await popperElement.trigger("mouseover");
        await new Promise((r) => setTimeout(r, DELAY_CLOSE * 2));
        await popperElement.trigger("mouseout");
        await new Promise((r) => setTimeout(r, DELAY_CLOSE * 2));
        expect(popperElement.isVisible()).toBe(false);
    });

    test("popper remains visible when clicked inside of popper", async () => {
        const wrapper = mountTarget("click");
        const reference = wrapper.find("button");
        const popperElement = wrapper.find(".popper-element");
        expect(popperElement.isVisible()).toBe(false);
        await reference.trigger("click");
        expect(popperElement.isVisible()).toBe(true);
        await popperElement.trigger("mouseover");
        expect(popperElement.isVisible()).toBe(true);
        await popperElement.trigger("mouseout");
        expect(popperElement.isVisible()).toBe(true);
        await popperElement.trigger("click");
        expect(popperElement.isVisible()).toBe(true);
    });
});
