import { describe, it, expect, vi, beforeEach } from "vitest";
import { screen, fireEvent, waitFor, within } from "@testing-library/react";
import "@testing-library/jest-dom/vitest";
import { renderWithProviders } from "../utils/test-utils";
import { CoreValuesSelectScreen } from "../../app/(app)/create/screens/select/CoreValuesSelectScreen";

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

  async function discardPendingValue() {
    fireEvent.click(await screen.findByRole("button", { name: "Discard" }));
  }

  async function editMeaningInOpenDialog(next: string) {
    const dialog = await screen.findByRole("dialog");
    const fields = within(dialog).getAllByRole("textbox");
    fireEvent.change(fields[0], { target: { value: next } });
  }

  it("opens core value detail modal when a preset chip is clicked", async () => {
    renderWithProviders(<CoreValuesSelectScreen />);
    fireEvent.click(screen.getByText("Accessibility"));
    const dialog = await screen.findByRole("dialog");
    expect(
      within(dialog).getByRole("button", { name: "Add Value" }),
    ).toBeInTheDocument();
    fireEvent.click(within(dialog).getByRole("button", { name: "More options" }));
    expect(
      screen.getByRole("menuitem", { name: "Customize" }),
    ).toBeInTheDocument();
    expect(
      screen.getByRole("menuitem", { name: "Duplicate" }),
    ).toBeInTheDocument();
    expect(
      screen.queryByRole("menuitem", { name: "Remove" }),
    ).not.toBeInTheDocument();
  });

  it("closes a pending value without confirm when fields are unchanged", async () => {
    renderWithProviders(<CoreValuesSelectScreen />);
    fireEvent.click(screen.getByText("Accessibility"));
    await screen.findByRole("dialog");
    fireEvent.keyDown(document, { key: "Escape" });
    await waitFor(() => {
      expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
    });
    expect(
      screen.queryByRole("button", { name: "Keep editing" }),
    ).not.toBeInTheDocument();
  });

  it("asks to discard when closing a pending value after an edit", async () => {
    renderWithProviders(<CoreValuesSelectScreen />);
    fireEvent.click(screen.getByText("Accessibility"));
    await editMeaningInOpenDialog("Changed meaning");
    fireEvent.keyDown(document, { key: "Escape" });
    expect(
      await screen.findByRole("button", { name: "Keep editing" }),
    ).toBeInTheDocument();
    await discardPendingValue();
    await waitFor(() => {
      expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
    });
  });

  it("keeps the pending value modal open when Keep editing is chosen", async () => {
    renderWithProviders(<CoreValuesSelectScreen />);
    fireEvent.click(screen.getByText("Accessibility"));
    await editMeaningInOpenDialog("Changed meaning");
    fireEvent.keyDown(document, { key: "Escape" });
    fireEvent.click(await screen.findByRole("button", { name: "Keep editing" }));
    const dialog = await screen.findByRole("dialog");
    expect(
      within(dialog).getByRole("button", { name: "Add Value" }),
    ).toBeInTheDocument();
  });

  it("saves details when Add Value is clicked", async () => {
    renderWithProviders(<CoreValuesSelectScreen />);
    fireEvent.click(screen.getByText("Accessibility"));
    const dialog = await screen.findByRole("dialog");
    fireEvent.click(
      within(dialog).getByRole("button", { name: "Add Value" }),
    );
    await waitFor(() => {
      expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
    });
  });

  it("opens meaning and signals fields editable without Customize", async () => {
    renderWithProviders(<CoreValuesSelectScreen />);
    fireEvent.click(screen.getByText("Accessibility"));
    const dialog = await screen.findByRole("dialog");
    const fields = within(dialog).getAllByRole("textbox");
    expect(fields.length).toBeGreaterThanOrEqual(2);
    for (const field of fields) {
      expect(field).toBeEnabled();
    }
  });

  it("shows Remove only after the value has been added", async () => {
    renderWithProviders(<CoreValuesSelectScreen />);
    fireEvent.click(screen.getByText("Accessibility"));
    const pending = await screen.findByRole("dialog");
    fireEvent.click(within(pending).getByRole("button", { name: "Add Value" }));
    await waitFor(() => {
      expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
    });
    fireEvent.click(screen.getByText("Accessibility"));
    const editing = await screen.findByRole("dialog");
    fireEvent.click(
      within(editing).getByRole("button", { name: "More options" }),
    );
    expect(screen.getByRole("menuitem", { name: "Remove" })).toBeInTheDocument();
    expect(
      screen.getByRole("menuitem", { name: "Customize" }),
    ).toBeInTheDocument();
    expect(
      screen.getByRole("menuitem", { name: "Duplicate" }),
    ).toBeInTheDocument();
  });

  it("Customize walks name and description before policy details", async () => {
    renderWithProviders(<CoreValuesSelectScreen />);
    fireEvent.click(screen.getByText("Accessibility"));
    const dialog = await screen.findByRole("dialog");
    fireEvent.click(
      within(dialog).getByRole("button", { name: "More options" }),
    );
    fireEvent.click(screen.getByRole("menuitem", { name: "Customize" }));

    expect(await screen.findByPlaceholderText("Policy name")).toHaveValue(
      "Accessibility",
    );
    expect(screen.queryByText("Custom policy details")).not.toBeInTheDocument();
    fireEvent.click(screen.getByRole("button", { name: "Next" }));
    fireEvent.click(screen.getByRole("button", { name: "Next" }));
    expect(
      await screen.findByText("Custom policy details"),
    ).toBeInTheDocument();
    expect(
      screen.getByRole("button", {
        name: "What does this value mean to your group?",
      }),
    ).toBeInTheDocument();
  });

  it("Customize shows meaning saved from the value modal", async () => {
    renderWithProviders(<CoreValuesSelectScreen />);
    fireEvent.click(screen.getByText("Accessibility"));
    const pending = await screen.findByRole("dialog");
    const fields = within(pending).getAllByRole("textbox");
    fireEvent.change(fields[0], {
      target: { value: "Edited meaning from value modal" },
    });
    fireEvent.click(within(pending).getByRole("button", { name: "Add Value" }));
    await waitFor(() => {
      expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
    });
    fireEvent.click(screen.getByText("Accessibility"));
    const editing = await screen.findByRole("dialog");
    fireEvent.click(
      within(editing).getByRole("button", { name: "More options" }),
    );
    fireEvent.click(screen.getByRole("menuitem", { name: "Customize" }));
    expect(await screen.findByPlaceholderText("Policy name")).toBeInTheDocument();
    fireEvent.click(screen.getByRole("button", { name: "Next" }));
    fireEvent.click(screen.getByRole("button", { name: "Next" }));
    fireEvent.click(
      await screen.findByRole("button", {
        name: "What does this value mean to your group?",
      }),
    );
    expect(
      screen.getByDisplayValue("Edited meaning from value modal"),
    ).toBeInTheDocument();
  });

  it("closing Customize without Finalize returns to the value modal", async () => {
    renderWithProviders(<CoreValuesSelectScreen />);
    fireEvent.click(screen.getByText("Accessibility"));
    const dialog = await screen.findByRole("dialog");
    fireEvent.click(
      within(dialog).getByRole("button", { name: "More options" }),
    );
    fireEvent.click(screen.getByRole("menuitem", { name: "Customize" }));
    expect(
      await screen.findByPlaceholderText("Policy name"),
    ).toBeInTheDocument();
    fireEvent.keyDown(document, { key: "Escape" });
    await waitFor(() => {
      expect(
        screen.queryByPlaceholderText("Policy name"),
      ).not.toBeInTheDocument();
    });
    const valueDialog = screen.getByRole("dialog");
    expect(
      within(valueDialog).getByRole("button", { name: "Add Value" }),
    ).toBeInTheDocument();
  });

  it("Customize Finalize shows reordered fields in the value modal", async () => {
    renderWithProviders(<CoreValuesSelectScreen />);
    fireEvent.click(screen.getByText("Accessibility"));
    const dialog = await screen.findByRole("dialog");
    fireEvent.click(
      within(dialog).getByRole("button", { name: "More options" }),
    );
    fireEvent.click(screen.getByRole("menuitem", { name: "Customize" }));

    fireEvent.click(await screen.findByRole("button", { name: "Next" }));
    fireEvent.click(screen.getByRole("button", { name: "Next" }));
    expect(
      await screen.findByText("Custom policy details"),
    ).toBeInTheDocument();

    const handles = screen.getAllByRole("button", {
      name: "Drag to reorder this field",
    });
    const rows = screen.getAllByRole("listitem");
    const store: Record<string, string> = {};
    const dataTransfer = {
      effectAllowed: "all",
      dropEffect: "move",
      setData(type: string, value: string) {
        store[type] = value;
      },
      getData(type: string) {
        return store[type] ?? "";
      },
    };
    fireEvent.pointerDown(handles[0]);
    fireEvent.dragStart(rows[0], { dataTransfer });
    fireEvent.drop(rows[1], { dataTransfer });

    fireEvent.click(screen.getByRole("button", { name: "Finalize" }));

    await waitFor(() => {
      expect(
        screen.queryByPlaceholderText("Policy name"),
      ).not.toBeInTheDocument();
    });

    const result = screen.getByRole("dialog");
    const labels = within(result)
      .getAllByRole("textbox")
      .map((el) => {
        const labelledby = el.getAttribute("aria-labelledby");
        return labelledby
          ? (document.getElementById(labelledby)?.textContent ?? "").trim()
          : "";
      });
    expect(labels[0]).toMatch(/Signals of Violation/);
    expect(labels[1]).toMatch(/What does this value mean to your group/);
  });

  // The "Add value" control opens an empty custom-policy wizard. Dismissing
  // without Finalize leaves the chip list unchanged. Finalize writes a
  // selected chip named from the wizard title.
  describe("Add value wizard", () => {
    const CUSTOM_LABEL = "ZZTopBespokeValue";

    /**
     * The label can also appear in the modal header while the modal
     * is open, and as the chip's "Remove" button aria-label. Scope to
     * chip-row buttons by excluding both.
     */
    function countCustomChips(label: string) {
      return screen
        .queryAllByRole("button", { name: label })
        .filter(
          (el) =>
            !el.closest('[role="dialog"]') &&
            (el.getAttribute("aria-label") ?? "") !== `Remove ${label}`,
        ).length;
    }

    async function completeAddValueWizard(label: string) {
      fireEvent.click(screen.getByRole("button", { name: "Add value" }));
      const nameInput = await screen.findByPlaceholderText("Policy name");
      fireEvent.change(nameInput, { target: { value: label } });
      fireEvent.click(screen.getByRole("button", { name: "Next" }));
      const descriptionInput =
        await screen.findByPlaceholderText("Policy description");
      fireEvent.change(descriptionInput, {
        target: { value: "A community-authored value." },
      });
      fireEvent.click(screen.getByRole("button", { name: "Next" }));
      fireEvent.click(await screen.findByRole("button", { name: "Finalize" }));
    }

    it("opens the empty custom-policy wizard from Add value", async () => {
      renderWithProviders(<CoreValuesSelectScreen />);
      fireEvent.click(screen.getByRole("button", { name: "Add value" }));
      expect(
        await screen.findByPlaceholderText("Policy name"),
      ).toHaveValue("");
      expect(screen.queryByPlaceholderText("Type to add")).not.toBeInTheDocument();
    });

    it("leaves no chip when the Add value wizard is dismissed", async () => {
      renderWithProviders(<CoreValuesSelectScreen />);
      fireEvent.click(screen.getByRole("button", { name: "Add value" }));
      await screen.findByPlaceholderText("Policy name");
      fireEvent.keyDown(document, { key: "Escape" });
      await waitFor(() => {
        expect(
          screen.queryByPlaceholderText("Policy name"),
        ).not.toBeInTheDocument();
      });
      expect(countCustomChips(CUSTOM_LABEL)).toBe(0);
      expect(screen.queryByPlaceholderText("Type to add")).not.toBeInTheDocument();
    });

    it("adds a selected chip named from the wizard title after Finalize", async () => {
      renderWithProviders(<CoreValuesSelectScreen />);
      await completeAddValueWizard(CUSTOM_LABEL);
      await waitFor(() => {
        expect(
          screen.queryByPlaceholderText("Policy name"),
        ).not.toBeInTheDocument();
      });
      expect(countCustomChips(CUSTOM_LABEL)).toBe(1);
    });
  });
});
