import {
  renderWithProviders as render,
  screen,
  cleanup,
  within,
  waitFor,
} from "../utils/test-utils";
import userEvent from "@testing-library/user-event";
import { describe, test, expect, afterEach } from "vitest";
import { useLayoutEffect } from "react";
import { DecisionApproachesScreen } from "../../app/(app)/create/screens/right-rail/DecisionApproachesScreen";
import { useCreateFlow } from "../../app/(app)/create/context/CreateFlowContext";

const CUSTOM_APPROACH_ID = "550e8400-e29b-41d4-a716-446655440000";

function DecisionApproachesScreenWithState({ initial }) {
  const { replaceState } = useCreateFlow();
  useLayoutEffect(() => {
    replaceState(initial);
  }, [replaceState, initial]);
  return <DecisionApproachesScreen />;
}

afterEach(() => {
  cleanup();
});

describe("Create flow decision-approaches page", () => {
  test("renders without error", () => {
    render(<DecisionApproachesScreen />);

    expect(
      screen.getByRole("heading", {
        name: "How should this community make difficult decisions?",
      }),
    ).toBeInTheDocument();
  });

  test("renders sidebar description with add link", () => {
    render(<DecisionApproachesScreen />);

    const addControl = screen.getByRole("button", {
      name: /^Add$/i,
    });
    expect(addControl).toBeInTheDocument();

    const description = addControl.parentElement;
    expect(description).not.toBeNull();
    expect(description?.textContent).toMatch(/Select as many as you need/);
    expect(description?.textContent).toMatch(/new decision making approaches/);
  });

  test("with a finalized custom approach, sidebar still shows add (not Remove policy)", () => {
    render(
      <DecisionApproachesScreenWithState
        initial={{
          selectedDecisionApproachIds: [CUSTOM_APPROACH_ID],
          customMethodCardMetaById: {
            [CUSTOM_APPROACH_ID]: {
              label: "My approach",
              supportText: "Desc",
            },
          },
        }}
      />,
    );

    expect(
      screen.queryByRole("button", { name: "Remove policy" }),
    ).not.toBeInTheDocument();
    const addControl = screen.getByRole("button", { name: /^Add$/i });
    expect(addControl).toBeInTheDocument();
    const description = addControl.parentElement;
    expect(description?.textContent).toMatch(/Select as many as you need/);
    expect(description?.textContent).toMatch(/new decision making approaches/);
  });

  test("renders message box with title and checkboxes", () => {
    render(<DecisionApproachesScreen />);

    const region = screen.getByRole("region", {
      name: "Consider defining approaches to steward key resources:",
    });
    expect(region).toBeInTheDocument();

    expect(
      within(region).getByRole("checkbox", {
        name: "Amend your CommunityRule",
      }),
    ).toBeInTheDocument();
    expect(
      within(region).getByRole("checkbox", { name: "Steward finances" }),
    ).toBeInTheDocument();
    expect(
      within(region).getByRole("checkbox", { name: "Project level decisions" }),
    ).toBeInTheDocument();
    expect(
      within(region).getByRole("checkbox", {
        name: "Discipline and member termination",
      }),
    ).toBeInTheDocument();
  });

  test("renders card stack with See all decision approaches toggle", () => {
    render(<DecisionApproachesScreen />);

    expect(
      screen.getByRole("button", { name: "See all decision approaches" }),
    ).toBeInTheDocument();
  });

  test("renders recommended approach cards", () => {
    render(<DecisionApproachesScreen />);

    expect(
      screen.getByRole("button", {
        name: /Lazy Consensus: A decision is assumed approved/,
      }),
    ).toBeInTheDocument();
    expect(
      screen.getByRole("button", {
        name: /Do-ocracy: Decisions are made by those who take initiative/,
      }),
    ).toBeInTheDocument();
    expect(
      screen.getByRole("button", {
        name: /Consensus Decision-Making: All members must agree/,
      }),
    ).toBeInTheDocument();
  });

  test("toggle expands and shows Show less", async () => {
    const user = userEvent.setup();
    render(<DecisionApproachesScreen />);

    const toggle = screen.getByRole("button", {
      name: "See all decision approaches",
    });
    await user.click(toggle);

    expect(
      screen.getByRole("button", { name: "Show less" }),
    ).toBeInTheDocument();
    expect(screen.getAllByRole("button", { name: /^add$/i })).toHaveLength(2);
  });

  test("expanded view reveals additional non-recommended approaches", async () => {
    const user = userEvent.setup();
    render(<DecisionApproachesScreen />);

    expect(
      screen.queryByRole("button", { name: /^Sociocracy:/ }),
    ).not.toBeInTheDocument();

    const toggle = screen.getByRole("button", {
      name: "See all decision approaches",
    });
    await user.click(toggle);

    expect(
      screen.getByRole("button", { name: /^Sociocracy:/ }),
    ).toBeInTheDocument();
  });

  test("selecting an approach in the expanded list does not move it to the top", async () => {
    const user = userEvent.setup();
    render(<DecisionApproachesScreen />);

    await user.click(
      screen.getByRole("button", { name: "See all decision approaches" }),
    );

    const cardLabels = () =>
      screen
        .getAllByRole("button")
        .map((el) => el.getAttribute("aria-label") || "")
        .filter((label) => label.includes(": "));

    const labelsBefore = cardLabels();
    expect(labelsBefore[0]).toMatch(/^Lazy Consensus:/);
    expect(labelsBefore.some((label) => label.startsWith("Sociocracy:"))).toBe(
      true,
    );

    await user.click(screen.getByRole("button", { name: /^Sociocracy:/ }));
    await user.click(
      within(await screen.findByRole("dialog")).getByRole("button", {
        name: "Add Approach",
      }),
    );
    await waitFor(() => {
      expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
    });

    expect(
      screen.getByRole("button", { name: "Show less" }),
    ).toBeInTheDocument();
    expect(cardLabels()).toEqual(labelsBefore);
    expect(
      screen.getByRole("button", { name: /^Sociocracy:/ }),
    ).toHaveTextContent("SELECTED");
  });

  test("opens approach fields editable without Customize", async () => {
    const user = userEvent.setup();
    render(<DecisionApproachesScreen />);

    await user.click(
      screen.getByRole("button", {
        name: /Lazy Consensus: A decision is assumed approved/,
      }),
    );
    const dialog = await screen.findByRole("dialog");
    const textboxes = within(dialog).getAllByRole("textbox");
    expect(textboxes.length).toBeGreaterThan(0);
    for (const field of textboxes) {
      expect(field).not.toBeDisabled();
    }
    await user.click(
      within(dialog).getByRole("button", { name: "More options" }),
    );
    expect(
      screen.getByRole("menuitem", { name: "Customize" }),
    ).toBeInTheDocument();
  });

  test("clicking a card opens the create modal and confirming selects it", async () => {
    const user = userEvent.setup();
    render(<DecisionApproachesScreen />);

    const card = screen.getByRole("button", {
      name: /Lazy Consensus: A decision is assumed approved/,
    });
    await user.click(card);

    const dialog = await screen.findByRole("dialog");
    expect(dialog).toBeInTheDocument();

    const confirmButton = within(dialog).getByRole("button", {
      name: "Add Approach",
    });
    await user.click(confirmButton);

    expect(screen.getByText("SELECTED")).toBeInTheDocument();
  });

  test("re-opening a selected approach shows Save; Remove is in the kebab", async () => {
    const user = userEvent.setup();
    render(<DecisionApproachesScreen />);

    const card = screen.getByRole("button", {
      name: /Lazy Consensus: A decision is assumed approved/,
    });
    await user.click(card);
    const dialog = await screen.findByRole("dialog");
    await user.click(
      within(dialog).getByRole("button", {
        name: "Add Approach",
      }),
    );

    await user.click(card);
    const dialogAgain = screen.getByRole("dialog");
    expect(
      within(dialogAgain).queryByRole("button", { name: "Remove" }),
    ).not.toBeInTheDocument();
    expect(
      within(dialogAgain).queryByRole("button", { name: "Add Approach" }),
    ).not.toBeInTheDocument();
    expect(
      within(dialogAgain).getByRole("button", { name: "Save" }),
    ).toBeInTheDocument();

    await user.click(within(dialogAgain).getByRole("button", { name: "More options" }));
    expect(screen.getByRole("menuitem", { name: "Remove" })).toBeInTheDocument();
  });

  test("Save on a selected approach persists the edit and closes", async () => {
    const user = userEvent.setup();
    render(<DecisionApproachesScreen />);

    const card = screen.getByRole("button", {
      name: /Lazy Consensus: A decision is assumed approved/,
    });
    await user.click(card);
    const dialog = await screen.findByRole("dialog");
    await user.click(
      within(dialog).getByRole("button", { name: "Add Approach" }),
    );

    await user.click(card);
    const dialogAgain = await screen.findByRole("dialog");
    const principleField = within(dialogAgain).getByRole("textbox", {
      name: /core principle/i,
    });
    await user.clear(principleField);
    await user.type(principleField, "Edited principle");
    await user.click(within(dialogAgain).getByRole("button", { name: "Save" }));

    await waitFor(() => {
      expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
    });
  });

  test("Remove from the kebab deselects the approach", async () => {
    const user = userEvent.setup();
    render(<DecisionApproachesScreen />);

    const card = screen.getByRole("button", {
      name: /Lazy Consensus: A decision is assumed approved/,
    });
    await user.click(card);
    await user.click(
      within(await screen.findByRole("dialog")).getByRole("button", {
        name: "Add Approach",
      }),
    );

    expect(card).toHaveTextContent("SELECTED");

    await user.click(card);
    await user.click(
      within(screen.getByRole("dialog")).getByRole("button", { name: "More options" }),
    );
    await user.click(screen.getByRole("menuitem", { name: "Remove" }));

    expect(card).not.toHaveTextContent("SELECTED");
  });

  test("when editing a published rule, method modal kebab has no Duplicate", async () => {
    const user = userEvent.setup();
    render(
      <DecisionApproachesScreenWithState
        initial={{
          editingPublishedRuleId: "published-rule-1",
          selectedDecisionApproachIds: ["lazy-consensus"],
        }}
      />,
    );

    const card = screen.getByRole("button", {
      name: /Lazy Consensus: A decision is assumed approved/,
    });
    await user.click(card);
    const dialog = await screen.findByRole("dialog");
    await user.click(
      within(dialog).getByRole("button", { name: "More options" }),
    );
    expect(
      screen.queryByRole("menuitem", { name: "Duplicate" }),
    ).not.toBeInTheDocument();
  });

  test("message box checkboxes are interactive", async () => {
    const user = userEvent.setup();
    render(<DecisionApproachesScreen />);

    const amendCheckbox = screen.getByRole("checkbox", {
      name: "Amend your CommunityRule",
    });
    expect(amendCheckbox).not.toBeChecked();
    await user.click(amendCheckbox);
    expect(amendCheckbox).toBeChecked();
  });

  test("each approach modal includes the key-resource applicable-scope chips", async () => {
    const user = userEvent.setup();
    render(<DecisionApproachesScreen />);

    await user.click(
      screen.getByRole("button", {
        name: /Lazy Consensus: A decision is assumed approved/,
      }),
    );
    const dialog = await screen.findByRole("dialog");
    expect(
      within(dialog).getByRole("button", {
        name: "Select Amend your CommunityRule",
      }),
    ).toBeInTheDocument();
    expect(
      within(dialog).getByRole("button", { name: "Select Steward finances" }),
    ).toBeInTheDocument();
    expect(
      within(dialog).getByRole("button", {
        name: "Select Project level decisions",
      }),
    ).toBeInTheDocument();
    expect(
      within(dialog).getByRole("button", {
        name: "Select Discipline and member termination",
      }),
    ).toBeInTheDocument();
  });

  test("checking a key-resource box does not highlight that chip on an unselected approach", async () => {
    const user = userEvent.setup();
    render(<DecisionApproachesScreen />);

    await user.click(
      screen.getByRole("checkbox", { name: "Steward finances" }),
    );

    await user.click(
      screen.getByRole("button", {
        name: /Lazy Consensus: A decision is assumed approved/,
      }),
    );
    const lazyDialog = await screen.findByRole("dialog");
    expect(
      within(lazyDialog).getByRole("button", {
        name: "Select Steward finances",
      }),
    ).toBeInTheDocument();
  });

  test("applicable-scope chips stay on the approach they were chosen for", async () => {
    const user = userEvent.setup();
    render(<DecisionApproachesScreen />);

    await user.click(
      screen.getByRole("button", {
        name: /Lazy Consensus: A decision is assumed approved/,
      }),
    );
    const lazyDialog = await screen.findByRole("dialog");
    await user.click(
      within(lazyDialog).getByRole("button", {
        name: "Select Steward finances",
      }),
    );
    expect(
      screen.getByRole("checkbox", { name: "Steward finances" }),
    ).toBeChecked();
    await user.click(
      within(lazyDialog).getByRole("button", { name: "Add Approach" }),
    );
    await waitFor(() => {
      expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
    });

    await user.click(
      screen.getByRole("button", {
        name: /Do-ocracy: Decisions are made by those who take initiative/,
      }),
    );
    const doocracyDialog = await screen.findByRole("dialog");
    expect(
      within(doocracyDialog).getByRole("button", {
        name: "Select Steward finances",
      }),
    ).toBeInTheDocument();
    expect(
      within(doocracyDialog).queryByRole("button", {
        name: "Deselect Steward finances",
      }),
    ).not.toBeInTheDocument();
    expect(
      screen.getByRole("checkbox", { name: "Steward finances" }),
    ).not.toBeChecked();
    await user.click(
      within(doocracyDialog).getByRole("button", { name: "Close dialog" }),
    );
    await waitFor(() => {
      expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
    });

    expect(
      screen.getByRole("checkbox", { name: "Steward finances" }),
    ).toBeChecked();

    await user.click(
      screen.getByRole("button", {
        name: /Lazy Consensus: A decision is assumed approved/,
      }),
    );
    const lazyAgain = await screen.findByRole("dialog");
    expect(
      within(lazyAgain).getByRole("button", {
        name: "Deselect Steward finances",
      }),
    ).toBeInTheDocument();
    expect(
      screen.getByRole("checkbox", { name: "Steward finances" }),
    ).toBeChecked();
  });

  test("checking a key-resource box with a modal open selects that chip on the open approach only", async () => {
    const user = userEvent.setup();
    render(<DecisionApproachesScreen />);

    await user.click(
      screen.getByRole("button", {
        name: /Lazy Consensus: A decision is assumed approved/,
      }),
    );
    const lazyDialog = await screen.findByRole("dialog");
    await user.click(
      within(lazyDialog).getByRole("button", { name: "Add Approach" }),
    );
    await waitFor(() => {
      expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
    });

    await user.click(
      screen.getByRole("button", {
        name: /Do-ocracy: Decisions are made by those who take initiative/,
      }),
    );
    const doocracyDialog = await screen.findByRole("dialog");
    await user.click(
      screen.getByRole("checkbox", { name: "Steward finances" }),
    );
    expect(
      within(doocracyDialog).getByRole("button", {
        name: "Deselect Steward finances",
      }),
    ).toBeInTheDocument();
    await user.click(
      within(doocracyDialog).getByRole("button", { name: "Close dialog" }),
    );
    await user.click(await screen.findByRole("button", { name: "Discard" }));
    await waitFor(() => {
      expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
    });

    await user.click(
      screen.getByRole("button", {
        name: /Lazy Consensus: A decision is assumed approved/,
      }),
    );
    const lazyAgain = await screen.findByRole("dialog");
    expect(
      within(lazyAgain).getByRole("button", {
        name: "Select Steward finances",
      }),
    ).toBeInTheDocument();
  });

  test("highlighting a key-resource chip on a second approach does not rewrite the first", async () => {
    const user = userEvent.setup();
    render(<DecisionApproachesScreen />);

    await user.click(
      screen.getByRole("button", {
        name: /Lazy Consensus: A decision is assumed approved/,
      }),
    );
    const lazyDialog = await screen.findByRole("dialog");
    await user.click(
      within(lazyDialog).getByRole("button", {
        name: "Select Steward finances",
      }),
    );
    expect(
      screen.getByRole("checkbox", { name: "Steward finances" }),
    ).toBeChecked();
    await user.click(
      within(lazyDialog).getByRole("button", { name: "Add Approach" }),
    );
    await waitFor(() => {
      expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
    });

    await user.click(
      screen.getByRole("button", {
        name: /Do-ocracy: Decisions are made by those who take initiative/,
      }),
    );
    const doocracyDialog = await screen.findByRole("dialog");
    await user.click(
      within(doocracyDialog).getByRole("button", {
        name: "Select Project level decisions",
      }),
    );
    expect(
      screen.getByRole("checkbox", { name: "Project level decisions" }),
    ).toBeChecked();
    expect(
      screen.getByRole("checkbox", { name: "Steward finances" }),
    ).not.toBeChecked();
    expect(
      within(doocracyDialog).getByRole("button", {
        name: "Select Steward finances",
      }),
    ).toBeInTheDocument();
    await user.click(
      within(doocracyDialog).getByRole("button", { name: "Add Approach" }),
    );
    await waitFor(() => {
      expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
    });

    expect(
      screen.getByRole("checkbox", { name: "Steward finances" }),
    ).toBeChecked();
    expect(
      screen.getByRole("checkbox", { name: "Project level decisions" }),
    ).toBeChecked();

    await user.click(
      screen.getByRole("button", {
        name: /Lazy Consensus: A decision is assumed approved/,
      }),
    );
    const lazyAgain = await screen.findByRole("dialog");
    expect(
      within(lazyAgain).getByRole("button", {
        name: "Deselect Steward finances",
      }),
    ).toBeInTheDocument();
    expect(
      within(lazyAgain).getByRole("button", {
        name: "Select Project level decisions",
      }),
    ).toBeInTheDocument();
    await user.click(
      within(lazyAgain).getByRole("button", { name: "Close dialog" }),
    );
    await waitFor(() => {
      expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
    });

    await user.click(
      screen.getByRole("button", {
        name: /Do-ocracy: Decisions are made by those who take initiative/,
      }),
    );
    const doocracyAgain = await screen.findByRole("dialog");
    expect(
      within(doocracyAgain).getByRole("button", {
        name: "Deselect Project level decisions",
      }),
    ).toBeInTheDocument();
    expect(
      within(doocracyAgain).getByRole("button", {
        name: "Select Steward finances",
      }),
    ).toBeInTheDocument();
  });
});
