import { describe, expect, it, vi, beforeEach, afterEach } from "vitest";
import { renderHook } from "@testing-library/react";

// ---------------------------------------------------------------------------
// Mock useReferenceData so we control exactly which rows are returned.
// The mock must be declared before the module under test is imported.
// ---------------------------------------------------------------------------
const mockUseReferenceData = vi.fn();

vi.mock("~/hooks/useReferenceData.js", () => ({
  useReferenceData: (selection: unknown) => mockUseReferenceData(selection),
}));

const { useWidgetFilterOptions } = await import("./useWidgetFilterOptions.js");

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
function makeReferenceData(overrides: {
  clients?: { id: string; name: string; code: string | null }[];
  countries?: { id: string; name: string; code: string }[];
  roles?: { id: string; name: string }[];
  chapters?: string[];
}) {
  return {
    clients: overrides.clients ?? [],
    countries: overrides.countries ?? [],
    roles: overrides.roles ?? [],
    chapters: overrides.chapters ?? [],
  };
}

// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------
describe("useWidgetFilterOptions", () => {
  beforeEach(() => {
    mockUseReferenceData.mockReset();
    // Default: all empty arrays
    mockUseReferenceData.mockReturnValue(makeReferenceData({}));
  });

  afterEach(() => {
    vi.restoreAllMocks();
  });

  // -------------------------------------------------------------------------
  // Return shape
  // -------------------------------------------------------------------------
  describe("return shape", () => {
    it("returns an object with clients, countries, roles, and chapters keys", () => {
      const { result } = renderHook(() => useWidgetFilterOptions());
      expect(result.current).toHaveProperty("clients");
      expect(result.current).toHaveProperty("countries");
      expect(result.current).toHaveProperty("roles");
      expect(result.current).toHaveProperty("chapters");
    });

    it("all returned values are arrays", () => {
      const { result } = renderHook(() => useWidgetFilterOptions());
      expect(Array.isArray(result.current.clients)).toBe(true);
      expect(Array.isArray(result.current.countries)).toBe(true);
      expect(Array.isArray(result.current.roles)).toBe(true);
      expect(Array.isArray(result.current.chapters)).toBe(true);
    });

    it("returns empty arrays when reference data rows are empty", () => {
      const { result } = renderHook(() => useWidgetFilterOptions());
      expect(result.current.clients).toHaveLength(0);
      expect(result.current.countries).toHaveLength(0);
      expect(result.current.roles).toHaveLength(0);
      expect(result.current.chapters).toHaveLength(0);
    });
  });

  // -------------------------------------------------------------------------
  // clients mapping
  // -------------------------------------------------------------------------
  describe("clients mapping", () => {
    it("maps client rows to { value: id, label: name }", () => {
      mockUseReferenceData.mockReturnValue(
        makeReferenceData({
          clients: [{ id: "c1", name: "Acme Studios", code: "ACME" }],
        }),
      );
      const { result } = renderHook(() => useWidgetFilterOptions({ clients: true }));
      expect(result.current.clients).toEqual([{ value: "c1", label: "Acme Studios" }]);
    });

    it("maps multiple client rows preserving order", () => {
      mockUseReferenceData.mockReturnValue(
        makeReferenceData({
          clients: [
            { id: "c1", name: "Alpha", code: null },
            { id: "c2", name: "Beta", code: null },
            { id: "c3", name: "Gamma", code: "G" },
          ],
        }),
      );
      const { result } = renderHook(() => useWidgetFilterOptions({ clients: true }));
      expect(result.current.clients).toEqual([
        { value: "c1", label: "Alpha" },
        { value: "c2", label: "Beta" },
        { value: "c3", label: "Gamma" },
      ]);
    });

    it("uses client.id as value and client.name as label", () => {
      mockUseReferenceData.mockReturnValue(
        makeReferenceData({
          clients: [{ id: "uuid-123", name: "Test Client", code: null }],
        }),
      );
      const { result } = renderHook(() => useWidgetFilterOptions({ clients: true }));
      const [option] = result.current.clients;
      expect(option!.value).toBe("uuid-123");
      expect(option!.label).toBe("Test Client");
    });

    it("returns empty clients array when no client rows are provided", () => {
      const { result } = renderHook(() => useWidgetFilterOptions({ clients: true }));
      expect(result.current.clients).toHaveLength(0);
    });
  });

  // -------------------------------------------------------------------------
  // countries mapping
  // -------------------------------------------------------------------------
  describe("countries mapping", () => {
    it("maps country rows to { value: id, label: name }", () => {
      mockUseReferenceData.mockReturnValue(
        makeReferenceData({
          countries: [{ id: "de", name: "Germany", code: "DE" }],
        }),
      );
      const { result } = renderHook(() => useWidgetFilterOptions({ countries: true }));
      expect(result.current.countries).toEqual([{ value: "de", label: "Germany" }]);
    });

    it("maps multiple country rows preserving order", () => {
      mockUseReferenceData.mockReturnValue(
        makeReferenceData({
          countries: [
            { id: "at", name: "Austria", code: "AT" },
            { id: "de", name: "Germany", code: "DE" },
          ],
        }),
      );
      const { result } = renderHook(() => useWidgetFilterOptions({ countries: true }));
      expect(result.current.countries).toHaveLength(2);
      expect(result.current.countries[0]!.value).toBe("at");
      expect(result.current.countries[1]!.value).toBe("de");
    });

    it("uses country.id as value and country.name as label", () => {
      mockUseReferenceData.mockReturnValue(
        makeReferenceData({
          countries: [{ id: "fr", name: "France", code: "FR" }],
        }),
      );
      const { result } = renderHook(() => useWidgetFilterOptions({ countries: true }));
      const [option] = result.current.countries;
      expect(option!.value).toBe("fr");
      expect(option!.label).toBe("France");
    });

    it("returns empty countries array when no rows are provided", () => {
      const { result } = renderHook(() => useWidgetFilterOptions({ countries: true }));
      expect(result.current.countries).toHaveLength(0);
    });
  });

  // -------------------------------------------------------------------------
  // roles mapping
  // -------------------------------------------------------------------------
  describe("roles mapping", () => {
    it("maps role rows to { value: id, label: name }", () => {
      mockUseReferenceData.mockReturnValue(
        makeReferenceData({
          roles: [{ id: "r1", name: "CG Supervisor" }],
        }),
      );
      const { result } = renderHook(() => useWidgetFilterOptions({ roles: true }));
      expect(result.current.roles).toEqual([{ value: "r1", label: "CG Supervisor" }]);
    });

    it("maps multiple role rows preserving order", () => {
      mockUseReferenceData.mockReturnValue(
        makeReferenceData({
          roles: [
            { id: "r1", name: "Animator" },
            { id: "r2", name: "Rigger" },
          ],
        }),
      );
      const { result } = renderHook(() => useWidgetFilterOptions({ roles: true }));
      expect(result.current.roles).toHaveLength(2);
      expect(result.current.roles[0]!.label).toBe("Animator");
      expect(result.current.roles[1]!.label).toBe("Rigger");
    });

    it("uses role.id as value and role.name as label", () => {
      mockUseReferenceData.mockReturnValue(
        makeReferenceData({
          roles: [{ id: "uuid-r42", name: "FX TD" }],
        }),
      );
      const { result } = renderHook(() => useWidgetFilterOptions({ roles: true }));
      const [option] = result.current.roles;
      expect(option!.value).toBe("uuid-r42");
      expect(option!.label).toBe("FX TD");
    });

    it("returns empty roles array when no rows are provided", () => {
      const { result } = renderHook(() => useWidgetFilterOptions({ roles: true }));
      expect(result.current.roles).toHaveLength(0);
    });
  });

  // -------------------------------------------------------------------------
  // chapters mapping
  // -------------------------------------------------------------------------
  describe("chapters mapping", () => {
    it("maps chapter strings to { value: chapter, label: chapter }", () => {
      mockUseReferenceData.mockReturnValue(makeReferenceData({ chapters: ["VFX"] }));
      const { result } = renderHook(() => useWidgetFilterOptions({ chapters: true }));
      expect(result.current.chapters).toEqual([{ value: "VFX", label: "VFX" }]);
    });

    it("maps multiple chapter strings preserving order", () => {
      mockUseReferenceData.mockReturnValue(
        makeReferenceData({ chapters: ["Animation", "Rigging", "VFX"] }),
      );
      const { result } = renderHook(() => useWidgetFilterOptions({ chapters: true }));
      expect(result.current.chapters).toHaveLength(3);
      expect(result.current.chapters[0]!.value).toBe("Animation");
      expect(result.current.chapters[0]!.label).toBe("Animation");
      expect(result.current.chapters[2]!.value).toBe("VFX");
    });

    it("uses the chapter string as both value and label", () => {
      mockUseReferenceData.mockReturnValue(makeReferenceData({ chapters: ["Lighting"] }));
      const { result } = renderHook(() => useWidgetFilterOptions({ chapters: true }));
      const [option] = result.current.chapters;
      expect(option!.value).toBe(option!.label);
      expect(option!.value).toBe("Lighting");
    });

    it("returns empty chapters array when no chapters are provided", () => {
      const { result } = renderHook(() => useWidgetFilterOptions({ chapters: true }));
      expect(result.current.chapters).toHaveLength(0);
    });
  });

  // -------------------------------------------------------------------------
  // FilterOption interface shape
  // -------------------------------------------------------------------------
  describe("FilterOption interface", () => {
    it("each option has exactly value and label properties", () => {
      mockUseReferenceData.mockReturnValue(
        makeReferenceData({
          clients: [{ id: "c1", name: "Studio A", code: null }],
        }),
      );
      const { result } = renderHook(() => useWidgetFilterOptions({ clients: true }));
      const option = result.current.clients[0]!;
      expect(Object.keys(option).sort()).toEqual(["label", "value"]);
    });

    it("value and label are both strings", () => {
      mockUseReferenceData.mockReturnValue(
        makeReferenceData({
          roles: [{ id: "r1", name: "Compositor" }],
        }),
      );
      const { result } = renderHook(() => useWidgetFilterOptions({ roles: true }));
      const option = result.current.roles[0]!;
      expect(typeof option.value).toBe("string");
      expect(typeof option.label).toBe("string");
    });
  });

  // -------------------------------------------------------------------------
  // Forwarding the selection argument to useReferenceData
  // -------------------------------------------------------------------------
  describe("selection forwarding", () => {
    it("calls useReferenceData with the provided selection object", () => {
      const selection = { clients: true, roles: true };
      renderHook(() => useWidgetFilterOptions(selection));
      expect(mockUseReferenceData).toHaveBeenCalledWith(selection);
    });

    it("calls useReferenceData with an empty object when no selection is provided", () => {
      renderHook(() => useWidgetFilterOptions());
      expect(mockUseReferenceData).toHaveBeenCalledWith({});
    });

    it("calls useReferenceData with the full selection when all flags are true", () => {
      const selection = { clients: true, countries: true, roles: true, chapters: true };
      renderHook(() => useWidgetFilterOptions(selection));
      expect(mockUseReferenceData).toHaveBeenCalledWith(selection);
    });
  });

  // -------------------------------------------------------------------------
  // Memoization — results are stable when data does not change
  // -------------------------------------------------------------------------
  describe("memoization", () => {
    it("returns the same clients array reference when reference data has not changed", () => {
      const clientRows = [{ id: "c1", name: "Stable", code: null }];
      mockUseReferenceData.mockReturnValue(makeReferenceData({ clients: clientRows }));
      const { result, rerender } = renderHook(() => useWidgetFilterOptions({ clients: true }));
      const first = result.current.clients;
      rerender();
      expect(result.current.clients).toBe(first);
    });

    it("returns the same chapters array reference when chapters have not changed", () => {
      const chapters = ["VFX", "Animation"];
      mockUseReferenceData.mockReturnValue(makeReferenceData({ chapters }));
      const { result, rerender } = renderHook(() => useWidgetFilterOptions({ chapters: true }));
      const first = result.current.chapters;
      rerender();
      expect(result.current.chapters).toBe(first);
    });
  });
});
