import { JsonSchema, Schema, SchemaRepresentation } from "effect"
import { describe, it } from "vitest"
import { assertFalse, assertTrue, deepStrictEqual, strictEqual } from "../../utils/assert.ts"

const json = (annotations?: Schema.Annotations.Annotations) => {
  const representation = SchemaRepresentation.fromAST(Schema.Json.ast).representation
  return annotations === undefined
    ? representation
    : {
      ...representation,
      annotations: {
        ...("annotations" in representation ? representation.annotations : undefined),
        ...annotations
      }
    }
}

describe("fromJsonSchemaDocument", () => {
  function assertFromJsonSchema(
    input: {
      readonly schema: JsonSchema.JsonSchema
      readonly options?: {
        readonly onEnter?: ((js: JsonSchema.JsonSchema) => JsonSchema.JsonSchema) | undefined
      }
    },
    expected: {
      readonly representation: SchemaRepresentation.Representation
      readonly references?: Record<string, SchemaRepresentation.Representation>
    },
    runtime?: string
  ) {
    const expectedDocument: SchemaRepresentation.Document = {
      representation: expected.representation,
      references: expected.references ?? {}
    }
    const jsonDocument = JsonSchema.fromSchemaDraft2020_12(input.schema)
    const document = SchemaRepresentation.fromJsonSchemaDocument(jsonDocument, input.options)
    deepStrictEqual(document, expectedDocument)
    const multiDocument = SchemaRepresentation.toMultiDocument(document)
    if (runtime !== undefined) {
      strictEqual(SchemaRepresentation.toCodeDocument(multiDocument).codes[0].runtime, runtime)
    }
    return document
  }

  it("{}", () => {
    assertFromJsonSchema(
      { schema: {} },
      {
        representation: json()
      },
      "Schema.Json"
    )
    assertFromJsonSchema(
      {
        schema: {
          title: "a",
          description: "b",
          default: "c",
          examples: ["d"],
          readOnly: true,
          writeOnly: true
        }
      },
      {
        representation: json({
          title: "a",
          description: "b",
          default: "c",
          examples: ["d"],
          readOnly: true,
          writeOnly: true
        })
      },
      `Schema.Json.annotate({ "title": "a", "description": "b", "default": "c", "examples": ["d"], "readOnly": true, "writeOnly": true })`
    )
  })

  describe("const", () => {
    it("const: literal (string)", () => {
      assertFromJsonSchema(
        { schema: { const: "a" } },
        {
          representation: { _tag: "Literal", literal: "a" }
        },
        `Schema.Literal("a")`
      )
      assertFromJsonSchema(
        { schema: { const: "a", description: "a" } },
        {
          representation: { _tag: "Literal", literal: "a", annotations: { description: "a" } }
        },
        `Schema.Literal("a").annotate({ "description": "a" })`
      )
    })

    it("const: literal (number)", () => {
      assertFromJsonSchema(
        { schema: { const: 1 } },
        {
          representation: { _tag: "Literal", literal: 1 }
        },
        `Schema.Literal(1)`
      )
    })

    it("const: literal (boolean)", () => {
      assertFromJsonSchema(
        { schema: { const: true } },
        {
          representation: { _tag: "Literal", literal: true }
        },
        `Schema.Literal(true)`
      )
    })

    it("const: null", () => {
      assertFromJsonSchema(
        { schema: { const: null } },
        {
          representation: { _tag: "Null" }
        },
        `Schema.Null`
      )
      assertFromJsonSchema(
        { schema: { const: null, description: "a" } },
        {
          representation: { _tag: "Null", annotations: { description: "a" } }
        },
        `Schema.Null.annotate({ "description": "a" })`
      )
    })

    it("const: non-literal", () => {
      assertFromJsonSchema(
        { schema: { const: {} } },
        {
          representation: json()
        },
        `Schema.Json`
      )
    })
  })

  describe("enum", () => {
    it("single enum (string)", () => {
      assertFromJsonSchema(
        { schema: { enum: ["a"] } },
        {
          representation: { _tag: "Literal", literal: "a" }
        },
        `Schema.Literal("a")`
      )
      assertFromJsonSchema(
        { schema: { enum: ["a"], description: "a" } },
        {
          representation: { _tag: "Literal", literal: "a", annotations: { description: "a" } }
        },
        `Schema.Literal("a").annotate({ "description": "a" })`
      )
    })

    it("single enum (number)", () => {
      assertFromJsonSchema(
        { schema: { enum: [1] } },
        {
          representation: { _tag: "Literal", literal: 1 }
        },
        `Schema.Literal(1)`
      )
    })

    it("single enum (boolean)", () => {
      assertFromJsonSchema(
        { schema: { enum: [true] } },
        {
          representation: { _tag: "Literal", literal: true }
        },
        `Schema.Literal(true)`
      )
    })

    it("multiple enum (literals)", () => {
      assertFromJsonSchema(
        { schema: { enum: ["a", 1] } },
        {
          representation: {
            _tag: "Union",
            types: [
              { _tag: "Literal", literal: "a" },
              { _tag: "Literal", literal: 1 }
            ],
            mode: "anyOf"
          }
        },
        `Schema.Literals(["a", 1])`
      )
      assertFromJsonSchema(
        { schema: { enum: ["a", 1], description: "a" } },
        {
          representation: {
            _tag: "Union",
            types: [
              { _tag: "Literal", literal: "a" },
              { _tag: "Literal", literal: 1 }
            ],
            mode: "anyOf",
            annotations: { description: "a" }
          }
        },
        `Schema.Literals(["a", 1]).annotate({ "description": "a" })`
      )
    })

    it("enum containing null", () => {
      assertFromJsonSchema(
        { schema: { enum: ["a", null] } },
        {
          representation: {
            _tag: "Union",
            types: [
              { _tag: "Literal", literal: "a" },
              { _tag: "Null" }
            ],
            mode: "anyOf"
          }
        },
        `Schema.Union([Schema.Literal("a"), Schema.Null])`
      )
    })
  })

  it("anyOf", () => {
    assertFromJsonSchema(
      { schema: { anyOf: [{ const: "a" }, { enum: [1, 2] }] } },
      {
        representation: {
          _tag: "Union",
          types: [
            { _tag: "Literal", literal: "a" },
            {
              _tag: "Union",
              types: [
                { _tag: "Literal", literal: 1 },
                { _tag: "Literal", literal: 2 }
              ],
              mode: "anyOf"
            }
          ],
          mode: "anyOf"
        }
      },
      `Schema.Union([Schema.Literal("a"), Schema.Literals([1, 2])])`
    )
  })

  it("anyOf with siblings", () => {
    assertFromJsonSchema(
      {
        schema: {
          type: "object",
          properties: { id: { type: "string" } },
          required: ["id"],
          anyOf: [
            { properties: { a: { type: "string" } }, required: ["a"] },
            { properties: { b: { type: "number" } }, required: ["b"] }
          ]
        }
      },
      {
        representation: {
          _tag: "Union",
          types: [
            {
              _tag: "Objects",
              propertySignatures: [
                { name: "a", isOptional: false, isMutable: false, type: { _tag: "String", checks: [] } },
                { name: "id", isOptional: false, isMutable: false, type: { _tag: "String", checks: [] } }
              ],
              indexSignatures: [{ parameter: { _tag: "String", checks: [] }, type: json() }],
              checks: []
            },
            {
              _tag: "Objects",
              propertySignatures: [
                {
                  name: "b",
                  isOptional: false,
                  isMutable: false,
                  type: { _tag: "Number", checks: [{ _tag: "Filter", meta: { _tag: "isFinite" } }] }
                },
                { name: "id", isOptional: false, isMutable: false, type: { _tag: "String", checks: [] } }
              ],
              indexSignatures: [{ parameter: { _tag: "String", checks: [] }, type: json() }],
              checks: []
            }
          ],
          mode: "anyOf"
        }
      }
    )
  })

  it("oneOf", () => {
    assertFromJsonSchema(
      { schema: { oneOf: [{ const: "a" }, { enum: [1, 2] }] } },
      {
        representation: {
          _tag: "Union",
          types: [
            { _tag: "Literal", literal: "a" },
            {
              _tag: "Union",
              types: [
                { _tag: "Literal", literal: 1 },
                { _tag: "Literal", literal: 2 }
              ],
              mode: "anyOf"
            }
          ],
          mode: "oneOf"
        }
      },
      `Schema.Union([Schema.Literal("a"), Schema.Literals([1, 2])], { mode: "oneOf" })`
    )
  })

  it("oneOf with siblings", () => {
    assertFromJsonSchema(
      {
        schema: {
          type: "object",
          properties: { id: { type: "string" } },
          required: ["id"],
          oneOf: [
            { properties: { a: { type: "string" } }, required: ["a"] },
            { properties: { b: { type: "number" } }, required: ["b"] }
          ]
        }
      },
      {
        representation: {
          _tag: "Union",
          types: [
            {
              _tag: "Objects",
              propertySignatures: [
                { name: "a", isOptional: false, isMutable: false, type: { _tag: "String", checks: [] } },
                { name: "id", isOptional: false, isMutable: false, type: { _tag: "String", checks: [] } }
              ],
              indexSignatures: [{ parameter: { _tag: "String", checks: [] }, type: json() }],
              checks: []
            },
            {
              _tag: "Objects",
              propertySignatures: [
                {
                  name: "b",
                  isOptional: false,
                  isMutable: false,
                  type: { _tag: "Number", checks: [{ _tag: "Filter", meta: { _tag: "isFinite" } }] }
                },
                { name: "id", isOptional: false, isMutable: false, type: { _tag: "String", checks: [] } }
              ],
              indexSignatures: [{ parameter: { _tag: "String", checks: [] }, type: json() }],
              checks: []
            }
          ],
          mode: "oneOf"
        }
      }
    )
  })

  describe("type: null", () => {
    it("type only", () => {
      assertFromJsonSchema(
        { schema: { type: "null" } },
        {
          representation: { _tag: "Null" }
        },
        `Schema.Null`
      )
    })
  })

  describe("type: string", () => {
    it("type only", () => {
      assertFromJsonSchema(
        { schema: { type: "string" } },
        {
          representation: { _tag: "String", checks: [] }
        },
        `Schema.String`
      )
    })

    describe("checks", () => {
      it("minLength", () => {
        assertFromJsonSchema(
          { schema: { type: "string", minLength: 1 } },
          {
            representation: {
              _tag: "String",
              checks: [{ _tag: "Filter", meta: { _tag: "isMinLength", minLength: 1 } }]
            }
          },
          `Schema.String.check(Schema.isMinLength(1))`
        )
      })

      it("maxLength", () => {
        assertFromJsonSchema(
          { schema: { type: "string", maxLength: 1 } },
          {
            representation: {
              _tag: "String",
              checks: [{ _tag: "Filter", meta: { _tag: "isMaxLength", maxLength: 1 } }]
            }
          },
          `Schema.String.check(Schema.isMaxLength(1))`
        )
      })

      it("pattern", () => {
        assertFromJsonSchema(
          { schema: { type: "string", pattern: "a*" } },
          {
            representation: {
              _tag: "String",
              checks: [
                { _tag: "Filter", meta: { _tag: "isPattern", regExp: new RegExp("a*") } }
              ]
            }
          },
          `Schema.String.check(Schema.isPattern(new RegExp("a*")))`
        )
        assertFromJsonSchema(
          { schema: { pattern: "a*" } },
          {
            representation: {
              _tag: "String",
              checks: [
                { _tag: "Filter", meta: { _tag: "isPattern", regExp: new RegExp("a*") } }
              ]
            }
          },
          `Schema.String.check(Schema.isPattern(new RegExp("a*")))`
        )
      })
    })
  })

  describe("type: number", () => {
    it("type only", () => {
      assertFromJsonSchema(
        { schema: { type: "number" } },
        {
          representation: {
            _tag: "Number",
            checks: [
              { _tag: "Filter", meta: { _tag: "isFinite" } }
            ]
          }
        },
        `Schema.Number.check(Schema.isFinite())`
      )
    })

    describe("checks", () => {
      it("minimum", () => {
        assertFromJsonSchema(
          { schema: { type: "number", minimum: 1 } },
          {
            representation: {
              _tag: "Number",
              checks: [
                { _tag: "Filter", meta: { _tag: "isFinite" } },
                { _tag: "Filter", meta: { _tag: "isGreaterThanOrEqualTo", minimum: 1 } }
              ]
            }
          },
          `Schema.Number.check(Schema.isFinite()).check(Schema.isGreaterThanOrEqualTo(1))`
        )
      })

      it("maximum", () => {
        assertFromJsonSchema(
          { schema: { type: "number", maximum: 1 } },
          {
            representation: {
              _tag: "Number",
              checks: [
                { _tag: "Filter", meta: { _tag: "isFinite" } },
                { _tag: "Filter", meta: { _tag: "isLessThanOrEqualTo", maximum: 1 } }
              ]
            }
          },
          `Schema.Number.check(Schema.isFinite()).check(Schema.isLessThanOrEqualTo(1))`
        )
      })

      it("exclusiveMinimum", () => {
        assertFromJsonSchema(
          { schema: { type: "number", exclusiveMinimum: 1 } },
          {
            representation: {
              _tag: "Number",
              checks: [
                { _tag: "Filter", meta: { _tag: "isFinite" } },
                { _tag: "Filter", meta: { _tag: "isGreaterThan", exclusiveMinimum: 1 } }
              ]
            }
          },
          `Schema.Number.check(Schema.isFinite()).check(Schema.isGreaterThan(1))`
        )
      })

      it("exclusiveMaximum", () => {
        assertFromJsonSchema(
          { schema: { type: "number", exclusiveMaximum: 1 } },
          {
            representation: {
              _tag: "Number",
              checks: [
                { _tag: "Filter", meta: { _tag: "isFinite" } },
                { _tag: "Filter", meta: { _tag: "isLessThan", exclusiveMaximum: 1 } }
              ]
            }
          },
          `Schema.Number.check(Schema.isFinite()).check(Schema.isLessThan(1))`
        )
      })

      it("multipleOf", () => {
        assertFromJsonSchema(
          { schema: { type: "number", multipleOf: 1 } },
          {
            representation: {
              _tag: "Number",
              checks: [
                { _tag: "Filter", meta: { _tag: "isFinite" } },
                { _tag: "Filter", meta: { _tag: "isMultipleOf", divisor: 1 } }
              ]
            }
          },
          `Schema.Number.check(Schema.isFinite()).check(Schema.isMultipleOf(1))`
        )
      })
    })
  })

  describe("type: integer", () => {
    it("type only", () => {
      assertFromJsonSchema(
        { schema: { type: "integer" } },
        {
          representation: {
            _tag: "Number",
            checks: [
              { _tag: "Filter", meta: { _tag: "isInt" } }
            ]
          }
        },
        `Schema.Number.check(Schema.isInt())`
      )
    })

    describe("checks", () => {
      it("minimum", () => {
        assertFromJsonSchema(
          { schema: { type: "integer", minimum: 1 } },
          {
            representation: {
              _tag: "Number",
              checks: [
                { _tag: "Filter", meta: { _tag: "isInt" } },
                { _tag: "Filter", meta: { _tag: "isGreaterThanOrEqualTo", minimum: 1 } }
              ]
            }
          },
          `Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(1))`
        )
      })

      it("maximum", () => {
        assertFromJsonSchema(
          { schema: { type: "integer", maximum: 1 } },
          {
            representation: {
              _tag: "Number",
              checks: [
                { _tag: "Filter", meta: { _tag: "isInt" } },
                { _tag: "Filter", meta: { _tag: "isLessThanOrEqualTo", maximum: 1 } }
              ]
            }
          },
          `Schema.Number.check(Schema.isInt()).check(Schema.isLessThanOrEqualTo(1))`
        )
      })

      it("exclusiveMinimum", () => {
        assertFromJsonSchema(
          { schema: { type: "integer", exclusiveMinimum: 1 } },
          {
            representation: {
              _tag: "Number",
              checks: [
                { _tag: "Filter", meta: { _tag: "isInt" } },
                { _tag: "Filter", meta: { _tag: "isGreaterThan", exclusiveMinimum: 1 } }
              ]
            }
          },
          `Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThan(1))`
        )
      })

      it("exclusiveMaximum", () => {
        assertFromJsonSchema(
          { schema: { type: "integer", exclusiveMaximum: 1 } },
          {
            representation: {
              _tag: "Number",
              checks: [
                { _tag: "Filter", meta: { _tag: "isInt" } },
                { _tag: "Filter", meta: { _tag: "isLessThan", exclusiveMaximum: 1 } }
              ]
            }
          },
          `Schema.Number.check(Schema.isInt()).check(Schema.isLessThan(1))`
        )
      })

      it("multipleOf", () => {
        assertFromJsonSchema(
          { schema: { type: "integer", multipleOf: 1 } },
          {
            representation: {
              _tag: "Number",
              checks: [
                { _tag: "Filter", meta: { _tag: "isInt" } },
                { _tag: "Filter", meta: { _tag: "isMultipleOf", divisor: 1 } }
              ]
            }
          },
          `Schema.Number.check(Schema.isInt()).check(Schema.isMultipleOf(1))`
        )
      })
    })
  })

  describe("type: boolean", () => {
    it("type only", () => {
      assertFromJsonSchema(
        { schema: { type: "boolean" } },
        {
          representation: { _tag: "Boolean" }
        },
        `Schema.Boolean`
      )
    })
  })

  describe("type: array", () => {
    it("type only", () => {
      assertFromJsonSchema(
        { schema: { type: "array" } },
        {
          representation: {
            _tag: "Arrays",
            elements: [],
            rest: [json()],
            checks: []
          }
        },
        `Schema.Array(Schema.Json)`
      )
    })

    it("items", () => {
      assertFromJsonSchema(
        {
          schema: {
            type: "array",
            items: { type: "string" }
          }
        },
        {
          representation: { _tag: "Arrays", elements: [], rest: [{ _tag: "String", checks: [] }], checks: [] }
        },
        `Schema.Array(Schema.String)`
      )
    })

    it("prefixItems", () => {
      assertFromJsonSchema(
        {
          schema: {
            type: "array",
            prefixItems: [{ type: "string" }],
            maxItems: 1
          }
        },
        {
          representation: {
            _tag: "Arrays",
            elements: [
              { isOptional: true, type: { _tag: "String", checks: [] } }
            ],
            rest: [],
            checks: []
          }
        },
        `Schema.Tuple([Schema.optionalKey(Schema.String)])`
      )

      assertFromJsonSchema(
        {
          schema: {
            type: "array",
            prefixItems: [{ type: "string" }],
            minItems: 1,
            maxItems: 1
          }
        },
        {
          representation: {
            _tag: "Arrays",
            elements: [
              { isOptional: false, type: { _tag: "String", checks: [] } }
            ],
            rest: [],
            checks: []
          }
        },
        `Schema.Tuple([Schema.String])`
      )
    })

    it("prefixItems & minItems", () => {
      assertFromJsonSchema(
        {
          schema: {
            type: "array",
            prefixItems: [{ type: "string" }],
            minItems: 1,
            items: { type: "number" }
          }
        },
        {
          representation: {
            _tag: "Arrays",
            elements: [
              { isOptional: false, type: { _tag: "String", checks: [] } }
            ],
            rest: [
              { _tag: "Number", checks: [{ _tag: "Filter", meta: { _tag: "isFinite" } }] }
            ],
            checks: []
          }
        },
        `Schema.TupleWithRest(Schema.Tuple([Schema.String]), [Schema.Number.check(Schema.isFinite())])`
      )
    })

    describe("checks", () => {
      it("minItems", () => {
        assertFromJsonSchema(
          { schema: { type: "array", minItems: 1 } },
          {
            representation: {
              _tag: "Arrays",
              elements: [],
              rest: [json()],
              checks: [{ _tag: "Filter", meta: { _tag: "isMinLength", minLength: 1 } }]
            }
          },
          `Schema.Array(Schema.Json).check(Schema.isMinLength(1))`
        )
      })

      it("maxItems", () => {
        assertFromJsonSchema(
          { schema: { type: "array", maxItems: 1 } },
          {
            representation: {
              _tag: "Arrays",
              elements: [],
              rest: [json()],
              checks: [{ _tag: "Filter", meta: { _tag: "isMaxLength", maxLength: 1 } }]
            }
          },
          `Schema.Array(Schema.Json).check(Schema.isMaxLength(1))`
        )
      })

      it("uniqueItems", () => {
        assertFromJsonSchema(
          { schema: { type: "array", uniqueItems: true } },
          {
            representation: {
              _tag: "Arrays",
              elements: [],
              rest: [json()],
              checks: [{ _tag: "Filter", meta: { _tag: "isUnique" } }]
            }
          },
          `Schema.Array(Schema.Json).check(Schema.isUnique())`
        )
      })
    })
  })

  describe("type: object", () => {
    it("type only", () => {
      assertFromJsonSchema(
        { schema: { type: "object" } },
        {
          representation: {
            _tag: "Objects",
            propertySignatures: [],
            indexSignatures: [
              { parameter: { _tag: "String", checks: [] }, type: json() }
            ],
            checks: []
          }
        },
        `Schema.Record(Schema.String, Schema.Json)`
      )
      assertFromJsonSchema(
        {
          schema: {
            type: "object",
            additionalProperties: false
          }
        },
        {
          representation: {
            _tag: "Objects",
            propertySignatures: [],
            indexSignatures: [],
            checks: []
          }
        },
        `Schema.Struct({  })`
      )
    })

    it("additionalProperties", () => {
      assertFromJsonSchema(
        {
          schema: {
            type: "object",
            additionalProperties: { type: "boolean" }
          }
        },
        {
          representation: {
            _tag: "Objects",
            propertySignatures: [],
            indexSignatures: [
              { parameter: { _tag: "String", checks: [] }, type: { _tag: "Boolean" } }
            ],
            checks: []
          }
        },
        `Schema.Record(Schema.String, Schema.Boolean)`
      )
    })

    it("properties", () => {
      assertFromJsonSchema(
        {
          schema: {
            type: "object",
            properties: { a: { type: "string" }, b: { type: "string" } },
            required: ["a"],
            additionalProperties: false
          }
        },
        {
          representation: {
            _tag: "Objects",
            propertySignatures: [
              {
                name: "a",
                type: { _tag: "String", checks: [] },
                isOptional: false,
                isMutable: false
              },
              {
                name: "b",
                type: { _tag: "String", checks: [] },
                isOptional: true,
                isMutable: false
              }
            ],
            indexSignatures: [],
            checks: []
          }
        },
        `Schema.Struct({ "a": Schema.String, "b": Schema.optionalKey(Schema.String) })`
      )
    })

    it("properties & additionalProperties", () => {
      assertFromJsonSchema(
        {
          schema: {
            type: "object",
            properties: { a: { type: "string" } },
            required: ["a"],
            additionalProperties: { type: "boolean" }
          }
        },
        {
          representation: {
            _tag: "Objects",
            propertySignatures: [{
              name: "a",
              type: { _tag: "String", checks: [] },
              isOptional: false,
              isMutable: false
            }],
            indexSignatures: [
              { parameter: { _tag: "String", checks: [] }, type: { _tag: "Boolean" } }
            ],
            checks: []
          }
        },
        `Schema.StructWithRest(Schema.Struct({ "a": Schema.String }), [Schema.Record(Schema.String, Schema.Boolean)])`
      )
    })

    it("patternProperties", () => {
      assertFromJsonSchema(
        {
          schema: {
            type: "object",
            patternProperties: {
              "a*": { type: "string" }
            },
            additionalProperties: false
          }
        },
        {
          representation: {
            _tag: "Objects",
            propertySignatures: [],
            indexSignatures: [
              {
                parameter: {
                  _tag: "String",
                  checks: [{ _tag: "Filter", meta: { _tag: "isPattern", regExp: new RegExp("a*") } }]
                },
                type: { _tag: "String", checks: [] }
              }
            ],
            checks: []
          }
        },
        `Schema.Record(Schema.String.check(Schema.isPattern(new RegExp("a*"))), Schema.String)`
      )
      assertFromJsonSchema(
        {
          schema: {
            type: "object",
            patternProperties: {
              "a*": { type: "string" },
              "b*": { type: "number" }
            },
            additionalProperties: false
          }
        },
        {
          representation: {
            _tag: "Objects",
            propertySignatures: [],
            indexSignatures: [
              {
                parameter: {
                  _tag: "String",
                  checks: [{ _tag: "Filter", meta: { _tag: "isPattern", regExp: new RegExp("a*") } }]
                },
                type: { _tag: "String", checks: [] }
              },
              {
                parameter: {
                  _tag: "String",
                  checks: [{ _tag: "Filter", meta: { _tag: "isPattern", regExp: new RegExp("b*") } }]
                },
                type: { _tag: "Number", checks: [{ _tag: "Filter", meta: { _tag: "isFinite" } }] }
              }
            ],
            checks: []
          }
        },
        `Schema.StructWithRest(Schema.Struct({  }), [Schema.Record(Schema.String.check(Schema.isPattern(new RegExp("a*"))), Schema.String), Schema.Record(Schema.String.check(Schema.isPattern(new RegExp("b*"))), Schema.Number.check(Schema.isFinite()))])`
      )
    })

    describe("checks", () => {
      it("minProperties", () => {
        assertFromJsonSchema(
          { schema: { type: "object", minProperties: 1 } },
          {
            representation: {
              _tag: "Objects",
              propertySignatures: [],
              indexSignatures: [
                { parameter: { _tag: "String", checks: [] }, type: json() }
              ],
              checks: [{ _tag: "Filter", meta: { _tag: "isMinProperties", minProperties: 1 } }]
            }
          },
          `Schema.Record(Schema.String, Schema.Json).check(Schema.isMinProperties(1))`
        )
      })

      it("maxProperties", () => {
        assertFromJsonSchema(
          { schema: { type: "object", maxProperties: 1 } },
          {
            representation: {
              _tag: "Objects",
              propertySignatures: [],
              indexSignatures: [
                { parameter: { _tag: "String", checks: [] }, type: json() }
              ],
              checks: [{ _tag: "Filter", meta: { _tag: "isMaxProperties", maxProperties: 1 } }]
            }
          },
          `Schema.Record(Schema.String, Schema.Json).check(Schema.isMaxProperties(1))`
        )
      })
    })

    describe("propertyNames", () => {
      it("pattern", () => {
        assertFromJsonSchema(
          {
            schema: {
              type: "object",
              propertyNames: { pattern: "^[A-Z]" }
            }
          },
          {
            representation: {
              _tag: "Objects",
              propertySignatures: [],
              indexSignatures: [
                {
                  parameter: { _tag: "String", checks: [] },
                  type: json()
                }
              ],
              checks: [{
                _tag: "Filter",
                meta: {
                  _tag: "isPropertyNames",
                  propertyNames: {
                    _tag: "String",
                    checks: [{ _tag: "Filter", meta: { _tag: "isPattern", regExp: new RegExp("^[A-Z]") } }]
                  }
                }
              }]
            }
          },
          `Schema.Record(Schema.String, Schema.Json).check(Schema.isPropertyNames(Schema.String.check(Schema.isPattern(new RegExp("^[A-Z]")))))`
        )
      })

      it("false", () => {
        assertFromJsonSchema(
          {
            schema: {
              type: "object",
              propertyNames: false
            }
          },
          {
            representation: {
              _tag: "Objects",
              propertySignatures: [],
              indexSignatures: [
                { parameter: { _tag: "String", checks: [] }, type: json() }
              ],
              checks: [
                { _tag: "Filter", meta: { _tag: "isPropertyNames", propertyNames: { _tag: "Never" } } }
              ]
            }
          },
          `Schema.Record(Schema.String, Schema.Json).check(Schema.isPropertyNames(Schema.Never))`
        )
      })

      it("allOf combines checks", () => {
        assertFromJsonSchema(
          {
            schema: {
              allOf: [
                { type: "object", propertyNames: { pattern: "^[A-Z]" } },
                { type: "object", propertyNames: { minLength: 2 } }
              ]
            }
          },
          {
            representation: {
              _tag: "Objects",
              propertySignatures: [],
              indexSignatures: [
                { parameter: { _tag: "String", checks: [] }, type: json() }
              ],
              checks: [
                {
                  _tag: "Filter",
                  meta: {
                    _tag: "isPropertyNames",
                    propertyNames: {
                      _tag: "String",
                      checks: [{ _tag: "Filter", meta: { _tag: "isPattern", regExp: new RegExp("^[A-Z]") } }]
                    }
                  }
                },
                {
                  _tag: "Filter",
                  meta: {
                    _tag: "isPropertyNames",
                    propertyNames: {
                      _tag: "String",
                      checks: [{ _tag: "Filter", meta: { _tag: "isMinLength", minLength: 2 } }]
                    }
                  }
                }
              ]
            }
          },
          `Schema.Record(Schema.String, Schema.Json).check(Schema.isPropertyNames(Schema.String.check(Schema.isPattern(new RegExp("^[A-Z]"))))).check(Schema.isPropertyNames(Schema.String.check(Schema.isMinLength(2))))`
        )
      })
    })
  })

  it("type: array of strings", () => {
    assertFromJsonSchema(
      {
        schema: { type: ["string", "null"] }
      },
      {
        representation: {
          _tag: "Union",
          types: [{ _tag: "String", checks: [] }, { _tag: "Null" }],
          mode: "anyOf"
        }
      },
      `Schema.Union([Schema.String, Schema.Null])`
    )
    assertFromJsonSchema(
      {
        schema: {
          type: ["string", "null"],
          description: "a"
        }
      },
      {
        representation: {
          _tag: "Union",
          types: [{ _tag: "String", checks: [] }, { _tag: "Null" }],
          mode: "anyOf",
          annotations: { description: "a" }
        }
      },
      `Schema.Union([Schema.String, Schema.Null]).annotate({ "description": "a" })`
    )
  })

  describe("$ref", () => {
    it("should create a Reference and a definition", () => {
      assertFromJsonSchema(
        {
          schema: {
            $ref: "#/$defs/A",
            $defs: {
              A: {
                type: "string"
              }
            }
          }
        },
        {
          representation: { _tag: "Reference", $ref: "A" },
          references: {
            A: {
              _tag: "String",
              checks: []
            }
          }
        }
      )
    })

    it("should resolve the $ref if there are annotations", () => {
      assertFromJsonSchema(
        {
          schema: {
            $ref: "#/$defs/A",
            description: "a",
            $defs: {
              A: {
                type: "string"
              }
            }
          }
        },
        {
          representation: {
            _tag: "String",
            checks: [],
            annotations: { description: "a" }
          },
          references: {
            A: {
              _tag: "String",
              checks: []
            }
          }
        }
      )
    })

    it("should resolve the $ref if there is an allOf", () => {
      assertFromJsonSchema(
        {
          schema: {
            allOf: [
              { $ref: "#/$defs/A" },
              { description: "a" }
            ],
            $defs: {
              A: {
                type: "string"
              }
            }
          }
        },
        {
          representation: {
            _tag: "String",
            checks: [],
            annotations: { description: "a" }
          },
          references: {
            A: {
              _tag: "String",
              checks: []
            }
          }
        }
      )
    })

    it("recursive schema", () => {
      assertFromJsonSchema(
        {
          schema: {
            $ref: "#/$defs/A",
            $defs: {
              A: {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "children": {
                    "type": "array",
                    "items": {
                      "$ref": "#/$defs/A"
                    }
                  }
                },
                "required": [
                  "name",
                  "children"
                ],
                "additionalProperties": false
              }
            }
          }
        },
        {
          representation: { _tag: "Reference", $ref: "A" },
          references: {
            A: {
              _tag: "Objects",
              propertySignatures: [
                {
                  name: "name",
                  type: { _tag: "String", checks: [] },
                  isOptional: false,
                  isMutable: false
                },
                {
                  name: "children",
                  type: {
                    _tag: "Arrays",
                    elements: [],
                    rest: [{
                      _tag: "Suspend",
                      checks: [],
                      thunk: {
                        _tag: "Reference",
                        $ref: "A"
                      }
                    }],
                    checks: []
                  },
                  isOptional: false,
                  isMutable: false
                }
              ],
              indexSignatures: [],
              checks: []
            }
          }
        }
      )
    })
  })

  describe("allOf", () => {
    it("resolves references on either side of an intersection", () => {
      const definition: JsonSchema.JsonSchema = { type: "string", minLength: 1 }
      const expected = {
        representation: {
          _tag: "String" as const,
          checks: [
            { _tag: "Filter" as const, meta: { _tag: "isMinLength" as const, minLength: 1 } },
            { _tag: "Filter" as const, meta: { _tag: "isMaxLength" as const, maxLength: 2 } }
          ]
        },
        references: {
          A: {
            _tag: "String" as const,
            checks: [{ _tag: "Filter" as const, meta: { _tag: "isMinLength" as const, minLength: 1 } }]
          }
        }
      }
      for (
        const schema of [
          {
            $ref: "#/$defs/A",
            allOf: [{ type: "string", maxLength: 2 }],
            $defs: { A: definition }
          },
          {
            allOf: [{ $ref: "#/$defs/A" }, { type: "string", maxLength: 2 }],
            $defs: { A: definition }
          }
        ]
      ) {
        assertFromJsonSchema({ schema }, expected)
      }
    })

    it("preserves annotations on array and object intersections", () => {
      assertFromJsonSchema(
        {
          schema: {
            type: "array",
            allOf: [{ description: "a" }]
          }
        },
        {
          representation: {
            _tag: "Arrays",
            elements: [],
            rest: [json()],
            checks: [],
            annotations: { description: "a" }
          }
        },
        `Schema.Array(Schema.Json).annotate({ "description": "a" })`
      )
      assertFromJsonSchema(
        {
          schema: {
            type: "object",
            additionalProperties: false,
            allOf: [{ description: "a" }]
          }
        },
        {
          representation: {
            _tag: "Objects",
            propertySignatures: [],
            indexSignatures: [],
            checks: [],
            annotations: { description: "a" }
          }
        },
        `Schema.Struct({  }).annotate({ "description": "a" })`
      )
    })

    it("returns Never when a union has no compatible member", () => {
      assertFromJsonSchema(
        {
          schema: {
            type: "string",
            allOf: [{ anyOf: [{ type: "number" }, { type: "boolean" }] }]
          }
        },
        { representation: { _tag: "Never" } },
        `Schema.Never`
      )
    })

    function assertLiteralRefinement(
      refinement: JsonSchema.JsonSchema,
      valid: string | number,
      invalid: string | number
    ) {
      for (const literal of [valid, invalid]) {
        for (const allOf of [[refinement, { const: literal }], [{ const: literal }, refinement]]) {
          const document = SchemaRepresentation.fromJsonSchemaDocument(
            JsonSchema.fromSchemaDraft2020_12({ allOf })
          )
          const is = Schema.is(SchemaRepresentation.toSchema(document))
          if (literal === valid) {
            assertTrue(is(literal))
          } else {
            assertFalse(is(literal))
          }
        }
      }
    }

    describe("literal refinements", () => {
      const cases: ReadonlyArray<
        readonly [
          name: string,
          refinement: JsonSchema.JsonSchema,
          valid: string | number,
          invalid: string | number
        ]
      > = [
        ["minLength", { type: "string", minLength: 2 }, "ab", "a"],
        ["maxLength", { type: "string", maxLength: 1 }, "a", "ab"],
        ["pattern", { type: "string", pattern: "^a+$" }, "aa", "ab"],
        ["integer", { type: "integer" }, 1, 1.5],
        ["multipleOf", { type: "number", multipleOf: 0.1 }, 0.3, 0.31],
        [
          "multipleOf beyond the toFixed precision limit",
          { type: "number", multipleOf: Number("1e-101") },
          0,
          Number("5e-102")
        ],
        ["multipleOf with a large scientific operand", { type: "number", multipleOf: 2 }, Number("1e21"), 1],
        [
          "multipleOf with a nonzero subnormal remainder",
          { type: "number", multipleOf: Number("1e-323") },
          0,
          Number("1.042e-321")
        ],
        ["minimum", { type: "number", minimum: 1 }, 1, 0],
        ["maximum", { type: "number", maximum: 1 }, 1, 2],
        ["exclusiveMinimum", { type: "number", exclusiveMinimum: 1 }, 2, 1],
        ["exclusiveMaximum", { type: "number", exclusiveMaximum: 1 }, 0, 1],
        [
          "filter group",
          { type: "number", allOf: [{ minimum: 1, maximum: 2, description: "range" }] },
          2,
          0
        ]
      ]

      for (const [name, refinement, valid, invalid] of cases) {
        it(name, () => {
          assertLiteralRefinement(refinement, valid, invalid)
        })
      }

      it("filters enum members", () => {
        const expected = {
          representation: {
            _tag: "Union" as const,
            types: [{ _tag: "Literal" as const, literal: "ab" }],
            mode: "anyOf" as const
          }
        }
        for (
          const [schema, member] of [
            [{ type: "string", minLength: 2 }, { enum: ["a", "ab"] }],
            [{ enum: ["a", "ab"] }, { type: "string", minLength: 2 }]
          ]
        ) {
          assertFromJsonSchema(
            { schema: { ...schema, allOf: [member] } },
            expected,
            `Schema.Literal("ab")`
          )
        }
      })
    })

    it("no type", () => {
      assertFromJsonSchema(
        {
          schema: {
            allOf: [
              { type: "string" }
            ]
          }
        },
        {
          representation: {
            _tag: "String",
            checks: []
          }
        },
        `Schema.String`
      )
    })

    describe("type: string", () => {
      it("& minLength", () => {
        assertFromJsonSchema(
          {
            schema: {
              type: "string",
              allOf: [
                { minLength: 1 }
              ]
            }
          },
          {
            representation: {
              _tag: "String",
              checks: [
                { _tag: "Filter", meta: { _tag: "isMinLength", minLength: 1 } }
              ]
            }
          },
          `Schema.String.check(Schema.isMinLength(1))`
        )
      })

      it("& minLength + description", () => {
        assertFromJsonSchema(
          {
            schema: {
              type: "string",
              allOf: [
                { minLength: 1, description: "b" }
              ]
            }
          },
          {
            representation: {
              _tag: "String",
              checks: [
                { _tag: "Filter", meta: { _tag: "isMinLength", minLength: 1 }, annotations: { description: "b" } }
              ]
            }
          },
          `Schema.String.check(Schema.isMinLength(1, { "description": "b" }))`
        )
      })

      it("description & minLength", () => {
        assertFromJsonSchema(
          {
            schema: {
              type: "string",
              description: "a",
              allOf: [
                { minLength: 1 }
              ]
            }
          },
          {
            representation: {
              _tag: "String",
              checks: [
                { _tag: "Filter", meta: { _tag: "isMinLength", minLength: 1 } }
              ],
              annotations: { description: "a" }
            }
          },
          `Schema.String.annotate({ "description": "a" }).check(Schema.isMinLength(1))`
        )
      })

      it("description & description", () => {
        assertFromJsonSchema(
          {
            schema: {
              type: "string",
              description: "a",
              allOf: [
                { description: "b" }
              ]
            }
          },
          {
            representation: {
              _tag: "String",
              checks: [],
              annotations: { description: "b" }
            }
          },
          `Schema.String.annotate({ "description": "b" })`
        )
      })

      it("description & minLength + description", () => {
        assertFromJsonSchema(
          {
            schema: {
              type: "string",
              description: "a",
              allOf: [
                { minLength: 1, description: "b" }
              ]
            }
          },
          {
            representation: {
              _tag: "String",
              checks: [
                { _tag: "Filter", meta: { _tag: "isMinLength", minLength: 1 }, annotations: { description: "b" } }
              ],
              annotations: { description: "a" }
            }
          },
          `Schema.String.annotate({ "description": "a" }).check(Schema.isMinLength(1, { "description": "b" }))`
        )
      })

      it("maxLength & minLength", () => {
        assertFromJsonSchema(
          {
            schema: {
              type: "string",
              maxLength: 2,
              allOf: [
                { minLength: 1 }
              ]
            }
          },
          {
            representation: {
              _tag: "String",
              checks: [
                { _tag: "Filter", meta: { _tag: "isMaxLength", maxLength: 2 } },
                { _tag: "Filter", meta: { _tag: "isMinLength", minLength: 1 } }
              ]
            }
          },
          `Schema.String.check(Schema.isMaxLength(2)).check(Schema.isMinLength(1))`
        )
      })

      it("description + maxLength & minLength", () => {
        assertFromJsonSchema(
          {
            schema: {
              type: "string",
              description: "a",
              maxLength: 2,
              allOf: [
                { minLength: 1 }
              ]
            }
          },
          {
            representation: {
              _tag: "String",
              checks: [
                { _tag: "Filter", meta: { _tag: "isMaxLength", maxLength: 2 } },
                { _tag: "Filter", meta: { _tag: "isMinLength", minLength: 1 } }
              ],
              annotations: { description: "a" }
            }
          },
          `Schema.String.annotate({ "description": "a" }).check(Schema.isMaxLength(2)).check(Schema.isMinLength(1))`
        )
      })

      it("description + maxLength & minLength + description", () => {
        assertFromJsonSchema(
          {
            schema: {
              type: "string",
              description: "a",
              maxLength: 2,
              allOf: [
                { minLength: 1, description: "b" }
              ]
            }
          },
          {
            representation: {
              _tag: "String",
              checks: [
                { _tag: "Filter", meta: { _tag: "isMaxLength", maxLength: 2 } },
                { _tag: "Filter", meta: { _tag: "isMinLength", minLength: 1 }, annotations: { description: "b" } }
              ],
              annotations: { description: "a" }
            }
          },
          `Schema.String.annotate({ "description": "a" }).check(Schema.isMaxLength(2)).check(Schema.isMinLength(1, { "description": "b" }))`
        )
      })

      it("& minLength + maxLength", () => {
        assertFromJsonSchema(
          {
            schema: {
              type: "string",
              allOf: [
                { minLength: 1, maxLength: 2 }
              ]
            }
          },
          {
            representation: {
              _tag: "String",
              checks: [
                { _tag: "Filter", meta: { _tag: "isMinLength", minLength: 1 } },
                { _tag: "Filter", meta: { _tag: "isMaxLength", maxLength: 2 } }
              ]
            }
          },
          `Schema.String.check(Schema.isMinLength(1)).check(Schema.isMaxLength(2))`
        )
      })

      it("& minLength + maxLength + description", () => {
        assertFromJsonSchema(
          {
            schema: {
              type: "string",
              allOf: [
                { minLength: 1, maxLength: 2, description: "b" }
              ]
            }
          },
          {
            representation: {
              _tag: "String",
              checks: [
                {
                  _tag: "FilterGroup",
                  checks: [
                    { _tag: "Filter", meta: { _tag: "isMinLength", minLength: 1 } },
                    { _tag: "Filter", meta: { _tag: "isMaxLength", maxLength: 2 } }
                  ],
                  annotations: { description: "b" }
                }
              ]
            }
          },
          `Schema.String.check(Schema.makeFilterGroup([Schema.isMinLength(1), Schema.isMaxLength(2)], { "description": "b" }))`
        )
      })

      it("& (minLength & maxLength + description)", () => {
        assertFromJsonSchema(
          {
            schema: {
              type: "string",
              allOf: [
                { minLength: 1, allOf: [{ maxLength: 2, description: "c" }] }
              ]
            }
          },
          {
            representation: {
              _tag: "String",
              checks: [
                { _tag: "Filter", meta: { _tag: "isMinLength", minLength: 1 } },
                { _tag: "Filter", meta: { _tag: "isMaxLength", maxLength: 2 }, annotations: { description: "c" } }
              ]
            }
          },
          `Schema.String.check(Schema.isMinLength(1)).check(Schema.isMaxLength(2, { "description": "c" }))`
        )
      })

      it("& (minLength + description & maxLength + description)", () => {
        assertFromJsonSchema(
          {
            schema: {
              type: "string",
              allOf: [
                { minLength: 1, description: "b", allOf: [{ maxLength: 2, description: "c" }] }
              ]
            }
          },
          {
            representation: {
              _tag: "String",
              checks: [
                {
                  _tag: "FilterGroup",
                  checks: [
                    { _tag: "Filter", meta: { _tag: "isMinLength", minLength: 1 } },
                    { _tag: "Filter", meta: { _tag: "isMaxLength", maxLength: 2 }, annotations: { description: "c" } }
                  ],
                  annotations: { description: "b" }
                }
              ]
            }
          },
          `Schema.String.check(Schema.makeFilterGroup([Schema.isMinLength(1), Schema.isMaxLength(2, { "description": "c" })], { "description": "b" }))`
        )
      })

      it("& string enum", () => {
        assertFromJsonSchema(
          {
            schema: {
              type: "string",
              allOf: [
                { enum: ["a"] }
              ]
            }
          },
          {
            representation: {
              _tag: "Literal",
              literal: "a"
            }
          },
          `Schema.Literal("a")`
        )
        assertFromJsonSchema(
          {
            schema: {
              type: "string",
              description: "a",
              allOf: [
                { enum: ["a"], description: "b" }
              ]
            }
          },
          {
            representation: {
              _tag: "Literal",
              literal: "a",
              annotations: { description: "b" }
            }
          },
          `Schema.Literal("a").annotate({ "description": "b" })`
        )
        assertFromJsonSchema(
          {
            schema: {
              type: "string",
              allOf: [
                { enum: ["a", "b"] }
              ]
            }
          },
          {
            representation: {
              _tag: "Union",
              types: [
                { _tag: "Literal", literal: "a" },
                { _tag: "Literal", literal: "b" }
              ],
              mode: "anyOf"
            }
          },
          `Schema.Literals(["a", "b"])`
        )
      })

      it("& mixed enum", () => {
        assertFromJsonSchema(
          {
            schema: {
              type: "string",
              allOf: [
                { enum: ["a", 1] }
              ]
            }
          },
          {
            representation: {
              _tag: "Union",
              types: [
                { _tag: "Literal", literal: "a" }
              ],
              mode: "anyOf"
            }
          },
          `Schema.Literal("a")`
        )
      })
    })

    describe("type: number", () => {
      it("number & integer", () => {
        assertFromJsonSchema(
          {
            schema: {
              type: "number",
              allOf: [
                { type: "integer" }
              ]
            }
          },
          {
            representation: {
              _tag: "Number",
              checks: [
                { _tag: "Filter", meta: { _tag: "isFinite" } },
                { _tag: "Filter", meta: { _tag: "isInt" } }
              ]
            }
          },
          `Schema.Number.check(Schema.isFinite()).check(Schema.isInt())`
        )
      })

      it("number & integer & integer", () => {
        assertFromJsonSchema(
          {
            schema: {
              type: "number",
              allOf: [
                { type: "integer", minimum: 2 },
                { type: "integer", maximum: 2 }
              ]
            }
          },
          {
            representation: {
              _tag: "Number",
              checks: [
                { _tag: "Filter", meta: { _tag: "isFinite" } },
                { _tag: "Filter", meta: { _tag: "isInt" } },
                { _tag: "Filter", meta: { _tag: "isGreaterThanOrEqualTo", minimum: 2 } },
                { _tag: "Filter", meta: { _tag: "isLessThanOrEqualTo", maximum: 2 } }
              ]
            }
          },
          `Schema.Number.check(Schema.isFinite()).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(2)).check(Schema.isLessThanOrEqualTo(2))`
        )
      })

      it("integer & number", () => {
        assertFromJsonSchema(
          {
            schema: {
              type: "integer",
              allOf: [
                { type: "number" }
              ]
            }
          },
          {
            representation: {
              _tag: "Number",
              checks: [
                { _tag: "Filter", meta: { _tag: "isInt" } },
                { _tag: "Filter", meta: { _tag: "isFinite" } }
              ]
            }
          },
          `Schema.Number.check(Schema.isInt()).check(Schema.isFinite())`
        )
      })

      it("& (minimum + description & maximum + description)", () => {
        assertFromJsonSchema(
          {
            schema: {
              type: "number",
              allOf: [
                { minimum: 1, description: "b", allOf: [{ maximum: 2, description: "c" }] }
              ]
            }
          },
          {
            representation: {
              _tag: "Number",
              checks: [
                { _tag: "Filter", meta: { _tag: "isFinite" } },
                {
                  _tag: "FilterGroup",
                  checks: [
                    { _tag: "Filter", meta: { _tag: "isGreaterThanOrEqualTo", minimum: 1 } },
                    {
                      _tag: "Filter",
                      meta: { _tag: "isLessThanOrEqualTo", maximum: 2 },
                      annotations: { description: "c" }
                    }
                  ],
                  annotations: { description: "b" }
                }
              ]
            }
          },
          `Schema.Number.check(Schema.isFinite()).check(Schema.makeFilterGroup([Schema.isGreaterThanOrEqualTo(1), Schema.isLessThanOrEqualTo(2, { "description": "c" })], { "description": "b" }))`
        )
      })

      it("& number enum", () => {
        assertFromJsonSchema(
          {
            schema: {
              type: "number",
              allOf: [
                { enum: [1] }
              ]
            }
          },
          {
            representation: {
              _tag: "Literal",
              literal: 1
            }
          },
          `Schema.Literal(1)`
        )
        assertFromJsonSchema(
          {
            schema: {
              type: "number",
              description: "a",
              allOf: [
                { enum: [1], description: "b" }
              ]
            }
          },
          {
            representation: {
              _tag: "Literal",
              literal: 1,
              annotations: { description: "b" }
            }
          },
          `Schema.Literal(1).annotate({ "description": "b" })`
        )
        assertFromJsonSchema(
          {
            schema: {
              type: "number",
              allOf: [
                { enum: [1, 2] }
              ]
            }
          },
          {
            representation: {
              _tag: "Union",
              types: [
                { _tag: "Literal", literal: 1 },
                { _tag: "Literal", literal: 2 }
              ],
              mode: "anyOf"
            }
          },
          `Schema.Literals([1, 2])`
        )
      })
    })

    describe("type: boolean", () => {
      it("& boolean enum", () => {
        assertFromJsonSchema(
          {
            schema: {
              type: "boolean",
              allOf: [
                { enum: [true] }
              ]
            }
          },
          {
            representation: {
              _tag: "Literal",
              literal: true
            }
          },
          `Schema.Literal(true)`
        )
        assertFromJsonSchema(
          {
            schema: {
              type: "boolean",
              description: "a",
              allOf: [
                { enum: [true], description: "b" }
              ]
            }
          },
          {
            representation: {
              _tag: "Literal",
              literal: true,
              annotations: { description: "b" }
            }
          },
          `Schema.Literal(true).annotate({ "description": "b" })`
        )
      })
    })

    describe("type: array", () => {
      function assertArrayAllOf(
        a: JsonSchema.JsonSchema,
        b: JsonSchema.JsonSchema,
        expected: Parameters<typeof assertFromJsonSchema>[1],
        runtime: string,
        valid: ReadonlyArray<unknown>,
        invalid: ReadonlyArray<unknown>
      ) {
        for (const [schema, member] of [[a, b], [b, a]]) {
          const document = assertFromJsonSchema(
            { schema: { ...schema, allOf: [member] } },
            expected,
            runtime
          )
          const is = Schema.is(SchemaRepresentation.toSchema(document))
          for (const value of valid) {
            assertTrue(is(value))
          }
          for (const value of invalid) {
            assertFalse(is(value))
          }
        }
      }

      it("uniqueItems & uniqueItems", () => {
        assertFromJsonSchema(
          {
            schema: {
              type: "array",
              uniqueItems: true,
              allOf: [
                { uniqueItems: true }
              ]
            }
          },
          {
            representation: {
              _tag: "Arrays",
              elements: [],
              rest: [json()],
              checks: [{ _tag: "Filter", meta: { _tag: "isUnique" } }]
            }
          },
          `Schema.Array(Schema.Json).check(Schema.isUnique())`
        )
      })

      it("combines unequal open prefixes", () => {
        assertArrayAllOf(
          {
            type: "array",
            prefixItems: [{ type: "string" }],
            minItems: 1,
            items: { type: "string" }
          },
          {
            type: "array",
            prefixItems: [{ type: "string" }, { const: "tail" }],
            minItems: 2,
            items: { type: "string" }
          },
          {
            representation: {
              _tag: "Arrays",
              elements: [
                { isOptional: false, type: { _tag: "String", checks: [] } },
                { isOptional: false, type: { _tag: "Literal", literal: "tail" } }
              ],
              rest: [{ _tag: "String", checks: [] }],
              checks: []
            }
          },
          `Schema.TupleWithRest(Schema.Tuple([Schema.String, Schema.Literal("tail")]), [Schema.String])`,
          [["head", "tail"], ["head", "tail", "more"]],
          [["head"], ["head", "other"], ["head", "tail", 1]]
        )
      })

      it("truncates optional elements forbidden by a closed tuple", () => {
        assertArrayAllOf(
          {
            type: "array",
            prefixItems: [{ type: "string" }],
            minItems: 1,
            maxItems: 1
          },
          {
            type: "array",
            prefixItems: [{ type: "string" }, { type: "number" }],
            minItems: 1,
            maxItems: 2
          },
          {
            representation: {
              _tag: "Arrays",
              elements: [{ isOptional: false, type: { _tag: "String", checks: [] } }],
              rest: [],
              checks: []
            }
          },
          `Schema.Tuple([Schema.String])`,
          [["head"]],
          [[], ["head", 1]]
        )
      })

      it("returns Never when a closed tuple forbids a required element", () => {
        assertArrayAllOf(
          {
            type: "array",
            prefixItems: [{ type: "string" }],
            minItems: 1,
            maxItems: 1
          },
          {
            type: "array",
            prefixItems: [{ type: "string" }, { type: "number" }],
            minItems: 2,
            maxItems: 2
          },
          { representation: { _tag: "Never" } },
          `Schema.Never`,
          [],
          [[], ["head"], ["head", 1]]
        )
      })

      it("combines an open rest with a closed rest", () => {
        assertArrayAllOf(
          {
            type: "array",
            prefixItems: [{ type: "string" }],
            minItems: 1,
            items: { type: "number" }
          },
          {
            type: "array",
            prefixItems: [{ type: "string" }],
            minItems: 1,
            maxItems: 1
          },
          {
            representation: {
              _tag: "Arrays",
              elements: [{ isOptional: false, type: { _tag: "String", checks: [] } }],
              rest: [],
              checks: []
            }
          },
          `Schema.Tuple([Schema.String])`,
          [["head"]],
          [[], ["head", 1]]
        )
      })

      it("closes the tuple when the rest intersection is Never", () => {
        assertArrayAllOf(
          {
            type: "array",
            prefixItems: [{ type: "string" }],
            minItems: 1,
            items: { type: "string" }
          },
          {
            type: "array",
            prefixItems: [{ type: "string" }],
            minItems: 1,
            items: { type: "number" }
          },
          {
            representation: {
              _tag: "Arrays",
              elements: [{ isOptional: false, type: { _tag: "String", checks: [] } }],
              rest: [],
              checks: []
            }
          },
          `Schema.Tuple([Schema.String])`,
          [["head"]],
          [[], ["head", "tail"], ["head", 1]]
        )
      })

      it("rejects a required literal that fails rest refinements", () => {
        assertArrayAllOf(
          {
            type: "array",
            minItems: 1,
            items: { const: 0 }
          },
          {
            type: "array",
            prefixItems: [{ type: "number", minimum: 1 }],
            minItems: 1,
            maxItems: 1
          },
          { representation: { _tag: "Never" } },
          `Schema.Never`,
          [],
          [[], [0]]
        )
      })

      it("truncates an optional literal that fails rest refinements", () => {
        assertArrayAllOf(
          {
            type: "array",
            items: { const: 0 }
          },
          {
            type: "array",
            prefixItems: [{ type: "number", minimum: 1 }],
            maxItems: 1
          },
          {
            representation: {
              _tag: "Arrays",
              elements: [],
              rest: [],
              checks: []
            }
          },
          `Schema.Tuple([])`,
          [[]],
          [[0]]
        )
      })

      it("preserves a literal that satisfies rest refinements", () => {
        assertArrayAllOf(
          {
            type: "array",
            minItems: 1,
            items: { const: 2 }
          },
          {
            type: "array",
            prefixItems: [{ type: "number", minimum: 1 }],
            minItems: 1,
            maxItems: 1
          },
          {
            representation: {
              _tag: "Arrays",
              elements: [{ isOptional: false, type: { _tag: "Literal", literal: 2 } }],
              rest: [],
              checks: [{ _tag: "Filter", meta: { _tag: "isMinLength", minLength: 1 } }]
            }
          },
          `Schema.Tuple([Schema.Literal(2)]).check(Schema.isMinLength(1))`,
          [[2]],
          [[], [0]]
        )
      })
    })

    describe("type: object", () => {
      it("add properties", () => {
        assertFromJsonSchema(
          {
            schema: {
              type: "object",
              additionalProperties: false,
              allOf: [
                { properties: { a: { type: "string" } } }
              ]
            }
          },
          {
            representation: {
              _tag: "Objects",
              propertySignatures: [
                {
                  name: "a",
                  type: { _tag: "String", checks: [] },
                  isOptional: true,
                  isMutable: false
                }
              ],
              indexSignatures: [],
              checks: []
            }
          },
          `Schema.Struct({ "a": Schema.optionalKey(Schema.String) })`
        )
      })

      it("add additionalProperties", () => {
        assertFromJsonSchema(
          {
            schema: {
              type: "object",
              allOf: [
                { additionalProperties: { type: "boolean" } }
              ]
            }
          },
          {
            representation: {
              _tag: "Objects",
              propertySignatures: [],
              indexSignatures: [
                { parameter: { _tag: "String", checks: [] }, type: { _tag: "Boolean" } }
              ],
              checks: []
            }
          },
          `Schema.Record(Schema.String, Schema.Boolean)`
        )
      })
    })
  })

  describe("options", () => {
    describe("onEnter", () => {
      it("additionalProperties false via onEnter", () => {
        assertFromJsonSchema(
          {
            schema: {
              type: "object",
              properties: {
                a: { type: "string" }
              },
              required: ["a"]
            },
            options: {
              onEnter: (js) => {
                if (js.type === "object" && js.additionalProperties === undefined) {
                  return { ...js, additionalProperties: false }
                }
                return js
              }
            }
          },
          {
            representation: {
              _tag: "Objects",
              propertySignatures: [
                {
                  name: "a",
                  type: { _tag: "String", checks: [] },
                  isOptional: false,
                  isMutable: false
                }
              ],
              indexSignatures: [],
              checks: []
            }
          },
          `Schema.Struct({ "a": Schema.String })`
        )
      })

      it("strips annotation keys via onEnter", () => {
        assertFromJsonSchema(
          {
            schema: {
              title: "a",
              description: "b",
              examples: ["d"]
            },
            options: {
              onEnter: (js) => {
                const out = { ...js }
                delete out.examples
                return out
              }
            }
          },
          {
            representation: json({
              title: "a",
              description: "b"
            })
          },
          `Schema.Json.annotate({ "title": "a", "description": "b" })`
        )
      })

      it("filters annotations by predicate via onEnter", () => {
        assertFromJsonSchema(
          {
            schema: {
              title: "a",
              description: "b",
              examples: ["d"],
              default: "c"
            },
            options: {
              onEnter: (js) => {
                const out: any = {}
                for (const [k, v] of Object.entries(js)) {
                  if (k === "title" || k === "default" || k === "type") out[k] = v
                }
                return out
              }
            }
          },
          {
            representation: json({
              title: "a",
              default: "c"
            })
          },
          `Schema.Json.annotate({ "title": "a", "default": "c" })`
        )
      })

      it("default preserves all annotations", () => {
        assertFromJsonSchema(
          {
            schema: {
              title: "a",
              description: "b",
              default: "c",
              examples: ["d"]
            }
          },
          {
            representation: json({
              title: "a",
              description: "b",
              default: "c",
              examples: ["d"]
            })
          },
          `Schema.Json.annotate({ "title": "a", "description": "b", "default": "c", "examples": ["d"] })`
        )
      })
    })
  })
})
