import { useEffect, useLayoutEffect } from "react";
import { describe, it, expect } from "vitest";
import { fireEvent, within } from "@testing-library/react";
import {
  renderWithProviders as render,
  screen,
  waitFor,
  dispatchBeforeUnload,
} from "../utils/test-utils";
import "@testing-library/jest-dom/vitest";
import { FinalReviewScreen } from "../../app/(app)/create/screens/review/FinalReviewScreen";
import { useCreateFlow } from "../../app/(app)/create/context/CreateFlowContext";
import type { CreateFlowState } from "../../app/(app)/create/types";

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

/**
 * Mounts the screen with a Customize-style preset selection and exposes the
 * latest `state` to the test via `onState`. Used by the edit-modal save
 * semantics suite below to assert what the user's edits actually persist
 * (or don't, on close).
 */
function FinalReviewWithStateProbe({
  onState,
  initial,
}: {
  onState: (_state: CreateFlowState) => void;
  initial: CreateFlowState;
}) {
  const { state, replaceState } = useCreateFlow();
  useLayoutEffect(() => {
    replaceState(initial);
  }, [replaceState, initial]);
  useEffect(() => {
    onState(state);
  }, [state, onState]);
  return <FinalReviewScreen />;
}

const FALLBACK_CARD_TITLE = "Your community";

function FinalReviewWithFlowState({
  title,
  communityContext,
}: {
  title: string;
  communityContext?: string;
}) {
  const { replaceState } = useCreateFlow();
  useLayoutEffect(() => {
    replaceState({
      title,
      ...(communityContext !== undefined ? { communityContext } : {}),
    });
  }, [replaceState, title, communityContext]);
  return <FinalReviewScreen />;
}

describe("FinalReviewScreen", () => {
  it("renders without crashing", () => {
    render(<FinalReviewScreen />);
    expect(screen.getByRole("heading", { level: 1 })).toBeInTheDocument();
  });

  it("renders lockup title", () => {
    render(<FinalReviewScreen />);
    expect(
      screen.getByRole("heading", {
        name: "Review your CommunityRule",
      }),
    ).toBeInTheDocument();
  });

  it("renders lockup description", () => {
    render(<FinalReviewScreen />);
    expect(
      screen.getByText(
        /Here's what other people will see. Make sure everything looks good before you finalize everything. Once the rule is finalized, you must use one of your decision-making mechanisms to edit it again./i,
      ),
    ).toBeInTheDocument();
  });

  it("renders Rule with fallback title when context has no name", () => {
    render(<FinalReviewScreen />);
    expect(screen.getByText(FALLBACK_CARD_TITLE)).toBeInTheDocument();
  });

  it("does not use summary as the card body when community context is empty", () => {
    function TitleAndSummaryHarness() {
      const { replaceState } = useCreateFlow();
      useLayoutEffect(() => {
        replaceState({
          title: "Oak Park Commons",
          summary:
            "Leftover template or one-line summary — must not appear as the Rule description.",
        });
      }, [replaceState]);
      return <FinalReviewScreen />;
    }
    render(<TitleAndSummaryHarness />);
    expect(
      screen.queryByText(/Leftover template or one-line summary/i),
    ).not.toBeInTheDocument();
  });

  it("renders Rule title from create flow state", async () => {
    render(
      <FinalReviewWithFlowState
        title="Oak Park Commons"
        communityContext="Local mutual aid."
      />,
    );
    await waitFor(() => {
      expect(screen.getByText("Oak Park Commons")).toBeInTheDocument();
    });
    expect(
      screen.getByText(/Local mutual aid\./i),
    ).toBeInTheDocument();
  });

  it("renders Rule as a button (card is interactive)", () => {
    render(<FinalReviewScreen />);
    const buttons = screen.getAllByRole("button");
    expect(buttons.length).toBeGreaterThanOrEqual(1);
    expect(
      buttons.some((el) => el.textContent?.includes(FALLBACK_CARD_TITLE)),
    ).toBe(true);
  });

  it("renders expanded Rule with category labels", () => {
    render(<FinalReviewScreen />);
    expect(screen.getByText("Values")).toBeInTheDocument();
    expect(screen.getByText("Communication")).toBeInTheDocument();
    expect(screen.getByText("Membership")).toBeInTheDocument();
    expect(screen.getByText("Decision-making")).toBeInTheDocument();
    expect(screen.getByText("Conflict management")).toBeInTheDocument();
  });

  it("renders category chips", () => {
    render(<FinalReviewScreen />);
    expect(screen.getByText("Consciousness")).toBeInTheDocument();
    expect(screen.getByText("Signal")).toBeInTheDocument();
    expect(screen.getByText("Open Admission")).toBeInTheDocument();
  });
});

/**
 * Seeds a Customize-from-template style state (method ids + core-value
 * snapshot) and asserts the final-review Rule renders the resolved
 * labels — the fix for "preselected chips don't register on final review".
 */
function FinalReviewWithCustomizeSelections() {
  const { replaceState } = useCreateFlow();
  useLayoutEffect(() => {
    replaceState({
      title: "Oak Park Commons",
      selectedCoreValueIds: ["1"],
      coreValuesChipsSnapshot: [
        { id: "1", label: "Accessibility", state: "selected" },
        { id: "2", label: "Accountability", state: "unselected" },
      ],
      selectedCommunicationMethodIds: ["signal"],
      selectedMembershipMethodIds: ["open-access"],
      selectedDecisionApproachIds: ["lazy-consensus"],
      selectedConflictManagementIds: ["peer-mediation"],
    });
  }, [replaceState]);
  return <FinalReviewScreen />;
}

describe("FinalReviewScreen — prefilled selections", () => {
  it("renders chips resolved from selection ids, not demo fallbacks", async () => {
    render(<FinalReviewWithCustomizeSelections />);
    await waitFor(() => {
      expect(screen.getByText("Accessibility")).toBeInTheDocument();
    });
    expect(screen.getByText("Signal")).toBeInTheDocument();
    expect(screen.getByText("Open Access")).toBeInTheDocument();
    expect(screen.getByText("Lazy Consensus")).toBeInTheDocument();
    expect(screen.getByText("Peer Mediation")).toBeInTheDocument();

    // Demo chips from `finalReview.json` must not leak through once the
    // user has real selections: "Open Admission" is shipped as fallback,
    // while the customize flow resolves to "Open Access".
    expect(screen.queryByText("Open Admission")).not.toBeInTheDocument();
    expect(screen.queryByText("Consciousness")).not.toBeInTheDocument();
  });
});

describe("FinalReviewScreen — chip detail modal", () => {
  it("opens the chip modal when a chip is clicked, matching the preset copy", async () => {
    render(<FinalReviewWithCustomizeSelections />);

    const signalChip = await screen.findByRole("button", { name: "Signal" });
    fireEvent.click(signalChip);

    // Modal subtitle is the `supportText` from communication.json for the
    // "signal" method — proves the chip click resolved the correct preset
    // and reused the TemplateChipDetailModal's by-label lookup.
    await waitFor(() => {
      expect(
        screen.getByText(
          /Encrypted messaging for high-security, private coordination\./i,
        ),
      ).toBeInTheDocument();
    });
    // Core-principle section heading is shared copy from the same messages
    // file; assert it renders to confirm the modal body hydrated.
    expect(
      screen.getAllByText(/core principle/i).length,
    ).toBeGreaterThanOrEqual(1);
  });

  it("keeps catalog section editors when a title override exists and there are no custom fields", async () => {
    render(
      <FinalReviewWithStateProbe
        onState={() => {
          /* noop */
        }}
        initial={{
          title: "Oak Park Commons",
          selectedCommunicationMethodIds: ["video-meetings"],
          customMethodCardMetaById: {
            "video-meetings": {
              label: "Video Meetings",
              supportText:
                "Synchronous video calls for remote face-to-face interaction.",
            },
          },
        }}
      />,
    );

    fireEvent.click(
      await screen.findByRole("button", { name: "Video Meetings" }),
    );
    const dialog = await screen.findByRole("dialog");
    expect(within(dialog).getAllByRole("textbox")).toHaveLength(3);
    expect(
      within(dialog).queryByText("No custom fields yet."),
    ).not.toBeInTheDocument();
    expect(
      within(dialog).getByText(/core principle/i),
    ).toBeInTheDocument();
  });

  it("method chip modal kebab offers Customize but not Duplicate", async () => {
    render(<FinalReviewWithCustomizeSelections />);
    fireEvent.click(await screen.findByRole("button", { name: "Signal" }));
    const dialog = await screen.findByRole("dialog");
    fireEvent.click(
      within(dialog).getByRole("button", { name: /more options/i }),
    );
    await waitFor(() => {
      expect(
        screen.getByRole("menuitem", { name: /^customize$/i }),
      ).toBeInTheDocument();
    });
    expect(
      screen.queryByRole("menuitem", { name: /^duplicate$/i }),
    ).not.toBeInTheDocument();
  });

  it("values chip modal kebab offers Customize and Duplicate under the cap", async () => {
    function CoreValuesHarness() {
      const { replaceState } = useCreateFlow();
      useLayoutEffect(() => {
        replaceState({
          selectedCoreValueIds: ["1"],
          coreValuesChipsSnapshot: [
            { id: "1", label: "Accessibility", state: "selected" },
          ],
        });
      }, [replaceState]);
      return <FinalReviewScreen />;
    }
    render(<CoreValuesHarness />);
    fireEvent.click(
      await screen.findByRole("button", { name: "Accessibility" }),
    );
    const dialog = await screen.findByRole("dialog");
    fireEvent.click(
      within(dialog).getByRole("button", { name: /more options/i }),
    );
    await waitFor(() => {
      expect(
        screen.getByRole("menuitem", { name: /^duplicate$/i }),
      ).toBeInTheDocument();
    });
    expect(
      screen.getByRole("menuitem", { name: /^customize$/i }),
    ).toBeInTheDocument();
  });

  it("values Customize starts on the policy name step", async () => {
    function CoreValuesHarness() {
      const { replaceState } = useCreateFlow();
      useLayoutEffect(() => {
        replaceState({
          selectedCoreValueIds: ["1"],
          coreValuesChipsSnapshot: [
            { id: "1", label: "Accessibility", state: "selected" },
          ],
        });
      }, [replaceState]);
      return <FinalReviewScreen />;
    }
    render(<CoreValuesHarness />);
    fireEvent.click(
      await screen.findByRole("button", { name: "Accessibility" }),
    );
    const dialog = await screen.findByRole("dialog");
    fireEvent.click(
      within(dialog).getByRole("button", { name: /more options/i }),
    );
    fireEvent.click(
      await screen.findByRole("menuitem", { name: /^customize$/i }),
    );
    expect(
      await screen.findByPlaceholderText("Policy name"),
    ).toHaveValue("Accessibility");
    expect(screen.queryByText("Custom policy details")).not.toBeInTheDocument();
  });

  it("opens method chip modal editable, with Save disabled until a field changes", async () => {
    render(<FinalReviewWithCustomizeSelections />);

    fireEvent.click(await screen.findByRole("button", { name: "Signal" }));
    const dialog = await screen.findByRole("dialog");

    const saveButton = within(dialog).getByRole("button", { name: "Save" });
    expect(saveButton).toBeDisabled();

    const principleField = within(dialog).getByRole("textbox", {
      name: /core principle/i,
    });
    expect(principleField).not.toBeDisabled();

    fireEvent.change(principleField, { target: { value: "Edited principle" } });
    await waitFor(() => {
      expect(
        within(dialog).getByRole("button", { name: "Save" }),
      ).not.toBeDisabled();
    });
  });

  it("opens a core-values chip with the matching preset meaning/signals", async () => {
    function CoreValuesHarness() {
      const { replaceState } = useCreateFlow();
      useLayoutEffect(() => {
        replaceState({
          selectedCoreValueIds: ["1"],
          coreValuesChipsSnapshot: [
            { id: "1", label: "Accessibility", state: "selected" },
          ],
        });
      }, [replaceState]);
      return <FinalReviewScreen />;
    }
    render(<CoreValuesHarness />);

    const chip = await screen.findByRole("button", { name: "Accessibility" });
    fireEvent.click(chip);

    await waitFor(() => {
      expect(
        screen.getByText(/what does this value mean to your group\?/i),
      ).toBeInTheDocument();
    });
    expect(
      screen.getByText(/signals of violation/i),
    ).toBeInTheDocument();
  });

  it("opens the editable Save modal for a values chip", async () => {
    // Plain custom-rule path: snapshot is set, sections is not.
    function CoreValuesHarness() {
      const { replaceState } = useCreateFlow();
      useLayoutEffect(() => {
        replaceState({
          selectedCoreValueIds: ["1"],
          coreValuesChipsSnapshot: [
            { id: "1", label: "Accessibility", state: "selected" },
          ],
        });
      }, [replaceState]);
      return <FinalReviewScreen />;
    }
    render(<CoreValuesHarness />);

    fireEvent.click(
      await screen.findByRole("button", { name: "Accessibility" }),
    );
    const dialog = await screen.findByRole("dialog");
    expect(
      within(dialog).getByRole("button", { name: "Save" }),
    ).toBeInTheDocument();
    expect(
      within(dialog).queryByRole("button", { name: "Close" }),
    ).not.toBeInTheDocument();
  });

  it("opens Save for a values chip (use-without-changes seeded snapshot)", async () => {
    // Mirrors the post-fix payload from `handleUseTemplateWithoutChanges`:
    // template Values section is stripped from `sections`, snapshot +
    // selected ids are seeded so the chip carries an `overrideKey`.
    function UseWithoutChangesHarness() {
      const { replaceState } = useCreateFlow();
      useLayoutEffect(() => {
        replaceState({
          title: "Oak Park Commons",
          // Values section deliberately absent — apply handler scrubs it.
          sections: [
            {
              categoryName: "Communication",
              entries: [{ title: "Signal", body: "…" }],
            },
          ],
          selectedCoreValueIds: ["1"],
          coreValuesChipsSnapshot: [
            { id: "1", label: "Accessibility", state: "selected" },
          ],
        });
      }, [replaceState]);
      return <FinalReviewScreen />;
    }
    render(<UseWithoutChangesHarness />);

    fireEvent.click(
      await screen.findByRole("button", { name: "Accessibility" }),
    );
    const dialog = await screen.findByRole("dialog");
    expect(
      within(dialog).getByRole("button", { name: "Save" }),
    ).toBeInTheDocument();
  });

  it("closes a values chip without discard when nothing changed", async () => {
    render(
      <FinalReviewWithStateProbe
        onState={() => {
          /* noop */
        }}
        initial={{
          selectedCoreValueIds: ["1"],
          coreValuesChipsSnapshot: [
            { id: "1", label: "Accessibility", state: "selected" },
          ],
          coreValueDetailsByChipId: {
            "1": {
              meaning: "Everyone can participate.",
              signals: "Captions and ramps.",
            },
          },
          customMethodCardFieldBlocksById: {
            "1": [
              {
                kind: "text",
                id: "facet-meaning",
                blockTitle: "What this value means",
                placeholderText: "Everyone can participate.",
              },
              {
                kind: "text",
                id: "facet-signals",
                blockTitle: "Signals",
                placeholderText: "Captions and ramps.",
              },
            ],
          },
        }}
      />,
    );

    fireEvent.click(
      await screen.findByRole("button", { name: "Accessibility" }),
    );
    const valuesDialog = await screen.findByRole("dialog");
    expect(within(valuesDialog).getAllByRole("textbox").length).toBeGreaterThan(
      0,
    );
    fireEvent.keyDown(document, { key: "Escape" });
    await waitFor(() => {
      expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
    });
    expect(
      screen.queryByRole("button", { name: "Keep editing" }),
    ).not.toBeInTheDocument();
  });

  it("closes a method chip without discard when nothing changed", async () => {
    render(
      <FinalReviewWithStateProbe
        onState={() => {
          /* noop */
        }}
        initial={{
          title: "Oak Park Commons",
          selectedCommunicationMethodIds: ["signal"],
        }}
      />,
    );

    fireEvent.click(await screen.findByRole("button", { name: "Signal" }));
    const methodDialog = await screen.findByRole("dialog");
    expect(within(methodDialog).getAllByRole("textbox").length).toBeGreaterThan(
      0,
    );
    fireEvent.keyDown(document, { key: "Escape" });
    await waitFor(() => {
      expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
    });
    expect(
      screen.queryByRole("button", { name: "Keep editing" }),
    ).not.toBeInTheDocument();
  });

  it("closes the chip edit modal when Back is pressed", async () => {
    render(
      <FinalReviewWithStateProbe
        onState={() => {}}
        initial={{
          title: "Oak Park Commons",
          selectedCommunicationMethodIds: ["signal"],
        }}
      />,
    );

    fireEvent.click(await screen.findByRole("button", { name: "Signal" }));
    const dialog = await screen.findByRole("dialog");
    fireEvent.click(within(dialog).getByRole("button", { name: "Back" }));
    await waitFor(() => {
      expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
    });
  });

});

/**
 * Save semantics for {@link FinalReviewChipEditModal}. Mirrors the
 * "edits ride along to publish" promise documented in the modal:
 *
 * 1. Save starts disabled (no edits yet → nothing to persist).
 * 2. Editing any field flips Save on; clicking it writes the typed
 *    `{group}MethodDetailsById[id]` entry into create-flow state.
 * 3. Closing without Save discards every typed change.
 */
describe("FinalReviewScreen — chip edit modal save semantics", () => {
  const baseSelections: CreateFlowState = {
    title: "Oak Park Commons",
    selectedCommunicationMethodIds: ["signal"],
  };

  it("starts with the Save button disabled until the user edits a field", async () => {
    let latest: CreateFlowState = {};
    render(
      <FinalReviewWithStateProbe
        onState={(s) => {
          latest = s;
        }}
        initial={baseSelections}
      />,
    );
    void latest;

    fireEvent.click(await screen.findByRole("button", { name: "Signal" }));
    const dialog = await screen.findByRole("dialog");
    const saveButton = within(dialog).getByRole("button", { name: "Save" });
    expect(saveButton).toBeDisabled();
    const principleField = within(dialog).getByRole("textbox", {
      name: /core principle/i,
    });

    fireEvent.change(principleField, {
      target: { value: "Edited principle" },
    });

    await waitFor(() => {
      expect(
        within(dialog).getByRole("button", { name: "Save" }),
      ).not.toBeDisabled();
    });
  });

  it("writes edits into communicationMethodDetailsById when Save is clicked", async () => {
    let latest: CreateFlowState = {};
    render(
      <FinalReviewWithStateProbe
        onState={(s) => {
          latest = s;
        }}
        initial={baseSelections}
      />,
    );

    fireEvent.click(await screen.findByRole("button", { name: "Signal" }));
    const dialog = await screen.findByRole("dialog");
    const principleField = within(dialog).getByRole("textbox", {
      name: /core principle/i,
    });
    fireEvent.change(principleField, {
      target: { value: "Edited principle" },
    });
    fireEvent.click(within(dialog).getByRole("button", { name: "Save" }));

    await waitFor(() => {
      expect(
        latest.communicationMethodDetailsById?.signal?.corePrinciple,
      ).toBe("Edited principle");
    });
  });

  it("discards typed edits when the modal closes without Save", async () => {
    let latest: CreateFlowState = {};
    render(
      <FinalReviewWithStateProbe
        onState={(s) => {
          latest = s;
        }}
        initial={baseSelections}
      />,
    );

    fireEvent.click(await screen.findByRole("button", { name: "Signal" }));
    const dialog = await screen.findByRole("dialog");
    const principleField = within(dialog).getByRole("textbox", {
      name: /core principle/i,
    });
    fireEvent.change(principleField, {
      target: { value: "Should NOT persist" },
    });

    fireEvent.keyDown(document, { key: "Escape" });
    await confirmDiscardCustomizeEdits();
    await waitFor(() => {
      expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
    });

    expect(latest.communicationMethodDetailsById).toBeUndefined();
  });

  it("blocks tab close while chip edits are unsaved", async () => {
    render(
      <FinalReviewWithStateProbe
        onState={() => {
          /* noop */
        }}
        initial={baseSelections}
      />,
    );

    fireEvent.click(await screen.findByRole("button", { name: "Signal" }));
    const dialog = await screen.findByRole("dialog");
    expect(dispatchBeforeUnload()).toBe(false);
    const principleField = within(dialog).getByRole("textbox", {
      name: /core principle/i,
    });
    fireEvent.change(principleField, {
      target: { value: "Unsaved on tab close" },
    });
    expect(dispatchBeforeUnload()).toBe(true);
  });

  it("shows consolidated placeholder for user-authored communication chips", async () => {
    const customId = "550e8400-e29b-41d4-a716-446655440000";
    render(
      <FinalReviewWithStateProbe
        onState={() => {}}
        initial={{
          title: "Oak Park Commons",
          selectedCommunicationMethodIds: [customId],
          customMethodCardMetaById: {
            [customId]: {
              label: "Custom Comm",
              supportText: "Support line from wizard",
            },
          },
        }}
      />,
    );

    fireEvent.click(
      await screen.findByRole("button", { name: "Custom Comm" }),
    );
    const dialog = await screen.findByRole("dialog");
    expect(
      within(dialog).getByText(/no custom fields yet/i),
    ).toBeInTheDocument();
    expect(within(dialog).queryByRole("textbox")).toBeNull();
  });

  it("shows custom communication chip when template sections exist (customize-from-template)", async () => {
    const customId = "550e8400-e29b-41d4-a716-446655440999";
    render(
      <FinalReviewWithStateProbe
        onState={() => {}}
        initial={{
          title: "Oak Park Commons",
          sections: [
            {
              categoryName: "Communication",
              entries: [{ title: "Signal", body: "…" }],
            },
          ],
          selectedCommunicationMethodIds: ["signal", customId],
          customMethodCardMetaById: {
            [customId]: {
              label: "Garden IRC",
              supportText: "Support line from wizard",
            },
          },
        }}
      />,
    );

    expect(
      await screen.findByRole("button", { name: "Garden IRC" }),
    ).toBeInTheDocument();
    expect(screen.getByRole("button", { name: "Signal" })).toBeInTheDocument();
  });

  it("shows editable field blocks for user-authored communication chips when configured", async () => {
    const customId = "550e8400-e29b-41d4-a716-446655440000";
    render(
      <FinalReviewWithStateProbe
        onState={() => {}}
        initial={{
          title: "Oak Park Commons",
          selectedCommunicationMethodIds: [customId],
          customMethodCardMetaById: {
            [customId]: {
              label: "Custom Comm",
              supportText: "Support line from wizard",
            },
          },
          customMethodCardFieldBlocksById: {
            [customId]: [
              {
                kind: "text",
                id: "f1",
                blockTitle: "Notes",
                placeholderText: "Detail here",
              },
            ],
          },
        }}
      />,
    );

    fireEvent.click(
      await screen.findByRole("button", { name: "Custom Comm" }),
    );
    const dialog = await screen.findByRole("dialog");
    expect(
      within(dialog).queryByText(/no custom fields yet/i),
    ).not.toBeInTheDocument();
    const notesField = within(dialog).getByRole("textbox", { name: /notes/i });
    expect(notesField).not.toBeDisabled();
    expect(notesField).toHaveValue("Detail here");
  });

  it("persists field block edits for user-authored communication chips on Save", async () => {
    const customId = "550e8400-e29b-41d4-a716-446655440000";
    let latest: CreateFlowState = {};
    render(
      <FinalReviewWithStateProbe
        onState={(s) => {
          latest = s;
        }}
        initial={{
          title: "Oak Park Commons",
          selectedCommunicationMethodIds: [customId],
          customMethodCardMetaById: {
            [customId]: {
              label: "Custom Comm",
              supportText: "Support line from wizard",
            },
          },
          customMethodCardFieldBlocksById: {
            [customId]: [
              {
                kind: "text",
                id: "f1",
                blockTitle: "Notes",
                placeholderText: "Detail here",
              },
            ],
          },
        }}
      />,
    );

    fireEvent.click(
      await screen.findByRole("button", { name: "Custom Comm" }),
    );
    const dialog = await screen.findByRole("dialog");
    const notesField = within(dialog).getByRole("textbox", { name: /notes/i });
    fireEvent.change(notesField, { target: { value: "Saved detail" } });
    fireEvent.click(within(dialog).getByRole("button", { name: "Save" }));

    await waitFor(() => {
      expect(
        latest.customMethodCardFieldBlocksById?.[customId]?.[0],
      ).toMatchObject({
        kind: "text",
        placeholderText: "Saved detail",
      });
    });
    expect(screen.getByRole("dialog")).toBeInTheDocument();
  });

  it("Customize opens the policy wizard prefilled with the chip title", async () => {
    render(
      <FinalReviewWithStateProbe
        onState={() => {}}
        initial={baseSelections}
      />,
    );

    fireEvent.click(await screen.findByRole("button", { name: "Signal" }));
    const dialog = await screen.findByRole("dialog");
    fireEvent.click(
      within(dialog).getByRole("button", { name: /more options/i }),
    );
    fireEvent.click(
      await screen.findByRole("menuitem", { name: /^customize$/i }),
    );

    expect(
      await screen.findByPlaceholderText("Policy name"),
    ).toHaveValue("Signal");
    fireEvent.keyDown(document, { key: "Escape" });

    await waitFor(() => {
      expect(screen.queryByPlaceholderText("Policy name")).not.toBeInTheDocument();
    });
    expect(screen.getByRole("dialog")).toBeInTheDocument();
    expect(
      within(screen.getByRole("dialog")).getByText("Signal"),
    ).toBeInTheDocument();
  });

  it("Customize wizard Finalize persists a renamed chip title", async () => {
    let latest: CreateFlowState = {};
    render(
      <FinalReviewWithStateProbe
        onState={(s) => {
          latest = s;
        }}
        initial={baseSelections}
      />,
    );

    fireEvent.click(await screen.findByRole("button", { name: "Signal" }));
    const dialog = await screen.findByRole("dialog");
    fireEvent.click(
      within(dialog).getByRole("button", { name: /more options/i }),
    );
    fireEvent.click(
      await screen.findByRole("menuitem", { name: /^customize$/i }),
    );

    expect(
      await screen.findByPlaceholderText("Policy name"),
    ).toBeInTheDocument();
    const nameInput = screen.getByPlaceholderText("Policy name");
    fireEvent.change(nameInput, { target: { value: "Custom Signal header" } });
    fireEvent.click(screen.getByRole("button", { name: "Next" }));
    fireEvent.click(screen.getByRole("button", { name: "Next" }));
    fireEvent.click(screen.getByRole("button", { name: "Finalize policy" }));

    await waitFor(() => {
      expect(latest.customMethodCardMetaById?.signal?.label).toBe(
        "Custom Signal header",
      );
    });
  });

  it("Customize Finalize shows reordered field blocks in the chip modal", async () => {
    let latest: CreateFlowState = {};
    render(
      <FinalReviewWithStateProbe
        onState={(s) => {
          latest = s;
        }}
        initial={baseSelections}
      />,
    );

    fireEvent.click(await screen.findByRole("button", { name: "Signal" }));
    const dialog = await screen.findByRole("dialog");
    fireEvent.click(
      within(dialog).getByRole("button", { name: /more options/i }),
    );
    fireEvent.click(
      await screen.findByRole("menuitem", { name: /^customize$/i }),
    );

    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[2], { dataTransfer });

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

    await waitFor(() => {
      expect(
        screen.queryByPlaceholderText("Policy name"),
      ).not.toBeInTheDocument();
    });
    await waitFor(() => {
      expect(
        latest.customMethodCardFieldBlocksById?.signal?.map((b) => b.id),
      ).toEqual([
        "facet-logisticsAdmin",
        "facet-codeOfConduct",
        "facet-corePrinciple",
      ]);
    });

    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(/Logistics, Admin/);
    expect(labels[1]).toMatch(/Code of Conduct/);
    expect(labels[2]).toMatch(/Core Principle/);
  });
});

function FinalReviewEditPublishedWithStateProbe({
  onState,
  initial,
}: {
  onState: (_state: CreateFlowState) => void;
  initial: CreateFlowState;
}) {
  const { state, replaceState } = useCreateFlow();
  useLayoutEffect(() => {
    replaceState(initial);
  }, [replaceState, initial]);
  useEffect(() => {
    onState(state);
  }, [state, onState]);
  return <FinalReviewScreen variant="editPublished" />;
}

describe("FinalReviewScreen — edit published title and description", () => {
  it("does not expose click-to-edit title or description on default final review", () => {
    render(
      <FinalReviewWithFlowState
        title="Oak"
        communityContext="Visible body"
      />,
    );
    expect(screen.queryByTestId("rule-title-edit")).not.toBeInTheDocument();
    expect(screen.queryByTestId("rule-description-edit")).not.toBeInTheDocument();
  });

  it("opens Save modal from title click and updates title", async () => {
    let latest: CreateFlowState = {};
    render(
      <FinalReviewEditPublishedWithStateProbe
        onState={(s) => {
          latest = s;
        }}
        initial={{
          title: "Oak Park Commons",
          communityContext: "Original",
          selectedCommunicationMethodIds: ["signal"],
        }}
      />,
    );

    fireEvent.click(await screen.findByTestId("rule-title-edit"));
    const dialog = await screen.findByRole("dialog");
    expect(within(dialog).getByText(/Community name/i)).toBeInTheDocument();
    const input = within(dialog).getByRole("textbox");
    fireEvent.change(input, { target: { value: "Renamed Commons" } });
    fireEvent.click(within(dialog).getByRole("button", { name: "Save" }));

    await waitFor(() => {
      expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
    });
    await waitFor(() => {
      expect(latest.title).toBe("Renamed Commons");
    });
  });

  it("opens Save modal from description click and updates communityContext + summary", async () => {
    let latest: CreateFlowState = {};
    render(
      <FinalReviewEditPublishedWithStateProbe
        onState={(s) => {
          latest = s;
        }}
        initial={{
          title: "Oak Park Commons",
          communityContext: "Original",
          summary: "Original",
          selectedCommunicationMethodIds: ["signal"],
        }}
      />,
    );
    void latest;

    fireEvent.click(await screen.findByTestId("rule-description-edit"));
    const dialog = await screen.findByRole("dialog");
    expect(
      within(dialog).getByText(/Community description/i),
    ).toBeInTheDocument();
    const input = within(dialog).getByRole("textbox");
    fireEvent.change(input, { target: { value: "Updated copy" } });
    fireEvent.click(within(dialog).getByRole("button", { name: "Save" }));

    await waitFor(() => {
      expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
    });
    await waitFor(() => {
      expect(latest.communityContext).toBe("Updated copy");
      expect(latest.summary).toBe("Updated copy");
    });
  });
});
