/**
 * Unit tests for the Object Visibility Membrane.
 *
 * Run: bun test shared/membrane.test.ts
 */

import { describe, expect, it } from "bun:test";
import {
	isVisibleTo,
	filterByVisibility,
	filterArrayByVisibility,
	DEFAULT_VISIBILITY,
	type ObjectVisibility,
} from "./membrane";

// ── isVisibleTo ──

describe("isVisibleTo", () => {
	it("treats undefined/null as viewer (open default)", () => {
		expect(isVisibleTo(undefined, "viewer")).toBe(true);
		expect(isVisibleTo(null, "viewer")).toBe(true);
		expect(isVisibleTo(undefined, "member")).toBe(true);
		expect(isVisibleTo(null, "admin")).toBe(true);
	});

	it("viewer visibility is visible to all roles", () => {
		expect(isVisibleTo("viewer", "viewer")).toBe(true);
		expect(isVisibleTo("viewer", "member")).toBe(true);
		expect(isVisibleTo("viewer", "moderator")).toBe(true);
		expect(isVisibleTo("viewer", "admin")).toBe(true);
	});

	it("member visibility is hidden from viewers", () => {
		expect(isVisibleTo("member", "viewer")).toBe(false);
		expect(isVisibleTo("member", "member")).toBe(true);
		expect(isVisibleTo("member", "moderator")).toBe(true);
		expect(isVisibleTo("member", "admin")).toBe(true);
	});

	it("moderator visibility is hidden from viewers and members", () => {
		expect(isVisibleTo("moderator", "viewer")).toBe(false);
		expect(isVisibleTo("moderator", "member")).toBe(false);
		expect(isVisibleTo("moderator", "moderator")).toBe(true);
		expect(isVisibleTo("moderator", "admin")).toBe(true);
	});

	it("admin visibility is only visible to admins", () => {
		expect(isVisibleTo("admin", "viewer")).toBe(false);
		expect(isVisibleTo("admin", "member")).toBe(false);
		expect(isVisibleTo("admin", "moderator")).toBe(false);
		expect(isVisibleTo("admin", "admin")).toBe(true);
	});

	it("unknown roles default to level 0", () => {
		expect(isVisibleTo("viewer", "unknown")).toBe(true); // level 0 >= 0
		expect(isVisibleTo("member", "unknown")).toBe(false); // level 0 < 1
	});
});

// ── filterByVisibility ──

describe("filterByVisibility", () => {
	const items: Record<string, { id: string; visibility?: ObjectVisibility }> = {
		a: { id: "a" },                          // undefined → viewer
		b: { id: "b", visibility: "viewer" },
		c: { id: "c", visibility: "member" },
		d: { id: "d", visibility: "moderator" },
		e: { id: "e", visibility: "admin" },
	};

	it("viewer sees only viewer-level items", () => {
		const result = filterByVisibility(items, "viewer");
		expect(Object.keys(result).sort()).toEqual(["a", "b"]);
	});

	it("member sees viewer + member items", () => {
		const result = filterByVisibility(items, "member");
		expect(Object.keys(result).sort()).toEqual(["a", "b", "c"]);
	});

	it("moderator sees viewer + member + moderator items", () => {
		const result = filterByVisibility(items, "moderator");
		expect(Object.keys(result).sort()).toEqual(["a", "b", "c", "d"]);
	});

	it("admin sees all items", () => {
		const result = filterByVisibility(items, "admin");
		expect(Object.keys(result).sort()).toEqual(["a", "b", "c", "d", "e"]);
	});

	it("handles empty record", () => {
		const result = filterByVisibility({}, "admin");
		expect(Object.keys(result)).toEqual([]);
	});

	it("supports custom getVis accessor", () => {
		const calEvents: Record<string, { id: string; accessVisibility?: string }> = {
			x: { id: "x", accessVisibility: "admin" },
			y: { id: "y" },
		};
		const result = filterByVisibility(calEvents, "viewer", (e) => e.accessVisibility as ObjectVisibility | undefined);
		expect(Object.keys(result)).toEqual(["y"]);
	});
});

// ── filterArrayByVisibility ──

describe("filterArrayByVisibility", () => {
	const items = [
		{ id: "1", visibility: undefined as ObjectVisibility | undefined },
		{ id: "2", visibility: "viewer" as ObjectVisibility },
		{ id: "3", visibility: "member" as ObjectVisibility },
		{ id: "4", visibility: "admin" as ObjectVisibility },
	];

	it("filters array correctly for viewer", () => {
		const result = filterArrayByVisibility(items, "viewer");
		expect(result.map((r) => r.id)).toEqual(["1", "2"]);
	});

	it("filters array correctly for member", () => {
		const result = filterArrayByVisibility(items, "member");
		expect(result.map((r) => r.id)).toEqual(["1", "2", "3"]);
	});

	it("admin sees all", () => {
		const result = filterArrayByVisibility(items, "admin");
		expect(result.map((r) => r.id)).toEqual(["1", "2", "3", "4"]);
	});

	it("handles empty array", () => {
		const result = filterArrayByVisibility([], "admin");
		expect(result).toEqual([]);
	});

	it("supports custom accessor for non-standard field names", () => {
		const events = [
			{ id: "e1", accessVisibility: "moderator" as ObjectVisibility },
			{ id: "e2" },
		];
		const result = filterArrayByVisibility(events, "viewer", (e) => (e as any).accessVisibility);
		expect(result.map((r) => r.id)).toEqual(["e2"]);
	});
});

// ── DEFAULT_VISIBILITY ──

describe("DEFAULT_VISIBILITY", () => {
	it("is viewer", () => {
		expect(DEFAULT_VISIBILITY).toBe("viewer");
	});
});
