import { Validator } from "@seriousme/openapi-schema-validator";
import { describe, expect, test } from "vitest";
import * as z from "zod";
// import * as zCore from "zod/v4/core";

const openAPI30Validator = new Validator();
/** @see https://github.com/colinhacks/zod/issues/5147 */
const validateOpenAPI30Schema = async (zodJSONSchema: Record<string, unknown>): Promise<true> => {
  const res = await openAPI30Validator.validate({
    openapi: "3.0.0",
    info: {
      title: "SampleApi",
      description: "Sample backend service",
      version: "1.0.0",
    },
    components: { schemas: { test: zodJSONSchema } },
    paths: {},
  });

  if (!res.valid) {
    // `console.error` should make `vitest` trow an unhandled error
    // printing the validation messages in consoles
    console.error(
      `OpenAPI schema is not valid against ${openAPI30Validator.version}`,
      JSON.stringify(res.errors, null, 2)
    );
  }

  return true;
};

describe("toJSONSchema", () => {
  test("primitive types", () => {
    expect(z.toJSONSchema(z.string())).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "string",
      }
    `);
    expect(z.toJSONSchema(z.number())).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "number",
      }
    `);
    expect(z.toJSONSchema(z.boolean())).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "boolean",
      }
    `);
    expect(z.toJSONSchema(z.null())).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "null",
      }
    `);
    expect(z.toJSONSchema(z.undefined(), { unrepresentable: "any" })).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
      }
    `);
    expect(z.toJSONSchema(z.any())).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
      }
    `);
    expect(z.toJSONSchema(z.unknown())).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
      }
    `);
    expect(z.toJSONSchema(z.never())).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "not": {},
      }
    `);
    expect(z.toJSONSchema(z.email())).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "format": "email",
        "pattern": "^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$",
        "type": "string",
      }
    `);
    expect(z.toJSONSchema(z.iso.datetime())).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "format": "date-time",
        "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
        "type": "string",
      }
    `);
    expect(z.toJSONSchema(z.iso.date())).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "format": "date",
        "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$",
        "type": "string",
      }
    `);
    expect(z.toJSONSchema(z.iso.time())).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "pattern": "^(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?$",
        "type": "string",
      }
    `);
    expect(z.toJSONSchema(z.iso.time({ precision: -1 }))).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "pattern": "^(?:[01]\\d|2[0-3]):[0-5]\\d$",
        "type": "string",
      }
    `);
    expect(z.toJSONSchema(z.iso.time({ precision: 0 }))).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "pattern": "^(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d$",
        "type": "string",
      }
    `);
    expect(z.toJSONSchema(z.iso.time({ precision: 3 }))).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "pattern": "^(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d\\.\\d{3}$",
        "type": "string",
      }
    `);
    expect(z.toJSONSchema(z.iso.duration())).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "format": "duration",
        "pattern": "^P(?:(\\d+W)|(?!.*W)(?=\\d|T\\d)(\\d+Y)?(\\d+M)?(\\d+D)?(T(?=\\d)(\\d+H)?(\\d+M)?(\\d+([.,]\\d+)?S)?)?)$",
        "type": "string",
      }
    `);
    expect(z.toJSONSchema(z.ipv4())).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "format": "ipv4",
        "pattern": "^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])$",
        "type": "string",
      }
    `);
    expect(z.toJSONSchema(z.ipv6())).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "format": "ipv6",
        "pattern": "^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))$",
        "type": "string",
      }
    `);
    expect(z.toJSONSchema(z.mac())).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "format": "mac",
        "pattern": "^(?:[0-9A-F]{2}:){5}[0-9A-F]{2}$|^(?:[0-9a-f]{2}:){5}[0-9a-f]{2}$",
        "type": "string",
      }
    `);
    expect(z.toJSONSchema(z.mac({ delimiter: ":" }))).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "format": "mac",
        "pattern": "^(?:[0-9A-F]{2}:){5}[0-9A-F]{2}$|^(?:[0-9a-f]{2}:){5}[0-9a-f]{2}$",
        "type": "string",
      }
    `);
    expect(z.toJSONSchema(z.mac({ delimiter: "-" }))).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "format": "mac",
        "pattern": "^(?:[0-9A-F]{2}-){5}[0-9A-F]{2}$|^(?:[0-9a-f]{2}-){5}[0-9a-f]{2}$",
        "type": "string",
      }
    `);
    expect(z.toJSONSchema(z.uuid())).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "format": "uuid",
        "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
        "type": "string",
      }
    `);
    expect(z.toJSONSchema(z.guid())).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "format": "uuid",
        "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$",
        "type": "string",
      }
    `);
    expect(z.toJSONSchema(z.url())).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "format": "uri",
        "type": "string",
      }
    `);
    expect(z.toJSONSchema(z.base64())).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "contentEncoding": "base64",
        "format": "base64",
        "pattern": "^$|^(?:[0-9a-zA-Z+/]{4})*(?:(?:[0-9a-zA-Z+/]{2}==)|(?:[0-9a-zA-Z+/]{3}=))?$",
        "type": "string",
      }
    `);
    expect(z.toJSONSchema(z.cuid())).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "format": "cuid",
        "pattern": "^[cC][^\\s-]{8,}$",
        "type": "string",
      }
    `);
    // expect(z.toJSONSchema(z.regex(/asdf/))).toMatchInlineSnapshot();
    expect(z.toJSONSchema(z.emoji())).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "format": "emoji",
        "pattern": "^(\\p{Extended_Pictographic}|\\p{Emoji_Component})+$",
        "type": "string",
      }
    `);
    expect(z.toJSONSchema(z.nanoid())).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "format": "nanoid",
        "pattern": "^[a-zA-Z0-9_-]{21}$",
        "type": "string",
      }
    `);
    expect(z.toJSONSchema(z.cuid2())).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "format": "cuid2",
        "pattern": "^[0-9a-z]+$",
        "type": "string",
      }
    `);
    expect(z.toJSONSchema(z.ulid())).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "format": "ulid",
        "pattern": "^[0-9A-HJKMNP-TV-Za-hjkmnp-tv-z]{26}$",
        "type": "string",
      }
    `);
    // expect(z.toJSONSchema(z.cidr())).toMatchInlineSnapshot();
    expect(z.toJSONSchema(z.number())).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "number",
      }
    `);
    expect(z.toJSONSchema(z.int())).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "maximum": 9007199254740991,
        "minimum": -9007199254740991,
        "type": "integer",
      }
    `);
    expect(z.toJSONSchema(z.int32())).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "maximum": 2147483647,
        "minimum": -2147483648,
        "type": "integer",
      }
    `);
    expect(z.toJSONSchema(z.float32())).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "maximum": 3.4028234663852886e+38,
        "minimum": -3.4028234663852886e+38,
        "type": "number",
      }
    `);
    expect(z.toJSONSchema(z.float64())).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "maximum": 1.7976931348623157e+308,
        "minimum": -1.7976931348623157e+308,
        "type": "number",
      }
    `);
    expect(z.toJSONSchema(z.jwt())).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "format": "jwt",
        "type": "string",
      }
    `);
  });

  test("unsupported schema types", () => {
    expect(() => z.toJSONSchema(z.bigint())).toThrow("BigInt cannot be represented in JSON Schema");
    expect(() => z.toJSONSchema(z.int64())).toThrow("BigInt cannot be represented in JSON Schema");
    expect(() => z.toJSONSchema(z.symbol())).toThrow("Symbols cannot be represented in JSON Schema");
    expect(() => z.toJSONSchema(z.void())).toThrow("Void cannot be represented in JSON Schema");
    expect(() => z.toJSONSchema(z.undefined())).toThrow("Undefined cannot be represented in JSON Schema");
    expect(() => z.toJSONSchema(z.date())).toThrow("Date cannot be represented in JSON Schema");
    expect(() => z.toJSONSchema(z.map(z.string(), z.number()))).toThrow("Map cannot be represented in JSON Schema");
    expect(() => z.toJSONSchema(z.set(z.string()))).toThrow("Set cannot be represented in JSON Schema");
    expect(() => z.toJSONSchema(z.custom(() => true))).toThrow("Custom types cannot be represented in JSON Schema");

    // Transform
    const transformSchema = z.string().transform((val) => Number.parseInt(val));
    expect(() => z.toJSONSchema(transformSchema)).toThrow("Transforms cannot be represented in JSON Schema");

    // Static catch values
    const staticCatchSchema = z.string().catch(() => "sup");
    expect(z.toJSONSchema(staticCatchSchema)).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "default": "sup",
        "type": "string",
      }
    `);

    // Dynamic catch values
    const dynamicCatchSchema = z.string().catch((ctx) => `${ctx.issues.length}`);
    expect(() => z.toJSONSchema(dynamicCatchSchema)).toThrow("Dynamic catch values are not supported in JSON Schema");
  });

  test("string formats", () => {
    expect(z.toJSONSchema(z.string().email())).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "format": "email",
        "pattern": "^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$",
        "type": "string",
      }
    `);
    expect(z.toJSONSchema(z.string().uuid())).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "format": "uuid",
        "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
        "type": "string",
      }
    `);
    expect(z.toJSONSchema(z.iso.datetime())).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "format": "date-time",
        "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
        "type": "string",
      }
    `);

    expect(z.toJSONSchema(z.iso.date())).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "format": "date",
        "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$",
        "type": "string",
      }
    `);
    expect(z.toJSONSchema(z.iso.time())).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "pattern": "^(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?$",
        "type": "string",
      }
    `);
    expect(z.toJSONSchema(z.iso.duration())).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "format": "duration",
        "pattern": "^P(?:(\\d+W)|(?!.*W)(?=\\d|T\\d)(\\d+Y)?(\\d+M)?(\\d+D)?(T(?=\\d)(\\d+H)?(\\d+M)?(\\d+([.,]\\d+)?S)?)?)$",
        "type": "string",
      }
    `);
    // expect(z.toJSONSchema(z.string().ip())).toMatchInlineSnapshot(`
    //   {
    //     "pattern": /\\(\\^\\(\\?:\\(\\?:25\\[0-5\\]\\|2\\[0-4\\]\\[0-9\\]\\|1\\[0-9\\]\\[0-9\\]\\|\\[1-9\\]\\[0-9\\]\\|\\[0-9\\]\\)\\\\\\.\\)\\{3\\}\\(\\?:25\\[0-5\\]\\|2\\[0-4\\]\\[0-9\\]\\|1\\[0-9\\]\\[0-9\\]\\|\\[1-9\\]\\[0-9\\]\\|\\[0-9\\]\\)\\$\\)\\|\\(\\^\\(\\(\\[a-fA-F0-9\\]\\{1,4\\}:\\)\\{7\\}\\|::\\(\\[a-fA-F0-9\\]\\{1,4\\}:\\)\\{0,6\\}\\|\\(\\[a-fA-F0-9\\]\\{1,4\\}:\\)\\{1\\}:\\(\\[a-fA-F0-9\\]\\{1,4\\}:\\)\\{0,5\\}\\|\\(\\[a-fA-F0-9\\]\\{1,4\\}:\\)\\{2\\}:\\(\\[a-fA-F0-9\\]\\{1,4\\}:\\)\\{0,4\\}\\|\\(\\[a-fA-F0-9\\]\\{1,4\\}:\\)\\{3\\}:\\(\\[a-fA-F0-9\\]\\{1,4\\}:\\)\\{0,3\\}\\|\\(\\[a-fA-F0-9\\]\\{1,4\\}:\\)\\{4\\}:\\(\\[a-fA-F0-9\\]\\{1,4\\}:\\)\\{0,2\\}\\|\\(\\[a-fA-F0-9\\]\\{1,4\\}:\\)\\{5\\}:\\(\\[a-fA-F0-9\\]\\{1,4\\}:\\)\\{0,1\\}\\)\\(\\[a-fA-F0-9\\]\\{1,4\\}\\|\\(\\(\\(25\\[0-5\\]\\)\\|\\(2\\[0-4\\]\\[0-9\\]\\)\\|\\(1\\[0-9\\]\\{2\\}\\)\\|\\(\\[0-9\\]\\{1,2\\}\\)\\)\\\\\\.\\)\\{3\\}\\(\\(25\\[0-5\\]\\)\\|\\(2\\[0-4\\]\\[0-9\\]\\)\\|\\(1\\[0-9\\]\\{2\\}\\)\\|\\(\\[0-9\\]\\{1,2\\}\\)\\)\\)\\$\\)/,
    //     "type": "string",
    //   }
    // `);
    expect(z.toJSONSchema(z.ipv4())).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "format": "ipv4",
        "pattern": "^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])$",
        "type": "string",
      }
    `);

    expect(z.toJSONSchema(z.ipv6())).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "format": "ipv6",
        "pattern": "^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))$",
        "type": "string",
      }
    `);

    expect(z.toJSONSchema(z.mac())).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "format": "mac",
        "pattern": "^(?:[0-9A-F]{2}:){5}[0-9A-F]{2}$|^(?:[0-9a-f]{2}:){5}[0-9a-f]{2}$",
        "type": "string",
      }
    `);
    expect(z.toJSONSchema(z.mac({ delimiter: ":" }))).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "format": "mac",
        "pattern": "^(?:[0-9A-F]{2}:){5}[0-9A-F]{2}$|^(?:[0-9a-f]{2}:){5}[0-9a-f]{2}$",
        "type": "string",
      }
    `);
    expect(z.toJSONSchema(z.mac({ delimiter: "-" }))).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "format": "mac",
        "pattern": "^(?:[0-9A-F]{2}-){5}[0-9A-F]{2}$|^(?:[0-9a-f]{2}-){5}[0-9a-f]{2}$",
        "type": "string",
      }
    `);

    expect(z.toJSONSchema(z.base64())).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "contentEncoding": "base64",
        "format": "base64",
        "pattern": "^$|^(?:[0-9a-zA-Z+/]{4})*(?:(?:[0-9a-zA-Z+/]{2}==)|(?:[0-9a-zA-Z+/]{3}=))?$",
        "type": "string",
      }
    `);
    expect(z.toJSONSchema(z.url())).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "format": "uri",
        "type": "string",
      }
    `);
    expect(z.toJSONSchema(z.guid())).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "format": "uuid",
        "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$",
        "type": "string",
      }
    `);
    expect(z.toJSONSchema(z.string().regex(/asdf/))).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "pattern": "asdf",
        "type": "string",
      }
    `);
  });

  test("string patterns", () => {
    expect(
      z.toJSONSchema(
        z
          .string()
          .startsWith("hello")
          .includes("cruel")
          .includes("dark", { position: 10 })
          .endsWith("world")
          .regex(/stuff/)
      )
    ).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "allOf": [
          {
            "pattern": "^hello.*",
          },
          {
            "pattern": "cruel",
          },
          {
            "pattern": "^.{10}dark",
          },
          {
            "pattern": ".*world$",
          },
          {
            "pattern": "stuff",
          },
        ],
        "type": "string",
      }
    `);

    expect(
      z.toJSONSchema(
        z
          .string()
          .startsWith("hello")
          .includes("cruel")
          .includes("dark", { position: 10 })
          .endsWith("world")
          .regex(/stuff/),
        {
          target: "draft-7",
        }
      )
    ).toMatchInlineSnapshot(`
      {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "allOf": [
          {
            "pattern": "^hello.*",
            "type": "string",
          },
          {
            "pattern": "cruel",
            "type": "string",
          },
          {
            "pattern": "^.{10}dark",
            "type": "string",
          },
          {
            "pattern": ".*world$",
            "type": "string",
          },
          {
            "pattern": "stuff",
            "type": "string",
          },
        ],
        "type": "string",
      }
    `);
  });

  test("number constraints", () => {
    expect(z.toJSONSchema(z.number().min(5).max(10))).toMatchInlineSnapshot(
      `
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "maximum": 10,
        "minimum": 5,
        "type": "number",
      }
    `
    );

    expect(z.toJSONSchema(z.number().gt(5).gt(10))).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "exclusiveMinimum": 10,
        "type": "number",
      }
    `);

    expect(z.toJSONSchema(z.number().gt(5).gte(10))).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "minimum": 10,
        "type": "number",
      }
    `);

    expect(z.toJSONSchema(z.number().lt(5).lt(3))).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "exclusiveMaximum": 3,
        "type": "number",
      }
    `);

    expect(z.toJSONSchema(z.number().lt(5).lt(3).lte(2))).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "maximum": 2,
        "type": "number",
      }
    `);

    expect(z.toJSONSchema(z.number().lt(5).lte(3))).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "maximum": 3,
        "type": "number",
      }
    `);

    expect(z.toJSONSchema(z.number().gt(5).lt(10))).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "exclusiveMaximum": 10,
        "exclusiveMinimum": 5,
        "type": "number",
      }
    `);
    expect(z.toJSONSchema(z.number().gte(5).lte(10))).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "maximum": 10,
        "minimum": 5,
        "type": "number",
      }
    `);
    expect(z.toJSONSchema(z.number().positive())).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "exclusiveMinimum": 0,
        "type": "number",
      }
    `);
    expect(z.toJSONSchema(z.number().negative())).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "exclusiveMaximum": 0,
        "type": "number",
      }
    `);
    expect(z.toJSONSchema(z.number().nonpositive())).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "maximum": 0,
        "type": "number",
      }
    `);
    expect(z.toJSONSchema(z.number().nonnegative())).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "minimum": 0,
        "type": "number",
      }
    `);
  });

  test("number constraints draft-4", () => {
    expect(z.toJSONSchema(z.number().gt(5).lt(10), { target: "draft-4" })).toMatchInlineSnapshot(`
      {
        "$schema": "http://json-schema.org/draft-04/schema#",
        "exclusiveMaximum": true,
        "exclusiveMinimum": true,
        "maximum": 10,
        "minimum": 5,
        "type": "number",
      }
    `);
  });

  test("target normalization draft-04 and draft-07", () => {
    // Test that both old (draft-4, draft-7) and new (draft-04, draft-07) target formats work
    // Test draft-04 / draft-4
    expect(z.toJSONSchema(z.number().gt(5), { target: "draft-04" })).toMatchInlineSnapshot(`
      {
        "$schema": "http://json-schema.org/draft-04/schema#",
        "exclusiveMinimum": true,
        "minimum": 5,
        "type": "number",
      }
    `);
    expect(z.toJSONSchema(z.number().gt(5), { target: "draft-4" })).toMatchInlineSnapshot(`
      {
        "$schema": "http://json-schema.org/draft-04/schema#",
        "exclusiveMinimum": true,
        "minimum": 5,
        "type": "number",
      }
    `);
    // Test draft-07 / draft-7
    expect(z.toJSONSchema(z.number().gt(5), { target: "draft-07" })).toMatchInlineSnapshot(`
      {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "exclusiveMinimum": 5,
        "type": "number",
      }
    `);
    expect(z.toJSONSchema(z.number().gt(5), { target: "draft-7" })).toMatchInlineSnapshot(`
      {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "exclusiveMinimum": 5,
        "type": "number",
      }
    `);
  });

  test("nullable openapi-3.0", () => {
    const schema = z.string().nullable();
    const jsonSchema = z.toJSONSchema(schema, { target: "openapi-3.0" });
    validateOpenAPI30Schema(jsonSchema);
    expect(jsonSchema).toMatchInlineSnapshot(`
      {
        "nullable": true,
        "type": "string",
      }
    `);
  });

  test("union with null openapi-3.0", () => {
    const schema = z.union([z.string(), z.null()]);
    const jsonSchema = z.toJSONSchema(schema, { target: "openapi-3.0" });
    validateOpenAPI30Schema(jsonSchema);
    expect(jsonSchema).toMatchInlineSnapshot(`
      {
        "anyOf": [
          {
            "type": "string",
          },
          {
            "enum": [
              null,
            ],
            "nullable": true,
            "type": "string",
          },
        ],
      }
    `);
  });

  test("number with exclusive min-max openapi-3.0", () => {
    const schema = z.number().lt(100).gt(1);
    const jsonSchema = z.toJSONSchema(schema, { target: "openapi-3.0" });
    validateOpenAPI30Schema(jsonSchema);
    expect(jsonSchema).toMatchInlineSnapshot(`
      {
        "exclusiveMaximum": true,
        "exclusiveMinimum": true,
        "maximum": 100,
        "minimum": 1,
        "type": "number",
      }
    `);
  });

  test("arrays", () => {
    expect(z.toJSONSchema(z.array(z.string()))).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "items": {
          "type": "string",
        },
        "type": "array",
      }
    `);
  });

  test("unions", () => {
    const schema = z.union([z.string(), z.number()]);
    expect(z.toJSONSchema(schema)).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "anyOf": [
          {
            "type": "string",
          },
          {
            "type": "number",
          },
        ],
      }
    `);
  });

  test("discriminated unions", () => {
    const schema = z.discriminatedUnion("type", [
      z.object({ type: z.literal("success"), data: z.string() }),
      z.object({ type: z.literal("error"), message: z.string() }),
    ]);
    expect(z.toJSONSchema(schema)).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "oneOf": [
          {
            "additionalProperties": false,
            "properties": {
              "data": {
                "type": "string",
              },
              "type": {
                "const": "success",
                "type": "string",
              },
            },
            "required": [
              "type",
              "data",
            ],
            "type": "object",
          },
          {
            "additionalProperties": false,
            "properties": {
              "message": {
                "type": "string",
              },
              "type": {
                "const": "error",
                "type": "string",
              },
            },
            "required": [
              "type",
              "message",
            ],
            "type": "object",
          },
        ],
      }
    `);
  });

  test("intersections", () => {
    const schema = z.intersection(z.object({ name: z.string() }), z.object({ age: z.number() }));

    expect(z.toJSONSchema(schema)).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "allOf": [
          {
            "additionalProperties": false,
            "properties": {
              "name": {
                "type": "string",
              },
            },
            "required": [
              "name",
            ],
            "type": "object",
          },
          {
            "additionalProperties": false,
            "properties": {
              "age": {
                "type": "number",
              },
            },
            "required": [
              "age",
            ],
            "type": "object",
          },
        ],
      }
    `);
  });

  test("record", () => {
    const schema = z.record(z.string(), z.boolean());
    expect(z.toJSONSchema(schema)).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "additionalProperties": {
          "type": "boolean",
        },
        "propertyNames": {
          "type": "string",
        },
        "type": "object",
      }
    `);
  });

  test("record openapi-3.0", () => {
    const schema = z.record(z.string(), z.boolean());
    const jsonSchema = z.toJSONSchema(schema, { target: "openapi-3.0" });
    validateOpenAPI30Schema(jsonSchema);
    expect(jsonSchema).toMatchInlineSnapshot(`
      {
        "additionalProperties": {
          "type": "boolean",
        },
        "type": "object",
      }
    `);
  });

  test("record with enum keys adds required", () => {
    const schema = z.record(z.enum(["key1", "key2"]), z.number());

    expect(z.toJSONSchema(schema)).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "additionalProperties": {
          "type": "number",
        },
        "propertyNames": {
          "enum": [
            "key1",
            "key2",
          ],
          "type": "string",
        },
        "required": [
          "key1",
          "key2",
        ],
        "type": "object",
      }
    `);
  });

  test("record filters enum values to strings and numbers for required", () => {
    enum NumberEnum {
      Zero = 0,
      One = 1,
    }
    const schema = z.record(z.enum(NumberEnum), z.string());

    expect(z.toJSONSchema(schema)).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "additionalProperties": {
          "type": "string",
        },
        "propertyNames": {
          "enum": [
            0,
            1,
          ],
          "type": "number",
        },
        "required": [
          0,
          1,
        ],
        "type": "object",
      }
    `);
  });

  test("strict record with regex key uses propertyNames", () => {
    const schema = z.record(z.string().regex(/^label:[a-z]{2}$/), z.string());

    expect(z.toJSONSchema(schema)).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "additionalProperties": {
          "type": "string",
        },
        "propertyNames": {
          "pattern": "^label:[a-z]{2}$",
          "type": "string",
        },
        "type": "object",
      }
    `);
  });

  test("looseRecord with regex key uses patternProperties", () => {
    const schema = z.looseRecord(z.string().regex(/^label:[a-z]{2}$/), z.string());

    expect(z.toJSONSchema(schema)).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "patternProperties": {
          "^label:[a-z]{2}$": {
            "type": "string",
          },
        },
        "type": "object",
      }
    `);
  });

  test("looseRecord with multiple regex patterns uses patternProperties", () => {
    const schema = z.looseRecord(
      z
        .string()
        .regex(/^prefix_/)
        .regex(/_suffix$/),
      z.number()
    );

    expect(z.toJSONSchema(schema)).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "patternProperties": {
          "^prefix_": {
            "type": "number",
          },
          "_suffix$": {
            "type": "number",
          },
        },
        "type": "object",
      }
    `);
  });

  test("looseRecord without regex key uses propertyNames", () => {
    // looseRecord with plain string key should still use propertyNames
    const schema = z.looseRecord(z.string(), z.boolean());

    expect(z.toJSONSchema(schema)).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "additionalProperties": {
          "type": "boolean",
        },
        "propertyNames": {
          "type": "string",
        },
        "type": "object",
      }
    `);
  });

  test("intersection of object with looseRecord uses patternProperties", () => {
    const zLabeled = z.object({ label: z.string() });
    const zLocalizedLabeled = z.looseRecord(z.string().regex(/^label:[a-z]{2}$/), z.string());
    const schema = zLabeled.and(zLocalizedLabeled);

    expect(z.toJSONSchema(schema)).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "allOf": [
          {
            "additionalProperties": false,
            "properties": {
              "label": {
                "type": "string",
              },
            },
            "required": [
              "label",
            ],
            "type": "object",
          },
          {
            "patternProperties": {
              "^label:[a-z]{2}$": {
                "type": "string",
              },
            },
            "type": "object",
          },
        ],
      }
    `);
  });

  test("tuple", () => {
    const schema = z.tuple([z.string(), z.number()]);
    expect(z.toJSONSchema(schema)).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "prefixItems": [
          {
            "type": "string",
          },
          {
            "type": "number",
          },
        ],
        "type": "array",
      }
    `);
  });

  test("tuple with rest", () => {
    const schema = z.tuple([z.string(), z.number()]).rest(z.boolean());
    expect(z.toJSONSchema(schema)).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "items": {
          "type": "boolean",
        },
        "prefixItems": [
          {
            "type": "string",
          },
          {
            "type": "number",
          },
        ],
        "type": "array",
      }
    `);
  });

  test("tuple openapi-3.0", () => {
    const schema = z.tuple([z.string(), z.number()]);
    const jsonSchema = z.toJSONSchema(schema, { target: "openapi-3.0" });
    validateOpenAPI30Schema(jsonSchema);
    expect(jsonSchema).toMatchInlineSnapshot(`
      {
        "items": {
          "anyOf": [
            {
              "type": "string",
            },
            {
              "type": "number",
            },
          ],
        },
        "maxItems": 2,
        "minItems": 2,
        "type": "array",
      }
    `);
  });

  test("tuple with rest openapi-3.0", () => {
    const schema = z.tuple([z.string(), z.number()]).rest(z.boolean());
    const jsonSchema = z.toJSONSchema(schema, { target: "openapi-3.0" });
    validateOpenAPI30Schema(jsonSchema);
    expect(jsonSchema).toMatchInlineSnapshot(`
      {
        "items": {
          "anyOf": [
            {
              "type": "string",
            },
            {
              "type": "number",
            },
            {
              "type": "boolean",
            },
          ],
        },
        "minItems": 3,
        "type": "array",
      }
    `);
  });

  test("tuple with null openapi-3.0", () => {
    const schema = z.tuple([z.string(), z.number(), z.null()]);
    const jsonSchema = z.toJSONSchema(schema, { target: "openapi-3.0" });
    validateOpenAPI30Schema(jsonSchema);
    expect(jsonSchema).toMatchInlineSnapshot(`
      {
        "items": {
          "anyOf": [
            {
              "type": "string",
            },
            {
              "type": "number",
            },
            {
              "enum": [
                null,
              ],
              "nullable": true,
              "type": "string",
            },
          ],
        },
        "maxItems": 3,
        "minItems": 3,
        "type": "array",
      }
    `);
  });

  test("tuple draft-7", () => {
    const schema = z.tuple([z.string(), z.number()]);
    expect(z.toJSONSchema(schema, { target: "draft-7", io: "input" })).toMatchInlineSnapshot(`
      {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "items": [
          {
            "type": "string",
          },
          {
            "type": "number",
          },
        ],
        "type": "array",
      }
    `);
  });

  test("tuple with rest draft-7", () => {
    const schema = z.tuple([z.string(), z.number()]).rest(z.boolean());
    expect(z.toJSONSchema(schema, { target: "draft-7", io: "input" })).toMatchInlineSnapshot(`
      {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "additionalItems": {
          "type": "boolean",
        },
        "items": [
          {
            "type": "string",
          },
          {
            "type": "number",
          },
        ],
        "type": "array",
      }
    `);
  });

  test("tuple with rest draft-7 - issue #5151 regression test", () => {
    // This test addresses issue #5151: tuple with rest elements and ids
    // in draft-7 had incorrect internal path handling affecting complex scenarios
    const primarySchema = z.string().meta({ id: "primary" });
    const restSchema = z.number().meta({ id: "rest" });
    const testSchema = z.tuple([primarySchema], restSchema);

    // Test both final output structure AND internal path handling
    const capturedPaths: string[] = [];
    const result = z.toJSONSchema(testSchema, {
      target: "draft-7",
      override: (ctx) => capturedPaths.push(ctx.path.join("/")),
    });

    // Verify correct draft-7 structure with metadata extraction
    expect(result).toMatchInlineSnapshot(`
      {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "additionalItems": {
          "$ref": "#/definitions/rest",
        },
        "definitions": {
          "primary": {
            "id": "primary",
            "type": "string",
          },
          "rest": {
            "id": "rest",
            "type": "number",
          },
        },
        "items": [
          {
            "$ref": "#/definitions/primary",
          },
        ],
        "type": "array",
      }
    `);

    // Verify internal paths are correct (this was the actual bug)
    expect(capturedPaths).toContain("items/0"); // prefix items should use "items" path
    expect(capturedPaths).toContain("additionalItems"); // rest should use "additionalItems" path
    expect(capturedPaths).not.toContain("prefixItems/0"); // should not use draft-2020-12 paths

    // Structural validations
    expect(Array.isArray(result.items)).toBe(true);
    expect(result.additionalItems).toBeDefined();
  });

  test("promise", () => {
    const schema = z.promise(z.string());
    expect(z.toJSONSchema(schema)).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "string",
      }
    `);
  });

  test("lazy", () => {
    const schema = z.lazy(() => z.string());
    expect(z.toJSONSchema(schema)).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "string",
      }
    `);
  });

  // enum
  test("enum", () => {
    const a = z.enum(["a", "b", "c"]);
    expect(z.toJSONSchema(a)).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "enum": [
          "a",
          "b",
          "c",
        ],
        "type": "string",
      }
    `);

    enum B {
      A = 0,
      B = 1,
      C = 2,
    }

    const b = z.enum(B);
    expect(z.toJSONSchema(b)).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "enum": [
          0,
          1,
          2,
        ],
        "type": "number",
      }
    `);
  });

  // literal
  test("literal", () => {
    const a = z.literal("hello");
    expect(z.toJSONSchema(a)).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "const": "hello",
        "type": "string",
      }
    `);

    const b = z.literal(7);
    expect(z.toJSONSchema(b)).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "const": 7,
        "type": "number",
      }
    `);

    const c = z.literal(["hello", undefined, null, 5, BigInt(1324)]);
    expect(() => z.toJSONSchema(c)).toThrow();

    const d = z.literal(["hello", null, 5]);
    expect(z.toJSONSchema(d)).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "enum": [
          "hello",
          null,
          5,
        ],
      }
    `);

    const e = z.literal(["hello", "zod", "v4"]);
    expect(z.toJSONSchema(e)).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "enum": [
          "hello",
          "zod",
          "v4",
        ],
        "type": "string",
      }
    `);
  });

  test("literal draft-4", () => {
    const a = z.literal("hello");
    expect(z.toJSONSchema(a, { target: "draft-4" })).toMatchInlineSnapshot(`
      {
        "$schema": "http://json-schema.org/draft-04/schema#",
        "enum": [
          "hello",
        ],
        "type": "string",
      }
    `);
  });

  // pipe
  test("pipe", () => {
    const schema = z
      .string()
      .transform((val) => Number.parseInt(val))
      .pipe(z.number());
    expect(z.toJSONSchema(schema)).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "number",
      }
    `);
  });

  test("simple objects", () => {
    const schema = z.object({
      name: z.string(),
      age: z.number(),
    });

    expect(z.toJSONSchema(schema)).toMatchInlineSnapshot(
      `
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "additionalProperties": false,
        "properties": {
          "age": {
            "type": "number",
          },
          "name": {
            "type": "string",
          },
        },
        "required": [
          "name",
          "age",
        ],
        "type": "object",
      }
    `
    );
  });

  test("additionalproperties in z.object", () => {
    const a = z.object({
      name: z.string(),
    });
    expect(z.toJSONSchema(a)).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "additionalProperties": false,
        "properties": {
          "name": {
            "type": "string",
          },
        },
        "required": [
          "name",
        ],
        "type": "object",
      }
    `);
    expect(z.toJSONSchema(a, { io: "input" })).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "properties": {
          "name": {
            "type": "string",
          },
        },
        "required": [
          "name",
        ],
        "type": "object",
      }
    `);
    expect(
      z.toJSONSchema(a, {
        io: "input",
        override(ctx) {
          const def = ctx.zodSchema._zod.def;
          if (def.type === "object" && !def.catchall) {
            (ctx.jsonSchema as z.core.JSONSchema.ObjectSchema).additionalProperties = false;
          }
        },
      })
    ).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "additionalProperties": false,
        "properties": {
          "name": {
            "type": "string",
          },
        },
        "required": [
          "name",
        ],
        "type": "object",
      }
    `);
  });

  test("catchall objects", () => {
    const a = z.strictObject({
      name: z.string(),
      age: z.number(),
    });

    expect(z.toJSONSchema(a)).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "additionalProperties": false,
        "properties": {
          "age": {
            "type": "number",
          },
          "name": {
            "type": "string",
          },
        },
        "required": [
          "name",
          "age",
        ],
        "type": "object",
      }
    `);

    const b = z
      .object({
        name: z.string(),
      })
      .catchall(z.string());

    expect(z.toJSONSchema(b)).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "additionalProperties": {
          "type": "string",
        },
        "properties": {
          "name": {
            "type": "string",
          },
        },
        "required": [
          "name",
        ],
        "type": "object",
      }
    `);

    const c = z.looseObject({
      name: z.string(),
    });

    expect(z.toJSONSchema(c)).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "additionalProperties": {},
        "properties": {
          "name": {
            "type": "string",
          },
        },
        "required": [
          "name",
        ],
        "type": "object",
      }
    `);
  });

  test("optional fields - object", () => {
    const schema = z.object({
      required: z.string(),
      optional: z.string().optional(),
      nonoptional: z.string().optional().nonoptional(),
    });

    const result = z.toJSONSchema(schema);

    expect(result).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "additionalProperties": false,
        "properties": {
          "nonoptional": {
            "type": "string",
          },
          "optional": {
            "type": "string",
          },
          "required": {
            "type": "string",
          },
        },
        "required": [
          "required",
          "nonoptional",
        ],
        "type": "object",
      }
    `);
  });

  test("recursive object", () => {
    interface Category {
      name: string;
      subcategories: Category[];
    }

    const categorySchema: z.ZodType<Category> = z.object({
      name: z.string(),
      subcategories: z.array(z.lazy(() => categorySchema)),
    });

    const result = z.toJSONSchema(categorySchema);
    expect(result).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "additionalProperties": false,
        "properties": {
          "name": {
            "type": "string",
          },
          "subcategories": {
            "items": {
              "$ref": "#",
            },
            "type": "array",
          },
        },
        "required": [
          "name",
          "subcategories",
        ],
        "type": "object",
      }
    `);
  });

  test("simple interface", () => {
    const userSchema = z.object({
      name: z.string(),
      age: z.number().optional(),
    });

    const result = z.toJSONSchema(userSchema);
    expect(result).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "additionalProperties": false,
        "properties": {
          "age": {
            "type": "number",
          },
          "name": {
            "type": "string",
          },
        },
        "required": [
          "name",
        ],
        "type": "object",
      }
    `);
  });

  test("catchall interface", () => {
    const a = z.strictObject({
      name: z.string(),
      age: z.number(),
    });

    expect(z.toJSONSchema(a)).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "additionalProperties": false,
        "properties": {
          "age": {
            "type": "number",
          },
          "name": {
            "type": "string",
          },
        },
        "required": [
          "name",
          "age",
        ],
        "type": "object",
      }
    `);

    const b = z
      .object({
        name: z.string(),
      })
      .catchall(z.string());

    expect(z.toJSONSchema(b)).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "additionalProperties": {
          "type": "string",
        },
        "properties": {
          "name": {
            "type": "string",
          },
        },
        "required": [
          "name",
        ],
        "type": "object",
      }
    `);

    const c = z.looseObject({
      name: z.string(),
    });

    expect(z.toJSONSchema(c)).toMatchInlineSnapshot(`
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "additionalProperties": {},
        "properties": {
          "name": {
            "type": "string",
          },
        },
        "required": [
          "name",
        ],
        "type": "object",
      }
    `);
  });

  test("recursive interface schemas", () => {
    const TreeNodeSchema = z.object({
      id: z.string(),
      get children() {
        return TreeNodeSchema;
      },
    });

    const result = z.toJSONSchema(TreeNodeSchema);

    // Should have definitions for recursive schema
    expect(JSON.stringify(result, null, 2)).toMatchInlineSnapshot(
      `
      "{
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "children": {
            "$ref": "#"
          }
        },
        "required": [
          "id",
          "children"
        ],
        "additionalProperties": false
      }"
    `
    );
  });

  test("mutually recursive interface schemas", () => {
    const FolderSchema = z.object({
      name: z.string(),
      get files() {
        return z.array(FileSchema);
      },
    });

    const FileSchema = z.object({
      name: z.string(),
      get parent() {
        return FolderSchema;
      },
    });

    const result = z.toJSONSchema(FolderSchema);

    // Should have definitions for both schemas
    expect(JSON.stringify(result, null, 2)).toMatchInlineSnapshot(
      `
      "{
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "files": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "name": {
                  "type": "string"
                },
                "parent": {
                  "$ref": "#"
                }
              },
              "required": [
                "name",
                "parent"
              ],
              "additionalProperties": false
            }
          }
        },
        "required": [
          "name",
          "files"
        ],
        "additionalProperties": false
      }"
    `
    );
  });
});

test("override", () => {
  const schema = z.toJSONSchema(z.string(), {
    override: (ctx) => {
      ctx.zodSchema;
      ctx.jsonSchema;
      ctx.jsonSchema.whatever = "sup";
    },
  });
  expect(schema).toMatchInlineSnapshot(`
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "string",
      "whatever": "sup",
    }
  `);
});

test("override: do not run on references", () => {
  let overrideCount = 0;
  const schema = z
    .union([z.string().date(), z.string().datetime(), z.string().datetime({ local: true })])
    .meta({ a: true })
    .transform((str) => new Date(str))
    .meta({ b: true })
    .pipe(z.date())
    .meta({ c: true })
    .brand("dateIn");
  z.toJSONSchema(schema, {
    unrepresentable: "any",
    io: "input",
    override(_) {
      overrideCount++;
    },
  });

  expect(overrideCount).toBe(12);
});

test("override with refs", () => {
  const a = z.string().optional();
  const result = z.toJSONSchema(a, {
    override(ctx) {
      if (ctx.zodSchema._zod.def.type === "string") {
        ctx.jsonSchema.type = "STRING" as "string";
      }
    },
  });

  expect(result).toMatchInlineSnapshot(`
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "STRING",
    }
  `);
});

test("override execution order", () => {
  const schema = z.union([z.string(), z.number()]);
  let unionSchema!: any;
  z.toJSONSchema(schema, {
    override(ctx) {
      if (ctx.zodSchema._zod.def.type === "union") {
        unionSchema = ctx.jsonSchema;
      }
    },
  });

  expect(unionSchema).toMatchInlineSnapshot(`
    {
      "anyOf": [
        {
          "type": "string",
        },
        {
          "type": "number",
        },
      ],
    }
  `);
});

test("override with path", () => {
  const userSchema = z.object({
    name: z.string(),
    age: z.number(),
  });

  const capturedPaths: (string | number)[][] = [];

  z.toJSONSchema(userSchema, {
    override(ctx) {
      capturedPaths.push(ctx.path);
    },
  });

  expect(capturedPaths).toMatchInlineSnapshot(`
    [
      [
        "properties",
        "age",
      ],
      [
        "properties",
        "name",
      ],
      [],
    ]
  `);
});

test("pipe", () => {
  const mySchema = z
    .string()
    .transform((val) => val.length)
    .pipe(z.number());
  // ZodPipe

  const a = z.toJSONSchema(mySchema);
  expect(a).toMatchInlineSnapshot(`
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "number",
    }
  `);
  // => { type: "number" }

  const b = z.toJSONSchema(mySchema, { io: "input" });
  expect(b).toMatchInlineSnapshot(`
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "string",
    }
  `);
  // => { type: "string" }
});

test("passthrough schemas", () => {
  const Internal = z.object({
    num: z.number(),
    str: z.string(),
  });
  //.meta({ id: "Internal" });

  const External = z.object({
    a: Internal,
    b: Internal.optional(),
    c: z.lazy(() => Internal),
    d: z.promise(Internal),
    e: z.pipe(Internal, Internal),
  });

  const result = z.toJSONSchema(External, {
    reused: "ref",
  });
  expect(result).toMatchInlineSnapshot(`
    {
      "$defs": {
        "__schema0": {
          "additionalProperties": false,
          "properties": {
            "num": {
              "type": "number",
            },
            "str": {
              "type": "string",
            },
          },
          "required": [
            "num",
            "str",
          ],
          "type": "object",
        },
      },
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "additionalProperties": false,
      "properties": {
        "a": {
          "$ref": "#/$defs/__schema0",
        },
        "b": {
          "$ref": "#/$defs/__schema0",
        },
        "c": {
          "$ref": "#/$defs/__schema0",
        },
        "d": {
          "$ref": "#/$defs/__schema0",
        },
        "e": {
          "$ref": "#/$defs/__schema0",
        },
      },
      "required": [
        "a",
        "c",
        "d",
        "e",
      ],
      "type": "object",
    }
  `);
});

test("extract schemas with id", () => {
  const name = z.string().meta({ id: "name" });
  const result = z.toJSONSchema(
    z.object({
      first_name: name,
      last_name: name.nullable(),
      middle_name: name.optional(),
      age: z.number().meta({ id: "age" }),
    })
  );
  expect(result).toMatchInlineSnapshot(`
    {
      "$defs": {
        "age": {
          "id": "age",
          "type": "number",
        },
        "name": {
          "id": "name",
          "type": "string",
        },
      },
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "additionalProperties": false,
      "properties": {
        "age": {
          "$ref": "#/$defs/age",
        },
        "first_name": {
          "$ref": "#/$defs/name",
        },
        "last_name": {
          "anyOf": [
            {
              "$ref": "#/$defs/name",
            },
            {
              "type": "null",
            },
          ],
        },
        "middle_name": {
          "$ref": "#/$defs/name",
        },
      },
      "required": [
        "first_name",
        "last_name",
        "age",
      ],
      "type": "object",
    }
  `);
});

test("unrepresentable literal values are ignored", () => {
  const a = z.toJSONSchema(z.literal(["hello", null, 5, BigInt(1324), undefined]), { unrepresentable: "any" });
  expect(a).toMatchInlineSnapshot(`
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "enum": [
        "hello",
        null,
        5,
        1324,
      ],
    }
  `);

  const b = z.toJSONSchema(z.literal([undefined, null, 5, BigInt(1324)]), {
    unrepresentable: "any",
  });
  expect(b).toMatchInlineSnapshot(`
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "enum": [
        null,
        5,
        1324,
      ],
    }
  `);

  const c = z.toJSONSchema(z.literal([undefined]), {
    unrepresentable: "any",
  });
  expect(c).toMatchInlineSnapshot(`
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
    }
  `);
});

test("describe with id", () => {
  const jobId = z.string().meta({ id: "jobId" });

  const a = z.toJSONSchema(
    z.object({
      current: jobId.describe("Current job"),
      previous: jobId.describe("Previous job"),
    })
  );
  expect(a).toMatchInlineSnapshot(`
    {
      "$defs": {
        "jobId": {
          "id": "jobId",
          "type": "string",
        },
      },
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "additionalProperties": false,
      "properties": {
        "current": {
          "$ref": "#/$defs/jobId",
          "description": "Current job",
        },
        "previous": {
          "$ref": "#/$defs/jobId",
          "description": "Previous job",
        },
      },
      "required": [
        "current",
        "previous",
      ],
      "type": "object",
    }
  `);
});

test("describe with id on wrapper", () => {
  // Test that $ref propagation works when processor sets a different ref (readonly -> innerType)
  // but parent was extracted due to having an id
  const roJobId = z.string().readonly().meta({ id: "roJobId" });

  const a = z.toJSONSchema(
    z.object({
      current: roJobId.describe("Current readonly job"),
      previous: roJobId.describe("Previous readonly job"),
    })
  );
  expect(a).toMatchInlineSnapshot(`
    {
      "$defs": {
        "roJobId": {
          "id": "roJobId",
          "readOnly": true,
          "type": "string",
        },
      },
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "additionalProperties": false,
      "properties": {
        "current": {
          "$ref": "#/$defs/roJobId",
          "description": "Current readonly job",
        },
        "previous": {
          "$ref": "#/$defs/roJobId",
          "description": "Previous readonly job",
        },
      },
      "required": [
        "current",
        "previous",
      ],
      "type": "object",
    }
  `);
});

test("overwrite id", () => {
  const jobId = z.string().meta({ id: "aaa" });

  const a = z.toJSONSchema(
    z.object({
      current: jobId,
      previous: jobId.meta({ id: "bbb" }),
    })
  );
  expect(a).toMatchInlineSnapshot(`
    {
      "$defs": {
        "aaa": {
          "id": "aaa",
          "type": "string",
        },
        "bbb": {
          "$ref": "#/$defs/aaa",
          "id": "bbb",
        },
      },
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "additionalProperties": false,
      "properties": {
        "current": {
          "$ref": "#/$defs/aaa",
        },
        "previous": {
          "$ref": "#/$defs/bbb",
        },
      },
      "required": [
        "current",
        "previous",
      ],
      "type": "object",
    }
  `);

  const b = z.toJSONSchema(
    z.object({
      current: jobId,
      previous: jobId.meta({ id: "ccc" }),
    }),
    {
      reused: "ref",
    }
  );
  expect(b).toMatchInlineSnapshot(`
    {
      "$defs": {
        "aaa": {
          "id": "aaa",
          "type": "string",
        },
        "ccc": {
          "$ref": "#/$defs/aaa",
          "id": "ccc",
        },
      },
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "additionalProperties": false,
      "properties": {
        "current": {
          "$ref": "#/$defs/aaa",
        },
        "previous": {
          "$ref": "#/$defs/ccc",
        },
      },
      "required": [
        "current",
        "previous",
      ],
      "type": "object",
    }
  `);
});

test("overwrite descriptions", () => {
  const field = z.string().describe("a").describe("b").describe("c");

  const a = z.toJSONSchema(
    z.object({
      d: field.describe("d"),
      e: field.describe("e"),
    })
  );
  expect(a).toMatchInlineSnapshot(`
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "additionalProperties": false,
      "properties": {
        "d": {
          "description": "d",
          "type": "string",
        },
        "e": {
          "description": "e",
          "type": "string",
        },
      },
      "required": [
        "d",
        "e",
      ],
      "type": "object",
    }
  `);

  const b = z.toJSONSchema(
    z.object({
      d: field.describe("d"),
      e: field.describe("e"),
    }),
    {
      reused: "ref",
    }
  );
  expect(b).toMatchInlineSnapshot(`
    {
      "$defs": {
        "__schema0": {
          "description": "c",
          "type": "string",
        },
      },
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "additionalProperties": false,
      "properties": {
        "d": {
          "$ref": "#/$defs/__schema0",
          "description": "d",
        },
        "e": {
          "$ref": "#/$defs/__schema0",
          "description": "e",
        },
      },
      "required": [
        "d",
        "e",
      ],
      "type": "object",
    }
  `);
});

test("top-level readonly", () => {
  const A = z
    .object({
      name: z.string(),
      get b() {
        return B;
      },
    })
    .readonly()
    .meta({ id: "A" });
  // z.globalRegistry.add(A, { id: "A" });
  // .meta({ id: "A" });

  const B = z
    .object({
      name: z.string(),
      get a() {
        return A;
      },
    })
    .readonly()
    .meta({ id: "B" });
  // z.globalRegistry.add(B, { id: "B" });
  // .meta({ id: "B" });

  const result = z.toJSONSchema(A);
  expect(result).toMatchInlineSnapshot(`
    {
      "$defs": {
        "B": {
          "additionalProperties": false,
          "id": "B",
          "properties": {
            "a": {
              "$ref": "#",
            },
            "name": {
              "type": "string",
            },
          },
          "readOnly": true,
          "required": [
            "name",
            "a",
          ],
          "type": "object",
        },
      },
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "additionalProperties": false,
      "id": "A",
      "properties": {
        "b": {
          "$ref": "#/$defs/B",
        },
        "name": {
          "type": "string",
        },
      },
      "readOnly": true,
      "required": [
        "name",
        "b",
      ],
      "type": "object",
    }
  `);
});

test("basic registry", () => {
  const myRegistry = z.registry<{ id: string }>();
  const User = z.object({
    name: z.string(),
    get posts() {
      return z.array(Post);
    },
  });

  const Post = z.object({
    title: z.string(),
    content: z.string(),
    get author() {
      return User;
    },
  });

  myRegistry.add(User, { id: "User" });
  myRegistry.add(Post, { id: "Post" });

  const result = z.toJSONSchema(myRegistry, {
    uri: (id) => `https://example.com/${id}.json`,
  });
  expect(result).toMatchInlineSnapshot(`
    {
      "schemas": {
        "Post": {
          "$id": "https://example.com/Post.json",
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "additionalProperties": false,
          "properties": {
            "author": {
              "$ref": "https://example.com/User.json",
            },
            "content": {
              "type": "string",
            },
            "title": {
              "type": "string",
            },
          },
          "required": [
            "title",
            "content",
            "author",
          ],
          "type": "object",
        },
        "User": {
          "$id": "https://example.com/User.json",
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "additionalProperties": false,
          "properties": {
            "name": {
              "type": "string",
            },
            "posts": {
              "items": {
                "$ref": "https://example.com/Post.json",
              },
              "type": "array",
            },
          },
          "required": [
            "name",
            "posts",
          ],
          "type": "object",
        },
      },
    }
  `);
});

test("_ref", () => {
  // const a = z.promise(z.string().describe("a"));
  const a = z.toJSONSchema(z.promise(z.string().describe("a")));
  expect(a).toMatchInlineSnapshot(`
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "description": "a",
      "type": "string",
    }
  `);

  const b = z.toJSONSchema(z.lazy(() => z.string().describe("a")));
  expect(b).toMatchInlineSnapshot(`
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "description": "a",
      "type": "string",
    }
  `);

  const c = z.toJSONSchema(z.optional(z.string().describe("a")));
  expect(c).toMatchInlineSnapshot(`
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "description": "a",
      "type": "string",
    }
  `);

  const d = z.toJSONSchema(z.string().meta({ id: "foo" }).describe("bar").optional());
  expect(d).toMatchInlineSnapshot(`
    {
      "$defs": {
        "foo": {
          "id": "foo",
          "type": "string",
        },
      },
      "$ref": "#/$defs/foo",
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "description": "bar",
    }
  `);
});

test("defaults/prefaults", () => {
  const a = z
    .string()
    .transform((val) => val.length)
    .pipe(z.number());
  const b = a.prefault("hello");
  const c = a.default(1234);

  // a
  expect(z.toJSONSchema(a)).toMatchInlineSnapshot(`
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "number",
    }
  `);
  expect(z.toJSONSchema(a, { io: "input" })).toMatchInlineSnapshot(`
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "string",
    }
  `);

  // b
  expect(z.toJSONSchema(b)).toMatchInlineSnapshot(`
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "number",
    }
  `);
  expect(z.toJSONSchema(b, { io: "input" })).toMatchInlineSnapshot(`
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "default": "hello",
      "type": "string",
    }
  `);
  // c
  expect(z.toJSONSchema(c)).toMatchInlineSnapshot(`
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "default": 1234,
      "type": "number",
    }
  `);
  expect(z.toJSONSchema(c, { io: "input" })).toMatchInlineSnapshot(`
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "string",
    }
  `);
});

test("input type", () => {
  const schema = z.object({
    a: z.string(),
    b: z.string().optional(),
    c: z.string().default("hello"),
    d: z.string().nullable(),
    e: z.string().prefault("hello"),
    f: z.string().catch("hello"),
    g: z.never(),
    h: z.union([z.string(), z.number().default(2)]),
    i: z.union([z.string(), z.string().optional()]),
  });
  expect(z.toJSONSchema(schema, { io: "input" })).toMatchInlineSnapshot(`
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "properties": {
        "a": {
          "type": "string",
        },
        "b": {
          "type": "string",
        },
        "c": {
          "default": "hello",
          "type": "string",
        },
        "d": {
          "anyOf": [
            {
              "type": "string",
            },
            {
              "type": "null",
            },
          ],
        },
        "e": {
          "default": "hello",
          "type": "string",
        },
        "f": {
          "default": "hello",
          "type": "string",
        },
        "g": {
          "not": {},
        },
        "h": {
          "anyOf": [
            {
              "type": "string",
            },
            {
              "default": 2,
              "type": "number",
            },
          ],
        },
        "i": {
          "anyOf": [
            {
              "type": "string",
            },
            {
              "type": "string",
            },
          ],
        },
      },
      "required": [
        "a",
        "d",
        "f",
        "g",
      ],
      "type": "object",
    }
  `);
  expect(z.toJSONSchema(schema, { io: "output" })).toMatchInlineSnapshot(`
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "additionalProperties": false,
      "properties": {
        "a": {
          "type": "string",
        },
        "b": {
          "type": "string",
        },
        "c": {
          "default": "hello",
          "type": "string",
        },
        "d": {
          "anyOf": [
            {
              "type": "string",
            },
            {
              "type": "null",
            },
          ],
        },
        "e": {
          "type": "string",
        },
        "f": {
          "default": "hello",
          "type": "string",
        },
        "g": {
          "not": {},
        },
        "h": {
          "anyOf": [
            {
              "type": "string",
            },
            {
              "default": 2,
              "type": "number",
            },
          ],
        },
        "i": {
          "anyOf": [
            {
              "type": "string",
            },
            {
              "type": "string",
            },
          ],
        },
      },
      "required": [
        "a",
        "c",
        "d",
        "e",
        "f",
        "g",
        "h",
      ],
      "type": "object",
    }
  `);
});

test("examples on pipe", () => {
  const schema = z
    .string()
    .meta({ examples: ["test"] })
    .transform(Number)
    // .pipe(z.transform(Number).meta({ examples: [4] }))
    .meta({ examples: [4] });

  const i = z.toJSONSchema(schema, { io: "input", unrepresentable: "any" });
  expect(i).toMatchInlineSnapshot(`
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "examples": [
        "test",
      ],
      "type": "string",
    }
  `);
  const o = z.toJSONSchema(schema, { io: "output", unrepresentable: "any" });
  expect(o).toMatchInlineSnapshot(`
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "examples": [
        4,
      ],
    }
  `);
});

// test("number checks", () => {
//   expect(z.toJSONSchema(z.number().int())).toMatchInlineSnapshot(`
//     {
//       "maximum": 9007199254740991,
//       "minimum": -9007199254740991,
//       "type": "integer",
//     }
//   `);
//   expect(z.toJSONSchema(z.int())).toMatchInlineSnapshot(`
//     {
//       "maximum": 9007199254740991,
//       "minimum": -9007199254740991,
//       "type": "integer",
//     }
//   `);
//   expect(z.toJSONSchema(z.int().positive())).toMatchInlineSnapshot(`
//     {
//       "exclusiveMinimum": 0,
//       "maximum": 9007199254740991,
//       "minimum": -9007199254740991,
//       "type": "integer",
//     }
//   `);
//   expect(z.toJSONSchema(z.int().nonnegative())).toMatchInlineSnapshot(`
//     {
//       "maximum": 9007199254740991,
//       "minimum": 0,
//       "type": "integer",
//     }
//   `);
//   expect(z.toJSONSchema(z.int().gt(0))).toMatchInlineSnapshot(`
//     {
//       "exclusiveMinimum": 0,
//       "maximum": 9007199254740991,
//       "minimum": -9007199254740991,
//       "type": "integer",
//     }
//   `);
//   expect(z.toJSONSchema(z.int().gte(0))).toMatchInlineSnapshot(`
//     {
//       "maximum": 9007199254740991,
//       "minimum": 0,
//       "type": "integer",
//     }
//   `);

// });

test("use output type for preprocess", () => {
  const a = z.preprocess((val) => String(val), z.string());

  expect(z.toJSONSchema(a, { io: "input" })).toMatchInlineSnapshot(`
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "string",
    }
  `);
});

// test("isTransforming", () => {
//   const tx = z.core.isTransforming;
//   expect(tx(z.string())).toEqual(false);
//   expect(tx(z.string().transform((val) => val))).toEqual(true);
//   expect(tx(z.string().pipe(z.string()))).toEqual(false);
//   expect(
//     tx(
//       z
//         .string()
//         .transform((val) => val)
//         .pipe(z.string())
//     )
//   ).toEqual(true);

//   const a = z.transform((val) => val);
//   expect(tx(z.transform((val) => val))).toEqual(true);
//   expect(tx(a.optional())).toEqual(true);

//   const b = z.string().optional();
//   expect(tx(b)).toEqual(false);

//   const c = z.string().prefault("hello");
//   expect(tx(c)).toEqual(false);

//   const d = z.string().default("hello");
//   expect(tx(d)).toEqual(false);
// });

test("flatten simple intersections", () => {
  const FirstSchema = z.object({
    testNum: z.number(),
  });

  const SecondSchema = z.object({
    testStr: z.string(),
  });

  const ThirdSchema = z.object({
    testBool: z.boolean(),
  });

  const HelloSchema = FirstSchema.and(SecondSchema).and(ThirdSchema).describe("123");

  // Zod 3
  // console.log(JSON.stringify(zodToJsonSchema(HelloSchema), null, 2));

  // Zod 4
  const result = z.toJSONSchema(HelloSchema, { target: "draft-7" });
  expect(result).toMatchInlineSnapshot(`
    {
      "$schema": "http://json-schema.org/draft-07/schema#",
      "allOf": [
        {
          "additionalProperties": false,
          "properties": {
            "testNum": {
              "type": "number",
            },
          },
          "required": [
            "testNum",
          ],
          "type": "object",
        },
        {
          "additionalProperties": false,
          "properties": {
            "testStr": {
              "type": "string",
            },
          },
          "required": [
            "testStr",
          ],
          "type": "object",
        },
        {
          "additionalProperties": false,
          "properties": {
            "testBool": {
              "type": "boolean",
            },
          },
          "required": [
            "testBool",
          ],
          "type": "object",
        },
      ],
      "description": "123",
    }
  `);
});

test("z.file()", () => {
  const a = z.file();
  expect(z.toJSONSchema(a)).toMatchInlineSnapshot(`
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "contentEncoding": "binary",
      "format": "binary",
      "type": "string",
    }
  `);

  const b = z.file().mime("image/png").min(1000).max(10000);
  expect(z.toJSONSchema(b)).toMatchInlineSnapshot(`
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "contentEncoding": "binary",
      "contentMediaType": "image/png",
      "format": "binary",
      "maxLength": 10000,
      "minLength": 1000,
      "type": "string",
    }
  `);

  const c = z.file().mime(["image/png", "image/jpg"]).min(1000).max(10000);
  expect(z.toJSONSchema(c)).toMatchInlineSnapshot(`
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "anyOf": [
        {
          "contentMediaType": "image/png",
        },
        {
          "contentMediaType": "image/jpg",
        },
      ],
      "contentEncoding": "binary",
      "format": "binary",
      "maxLength": 10000,
      "minLength": 1000,
      "type": "string",
    }
  `);
});

test("custom toJSONSchema", () => {
  const schema = z.instanceof(Date);
  schema._zod.toJSONSchema = () => ({
    type: "string",
    format: "date-time",
  });

  expect(z.toJSONSchema(schema)).toMatchInlineSnapshot(`
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "format": "date-time",
      "type": "string",
    }
  `);
});

test("cycle detection - root", () => {
  const schema = z.object({
    name: z.string(),
    get subcategories() {
      return z.array(schema);
    },
  });

  expect(() => z.toJSONSchema(schema, { cycles: "throw" })).toThrowErrorMatchingInlineSnapshot(`
    [Error: Cycle detected: #/properties/subcategories/items/<root>

    Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.]
  `);
});

test("cycle detection - mutual recursion", () => {
  const A = z.object({
    name: z.string(),
    get subcategories() {
      return z.array(B);
    },
  });

  const B = z.object({
    name: z.string(),
    get subcategories() {
      return z.array(A);
    },
  });

  expect(() => z.toJSONSchema(A, { cycles: "throw" })).toThrowErrorMatchingInlineSnapshot(`
    [Error: Cycle detected: #/properties/subcategories/items/properties/subcategories/items/<root>

    Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.]
  `);
});
