import { useEffect, useLayoutEffect } from "react";
import { describe, it, expect, afterEach } from "vitest";
import {
  renderWithProviders as render,
  screen,
  cleanup,
  within,
  waitFor,
  dispatchBeforeUnload,
} from "../utils/test-utils";
import { fireEvent } from "@testing-library/react";
import "@testing-library/jest-dom/vitest";
import { CommunicationMethodsScreen } from "../../app/(app)/create/screens/card/CommunicationMethodsScreen";
import { useCreateFlow } from "../../app/(app)/create/context/CreateFlowContext";
import type { CreateFlowState } from "../../app/(app)/create/types";
import { communicationPresetFor } from "../../lib/create/finalReviewChipPresets";
import messages from "../../messages/en/index";

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

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

async function declineDiscardCustomizeEdits() {
  fireEvent.click(
    await screen.findByRole("button", { name: "Keep editing" }),
  );
}

/**
 * Mounts the screen with optional starting state and exposes the latest
 * `state` to the test harness so we can assert the persistence side of
 * the Add Platform flow without driving the wizard's Next chain.
 */
const EMPTY_STATE: CreateFlowState = {};

function ScreenWithStateProbe({
  onState,
  initial = EMPTY_STATE,
}: {
  onState: (_state: CreateFlowState) => void;
  initial?: CreateFlowState;
}) {
  const { state, replaceState } = useCreateFlow();
  useLayoutEffect(() => {
    replaceState(initial);
  }, [replaceState, initial]);
  useEffect(() => {
    onState(state);
  }, [state, onState]);
  return <CommunicationMethodsScreen />;
}

/**
 * Confirms the persistence half of the Add-Platform flow that lets the
 * final-review chip edit modal start from a known seed instead of always
 * snapping back to preset copy. See {@link CommunicationMethodEditFields}
 * and `buildPublishPayload` for the read side.
 */
describe("CommunicationMethodsScreen — Add Platform persistence", () => {
  it("seeds the modal from preset and persists edits + selection on Confirm", async () => {
    let latest: CreateFlowState = {};
    render(
      <ScreenWithStateProbe
        onState={(s) => {
          latest = s;
        }}
      />,
    );

    fireEvent.click(
      screen.getAllByRole("button", { name: /Signal: Encrypted messaging/ })[0],
    );

    const dialog = await screen.findByRole("dialog");
    expect(within(dialog).queryByAltText("Help")).not.toBeInTheDocument();
    const textboxes = within(dialog).getAllByRole("textbox");
    expect(textboxes.length).toBe(3);
    const corePrincipleField = textboxes[0] as HTMLTextAreaElement;
    // Preset corePrinciple must seed into the first body textarea so the user
    // edits a real starting point rather than an empty field.
    expect(corePrincipleField.value.length).toBeGreaterThan(0);

    fireEvent.change(corePrincipleField, { target: { value: "Custom principle" } });
    fireEvent.click(
      within(dialog).getByRole("button", {
        name: "Add Platform",
      }),
    );

    await waitFor(() => {
      expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
    });
    await waitFor(() => {
      expect(latest.selectedCommunicationMethodIds).toContain("signal");
    });
    expect(
      latest.communicationMethodDetailsById?.signal?.corePrinciple,
    ).toBe("Custom principle");
  });

  it("does not persist edits when the modal closes without Confirm", async () => {
    let latest: CreateFlowState = {};
    render(
      <ScreenWithStateProbe
        onState={(s) => {
          latest = s;
        }}
      />,
    );

    fireEvent.click(
      screen.getAllByRole("button", { name: /Signal: Encrypted messaging/ })[0],
    );
    const dialog = await screen.findByRole("dialog");
    void dialog;
    fireEvent.keyDown(document, { key: "Escape" });
    await waitFor(() => {
      expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
    });

    expect(latest.selectedCommunicationMethodIds ?? []).not.toContain("signal");
    expect(latest.communicationMethodDetailsById).toBeUndefined();
  });

  it("re-seeds the modal from a saved override when reopening the same chip", async () => {
    let latest: CreateFlowState = {};
    render(
      <ScreenWithStateProbe
        onState={(s) => {
          latest = s;
        }}
        initial={{
          selectedCommunicationMethodIds: ["signal"],
          communicationMethodDetailsById: {
            signal: {
              corePrinciple: "Saved principle",
              logisticsAdmin: "Saved logistics",
              codeOfConduct: "Saved coc",
            },
          },
        }}
      />,
    );
    void latest;

    fireEvent.click(
      screen.getAllByRole("button", { name: /Signal: Encrypted messaging/ })[0],
    );
    const dialog = await screen.findByRole("dialog");
    const textareas = within(dialog).getAllByRole(
      "textbox",
    ) as HTMLTextAreaElement[];
    expect(textareas.length).toBe(3);
    expect(textareas[0].value).toBe("Saved principle");
    expect(textareas[1].value).toBe("Saved logistics");
    expect(textareas[2].value).toBe("Saved coc");
  });

  it("persists edits and closes when Save is clicked on an already-selected platform", async () => {
    let latest: CreateFlowState = {};
    render(
      <ScreenWithStateProbe
        onState={(s) => {
          latest = s;
        }}
        initial={{
          selectedCommunicationMethodIds: ["signal"],
          communicationMethodDetailsById: {
            signal: {
              corePrinciple: "Saved principle",
              logisticsAdmin: "Saved logistics",
              codeOfConduct: "Saved coc",
            },
          },
        }}
      />,
    );

    fireEvent.click(
      screen.getAllByRole("button", { name: /Signal: Encrypted messaging/ })[0],
    );
    const dialog = await screen.findByRole("dialog");
    const textareas = within(dialog).getAllByRole(
      "textbox",
    ) as HTMLTextAreaElement[];
    fireEvent.change(textareas[0], { target: { value: "Edited principle" } });
    fireEvent.click(within(dialog).getByRole("button", { name: "Save" }));

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

  it("keeps catalog section editors when a title override exists and there are no custom fields", async () => {
    const details = communicationPresetFor("video-meetings");
    const noFieldsHint =
      messages.create.customRule.customMethodCardWizard.editModal
        .noCustomFieldsYet;
    render(
      <ScreenWithStateProbe
        onState={() => {
          /* noop */
        }}
        initial={{
          selectedCommunicationMethodIds: ["video-meetings"],
          customMethodCardMetaById: {
            "video-meetings": {
              label: "Video Meetings",
              supportText:
                "Synchronous video calls for remote face-to-face interaction.",
            },
          },
          communicationMethodDetailsById: {
            "video-meetings": details,
          },
        }}
      />,
    );

    fireEvent.click(
      screen.getAllByRole("button", {
        name: /Video Meetings: Synchronous video/,
      })[0],
    );
    const dialog = await screen.findByRole("dialog");
    const textareas = within(dialog).getAllByRole(
      "textbox",
    ) as HTMLTextAreaElement[];
    expect(textareas).toHaveLength(3);
    expect(textareas[0].value).toBe(details.corePrinciple);
    expect(textareas[1].value).toBe(details.logisticsAdmin);
    expect(textareas[2].value).toBe(details.codeOfConduct);
    expect(within(dialog).queryByText(noFieldsHint)).not.toBeInTheDocument();
  });

  it("opens meaning fields editable without Customize", async () => {
    render(
      <ScreenWithStateProbe
        onState={() => {
          /* noop */
        }}
      />,
    );

    fireEvent.click(
      screen.getAllByRole("button", { name: /Signal: Encrypted messaging/ })[0],
    );
    const dialog = await screen.findByRole("dialog");
    const textareas = within(dialog).getAllByRole(
      "textbox",
    ) as HTMLTextAreaElement[];
    expect(textareas[0]).not.toBeDisabled();
    fireEvent.click(within(dialog).getByRole("button", { name: "More options" }));
    expect(
      screen.getByRole("menuitem", { name: "Customize" }),
    ).toBeInTheDocument();
  });

  it("closing the wizard without Finalize leaves the card modal and does not persist", async () => {
    let latest: CreateFlowState = {};
    render(
      <ScreenWithStateProbe
        onState={(s) => {
          latest = s;
        }}
      />,
    );

    fireEvent.click(
      screen.getAllByRole("button", { name: /Signal: Encrypted messaging/ })[0],
    );
    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();
    });
    expect(screen.getByRole("dialog")).toBeInTheDocument();
    expect(latest.customMethodCardMetaById).toBeUndefined();
  });

  it("Back from the wizard with edits asks to discard and does not persist", async () => {
    let latest: CreateFlowState = {};
    render(
      <ScreenWithStateProbe
        onState={(s) => {
          latest = s;
        }}
      />,
    );

    fireEvent.click(
      screen.getAllByRole("button", { name: /Signal: Encrypted messaging/ })[0],
    );
    const dialog = await screen.findByRole("dialog");
    fireEvent.click(within(dialog).getByRole("button", { name: "More options" }));
    fireEvent.click(screen.getByRole("menuitem", { name: "Customize" }));

    const nameInput = await screen.findByPlaceholderText("Policy name");
    fireEvent.change(nameInput, { target: { value: "Renamed in wizard" } });
    fireEvent.click(screen.getByRole("button", { name: "Back" }));

    expect(
      await screen.findByRole("button", { name: "Keep editing" }),
    ).toBeInTheDocument();
    fireEvent.click(screen.getByRole("button", { name: "Discard" }));

    await waitFor(() => {
      expect(screen.queryByPlaceholderText("Policy name")).not.toBeInTheDocument();
    });
    expect(screen.getByRole("dialog")).toBeInTheDocument();
    expect(latest.customMethodCardMetaById).toBeUndefined();
  });

  it("Escape after a field edit stays open when user declines discard confirm", async () => {
    render(
      <ScreenWithStateProbe
        onState={() => {
          /* noop */
        }}
      />,
    );

    fireEvent.click(
      screen.getAllByRole("button", { name: /Signal: Encrypted messaging/ })[0],
    );
    const dialog = await screen.findByRole("dialog");
    const textboxes = within(dialog).getAllByRole(
      "textbox",
    ) as HTMLTextAreaElement[];
    fireEvent.change(textboxes[0], { target: { value: "Edited principle" } });

    fireEvent.keyDown(document, { key: "Escape" });
    const keepEditing = await screen.findByRole("button", {
      name: "Keep editing",
    });
    expect(keepEditing.parentElement).toHaveClass(
      "absolute",
      "left-[16px]",
      "top-[12px]",
    );
    await declineDiscardCustomizeEdits();

    expect(screen.getByRole("dialog")).toBeInTheDocument();
  });

  it("Escape after a field edit discards without persisting when confirmed", async () => {
    let latest: CreateFlowState = {};
    render(
      <ScreenWithStateProbe
        onState={(s) => {
          latest = s;
        }}
        initial={{
          selectedCommunicationMethodIds: ["signal"],
          communicationMethodDetailsById: {
            signal: {
              corePrinciple: "Saved principle",
              logisticsAdmin: "Saved logistics",
              codeOfConduct: "Saved coc",
            },
          },
        }}
      />,
    );

    fireEvent.click(
      screen.getAllByRole("button", { name: /Signal: Encrypted messaging/ })[0],
    );
    const dialog = await screen.findByRole("dialog");
    const textboxes = within(dialog).getAllByRole(
      "textbox",
    ) as HTMLTextAreaElement[];
    fireEvent.change(textboxes[0], { target: { value: "Edited principle" } });
    fireEvent.keyDown(document, { key: "Escape" });
    await confirmDiscardCustomizeEdits();

    await waitFor(() => {
      expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
    });
    expect(
      latest.communicationMethodDetailsById?.signal?.corePrinciple,
    ).toBe("Saved principle");
  });

  it("Customize wizard prefills the card title and persists a rename on Finalize", async () => {
    const customId = "00000000-0000-4000-8000-0000000000aa";
    let latest: CreateFlowState = {};
    render(
      <ScreenWithStateProbe
        onState={(s) => {
          latest = s;
        }}
        initial={{
          selectedCommunicationMethodIds: [customId],
          customMethodCardMetaById: {
            [customId]: { label: "Original title", supportText: "Sub" },
          },
          communicationMethodDetailsById: {
            [customId]: {
              corePrinciple: "p",
              logisticsAdmin: "l",
              codeOfConduct: "c",
            },
          },
        }}
      />,
    );

    fireEvent.click(
      screen.getAllByRole("button", { name: /Original title/ })[0],
    );
    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();
    const nameInput = screen.getByPlaceholderText("Policy name");
    expect(nameInput).toHaveValue("Original title");
    fireEvent.change(nameInput, { target: { value: "Renamed policy" } });
    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: "Core Principle & Scope" }),
    ).toBeInTheDocument();
    fireEvent.click(screen.getByRole("button", { name: "Finalize policy" }));

    await waitFor(() => {
      expect(latest.customMethodCardMetaById?.[customId]?.label).toBe(
        "Renamed policy",
      );
    });
  });

  it("stores preset id title override in customMethodCardMetaById on Finalize", async () => {
    let latest: CreateFlowState = {};
    render(
      <ScreenWithStateProbe
        onState={(s) => {
          latest = s;
        }}
      />,
    );

    fireEvent.click(
      screen.getAllByRole("button", { name: /Signal: Encrypted messaging/ })[0],
    );
    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("Signal");
    fireEvent.click(screen.getByRole("button", { name: "Next" }));
    fireEvent.click(screen.getByRole("button", { name: "Next" }));
    expect(
      await screen.findByText("Custom policy details"),
    ).toBeInTheDocument();
    fireEvent.click(
      screen.getByRole("button", { name: "Core Principle & Scope" }),
    );
    expect(await screen.findByText("Add text block")).toBeInTheDocument();
    expect(
      screen.getByDisplayValue(/We prioritize privacy and security/i),
    ).toBeInTheDocument();
    fireEvent.click(screen.getByRole("button", { name: "Back" }));
    fireEvent.click(screen.getByRole("button", { name: "Back" }));
    fireEvent.click(screen.getByRole("button", { name: "Back" }));
    const nameInput = await screen.findByPlaceholderText("Policy name");
    expect(nameInput).toHaveValue("Signal");
    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 card modal", async () => {
    let latest: CreateFlowState = {};
    render(
      <ScreenWithStateProbe
        onState={(s) => {
          latest = s;
        }}
      />,
    );

    fireEvent.click(
      screen.getAllByRole("button", { name: /Signal: Encrypted messaging/ })[0],
    );
    const cardDialog = await screen.findByRole("dialog");
    fireEvent.click(
      within(cardDialog).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[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/);
  });

  it("does not block tab close when the create modal is unchanged", async () => {
    render(
      <ScreenWithStateProbe
        onState={() => {
          /* noop */
        }}
      />,
    );

    fireEvent.click(
      screen.getAllByRole("button", { name: /Signal: Encrypted messaging/ })[0],
    );
    await screen.findByRole("dialog");
    expect(dispatchBeforeUnload()).toBe(false);
  });

  it("blocks tab close while the create modal has unsaved field edits", async () => {
    render(
      <ScreenWithStateProbe
        onState={() => {
          /* noop */
        }}
      />,
    );

    fireEvent.click(
      screen.getAllByRole("button", { name: /Signal: Encrypted messaging/ })[0],
    );
    const dialog = await screen.findByRole("dialog");
    const textboxes = within(dialog).getAllByRole(
      "textbox",
    ) as HTMLTextAreaElement[];
    fireEvent.change(textboxes[0], { target: { value: "Unsaved principle" } });
    expect(dispatchBeforeUnload()).toBe(true);
  });

  it("blocks tab close while the custom-policy wizard has unsaved edits", async () => {
    render(
      <ScreenWithStateProbe
        onState={() => {
          /* noop */
        }}
      />,
    );

    fireEvent.click(
      screen.getAllByRole("button", { name: /Signal: Encrypted messaging/ })[0],
    );
    const dialog = await screen.findByRole("dialog");
    fireEvent.click(within(dialog).getByRole("button", { name: "More options" }));
    fireEvent.click(screen.getByRole("menuitem", { name: "Customize" }));

    const nameInput = await screen.findByPlaceholderText("Policy name");
    expect(dispatchBeforeUnload()).toBe(false);
    fireEvent.change(nameInput, { target: { value: "Renamed in wizard" } });
    expect(dispatchBeforeUnload()).toBe(true);
  });
});
