/// <reference types="@testing-library/jest-dom" />
import React from "react";
import { Collapse } from "./index";

import { act, render, screen } from "@testing-library/react";

describe("Collapse", () => {
  let resizeCallback: (() => void) | null = null;

  beforeEach(() => {
    resizeCallback = null;
    // Mock scrollHeight since jsdom doesn't compute layout
    Object.defineProperty(HTMLDivElement.prototype, "scrollHeight", {
      configurable: true,
      get() {
        return 200;
      },
    });

    // Mock ResizeObserver to capture the callback
    global.ResizeObserver = jest.fn().mockImplementation(callback => ({
      observe: jest.fn(() => {
        resizeCallback = callback;
      }),
      unobserve: jest.fn(),
      disconnect: jest.fn(),
    }));
  });

  describe("rendering", () => {
    it("renders children content", () => {
      render(
        <Collapse open={true}>
          <p>Collapsed Content</p>
        </Collapse>
      );
      expect(screen.getByText("Collapsed Content")).toBeInTheDocument();
    });

    it("renders children even when closed", () => {
      render(
        <Collapse open={false}>
          <p>Hidden Content</p>
        </Collapse>
      );
      // Content is in DOM but visually hidden via maxHeight/opacity
      expect(screen.getByText("Hidden Content")).toBeInTheDocument();
    });

    it("applies displayName correctly", () => {
      expect(Collapse.displayName).toBe("Collapse");
    });
  });

  describe("open state", () => {
    it("sets maxHeight to scrollHeight when open", () => {
      const { container } = render(
        <Collapse open={true}>
          <p>Content</p>
        </Collapse>
      );
      const wrapper = container.firstChild as HTMLElement;
      expect(wrapper.style.maxHeight).toBe("200px");
    });

    it("sets maxHeight to 0 when closed", () => {
      const { container } = render(
        <Collapse open={false}>
          <p>Content</p>
        </Collapse>
      );
      const wrapper = container.firstChild as HTMLElement;
      expect(wrapper.style.maxHeight).toBe("0");
    });

    it("sets opacity to 1 when open", () => {
      const { container } = render(
        <Collapse open={true}>
          <p>Content</p>
        </Collapse>
      );
      const wrapper = container.firstChild as HTMLElement;
      expect(wrapper.style.opacity).toBe("1");
    });

    it("sets opacity to 0 when closed", () => {
      const { container } = render(
        <Collapse open={false}>
          <p>Content</p>
        </Collapse>
      );
      const wrapper = container.firstChild as HTMLElement;
      expect(wrapper.style.opacity).toBe("0");
    });
  });

  describe("accessibility", () => {
    it("sets aria-hidden to false when open", () => {
      const { container } = render(
        <Collapse open={true}>
          <p>Content</p>
        </Collapse>
      );
      const wrapper = container.firstChild as HTMLElement;
      expect(wrapper).toHaveAttribute("aria-hidden", "false");
    });

    it("sets aria-hidden to true when closed", () => {
      const { container } = render(
        <Collapse open={false}>
          <p>Content</p>
        </Collapse>
      );
      const wrapper = container.firstChild as HTMLElement;
      expect(wrapper).toHaveAttribute("aria-hidden", "true");
    });
  });

  describe("transition classes", () => {
    it("applies transition classes to the outer wrapper", () => {
      const { container } = render(
        <Collapse open={true}>
          <p>Content</p>
        </Collapse>
      );
      const wrapper = container.firstChild as HTMLElement;
      expect(wrapper).toHaveClass("overflow-hidden");
      expect(wrapper).toHaveClass("transition-all");
      expect(wrapper).toHaveClass("duration-300");
      expect(wrapper).toHaveClass("ease-in-out");
    });
  });

  describe("toggling open/closed", () => {
    it("updates maxHeight when open prop changes to true", () => {
      const { container, rerender } = render(
        <Collapse open={false}>
          <p>Content</p>
        </Collapse>
      );
      const wrapper = container.firstChild as HTMLElement;
      expect(wrapper.style.maxHeight).toBe("0");

      rerender(
        <Collapse open={true}>
          <p>Content</p>
        </Collapse>
      );
      expect(wrapper.style.maxHeight).toBe("200px");
    });

    it("updates maxHeight when open prop changes to false", () => {
      const { container, rerender } = render(
        <Collapse open={true}>
          <p>Content</p>
        </Collapse>
      );
      const wrapper = container.firstChild as HTMLElement;
      expect(wrapper.style.maxHeight).toBe("200px");

      rerender(
        <Collapse open={false}>
          <p>Content</p>
        </Collapse>
      );
      expect(wrapper.style.maxHeight).toBe("0");
    });

    it("updates opacity when toggling", () => {
      const { container, rerender } = render(
        <Collapse open={false}>
          <p>Content</p>
        </Collapse>
      );
      const wrapper = container.firstChild as HTMLElement;
      expect(wrapper.style.opacity).toBe("0");

      rerender(
        <Collapse open={true}>
          <p>Content</p>
        </Collapse>
      );
      expect(wrapper.style.opacity).toBe("1");
    });
  });

  describe("ResizeObserver", () => {
    it("creates ResizeObserver when open", () => {
      render(
        <Collapse open={true}>
          <p>Content</p>
        </Collapse>
      );
      expect(global.ResizeObserver).toHaveBeenCalled();
    });

    it("does not create ResizeObserver when closed", () => {
      render(
        <Collapse open={false}>
          <p>Content</p>
        </Collapse>
      );
      // ResizeObserver constructor is called but observe is not invoked
      // when closed, no observer is created
      const instances = (global.ResizeObserver as jest.Mock).mock.results;
      if (instances.length > 0) {
        const observeMock = instances[0].value.observe;
        expect(observeMock).not.toHaveBeenCalled();
      }
    });

    it("disconnects ResizeObserver on unmount", () => {
      const { unmount } = render(
        <Collapse open={true}>
          <p>Content</p>
        </Collapse>
      );
      const instances = (global.ResizeObserver as jest.Mock).mock.results;
      const disconnectMock = instances[instances.length - 1].value.disconnect;
      unmount();
      expect(disconnectMock).toHaveBeenCalled();
    });

    it("invokes updateHeight via ResizeObserver callback", () => {
      const { container } = render(
        <Collapse open={true}>
          <p>Content</p>
        </Collapse>
      );

      // Change scrollHeight to simulate resize
      Object.defineProperty(HTMLDivElement.prototype, "scrollHeight", {
        configurable: true,
        get() {
          return 500;
        },
      });

      // Trigger the ResizeObserver callback within act
      act(() => {
        if (resizeCallback) {
          resizeCallback();
        }
      });

      const wrapper = container.firstChild as HTMLElement;
      expect(wrapper.style.maxHeight).toBe("500px");
    });
  });

  describe("dynamic children", () => {
    it("re-evaluates height when children change", () => {
      const { rerender, container } = render(
        <Collapse open={true}>
          <p>Short</p>
        </Collapse>
      );

      Object.defineProperty(HTMLDivElement.prototype, "scrollHeight", {
        configurable: true,
        get() {
          return 400;
        },
      });

      rerender(
        <Collapse open={true}>
          <p>Short</p>
          <p>More content that makes it taller</p>
        </Collapse>
      );

      const wrapper = container.firstChild as HTMLElement;
      expect(wrapper.style.maxHeight).toBe("400px");
    });
  });
});
