import { describe, expect, it, vi, beforeEach } from "vitest";
import { render, screen, fireEvent } from "~/test-utils.js";
import userEvent from "@testing-library/user-event";
import { AnimatedModal } from "./AnimatedModal.js";

// Mock framer-motion so animations resolve immediately in jsdom
vi.mock("framer-motion", () => ({
  motion: {
    div: ({
      children,
      ...props
    }: React.HTMLAttributes<HTMLDivElement> & { children?: React.ReactNode }) => (
      <div {...props}>{children}</div>
    ),
  },
  AnimatePresence: ({ children }: { children: React.ReactNode }) => <>{children}</>,
}));

// Mock useFocusTrap – focus management is tested separately
vi.mock("~/hooks/useFocusTrap.js", () => ({
  useFocusTrap: vi.fn(),
}));

const defaultProps = {
  open: true,
  onClose: vi.fn(),
  children: <p>Modal content</p>,
};

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

  describe("visibility", () => {
    it("renders children when open is true", () => {
      render(<AnimatedModal {...defaultProps} />);
      expect(screen.getByText("Modal content")).toBeInTheDocument();
    });

    it("renders nothing when open is false", () => {
      render(<AnimatedModal {...defaultProps} open={false} />);
      expect(screen.queryByText("Modal content")).not.toBeInTheDocument();
    });
  });

  describe("ARIA semantics", () => {
    it("renders a dialog element with aria-modal", () => {
      render(<AnimatedModal {...defaultProps} />);
      const dialog = screen.getByRole("dialog");
      expect(dialog).toBeInTheDocument();
      expect(dialog).toHaveAttribute("aria-modal", "true");
    });

    it("sets aria-labelledby when ariaLabelledBy prop is provided", () => {
      render(<AnimatedModal {...defaultProps} ariaLabelledBy="modal-title" />);
      const dialog = screen.getByRole("dialog");
      expect(dialog).toHaveAttribute("aria-labelledby", "modal-title");
    });

    it("does not set aria-labelledby when ariaLabelledBy is omitted", () => {
      render(<AnimatedModal {...defaultProps} />);
      const dialog = screen.getByRole("dialog");
      expect(dialog).not.toHaveAttribute("aria-labelledby");
    });
  });

  describe("backdrop close", () => {
    it("calls onClose when the overlay is clicked (default)", () => {
      const onClose = vi.fn();
      const { container } = render(<AnimatedModal {...defaultProps} onClose={onClose} />);
      // The overlay is the first div inside the outer container (aria-hidden)
      const overlay = container.querySelector('[aria-hidden="true"]') as HTMLElement;
      expect(overlay).toBeTruthy();
      fireEvent.click(overlay);
      expect(onClose).toHaveBeenCalledTimes(1);
    });

    it("does NOT call onClose when disableBackdropClose is true", () => {
      const onClose = vi.fn();
      const { container } = render(
        <AnimatedModal {...defaultProps} onClose={onClose} disableBackdropClose />,
      );
      const overlay = container.querySelector('[aria-hidden="true"]') as HTMLElement;
      fireEvent.click(overlay);
      expect(onClose).not.toHaveBeenCalled();
    });
  });

  describe("keyboard – Escape", () => {
    it("calls onClose when Escape is pressed while open", async () => {
      const user = userEvent.setup();
      const onClose = vi.fn();
      render(<AnimatedModal {...defaultProps} onClose={onClose} />);
      await user.keyboard("{Escape}");
      expect(onClose).toHaveBeenCalledTimes(1);
    });

    it("does NOT call onClose for other keys", async () => {
      const user = userEvent.setup();
      const onClose = vi.fn();
      render(<AnimatedModal {...defaultProps} onClose={onClose} />);
      await user.keyboard("{Enter}");
      expect(onClose).not.toHaveBeenCalled();
    });

    it("does NOT register the keydown listener when closed", async () => {
      const user = userEvent.setup();
      const onClose = vi.fn();
      render(<AnimatedModal {...defaultProps} open={false} onClose={onClose} />);
      await user.keyboard("{Escape}");
      expect(onClose).not.toHaveBeenCalled();
    });
  });

  describe("className props", () => {
    it("applies custom maxWidth class to the panel", () => {
      render(<AnimatedModal {...defaultProps} maxWidth="max-w-2xl" />);
      const dialog = screen.getByRole("dialog");
      expect(dialog.className).toContain("max-w-2xl");
    });

    it("uses max-w-xl as the default maxWidth", () => {
      render(<AnimatedModal {...defaultProps} />);
      const dialog = screen.getByRole("dialog");
      expect(dialog.className).toContain("max-w-xl");
    });

    it("applies additional className to the panel", () => {
      render(<AnimatedModal {...defaultProps} className="my-custom-class" />);
      const dialog = screen.getByRole("dialog");
      expect(dialog.className).toContain("my-custom-class");
    });

    it("applies custom overlayClassName when provided", () => {
      const { container } = render(
        <AnimatedModal {...defaultProps} overlayClassName="custom-overlay" />,
      );
      const overlay = container.querySelector('[aria-hidden="true"]') as HTMLElement;
      expect(overlay.className).toContain("custom-overlay");
    });

    it("falls back to default overlay classes when overlayClassName is omitted", () => {
      const { container } = render(<AnimatedModal {...defaultProps} />);
      const overlay = container.querySelector('[aria-hidden="true"]') as HTMLElement;
      expect(overlay.className).toContain("bg-black/40");
    });
  });

  describe("children", () => {
    it("renders arbitrary children inside the dialog panel", () => {
      render(
        <AnimatedModal {...defaultProps}>
          <button>Submit</button>
        </AnimatedModal>,
      );
      expect(screen.getByRole("button", { name: /submit/i })).toBeInTheDocument();
    });
  });
});
