import { fireEvent, render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { TabPanel } from "../TabPanel";
import { Tabs } from "../Tabs";
import TabsStatic from "../Tabs.static";

const arr = [1, 2, 3, 4];

const TabsExample = (props) => {
  return (
    <Tabs data-testid="test-id" {...props}>
      <TabPanel id="tabpanel-1" tab="Tab 1">
        Tab 1 content
      </TabPanel>
      <TabPanel id="tabpanel-2" tab="Tab 2">
        Tab 2 content
      </TabPanel>
      <TabPanel id="tabpanel-3" tab="Tab 3">
        Tab 3 content
      </TabPanel>
      <TabPanel id="tabpanel-4" isDisabled tab="Tab 4">
        Tab 4 content
      </TabPanel>
    </Tabs>
  );
};

//children were required plus they helped to check defaultTabIndex
describe("rendering Tabs", () => {
  describe("initial state", () => {
    it("has class tab-list", () => {
      const { getByTestId } = render(<TabsExample />);
      expect(getByTestId("test-id")).toBeInTheDocument();
      expect(getByTestId("test-id")).toHaveClass("tab-list");
    });
    it("has role tablist", () => {
      const { getByTestId } = render(<TabsExample />);
      expect(getByTestId("test-id")).toBeInTheDocument();
      expect(getByTestId("test-id").getAttribute("role")).toBe("tablist");
    });
    it("default activeTabIndex is set to 0", () => {
      const { container } = render(<TabsExample />);
      expect(
        container.querySelector('button[tabindex="0"]'),
      ).toBeInTheDocument();
      expect(container.querySelector('button[tabindex="0"]').textContent).toBe(
        "Tab 1",
      );
    });
  });
  describe("passed props", () => {
    it("has additional class when classesTabNav is set", () => {
      const { getByTestId } = render(
        <TabsExample classesTabNav="test-class" />,
      );
      expect(getByTestId("test-id")).toHaveClass("tab-list");
      expect(getByTestId("test-id")).toHaveClass("test-class");
    });
    it("has additional class tab-list--equal when hasEqualTabWidth prop is passed", () => {
      const { getByTestId } = render(<TabsExample hasEqualTabWidth />);
      expect(getByTestId("test-id")).toHaveClass("tab-list");
      expect(getByTestId("test-id")).toHaveClass("tab-list--equal");
    });
    it("has additional class tab-list--fullwidth when isFullWidth prop is passed", () => {
      const { getByTestId } = render(<TabsExample isFullWidth />);
      expect(getByTestId("test-id")).toHaveClass("tab-list");
      expect(getByTestId("test-id")).toHaveClass("tab-list--fullwidth");
    });
    it("renders list element with role presentation and class tab-list__hr when isFullWidth prop is passed", () => {
      const { container } = render(<TabsExample isFullWidth />);
      const li = container.getElementsByClassName("tab-list__hr");
      expect(li.length).toBe(1);
      expect(li[0]).toBeInTheDocument();
      expect(li[0].getAttribute("role")).toBe("presentation");
    });
    it("activeTabIndex is set to 0, therefore text content of first TabPanel doesnt have hidden attribute", () => {
      const { container } = render(<TabsExample />);
      for (let i of arr) {
        const panel = container.querySelector(`#tabpanel-${i}`);
        expect(panel).toBeInTheDocument();
        if (i === 1) {
          expect(panel.getAttribute("hidden")).toBe(null);
        } else {
          expect(panel.getAttribute("hidden")).toBe("");
        }
      }
    });
    it("activeTabIndex is set to 0, therefore first tab button has arial-selected set to true and other buttons to false", () => {
      const { container } = render(<TabsExample />);
      for (let i of arr) {
        const button = container.querySelector(
          `button[aria-controls="tabpanel-${i}"]`,
        );
        expect(button).toBeInTheDocument();
        if (i === 1) {
          expect(button.getAttribute("aria-selected")).toBe("true");
        } else {
          expect(button.getAttribute("aria-selected")).toBe("false");
        }
      }
    });

    it("toggles left and right overflow classes based on scroll position", () => {
      const { getByTestId } = render(<TabsExample />);
      const tabList = getByTestId("test-id");
      const viewport = tabList.parentElement;
      const tabsStatic = TabsStatic.getInstance(tabList);

      let scrollLeft = 0;
      Object.defineProperty(tabList, "clientWidth", {
        configurable: true,
        value: 200,
      });
      Object.defineProperty(tabList, "scrollWidth", {
        configurable: true,
        value: 500,
      });
      Object.defineProperty(tabList, "scrollLeft", {
        configurable: true,
        get: () => scrollLeft,
        set: (value) => {
          scrollLeft = value;
        },
      });

      tabsStatic?.update();

      expect(viewport).not.toHaveClass("has-left-overflow");
      expect(viewport).toHaveClass("has-right-overflow");

      scrollLeft = 300;
      fireEvent.scroll(tabList);

      expect(viewport).toHaveClass("has-left-overflow");
      expect(viewport).not.toHaveClass("has-right-overflow");
    });

    it("centers clicked active tab inside the scroll container", () => {
      const { getByTestId, container } = render(<TabsExample />);
      const tabList = getByTestId("test-id");
      const secondTab = container.querySelector(
        'button[aria-controls="tabpanel-2"]',
      );
      const scrollToSpy = vi.fn();

      Object.defineProperty(tabList, "clientWidth", {
        configurable: true,
        value: 200,
      });
      Object.defineProperty(tabList, "scrollWidth", {
        configurable: true,
        value: 600,
      });
      Object.defineProperty(tabList, "scrollLeft", {
        configurable: true,
        value: 180,
        writable: true,
      });
      Object.defineProperty(tabList, "getBoundingClientRect", {
        configurable: true,
        value: () => ({
          top: 0,
          bottom: 0,
          left: 0,
          right: 200,
          width: 200,
          height: 0,
          x: 0,
          y: 0,
          toJSON: () => ({}),
        }),
      });
      Object.defineProperty(secondTab, "getBoundingClientRect", {
        configurable: true,
        value: () => ({
          top: 0,
          bottom: 0,
          left: 40,
          right: 120,
          width: 80,
          height: 0,
          x: 40,
          y: 0,
          toJSON: () => ({}),
        }),
      });
      tabList.scrollTo = scrollToSpy;

      fireEvent.click(secondTab);

      expect(scrollToSpy).toHaveBeenCalledWith({
        left: 160,
        behavior: "smooth",
      });
    });

    it("does not center on pointer-style focus before click activation", () => {
      const { getByTestId, container } = render(<TabsExample />);
      const tabList = getByTestId("test-id");
      const secondTab = container.querySelector(
        'button[aria-controls="tabpanel-2"]',
      );
      const scrollToSpy = vi.fn();

      secondTab.matches = vi.fn((selector) => selector !== ":focus-visible");
      tabList.scrollTo = scrollToSpy;

      fireEvent.focus(secondTab);

      expect(scrollToSpy).not.toHaveBeenCalled();
    });
  });
  describe("checking fireEvents", () => {
    it("click in disabled tab button doesnt cause change", () => {
      const { container } = render(<TabsExample />);
      const disabledButton = container.querySelector(
        'button[aria-disabled="true"]',
      );
      expect(disabledButton).toBeInTheDocument();
      fireEvent.click(disabledButton);
      for (let i of arr) {
        const button = container.querySelector(
          `button[aria-controls="tabpanel-${i}"]`,
        );
        expect(button).toBeInTheDocument();
        expect(button.getAttribute("aria-selected")).toBe(
          i === 1 ? "true" : "false",
        );
      }
    });
    it("click on active tab doesnt cause change", () => {
      const { container } = render(<TabsExample />);
      const activeBtn = container.querySelector(
        'button[aria-controls="tabpanel-1"]',
      );
      expect(activeBtn).toBeInTheDocument();
      fireEvent.click(activeBtn);
      for (let i of arr) {
        if (i === 1) {
          expect(activeBtn.getAttribute("aria-selected")).toBe("true");
        } else {
          const button = container.querySelector(
            `button[aria-controls="tabpanel-${i}"]`,
          );
          expect(button).toBeInTheDocument();
          expect(button.getAttribute("aria-selected")).toBe("false");
        }
      }
    });
    it("click on inactive tab cause change - clicked button is selected", () => {
      const { container } = render(<TabsExample />);
      const btn = container.querySelector('button[aria-controls="tabpanel-2"]');
      expect(btn).toBeInTheDocument();
      fireEvent.click(btn);
      for (let i of arr) {
        if (i === 2) {
          expect(btn.getAttribute("aria-selected")).toBe("true");
        } else {
          const button = container.querySelector(
            `button[aria-controls="tabpanel-${i}"]`,
          );
          expect(button).toBeInTheDocument();
          expect(button.getAttribute("aria-selected")).toBe("false");
        }
      }
    });
    it("click on inactive tab cause change - text content that belongs to clicked tab doesn't have hidden attribute", () => {
      const { container } = render(<TabsExample />);
      const btn = container.querySelector('button[aria-controls="tabpanel-2"]');
      expect(btn).toBeInTheDocument();
      fireEvent.click(btn);
      for (let i of arr) {
        const panel = container.querySelector(`#tabpanel-${i}`);
        expect(panel).toBeInTheDocument();
        if (i === 2) {
          expect(panel.getAttribute("hidden")).toBe(null);
        } else {
          expect(panel.getAttribute("hidden")).toBe("");
        }
      }
    });
  });
  describe("keyboard navigation", () => {
    let user;
    let tabs;
    let panels;
    beforeEach(() => {
      user = userEvent.setup();
      render(<TabsExample />);
      panels = screen.getAllByRole("tabpanel", { hidden: true });
      tabs = screen.getAllByRole("tab");
    });
    beforeAll(() => {
      window.document.getSelection = vi.fn();
    });
    it('"ArrowLeft" moves focus left and loops from first to last item', async () => {
      expect.assertions(3);

      tabs[0].focus();
      expect(tabs[0]).toHaveFocus();

      await user.keyboard("{ArrowLeft}");
      expect(tabs[3]).toHaveFocus();

      await user.keyboard("{ArrowLeft}");
      expect(tabs[2]).toHaveFocus();
    });
    it('"ArrowRight" moves focus right and loops from last to first', async () => {
      expect.assertions(3);

      tabs[3].focus();
      expect(tabs[3]).toHaveFocus();

      await user.keyboard("{ArrowRight}");
      expect(tabs[0]).toHaveFocus();

      await user.keyboard("{ArrowRight}");
      expect(tabs[1]).toHaveFocus();
    });
    it('"Tab" moves focus to the current active tab panel', async () => {
      expect.assertions(2);

      tabs[0].focus();
      expect(tabs[0]).toHaveFocus();

      await user.keyboard("{Tab}");
      expect(panels[0]).toHaveFocus();
    });
    it('"Enter" key activates a new panel', async () => {
      expect.assertions(5);

      fireEvent.click(tabs[0]);
      tabs[0].focus();
      expect(panels[0]).toBeVisible();

      await user.keyboard("{ArrowRight}{Enter}");
      panels.forEach((_, i) => {
        if (i === 1) {
          expect(panels[i]).toBeVisible();
        } else {
          expect(panels[i]).not.toBeVisible();
        }
      });
    });
    it('"Spacebar" key activates a new panel', async () => {
      expect.assertions(5);

      fireEvent.click(tabs[0]);
      tabs[0].focus();
      expect(panels[0]).toBeVisible();

      await user.keyboard("{ArrowRight} ");
      panels.forEach((_, i) => {
        if (i === 1) {
          expect(panels[i]).toBeVisible();
        } else {
          expect(panels[i]).not.toBeVisible();
        }
      });
    });
    it("disabled tabs cannot be activated", async () => {
      tabs[3].focus();
      expect(tabs[3]).toHaveAttribute("aria-disabled");

      await user.keyboard("{Enter} ");
      expect(panels[0]).toBeVisible();
      expect(panels[3]).not.toBeVisible();
    });
  });
});
