/* @vitest-environment jsdom */

import { act } from "react";
import { createRoot, type Root } from "react-dom/client";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";

import { PrivateAlbumSchema } from "@/data/schemas/private-zone";

import { PrivateAlbumGallery } from "../private-album-gallery";

const album = PrivateAlbumSchema.parse({
  albumId: "album-1",
  title: "Private afternoon",
  imageCount: 5,
  images: [
    { url: "/images/private-zone/photo-0.png", locked: false, index: 0 },
    { url: "", locked: false, index: 1 },
    { url: "/images/private-zone/locked.png", locked: true, index: 2 },
    { url: "/images/private-zone/photo-3.png", locked: false, index: 3 },
    { url: "/images/private-zone/photo-4.png", locked: false, index: 4 },
  ],
  locked: false,
  unlocked: true,
  lockDetail: { locked: false },
});

describe("PrivateAlbumGallery", () => {
  let container: HTMLDivElement;
  let root: Root;
  let onClose: () => void;
  let onImageIndexChange: (index: number) => void;

  beforeEach(() => {
    (globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean })
      .IS_REACT_ACT_ENVIRONMENT = true;
    Object.defineProperties(HTMLElement.prototype, {
      setPointerCapture: {
        configurable: true,
        value: vi.fn(),
      },
      hasPointerCapture: {
        configurable: true,
        value: vi.fn(() => true),
      },
      releasePointerCapture: {
        configurable: true,
        value: vi.fn(),
      },
    });
    onClose = vi.fn();
    onImageIndexChange = vi.fn();
    container = document.createElement("div");
    document.body.append(container);
    root = createRoot(container);
  });

  afterEach(() => {
    act(() => root.unmount());
    container.remove();
  });

  it("follows a drag and switches to the next available source index", () => {
    renderGallery(0);
    const viewport = getViewport();
    Object.defineProperty(viewport, "clientWidth", {
      configurable: true,
      value: 390,
    });

    dispatchPointer(viewport, "pointerdown", 300);
    dispatchPointer(viewport, "pointermove", 200);
    expect(viewport.querySelector<HTMLElement>("[aria-hidden=false]")?.style.getPropertyValue("--gallery-drag-offset")).toBe("-100px");
    dispatchPointer(viewport, "pointerup", 200);

    expect(onImageIndexChange).toHaveBeenCalledWith(3);
    expect(container.textContent).toContain("2 / 3");
  });

  it("snaps back on pointer cancellation without changing images", () => {
    renderGallery(3);
    const viewport = getViewport();
    Object.defineProperty(viewport, "clientWidth", {
      configurable: true,
      value: 390,
    });

    dispatchPointer(viewport, "pointerdown", 300);
    dispatchPointer(viewport, "pointermove", 200);
    dispatchPointer(viewport, "pointercancel", 200);

    expect(onImageIndexChange).not.toHaveBeenCalled();
    expect(container.textContent).toContain("2 / 3");
  });

  it("uses the same available indexes for controls and keyboard navigation", () => {
    renderGallery(3);

    act(() => {
      container
        .querySelector<HTMLButtonElement>('button[aria-label="Next photo"]')
        ?.click();
    });
    expect(onImageIndexChange).toHaveBeenLastCalledWith(4);

    act(() => {
      document.dispatchEvent(
        new KeyboardEvent("keydown", { key: "ArrowLeft", bubbles: true }),
      );
    });
    expect(onImageIndexChange).toHaveBeenLastCalledWith(3);

    act(() => {
      document.dispatchEvent(
        new KeyboardEvent("keydown", { key: "Escape", bubbles: true }),
      );
    });
    expect(onClose).toHaveBeenCalledOnce();
  });

  function renderGallery(imageIndex: number): void {
    act(() => {
      root.render(
        <PrivateAlbumGallery
          album={album}
          imageIndex={imageIndex}
          onClose={onClose}
          onImageIndexChange={onImageIndexChange}
        />,
      );
    });
  }

  function getViewport(): HTMLDivElement {
    const viewport = container.querySelector<HTMLDivElement>(
      '[data-testid="private-album-gallery-viewport"]',
    );
    if (!viewport) throw new Error("Missing Gallery viewport");
    return viewport;
  }
});

function dispatchPointer(
  target: HTMLElement,
  type: "pointerdown" | "pointermove" | "pointerup" | "pointercancel",
  clientX: number,
): void {
  const event = new MouseEvent(type, {
    bubbles: true,
    button: 0,
    clientX,
  });
  Object.defineProperty(event, "pointerId", { value: 1 });
  act(() => target.dispatchEvent(event));
}
