import { render, screen, within } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { describe, expect, it, vi } from "vitest";

import { Button } from "./button";
import { DataTable, type DataTableColumn } from "./data-table";

type ReleaseRow = {
  id: string;
  lane: string;
  note: string;
  owner: string;
  risk: number;
  status: string;
};

const rows: ReleaseRow[] = [
  {
    id: "legal",
    lane: "Legal",
    note: "Footnote needs one more pass before the customer note goes out.",
    owner: "Ava",
    risk: 3,
    status: "Pending"
  },
  {
    id: "support",
    lane: "Support",
    note: "Macro pack is staged and aligned with the migration narrative.",
    owner: "Nia",
    risk: 1,
    status: "Ready"
  },
  {
    id: "engineering",
    lane: "Engineering",
    note: "Canary checks are green and ready for the 10% wave.",
    owner: "Mika",
    risk: 2,
    status: "Watching"
  },
  {
    id: "editorial",
    lane: "Editorial",
    note: "Copy is locked and the public note stays staged.",
    owner: "Jun",
    risk: 4,
    status: "Staged"
  }
];

const columns: DataTableColumn<ReleaseRow>[] = [
  {
    accessor: "lane",
    header: "Lane",
    id: "lane",
    sortable: true
  },
  {
    accessor: "owner",
    header: "Owner",
    id: "owner",
    sortable: true
  },
  {
    accessor: "status",
    header: "Status",
    id: "status"
  },
  {
    accessor: "risk",
    align: "end",
    header: "Risk",
    id: "risk",
    sortable: true
  },
  {
    cell: (row) => row.note,
    header: "Narrative",
    id: "note",
    searchValue: (row) => row.note
  }
];

function getBodyRows() {
  return screen
    .getAllByRole("row")
    .filter((row) => row.closest("tbody") !== null);
}

function getRenderedLanes() {
  return getBodyRows().map((row) => within(row).getAllByRole("cell")[0].textContent);
}

describe("DataTable", () => {
  it("renders semantic table slots, toolbar search, and row actions", () => {
    render(
      <DataTable
        columns={columns}
        renderRowActions={(row) => <Button size="sm">Open {row.lane}</Button>}
        rows={rows.slice(0, 2)}
        toolbarActions={<Button size="sm" variant="secondary">Create lane</Button>}
      />
    );

    expect(screen.getByRole("table").closest('[data-slot="root"]')).toBeInTheDocument();
    expect(screen.getByRole("searchbox", { name: "Search rows" })).toHaveAttribute(
      "data-slot",
      "input"
    );
    expect(
      screen.getByRole("button", { name: "Create lane" }).closest('[data-slot="actions"]')
    ).toBeInTheDocument();
    expect(screen.getByRole("columnheader", { name: /lane/i })).toHaveAttribute(
      "data-slot",
      "header"
    );
    expect(
      screen.getByRole("button", { name: "Open Legal" }).closest('[data-slot="actions"]')
    ).toBeInTheDocument();
  });

  it("sorts rows when a sortable header is activated", async () => {
    const user = userEvent.setup();

    render(<DataTable columns={columns} rows={rows.slice(0, 3)} />);

    expect(getRenderedLanes()).toEqual(["Legal", "Support", "Engineering"]);

    await user.click(within(screen.getByRole("columnheader", { name: /risk/i })).getByRole("button"));

    expect(getRenderedLanes()).toEqual(["Support", "Engineering", "Legal"]);

    await user.click(within(screen.getByRole("columnheader", { name: /risk/i })).getByRole("button"));

    expect(getRenderedLanes()).toEqual(["Legal", "Engineering", "Support"]);
  });

  it("supports row selection and shows a bulk selection surface", async () => {
    const user = userEvent.setup();

    render(
      <DataTable
        columns={columns}
        enableSelection
        rows={rows.slice(0, 2)}
        selectionActions={() => <Button size="sm">Assign owner</Button>}
      />
    );

    await user.click(screen.getByRole("checkbox", { name: "Select row 1" }));

    expect(getBodyRows()[0]).toHaveAttribute("data-selected", "");
    expect(screen.getByText("1 selected")).toBeInTheDocument();
    expect(screen.getByRole("button", { name: "Assign owner" })).toBeInTheDocument();

    await user.click(screen.getByRole("button", { name: "Clear selection" }));

    expect(screen.queryByText("1 selected")).not.toBeInTheDocument();
  });

  it("filters rows from the built-in search and falls back to the empty state", async () => {
    const user = userEvent.setup();

    render(<DataTable columns={columns} rows={rows.slice(0, 3)} />);

    await user.type(screen.getByRole("searchbox", { name: "Search rows" }), "migration");

    expect(
      screen.getByText("Macro pack is staged and aligned with the migration narrative.")
    ).toBeInTheDocument();
    expect(
      screen.queryByText("Canary checks are green and ready for the 10% wave.")
    ).not.toBeInTheDocument();

    await user.clear(screen.getByRole("searchbox", { name: "Search rows" }));
    await user.type(screen.getByRole("searchbox", { name: "Search rows" }), "missing");

    expect(screen.getByText("No matching rows")).toBeInTheDocument();
    expect(
      screen.getByText(
        "Try another search, clear filters, or create a new record to repopulate this view."
      )
    ).toBeInTheDocument();
  });

  it("paginates rows and updates the visible range", async () => {
    const user = userEvent.setup();

    render(
      <DataTable
        columns={columns}
        defaultPageSize={2}
        pageSizeOptions={[2]}
        rows={rows}
      />
    );

    expect(screen.getByText("1-2 of 4")).toBeInTheDocument();
    expect(getRenderedLanes()).toEqual(["Legal", "Support"]);
    expect(screen.getByRole("button", { name: "Previous" })).toBeDisabled();

    await user.click(screen.getByRole("button", { name: "Next" }));

    expect(screen.getByText("3-4 of 4")).toBeInTheDocument();
    expect(getRenderedLanes()).toEqual(["Engineering", "Editorial"]);
    expect(screen.getByRole("button", { name: "Next" })).toBeDisabled();
  });

  it("renders loading status without dropping the table chrome", () => {
    render(<DataTable columns={columns} loading rows={rows} />);

    expect(screen.getByRole("status")).toHaveTextContent("Loading rows");
    expect(screen.getByRole("columnheader", { name: /lane/i })).toBeInTheDocument();
    expect(screen.getByRole("table").closest('[data-slot="root"]')).toHaveAttribute(
      "data-loading",
      ""
    );
  });

  it("supports controlled sorting and selection callbacks", async () => {
    const user = userEvent.setup();
    const onSortingChange = vi.fn();
    const onSelectionChange = vi.fn();

    render(
      <DataTable
        columns={columns}
        enableSelection
        onSelectionChange={onSelectionChange}
        onSortingChange={onSortingChange}
        rows={rows.slice(0, 2)}
      />
    );

    await user.click(within(screen.getByRole("columnheader", { name: /lane/i })).getByRole("button"));
    await user.click(screen.getByRole("checkbox", { name: "Select row 2" }));

    expect(onSortingChange).toHaveBeenCalledWith([{ desc: false, id: "lane" }]);
    expect(onSelectionChange).toHaveBeenCalledWith({ support: true });
  });

  it("toggles hideable columns from the built-in view menu", async () => {
    const user = userEvent.setup();

    render(
      <DataTable
        columns={[
          {
            accessor: "lane",
            header: "Lane",
            hideable: false,
            id: "lane"
          },
          {
            accessor: "owner",
            header: "Owner",
            hideable: true,
            id: "owner"
          }
        ]}
        rows={rows.slice(0, 2)}
      />
    );

    expect(screen.getByRole("columnheader", { name: /owner/i })).toBeInTheDocument();

    await user.click(screen.getByRole("button", { name: "View" }));
    await user.click(screen.getByRole("menuitemcheckbox", { name: "Owner" }));

    expect(screen.queryByRole("columnheader", { name: /owner/i })).not.toBeInTheDocument();
    expect(screen.queryByText("Ava")).not.toBeInTheDocument();
  });

  it("switches density from the built-in view menu", async () => {
    const user = userEvent.setup();

    render(<DataTable columns={columns} rows={rows.slice(0, 2)} />);

    const root = screen.getByRole("table").closest('[data-slot="root"]');
    expect(root).toHaveAttribute("data-density", "comfortable");

    await user.click(screen.getByRole("button", { name: "View" }));
    await user.click(screen.getByRole("menuitemradio", { name: "Compact" }));

    expect(root).toHaveAttribute("data-density", "compact");
    expect(screen.getByRole("columnheader", { name: /lane/i })).toHaveAttribute(
      "data-density",
      "compact"
    );
  });

  it("opens row details inside a sheet", async () => {
    const user = userEvent.setup();

    render(
      <DataTable
        columns={columns}
        renderRowDetails={(row) => <div>{row.note}</div>}
        rowDetailsDescription={(row) => `${row.lane} handoff`}
        rowDetailsTitle={(row) => `${row.lane} detail`}
        rows={rows.slice(0, 2)}
      />
    );

    await user.click(screen.getAllByRole("button", { name: "Open details" })[0]);

    const detailHeading = await screen.findByText("Legal detail");
    const sheet = detailHeading.closest('[data-slot="content"]');

    expect(detailHeading).toBeInTheDocument();
    expect(screen.getByText("Legal handoff")).toBeInTheDocument();
    expect(
      within(sheet as HTMLElement).getByText(
        "Footnote needs one more pass before the customer note goes out."
      )
    ).toBeInTheDocument();
  });
});
