import { describe, expect, it, vi, beforeEach } from "vitest";
import { render, screen, within } from "~/test-utils.js";
import userEvent from "@testing-library/user-event";
import { ColumnTogglePanel } from "./ColumnTogglePanel.js";
import type { ColumnDef } from "@capakraken/shared";

// Mock useAnchoredOverlay – in jsdom there is no real layout engine, so we
// just return stable refs and a no-op handleOpenChange.
vi.mock("~/hooks/useAnchoredOverlay.js", () => ({
  useAnchoredOverlay: ({ onClose }: { onClose: () => void }) => ({
    triggerRef: { current: null },
    panelRef: { current: null },
    position: { top: 0, left: 0 },
    handleOpenChange: (open: boolean) => {
      if (!open) onClose();
    },
  }),
}));

// -----------------------------------------------------------------------
// Fixtures
// -----------------------------------------------------------------------

const builtinColumns: ColumnDef[] = [
  { key: "name", label: "Name", defaultVisible: true, hideable: false },
  { key: "role", label: "Role", defaultVisible: true, hideable: true },
  { key: "dept", label: "Dept", defaultVisible: false, hideable: true },
];

const customColumns: ColumnDef[] = [
  { key: "cf_level", label: "Level", defaultVisible: true, hideable: true, isCustom: true },
];

const allColumns: ColumnDef[] = [...builtinColumns, ...customColumns];

const defaultKeys = ["name", "role"];
const visibleKeys = ["name", "role", "cf_level"];

function renderPanel(overrides: Partial<React.ComponentProps<typeof ColumnTogglePanel>> = {}) {
  const onSetVisible = vi.fn();
  const utils = render(
    <ColumnTogglePanel
      allColumns={allColumns}
      visibleKeys={visibleKeys}
      onSetVisible={onSetVisible}
      defaultKeys={defaultKeys}
      {...overrides}
    />,
  );
  return { ...utils, onSetVisible };
}

// -----------------------------------------------------------------------
// Tests
// -----------------------------------------------------------------------

describe("ColumnTogglePanel", () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  describe("trigger button", () => {
    it("renders the column-visibility toggle button", () => {
      renderPanel();
      expect(screen.getByRole("button", { name: /column visibility/i })).toBeInTheDocument();
    });

    it("panel is not visible before the button is clicked", () => {
      renderPanel();
      expect(screen.queryByText("Columns")).not.toBeInTheDocument();
    });

    it("opens the panel when the trigger button is clicked", async () => {
      const user = userEvent.setup();
      renderPanel();
      await user.click(screen.getByRole("button", { name: /column visibility/i }));
      expect(screen.getByText("Columns")).toBeInTheDocument();
    });

    it("closes the panel when the trigger button is clicked again", async () => {
      const user = userEvent.setup();
      renderPanel();
      const trigger = screen.getByRole("button", { name: /column visibility/i });
      await user.click(trigger);
      await user.click(trigger);
      expect(screen.queryByText("Columns")).not.toBeInTheDocument();
    });
  });

  describe("panel content – builtin columns", () => {
    async function openPanel() {
      const user = userEvent.setup();
      const utils = renderPanel();
      await user.click(screen.getByRole("button", { name: /column visibility/i }));
      return { user, ...utils };
    }

    it("renders all builtin column labels inside the panel", async () => {
      await openPanel();
      expect(screen.getByText("Name")).toBeInTheDocument();
      expect(screen.getByText("Role")).toBeInTheDocument();
      expect(screen.getByText("Dept")).toBeInTheDocument();
    });

    it("shows visible columns as checked", async () => {
      await openPanel();
      // "name" and "role" are in visibleKeys
      const roleCheckbox = screen.getByRole("checkbox", { name: /role/i });
      expect(roleCheckbox).toBeChecked();
    });

    it("shows non-visible hideable columns as unchecked", async () => {
      await openPanel();
      const deptCheckbox = screen.getByRole("checkbox", { name: /dept/i });
      expect(deptCheckbox).not.toBeChecked();
    });

    it("disables the checkbox for non-hideable columns", async () => {
      await openPanel();
      const nameCheckbox = screen.getByRole("checkbox", { name: /name/i });
      expect(nameCheckbox).toBeDisabled();
    });

    it("enables the checkbox for hideable columns", async () => {
      await openPanel();
      const roleCheckbox = screen.getByRole("checkbox", { name: /role/i });
      expect(roleCheckbox).not.toBeDisabled();
    });
  });

  describe("panel content – custom columns", () => {
    async function openPanel() {
      const user = userEvent.setup();
      const utils = renderPanel();
      await user.click(screen.getByRole("button", { name: /column visibility/i }));
      return { user, ...utils };
    }

    it("renders the 'Custom Fields' section header", async () => {
      await openPanel();
      expect(screen.getByText("Custom Fields")).toBeInTheDocument();
    });

    it("renders custom column labels", async () => {
      await openPanel();
      expect(screen.getByText("Level")).toBeInTheDocument();
    });

    it("does not render 'Custom Fields' header when there are no custom columns", async () => {
      const user = userEvent.setup();
      renderPanel({ allColumns: builtinColumns });
      await user.click(screen.getByRole("button", { name: /column visibility/i }));
      expect(screen.queryByText("Custom Fields")).not.toBeInTheDocument();
    });
  });

  describe("toggling visibility", () => {
    it("calls onSetVisible without a key when a visible hideable column is unchecked", async () => {
      const user = userEvent.setup();
      const { onSetVisible } = renderPanel();
      await user.click(screen.getByRole("button", { name: /column visibility/i }));
      await user.click(screen.getByRole("checkbox", { name: /role/i }));
      expect(onSetVisible).toHaveBeenCalledOnce();
      const [keys] = onSetVisible.mock.calls[0] as [string[]];
      expect(keys).not.toContain("role");
    });

    it("calls onSetVisible with the key added when a hidden hideable column is checked", async () => {
      const user = userEvent.setup();
      const { onSetVisible } = renderPanel();
      await user.click(screen.getByRole("button", { name: /column visibility/i }));
      await user.click(screen.getByRole("checkbox", { name: /dept/i }));
      expect(onSetVisible).toHaveBeenCalledOnce();
      const [keys] = onSetVisible.mock.calls[0] as [string[]];
      expect(keys).toContain("dept");
    });

    it("does NOT call onSetVisible when a non-hideable column checkbox is clicked", async () => {
      const user = userEvent.setup();
      const { onSetVisible } = renderPanel();
      await user.click(screen.getByRole("button", { name: /column visibility/i }));
      // The non-hideable checkbox is disabled – userEvent won't fire onChange on disabled inputs.
      const nameCheckbox = screen.getByRole("checkbox", { name: /name/i });
      await user.click(nameCheckbox);
      expect(onSetVisible).not.toHaveBeenCalled();
    });
  });

  describe("reset button", () => {
    it("renders the Reset button inside the panel", async () => {
      const user = userEvent.setup();
      renderPanel();
      await user.click(screen.getByRole("button", { name: /column visibility/i }));
      expect(screen.getByRole("button", { name: /reset/i })).toBeInTheDocument();
    });

    it("calls onSetVisible with defaultKeys when Reset is clicked", async () => {
      const user = userEvent.setup();
      const { onSetVisible } = renderPanel();
      await user.click(screen.getByRole("button", { name: /column visibility/i }));
      await user.click(screen.getByRole("button", { name: /reset/i }));
      expect(onSetVisible).toHaveBeenCalledWith(defaultKeys);
    });
  });

  describe("drag-and-drop reordering", () => {
    it("calls onSetVisible with reordered keys after a drop onto a different row", async () => {
      const user = userEvent.setup();
      const { onSetVisible } = renderPanel({
        visibleKeys: ["name", "role", "dept"],
        allColumns: [
          { key: "name", label: "Name", defaultVisible: true, hideable: false },
          { key: "role", label: "Role", defaultVisible: true, hideable: true },
          { key: "dept", label: "Dept", defaultVisible: true, hideable: true },
        ],
      });
      await user.click(screen.getByRole("button", { name: /column visibility/i }));

      // Simulate dragStart on "role" row and drop onto "dept" row using fireEvent
      // (userEvent doesn't support drag-and-drop; we use direct fireEvent).
      const { fireEvent } = await import("@testing-library/react");
      const rows = screen.getAllByRole("checkbox");
      // role is index 1, dept is index 2
      const roleRow = rows[1]!.closest("div[draggable]") as HTMLElement;
      const deptRow = rows[2]!.closest("div[draggable]") as HTMLElement;
      expect(roleRow).not.toBeNull();
      expect(deptRow).not.toBeNull();

      fireEvent.dragStart(roleRow);
      fireEvent.dragOver(deptRow);
      fireEvent.drop(deptRow);

      expect(onSetVisible).toHaveBeenCalledOnce();
      const [keys] = onSetVisible.mock.calls[0] as [string[]];
      // After moving "role" onto "dept": ["name", "dept", "role"]
      expect(keys.indexOf("dept")).toBeLessThan(keys.indexOf("role"));
    });
  });
});
