import { describe, expect, test } from "vitest";
import { z } from "zod";
import { getNestedSchema, getNestedValue, setNestedValue } from "./utils";

describe("getNestedValue", () => {
	test("returns value at simple path", () => {
		expect(getNestedValue({ name: "test" }, "name")).toBe("test");
	});

	test("returns value at nested path", () => {
		expect(getNestedValue({ config: { name: "test" } }, "config.name")).toBe(
			"test",
		);
	});

	test("returns undefined for missing path", () => {
		expect(getNestedValue({ config: {} }, "config.name")).toBe(undefined);
	});

	test("returns undefined when parent is null", () => {
		expect(getNestedValue({ config: null }, "config.name")).toBe(undefined);
	});

	test("handles deeply nested paths", () => {
		const obj = { a: { b: { c: { d: "deep" } } } };
		expect(getNestedValue(obj, "a.b.c.d")).toBe("deep");
	});
});

describe("setNestedValue", () => {
	test("sets value at simple path", () => {
		expect(setNestedValue({}, "name", "test")).toEqual({ name: "test" });
	});

	test("sets value at nested path", () => {
		expect(setNestedValue({}, "config.name", "test")).toEqual({
			config: { name: "test" },
		});
	});

	test("preserves existing sibling values", () => {
		const obj = { config: { existing: "keep" } };
		expect(setNestedValue(obj, "config.name", "test")).toEqual({
			config: { existing: "keep", name: "test" },
		});
	});

	test("is immutable - does not modify original", () => {
		const obj = { config: { name: "old" } };
		setNestedValue(obj, "config.name", "new");
		expect(obj.config.name).toBe("old");
	});

	test("handles deeply nested paths", () => {
		expect(setNestedValue({}, "a.b.c.d", "deep")).toEqual({
			a: { b: { c: { d: "deep" } } },
		});
	});
});

describe("getNestedSchema", () => {
	test("returns schema for simple path", () => {
		const schema = z.object({ name: z.string() });
		const result = getNestedSchema(schema, "name");
		expect(result).toBeInstanceOf(z.ZodString);
	});

	test("returns schema for nested path", () => {
		const schema = z.object({ config: z.object({ name: z.string() }) });
		const result = getNestedSchema(schema, "config.name");
		expect(result).toBeInstanceOf(z.ZodString);
	});

	test("unwraps nullable wrapper", () => {
		const schema = z.object({
			config: z.object({ name: z.string() }).nullable(),
		});
		const result = getNestedSchema(schema, "config.name");
		const def = result?._def ?? (result as unknown as { def?: unknown })?.def;
		const typeName =
			(def as { typeName?: string })?.typeName ??
			(def as { type?: string })?.type;
		expect(typeName).toBe("string");
	});

	test("unwraps optional wrapper", () => {
		const schema = z.object({
			config: z.object({ name: z.string() }).optional(),
		});
		const result = getNestedSchema(schema, "config.name");
		const def = result?._def ?? (result as unknown as { def?: unknown })?.def;
		const typeName =
			(def as { typeName?: string })?.typeName ??
			(def as { type?: string })?.type;
		expect(typeName).toBe("string");
	});

	test("returns undefined for invalid path", () => {
		const schema = z.object({ name: z.string() });
		expect(getNestedSchema(schema, "missing.path")).toBe(undefined);
	});

	test("returns undefined when path goes through non-object", () => {
		const schema = z.object({ name: z.string() });
		expect(getNestedSchema(schema, "name.invalid")).toBe(undefined);
	});

	test("returns schema for array element path", () => {
		const schema = z.object({
			items: z.array(z.object({ name: z.string() })),
		});
		const result = getNestedSchema(schema, "items[0].name");
		expect(result).toBeInstanceOf(z.ZodString);
	});

	test("returns schema for array element path with min/max", () => {
		const schema = z.object({
			items: z
				.array(z.object({ name: z.string() }))
				.min(1)
				.max(10),
		});
		const result = getNestedSchema(schema, "items[0].name");
		expect(result).toBeInstanceOf(z.ZodString);
	});
});
