import { describe, expect, it, vi } from "vitest";
import { render, screen } from "~/test-utils.js";
import { ProgressRing } from "./ProgressRing.js";

// Helper: grab the progress circle (second circle = the arc, not the track)
function getProgressCircle(container: HTMLElement): SVGCircleElement {
  const circles = container.querySelectorAll("circle");
  // First circle is the track, second is the progress arc
  return circles[1] as SVGCircleElement;
}

function getTrackCircle(container: HTMLElement): SVGCircleElement {
  const circles = container.querySelectorAll("circle");
  return circles[0] as SVGCircleElement;
}

describe("ProgressRing", () => {
  describe("SVG geometry", () => {
    it("renders an SVG element with correct width and height", () => {
      const { container } = render(<ProgressRing value={50} size={60} />);
      const svg = container.querySelector("svg");
      expect(svg).toHaveAttribute("width", "60");
      expect(svg).toHaveAttribute("height", "60");
    });

    it("uses default size of 40 when size is not provided", () => {
      const { container } = render(<ProgressRing value={50} />);
      const svg = container.querySelector("svg");
      expect(svg).toHaveAttribute("width", "40");
      expect(svg).toHaveAttribute("height", "40");
    });

    it("sets the viewBox to match the size", () => {
      const { container } = render(<ProgressRing value={25} size={80} />);
      const svg = container.querySelector("svg");
      expect(svg).toHaveAttribute("viewBox", "0 0 80 80");
    });

    it("positions both circles at the center of the SVG", () => {
      const { container } = render(<ProgressRing value={50} size={40} strokeWidth={4} />);
      const track = getTrackCircle(container);
      const progress = getProgressCircle(container);
      expect(track).toHaveAttribute("cx", "20");
      expect(track).toHaveAttribute("cy", "20");
      expect(progress).toHaveAttribute("cx", "20");
      expect(progress).toHaveAttribute("cy", "20");
    });

    it("calculates the correct radius from size and strokeWidth", () => {
      // radius = (size - strokeWidth) / 2 = (40 - 4) / 2 = 18
      const { container } = render(<ProgressRing value={50} size={40} strokeWidth={4} />);
      const track = getTrackCircle(container);
      expect(track).toHaveAttribute("r", "18");
    });
  });

  describe("progress offset calculation (animated=false)", () => {
    // With animated=false, mounted=true from the start so offset is applied immediately

    it("sets strokeDashoffset to 0 for value=100 (full circle)", () => {
      const { container } = render(
        <ProgressRing value={100} size={40} strokeWidth={4} animated={false} />,
      );
      const progress = getProgressCircle(container);
      const circumference = parseFloat(progress.getAttribute("stroke-dasharray") ?? "0");
      const offset = parseFloat(progress.getAttribute("stroke-dashoffset") ?? "1");
      expect(offset).toBeCloseTo(0, 2);
      expect(circumference).toBeGreaterThan(0);
    });

    it("sets strokeDashoffset equal to circumference for value=0 (empty circle)", () => {
      const { container } = render(
        <ProgressRing value={0} size={40} strokeWidth={4} animated={false} />,
      );
      const progress = getProgressCircle(container);
      const circumference = parseFloat(progress.getAttribute("stroke-dasharray") ?? "0");
      const offset = parseFloat(progress.getAttribute("stroke-dashoffset") ?? "0");
      expect(offset).toBeCloseTo(circumference, 2);
    });

    it("sets strokeDashoffset to half circumference for value=50", () => {
      const { container } = render(
        <ProgressRing value={50} size={40} strokeWidth={4} animated={false} />,
      );
      const progress = getProgressCircle(container);
      const circumference = parseFloat(progress.getAttribute("stroke-dasharray") ?? "0");
      const offset = parseFloat(progress.getAttribute("stroke-dashoffset") ?? "0");
      expect(offset).toBeCloseTo(circumference / 2, 1);
    });
  });

  describe("value clamping", () => {
    it("clamps values below 0 to 0", () => {
      const { container } = render(<ProgressRing value={-10} animated={false} />);
      const progress0 = getProgressCircle(container);
      const { container: container0 } = render(<ProgressRing value={0} animated={false} />);
      const progressAt0 = getProgressCircle(container0);
      expect(progress0.getAttribute("stroke-dashoffset")).toBe(
        progressAt0.getAttribute("stroke-dashoffset"),
      );
    });

    it("clamps values above 100 to 100", () => {
      const { container } = render(<ProgressRing value={150} animated={false} />);
      const progressOver = getProgressCircle(container);
      const { container: container100 } = render(<ProgressRing value={100} animated={false} />);
      const progress100 = getProgressCircle(container100);
      expect(progressOver.getAttribute("stroke-dashoffset")).toBe(
        progress100.getAttribute("stroke-dashoffset"),
      );
    });
  });

  describe("colors", () => {
    it("applies the default blue color to the progress arc", () => {
      const { container } = render(<ProgressRing value={50} animated={false} />);
      const progress = getProgressCircle(container);
      expect(progress.getAttribute("stroke")).toContain("blue-500");
    });

    it("applies a custom color to the progress arc", () => {
      const { container } = render(<ProgressRing value={50} color="#ff0000" animated={false} />);
      const progress = getProgressCircle(container);
      expect(progress).toHaveAttribute("stroke", "#ff0000");
    });

    it("applies the default gray trackColor to the track circle", () => {
      const { container } = render(<ProgressRing value={50} animated={false} />);
      const track = getTrackCircle(container);
      expect(track.getAttribute("stroke")).toContain("gray-200");
    });

    it("applies a custom trackColor to the track circle", () => {
      const { container } = render(
        <ProgressRing value={50} trackColor="#cccccc" animated={false} />,
      );
      const track = getTrackCircle(container);
      expect(track).toHaveAttribute("stroke", "#cccccc");
    });
  });

  describe("children", () => {
    it("renders children overlaid in the center when provided", () => {
      render(
        <ProgressRing value={75} animated={false}>
          <span data-testid="label">75%</span>
        </ProgressRing>,
      );
      expect(screen.getByTestId("label")).toBeInTheDocument();
      expect(screen.getByText("75%")).toBeInTheDocument();
    });

    it("does not render the children overlay when children is not provided", () => {
      const { container } = render(<ProgressRing value={75} animated={false} />);
      // The absolute overlay div for children should be absent
      const overlay = container.querySelector(".absolute.inset-0.flex");
      expect(overlay).not.toBeInTheDocument();
    });
  });

  describe("animation", () => {
    it("sets strokeDashoffset to circumference initially when animated=true (before RAF fires)", () => {
      // In jsdom, requestAnimationFrame is synchronous by default in vitest/jsdom,
      // so mounted transitions to true immediately. We verify the component doesn't throw
      // and renders a valid offset.
      const { container } = render(<ProgressRing value={50} animated={true} />);
      const progress = getProgressCircle(container);
      const offset = progress.getAttribute("stroke-dashoffset");
      expect(offset).not.toBeNull();
      expect(isNaN(parseFloat(offset!))).toBe(false);
    });

    it("disables the CSS transition when animated=false", () => {
      const { container } = render(<ProgressRing value={50} animated={false} />);
      const progress = getProgressCircle(container);
      expect(progress).toHaveStyle({ transition: "none" });
    });
  });

  describe("className prop", () => {
    it("applies additional className to the wrapper div", () => {
      const { container } = render(
        <ProgressRing value={50} animated={false} className="my-ring" />,
      );
      const wrapper = container.firstChild as HTMLElement;
      expect(wrapper.className).toContain("my-ring");
    });
  });

  describe("wrapper dimensions", () => {
    it("sets inline style width and height on the wrapper to match size", () => {
      const { container } = render(<ProgressRing value={50} size={64} animated={false} />);
      const wrapper = container.firstChild as HTMLElement;
      expect(wrapper).toHaveStyle({ width: "64px", height: "64px" });
    });
  });
});
