import { expect, expectTypeOf, test } from "vitest";
import * as z from "zod/v4";

test("type inference", () => {
  const booleanRecord = z.record(z.string(), z.boolean());
  type booleanRecord = typeof booleanRecord._output;

  const recordWithEnumKeys = z.record(z.enum(["Tuna", "Salmon"]), z.string());
  type recordWithEnumKeys = z.infer<typeof recordWithEnumKeys>;

  const recordWithLiteralKey = z.record(z.literal(["Tuna", "Salmon", 21]), z.string());
  type recordWithLiteralKey = z.infer<typeof recordWithLiteralKey>;

  const recordWithLiteralUnionKeys = z.record(
    z.union([z.literal("Tuna"), z.literal("Salmon"), z.literal(21)]),
    z.string()
  );
  type recordWithLiteralUnionKeys = z.infer<typeof recordWithLiteralUnionKeys>;

  enum Enum {
    Tuna = 0,
    Salmon = "Shark",
  }

  const recordWithTypescriptEnum = z.record(z.enum(Enum), z.string());
  type recordWithTypescriptEnum = z.infer<typeof recordWithTypescriptEnum>;

  expectTypeOf<booleanRecord>().toEqualTypeOf<Record<string, boolean>>();
  expectTypeOf<recordWithEnumKeys>().toEqualTypeOf<Record<"Tuna" | "Salmon", string>>();
  expectTypeOf<recordWithLiteralKey>().toEqualTypeOf<Record<"Tuna" | "Salmon" | 21, string>>();
  expectTypeOf<recordWithLiteralUnionKeys>().toEqualTypeOf<Record<"Tuna" | "Salmon" | 21, string>>();
  expectTypeOf<recordWithTypescriptEnum>().toEqualTypeOf<Record<Enum, string>>();
});

test("enum exhaustiveness", () => {
  const schema = z.record(z.enum(["Tuna", "Salmon"]), z.string());
  expect(
    schema.parse({
      Tuna: "asdf",
      Salmon: "asdf",
    })
  ).toEqual({
    Tuna: "asdf",
    Salmon: "asdf",
  });

  expect(schema.safeParse({ Tuna: "asdf", Salmon: "asdf", Trout: "asdf" })).toMatchInlineSnapshot(`
    {
      "error": [ZodError: [
      {
        "code": "unrecognized_keys",
        "keys": [
          "Trout"
        ],
        "path": [],
        "message": "Unrecognized key: \\"Trout\\""
      }
    ]],
      "success": false,
    }
  `);
  expect(schema.safeParse({ Tuna: "asdf" })).toMatchInlineSnapshot(`
    {
      "error": [ZodError: [
      {
        "expected": "string",
        "code": "invalid_type",
        "path": [
          "Salmon"
        ],
        "message": "Invalid input: expected string, received undefined"
      }
    ]],
      "success": false,
    }
  `);
});

test("optional-in value type", () => {
  const defaulted = z.record(z.enum(["Tuna", "Salmon"]), z.string().default("unknown"));
  expectTypeOf<z.input<typeof defaulted>>().toEqualTypeOf<Partial<Record<"Tuna" | "Salmon", string | undefined>>>();
  expectTypeOf<z.output<typeof defaulted>>().toEqualTypeOf<Record<"Tuna" | "Salmon", string>>();
  expect(defaulted.parse({ Tuna: "asdf" })).toEqual({ Tuna: "asdf", Salmon: "unknown" });

  const prefaulted = z.record(z.enum(["Tuna", "Salmon"]), z.string().prefault("unknown"));
  expectTypeOf<z.input<typeof prefaulted>>().toEqualTypeOf<Partial<Record<"Tuna" | "Salmon", string | undefined>>>();
  expect(prefaulted.parse({ Tuna: "asdf" })).toEqual({ Tuna: "asdf", Salmon: "unknown" });

  const optional = z.record(z.enum(["Tuna", "Salmon"]), z.string().optional());
  expectTypeOf<z.input<typeof optional>>().toEqualTypeOf<Partial<Record<"Tuna" | "Salmon", string | undefined>>>();
  // toStrictEqual, not toEqual: an exhaustive record assigns every key, and toEqual cannot tell an absent key from one holding undefined.
  expect(optional.parse({ Tuna: "asdf" })).toStrictEqual({ Tuna: "asdf", Salmon: undefined });

  // A value that needs a slot keeps the key required, and a non-enumerable key stays an index signature either way.
  const required = z.record(z.enum(["Tuna", "Salmon"]), z.string());
  expectTypeOf<z.input<typeof required>>().toEqualTypeOf<Record<"Tuna" | "Salmon", string>>();
  const indexed = z.record(z.string(), z.string().default("unknown"));
  expectTypeOf<z.input<typeof indexed>>().toEqualTypeOf<Record<string, string | undefined>>();

  // A catch and a preprocess pipe declare no static optin, so the key stays required even where the parser can fill it. The static predicate and the one the JSON Schema emitter uses resolve these differently, and the two have to keep agreeing.
  const caught = z.record(z.enum(["Tuna", "Salmon"]), z.string().catch("unknown"));
  expectTypeOf<z.input<typeof caught>>().toEqualTypeOf<Record<"Tuna" | "Salmon", string>>();
  const preprocessed = z.record(
    z.enum(["Tuna", "Salmon"]),
    z.preprocess((v) => v, z.string())
  );
  expectTypeOf<z.input<typeof preprocessed>>().toEqualTypeOf<Record<"Tuna" | "Salmon", unknown>>();
});

test("typescript enum exhaustiveness", () => {
  enum BigFish {
    Tuna = 0,
    Salmon = "Shark",
  }

  const schema = z.record(z.enum(BigFish), z.string());
  const value = {
    [BigFish.Tuna]: "asdf",
    [BigFish.Salmon]: "asdf",
  };

  expect(schema.parse(value)).toEqual(value);

  expect(schema.safeParse({ [BigFish.Tuna]: "asdf", [BigFish.Salmon]: "asdf", Trout: "asdf" })).toMatchInlineSnapshot(`
    {
      "error": [ZodError: [
      {
        "code": "unrecognized_keys",
        "keys": [
          "Trout"
        ],
        "path": [],
        "message": "Unrecognized key: \\"Trout\\""
      }
    ]],
      "success": false,
    }
  `);
  expect(schema.safeParse({ [BigFish.Tuna]: "asdf" })).toMatchInlineSnapshot(`
    {
      "error": [ZodError: [
      {
        "expected": "string",
        "code": "invalid_type",
        "path": [
          "Shark"
        ],
        "message": "Invalid input: expected string, received undefined"
      }
    ]],
      "success": false,
    }
  `);
  expect(schema.safeParse({ [BigFish.Salmon]: "asdf" })).toMatchInlineSnapshot(`
    {
      "error": [ZodError: [
      {
        "expected": "string",
        "code": "invalid_type",
        "path": [
          0
        ],
        "message": "Invalid input: expected string, received undefined"
      }
    ]],
      "success": false,
    }
  `);
});

test("literal exhaustiveness", () => {
  const schema = z.record(z.literal(["Tuna", "Salmon", 21]), z.string());
  schema.parse({
    Tuna: "asdf",
    Salmon: "asdf",
    21: "asdf",
  });

  expect(schema.safeParse({ Tuna: "asdf", Salmon: "asdf", 21: "asdf", Trout: "asdf" })).toMatchInlineSnapshot(`
    {
      "error": [ZodError: [
      {
        "code": "unrecognized_keys",
        "keys": [
          "Trout"
        ],
        "path": [],
        "message": "Unrecognized key: \\"Trout\\""
      }
    ]],
      "success": false,
    }
  `);
  expect(schema.safeParse({ Tuna: "asdf" })).toMatchInlineSnapshot(`
    {
      "error": [ZodError: [
      {
        "expected": "string",
        "code": "invalid_type",
        "path": [
          "Salmon"
        ],
        "message": "Invalid input: expected string, received undefined"
      },
      {
        "expected": "string",
        "code": "invalid_type",
        "path": [
          21
        ],
        "message": "Invalid input: expected string, received undefined"
      }
    ]],
      "success": false,
    }
  `);
});

test("pipe exhaustiveness", () => {
  const schema = z.record(z.enum(["Tuna", "Salmon"]).pipe(z.any()), z.string());
  expect(schema.parse({ Tuna: "asdf", Salmon: "asdf" })).toEqual({
    Tuna: "asdf",
    Salmon: "asdf",
  });

  expect(schema.safeParse({ Tuna: "asdf", Salmon: "asdf", Trout: "asdf" })).toMatchInlineSnapshot(`
    {
      "error": [ZodError: [
      {
        "code": "unrecognized_keys",
        "keys": [
          "Trout"
        ],
        "path": [],
        "message": "Unrecognized key: \\"Trout\\""
      }
    ]],
      "success": false,
    }
  `);
  expect(schema.safeParse({ Tuna: "asdf" })).toMatchInlineSnapshot(`
    {
      "error": [ZodError: [
      {
        "expected": "string",
        "code": "invalid_type",
        "path": [
          "Salmon"
        ],
        "message": "Invalid input: expected string, received undefined"
      }
    ]],
      "success": false,
    }
  `);
});

test("union exhaustiveness", () => {
  const schema = z.record(z.union([z.literal("Tuna"), z.literal("Salmon"), z.literal(21)]), z.string());
  expect(schema.parse({ Tuna: "asdf", Salmon: "asdf", 21: "asdf" })).toEqual({
    Tuna: "asdf",
    Salmon: "asdf",
    21: "asdf",
  });

  expect(schema.safeParse({ Tuna: "asdf", Salmon: "asdf", 21: "asdf", Trout: "asdf" })).toMatchInlineSnapshot(`
    {
      "error": [ZodError: [
      {
        "code": "unrecognized_keys",
        "keys": [
          "Trout"
        ],
        "path": [],
        "message": "Unrecognized key: \\"Trout\\""
      }
    ]],
      "success": false,
    }
  `);
  expect(schema.safeParse({ Tuna: "asdf" })).toMatchInlineSnapshot(`
    {
      "error": [ZodError: [
      {
        "expected": "string",
        "code": "invalid_type",
        "path": [
          "Salmon"
        ],
        "message": "Invalid input: expected string, received undefined"
      },
      {
        "expected": "string",
        "code": "invalid_type",
        "path": [
          21
        ],
        "message": "Invalid input: expected string, received undefined"
      }
    ]],
      "success": false,
    }
  `);
});

test("applies transforms on the key schema (#5296)", () => {
  const single = z.record(
    z.literal("a").transform(() => "b" as const),
    z.string()
  );
  expect(single.parse({ a: "John" })).toEqual({ b: "John" });

  const multi = z.record(
    z.literal(["a", "b"]).transform((k) => k.toUpperCase()),
    z.number()
  );
  expect(multi.parse({ a: 1, b: 2 })).toEqual({ A: 1, B: 2 });

  // required-key semantics still hold when the keyType has a known value set
  expect(multi.safeParse({ a: 1 }).success).toBe(false);

  const en = z.record(
    z.enum(["a", "b"]).transform((k) => k.toUpperCase()),
    z.number()
  );
  expect(en.parse({ a: 1, b: 2 })).toEqual({ A: 1, B: 2 });

  // matches partialRecord, which already applied transforms
  const part = z.partialRecord(
    z.literal("a").transform(() => "b" as const),
    z.string()
  );
  expect(part.parse({ a: "John" })).toEqual({ b: "John" });
});

test("surfaces key schema refinement failures as invalid_key", () => {
  // refine rejects "b" but it's still in the literal's value set
  const schema = z.record(
    z.literal(["a", "b"]).refine((k) => k === "a", { message: "only 'a' is allowed" }),
    z.string()
  );

  expect(schema.safeParse({ a: "ok", b: "nope" })).toMatchInlineSnapshot(`
    {
      "error": [ZodError: [
      {
        "code": "invalid_key",
        "origin": "record",
        "issues": [
          {
            "code": "custom",
            "path": [],
            "message": "only 'a' is allowed"
          }
        ],
        "path": [
          "b"
        ],
        "message": "Invalid key in record"
      }
    ]],
      "success": false,
    }
  `);
});

test("string record parse - pass", () => {
  const schema = z.record(z.string(), z.boolean());
  schema.parse({
    k1: true,
    k2: false,
    1234: false,
  });

  expect(schema.safeParse({ asdf: 1234 }).success).toEqual(false);
  expect(schema.safeParse("asdf")).toMatchInlineSnapshot(`
    {
      "error": [ZodError: [
      {
        "expected": "record",
        "code": "invalid_type",
        "path": [],
        "message": "Invalid input: expected record, received string"
      }
    ]],
      "success": false,
    }
  `);
});

test("key and value getters", () => {
  const rec = z.record(z.string(), z.number());

  rec.keyType.parse("asdf");
  rec.valueType.parse(1234);
});

test("is not vulnerable to prototype pollution", async () => {
  const rec = z.record(
    z.string(),
    z.object({
      a: z.string(),
    })
  );

  const data = JSON.parse(`
    {
      "__proto__": {
        "a": "evil"
      },
      "b": {
        "a": "good"
      }
    }
  `);

  const obj1 = rec.parse(data);
  expect(obj1.a).toBeUndefined();

  const obj2 = rec.safeParse(data);
  expect(obj2.success).toBe(true);
  if (obj2.success) {
    expect(obj2.data.a).toBeUndefined();
  }

  const obj3 = await rec.parseAsync(data);
  expect(obj3.a).toBeUndefined();

  const obj4 = await rec.safeParseAsync(data);
  expect(obj4.success).toBe(true);
  if (obj4.success) {
    expect(obj4.data.a).toBeUndefined();
  }
});

test("key schema cannot normalize an input key into __proto__", async () => {
  const value = z.object({ a: z.string() });
  const payload = { a: "evil" };
  const wire = (key: string) => JSON.parse(JSON.stringify({ [key]: payload }));

  const cases = [
    [z.record(z.string().toLowerCase(), value), wire("__PROTO__")],
    [z.record(z.string().trim(), value), wire(" __proto__ ")],
    [z.record(z.string().normalize("NFKC"), value), wire("＿＿ｐｒｏｔｏ＿＿")],
    [
      z.record(
        z.string().transform((s) => s.slice(2)),
        value
      ),
      wire("x:__proto__"),
    ],
  ] as const;

  for (const [schema, data] of cases) {
    const result = schema.parse(data);
    expect(Object.getPrototypeOf(result)).toBe(Object.prototype);
    expect(Object.keys(result)).toEqual([]);
    expect((result as any).a).toBeUndefined();

    expect(Object.getPrototypeOf(await schema.parseAsync(data))).toBe(Object.prototype);
  }

  // an async value schema takes the promise write path
  const asyncValue = z.record(
    z.string().toLowerCase(),
    value.refine(async () => true)
  );
  expect(Object.getPrototypeOf(await asyncValue.parseAsync(wire("__PROTO__")))).toBe(Object.prototype);

  // ordinary normalization still lands as an own key
  expect(z.record(z.string().toLowerCase(), value).parse({ KEY: payload })).toEqual({ key: payload });
});

test("dont remove undefined values", () => {
  const result1 = z.record(z.string(), z.any()).parse({ foo: undefined });

  expect(result1).toEqual({
    foo: undefined,
  });
});

test("allow undefined values", () => {
  const schema = z.record(z.string(), z.undefined());

  expect(
    Object.keys(
      schema.parse({
        _test: undefined,
      })
    )
  ).toEqual(["_test"]);
});

test("async parsing", async () => {
  const schema = z
    .record(
      z.string(),
      z
        .string()
        .optional()
        .refine(async () => true)
    )
    .refine(async () => true);

  const data = {
    foo: "bar",
    baz: "qux",
  };
  const result = await schema.safeParseAsync(data);
  expect(result.data).toEqual(data);
});

test("async parsing", async () => {
  const schema = z
    .record(
      z.string(),
      z
        .string()
        .optional()
        .refine(async () => false)
    )
    .refine(async () => false);

  const data = {
    foo: "bar",
    baz: "qux",
  };
  const result = await schema.safeParseAsync(data);
  expect(result.success).toEqual(false);
  expect(result.error).toMatchInlineSnapshot(`
    [ZodError: [
      {
        "code": "custom",
        "path": [
          "foo"
        ],
        "message": "Invalid input"
      },
      {
        "code": "custom",
        "path": [
          "baz"
        ],
        "message": "Invalid input"
      },
      {
        "code": "custom",
        "path": [],
        "message": "Invalid input"
      }
    ]]
  `);
});

test("partial record", () => {
  const schema = z.partialRecord(z.string(), z.string());
  type schema = z.infer<typeof schema>;
  expectTypeOf<schema>().toEqualTypeOf<Partial<Record<string, string>>>();

  const Keys = z.enum(["id", "name", "email"]); //.or(z.never());
  const Person = z.partialRecord(Keys, z.string());
  expectTypeOf<z.infer<typeof Person>>().toEqualTypeOf<Partial<Record<"id" | "name" | "email", string>>>();

  Person.parse({
    id: "123",
    // name: "John",
    // email: "john@example.com",
  });

  Person.parse({
    // id: "123",
    // name: "John",
    email: "john@example.com",
  });

  expect(Person.def.keyType._zod.def.type).toEqual("enum");
});

test("partialRecord with z.literal([key, ...])", () => {
  const Keys = z.literal(["id", "name", "email"]);
  const schema = z.partialRecord(Keys, z.string());
  type Schema = z.infer<typeof schema>;
  expectTypeOf<Schema>().toEqualTypeOf<Partial<Record<"id" | "name" | "email", string>>>();

  // Should parse valid partials
  expect(schema.parse({})).toEqual({});
  expect(schema.parse({ id: "1" })).toEqual({ id: "1" });
  expect(schema.parse({ name: "n", email: "e@example.com" })).toEqual({ name: "n", email: "e@example.com" });

  // Should fail with unrecognized key, error checked via inline snapshot
  expect(schema.safeParse({ foo: "bar" })).toMatchInlineSnapshot(`
    {
      "error": [ZodError: [
      {
        "code": "unrecognized_keys",
        "keys": [
          "foo"
        ],
        "path": [],
        "message": "Unrecognized key: \\"foo\\""
      }
    ]],
      "success": false,
    }
  `);
});

test("partialRecord with numeric literal keys", () => {
  const Keys = z.literal([1, 2, 3]);
  const schema = z.partialRecord(Keys, z.string());
  type Schema = z.infer<typeof schema>;
  expectTypeOf<Schema>().toEqualTypeOf<Partial<Record<1 | 2 | 3, string>>>();

  // Should parse valid partials with numeric keys (as strings in JS objects)
  expect(schema.parse({})).toEqual({});
  expect(schema.parse({ 1: "one" })).toEqual({ 1: "one" });
  expect(schema.parse({ 2: "two", 3: "three" })).toEqual({ 2: "two", 3: "three" });

  // Should fail with unrecognized key
  expect(schema.safeParse({ 4: "four" }).success).toBe(false);
});

test("partialRecord with union of string and numeric literal keys", () => {
  const StringKeys = z.literal(["a", "b", "c"]);
  const NumericKeys = z.literal([1, 2, 3]);
  const schema = z.partialRecord(z.union([StringKeys, NumericKeys]), z.string());
  type Schema = z.infer<typeof schema>;
  expectTypeOf<Schema>().toEqualTypeOf<Partial<Record<"a" | "b" | "c" | 1 | 2 | 3, string>>>();

  // Should parse valid partials with mixed keys
  expect(schema.parse({})).toEqual({});
  expect(schema.parse({ a: "1", 2: "4" })).toEqual({ a: "1", 2: "4" });
  expect(schema.parse({ a: "a", b: "b", 1: "1", 2: "2" })).toEqual({ a: "a", b: "b", 1: "1", 2: "2" });

  // Should fail with unrecognized key
  expect(schema.safeParse({ d: "d" }).success).toBe(false);
  expect(schema.safeParse({ 4: "4" }).success).toBe(false);
});

test("looseRecord passes through non-matching keys", () => {
  const schema = z.looseRecord(z.string().regex(/^S_/), z.string());

  // Keys matching pattern are validated
  expect(schema.parse({ S_name: "John" })).toEqual({ S_name: "John" });
  expect(() => schema.parse({ S_name: 123 })).toThrow(); // wrong value type

  // Keys not matching pattern pass through unchanged
  expect(schema.parse({ S_name: "John", other: "value" })).toEqual({ S_name: "John", other: "value" });
  expect(schema.parse({ S_name: "John", count: 123 })).toEqual({ S_name: "John", count: 123 });
  expect(schema.parse({ other: "value" })).toEqual({ other: "value" });
});

test("looseRecord with closed key schema passes through unrecognized keys", () => {
  const enumSchema = z.looseRecord(z.enum(["foo", "bar"]), z.any());
  expect(enumSchema.parse({ foo: 123, bar: {}, baz: null })).toEqual({
    foo: 123,
    bar: {},
    baz: null,
  });

  const literalSchema = z.looseRecord(z.literal(["foo", "bar"]), z.any());
  expect(literalSchema.parse({ foo: 123, bar: {}, baz: null })).toEqual({
    foo: 123,
    bar: {},
    baz: null,
  });

  // Recognized keys are still validated
  const validated = z.looseRecord(z.enum(["foo", "bar"]), z.string());
  expect(validated.parse({ foo: "ok", bar: "ok", baz: 123 })).toEqual({
    foo: "ok",
    bar: "ok",
    baz: 123,
  });
  expect(() => validated.parse({ foo: 123 })).toThrow();
});

test("record with closed key schema still rejects unrecognized keys", () => {
  const schema = z.record(z.enum(["foo", "bar"]), z.any());
  expect(schema.safeParse({ foo: 123, bar: {}, baz: null }).success).toBe(false);
});

// __proto__ in input must not replace the prototype of the parsed object via the assignment setter on the result {}. https://github.com/colinhacks/zod/security/advisories/GHSA-r34p-xfmx-58wv
test("looseRecord with closed key schema drops __proto__", () => {
  const schema = z.looseRecord(z.enum(["foo", "bar"]), z.any());
  const parsed = schema.parse(JSON.parse('{"foo":1,"bar":2,"__proto__":{"isAdmin":true}}'));
  expect(Object.keys(parsed)).toEqual(["foo", "bar"]);
  expect((parsed as any).isAdmin).toBeUndefined();
  expect(Object.getPrototypeOf(parsed)).toBe(Object.prototype);
});

test("intersection of loose records", () => {
  const schema = z.intersection(
    z.object({ name: z.string() }).passthrough(),
    z.intersection(
      z.looseRecord(z.string().regex(/^S_/), z.string()),
      z.looseRecord(z.string().regex(/^N_/), z.number())
    )
  );

  // Each pattern validates its matching keys
  const result = schema.parse({ name: "John", S_foo: "bar", N_count: 123 });
  expect(result.name).toBe("John");
  expect(result.S_foo).toBe("bar");
  expect(result.N_count).toBe(123);

  // Keys not matching any pattern pass through
  const result2 = schema.parse({ name: "John", S_foo: "bar", N_count: 123, other: "value" });
  expect(result2.other).toBe("value");

  // Validation errors still occur for matching keys
  expect(() => schema.parse({ name: "John", S_foo: 123 })).toThrow(); // S_foo should be string
  expect(() => schema.parse({ name: "John", N_count: "abc" })).toThrow(); // N_count should be number
});

test("object with looseRecord index signature", () => {
  // Simulates TypeScript index signature: { label: string; [key: `label:${string}`]: string }
  const schema = z.object({ label: z.string() }).and(z.looseRecord(z.string().regex(/^label:[a-z]{2}$/), z.string()));

  type Schema = z.infer<typeof schema>;
  expectTypeOf<Schema>().toEqualTypeOf<{ label: string } & Record<string, string>>();

  // Valid: has required property and matching pattern keys
  expect(schema.parse({ label: "Purple", "label:en": "Purple", "label:ru": "Пурпурный" })).toEqual({
    label: "Purple",
    "label:en": "Purple",
    "label:ru": "Пурпурный",
  });

  // Valid: just required property
  expect(schema.parse({ label: "Purple" })).toEqual({ label: "Purple" });

  // Invalid: missing required property
  expect(schema.safeParse({ "label:en": "Purple" })).toMatchInlineSnapshot(`
    {
      "error": [ZodError: [
      {
        "expected": "string",
        "code": "invalid_type",
        "path": [
          "label"
        ],
        "message": "Invalid input: expected string, received undefined"
      }
    ]],
      "success": false,
    }
  `);

  // Invalid: pattern key with wrong value type
  expect(schema.safeParse({ label: "Purple", "label:en": 123 })).toMatchInlineSnapshot(`
    {
      "error": [ZodError: [
      {
        "expected": "string",
        "code": "invalid_type",
        "path": [
          "label:en"
        ],
        "message": "Invalid input: expected string, received number"
      }
    ]],
      "success": false,
    }
  `);
});

test("numeric string keys", () => {
  const schema = z.record(z.number(), z.number());

  // Numeric string keys work
  expect(schema.parse({ 1: 100, 2: 200 })).toEqual({ 1: 100, 2: 200 });
  expect(schema.parse({ "1.5": 100, "-3": 200 })).toEqual({ "1.5": 100, "-3": 200 });

  // Non-numeric keys fail
  expect(schema.safeParse({ abc: 100 }).success).toBe(false);

  // Integer constraint is respected
  const intSchema = z.record(z.number().int(), z.number());
  expect(intSchema.parse({ 1: 100 })).toEqual({ 1: 100 });
  expect(intSchema.safeParse({ "1.5": 100 }).success).toBe(false);

  // Transforms on numeric keys work
  const transformedSchema = z.record(
    z.number().overwrite((n) => n * 2),
    z.string()
  );
  expect(transformedSchema.parse({ 5: "five", 10: "ten" })).toEqual({ 10: "five", 20: "ten" });
});

test("v3-compat single-arg form: z.record(valueType)", () => {
  // single arg should default keyType to z.string() and use the arg as valueType
  const schema = (z.record as any)(z.number());
  expect(schema.keyType._zod.def.type).toEqual("string");
  expect(schema.valueType._zod.def.type).toEqual("number");

  expect(schema.parse({ a: 1, b: 2 })).toEqual({ a: 1, b: 2 });
  expect(schema.safeParse({ a: "x" }).success).toBe(false);

  // params still flow through in the single-arg form
  const withMessage = (z.record as any)(z.number(), "must be a number record");
  expect(withMessage.keyType._zod.def.type).toEqual("string");
  expect(withMessage.valueType._zod.def.type).toEqual("number");

  // toJSONSchema should produce a well-formed schema (regression: previously produced additionalProperties from undefined valueType, crashing process())
  const json = z.toJSONSchema(schema);
  expect(json).toMatchObject({
    type: "object",
    propertyNames: { type: "string" },
    additionalProperties: { type: "number" },
  });
});

test("__proto__ in a finite key set is stripped", () => {
  const data = JSON.parse('{"__proto__":{"a":"declared"},"b":{"a":"good"}}');

  for (const schema of [
    z.record(z.enum(["__proto__", "b"]), z.object({ a: z.string() })),
    z.record(z.literal(["__proto__", "b"]), z.object({ a: z.string() })),
  ]) {
    const parsed: any = schema.parse(data);
    expect(Object.getPrototypeOf(parsed)).toBe(Object.prototype);
    expect(Object.prototype.hasOwnProperty.call(parsed, "__proto__")).toBe(false);
    expect(Object.keys(parsed)).toEqual(["b"]);
    expect(parsed.a).toBeUndefined();
  }

  expect(({} as any).a).toBeUndefined();
});

test("a raw __proto__ input key stays skipped in loose mode", () => {
  const parsed: any = z.looseRecord(z.iso.datetime(), z.unknown()).parse(JSON.parse('{"__proto__":{"a":1}}'));

  expect(Object.getPrototypeOf(parsed)).toBe(Object.prototype);
  expect(Object.prototype.hasOwnProperty.call(parsed, "__proto__")).toBe(false);
});

test("partialRecord strips a declared __proto__ key", () => {
  const schema = z.partialRecord(z.enum(["__proto__", "b"]), z.string());
  const parsed: any = schema.parse(Object.fromEntries([["__proto__", "declared"]]));

  expect(Object.getPrototypeOf(parsed)).toBe(Object.prototype);
  expect(Object.prototype.hasOwnProperty.call(parsed, "__proto__")).toBe(false);
  expect(parsed).toEqual({});
  expect(schema.parse({})).toEqual({});
  expect(schema.safeParse({ other: "x" }).success).toBe(false);
});

test("partialRecord strips a declared __proto__ key in loose mode", () => {
  const key = z.enum(["__proto__"]).refine(() => false);
  const schema = z.partialRecord(key, z.unknown(), { mode: "loose" });
  const value = { marker: true };
  const parsed: any = schema.parse(Object.fromEntries([["__proto__", value]]));

  expect(Object.getPrototypeOf(parsed)).toBe(Object.prototype);
  expect(Object.prototype.hasOwnProperty.call(parsed, "__proto__")).toBe(false);
  expect(parsed).toEqual({});
  expect(parsed.marker).toBeUndefined();
});

test("partial is internal to partialRecord", () => {
  // @ts-expect-error partial is not a public record parameter
  z.record(z.enum(["a"]), z.string(), { partial: true });

  const schema = z.partialRecord(z.enum(["a"]), z.string(), { partial: false } as any);
  expect(schema.parse({})).toEqual({});
});

test("partialRecord strips a declared __proto__ key before transforms", () => {
  const schema = z.partialRecord(
    z.literal("__proto__").transform(() => "safe" as const),
    z.string()
  );

  expect(schema.parse(Object.fromEntries([["__proto__", "value"]]))).toEqual({});
});

test("a finite __proto__ key is ignored whether present or missing", () => {
  const schema = z.record(z.enum(["__proto__"]), z.string());
  const parsed: any = schema.parse({});

  expect(Object.getPrototypeOf(parsed)).toBe(Object.prototype);
  expect(Object.prototype.hasOwnProperty.call(parsed, "__proto__")).toBe(false);
  expect(schema.parse(Object.fromEntries([["__proto__", 123]]))).toEqual({});
});
