/**
 * Menu position calculation tests
 */

import { describe, it, expect, beforeEach } from "vitest";
import { calculateMenuPosition, type MenuPosition } from "./menuPosition";

describe("menu position calculation", () => {
  let mockElement: HTMLElement;

  function createMockElement(rect: any = {}) {
    const element = document.createElement("button");
    const defaultRect = {
      top: 100,
      bottom: 140,
      left: 50,
      right: 150,
      width: 100,
      height: 40,
      ...rect,
    };
    element.getBoundingClientRect = () => defaultRect as any;
    return element;
  }

  beforeEach(() => {
    mockElement = createMockElement();

    // Mock window dimensions
    Object.defineProperty(window, "innerHeight", {
      value: 800,
      writable: true,
    });
    Object.defineProperty(window, "innerWidth", {
      value: 1024,
      writable: true,
    });
  });

  describe("basic positioning", () => {
    it("should return a MenuPosition object", () => {
      const position = calculateMenuPosition(mockElement);

      expect(position).toHaveProperty("x");
      expect(position).toHaveProperty("y");
      expect(position).toHaveProperty("placement");
      expect(typeof position.x).toBe("number");
      expect(typeof position.y).toBe("number");
      expect(["bottom", "top"]).toContain(position.placement);
    });

    it("should use default menu dimensions", () => {
      const position1 = calculateMenuPosition(mockElement);
      const position2 = calculateMenuPosition(mockElement, 160, 120);

      // With default dimensions (160x120), should place below
      expect(position1.placement).toBe("bottom");
    });

    it("should accept custom menu dimensions", () => {
      const position = calculateMenuPosition(mockElement, 200, 150);

      expect(typeof position.x).toBe("number");
      expect(typeof position.y).toBe("number");
    });
  });

  describe("vertical placement", () => {
    it("should place menu below trigger when there is space", () => {
      // Element at top 100, bottom 140, with 800px viewport
      // Space below = 800 - 140 = 660px (plenty for 120px menu)
      const position = calculateMenuPosition(mockElement, 160, 120);

      expect(position.placement).toBe("bottom");
      expect(position.y).toBe(144); // 140 (bottom) + 4 (gap)
    });

    it("should place menu above trigger when no space below", () => {
      // Element at bottom 750, only 50px below (not enough for 120px menu)
      mockElement = createMockElement({
        top: 700,
        bottom: 750,
        left: 50,
        right: 150,
        width: 100,
        height: 50,
      });

      const position = calculateMenuPosition(mockElement, 160, 120);

      expect(position.placement).toBe("top");
      expect(position.y).toBe(576); // 700 - 120 - 4
    });

    it("should fallback to below when space is insufficient both ways", () => {
      // Element in middle with very small viewport
      Object.defineProperty(window, "innerHeight", {
        value: 180,
        writable: true,
      });
      mockElement = createMockElement({
        top: 80,
        bottom: 100,
        left: 50,
        right: 150,
        width: 100,
        height: 20,
      });

      const position = calculateMenuPosition(mockElement, 160, 120);

      // Should prefer bottom even if doesn't fit
      expect(position.placement).toBe("bottom");
    });
  });

  describe("horizontal positioning", () => {
    it("should align menu right edge with button right edge", () => {
      const position = calculateMenuPosition(mockElement, 160, 120);

      // Button right = 150, menu width = 160
      // x should be 150 - 160 = -10, but clamped to 8
      expect(position.x).toBe(8);
    });

    it("should prevent overflow on right edge", () => {
      // Element far to the right
      mockElement = createMockElement({
        top: 100,
        bottom: 140,
        left: 950,
        right: 1050, // Beyond 1024px viewport
        width: 100,
        height: 40,
      });

      const position = calculateMenuPosition(mockElement, 160, 120);

      // Should clamp to viewport width - menu width - margin
      // 1024 - 160 - 8 = 856
      expect(position.x).toBe(856);
    });

    it("should prevent overflow on left edge", () => {
      // Element far to the left
      mockElement = createMockElement({
        top: 100,
        bottom: 140,
        left: 10,
        right: 60,
        width: 50,
        height: 40,
      });

      const position = calculateMenuPosition(mockElement, 160, 120);

      // Should respect left margin of 8px
      expect(position.x).toBeGreaterThanOrEqual(8);
    });

    it("should center menu horizontally when possible", () => {
      // Element in middle of viewport
      mockElement = createMockElement({
        top: 100,
        bottom: 140,
        left: 432,
        right: 592, // Centered at 512px
        width: 160,
        height: 40,
      });

      const position = calculateMenuPosition(mockElement, 160, 120);

      // Menu aligns right with button right (592), so x = 592 - 160 = 432
      expect(position.x).toBe(432);
    });
  });

  describe("edge cases", () => {
    it("should handle very small viewport", () => {
      Object.defineProperty(window, "innerHeight", {
        value: 100,
        writable: true,
      });
      Object.defineProperty(window, "innerWidth", {
        value: 150,
        writable: true,
      });

      const position = calculateMenuPosition(mockElement, 160, 120);

      // With a very small viewport, the position calculation may return negative x
      // This is acceptable as the menu can overflow the viewport in edge cases
      expect(typeof position.x).toBe("number");
      expect(typeof position.y).toBe("number");
      expect(["bottom", "top"]).toContain(position.placement);
    });

    it("should handle element at viewport edges", () => {
      // Element at top-left corner
      mockElement = createMockElement({
        top: 0,
        bottom: 40,
        left: 0,
        right: 40,
        width: 40,
        height: 40,
      });

      const position = calculateMenuPosition(mockElement, 160, 120);

      // Should position below
      expect(position.placement).toBe("bottom");
      // X should be clamped to left margin
      expect(position.x).toBe(8);
    });

    it("should handle large menu dimensions", () => {
      const position = calculateMenuPosition(mockElement, 500, 400);

      expect(typeof position.x).toBe("number");
      expect(typeof position.y).toBe("number");
      expect(position.x).toBeGreaterThanOrEqual(0);
    });
  });

  describe("consistency", () => {
    it("should return consistent results for same input", () => {
      const position1 = calculateMenuPosition(mockElement, 160, 120);
      const position2 = calculateMenuPosition(mockElement, 160, 120);

      expect(position1.x).toBe(position2.x);
      expect(position1.y).toBe(position2.y);
      expect(position1.placement).toBe(position2.placement);
    });

    it("should respect gaps and margins", () => {
      const position = calculateMenuPosition(mockElement, 160, 120);

      // If placed below, y should be at least bottom + 4
      if (position.placement === "bottom") {
        expect(position.y).toBeGreaterThanOrEqual(140 + 4);
      }

      // X should always respect 8px margins
      expect(position.x).toBeGreaterThanOrEqual(8);
    });
  });
});
