import { fireEvent, render, screen } from "@testing-library/react";
import { expect, it, vi } from "vitest";
import { Tree } from "./tree.js";
import type { TreeNode } from "./types.js";

const simpleTree: TreeNode = {
  id: "$",
  name: "root",
  children: [
    {
      id: "$.child1",
      name: "Child 1",
      children: [
        {
          id: "$.child1.1",
          name: "Sub child 1.1",
        },
        {
          id: "$.child1.2",
          name: "Sub child 1.2",
        },
        {
          id: "$.child1.3",
          name: "Sub child 1.3",
        },
      ],
    },
    {
      id: "$.child2",
      name: "Child 2",
      children: [
        {
          id: "$.child2.1",
          name: "Sub child 2.1",
        },
      ],
    },
  ],
};

it("tree is collapsed by default", async () => {
  render(<Tree tree={simpleTree} />);
  const nodes = await screen.findAllByRole("treeitem");
  expect(nodes).toHaveLength(2);
  expect(nodes[0]).toHaveTextContent("Child 1");
  expect(nodes[0]).toHaveAttribute("aria-expanded", "false");
  expect(nodes[1]).toHaveTextContent("Child 2");
  expect(nodes[1]).toHaveAttribute("aria-expanded", "false");
});

it("expand nodes by clicking on it", async () => {
  render(<Tree tree={simpleTree} />);
  const child1 = await screen.findByText("Child 1");
  fireEvent.click(child1);
  const nodes = await screen.findAllByRole("treeitem");
  expect(nodes).toHaveLength(5);
  expect(nodes[0]).toHaveTextContent("Child 1");
  expect(nodes[0]).toHaveAttribute("aria-expanded", "true");

  expect(nodes[1]).toHaveTextContent("Sub child 1.1");
  expect(nodes[2]).toHaveTextContent("Sub child 1.2");
  expect(nodes[3]).toHaveTextContent("Sub child 1.3");

  expect(nodes[4]).toHaveTextContent("Child 2");
  expect(nodes[4]).toHaveAttribute("aria-expanded", "false");
});

it("expand nodes by pressing enter on it", async () => {
  render(<Tree tree={simpleTree} />);
  const treeNode = await screen.findByRole("tree");
  fireEvent.focus(treeNode);
  fireEvent.keyDown(treeNode, { key: "Enter", code: "Enter" });
  expect(await screen.findAllByRole("treeitem")).toHaveLength(5);
});

it("expand nodes by pressing space on it", async () => {
  render(<Tree tree={simpleTree} />);
  const treeNode = await screen.findByRole("tree");
  fireEvent.focus(treeNode);
  fireEvent.keyDown(treeNode, { key: "Space", code: "Space" });
  expect(await screen.findAllByRole("treeitem")).toHaveLength(5);
});

it("use left right arrow to expand and collapse", async () => {
  render(<Tree tree={simpleTree} />);
  const treeNode = await screen.findByRole("tree");
  fireEvent.focus(treeNode);
  fireEvent.keyDown(treeNode, { key: "ArrowRight", code: "ArrowRight" });
  expect(await screen.findAllByRole("treeitem")).toHaveLength(5);
  fireEvent.keyDown(treeNode, { key: "ArrowLeft", code: "ArrowLeft" });
  expect(await screen.findAllByRole("treeitem")).toHaveLength(2);
});

it("use up down arrow to navigate", async () => {
  render(<Tree tree={simpleTree} />);
  const treeNode = await screen.findByRole("tree");
  fireEvent.focus(treeNode);
  let nodes = await screen.findAllByRole("treeitem");

  expect(treeNode).toHaveAttribute("aria-activedescendant", nodes[0].id);
  fireEvent.keyDown(treeNode, { key: "ArrowDown", code: "ArrowDown" });
  expect(treeNode).toHaveAttribute("aria-activedescendant", nodes[1].id);
  fireEvent.keyDown(treeNode, { key: "Space", code: "Space" });
  nodes = await screen.findAllByRole("treeitem");
  fireEvent.keyDown(treeNode, { key: "ArrowDown", code: "ArrowDown" });
  expect(treeNode).toHaveAttribute("aria-activedescendant", nodes[2].id);
  fireEvent.keyDown(treeNode, { key: "ArrowUp", code: "ArrowUp" });
  expect(treeNode).toHaveAttribute("aria-activedescendant", nodes[1].id);
  fireEvent.keyDown(treeNode, { key: "ArrowDown", code: "ArrowDown" });
  fireEvent.keyDown(treeNode, { key: "ArrowDown", code: "ArrowDown" });
  expect(treeNode).toHaveAttribute("aria-activedescendant", nodes[0].id);
});

it("collapse expanded directory by clicking in selectionMode=single", async () => {
  render(<Tree tree={simpleTree} selectionMode="single" />);
  const child1 = await screen.findByText("Child 1");

  // Click to expand
  fireEvent.click(child1);
  expect(await screen.findAllByRole("treeitem")).toHaveLength(5);

  // Click again to collapse
  fireEvent.click(child1);
  const nodes = await screen.findAllByRole("treeitem");
  expect(nodes).toHaveLength(2);
  expect(nodes[0]).toHaveAttribute("aria-expanded", "false");
});

it("collapse expanded directory by pressing space in selectionMode=single", async () => {
  render(<Tree tree={simpleTree} selectionMode="single" />);
  const treeNode = await screen.findByRole("tree");
  fireEvent.focus(treeNode);

  // Space to expand (focus defaults to first item: Child 1)
  fireEvent.keyDown(treeNode, { key: "Space", code: "Space" });
  expect(await screen.findAllByRole("treeitem")).toHaveLength(5);

  // Space again to collapse (focus stays on Child 1)
  fireEvent.keyDown(treeNode, { key: "Space", code: "Space" });
  expect(await screen.findAllByRole("treeitem")).toHaveLength(2);
});

it("collapse expanded directory by pressing enter in selectionMode=single", async () => {
  render(<Tree tree={simpleTree} selectionMode="single" />);
  const treeNode = await screen.findByRole("tree");
  fireEvent.focus(treeNode);

  // Enter to expand
  fireEvent.keyDown(treeNode, { key: "Enter", code: "Enter" });
  expect(await screen.findAllByRole("treeitem")).toHaveLength(5);

  // Enter again to collapse
  fireEvent.keyDown(treeNode, { key: "Enter", code: "Enter" });
  expect(await screen.findAllByRole("treeitem")).toHaveLength(2);
});

it("expand-collapse round trip by clicking in selectionMode=single", async () => {
  render(<Tree tree={simpleTree} selectionMode="single" />);
  const child1 = await screen.findByText("Child 1");

  // Expand
  fireEvent.click(child1);
  expect(await screen.findAllByRole("treeitem")).toHaveLength(5);

  // Collapse
  fireEvent.click(child1);
  expect(await screen.findAllByRole("treeitem")).toHaveLength(2);

  // Re-expand
  fireEvent.click(child1);
  expect(await screen.findAllByRole("treeitem")).toHaveLength(5);
});

it("clicking a file in selectionMode=single still selects it", async () => {
  const onSelect = vi.fn();
  render(<Tree tree={simpleTree} selectionMode="single" onSelect={onSelect} />);

  // Expand Child 1 first
  const child1 = await screen.findByText("Child 1");
  fireEvent.click(child1);

  // Click a leaf node
  const subChild = await screen.findByText("Sub child 1.2");
  fireEvent.click(subChild);
  expect(onSelect).toHaveBeenCalledWith("$.child1.2");
});

it("clicking a directory in selectionMode=single fires onSelect", async () => {
  const onSelect = vi.fn();
  render(<Tree tree={simpleTree} selectionMode="single" onSelect={onSelect} />);

  const child1 = await screen.findByText("Child 1");
  fireEvent.click(child1);
  expect(onSelect).toHaveBeenCalledWith("$.child1");
});
