import { BigDecimal, Chunk, DateTime, Effect, HashMap, HashSet, Option, Order, Schema, SchemaIssue } from "effect"
import { FastCheck, TestSchema } from "effect/testing"
import { describe, it } from "vitest"
import { assertInclude, assertInstanceOf, deepStrictEqual, strictEqual, throws } from "../utils/assert.ts"

function assertUnsupportedSchema(schema: Schema.Constraint, message: string) {
  throws(() => Schema.toArbitrary(schema), message)
}

function verifyGeneration<S extends Schema.ConstraintCodec<unknown, unknown>>(schema: S, numRuns?: number) {
  const asserts = new TestSchema.Asserts(schema)
  if (numRuns === undefined) {
    asserts.arbitrary().verifyGeneration()
  } else {
    asserts.arbitrary().verifyGeneration({ params: { numRuns } })
  }
}

// Guard for "fast but wrong" regressions: samples the derived arbitrary and
// asserts an output invariant (length/size/property-count bounds) over many runs.
function assertInvariant(schema: Schema.Constraint, predicate: (value: any) => boolean, numRuns = 200) {
  FastCheck.assert(FastCheck.property(Schema.toArbitrary(schema), predicate), { numRuns })
}

function assertRecursiveNoFiniteGenerationPath(schema: Schema.Constraint) {
  throws(
    () => Schema.toArbitrary(schema),
    (e) => {
      assertInstanceOf(e, Error)
      assertInclude(
        e.message,
        "Unable to derive an arbitrary for a recursive schema without a finite generation path"
      )
    }
  )
}

function minSizeOne<A>(size: (a: A) => number) {
  return Schema.makeFilter((a: A) => size(a) >= 1, {
    expected: "a value with a size of at least 1",
    arbitrary: {
      constraint: {
        minLength: 1
      }
    }
  })
}

function CustomArray<A extends Schema.Constraint>(
  value: A,
  toArbitrary: Schema.Annotations.ToArbitrary.Declaration<ReadonlyArray<A["Type"]>, readonly [A]>
) {
  return Schema.declareConstructor<ReadonlyArray<A["Type"]>>()(
    [value],
    () => (input, ast) =>
      globalThis.Array.isArray(input)
        ? Effect.succeed(input as ReadonlyArray<A["Type"]>)
        : Effect.fail(new SchemaIssue.InvalidType(ast, Option.some(input))),
    { toArbitrary }
  )
}

describe("Arbitrary generation", () => {
  describe("Thrown errors", () => {
    it("Declaration", () => {
      assertUnsupportedSchema(
        Schema.Struct({ a: Schema.instanceOf(globalThis.URL) }),
        `Unsupported AST Declaration
  at ["a"]`
      )
    })

    it("Never", () => {
      assertUnsupportedSchema(
        Schema.Struct({ a: Schema.Never }),
        `Unsupported AST Never
  at ["a"]`
      )
    })

    it("impossible object property constraints", () => {
      assertUnsupportedSchema(
        Schema.Struct({ a: Schema.Number }).check(Schema.isMinProperties(2)),
        "Unable to derive an arbitrary for object property constraints"
      )
    })

    it("impossible object max property constraints", () => {
      assertUnsupportedSchema(
        Schema.Struct({ a: Schema.Number }).check(Schema.isMaxProperties(0)),
        "Unable to derive an arbitrary for object property constraints"
      )
    })

    it("impossible number constraints", () => {
      assertUnsupportedSchema(
        Schema.Number.check(Schema.isGreaterThan(1), Schema.isLessThan(1)),
        "Unable to derive an arbitrary for number constraints"
      )
    })

    it("impossible integer constraints", () => {
      assertUnsupportedSchema(
        Schema.Int.check(Schema.isGreaterThan(1), Schema.isLessThan(2)),
        "Unable to derive an arbitrary for integer constraints"
      )
    })

    it("impossible ordered bigint constraints", () => {
      assertUnsupportedSchema(
        Schema.BigInt.check(Schema.isGreaterThanBigInt(1n), Schema.isLessThanBigInt(2n)),
        "Unable to derive an arbitrary for the ordered bigint constraints"
      )
    })

    it("impossible array constraints", () => {
      assertUnsupportedSchema(
        Schema.Array(Schema.String).check(Schema.isMinLength(2), Schema.isMaxLength(1)),
        "Unable to derive an arbitrary for array constraints"
      )
    })

    it("impossible size constraints", () => {
      assertUnsupportedSchema(
        Schema.ReadonlySet(Schema.String).check(Schema.isMinSize(2), Schema.isMaxSize(1)),
        "Unable to derive an arbitrary for size constraints"
      )
    })
  })

  it("should pass the constraint to the override annotation", () => {
    let constraint: Schema.Annotations.ToArbitrary.GenerationConstraint | undefined
    const schema = Schema.Int.check(Schema.isBetween({ minimum: 1, maximum: 100 })).annotate({
      toArbitrary: () => (fc, ctx) => {
        constraint = ctx.constraint
        return fc.constant(1)
      }
    })
    verifyGeneration(schema)
    deepStrictEqual(constraint, {
      integer: true,
      ordered: {
        order: Order.Number,
        minimum: 1,
        maximum: 100
      }
    })
  })

  it("should keep noNaN and noInfinity as separate number constraints", () => {
    const constraints: Array<unknown> = []
    const fc = {
      ...FastCheck,
      float: (constraint?: Parameters<typeof FastCheck.float>[0]) => {
        constraints.push(constraint)
        return FastCheck.constant(0)
      }
    } as typeof FastCheck
    const noNaN = Schema.makeFilter((n: number) => !globalThis.Number.isNaN(n), {
      arbitrary: {
        constraint: {
          noNaN: true
        }
      }
    })
    const noInfinity = Schema.makeFilter(
      (n: number) => n !== globalThis.Number.POSITIVE_INFINITY && n !== globalThis.Number.NEGATIVE_INFINITY,
      {
        arbitrary: {
          constraint: {
            noInfinity: true
          }
        }
      }
    )

    Schema.toArbitraryLazy(Schema.Number.check(noNaN))(fc)
    Schema.toArbitraryLazy(Schema.Number.check(noInfinity))(fc)
    Schema.toArbitraryLazy(Schema.Finite)(fc)

    deepStrictEqual(constraints, [
      { noNaN: true },
      { noDefaultInfinity: true },
      { noDefaultInfinity: true, noNaN: true }
    ])
  })

  describe("report and candidates", () => {
    it("should use filter candidates with the merged constraint context", () => {
      let constraint: Schema.Annotations.ToArbitrary.GenerationConstraint | undefined
      const schema = Schema.String.check(
        Schema.isMinLength(9),
        Schema.makeFilter((s: string) => s === "candidate", {
          expected: "candidate",
          arbitrary: {
            candidate: {
              make: (fc, ctx) => {
                constraint = ctx.constraint
                return fc.constant("candidate")
              }
            }
          }
        })
      )
      const result = Schema.toArbitrary(schema, { report: true })

      deepStrictEqual(result.report.warnings, [])
      deepStrictEqual(constraint, { minLength: 9 })
      FastCheck.assert(FastCheck.property(result.value, (s) => s === "candidate"), { numRuns: 20 })
    })

    it("should allow candidates to be disabled for a context", () => {
      let calls = 0
      const schema = Schema.String.check(
        Schema.makeFilter(() => true, {
          arbitrary: {
            candidate: {
              make: () => {
                calls++
                return undefined
              }
            }
          }
        })
      )
      const result = Schema.toArbitrary(schema, { report: true })

      strictEqual(calls, 1)
      deepStrictEqual(result.report.warnings, [])
    })

    it("should fail fast for invalid candidate weights", () => {
      const makeSchema = (weight: number) =>
        Schema.String.check(
          Schema.makeFilter(() => true, {
            arbitrary: {
              candidate: {
                weight,
                make: (fc) => fc.constant("candidate")
              }
            }
          })
        )

      throws(
        () => Schema.toArbitrary(makeSchema(0)),
        "Unable to derive an arbitrary for a candidate with an invalid weight"
      )
      throws(
        () => Schema.toArbitrary(makeSchema(0.5)),
        "Unable to derive an arbitrary for a candidate with an invalid weight"
      )
    })

    it("should report opaque filters", () => {
      const schema = Schema.Struct({
        a: Schema.String.check(Schema.makeFilter((s: string) => s.length > 0, { expected: "a custom string" }))
      })
      const result = Schema.toArbitrary(schema, { report: true })

      deepStrictEqual(result.report.warnings, [
        { _tag: "OpaqueFilter", path: ["a"], description: "a custom string" }
      ])
      FastCheck.assert(FastCheck.property(result.value, (a) => a.a.length > 0), { numRuns: 5 })
    })

    it("should not report child filters when a filter group provides arbitrary metadata", () => {
      const schema = Schema.String.check(
        Schema.makeFilterGroup(
          [
            Schema.makeFilter((s: string) => s.startsWith("a"), { expected: "starts with a" }),
            Schema.makeFilter((s: string) => s.endsWith("a"), { expected: "ends with a" })
          ],
          {
            arbitrary: {
              candidate: {
                make: (fc) => fc.constant("a")
              }
            }
          }
        )
      )
      const result = Schema.toArbitrary(schema, { report: true })

      deepStrictEqual(result.report.warnings, [])
      FastCheck.assert(FastCheck.property(result.value, (s) => s === "a"), { numRuns: 20 })
    })

    it("should not report warnings for constructive built-in filters", () => {
      const schema = Schema.Struct({
        string: Schema.String.check(Schema.isMinLength(1), Schema.isStartsWith("a")),
        number: Schema.Int.check(Schema.isBetween({ minimum: 1, maximum: 10 })),
        array: Schema.Array(Schema.String).check(Schema.isMinLength(1), Schema.isUnique()),
        object: Schema.Record(Schema.String, Schema.Number).check(Schema.isMinProperties(1), Schema.isMaxProperties(3)),
        set: Schema.ReadonlySet(Schema.String).check(Schema.isMinSize(1), Schema.isMaxSize(3))
      })
      const result = Schema.toArbitrary(schema, { report: true })

      deepStrictEqual(result.report.warnings, [])
    })
  })

  describe("object property counts", () => {
    it("enforces minProperties on optional-only structs", () => {
      const schema = Schema.Struct({
        a: Schema.optionalKey(Schema.String),
        b: Schema.optionalKey(Schema.String),
        c: Schema.optionalKey(Schema.String)
      }).check(Schema.isMinProperties(2))
      FastCheck.assert(
        FastCheck.property(Schema.toArbitrary(schema), (o) => globalThis.Object.keys(o).length >= 2),
        { numRuns: 100 }
      )
      verifyGeneration(schema)
    })

    it("enforces maxProperties on optional-only structs", () => {
      const schema = Schema.Struct({
        a: Schema.optionalKey(Schema.String),
        b: Schema.optionalKey(Schema.String),
        c: Schema.optionalKey(Schema.String)
      }).check(Schema.isMaxProperties(1))
      FastCheck.assert(
        FastCheck.property(Schema.toArbitrary(schema), (o) => globalThis.Object.keys(o).length <= 1),
        { numRuns: 100 }
      )
      verifyGeneration(schema)
    })

    it("enforces minProperties with symbol optional keys", () => {
      const key = Symbol.for("toArbitrary/optional")
      const schema = Schema.Struct({
        [key]: Schema.optionalKey(Schema.String),
        b: Schema.optionalKey(Schema.String)
      }).check(Schema.isMinProperties(2))
      FastCheck.assert(
        FastCheck.property(Schema.toArbitrary(schema), (o) =>
          globalThis.Reflect.ownKeys(o).length >= 2 &&
          globalThis.Object.prototype.hasOwnProperty.call(o, key)),
        { numRuns: 100 }
      )
      verifyGeneration(schema)
    })

    it("enforces a property-count range with required and optional keys", () => {
      const schema = Schema.Struct({
        r: Schema.String,
        a: Schema.optionalKey(Schema.String),
        b: Schema.optionalKey(Schema.String),
        c: Schema.optionalKey(Schema.String)
      }).check(Schema.isPropertiesLengthBetween(2, 3))
      FastCheck.assert(
        FastCheck.property(Schema.toArbitrary(schema), (o) => {
          const n = globalThis.Object.keys(o).length
          return n >= 2 && n <= 3
        }),
        { numRuns: 100 }
      )
      verifyGeneration(schema)
    })

    it("fails fast when optional keys cannot satisfy minProperties", () => {
      assertUnsupportedSchema(
        Schema.Struct({ r: Schema.String, a: Schema.optionalKey(Schema.String) }).check(Schema.isMinProperties(3)),
        "Unable to derive an arbitrary for object property constraints"
      )
    })

    it("fails fast when required keys already exceed maxProperties", () => {
      assertUnsupportedSchema(
        Schema.Struct({ r: Schema.String, a: Schema.optionalKey(Schema.String) }).check(Schema.isMaxProperties(0)),
        "Unable to derive an arbitrary for object property constraints"
      )
    })

    // Regression guard: with the previous discard-based generation, requiring
    // every optional key to be present is astronomically unlikely (~0.75 ** 64),
    // so sampling would effectively hang. The count-controlled subset generates
    // these instantly.
    it("generates dense optional-key structs without excessive filtering", () => {
      const fields: Record<string, Schema.optionalKey<typeof Schema.String>> = {}
      for (let i = 0; i < 64; i++) {
        fields[`k${i}`] = Schema.optionalKey(Schema.String)
      }
      const schema = Schema.Struct(fields).check(Schema.isMinProperties(64))
      FastCheck.assert(
        FastCheck.property(Schema.toArbitrary(schema), (o) => globalThis.Object.keys(o).length === 64),
        { numRuns: 100 }
      )
    })
  })

  it("Any", () => {
    verifyGeneration(Schema.Any)
  })

  it("Unknown", () => {
    verifyGeneration(Schema.Unknown)
  })

  it("Void", () => {
    verifyGeneration(Schema.Void)
  })

  it("Null", () => {
    verifyGeneration(Schema.Null)
  })

  it("String", () => {
    verifyGeneration(Schema.String)
  })

  it("Number", () => {
    verifyGeneration(Schema.Number)
  })

  it("Boolean", () => {
    verifyGeneration(Schema.Boolean)
  })

  it("BigInt", () => {
    verifyGeneration(Schema.BigInt)
  })

  it("Symbol", () => {
    verifyGeneration(Schema.Symbol)
  })

  it("UniqueSymbol", () => {
    verifyGeneration(Schema.UniqueSymbol(Symbol.for("a")))
  })

  it("ObjectKeyword", () => {
    verifyGeneration(Schema.ObjectKeyword)
  })

  describe("Literal", () => {
    it("string", () => {
      verifyGeneration(Schema.Literal("a"))
    })

    it("number", () => {
      verifyGeneration(Schema.Literal(1))
    })

    it("boolean", () => {
      verifyGeneration(Schema.Literal(true))
    })

    it("bigint", () => {
      verifyGeneration(Schema.Literal(1n))
    })
  })

  it("Literals", () => {
    verifyGeneration(Schema.Literals(["a", "b", "c"]))
  })

  describe("TemplateLiteral", () => {
    it("a", () => {
      const schema = Schema.TemplateLiteral([Schema.Literal("a")])
      verifyGeneration(schema)
    })

    it("a b", () => {
      const schema = Schema.TemplateLiteral([Schema.Literal("a"), Schema.Literal(" "), Schema.Literal("b")])
      verifyGeneration(schema)
    })

    it("a${string}", () => {
      const schema = Schema.TemplateLiteral([Schema.Literal("a"), Schema.String])
      verifyGeneration(schema)
    })

    it("a${number}", () => {
      const schema = Schema.TemplateLiteral([Schema.Literal("a"), Schema.Number])
      verifyGeneration(schema)
    })

    it("${number} excludes non-finite values", () => {
      const schema = Schema.TemplateLiteral([Schema.Number])
      assertInvariant(schema, (s) => s !== "NaN" && s !== "Infinity" && s !== "-Infinity")
    })

    it("${number | \"a\"} excludes non-finite values", () => {
      const schema = Schema.TemplateLiteral([Schema.Union([Schema.Number, Schema.Literal("a")])])
      assertInvariant(schema, (s) => s !== "NaN" && s !== "Infinity" && s !== "-Infinity")
    })

    it("a", () => {
      const schema = Schema.TemplateLiteral([Schema.Literal("a")])
      verifyGeneration(schema)
    })

    it("${string}", () => {
      const schema = Schema.TemplateLiteral([Schema.String])
      verifyGeneration(schema)
    })

    it("a${string}b", () => {
      const schema = Schema.TemplateLiteral([Schema.Literal("a"), Schema.String, Schema.Literal("b")])
      verifyGeneration(schema)
    })

    it("user_${uuid}", () => {
      const schema = Schema.TemplateLiteral(["user_", Schema.String.check(Schema.isUUID())])
      verifyGeneration(schema)
    })

    it("https://www.typescriptlang.org/docs/handbook/2/template-literal-types.html", async () => {
      const EmailLocaleIDs = Schema.Literals(["welcome_email", "email_heading"])
      const FooterLocaleIDs = Schema.Literals(["footer_title", "footer_sendoff"])
      const schema = Schema.TemplateLiteral([Schema.Union([EmailLocaleIDs, FooterLocaleIDs]), "_id"])
      verifyGeneration(schema)
    })

    it("< + h + (1|2) + >", async () => {
      const schema = Schema.TemplateLiteral([
        Schema.Literal("<"),
        Schema.TemplateLiteral([Schema.Literal("h"), Schema.Union([Schema.Literal(1), Schema.Literal(2)])]),
        Schema.Literal(">")
      ])
      verifyGeneration(schema)
    })
  })

  describe("Enum", () => {
    it("Numeric enum", () => {
      enum Fruits {
        Apple,
        Banana
      }
      verifyGeneration(Schema.Enum(Fruits))
    })

    it("String enum", () => {
      enum Fruits {
        Apple = "apple",
        Banana = "banana",
        Cantaloupe = 0
      }
      verifyGeneration(Schema.Enum(Fruits))
    })

    it("Const enum", () => {
      const Fruits = {
        Apple: "apple",
        Banana: "banana",
        Cantaloupe: 3
      } as const
      verifyGeneration(Schema.Enum(Fruits))
    })
  })

  it("Union", () => {
    verifyGeneration(
      Schema.Union([Schema.String, Schema.Number])
    )
  })

  describe("Tuple", () => {
    it("empty", () => {
      verifyGeneration(
        Schema.Tuple([])
      )
    })

    it("required element", () => {
      verifyGeneration(
        Schema.Tuple([Schema.String])
      )
      verifyGeneration(
        Schema.Tuple([Schema.String, Schema.Number])
      )
    })

    it("optionalKey element", () => {
      verifyGeneration(
        Schema.Tuple([Schema.optionalKey(Schema.Number)])
      )
      verifyGeneration(
        Schema.Tuple([Schema.String, Schema.optionalKey(Schema.Number)])
      )
    })

    it("optional element", () => {
      verifyGeneration(
        Schema.Tuple([Schema.optional(Schema.Number)])
      )
      verifyGeneration(
        Schema.Tuple([Schema.String, Schema.optional(Schema.Number)])
      )
    })
  })

  describe("Array", () => {
    it("Array", () => {
      verifyGeneration(Schema.Array(Schema.String))
    })
  })

  it("TupleWithRest", () => {
    verifyGeneration(
      Schema.TupleWithRest(Schema.Tuple([Schema.Boolean]), [Schema.Number, Schema.String])
    )
    verifyGeneration(
      Schema.TupleWithRest(Schema.Tuple([]), [Schema.Number, Schema.String])
    )
    verifyGeneration(
      Schema.TupleWithRest(Schema.Tuple([Schema.optionalKey(Schema.Boolean)]), [Schema.Number]).check(
        Schema.isMinLength(3)
      )
    )
  })

  describe("Struct", () => {
    it("empty", () => {
      verifyGeneration(Schema.Struct({}))
    })

    it("required fields", () => {
      verifyGeneration(Schema.Struct({
        a: Schema.String
      }))
      verifyGeneration(Schema.Struct({
        a: Schema.String,
        b: Schema.Number
      }))
    })

    it("required field with undefined", () => {
      verifyGeneration(Schema.Struct({
        a: Schema.UndefinedOr(Schema.String)
      }))
    })

    it("optionalKey field", () => {
      verifyGeneration(Schema.Struct({
        a: Schema.optionalKey(Schema.String)
      }))
    })

    it("optional field", () => {
      verifyGeneration(Schema.Struct({
        a: Schema.optional(Schema.String)
      }))
    })
  })

  describe("Record", () => {
    it("Record(String, Number)", () => {
      verifyGeneration(Schema.Record(Schema.String, Schema.Number))
    })

    it("Record(Symbol, Number)", () => {
      verifyGeneration(Schema.Record(Schema.Symbol, Schema.Number))
    })
  })

  it("StructWithRest", () => {
    verifyGeneration(Schema.StructWithRest(
      Schema.Struct({ a: Schema.Number }),
      [Schema.Record(Schema.String, Schema.Number)]
    ))
    verifyGeneration(Schema.StructWithRest(
      Schema.Struct({ a: Schema.Number }),
      [Schema.Record(Schema.Symbol, Schema.Number)]
    ))
  })

  describe("Class", () => {
    it("Class", () => {
      class A extends Schema.Class<A>("A")({
        a: Schema.String
      }) {}
      const schema = A
      verifyGeneration(schema)
    })
  })

  describe("suspend", () => {
    it("Tuple", () => {
      const Rec = Schema.suspend((): Schema.Codec<unknown> => schema)
      const schema = Schema.Tuple([
        Schema.Number,
        Schema.NullOr(Rec)
      ])
      verifyGeneration(schema)
    })

    it("Array", () => {
      const Rec = Schema.suspend((): Schema.Codec<unknown> => schema)
      const schema: any = Schema.Array(Schema.Union([Schema.String, Rec]))
      verifyGeneration(schema)
    })

    it("Struct", () => {
      const Rec = Schema.suspend((): Schema.Codec<unknown> => schema)
      const schema = Schema.Struct({
        a: Schema.String,
        as: Schema.Array(Rec)
      })
      verifyGeneration(schema)
    })

    it("Record", () => {
      const Rec = Schema.suspend((): Schema.Codec<unknown> => schema)
      const schema = Schema.Record(Schema.String, Rec)
      verifyGeneration(schema)
    })

    it("optional", () => {
      const Rec = Schema.suspend((): Schema.Codec<unknown> => schema)
      const schema: any = Schema.Struct({
        a: Schema.optional(Rec)
      })
      verifyGeneration(schema)
    })

    it("Array + Array", () => {
      const Rec = Schema.suspend((): Schema.Codec<unknown> => schema)
      const schema: any = Schema.Struct({
        a: Schema.Array(Rec),
        b: Schema.Array(Rec)
      })
      verifyGeneration(schema)
    })

    it("optional + Array", () => {
      const Rec = Schema.suspend((): Schema.Codec<unknown> => schema)
      const schema: any = Schema.Struct({
        a: Schema.optional(Rec),
        b: Schema.Array(Rec)
      })
      verifyGeneration(schema)
    })

    it("required recursive field without finite generation path", () => {
      const Rec = Schema.suspend((): Schema.Codec<unknown> => schema)
      const schema: any = Schema.Struct({
        a: Rec
      })
      throws(
        () => Schema.toArbitrary(schema),
        (e) => {
          assertInstanceOf(e, Error)
          assertInclude(
            e.message,
            "Unable to derive an arbitrary for a recursive schema without a finite generation path"
          )
          assertInclude(e.message, `at ["a"]`)
        }
      )
    })

    it("non-empty recursive array without finite generation path", () => {
      const Rec = Schema.suspend((): Schema.Codec<unknown> => schema)
      const schema: any = Schema.Array(Rec).check(Schema.isMinLength(1))
      throws(
        () => Schema.toArbitrary(schema),
        (e) => {
          assertInstanceOf(e, Error)
          assertInclude(
            e.message,
            "Unable to derive an arbitrary for a recursive schema without a finite generation path"
          )
          assertInclude(e.message, "at [0]")
        }
      )
    })

    it("optional-key recursive tuple made non-empty without finite generation path", () => {
      const Rec = Schema.suspend((): Schema.Codec<unknown> => schema)
      const schema: any = Schema.Tuple([Schema.optionalKey(Rec)]).check(Schema.isMinLength(1))
      assertRecursiveNoFiniteGenerationPath(schema)
    })

    it("optional-key recursive struct made non-empty without finite generation path", () => {
      const Rec = Schema.suspend((): Schema.Codec<unknown> => schema)
      const schema: any = Schema.Struct({
        a: Schema.optionalKey(Rec)
      }).check(Schema.isMinProperties(1))
      assertRecursiveNoFiniteGenerationPath(schema)
    })

    it("purely recursive tuple rest made non-empty without finite generation path", () => {
      const Rec = Schema.suspend((): Schema.Codec<unknown> => schema)
      const schema: any = Schema.TupleWithRest(Schema.Tuple([Schema.String]), [Rec]).check(Schema.isMinLength(2))
      assertRecursiveNoFiniteGenerationPath(schema)
    })

    // Regression guard: the terminal rest branch must honor the remaining
    // minLength after fixed elements. Otherwise it generates an empty rest, the
    // value fails the minLength filter, and sampling hangs.
    it("non-empty recursive tuple rest with a finite union branch", () => {
      const Rec = Schema.suspend((): Schema.Codec<unknown> => schema)
      const schema: any = Schema.TupleWithRest(
        Schema.Tuple([Schema.String]),
        [Schema.Union([Schema.Number, Rec])]
      ).check(Schema.isMinLength(2))
      FastCheck.assert(
        FastCheck.property(Schema.toArbitrary(schema), (a) => (a as Array<unknown>).length >= 2),
        { numRuns: 100 }
      )
    })

    it("should use filter candidates in recursive terminal paths", () => {
      const Leaf = Schema.String.check(
        Schema.makeFilter((s: string) => s === "leaf", {
          expected: "leaf",
          arbitrary: {
            candidate: {
              make: (fc) => fc.constant("leaf")
            }
          }
        })
      )
      const Rec = Schema.suspend((): Schema.Codec<unknown> => schema)
      const schema: any = Schema.Union([
        Leaf,
        Schema.Array(Rec).check(Schema.isMinLength(1))
      ])

      verifyGeneration(schema, 20)
    })

    it("non-empty recursive ReadonlySet without finite generation path", () => {
      const Rec = Schema.suspend((): Schema.Codec<unknown> => schema)
      const schema: any = Schema.ReadonlySet(Rec).check(Schema.isMinSize(1))
      assertRecursiveNoFiniteGenerationPath(schema)
    })

    it("non-empty recursive HashSet without finite generation path", () => {
      const Rec = Schema.suspend((): Schema.Codec<unknown> => schema)
      const schema: any = Schema.HashSet(Rec).check(minSizeOne(HashSet.size))
      assertRecursiveNoFiniteGenerationPath(schema)
    })

    it("non-empty recursive Chunk without finite generation path", () => {
      const Rec = Schema.suspend((): Schema.Codec<unknown> => schema)
      const schema: any = Schema.Chunk(Rec).check(minSizeOne(Chunk.size))
      assertRecursiveNoFiniteGenerationPath(schema)
    })

    it("non-empty recursive ReadonlyMap without finite generation path", () => {
      const Rec = Schema.suspend((): Schema.Codec<unknown> => schema)
      const schema: any = Schema.ReadonlyMap(Schema.String, Rec).check(Schema.isMinSize(1))
      assertRecursiveNoFiniteGenerationPath(schema)
    })

    it("non-empty recursive HashMap without finite generation path", () => {
      const Rec = Schema.suspend((): Schema.Codec<unknown> => schema)
      const schema: any = Schema.HashMap(Schema.String, Rec).check(minSizeOne(HashMap.size))
      assertRecursiveNoFiniteGenerationPath(schema)
    })

    it("non-empty recursive collections with finite union branches", () => {
      const SetRec = Schema.suspend((): Schema.Codec<unknown> => setSchema)
      const setSchema: any = Schema.ReadonlySet(Schema.Union([Schema.String, SetRec])).check(Schema.isMinSize(1))
      verifyGeneration(setSchema, 20)

      const HashSetRec = Schema.suspend((): Schema.Codec<unknown> => hashSetSchema)
      const hashSetSchema: any = Schema.HashSet(Schema.Union([Schema.String, HashSetRec])).check(
        minSizeOne(HashSet.size)
      )
      verifyGeneration(hashSetSchema, 20)

      const ChunkRec = Schema.suspend((): Schema.Codec<unknown> => chunkSchema)
      const chunkSchema: any = Schema.Chunk(Schema.Union([Schema.String, ChunkRec])).check(minSizeOne(Chunk.size))
      verifyGeneration(chunkSchema, 20)

      const MapRec = Schema.suspend((): Schema.Codec<unknown> => mapSchema)
      const mapSchema: any = Schema.ReadonlyMap(Schema.String, Schema.Union([Schema.Number, MapRec])).check(
        Schema.isMinSize(1)
      )
      verifyGeneration(mapSchema, 20)

      const HashMapRec = Schema.suspend((): Schema.Codec<unknown> => hashMapSchema)
      const hashMapSchema: any = Schema.HashMap(Schema.String, Schema.Union([Schema.Number, HashMapRec])).check(
        minSizeOne(HashMap.size)
      )
      verifyGeneration(hashMapSchema, 20)
    })

    it("custom generic declaration with an explicit terminal branch", () => {
      const Rec = Schema.suspend((): Schema.Codec<unknown> => schema)
      const schema: any = CustomArray(Rec, ([value]) => (fc, ctx) => {
        const terminal = fc.constant([])
        const arbitrary = fc.array(value.arbitrary, { maxLength: 2 })
        return {
          arbitrary: ctx.recursion === undefined ? arbitrary : fc.oneof(ctx.recursion, terminal, arbitrary),
          terminal
        }
      })

      verifyGeneration(schema, 20)
    })

    it("custom generic declaration without a terminal branch", () => {
      const Rec = Schema.suspend((): Schema.Codec<unknown> => schema)
      const schema: any = CustomArray(Rec, ([value]) => (fc) => {
        return fc.array(value.arbitrary, { minLength: 1, maxLength: 1 })
      })

      assertRecursiveNoFiniteGenerationPath(schema)
    })

    it("mutually suspended schemas", () => {
      interface Expression {
        readonly type: "expression"
        readonly value: number | Operation
      }

      interface Operation {
        readonly type: "operation"
        readonly operator: "+" | "-"
        readonly left: Expression
        readonly right: Expression
      }

      const Expression = Schema.Struct({
        type: Schema.Literal("expression"),
        value: Schema.Union([Schema.Finite, Schema.suspend((): Schema.Codec<Operation> => Operation)])
      })

      const Operation = Schema.Struct({
        type: Schema.Literal("operation"),
        operator: Schema.Literals(["+", "-"]),
        left: Expression,
        right: Expression
      })
      verifyGeneration(Operation)
    })

    it("Option", () => {
      const Rec = Schema.suspend((): Schema.Codec<unknown> => schema)
      const schema = Schema.Struct({
        a: Schema.String,
        as: Schema.Option(Rec)
      })
      verifyGeneration(schema)
    })

    it("ReadonlySet", () => {
      const Rec = Schema.suspend((): Schema.Codec<unknown> => schema)
      const schema = Schema.ReadonlySet(Rec)
      verifyGeneration(schema)
    })

    it("ReadonlyMap", () => {
      const Rec = Schema.suspend((): Schema.Codec<unknown> => schema)
      const schema = Schema.ReadonlyMap(Schema.String, Rec)
      verifyGeneration(schema)
    })

    it("HashMap", () => {
      const Rec = Schema.suspend((): Schema.Codec<unknown> => schema)
      const schema = Schema.HashMap(Schema.String, Rec)
      verifyGeneration(schema)
    })
  })

  // Extended safety net for the recursion × constraint code paths (terminal
  // length minimization, optional inclusion, rest minLength, constraint
  // merging). Each test asserts an output invariant over many runs: a refactor
  // that makes generation "fast but wrong" (a value that violates the
  // constraint) fails here cleanly.
  describe("recursion × constraint guards", () => {
    it("recursive array with a finite branch honors minLength", () => {
      const Rec = Schema.suspend((): Schema.Codec<unknown> => schema)
      const schema: any = Schema.Array(Schema.Union([Schema.String, Rec])).check(Schema.isMinLength(2))
      assertInvariant(schema, (a) => globalThis.Array.isArray(a) && a.length >= 2)
    })

    it("recursive tuple rest with a post-rest element honors minLength", () => {
      const Rec = Schema.suspend((): Schema.Codec<unknown> => schema)
      const schema: any = Schema.TupleWithRest(
        Schema.Tuple([Schema.String]),
        [Schema.Union([Schema.Number, Rec]), Schema.Boolean]
      ).check(Schema.isMinLength(3))
      assertInvariant(schema, (a) => (a as Array<unknown>).length >= 3)
    })

    it("recursive struct with required + optional self-ref honors minProperties", () => {
      const Rec = Schema.suspend((): Schema.Codec<unknown> => schema)
      const schema: any = Schema.Struct({
        a: Schema.String,
        self: Schema.optionalKey(Rec)
      }).check(Schema.isMinProperties(1))
      assertInvariant(
        schema,
        (o) => globalThis.Object.keys(o).length >= 1 && globalThis.Object.prototype.hasOwnProperty.call(o, "a")
      )
    })

    it("recursive record value with a finite branch honors minProperties", () => {
      const Rec = Schema.suspend((): Schema.Codec<unknown> => schema)
      const schema: any = Schema.Record(Schema.String, Schema.Union([Schema.Number, Rec])).check(
        Schema.isMinProperties(1)
      )
      assertInvariant(schema, (o) => globalThis.Object.keys(o).length >= 1)
    })

    it("recursive ReadonlySet with a finite branch honors minSize", () => {
      const Rec = Schema.suspend((): Schema.Codec<unknown> => schema)
      const schema: any = Schema.ReadonlySet(Schema.Union([Schema.String, Rec])).check(Schema.isMinSize(1))
      assertInvariant(schema, (s: any) => s.size >= 1)
    })

    it("recursive ReadonlyMap with a finite branch honors minSize", () => {
      const Rec = Schema.suspend((): Schema.Codec<unknown> => schema)
      const schema: any = Schema.ReadonlyMap(Schema.String, Schema.Union([Schema.Number, Rec])).check(
        Schema.isMinSize(1)
      )
      assertInvariant(schema, (m: any) => m.size >= 1)
    })

    it("merges multiple minLength filters (takes the maximum)", () => {
      const schema = Schema.Array(Schema.String).check(Schema.isMinLength(2), Schema.isMinLength(4))
      assertInvariant(schema, (a: any) => a.length >= 4)
    })

    it("merges minLength and maxLength filters into a range", () => {
      const schema = Schema.Array(Schema.String).check(Schema.isMinLength(2), Schema.isMaxLength(5))
      assertInvariant(schema, (a: any) => a.length >= 2 && a.length <= 5)
    })

    it("merges ordered numeric bounds from multiple filters", () => {
      const schema = Schema.Number.check(Schema.isGreaterThan(3), Schema.isLessThan(10))
      assertInvariant(schema, (n: any) => n > 3 && n < 10, 300)
    })
  })

  describe("checks", () => {
    it("isMinLength(2)", () => {
      verifyGeneration(Schema.String.pipe(Schema.check(Schema.isMinLength(2))))
      verifyGeneration(Schema.Array(Schema.String).pipe(Schema.check(Schema.isMinLength(2))))
    })

    it("isMaxLength(2)", () => {
      verifyGeneration(Schema.String.pipe(Schema.check(Schema.isMaxLength(2))))
      verifyGeneration(Schema.Array(Schema.String).pipe(Schema.check(Schema.isMaxLength(2))))
    })

    it("isMinLength(2) & isMaxLength(4)", () => {
      verifyGeneration(Schema.String.pipe(Schema.check(Schema.isMinLength(2), Schema.isMaxLength(4))))
      verifyGeneration(
        Schema.Array(Schema.String).pipe(Schema.check(Schema.isMinLength(2), Schema.isMaxLength(4)))
      )
    })

    it("isLength(2)", () => {
      verifyGeneration(Schema.String.pipe(Schema.check(Schema.isLengthBetween(2, 2))))
      verifyGeneration(Schema.Array(Schema.String).pipe(Schema.check(Schema.isLengthBetween(2, 2))))
    })

    it("isMinProperties(2)", () => {
      verifyGeneration(
        Schema.Record(Schema.String, Schema.Number).check(Schema.isMinProperties(2))
      )
    })

    it("isMaxProperties(2)", () => {
      verifyGeneration(
        Schema.Record(Schema.String, Schema.Number).check(Schema.isMaxProperties(2))
      )
    })

    it("isMinProperties(2) & isMaxProperties(4)", () => {
      verifyGeneration(
        Schema.Record(Schema.String, Schema.Number).check(Schema.isMinProperties(2), Schema.isMaxProperties(4))
      )
    })

    it("isPropertiesLengthBetween(2, 2)", () => {
      verifyGeneration(
        Schema.Record(Schema.String, Schema.Number).check(Schema.isPropertiesLengthBetween(2, 2))
      )
    })

    it("isBetween(1, 100)", () => {
      verifyGeneration(Schema.Number.check(Schema.isBetween({ minimum: 1, maximum: 100 })))
    })

    it("isInt", () => {
      verifyGeneration(Schema.Number.check(Schema.isInt()))
    })

    it("isInt & isBetween(1, 100)", () => {
      verifyGeneration(Schema.Int.check(Schema.isBetween({ minimum: 1, maximum: 100 })))
    })

    it("isInt & isGreaterThan(1.2)", () => {
      verifyGeneration(Schema.Int.check(Schema.isGreaterThan(1.2)))
    })

    it("isInt & isLessThan(10.8)", () => {
      verifyGeneration(Schema.Int.check(Schema.isLessThan(10.8)))
    })

    it("isInt & isBetween(1, 10) with exclusive bounds", () => {
      verifyGeneration(Schema.Int.check(Schema.isBetween({
        minimum: 1,
        maximum: 10,
        exclusiveMinimum: true,
        exclusiveMaximum: true
      })))
    })

    it("isInt32", () => {
      verifyGeneration(Schema.Number.check(Schema.isInt32()))
    })

    it("isPattern", () => {
      verifyGeneration(Schema.String.check(Schema.isPattern(/^[A-Z]{3}[0-9]{3}$/)))
    })

    it("isNonEmpty + isPattern", () => {
      verifyGeneration(Schema.NonEmptyString.check(Schema.isPattern(/^[-]*$/)))
    })

    it("isPattern + isPattern", () => {
      verifyGeneration(
        Schema.String.check(Schema.isPattern(/^[^A-Z]*$/), Schema.isPattern(/^0x[0-9a-f]{40}$/))
      )
    })

    it("isGreaterThanOrEqualToDate", () => {
      verifyGeneration(Schema.Date.check(Schema.isGreaterThanOrEqualToDate(new Date(0))))
    })

    it("isGreaterThanDate", () => {
      verifyGeneration(Schema.Date.check(Schema.isGreaterThanDate(new Date(0))))
    })

    it("isLessThanOrEqualToDate", () => {
      verifyGeneration(Schema.Date.check(Schema.isLessThanOrEqualToDate(new Date(10))))
    })

    it("isLessThanDate", () => {
      verifyGeneration(Schema.Date.check(Schema.isLessThanDate(new Date(10))))
    })

    it("isBetweenDate", () => {
      verifyGeneration(Schema.Date.check(Schema.isBetweenDate({ minimum: new Date(0), maximum: new Date(10) })))
    })

    it("isBetweenDate with exclusive bounds", () => {
      verifyGeneration(Schema.Date.check(Schema.isBetweenDate({
        minimum: new Date(0),
        maximum: new Date(10),
        exclusiveMinimum: true,
        exclusiveMaximum: true
      })))
    })

    it("DateValid", () => {
      verifyGeneration(Schema.DateValid)
    })

    it("isGreaterThanOrEqualToBigInt", () => {
      verifyGeneration(Schema.BigInt.check(Schema.isGreaterThanOrEqualToBigInt(BigInt(0))))
    })

    it("isGreaterThanBigInt", () => {
      verifyGeneration(Schema.BigInt.check(Schema.isGreaterThanBigInt(BigInt(0))))
    })

    it("isLessThanOrEqualToBigInt", () => {
      verifyGeneration(Schema.BigInt.check(Schema.isLessThanOrEqualToBigInt(BigInt(10))))
    })

    it("isLessThanBigInt", () => {
      verifyGeneration(Schema.BigInt.check(Schema.isLessThanBigInt(BigInt(10))))
    })

    it("isBetweenBigInt", () => {
      verifyGeneration(Schema.BigInt.check(Schema.isBetweenBigInt({ minimum: BigInt(0), maximum: BigInt(10) })))
    })

    it("isBetweenBigInt with exclusive bounds", () => {
      verifyGeneration(Schema.BigInt.check(Schema.isBetweenBigInt({
        minimum: BigInt(0),
        maximum: BigInt(10),
        exclusiveMinimum: true,
        exclusiveMaximum: true
      })))
    })

    it("isBetweenBigDecimal", () => {
      verifyGeneration(
        Schema.BigDecimal.check(
          Schema.isBetweenBigDecimal({
            minimum: BigDecimal.make(100n, 0),
            maximum: BigDecimal.make(200n, 0)
          })
        )
      )
    })

    it("isBetweenBigDecimal with decimal scale", () => {
      verifyGeneration(
        Schema.BigDecimal.check(
          Schema.isBetweenBigDecimal({
            minimum: BigDecimal.fromStringUnsafe("1.01"),
            maximum: BigDecimal.fromStringUnsafe("1.02")
          })
        )
      )
    })

    it("non-natural Number order", () => {
      const order = Order.flip(Order.Number)
      verifyGeneration(Schema.Finite.check(Schema.makeIsGreaterThan({ order })(0)))
    })

    it("non-natural Int order", () => {
      const order = Order.flip(Order.Number)
      verifyGeneration(Schema.Int.check(Schema.makeIsGreaterThan({ order })(0)))
    })

    it("non-natural Date order", () => {
      const order = Order.flip(Order.Date)
      verifyGeneration(Schema.DateValid.check(Schema.makeIsGreaterThan({ order })(new Date(0))))
    })

    it("non-natural BigInt order", () => {
      const order = Order.flip(Order.BigInt)
      verifyGeneration(Schema.BigInt.check(Schema.makeIsGreaterThan({ order })(BigInt(0))))
    })

    it("non-natural BigDecimal order", () => {
      const order = Order.flip(BigDecimal.Order)
      verifyGeneration(Schema.BigDecimal.check(Schema.makeIsGreaterThan({ order })(BigDecimal.make(0n, 0))))
    })
  })

  it("Finite", () => {
    verifyGeneration(Schema.Finite)
  })

  it("Date", () => {
    verifyGeneration(Schema.Date)
  })

  it("URL", () => {
    verifyGeneration(Schema.URL)
  })

  it("RegExp", () => {
    verifyGeneration(Schema.RegExp)
  })

  it("Duration", () => {
    verifyGeneration(Schema.Duration)
  })

  it("BigDecimal", () => {
    verifyGeneration(Schema.BigDecimal)
  })

  it("DateTimeUtc", () => {
    verifyGeneration(Schema.DateTimeUtc)
  })

  it("DateTimeUtc with ordered DateTime constraints", () => {
    const start = DateTime.makeUnsafe(0)
    verifyGeneration(
      Schema.DateTimeUtc.check(
        Schema.makeIsGreaterThan({ order: DateTime.Order })(start)
      )
    )
  })

  it("DateTimeUtc with non-natural DateTime order", () => {
    const order = Order.flip(DateTime.Order)
    verifyGeneration(
      Schema.DateTimeUtc.check(
        Schema.makeIsGreaterThan({ order })(DateTime.makeUnsafe(0))
      )
    )
  })

  it("TimeZoneOffset", () => {
    verifyGeneration(Schema.TimeZoneOffset)
  })

  it("TimeZoneNamed", () => {
    verifyGeneration(Schema.TimeZoneNamed)
  })

  it("TimeZone", () => {
    verifyGeneration(Schema.TimeZone)
  })

  it("DateTimeZoned", () => {
    verifyGeneration(Schema.DateTimeZoned)
  })

  it("DateTimeZoned with ordered DateTime constraints", () => {
    const start = DateTime.makeZonedUnsafe(0, { timeZone: "UTC" })
    verifyGeneration(
      Schema.DateTimeZoned.check(
        Schema.makeIsGreaterThan({ order: DateTime.Order })(start)
      )
    )
  })

  it("DateTimeZoned with non-natural DateTime order", () => {
    const order = Order.flip(DateTime.Order)
    verifyGeneration(
      Schema.DateTimeZoned.check(
        Schema.makeIsGreaterThan({ order })(DateTime.makeZonedUnsafe(0, { timeZone: "UTC" }))
      )
    )
  })

  it("Uint8Array", () => {
    verifyGeneration(Schema.Uint8Array)
  })

  it("UnknownFromJsonString", () => {
    verifyGeneration(Schema.UnknownFromJsonString)
  })

  it("Option(String)", () => {
    verifyGeneration(Schema.Option(Schema.String))
  })

  it("Result(Number, String)", () => {
    verifyGeneration(Schema.Result(Schema.Number, Schema.String))
  })

  describe("ReadonlySet", () => {
    it("ReadonlySet(Number)", () => {
      verifyGeneration(Schema.ReadonlySet(Schema.Number))
    })
  })

  describe("ReadonlyMap", () => {
    it("ReadonlyMap(String, Number)", () => {
      verifyGeneration(Schema.ReadonlyMap(Schema.String, Schema.Number))
    })

    it("isMinSize(2)", () => {
      verifyGeneration(
        Schema.ReadonlyMap(Schema.String, Schema.Number).check(Schema.isMinSize(2))
      )
    })

    it("isMaxSize(4)", () => {
      verifyGeneration(
        Schema.ReadonlyMap(Schema.String, Schema.Number).check(Schema.isMaxSize(4))
      )
    })

    it("isMinSize(2) & isMaxSize(4)", () => {
      verifyGeneration(
        Schema.ReadonlyMap(Schema.String, Schema.Number).check(Schema.isMinSize(2), Schema.isMaxSize(4))
      )
    })

    it("isSizeBetween(2, 2)", () => {
      verifyGeneration(
        Schema.ReadonlyMap(Schema.String, Schema.Number).check(Schema.isSizeBetween(2, 2))
      )
    })
  })

  describe("HashMap", () => {
    it("HashMap(String, Number)", () => {
      verifyGeneration(Schema.HashMap(Schema.String, Schema.Number))
    })
  })

  describe("Redacted", () => {
    it("Redacted(String)", () => {
      const schema = Schema.Redacted(Schema.String)
      verifyGeneration(schema)
    })

    it("with label", () => {
      const schema = Schema.Redacted(Schema.String, { label: "password" })
      verifyGeneration(schema)
    })
  })

  describe("constraint behavior", () => {
    it("String", () => {
      verifyGeneration(Schema.String)
    })

    it("String & nonEmpty", () => {
      verifyGeneration(Schema.NonEmptyString)
    })

    it("String & isNonEmpty & isMinLength(2)", () => {
      verifyGeneration(Schema.String.check(Schema.isNonEmpty()).check(Schema.isMinLength(2)))
    })

    it("String & isMinLength(2) & isNonEmpty", () => {
      verifyGeneration(Schema.String.check(Schema.isMinLength(2)).check(Schema.isNonEmpty()))
    })

    it("String & isNonEmpty & isMaxLength(2)", () => {
      verifyGeneration(Schema.String.check(Schema.isNonEmpty()).check(Schema.isMaxLength(2)))
    })

    it("String & isLength(2)", () => {
      verifyGeneration(Schema.String.check(Schema.isLengthBetween(2, 2)))
    })

    it("isStartsWith", () => {
      verifyGeneration(Schema.String.check(Schema.isStartsWith("a")))
    })

    it("isEndsWith", () => {
      verifyGeneration(Schema.String.check(Schema.isEndsWith("a")))
    })

    it("Number", () => {
      verifyGeneration(Schema.Number)
    })

    it("isFinite", () => {
      verifyGeneration(Schema.Number.check(Schema.isFinite()))
    })

    it("isInt", () => {
      verifyGeneration(Schema.Number.check(Schema.isInt()))
    })

    it("isFinite & isInt", () => {
      verifyGeneration(Schema.Number.check(Schema.isFinite(), Schema.isInt()))
    })

    it("isInt32", () => {
      verifyGeneration(Schema.Number.check(Schema.isInt32()))
    })

    it("isGreaterThan", () => {
      verifyGeneration(Schema.Number.check(Schema.isGreaterThan(10)))
    })

    it("isBetween", () => {
      verifyGeneration(Schema.Number.check(Schema.isBetween({ minimum: 1, maximum: 10 })))
    })

    it("Number with non-natural order", () => {
      const order = Order.flip(Order.Number)
      verifyGeneration(Schema.Finite.check(Schema.makeIsGreaterThan({ order })(0)))
    })

    it("ordered lower bounds keep the strongest bound", () => {
      verifyGeneration(Schema.Number.check(Schema.isGreaterThan(1), Schema.isGreaterThanOrEqualTo(3)))
    })

    it("ordered lower bounds preserve exclusivity for equal bounds", () => {
      verifyGeneration(Schema.Number.check(Schema.isGreaterThan(1), Schema.isGreaterThanOrEqualTo(1)))
    })

    it("ordered upper bounds keep the strongest bound", () => {
      verifyGeneration(Schema.Number.check(Schema.isLessThan(10), Schema.isLessThanOrEqualTo(8)))
    })

    it("ordered upper bounds preserve exclusivity for equal bounds", () => {
      verifyGeneration(Schema.Number.check(Schema.isLessThan(1), Schema.isLessThanOrEqualTo(1)))
    })

    it("isInt & isGreaterThan", () => {
      verifyGeneration(Schema.Int.check(Schema.isGreaterThan(1)))
    })

    it("isInt & isGreaterThan fractional", () => {
      verifyGeneration(Schema.Int.check(Schema.isGreaterThan(1.2)))
    })

    it("isInt & isLessThan", () => {
      verifyGeneration(Schema.Int.check(Schema.isLessThan(10)))
    })

    it("isInt & isLessThan fractional", () => {
      verifyGeneration(Schema.Int.check(Schema.isLessThan(10.8)))
    })

    it("isInt & isBetween with fractional bounds", () => {
      verifyGeneration(Schema.Int.check(Schema.isBetween({ minimum: 1.2, maximum: 10.8 })))
    })

    it("isInt & isBetween with exclusive bounds", () => {
      verifyGeneration(Schema.Int.check(Schema.isBetween({
        minimum: 1,
        maximum: 10,
        exclusiveMinimum: true,
        exclusiveMaximum: true
      })))
    })

    it("Int with non-natural order", () => {
      const order = Order.flip(Order.Number)
      verifyGeneration(Schema.Int.check(Schema.makeIsGreaterThan({ order })(0)))
    })

    it("isGreaterThanDate", () => {
      verifyGeneration(Schema.Date.check(Schema.isGreaterThanDate(new Date(0))))
    })

    it("isGreaterThanOrEqualToDate", () => {
      verifyGeneration(Schema.Date.check(Schema.isGreaterThanOrEqualToDate(new Date(0))))
    })

    it("isLessThanDate", () => {
      verifyGeneration(Schema.Date.check(Schema.isLessThanDate(new Date(10))))
    })

    it("isLessThanOrEqualToDate", () => {
      verifyGeneration(Schema.Date.check(Schema.isLessThanOrEqualToDate(new Date(10))))
    })

    it("isBetweenDate", () => {
      verifyGeneration(Schema.Date.check(Schema.isBetweenDate({ minimum: new Date(0), maximum: new Date(10) })))
    })

    it("isBetweenDate with exclusive bounds", () => {
      verifyGeneration(Schema.Date.check(Schema.isBetweenDate({
        minimum: new Date(0),
        maximum: new Date(10),
        exclusiveMinimum: true,
        exclusiveMaximum: true
      })))
    })

    it("isValidDate", () => {
      verifyGeneration(Schema.Date.check(Schema.isDateValid()))
    })

    it("isValidDate & isGreaterThanOrEqualToDate", () => {
      verifyGeneration(Schema.Date.check(Schema.isDateValid(), Schema.isGreaterThanOrEqualToDate(new Date(0))))
    })

    it("Date with non-natural order", () => {
      const order = Order.flip(Order.Date)
      verifyGeneration(Schema.DateValid.check(Schema.makeIsGreaterThan({ order })(new Date(0))))
    })

    it("isGreaterThanOrEqualToBigInt", () => {
      verifyGeneration(Schema.BigInt.check(Schema.isGreaterThanOrEqualToBigInt(BigInt(0))))
    })

    it("isGreaterThanBigInt", () => {
      verifyGeneration(Schema.BigInt.check(Schema.isGreaterThanBigInt(BigInt(0))))
    })

    it("isLessThanOrEqualToBigInt", () => {
      verifyGeneration(Schema.BigInt.check(Schema.isLessThanOrEqualToBigInt(BigInt(10))))
    })

    it("isLessThanBigInt", () => {
      verifyGeneration(Schema.BigInt.check(Schema.isLessThanBigInt(BigInt(10))))
    })

    it("isBetweenBigInt", () => {
      verifyGeneration(Schema.BigInt.check(Schema.isBetweenBigInt({ minimum: BigInt(0), maximum: BigInt(10) })))
    })

    it("isBetweenBigInt with exclusive bounds", () => {
      verifyGeneration(Schema.BigInt.check(Schema.isBetweenBigInt({
        minimum: BigInt(0),
        maximum: BigInt(10),
        exclusiveMinimum: true,
        exclusiveMaximum: true
      })))
    })

    it("BigInt with non-natural order", () => {
      const order = Order.flip(Order.BigInt)
      verifyGeneration(Schema.BigInt.check(Schema.makeIsGreaterThan({ order })(BigInt(0))))
    })

    it("isGreaterThanOrEqualToBigDecimal", () => {
      verifyGeneration(Schema.BigDecimal.check(Schema.isGreaterThanOrEqualToBigDecimal(BigDecimal.make(0n, 0))))
    })

    it("isGreaterThanBigDecimal", () => {
      verifyGeneration(Schema.BigDecimal.check(Schema.isGreaterThanBigDecimal(BigDecimal.make(0n, 0))))
    })

    it("isLessThanOrEqualToBigDecimal", () => {
      verifyGeneration(Schema.BigDecimal.check(Schema.isLessThanOrEqualToBigDecimal(BigDecimal.make(10n, 0))))
    })

    it("isLessThanBigDecimal", () => {
      verifyGeneration(Schema.BigDecimal.check(Schema.isLessThanBigDecimal(BigDecimal.make(10n, 0))))
    })

    it("isBetweenBigDecimal", () => {
      verifyGeneration(Schema.BigDecimal.check(Schema.isBetweenBigDecimal({
        minimum: BigDecimal.make(100n, 0),
        maximum: BigDecimal.make(200n, 0)
      })))
    })

    it("isBetweenBigDecimal with decimal scale", () => {
      verifyGeneration(Schema.BigDecimal.check(Schema.isBetweenBigDecimal({
        minimum: BigDecimal.fromStringUnsafe("1.01"),
        maximum: BigDecimal.fromStringUnsafe("1.02")
      })))
    })

    it("isBetweenBigDecimal with exclusive decimal bounds", () => {
      verifyGeneration(
        Schema.BigDecimal.check(Schema.isBetweenBigDecimal({
          minimum: BigDecimal.fromStringUnsafe("1.01"),
          maximum: BigDecimal.fromStringUnsafe("1.02"),
          exclusiveMinimum: true,
          exclusiveMaximum: true
        })),
        1_000
      )
    })

    it("isBetweenBigDecimal with exclusive negative decimal bounds", () => {
      verifyGeneration(
        Schema.BigDecimal.check(Schema.isBetweenBigDecimal({
          minimum: BigDecimal.fromStringUnsafe("-1.02"),
          maximum: BigDecimal.fromStringUnsafe("-1.01"),
          exclusiveMinimum: true,
          exclusiveMaximum: true
        })),
        1_000
      )
    })

    it("isBetweenBigDecimal with a single inclusive decimal value", () => {
      verifyGeneration(
        Schema.BigDecimal.check(Schema.isBetweenBigDecimal({
          minimum: BigDecimal.fromStringUnsafe("1.01"),
          maximum: BigDecimal.fromStringUnsafe("1.01")
        })),
        1_000
      )
    })

    it("isBetweenBigDecimal with exclusive bounds requiring a higher scale", () => {
      verifyGeneration(
        Schema.BigDecimal.check(Schema.isBetweenBigDecimal({
          minimum: BigDecimal.fromStringUnsafe("0"),
          maximum: BigDecimal.fromStringUnsafe("0.00000000000000000001"),
          exclusiveMinimum: true,
          exclusiveMaximum: true
        })),
        1_000
      )
    })

    it("isBetweenBigDecimal with impossible exclusive bounds", () => {
      throws(() =>
        Schema.toArbitrary(Schema.BigDecimal.check(Schema.isBetweenBigDecimal({
          minimum: BigDecimal.fromStringUnsafe("1.01"),
          maximum: BigDecimal.fromStringUnsafe("1.01"),
          exclusiveMinimum: true,
          exclusiveMaximum: true
        }))), "Unable to derive an arbitrary for the ordered BigDecimal constraints")
    })

    it("isGreaterThanBigDecimal + isLessThanBigDecimal with impossible bounds", () => {
      throws(() =>
        Schema.toArbitrary(Schema.BigDecimal.check(
          Schema.isGreaterThanBigDecimal(BigDecimal.fromStringUnsafe("1.01")),
          Schema.isLessThanBigDecimal(BigDecimal.fromStringUnsafe("1.01"))
        )), "Unable to derive an arbitrary for the ordered BigDecimal constraints")
    })

    it("isGreaterThanBigDecimal + isLessThanBigDecimal", () => {
      verifyGeneration(
        Schema.BigDecimal.check(
          Schema.isGreaterThanBigDecimal(BigDecimal.fromStringUnsafe("1.01")),
          Schema.isLessThanBigDecimal(BigDecimal.fromStringUnsafe("1.02"))
        ),
        1_000
      )
    })

    it("BigDecimal with non-natural order", () => {
      const order = Order.flip(BigDecimal.Order)
      verifyGeneration(Schema.BigDecimal.check(Schema.makeIsGreaterThan({ order })(BigDecimal.make(0n, 0))))
    })

    it("DateTimeUtc with non-natural order", () => {
      const order = Order.flip(DateTime.Order)
      verifyGeneration(Schema.DateTimeUtc.check(Schema.makeIsGreaterThan({ order })(DateTime.makeUnsafe(0))))
    })

    it("DateTimeZoned with non-natural order", () => {
      const order = Order.flip(DateTime.Order)
      verifyGeneration(
        Schema.DateTimeZoned.check(
          Schema.makeIsGreaterThan({ order })(DateTime.makeZonedUnsafe(0, { timeZone: "UTC" }))
        )
      )
    })

    it("UniqueArray", () => {
      verifyGeneration(Schema.UniqueArray(Schema.String))
      verifyGeneration(Schema.UniqueArray(Schema.String).check(Schema.isMaxLength(2)))
    })
  })
})
