import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { render, screen, fireEvent, cleanup } from "@testing-library/react";

const { pushMock } = vi.hoisted(() => ({ pushMock: vi.fn() }));
vi.mock("next/navigation", () => ({ useRouter: () => ({ push: pushMock }) }));

import { UnsavedChangesGuard } from "@/components/po/unsaved-changes-guard";

// Each test adds a real <a> to the document so the capture-phase click
// interceptor has something to catch.
const links: HTMLAnchorElement[] = [];
function addLink(href: string) {
  const a = document.createElement("a");
  a.setAttribute("href", href);
  a.textContent = "go";
  document.body.appendChild(a);
  links.push(a);
  return a;
}

beforeEach(() => pushMock.mockClear());
afterEach(() => {
  cleanup();
  links.splice(0).forEach((a) => a.remove());
});

const noop = () => {};

describe("UnsavedChangesGuard", () => {
  it("does not intercept navigation when there are no unsaved changes", () => {
    render(<UnsavedChangesGuard enabled={false} onSaveDraft={noop} saving={false} />);
    const link = addLink("/catalogue/vendors");
    const notCanceled = fireEvent.click(link);
    expect(notCanceled).toBe(true); // default not prevented → navigation proceeds
    expect(screen.queryByText("Unsaved changes")).not.toBeInTheDocument();
  });

  it("intercepts an internal link click and opens the prompt when dirty", () => {
    render(<UnsavedChangesGuard enabled onSaveDraft={noop} saving={false} />);
    const link = addLink("/catalogue/vendors");
    const notCanceled = fireEvent.click(link);
    expect(notCanceled).toBe(false); // navigation blocked
    expect(pushMock).not.toHaveBeenCalled();
    expect(screen.getByText("Unsaved changes")).toBeInTheDocument();
    expect(screen.getByRole("button", { name: "Save as draft" })).toBeInTheDocument();
    expect(screen.getByRole("button", { name: "Discard changes" })).toBeInTheDocument();
    expect(screen.getByRole("button", { name: "Stay on page" })).toBeInTheDocument();
  });

  it("does not intercept external links (left to the browser's own prompt)", () => {
    render(<UnsavedChangesGuard enabled onSaveDraft={noop} saving={false} />);
    const link = addLink("https://example.com/elsewhere");
    const notCanceled = fireEvent.click(link);
    expect(notCanceled).toBe(true);
    expect(screen.queryByText("Unsaved changes")).not.toBeInTheDocument();
  });

  it("'Stay on page' closes the prompt without navigating", () => {
    render(<UnsavedChangesGuard enabled onSaveDraft={noop} saving={false} />);
    fireEvent.click(addLink("/dashboard"));
    fireEvent.click(screen.getByRole("button", { name: "Stay on page" }));
    expect(screen.queryByText("Unsaved changes")).not.toBeInTheDocument();
    expect(pushMock).not.toHaveBeenCalled();
  });

  it("'Discard changes' navigates to the intended destination", () => {
    render(<UnsavedChangesGuard enabled onSaveDraft={noop} saving={false} />);
    fireEvent.click(addLink("/catalogue/vendors"));
    fireEvent.click(screen.getByRole("button", { name: "Discard changes" }));
    expect(pushMock).toHaveBeenCalledWith("/catalogue/vendors");
  });

  it("'Save as draft' runs the save handler and closes the prompt", () => {
    const onSaveDraft = vi.fn();
    render(<UnsavedChangesGuard enabled onSaveDraft={onSaveDraft} saving={false} />);
    fireEvent.click(addLink("/dashboard"));
    fireEvent.click(screen.getByRole("button", { name: "Save as draft" }));
    expect(onSaveDraft).toHaveBeenCalledTimes(1);
    expect(pushMock).not.toHaveBeenCalled(); // the save action does its own redirect
    expect(screen.queryByText("Unsaved changes")).not.toBeInTheDocument();
  });

  it("arms the browser beforeunload prompt only while dirty", () => {
    const { rerender } = render(<UnsavedChangesGuard enabled onSaveDraft={noop} saving={false} />);
    const dirtyEvt = new Event("beforeunload", { cancelable: true });
    window.dispatchEvent(dirtyEvt);
    expect(dirtyEvt.defaultPrevented).toBe(true);

    rerender(<UnsavedChangesGuard enabled={false} onSaveDraft={noop} saving={false} />);
    const cleanEvt = new Event("beforeunload", { cancelable: true });
    window.dispatchEvent(cleanEvt);
    expect(cleanEvt.defaultPrevented).toBe(false);
  });
});
