import { mount } from "@vue/test-utils";
import { describe, it, expect } from "vitest";

import UTabs from "../UTabs.vue";
import UTab from "../../ui.navigation-tab/UTab.vue";
import UButton from "../../ui.button/UButton.vue";

import type { Props, UTabsOption } from "../types";

describe("UTabs.vue", () => {
  // Global options definition
  const options: UTabsOption[] = [
    { value: "tab1", label: "Tab 1" },
    { value: "tab2", label: "Tab 2" },
    { value: "tab3", label: "Tab 3" },
  ];

  describe("Props", () => {
    it("ModelValue – correctly sets the selected tab", () => {
      const modelValue = "tab2";
      const expectedActiveClass = "border-primary";

      const component = mount(UTabs, {
        props: {
          options,
          modelValue,
        },
      });

      // Find all UTab components
      const tabs = component.findAllComponents(UTab);

      // Check that the correct tab is active
      expect(tabs.length).toBe(options.length);

      // The second tab should be active
      const activeTab = tabs[1];

      expect(activeTab.classes()).toContain(expectedActiveClass);
    });

    it("Options – renders the correct number of tabs from options", () => {
      const component = mount(UTabs, {
        props: {
          options,
        },
      });

      // Find all UTab components
      const tabs = component.findAllComponents(UTab);

      // Check that the correct number of tabs is rendered
      expect(tabs.length).toBe(options.length);

      // Check that the tabs have the correct labels
      options.forEach((option, index) => {
        expect(tabs[index].text()).toBe(option.label);
      });
    });

    it("Size – applies the correct size to tabs", () => {
      const sizes = ["2xs", "xs", "sm", "md", "lg", "xl"];

      sizes.forEach((size) => {
        const component = mount(UTabs, {
          props: {
            options: [{ value: "tab1", label: "Tab 1" }],
            size: size as Props["size"],
          },
        });

        // Find the UTab component
        const tab = component.findComponent(UTab);

        // Find the UButton inside the UTab
        const button = tab.findComponent(UButton);

        // Check that the button has the correct size
        expect(button.props("size")).toBe(size);
      });
    });

    it("Scrollable – applies scrollable class when scrollable prop is true", () => {
      const scrollable = true;

      const component = mount(UTabs, {
        props: {
          options,
          scrollable,
        },
      });

      // Find the tabs container
      const tabsContainer = component.find(`[vl-key="tabs"]`);

      // Check that the container has the scrollable class
      expect(tabsContainer.classes()).toContain("scroll-smooth");
    });

    it("Scroll – shows scroll buttons when scrollable and content overflows", async () => {
      const manyOptions: UTabsOption[] = Array.from({ length: 10 }, (_, i) => ({
        value: `tab${i}`,
        label: `Tab ${i}`,
      }));

      const component = mount(UTabs, {
        props: {
          options: manyOptions,
          scrollable: true,
        },
      });

      // Mock the scroll position to show both arrows
      const scrollContainer = component.find(`[vl-key="tabs"]`).element;

      Object.defineProperty(scrollContainer, "scrollLeft", { value: 10 });
      Object.defineProperty(scrollContainer, "scrollWidth", { value: 1000 });
      Object.defineProperty(scrollContainer, "clientWidth", { value: 500 });

      // Trigger scroll event
      await component.find("[vl-key='tabs']").trigger("scroll");

      // Both arrows should be visible
      const buttons = component.findAllComponents(UButton);

      // Check that at least two buttons are rendered
      expect(buttons.length).toBeGreaterThanOrEqual(2);

      // Find the buttons with the correct icons
      const prevButton = buttons.find((button) => button.props("icon") === "chevron_left");
      const nextButton = buttons.find((button) => button.props("icon") === "chevron_right");

      expect(prevButton).toBeDefined();
      expect(nextButton).toBeDefined();
    });

    it("Block – provides block value to tabs", () => {
      const block = true;

      const component = mount(UTabs, {
        props: {
          options: [{ value: "tab1", label: "Tab 1" }],
          block,
        },
      });

      // Find the UTab component
      const tab = component.findComponent(UTab);

      // Find the UButton inside the UTab
      const button = tab.findComponent(UButton);

      // Check that the button has the block prop
      expect(button.props("block")).toBe(block);
    });

    it("Square – provides square value to tabs", () => {
      const square = true;

      const component = mount(UTabs, {
        props: {
          options: [{ value: "tab1", label: "Tab 1" }],
          square,
        },
      });

      // Find the UTab component
      const tab = component.findComponent(UTab);

      // Find the UButton inside the UTab
      const button = tab.findComponent(UButton);

      // Check that the button has the square prop
      expect(button.props("square")).toBe(square);
    });

    it("DataTest – applies the correct data-test attribute", () => {
      const dataTest = "test-tabs";
      const singleOption = [options[0]];

      const component = mount(UTabs, {
        props: {
          options: singleOption,
          dataTest,
        },
      });

      // Find the tabs container
      const tabsContainer = component.find(`[data-test="${dataTest}"]`);

      // Check that the container has the data-test attribute
      expect(tabsContainer.exists()).toBe(true);

      // Check that the tab has the correct data-test attribute
      const tab = component.find(`[data-test="${dataTest}-item-0"]`);

      expect(tab.exists()).toBe(true);
    });
  });

  describe("Slots", () => {
    it("Default – renders content from default slot", () => {
      const slotContent = "Custom Tabs";
      const slotClass = "custom-tabs";

      const component = mount(UTabs, {
        slots: {
          default: `<div class="${slotClass}">${slotContent}</div>`,
        },
      });

      expect(component.find(`.${slotClass}`).exists()).toBe(true);
      expect(component.find(`.${slotClass}`).text()).toBe(slotContent);
    });

    it("Prev – renders content from prev slot when scrollable", async () => {
      const slotContent = "Prev";
      const slotClass = "prev-content";
      const dataTest = "test-tabs";

      const manyOptions: UTabsOption[] = Array.from({ length: 10 }, (_, i) => ({
        value: `tab${i}`,
        label: `Tab ${i}`,
      }));

      const component = mount(UTabs, {
        props: {
          options: manyOptions,
          scrollable: true,
          dataTest,
        },
        slots: {
          prev: `<div class="${slotClass}">${slotContent}</div>`,
        },
      });

      // Mock the scroll position to show left arrow
      const scrollContainer = component.find(`[vl-key="tabs"]`).element;

      Object.defineProperty(scrollContainer, "scrollLeft", { value: 10 });
      Object.defineProperty(scrollContainer, "scrollWidth", { value: 1000 });
      Object.defineProperty(scrollContainer, "clientWidth", { value: 500 });

      // Trigger scroll event
      await component.find("[vl-key='tabs']").trigger("scroll");

      // Now the left arrow should be visible
      expect(component.find(`.${slotClass}`).exists()).toBe(true);
      expect(component.find(`.${slotClass}`).text()).toBe(slotContent);
    });

    it("Next – renders content from next slot when scrollable", async () => {
      const slotContent = "Next";
      const slotClass = "next-content";
      const dataTest = "test-tabs";

      const manyOptions: UTabsOption[] = Array.from({ length: 10 }, (_, i) => ({
        value: `tab${i}`,
        label: `Tab ${i}`,
      }));

      const component = mount(UTabs, {
        props: {
          options: manyOptions,
          scrollable: true,
          dataTest,
        },
        slots: {
          next: `<div class="${slotClass}">${slotContent}</div>`,
        },
      });

      // Mock the scroll position to show right arrow
      const scrollContainer = component.find(`[vl-key="tabs"]`).element;

      Object.defineProperty(scrollContainer, "scrollLeft", { value: 0 });
      Object.defineProperty(scrollContainer, "scrollWidth", { value: 1000 });
      Object.defineProperty(scrollContainer, "clientWidth", { value: 500 });

      // Trigger scroll event
      await component.find("[vl-key='tabs']").trigger("scroll");

      // Now the right arrow should be visible
      expect(component.find(`.${slotClass}`).exists()).toBe(true);
      expect(component.find(`.${slotClass}`).text()).toBe(slotContent);
    });

    it("Left – renders content from left slot for each tab", () => {
      const slotText = "Left";
      const slotClass = "left-content";

      const component = mount(UTabs, {
        props: {
          options,
        },
        slots: {
          left: `<span class="${slotClass}">${slotText}</span>`,
        },
      });

      const leftContents = component.findAll(`.${slotClass}`);

      expect(leftContents.length).toBe(options.length);
      leftContents.forEach((left) => {
        expect(left.text()).toBe(slotText);
      });
    });

    it("Label – renders content from label slot instead of default label", () => {
      const testOptions: UTabsOption[] = [{ value: "tab1", label: "Tab 1" }];
      const slotText = "Custom Label";
      const slotClass = "label-content";

      const component = mount(UTabs, {
        props: {
          options: testOptions,
        },
        slots: {
          label: `<span class="${slotClass}">${slotText}</span>`,
        },
      });

      expect(component.find(`.${slotClass}`).exists()).toBe(true);
      expect(component.find(`.${slotClass}`).text()).toBe(slotText);
      expect(component.text()).not.toContain(testOptions[0].label);
    });

    it("Right – renders content from right slot", () => {
      const slotText = "Right";
      const slotClass = "right-content";

      const component = mount(UTabs, {
        props: {
          options,
        },
        slots: {
          right: `<span class="${slotClass}">${slotText}</span>`,
        },
      });

      expect(component.findAll(`.${slotClass}`).length).toBe(options.length);
      expect(component.find(`.${slotClass}`).text()).toBe(slotText);
    });

    it("Slot – passes item, index and active state to slots", () => {
      const modelValue = "tab2";

      const component = mount(UTabs, {
        props: {
          options,
          modelValue,
        },
        slots: {
          left: `
            <div
              :data-value="params.item.value"
              :data-index="params.index"
              :data-active="params.active"
            />
          `,
        },
      });

      options.forEach((option, index) => {
        const el = component.find(`[data-value="${option.value}"][data-index="${index}"]`);

        expect(el.exists()).toBe(true);
      });

      const activeEl = component.find('[data-active="true"]');

      expect(activeEl.exists()).toBe(true);
      expect(activeEl.attributes("data-value")).toBe(modelValue);
    });
  });

  describe("Events", () => {
    it("Update:modelValue – emits update:modelValue event when tab is clicked", async () => {
      const component = mount(UTabs, {
        props: {
          options,
          modelValue: "tab1",
        },
      });

      // Find the second tab and click it
      const secondTab = component.findAllComponents(UTab)[1];

      await secondTab.trigger("click");

      // Check that the update:modelValue event was emitted with the correct value
      expect(component.emitted("update:modelValue")).toBeTruthy();
      expect(component.emitted("update:modelValue")?.[0]).toEqual(["tab2"]);
    });
  });

  describe("Exposed refs", () => {
    it("wrapperRef – exposes wrapperRef", () => {
      const singleOption = [options[0]];

      const component = mount(UTabs, {
        props: {
          options: singleOption,
        },
      });

      expect(component.vm.wrapperRef).toBeDefined();
    });
  });
});
