import { createVNode, h } from "vue";
import { describe, test, expect, afterEach, vi, beforeEach } from "vitest";
import { enableAutoUnmount, flushPromises } from "@vue/test-utils";

import InstanceRegistry from "../InstanceRegistry";
import useProgrammaticComponent from "../useProgrammatic";

describe("useProgrammatic tests", () => {
    // create a new factory
    const factory = useProgrammaticComponent();

    beforeEach(() => {
        vi.useFakeTimers();
    });

    afterEach(() => {
        // clear body after each test
        document.body.innerHTML = "";
        vi.useRealTimers();

        // clear factory items
        factory.closeAll();
        flushPromises(); // await promise finished
    });

    enableAutoUnmount(afterEach);

    test("test mounting component correctly", async () => {
        const component = createVNode({
            template: `<button id="mycomp"></button>`,
        });

        // open element
        const { close, promise } = factory.open(component);

        // check promise get called
        const handler = vi.fn();
        promise.then(() => handler());
        expect(handler).not.toHaveBeenCalled();

        // check element exist
        let el = document.body.querySelector("#mycomp");
        expect(el).not.toBeNull();

        // close element through handler
        close();
        vi.runAllTimers();

        // check element does not exist
        el = document.body.querySelector("#mycomp");
        expect(el).toBeNull();

        await flushPromises(); // await promise finished
        expect(handler).toHaveBeenCalledOnce();
    });

    test("test mounting with target correctly", () => {
        const container = document.createElement("div");
        container.id = "my-cool-container";
        document.body.appendChild(container);

        const component = createVNode({
            template: `<button id="mycomp"></button>`,
        });

        // open element
        const { close } = factory.open(component, {}, "#my-cool-container");

        // check element exist
        let el = document.body.querySelector("#mycomp");
        expect(el).not.toBeNull();

        let bodyElements = document.body.querySelectorAll("body > *");
        expect(bodyElements).toHaveLength(1);

        // close element through handler
        close();
        vi.runAllTimers();

        // check element does not exist
        el = document.body.querySelector("#mycomp");
        expect(el).toBeNull();

        // check container still exists
        bodyElements = document.body.querySelectorAll("body > *");
        expect(bodyElements).toHaveLength(1);
    });

    test("test close event working correctly", () => {
        const component = createVNode({
            template: `<button id="mycomp" @click="$emit('close', 'abc')"></button>`,
        });

        const onClose = vi.fn();

        // open element
        factory.open(component, { onClose });

        // check element exist
        let el = document.body.querySelector("button");
        expect(el).not.toBeNull();

        // close element through click
        el?.click();
        vi.runAllTimers();

        // check element does not exist
        el = document.body.querySelector("button");
        expect(el).toBeNull();

        expect(onClose).toHaveBeenCalledOnce();
    });

    test("test props working correctly", () => {
        const component = createVNode({
            template: `<button id="mycomp"></button>`,
        });

        // open element
        const { close } = factory.open(component, {
            props: { "data-oruga": "programmatic" },
        });

        // check element exist

        let el = document.body.querySelector<HTMLButtonElement>(
            '[data-oruga="programmatic"]',
        );
        expect(el).not.toBeNull();

        // close element through click
        close();
        vi.runAllTimers();

        // check element does not exist
        el = document.body.querySelector<HTMLButtonElement>(
            '[data-oruga="programmatic"]',
        );
        expect(el).toBeNull();
    });

    test("test using custom instance registry", () => {
        const instanceRegistry = new InstanceRegistry();

        expect(instanceRegistry.count()).toBe(0);

        const { close } = factory.open("div", {
            registry: instanceRegistry,
        });

        expect(instanceRegistry.count()).toBe(1);

        close();
        vi.runAllTimers();

        expect(instanceRegistry.count()).toBe(0);
    });

    test("test closeAll is working correctly", () => {
        const root = document.createElement("div");

        // open elements
        factory.open("div", {}, root);
        factory.open("div", {}, root);

        expect(factory.count()).toBe(2);

        let apps = root.querySelectorAll("#programmatic-app");
        expect(apps).toHaveLength(2);

        // close all elements
        factory.closeAll();
        vi.runAllTimers();

        expect(factory.count()).toBe(0);

        // check elements are removed
        apps = root.querySelectorAll("#programmatic-app");
        expect(apps).toHaveLength(0);
    });

    test("test close last is working correctly", () => {
        // open elements
        factory.open("div");
        factory.open("div");

        expect(factory.count()).toBe(2);

        let bodyElements = document.body.querySelectorAll("#programmatic-app");
        expect(bodyElements).toHaveLength(2);

        // close last element
        factory.close();
        vi.runAllTimers();

        expect(factory.count()).toBe(1);

        bodyElements = document.body.querySelectorAll("#programmatic-app");
        expect(bodyElements).toHaveLength(1);

        // close last element
        factory.close();
        vi.runAllTimers();

        expect(factory.count()).toBe(0);

        bodyElements = document.body.querySelectorAll("#programmatic-app");
        expect(bodyElements).toHaveLength(0);
    });

    test("test render slot correctly", () => {
        // create inner slot element
        const slot = h("p", { "data-oruga": "inner-slot" }, "HELP");

        const component = createVNode(
            { template: `<button id="mycomp"><slot /></button>` },
            null,
            () => slot,
        );

        // open elements
        const { close } = factory.open(component);

        // check element exist
        const button = document.body.querySelector("button");
        expect(button).not.toBeNull();

        // check if slot element exist
        expect(button?.innerHTML).toBe(`<p data-oruga="inner-slot">HELP</p>`);
        const innerSlot = document.body.querySelector(
            '[data-oruga="inner-slot"]',
        );
        expect(innerSlot).not.toBeNull();

        // close element
        close();
        vi.runAllTimers();
    });
});
