import {
  Cause,
  DateTime,
  Duration,
  Effect,
  HashMap,
  Option,
  Redacted,
  Result,
  Schema,
  SchemaGetter,
  SchemaIssue,
  SchemaParser,
  SchemaTransformation
} from "effect"
import { TestSchema } from "effect/testing"
import { describe, it } from "vitest"
import { assertTrue, deepStrictEqual, strictEqual, throws } from "../utils/assert.ts"

const isDeno = "Deno" in globalThis

const FiniteFromDate = Schema.Date.pipe(Schema.decodeTo(
  Schema.Number,
  SchemaTransformation.transform({
    decode: (date) => date.getTime(),
    encode: (n) => new Date(n)
  })
))

describe("Serializers", () => {
  describe("toCodecJson", () => {
    it("exposes the source schema", () => {
      const schema = Schema.FiniteFromString
      const serializer = Schema.toCodecJson(schema)
      strictEqual(serializer.schema, schema)
    })

    it("should reorder the types in the Union based on the encoded side", async () => {
      const schema = Schema.Union([
        Schema.String,
        Schema.String.pipe(Schema.encodeTo(Schema.BigInt, {
          decode: SchemaGetter.transform((n: bigint) => String(n) + "a"),
          encode: SchemaGetter.transform(() => 0n)
        }))
      ])
      const serializer = Schema.toCodecJson(schema)
      const asserts = new TestSchema.Asserts(Schema.toCodecJson(serializer))

      const decoding = asserts.decoding()
      await decoding.succeed("1", "1a")
    })

    describe("schemas without encoding", () => {
      describe("Unsupported schemas", () => {
        it("Struct with Symbol property name", () => {
          const a = Symbol.for("a")
          const schema = Schema.Struct({
            [a]: Schema.String
          })
          throws(
            () => Schema.toCodecJson(schema),
            "Objects property names must be strings"
          )
        })
      })

      describe("Declaration", () => {
        it("instanceOf without annotations", async () => {
          const schema = Schema.instanceOf(URL)
          const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))

          const encoding = asserts.encoding()
          await encoding.succeed(new URL("https://effect.website"), null)

          const decoding = asserts.decoding()
          await decoding.fail("https://effect.website/", `Expected null, got "https://effect.website/"`)
        })

        describe("instanceOf with annotation", () => {
          it("arg: message: string", async () => {
            class MyError extends Error {
              constructor(message?: string) {
                super(message)
                this.name = "MyError"
                Object.setPrototypeOf(this, MyError.prototype)
              }
            }

            const schema = Schema.instanceOf(
              MyError,
              {
                title: "MyError",
                toCodec: () =>
                  Schema.link<MyError>()(
                    Schema.String,
                    SchemaTransformation.transform({
                      decode: (message) => new MyError(message),
                      encode: (e) => e.message
                    })
                  )
              }
            )
            const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))

            const encoding = asserts.encoding()
            await encoding.succeed(new MyError("a"), "a")

            const decoding = asserts.decoding()
            await decoding.succeed("a", new MyError("a"))
          })

          it("arg: struct", async () => {
            class MyError extends Error {
              static Props = Schema.Struct({
                message: Schema.String,
                cause: Schema.String
              })

              constructor(props: typeof MyError.Props["Type"]) {
                super(props.message, { cause: props.cause })
                this.name = "MyError"
                Object.setPrototypeOf(this, MyError.prototype)
              }

              static schema = Schema.instanceOf(
                MyError,
                {
                  title: "MyError",
                  toCodec: () =>
                    Schema.link<MyError>()(
                      MyError.Props,
                      SchemaTransformation.transform({
                        decode: (props) => new MyError(props),
                        encode: (e) => ({
                          message: e.message,
                          cause: typeof e.cause === "string" ? e.cause : String(e.cause)
                        })
                      })
                    )
                }
              )
            }

            const schema = MyError.schema
            const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))

            const encoding = asserts.encoding()
            await encoding.succeed(new MyError({ message: "a", cause: "b" }), {
              message: "a",
              cause: "b"
            })

            const decoding = asserts.decoding()
            await decoding.succeed({ message: "a", cause: "b" }, new MyError({ message: "a", cause: "b" }))
          })
        })
      })

      describe("should return the same reference if nothing changed", () => {
        it("Struct", async () => {
          const schema = Schema.Struct({
            a: Schema.String,
            b: Schema.Boolean
          })
          const serializer = Schema.toCodecJson(schema)
          strictEqual(serializer.ast, schema.ast)
        })

        it("Record", async () => {
          const schema = Schema.Record(Schema.String, Schema.Boolean)
          const serializer = Schema.toCodecJson(schema)
          strictEqual(serializer.ast, schema.ast)
        })

        it("Tuple", async () => {
          const schema = Schema.Tuple([Schema.String, Schema.Boolean])
          const serializer = Schema.toCodecJson(schema)
          strictEqual(serializer.ast, schema.ast)
        })

        it("Array", async () => {
          const schema = Schema.Array(Schema.String)
          const serializer = Schema.toCodecJson(schema)
          strictEqual(serializer.ast, schema.ast)
        })

        it("Union", async () => {
          const schema = Schema.Union([Schema.String, Schema.Boolean])
          const serializer = Schema.toCodecJson(schema)
          strictEqual(serializer.ast, schema.ast)
        })
      })

      it("should apply the construction process to the provided link in the serializer annotation", async () => {
        const schema = Schema.Struct({
          a: Schema.Date.annotate({
            toCodec: () =>
              Schema.link<Date>()(
                Schema.Date,
                SchemaTransformation.passthrough()
              )
          }),
          b: Schema.Number
        })
        const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))

        const encoding = asserts.encoding()
        await encoding.succeed({ a: new Date("2021-01-01"), b: 1 }, {
          a: "2021-01-01T00:00:00.000Z",
          b: 1
        })
      })

      it("Never", async () => {
        const schema = Schema.Never
        const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))

        const encoding = asserts.encoding()
        await encoding.fail({}, "Expected never, got {}")
      })

      it("Any", async () => {
        const schema = Schema.Any
        const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))

        const encoding = asserts.encoding()
        await encoding.succeed(() => {})

        const decoding = asserts.decoding()
        await decoding.succeed(() => {})
      })

      it("Unknown", async () => {
        const schema = Schema.Unknown
        const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))

        const encoding = asserts.encoding()
        await encoding.succeed("a")
        await encoding.succeed(1)
        await encoding.succeed(true)
        await encoding.succeed(null)
        await encoding.succeed({ a: "a", b: 1, c: true })
        await encoding.succeed(["a", 1, true])
        await encoding.fail({ a: 1n }, `Expected JSON value, got {"a":1n}`)

        const decoding = asserts.decoding()
        await decoding.succeed("a")
        await decoding.succeed(1)
        await decoding.succeed(true)
        await decoding.succeed(null)
        await decoding.succeed({ a: "a", b: 1, c: true })
        await decoding.succeed(["a", 1, true])
        await decoding.fail({ a: 1n }, `Expected JSON value, got {"a":1n}`)
      })

      it("ObjectKeyword", async () => {
        const schema = Schema.ObjectKeyword
        const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))

        const encoding = asserts.encoding()
        await encoding.succeed({ a: "a", b: 1, c: true })
        await encoding.succeed(["a", 1, true])
        await encoding.fail("a", `Expected object | array | function, got "a"`)
        await encoding.fail({ a: 1n }, `Expected JSON value, got {"a":1n}`)

        const decoding = asserts.decoding()
        await decoding.succeed({ a: "a", b: 1, c: true })
        await decoding.succeed(["a", 1, true])
        await decoding.fail("a", `Expected object | array | function, got "a"`)
        await decoding.fail({ a: 1n }, `Expected JSON value, got {"a":1n}`)
      })

      it("Undefined", async () => {
        const schema = Schema.Undefined
        const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))

        const encoding = asserts.encoding()
        await encoding.succeed(undefined, null)
      })

      it("Void", async () => {
        const schema = Schema.Void
        const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))

        const encoding = asserts.encoding()
        await encoding.succeed(undefined, null)
      })

      it("Null", async () => {
        const schema = Schema.Null
        const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))

        const encoding = asserts.encoding()
        await encoding.succeed(null)
      })

      it("String", async () => {
        const schema = Schema.String
        const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))

        const encoding = asserts.encoding()
        await encoding.succeed("a")

        const decoding = asserts.decoding()
        await decoding.succeed("a", "a")
      })

      describe("Number", () => {
        it("Number", async () => {
          const schema = Schema.Number
          const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))

          const encoding = asserts.encoding()
          await encoding.succeed(1)
          await encoding.succeed(-1)
          await encoding.succeed(1.2)
          await encoding.succeed(Infinity, "Infinity")
          await encoding.succeed(-Infinity, "-Infinity")
          await encoding.succeed(NaN, "NaN")

          const decoding = asserts.decoding()
          await decoding.succeed(1)
          await decoding.succeed(-1)
          await decoding.succeed(1.2)
          await decoding.succeed("Infinity", Infinity)
          await decoding.succeed("-Infinity", -Infinity)
          await decoding.succeed("NaN", NaN)
          await decoding.succeed(Infinity)
          await decoding.succeed(-Infinity)
          await decoding.succeed(NaN)
          await decoding.fail(null, `Expected number | "Infinity" | "-Infinity" | "NaN", got null`)
          await decoding.fail("a", `Expected "Infinity" | "-Infinity" | "NaN", got "a"`)
        })

        describe("checks", () => {
          it("Finite", async () => {
            const schema = Schema.Finite
            const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))

            const encoding = asserts.encoding()
            await encoding.succeed(1)
            await encoding.succeed(-1)
            await encoding.succeed(1.2)
            await encoding.fail(Infinity, "Expected a finite number, got Infinity")
            await encoding.fail(-Infinity, "Expected a finite number, got -Infinity")
            await encoding.fail(NaN, "Expected a finite number, got NaN")

            const decoding = asserts.decoding()
            await decoding.succeed(1)
            await decoding.succeed(-1)
            await decoding.succeed(1.2)
            await decoding.fail("Infinity", `Expected number, got "Infinity"`)
            await decoding.fail("-Infinity", `Expected number, got "-Infinity"`)
            await decoding.fail("NaN", `Expected number, got "NaN"`)
            await decoding.fail(Infinity, `Expected a finite number, got Infinity`)
            await decoding.fail(-Infinity, `Expected a finite number, got -Infinity`)
            await decoding.fail(NaN, `Expected a finite number, got NaN`)
            await decoding.fail(null, `Expected number, got null`)
            await decoding.fail("a", `Expected number, got "a"`)
          })

          it("Int", async () => {
            const schema = Schema.Int
            const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))

            const encoding = asserts.encoding()
            await encoding.succeed(1)
            await encoding.succeed(-1)
            await encoding.fail(1.2, `Expected an integer, got 1.2`)
            await encoding.fail(Infinity, "Expected an integer, got Infinity")
            await encoding.fail(-Infinity, "Expected an integer, got -Infinity")
            await encoding.fail(NaN, "Expected an integer, got NaN")

            const decoding = asserts.decoding()
            await decoding.succeed(1)
            await decoding.succeed(-1)
            await decoding.fail(1.2, `Expected an integer, got 1.2`)
            await decoding.fail("Infinity", `Expected number, got "Infinity"`)
            await decoding.fail("-Infinity", `Expected number, got "-Infinity"`)
            await decoding.fail("NaN", `Expected number, got "NaN"`)
            await decoding.fail(Infinity, `Expected an integer, got Infinity`)
            await decoding.fail(-Infinity, `Expected an integer, got -Infinity`)
            await decoding.fail(NaN, `Expected an integer, got NaN`)
            await decoding.fail(null, `Expected number, got null`)
            await decoding.fail("a", `Expected number, got "a"`)
          })

          it("isGreaterThanOrEqualTo", async () => {
            const schema = Schema.Number.check(Schema.isGreaterThanOrEqualTo(1))
            const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))

            const encoding = asserts.encoding()
            await encoding.succeed(1)
            await encoding.fail(-1, `Expected a value greater than or equal to 1, got -1`)
            await encoding.succeed(1.2)
            await encoding.succeed(Infinity, "Infinity")
            await encoding.fail(-Infinity, "Expected a value greater than or equal to 1, got -Infinity")
            await encoding.fail(NaN, "Expected a value greater than or equal to 1, got NaN")

            const decoding = asserts.decoding()
            await decoding.succeed(1)
            await encoding.fail(-1, `Expected a value greater than or equal to 1, got -1`)
            await decoding.succeed(1.2)
            await decoding.succeed("Infinity", Infinity)
            await decoding.fail("-Infinity", `Expected a value greater than or equal to 1, got -Infinity`)
            await decoding.fail("NaN", `Expected a value greater than or equal to 1, got NaN`)
            await decoding.succeed(Infinity)
            await decoding.fail(-Infinity, `Expected a value greater than or equal to 1, got -Infinity`)
            await decoding.fail(NaN, `Expected a value greater than or equal to 1, got NaN`)
            await decoding.fail(null, `Expected number | "Infinity" | "-Infinity" | "NaN", got null`)
            await decoding.fail("a", `Expected "Infinity" | "-Infinity" | "NaN", got "a"`)
          })
        })
      })

      it("Boolean", async () => {
        const schema = Schema.Boolean
        const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))

        const encoding = asserts.encoding()
        await encoding.succeed(true)

        const decoding = asserts.decoding()
        await decoding.succeed(true, true)
      })

      it("Symbol", async () => {
        const schema = Schema.Symbol
        const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))

        const encoding = asserts.encoding()
        await encoding.succeed(Symbol.for("a"), "Symbol(a)")
        await encoding.fail(
          Symbol("a"),
          "cannot serialize to string, Symbol is not registered"
        )
        await encoding.fail(
          Symbol(),
          "cannot serialize to string, Symbol is not registered"
        )

        const decoding = asserts.decoding()
        await decoding.succeed("Symbol(a)", Symbol.for("a"))
      })

      it("UniqueSymbol", async () => {
        const schema = Schema.UniqueSymbol(Symbol.for("a"))
        const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))

        const encoding = asserts.encoding()
        await encoding.succeed(Symbol.for("a"), "Symbol(a)")

        const decoding = asserts.decoding()
        await decoding.succeed("Symbol(a)", Symbol.for("a"))
      })

      it("BigInt", async () => {
        const schema = Schema.BigInt
        const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))

        const encoding = asserts.encoding()
        await encoding.succeed(1n, "1")

        const decoding = asserts.decoding()
        await decoding.succeed("1", 1n)
      })

      it("PropertyKey", async () => {
        const schema = Schema.PropertyKey
        const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))

        const encoding = asserts.encoding()
        await encoding.succeed("a")
        await encoding.succeed(1)
        await encoding.succeed(Symbol.for("a"), "Symbol(a)")

        const decoding = asserts.decoding()
        await decoding.succeed("a")
        await decoding.succeed(1)
        await decoding.succeed("Symbol(a)", Symbol.for("a"))
      })

      describe("Literal", () => {
        it("string", async () => {
          const schema = Schema.Literal("a")
          const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))

          const encoding = asserts.encoding()
          await encoding.succeed("a")

          const decoding = asserts.decoding()
          await decoding.succeed("a")
        })

        it("number", async () => {
          const schema = Schema.Literal(1)
          const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))

          const encoding = asserts.encoding()
          await encoding.succeed(1)

          const decoding = asserts.decoding()
          await decoding.succeed(1)
        })

        it("boolean", async () => {
          const schema = Schema.Literal(true)
          const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))

          const encoding = asserts.encoding()
          await encoding.succeed(true)

          const decoding = asserts.decoding()
          await decoding.succeed(true)
        })

        it("bigint", async () => {
          const schema = Schema.Literal(1n)
          const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))

          const encoding = asserts.encoding()
          await encoding.succeed(1n, "1")

          const decoding = asserts.decoding()
          await decoding.succeed("1", 1n)
        })
      })

      it("Literals", async () => {
        const schema = Schema.Literals(["a", 1, 2n, true])
        const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))

        const decoding = asserts.decoding()
        await decoding.fail(
          "-",
          `Expected "a" | 1 | "2" | true, got "-"`
        )
      })

      describe("TemplateLiteral", () => {
        it("1n + string", async () => {
          const schema = Schema.TemplateLiteral([1n, Schema.String])
          const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))

          const encoding = asserts.encoding()
          await encoding.succeed("1a")

          const decoding = asserts.decoding()
          await decoding.succeed("1a")
        })

        it(`"a" + bigint`, async () => {
          const schema = Schema.TemplateLiteral(["a", Schema.BigInt])
          const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))

          const encoding = asserts.encoding()
          await encoding.succeed("a1")

          const decoding = asserts.decoding()
          await decoding.succeed("a1")
        })
      })

      it("Enum", async () => {
        enum Fruits {
          Apple,
          Banana
        }
        const schema = Schema.Enum(Fruits)
        const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))

        const encoding = asserts.encoding()
        await encoding.succeed(Fruits.Apple, 0)
        await encoding.succeed(Fruits.Banana, 1)

        const decoding = asserts.decoding()
        await decoding.succeed(0, Fruits.Apple)
        await decoding.succeed(1, Fruits.Banana)
      })

      describe("Struct", () => {
        it("Date", async () => {
          const schema = Schema.Struct({
            a: Schema.Date
          })
          const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))

          const encoding = asserts.encoding()
          await encoding.succeed(
            { a: new Date("2021-01-01") },
            { a: "2021-01-01T00:00:00.000Z" }
          )
        })

        it("UndefinedOr(Date)", async () => {
          const schema = Schema.Struct({
            a: Schema.UndefinedOr(Schema.Date)
          })
          const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))

          const encoding = asserts.encoding()
          await encoding.succeed({ a: new Date("2021-01-01") }, {
            a: "2021-01-01T00:00:00.000Z"
          })
          await encoding.succeed({ a: undefined }, { a: null })

          const decoding = asserts.decoding()
          await decoding.succeed({ a: "2021-01-01T00:00:00.000Z" }, {
            a: new Date("2021-01-01")
          })
          await decoding.succeed({ a: null }, { a: undefined })
        })

        it("NullOr(Date)", async () => {
          const schema = Schema.Struct({
            a: Schema.NullOr(Schema.Date)
          })
          const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))

          const encoding = asserts.encoding()
          await encoding.succeed({ a: new Date("2021-01-01") }, {
            a: "2021-01-01T00:00:00.000Z"
          })
          await encoding.succeed({ a: null }, { a: null })

          const decoding = asserts.decoding()
          await decoding.succeed({ a: "2021-01-01T00:00:00.000Z" }, {
            a: new Date("2021-01-01")
          })
          await decoding.succeed({ a: null }, { a: null })
        })

        it("optionalKey(Date)", async () => {
          const schema = Schema.Struct({
            a: Schema.optionalKey(Schema.Date)
          })
          const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))

          const encoding = asserts.encoding()
          await encoding.succeed({ a: new Date("2021-01-01") }, {
            a: "2021-01-01T00:00:00.000Z"
          })
          await encoding.succeed({}, {})

          const decoding = asserts.decoding()
          await decoding.succeed({ a: "2021-01-01T00:00:00.000Z" }, {
            a: new Date("2021-01-01")
          })
          await decoding.succeed({}, {})
        })

        it("optional(Date)", async () => {
          const schema = Schema.Struct({
            a: Schema.optional(Schema.Date)
          })
          const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))

          const encoding = asserts.encoding()
          await encoding.succeed({ a: new Date("2021-01-01") }, {
            a: "2021-01-01T00:00:00.000Z"
          })
          await encoding.succeed({}, {})
          await encoding.succeed({ a: undefined }, { a: null })

          const decoding = asserts.decoding()
          await decoding.succeed({ a: "2021-01-01T00:00:00.000Z" }, {
            a: new Date("2021-01-01")
          })
          await decoding.succeed({}, {})
          await decoding.succeed({ a: null }, { a: undefined })
        })
      })

      it("Record(Symbol, Date)", async () => {
        const schema = Schema.Record(Schema.Symbol, Schema.Date)
        const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))

        const encoding = asserts.encoding()
        await encoding.succeed(
          { [Symbol.for("a")]: new Date("2021-01-01"), [Symbol.for("b")]: new Date("2021-01-01") },
          { "Symbol(a)": "2021-01-01T00:00:00.000Z", "Symbol(b)": "2021-01-01T00:00:00.000Z" }
        )

        const decoding = asserts.decoding()
        await decoding.succeed(
          { "Symbol(a)": "2021-01-01T00:00:00.000Z", "Symbol(b)": "2021-01-01T00:00:00.000Z" },
          { [Symbol.for("a")]: new Date("2021-01-01"), [Symbol.for("b")]: new Date("2021-01-01") }
        )
      })

      describe("Tuple", () => {
        it("Date", async () => {
          const schema = Schema.Tuple([Schema.Date])
          const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))

          const encoding = asserts.encoding()
          await encoding.succeed(
            [new Date("2021-01-01")],
            ["2021-01-01T00:00:00.000Z"]
          )

          const decoding = asserts.decoding()
          await decoding.succeed(
            ["2021-01-01T00:00:00.000Z"],
            [new Date("2021-01-01")]
          )
        })

        it("UndefinedOr(Date)", async () => {
          const schema = Schema.Tuple([Schema.UndefinedOr(Schema.Date)])
          const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))

          const encoding = asserts.encoding()
          await encoding.succeed(
            [new Date("2021-01-01")],
            ["2021-01-01T00:00:00.000Z"]
          )
          await encoding.succeed([undefined], [null])

          const decoding = asserts.decoding()
          await decoding.succeed(["2021-01-01T00:00:00.000Z"], [
            new Date("2021-01-01")
          ])
          await decoding.succeed([null], [undefined])
        })

        it("NullOr(Date)", async () => {
          const schema = Schema.Tuple([Schema.NullOr(Schema.Date)])
          const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))

          const encoding = asserts.encoding()
          await encoding.succeed([new Date("2021-01-01")], [
            "2021-01-01T00:00:00.000Z"
          ])
          await encoding.succeed([null], [null])

          const decoding = asserts.decoding()
          await decoding.succeed(["2021-01-01T00:00:00.000Z"], [
            new Date("2021-01-01")
          ])
          await decoding.succeed([null], [null])
        })

        it("optionalKey(Date)", async () => {
          const schema = Schema.Tuple([Schema.optionalKey(Schema.Date)])
          const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))

          const encoding = asserts.encoding()
          await encoding.succeed([new Date("2021-01-01")], [
            "2021-01-01T00:00:00.000Z"
          ])
          await encoding.succeed([], [])

          const decoding = asserts.decoding()
          await decoding.succeed(["2021-01-01T00:00:00.000Z"], [
            new Date("2021-01-01")
          ])
          await decoding.succeed([], [])
        })

        it("optional(Date)", async () => {
          const schema = Schema.Tuple([Schema.optional(Schema.Date)])
          const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))

          const encoding = asserts.encoding()
          await encoding.succeed([new Date("2021-01-01")], [
            "2021-01-01T00:00:00.000Z"
          ])
          await encoding.succeed([], [])
          await encoding.succeed([undefined], [null])

          const decoding = asserts.decoding()
          await decoding.succeed(["2021-01-01T00:00:00.000Z"], [
            new Date("2021-01-01")
          ])
          await decoding.succeed([], [])
          await decoding.succeed([null], [undefined])
        })
      })

      it("Array(Date)", async () => {
        const schema = Schema.Array(Schema.Date)
        const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))

        const encoding = asserts.encoding()
        await encoding.succeed(
          [new Date("2021-01-01"), new Date("2021-01-02")],
          ["2021-01-01T00:00:00.000Z", "2021-01-02T00:00:00.000Z"]
        )
      })

      describe("Union", () => {
        it("NullOr(String)", async () => {
          const schema = Schema.NullOr(Schema.String)
          const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))

          const encoding = asserts.encoding()
          await encoding.succeed("a")
          await encoding.succeed(null)

          const decoding = asserts.decoding()
          await decoding.succeed(null)
          await decoding.succeed("a")
        })

        it("NullOr(Number)", async () => {
          const schema = Schema.NullOr(Schema.Number)
          const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))

          const encoding = asserts.encoding()
          await encoding.succeed(1)
          await encoding.succeed(null)

          const decoding = asserts.decoding()
          await decoding.succeed(null)
          await decoding.succeed(1)
        })

        it("Array(NullOr(Number))", async () => {
          const schema = Schema.Array(Schema.NullOr(Schema.Number))
          const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))

          const encoding = asserts.encoding()
          await encoding.succeed([1, null])

          const decoding = asserts.decoding()
          await decoding.succeed([1, null])
        })

        it("Union(Schema.Date, Finite)", async () => {
          const schema = Schema.Union([Schema.Date, Schema.Finite])
          const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))

          const encoding = asserts.encoding()
          await encoding.succeed(
            new Date("2021-01-01"),
            "2021-01-01T00:00:00.000Z"
          )
          await encoding.succeed(0)
        })
      })

      it("Suspend", async () => {
        interface Category {
          readonly a: number
          readonly categories: ReadonlyArray<Category>
        }

        const schema = Schema.Struct({
          a: Schema.Finite.check(Schema.isGreaterThan(0)),
          categories: Schema.Array(Schema.suspend((): Schema.Codec<Category> => schema))
        })
        const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))

        const encoding = asserts.encoding()
        await encoding.succeed({ a: 1, categories: [] }, {
          a: 1,
          categories: []
        })
        await encoding.succeed({
          a: 1,
          categories: [{ a: 2, categories: [] }]
        }, {
          a: 1,
          categories: [
            { a: 2, categories: [] }
          ]
        })

        const decoding = asserts.decoding()
        await decoding.succeed({
          a: 1,
          categories: []
        }, { a: 1, categories: [] })
        await decoding.succeed({
          a: 1,
          categories: [
            { a: 2, categories: [] }
          ]
        }, { a: 1, categories: [{ a: 2, categories: [] }] })
      })

      it("Class", async () => {
        class A extends Schema.Class<A>("A")(Schema.Struct({
          a: Schema.Finite
        })) {}
        const asserts = new TestSchema.Asserts(Schema.toCodecJson(Schema.toType(A)))

        const encoding = asserts.encoding()
        await encoding.succeed(new A({ a: 0 }), { a: 0 })

        const decoding = asserts.decoding()
        await decoding.succeed({ a: 0 }, new A({ a: 0 }))
      })

      it("ErrorClass", async () => {
        class E extends Schema.ErrorClass<E>("E")({
          a: Schema.Finite
        }) {}
        const asserts = new TestSchema.Asserts(Schema.toCodecJson(Schema.toType(E)))

        const encoding = asserts.encoding()
        await encoding.succeed(new E({ a: 0 }), { a: 0 })

        const decoding = asserts.decoding()
        await decoding.succeed({ a: 0 }, new E({ a: 0 }))
      })

      it("Date", async () => {
        const schema = Schema.Date
        const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))

        const encoding = asserts.encoding()
        await encoding.succeed(
          new Date("2021-01-01"),
          "2021-01-01T00:00:00.000Z"
        )
      })

      it("Error", async () => {
        const schema = Schema.Error()
        const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))

        const encoding = asserts.encoding()
        await encoding.succeed(
          new Error("a"),
          { name: "Error", message: "a" }
        )
        await encoding.succeed(
          new Error("a", { cause: new Error("b") }),
          { name: "Error", message: "a", cause: { name: "Error", message: "b" } }
        )
        await encoding.succeed(
          new Error("a", { cause: "b" }),
          { name: "Error", message: "a", cause: "b" }
        )
        const selfCause = new Error("a")
        selfCause.stack = "stack"
        selfCause.cause = selfCause
        await encoding.succeed(
          selfCause,
          {
            name: "Error",
            message: "a",
            cause: "[Circular]"
          }
        )
        const cyclicCause: Record<string, unknown> = {}
        cyclicCause.self = cyclicCause
        await encoding.succeed(
          new Error("a", { cause: cyclicCause }),
          { name: "Error", message: "a", cause: {} }
        )

        const decoding = asserts.decoding()
        // Error: message only
        await decoding.succeed(
          { message: "a" },
          new Error("a")
        )
        // Error: message and name
        await decoding.succeed(
          { name: "b", message: "a" },
          (() => {
            const err = new Error("a")
            err.name = "b"
            return err
          })()
        )
        // Error: message, name, and stack
        await decoding.succeed(
          { name: "b", message: "a", stack: "c" },
          (() => {
            const err = new Error("a")
            err.name = "b"
            err.stack = "c"
            return err
          })()
        )
        // Error: message and cause
        await decoding.succeed(
          { message: "a", cause: { message: "b" } },
          new Error("a", { cause: new Error("b") })
        )
        // Error: explicit null cause
        await decoding.succeed(
          { message: "a", cause: null },
          new Error("a", { cause: null })
        )
      })

      it("Error with stack", async () => {
        const schema = Schema.Error({ includeStack: true })
        const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))
        const error = new Error("a")
        error.stack = "stack"
        const customError = new Error("b")
        customError.name = "CustomError"
        customError.stack = "custom stack"

        const encoding = asserts.encoding()
        await encoding.succeed(error, { name: "Error", message: "a", stack: "stack" })
        await encoding.succeed(customError, { name: "CustomError", message: "b", stack: "custom stack" })

        const decoding = asserts.decoding()
        await decoding.succeed(
          { message: "a", stack: "stack" },
          error
        )
        await decoding.succeed(
          { name: "CustomError", message: "b", stack: "custom stack" },
          customError
        )
      })

      it("Error with excluded cause", async () => {
        const schema = Schema.Error({ excludeCause: true })
        const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))

        const encoding = asserts.encoding()
        await encoding.succeed(
          new Error("a", { cause: new Error("b") }),
          { name: "Error", message: "a" }
        )
      })

      it("URL", async () => {
        const schema = Schema.URL
        const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))

        const encoding = asserts.encoding()
        await encoding.succeed(
          new URL("https://example.com"),
          "https://example.com/"
        )

        const decoding = asserts.decoding()
        await decoding.succeed(
          "https://example.com",
          new URL("https://example.com")
        )
        await decoding.succeed(
          "https://example.com/",
          new URL("https://example.com")
        )
        await decoding.fail(
          "not a url",
          `Invalid URL string: not a url`
        )
      })

      it("URLSearchParams", async () => {
        const schema = Schema.URLSearchParams
        const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))

        const encoding = asserts.encoding()
        await encoding.succeed(
          new URLSearchParams("a=1&b=two"),
          "a=1&b=two"
        )

        const decoding = asserts.decoding()
        await decoding.succeed(
          "a=1&b=two",
          new URLSearchParams("a=1&b=two")
        )
      })

      it.skipIf(isDeno)("File", async () => {
        const schema = Schema.File
        const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))

        const encoding = asserts.encoding()
        await encoding.succeed(
          new File(["hi"], "note.txt", { type: "text/plain", lastModified: 123 }),
          { data: "aGk=", type: "text/plain", name: "note.txt", lastModified: 123 }
        )

        const decoding = asserts.decoding()
        await decoding.succeed(
          { data: "aGk=", type: "text/plain", name: "note.txt", lastModified: 123 },
          new File(["hi"], "note.txt", { type: "text/plain", lastModified: 123 })
        )
      })

      it.skipIf(isDeno)("FormData", async () => {
        const schema = Schema.FormData
        const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))

        const encoding = asserts.encoding()
        const formData = new FormData()
        formData.append("a", "1")
        formData.append("b", new File(["hi"], "note.txt", { type: "text/plain", lastModified: 123 }))
        await encoding.succeed(
          formData,
          [
            ["a", { _tag: "String", value: "1" }],
            ["b", { _tag: "File", value: { data: "aGk=", type: "text/plain", name: "note.txt", lastModified: 123 } }]
          ]
        )

        const decoding = asserts.decoding()
        const expected = new FormData()
        expected.append("a", "1")
        expected.append("b", new File(["hi"], "note.txt", { type: "text/plain", lastModified: 123 }))
        await decoding.succeed(
          [
            ["a", { _tag: "String", value: "1" }],
            ["b", { _tag: "File", value: { data: "aGk=", type: "text/plain", name: "note.txt", lastModified: 123 } }]
          ],
          expected
        )
      })

      it("RegExp", async () => {
        const schema = Schema.RegExp
        const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))

        const encoding = asserts.encoding()
        await encoding.succeed(new RegExp("a"), { source: "a", flags: "" })
        await encoding.succeed(new RegExp("a", "i"), { source: "a", flags: "i" })

        const decoding = asserts.decoding()
        await decoding.succeed({ source: "a", flags: "" }, new RegExp("a"))
        await decoding.succeed({ source: "a", flags: "i" }, new RegExp("a", "i"))
        await decoding.fail(
          { source: "(", flags: "" },
          `SyntaxError: Invalid regular expression: /(/: Unterminated group`
        )
        await decoding.fail(
          { source: "a", flags: "x" },
          `SyntaxError: Invalid flags supplied to RegExp constructor 'x'`
        )
      })

      it("Uint8Array", async () => {
        const schema = Schema.Uint8Array
        const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))

        const encoding = asserts.encoding()
        await encoding.succeed(new Uint8Array([1, 2, 3]), "AQID")

        const decoding = asserts.decoding()
        await decoding.succeed("AQID", new Uint8Array([1, 2, 3]))
        await decoding.fail(
          "not a base64 string",
          "Length must be a multiple of 4, but is 19"
        )
      })

      it("Duration", async () => {
        const schema = Schema.Duration
        const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))

        const encoding = asserts.encoding()
        await encoding.succeed(Duration.infinity, { _tag: "Infinity" })
        await encoding.succeed(Duration.negativeInfinity, { _tag: "NegativeInfinity" })
        await encoding.succeed(Duration.nanos(1000n), { _tag: "Nanos", value: "1000" })
        await encoding.succeed(Duration.nanos(-1000n), { _tag: "Nanos", value: "-1000" })
        await encoding.succeed(Duration.millis(1), { _tag: "Millis", value: 1 })
        await encoding.succeed(Duration.millis(-1), { _tag: "Millis", value: -1 })
        await encoding.succeed(Duration.zero, { _tag: "Millis", value: 0 })

        const decoding = asserts.decoding()
        await decoding.succeed({ _tag: "Infinity" }, Duration.infinity)
        await decoding.succeed({ _tag: "NegativeInfinity" }, Duration.negativeInfinity)
        await decoding.succeed({ _tag: "Millis", value: 1 }, Duration.millis(1))
        await decoding.succeed({ _tag: "Millis", value: -1 }, Duration.millis(-1))
        await decoding.succeed({ _tag: "Nanos", value: "1000" }, Duration.nanos(1000n))
        await decoding.succeed({ _tag: "Nanos", value: "-1000" }, Duration.nanos(-1000n))
      })

      it("DateTimeUtc", async () => {
        const schema = Schema.DateTimeUtc
        const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))

        const encoding = asserts.encoding()
        await encoding.succeed(
          DateTime.makeUnsafe("2021-01-01T00:00:00.000Z"),
          "2021-01-01T00:00:00.000Z"
        )

        const decoding = asserts.decoding()
        await decoding.succeed(
          "2021-01-01T00:00:00.000Z",
          DateTime.makeUnsafe("2021-01-01T00:00:00.000Z")
        )
      })

      it("Option(Date)", async () => {
        const schema = Schema.Option(Schema.Date)
        const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))

        const encoding = asserts.encoding()
        await encoding.succeed(Option.some(new Date("2021-01-01")), {
          _tag: "Some",
          value: "2021-01-01T00:00:00.000Z"
        })
        await encoding.succeed(Option.none(), { _tag: "None" })
      })

      describe("Redacted", () => {
        it("Redacted(Option(String))", async () => {
          const schema = Schema.Redacted(Schema.Option(Schema.String))
          const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))

          const encoding = asserts.encoding()
          await encoding.succeed(
            Redacted.make(Option.none()),
            { _tag: "None" }
          )
          await encoding.succeed(
            Redacted.make(Option.some("a")),
            { _tag: "Some", value: "a" }
          )

          const decoding = asserts.decoding()
          await decoding.succeed(
            { _tag: "None" },
            Redacted.make(Option.none())
          )
          await decoding.succeed(
            { _tag: "Some", value: "a" },
            Redacted.make(Option.some("a"))
          )
        })

        it("Redacted(Option(String)) disallowJsonEncode", async () => {
          const schema = Schema.Redacted(Schema.Option(Schema.String), {
            disallowJsonEncode: true
          })
          const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))

          const encoding = asserts.encoding()
          await encoding.fail(
            Redacted.make(Option.none()),
            `Cannot serialize Redacted`
          )
          await encoding.fail(
            Redacted.make(Option.some("a")),
            `Cannot serialize Redacted`
          )

          const decoding = asserts.decoding()
          await decoding.succeed(
            { _tag: "None" },
            Redacted.make(Option.none())
          )
          await decoding.succeed(
            { _tag: "Some", value: "a" },
            Redacted.make(Option.some("a"))
          )
        })

        it("encoding a Redacted with a label", async () => {
          const schema = Schema.Redacted(Schema.String, { label: "password" })
          const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))

          const encoding = asserts.encoding()
          await encoding.fail(
            Redacted.make("a", { label: "API key" }),
            `Expected "password", got "API key"
  at ["label"]`
          )
        })

        it("encoding a Redacted with a different label", async () => {
          const schema = Schema.Redacted(Schema.String)
          const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))

          const encoding = asserts.encoding()
          await encoding.succeed(
            Redacted.make("a", { label: "API key" }),
            "a"
          )
        })
      })

      it("ReadonlySet", async () => {
        const schema = Schema.ReadonlySet(Schema.Option(Schema.Date))
        const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))

        const encoding = asserts.encoding()
        await encoding.succeed(new Set([Option.some(new Date("2021-01-01"))]), [{
          _tag: "Some",
          value: "2021-01-01T00:00:00.000Z"
        }])

        const decoding = asserts.decoding()
        await decoding.succeed(
          [{ _tag: "Some", value: "2021-01-01T00:00:00.000Z" }],
          new Set([Option.some(new Date("2021-01-01"))])
        )
      })

      it("ReadonlyMap", async () => {
        const schema = Schema.ReadonlyMap(Schema.Option(Schema.Date), Schema.Finite)
        const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))

        const encoding = asserts.encoding()
        await encoding.succeed(
          new Map([[Option.some(new Date("2021-01-01")), 0]]),
          [[
            { _tag: "Some", value: "2021-01-01T00:00:00.000Z" },
            0
          ]]
        )

        const decoding = asserts.decoding()
        await decoding.succeed(
          [[{ _tag: "Some", value: "2021-01-01T00:00:00.000Z" }, 0]],
          new Map([[Option.some(new Date("2021-01-01")), 0]])
        )
      })

      it("HashMap", async () => {
        const schema = Schema.HashMap(Schema.Option(Schema.Date), Schema.Finite)
        const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))

        const encoding = asserts.encoding()
        await encoding.succeed(
          HashMap.make([Option.some(new Date("2021-01-01")), 0]),
          [[
            { _tag: "Some", value: "2021-01-01T00:00:00.000Z" },
            0
          ]]
        )

        const decoding = asserts.decoding()
        await decoding.succeed(
          [[{ _tag: "Some", value: "2021-01-01T00:00:00.000Z" }, 0]],
          HashMap.make([Option.some(new Date("2021-01-01")), 0])
        )
      })
    })

    describe("schemas with encoding", () => {
      it("FiniteFromDate", async () => {
        const schema = FiniteFromDate
        const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))

        const encoding = asserts.encoding()
        await encoding.succeed(0, "1970-01-01T00:00:00.000Z")
      })

      it("Struct", async () => {
        const schema = Schema.Struct({
          a: FiniteFromDate,
          b: FiniteFromDate
        })
        const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))

        const encoding = asserts.encoding()
        await encoding.succeed(
          { a: 0, b: 0 },
          { a: "1970-01-01T00:00:00.000Z", b: "1970-01-01T00:00:00.000Z" }
        )
      })

      it("Tuple(Schema.Date, Schema.Date)", async () => {
        const schema = Schema.Tuple([FiniteFromDate, FiniteFromDate])
        const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))

        const encoding = asserts.encoding()
        await encoding.succeed(
          [0, 0],
          ["1970-01-01T00:00:00.000Z", "1970-01-01T00:00:00.000Z"]
        )
      })

      it("Class", async () => {
        class A extends Schema.Class<A>("A")(Schema.Struct({
          a: FiniteFromDate
        })) {}
        const asserts = new TestSchema.Asserts(Schema.toCodecJson(A))

        const encoding = asserts.encoding()
        await encoding.succeed(new A({ a: 0 }), { a: "1970-01-01T00:00:00.000Z" })

        const decoding = asserts.decoding()
        await decoding.succeed({ a: "1970-01-01T00:00:00.000Z" }, new A({ a: 0 }))
      })

      it("Error", async () => {
        class E extends Schema.ErrorClass<E>("E")({
          a: FiniteFromDate
        }) {}
        const asserts = new TestSchema.Asserts(Schema.toCodecJson(E))

        const encoding = asserts.encoding()
        await encoding.succeed(new E({ a: 0 }), { a: "1970-01-01T00:00:00.000Z" })

        const decoding = asserts.decoding()
        await decoding.succeed({ a: "1970-01-01T00:00:00.000Z" }, new E({ a: 0 }))
      })

      it("Enum", async () => {
        enum Fruits {
          Apple,
          Banana = "banana"
        }
        const schema = Schema.Enum(Fruits)
        const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))

        const encoding = asserts.encoding()
        await encoding.succeed(Fruits.Apple, 0)
        await encoding.succeed(Fruits.Banana, "banana")

        const decoding = asserts.decoding()
        await decoding.succeed(0, Fruits.Apple)
      })

      it("Option(Option(FiniteFromDate))", async () => {
        const schema = Schema.Option(Schema.Option(FiniteFromDate))
        const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))

        const encoding = asserts.encoding()
        await encoding.succeed(Option.some(Option.some(0)), {
          _tag: "Some",
          value: {
            _tag: "Some",
            value: "1970-01-01T00:00:00.000Z"
          }
        })
      })

      it("ReadonlyMap(Option(Symbol), Date)", async () => {
        const schema = Schema.ReadonlyMap(Schema.Option(Schema.Symbol), Schema.Date)
        const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))

        const encoding = asserts.encoding()
        await encoding.succeed(
          new Map([[Option.some(Symbol.for("a")), new Date("2021-01-01")]]),
          [[
            { _tag: "Some", value: "Symbol(a)" },
            "2021-01-01T00:00:00.000Z"
          ]]
        )

        const decoding = asserts.decoding()
        await decoding.succeed(
          [[{ _tag: "Some", value: "Symbol(a)" }, "2021-01-01T00:00:00.000Z"]],
          new Map([[Option.some(Symbol.for("a")), new Date("2021-01-01")]])
        )
      })

      it("Defect", async () => {
        const schema = Schema.Defect()
        const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))

        const encoding = asserts.encoding()
        await encoding.succeed(new Error("a"), { name: "Error", message: "a" })
        await encoding.succeed(
          new Error("a", { cause: new Error("b") }),
          { name: "Error", message: "a", cause: { name: "Error", message: "b" } }
        )
        await encoding.succeed(
          new Error("a", { cause: "b" }),
          { name: "Error", message: "a", cause: "b" }
        )
        await encoding.succeed(
          new Cause.NoSuchElementError(),
          { name: "NoSuchElementError", message: "" }
        )
        const cyclicDefect: Record<string, unknown> = {}
        cyclicDefect.self = cyclicDefect
        await encoding.succeed(cyclicDefect, {})
        await encoding.succeed("a")
        await encoding.succeed({ a: 1 })

        const decoding = asserts.decoding()
        await decoding.succeed(
          { message: "a", cause: { message: "b" } },
          new Error("a", { cause: new Error("b") })
        )
        await decoding.succeed(
          { message: "a", cause: null },
          new Error("a", { cause: null })
        )
      })

      it("Defect with excluded cause", async () => {
        const schema = Schema.Defect({ excludeCause: true })
        const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))

        const encoding = asserts.encoding()
        await encoding.succeed(
          new Error("a", { cause: new Error("b") }),
          { name: "Error", message: "a" }
        )
      })

      it("Cause(Option(Finite), Option(String))", async () => {
        const schema = Schema.Cause(Schema.Option(Schema.Finite), Schema.Option(Schema.String))
        const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))

        const encoding = asserts.encoding()
        await encoding.succeed(Cause.fail(Option.some(1)), [{
          _tag: "Fail",
          error: { _tag: "Some", value: 1 }
        }])
        await encoding.succeed(Cause.die(Option.some("a")), [{
          _tag: "Die",
          defect: { _tag: "Some", value: "a" }
        }])
        await encoding.succeed(Cause.interrupt(1), [{
          _tag: "Interrupt",
          fiberId: 1
        }])
        await encoding.succeed(Cause.interrupt(), [{
          _tag: "Interrupt",
          fiberId: null
        }])
      })

      it("DateTimeUtcFromValidDate", async () => {
        const schema = Schema.DateTimeUtcFromDate
        const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))

        const encoding = asserts.encoding()
        await encoding.succeed(
          DateTime.makeUnsafe("2021-01-01T00:00:00.000Z"),
          "2021-01-01T00:00:00.000Z"
        )

        const decoding = asserts.decoding()
        await decoding.succeed(
          "2021-01-01T00:00:00.000Z",
          DateTime.makeUnsafe("2021-01-01T00:00:00.000Z")
        )
      })
    })

    it("StandardSchemaV1FailureResult", async () => {
      const b = Symbol.for("b")

      const schema = Schema.Struct({
        a: Schema.NonEmptyString,
        [b]: Schema.Finite,
        c: Schema.Tuple([Schema.String])
      })

      const r = SchemaParser.decodeUnknownExit(schema)({ a: "", c: [] }, { errors: "all" })

      assertTrue(r._tag === "Failure")
      assertTrue(r.cause.reasons.length === 1)
      const failure = r.cause.reasons[0]
      assertTrue(failure._tag === "Fail")

      const failureResult = SchemaIssue.makeFormatterStandardSchemaV1({
        leafHook: SchemaIssue.defaultLeafHook
      })(failure.error)

      const asserts = new TestSchema.Asserts(Schema.toCodecJson(Schema.StandardSchemaV1FailureResult))

      const encoding = asserts.encoding()
      await encoding.succeed(failureResult, {
        issues: [
          { path: ["a"], message: `Expected a value with a length of at least 1, got ""` },
          { path: ["c", 0], message: "Missing key" },
          { path: ["Symbol(b)"], message: "Missing key" }
        ]
      })

      const decoding = asserts.decoding()
      await decoding.succeed({
        issues: [
          { path: ["a"], message: `Expected a value with a length of at least 1, got ""` },
          { path: ["c", 0], message: "Missing key" },
          { path: ["Symbol(b)"], message: "Missing key" }
        ]
      }, failureResult)
    })
  })

  describe("toCodecStringTree", () => {
    it("exposes the source schema", () => {
      const schema = Schema.FiniteFromString
      const serializer = Schema.toCodecStringTree(schema)
      strictEqual(serializer.schema, schema)
    })

    it("should reorder the types in the Union based on the encoded side", async () => {
      const schema = Schema.Union([
        Schema.String,
        Schema.String.pipe(Schema.encodeTo(Schema.BigInt, {
          decode: SchemaGetter.transform((n: bigint) => String(n) + "a"),
          encode: SchemaGetter.transform(() => 0n)
        }))
      ])
      const serializer = Schema.toCodecStringTree(schema)
      const asserts = new TestSchema.Asserts(Schema.toCodecJson(serializer))

      const decoding = asserts.decoding()
      await decoding.succeed("1", "1a")
    })

    describe("should return the same reference if nothing changed", () => {
      it("String", async () => {
        const schema = Schema.String
        const serializer = Schema.toCodecStringTree(schema)
        strictEqual(serializer.ast, schema.ast)
      })

      it("Struct({ a: String })", async () => {
        const schema = Schema.Struct({
          a: Schema.String
        })
        const serializer = Schema.toCodecStringTree(schema)
        strictEqual(serializer.ast, schema.ast)
      })
    })

    describe("should memoize the result", () => {
      it("Struct", async () => {
        const schema = Schema.Struct({
          a: Schema.Finite
        })
        const serializer = Schema.toCodecStringTree(schema)
        strictEqual(serializer.ast, Schema.toCodecStringTree(serializer).ast)
      })

      it("Array", async () => {
        const schema = Schema.Array(Schema.Finite)
        const serializer = Schema.toCodecStringTree(schema)
        strictEqual(serializer.ast, Schema.toCodecStringTree(serializer).ast)
      })
    })

    describe("schemas without encoding", () => {
      describe("Unsupported schemas", () => {
        it("Struct with Symbol property name", () => {
          const a = Symbol.for("a")
          const schema = Schema.Struct({
            [a]: Schema.String
          })
          throws(
            () => Schema.toCodecStringTree(schema),
            "Objects property names must be strings"
          )
        })
      })

      it("Declaration", async () => {
        const schema = Schema.instanceOf(URL)
        const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))

        const encoding = asserts.encoding()
        await encoding.succeed(new URL("https://effect.website"), undefined)

        const decoding = asserts.decoding()
        await decoding.fail("https://effect.website/", `Expected undefined, got "https://effect.website/"`)
      })

      it("Unknown", async () => {
        const schema = Schema.Unknown
        const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))

        const encoding = asserts.encoding()
        await encoding.succeed("a")
        await encoding.fail(1, `Expected StringTree, got 1`)
        await encoding.succeed({ a: "a" })
        await encoding.succeed(["a"])
        await encoding.fail({ a: 1 }, `Expected StringTree, got {"a":1}`)

        const decoding = asserts.decoding()
        await decoding.succeed("a")
        await decoding.fail(1, `Expected StringTree, got 1`)
        await decoding.succeed({ a: "a" })
        await decoding.succeed(["a"])
        await decoding.fail({ a: 1 }, `Expected StringTree, got {"a":1}`)
      })

      it("ObjectKeyword", async () => {
        const schema = Schema.ObjectKeyword
        const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))

        const encoding = asserts.encoding()
        await encoding.fail("a", `Expected object | array | function, got "a"`)
        await encoding.fail(1, `Expected object | array | function, got 1`)
        await encoding.succeed({ a: "a" })
        await encoding.succeed(["a"])
        await encoding.fail({ a: 1 }, `Expected StringTree, got {"a":1}`)

        const decoding = asserts.decoding()
        await decoding.fail("a", `Expected object | array | function, got "a"`)
        await decoding.fail(1, `Expected StringTree, got 1`)
        await decoding.succeed({ a: "a" })
        await decoding.succeed(["a"])
        await decoding.fail({ a: 1 }, `Expected StringTree, got {"a":1}`)
      })

      it("Never", async () => {
        const schema = Schema.Never
        const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))

        const encoding = asserts.encoding()
        await encoding.fail({}, "Expected never, got {}")
      })

      it("Any should be an escape hatch", async () => {
        const schema = Schema.Any
        const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))

        const encoding = asserts.encoding()
        await encoding.succeed(() => {})

        const decoding = asserts.decoding()
        await decoding.succeed(() => {})
      })

      it("Undefined", async () => {
        const schema = Schema.Undefined
        const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))

        const encoding = asserts.encoding()
        await encoding.succeed(undefined)

        const decoding = asserts.decoding()
        await decoding.succeed(undefined)
      })

      it("Void", async () => {
        const schema = Schema.Void
        const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))

        const encoding = asserts.encoding()
        await encoding.succeed(undefined)

        const decoding = asserts.decoding()
        await decoding.succeed(undefined)
      })

      it("Null", async () => {
        const schema = Schema.Null
        const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))

        const encoding = asserts.encoding()
        await encoding.succeed(null, "null")

        const decoding = asserts.decoding()
        await decoding.succeed("null", null)
      })

      it("String", async () => {
        const schema = Schema.String
        const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))

        const encoding = asserts.encoding()
        await encoding.succeed("a")

        const decoding = asserts.decoding()
        await decoding.succeed("a")
      })

      describe("Number", () => {
        it("Number", async () => {
          const schema = Schema.Number
          const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))

          const encoding = asserts.encoding()
          await encoding.succeed(1, "1")
          await encoding.succeed(-1, "-1")
          await encoding.succeed(1.2, "1.2")
          await encoding.succeed(Infinity, "Infinity")
          await encoding.succeed(-Infinity, "-Infinity")
          await encoding.succeed(NaN, "NaN")

          const decoding = asserts.decoding()
          await decoding.succeed("1", 1)
          await decoding.succeed("-1", -1)
          await decoding.succeed("1.2", 1.2)
          await decoding.succeed("Infinity", Infinity)
          await decoding.succeed("-Infinity", -Infinity)
          await decoding.succeed("NaN", NaN)
          await decoding.fail(Infinity, `Expected string | "Infinity" | "-Infinity" | "NaN", got Infinity`)
          await decoding.fail(-Infinity, `Expected string | "Infinity" | "-Infinity" | "NaN", got -Infinity`)
          await decoding.fail(NaN, `Expected string | "Infinity" | "-Infinity" | "NaN", got NaN`)
          await decoding.fail(null, `Expected string | "Infinity" | "-Infinity" | "NaN", got null`)
          await decoding.fail(
            "a",
            `Expected a string representing a finite number, got "a"
Expected "Infinity" | "-Infinity" | "NaN", got "a"`
          )
        })

        describe("checks", () => {
          it("Finite", async () => {
            const schema = Schema.Finite
            const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))

            const encoding = asserts.encoding()
            await encoding.succeed(1, "1")
            await encoding.succeed(-1, "-1")
            await encoding.succeed(1.2, "1.2")
            await encoding.fail(Infinity, "Expected a finite number, got Infinity")
            await encoding.fail(-Infinity, "Expected a finite number, got -Infinity")
            await encoding.fail(NaN, "Expected a finite number, got NaN")

            const decoding = asserts.decoding()
            await decoding.succeed("1", 1)
            await decoding.succeed("-1", -1)
            await decoding.succeed("1.2", 1.2)
            await decoding.fail("Infinity", `Expected a string representing a finite number, got "Infinity"`)
            await decoding.fail("-Infinity", `Expected a string representing a finite number, got "-Infinity"`)
            await decoding.fail("NaN", `Expected a string representing a finite number, got "NaN"`)
            await decoding.fail(Infinity, `Expected string, got Infinity`)
            await decoding.fail(-Infinity, `Expected string, got -Infinity`)
            await decoding.fail(NaN, `Expected string, got NaN`)
            await decoding.fail(null, `Expected string, got null`)
            await decoding.fail("a", `Expected a string representing a finite number, got "a"`)
          })

          it("Int", async () => {
            const schema = Schema.Int
            const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))

            const encoding = asserts.encoding()
            await encoding.succeed(1, "1")
            await encoding.succeed(-1, "-1")
            await encoding.fail(1.2, `Expected an integer, got 1.2`)
            await encoding.fail(Infinity, `Expected an integer, got Infinity`)
            await encoding.fail(-Infinity, `Expected an integer, got -Infinity`)
            await encoding.fail(NaN, `Expected an integer, got NaN`)

            const decoding = asserts.decoding()
            await decoding.succeed("1", 1)
            await decoding.succeed("-1", -1)
            await decoding.fail("1.2", `Expected an integer, got 1.2`)
            await decoding.fail("Infinity", `Expected a string representing a finite number, got "Infinity"`)
            await decoding.fail("-Infinity", `Expected a string representing a finite number, got "-Infinity"`)
            await decoding.fail("NaN", `Expected a string representing a finite number, got "NaN"`)
            await decoding.fail(Infinity, `Expected string, got Infinity`)
            await decoding.fail(-Infinity, `Expected string, got -Infinity`)
            await decoding.fail(NaN, `Expected string, got NaN`)
            await decoding.fail(null, `Expected string, got null`)
            await decoding.fail("a", `Expected a string representing a finite number, got "a"`)
          })

          it("isGreaterThanOrEqualTo", async () => {
            const schema = Schema.Number.check(Schema.isGreaterThanOrEqualTo(1))
            const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))

            const encoding = asserts.encoding()
            await encoding.succeed(1, "1")
            await encoding.fail(-1, `Expected a value greater than or equal to 1, got -1`)
            await encoding.succeed(1.2, "1.2")
            await encoding.succeed(Infinity, "Infinity")
            await encoding.fail(-Infinity, "Expected a value greater than or equal to 1, got -Infinity")
            await encoding.fail(NaN, "Expected a value greater than or equal to 1, got NaN")

            const decoding = asserts.decoding()
            await decoding.succeed("1", 1)
            await decoding.fail("-1", `Expected a value greater than or equal to 1, got -1`)
            await decoding.succeed("1.2", 1.2)
            await decoding.succeed("Infinity", Infinity)
            await decoding.fail("-Infinity", `Expected a value greater than or equal to 1, got -Infinity`)
            await decoding.fail("NaN", `Expected a value greater than or equal to 1, got NaN`)
            await decoding.fail(Infinity, `Expected string | "Infinity" | "-Infinity" | "NaN", got Infinity`)
            await decoding.fail(-Infinity, `Expected string | "Infinity" | "-Infinity" | "NaN", got -Infinity`)
            await decoding.fail(NaN, `Expected string | "Infinity" | "-Infinity" | "NaN", got NaN`)
            await decoding.fail(null, `Expected string | "Infinity" | "-Infinity" | "NaN", got null`)
            await decoding.fail(
              "a",
              `Expected a string representing a finite number, got "a"
Expected "Infinity" | "-Infinity" | "NaN", got "a"`
            )
          })
        })
      })

      it("Boolean", async () => {
        const schema = Schema.Boolean
        const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))

        const encoding = asserts.encoding()
        await encoding.succeed(true, "true")
        await encoding.succeed(false, "false")

        const decoding = asserts.decoding()
        await decoding.succeed("true", true)
        await decoding.succeed("false", false)
      })

      it("Symbol", async () => {
        const schema = Schema.Symbol
        const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))

        const encoding = asserts.encoding()
        await encoding.succeed(Symbol.for("a"), "Symbol(a)")
        await encoding.fail(
          Symbol("a"),
          "cannot serialize to string, Symbol is not registered"
        )
        await encoding.fail(
          Symbol(),
          "cannot serialize to string, Symbol is not registered"
        )

        const decoding = asserts.decoding()
        await decoding.succeed("Symbol(a)", Symbol.for("a"))
        await decoding.fail("a", `Expected a string representing a symbol, got "a"`)
      })

      it("UniqueSymbol", async () => {
        const schema = Schema.UniqueSymbol(Symbol.for("a"))
        const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))

        const encoding = asserts.encoding()
        await encoding.succeed(Symbol.for("a"), "Symbol(a)")

        const decoding = asserts.decoding()
        await decoding.succeed("Symbol(a)", Symbol.for("a"))
        await decoding.fail("a", `Expected a string representing a symbol, got "a"`)
      })

      it("BigInt", async () => {
        const schema = Schema.BigInt
        const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))

        const encoding = asserts.encoding()
        await encoding.succeed(1n, "1")

        const decoding = asserts.decoding()
        await decoding.succeed("1", 1n)
        await decoding.fail("a", `Expected a string representing a bigint, got "a"`)
      })

      it("PropertyKey", async () => {
        const schema = Schema.PropertyKey
        const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))

        const encoding = asserts.encoding()
        await encoding.succeed("a", "a")
        await encoding.succeed(1, "1")
        await encoding.succeed(Symbol.for("a"), "Symbol(a)")

        const decoding = asserts.decoding()
        await decoding.succeed("a", "a")
        await decoding.succeed("1", 1)
        await decoding.succeed("Symbol(a)", Symbol.for("a"))
      })

      describe("Literal", () => {
        it("string", async () => {
          const schema = Schema.Literal("a")
          const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))

          const encoding = asserts.encoding()
          await encoding.succeed("a", "a")

          const decoding = asserts.decoding()
          await decoding.succeed("a", "a")
        })

        it("number", async () => {
          const schema = Schema.Literal(1)
          const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))

          const encoding = asserts.encoding()
          await encoding.succeed(1, "1")

          const decoding = asserts.decoding()
          await decoding.succeed("1", 1)
        })

        it("boolean", async () => {
          const schema = Schema.Literal(true)
          const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))

          const encoding = asserts.encoding()
          await encoding.succeed(true, "true")

          const decoding = asserts.decoding()
          await decoding.succeed("true", true)
        })

        it("bigint", async () => {
          const schema = Schema.Literal(1n)
          const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))

          const encoding = asserts.encoding()
          await encoding.succeed(1n, "1")

          const decoding = asserts.decoding()
          await decoding.succeed("1", 1n)
        })
      })

      it("Literals", async () => {
        const schema = Schema.Literals(["a", 1, 2n, true])
        const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))

        const decoding = asserts.decoding()
        await decoding.fail(
          "-",
          `Expected "a" | "1" | "2" | "true", got "-"`
        )
      })

      describe("TemplateLiteral", () => {
        it("1n + string", async () => {
          const schema = Schema.TemplateLiteral([1n, Schema.String])
          const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))

          const encoding = asserts.encoding()
          await encoding.succeed("1a")

          const decoding = asserts.decoding()
          await decoding.succeed("1a")
        })

        it(`"a" + bigint`, async () => {
          const schema = Schema.TemplateLiteral(["a", Schema.BigInt])
          const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))

          const encoding = asserts.encoding()
          await encoding.succeed("a1")

          const decoding = asserts.decoding()
          await decoding.succeed("a1")
        })
      })

      it("Enum", async () => {
        enum Fruits {
          Apple,
          Banana
        }
        const schema = Schema.Enum(Fruits)
        const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))

        const encoding = asserts.encoding()
        await encoding.succeed(Fruits.Apple, "0")
        await encoding.succeed(Fruits.Banana, "1")

        const decoding = asserts.decoding()
        await decoding.succeed("0", Fruits.Apple)
        await decoding.succeed("1", Fruits.Banana)
      })

      describe("Struct", () => {
        it("Date", async () => {
          const schema = Schema.Struct({
            a: Schema.Date
          })
          const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))

          const encoding = asserts.encoding()
          await encoding.succeed({ a: new Date("2021-01-01") }, {
            a: "2021-01-01T00:00:00.000Z"
          })

          const decoding = asserts.decoding()
          await decoding.succeed({ a: "2021-01-01T00:00:00.000Z" }, {
            a: new Date("2021-01-01")
          })
        })

        it("UndefinedOr(Date)", async () => {
          const schema = Schema.Struct({
            a: Schema.UndefinedOr(Schema.Date)
          })
          const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))

          const encoding = asserts.encoding()
          await encoding.succeed({ a: new Date("2021-01-01") }, {
            a: "2021-01-01T00:00:00.000Z"
          })
          await encoding.succeed({ a: undefined }, { a: undefined })

          const decoding = asserts.decoding()
          await decoding.succeed({ a: "2021-01-01T00:00:00.000Z" }, {
            a: new Date("2021-01-01")
          })
          await decoding.succeed({ a: undefined }, { a: undefined })
        })

        it("NullOr(Date)", async () => {
          const schema = Schema.Struct({
            a: Schema.NullOr(Schema.Date)
          })
          const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))

          const encoding = asserts.encoding()
          await encoding.succeed({ a: new Date("2021-01-01") }, {
            a: "2021-01-01T00:00:00.000Z"
          })
          await encoding.succeed({ a: null }, { a: "null" })

          const decoding = asserts.decoding()
          await decoding.succeed({ a: "2021-01-01T00:00:00.000Z" }, {
            a: new Date("2021-01-01")
          })
          await decoding.succeed({ a: "null" }, { a: null })
        })

        it("optionalKey(Date)", async () => {
          const schema = Schema.Struct({
            a: Schema.optionalKey(Schema.Date)
          })
          const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))

          const encoding = asserts.encoding()
          await encoding.succeed({ a: new Date("2021-01-01") }, {
            a: "2021-01-01T00:00:00.000Z"
          })
          await encoding.succeed({}, {})

          const decoding = asserts.decoding()
          await decoding.succeed({ a: "2021-01-01T00:00:00.000Z" }, {
            a: new Date("2021-01-01")
          })
          await decoding.succeed({}, {})
        })

        it("optional(Date)", async () => {
          const schema = Schema.Struct({
            a: Schema.optional(Schema.Date)
          })
          const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))

          const encoding = asserts.encoding()
          await encoding.succeed({ a: new Date("2021-01-01") }, {
            a: "2021-01-01T00:00:00.000Z"
          })
          await encoding.succeed({}, {})
          await encoding.succeed({ a: undefined }, { a: undefined })

          const decoding = asserts.decoding()
          await decoding.succeed({ a: "2021-01-01T00:00:00.000Z" }, {
            a: new Date("2021-01-01")
          })
          await decoding.succeed({}, {})
          await decoding.succeed({ a: undefined }, { a: undefined })
        })
      })

      it("Record(Symbol, Date)", async () => {
        const schema = Schema.Record(Schema.Symbol, Schema.Date)
        const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))

        const encoding = asserts.encoding()
        await encoding.succeed(
          { [Symbol.for("a")]: new Date("2021-01-01"), [Symbol.for("b")]: new Date("2021-01-01") },
          { "Symbol(a)": "2021-01-01T00:00:00.000Z", "Symbol(b)": "2021-01-01T00:00:00.000Z" }
        )

        const decoding = asserts.decoding()
        await decoding.succeed(
          { "Symbol(a)": "2021-01-01T00:00:00.000Z", "Symbol(b)": "2021-01-01T00:00:00.000Z" },
          { [Symbol.for("a")]: new Date("2021-01-01"), [Symbol.for("b")]: new Date("2021-01-01") }
        )
      })

      describe("Tuple", () => {
        it("Date", async () => {
          const schema = Schema.Tuple([Schema.Date])
          const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))

          const encoding = asserts.encoding()
          await encoding.succeed(
            [new Date("2021-01-01")],
            ["2021-01-01T00:00:00.000Z"]
          )

          const decoding = asserts.decoding()
          await decoding.succeed(
            ["2021-01-01T00:00:00.000Z"],
            [new Date("2021-01-01")]
          )
        })

        it("UndefinedOr(Date)", async () => {
          const schema = Schema.Tuple([Schema.UndefinedOr(Schema.Date)])
          const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))

          const encoding = asserts.encoding()
          await encoding.succeed(
            [new Date("2021-01-01")],
            ["2021-01-01T00:00:00.000Z"]
          )
          await encoding.succeed([undefined])

          const decoding = asserts.decoding()
          await decoding.succeed(["2021-01-01T00:00:00.000Z"], [
            new Date("2021-01-01")
          ])
          await decoding.succeed([undefined])
        })

        it("NullOr(Date)", async () => {
          const schema = Schema.Tuple([Schema.NullOr(Schema.Date)])
          const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))

          const encoding = asserts.encoding()
          await encoding.succeed([new Date("2021-01-01")], [
            "2021-01-01T00:00:00.000Z"
          ])
          await encoding.succeed([null], ["null"])

          const decoding = asserts.decoding()
          await decoding.succeed(["2021-01-01T00:00:00.000Z"], [
            new Date("2021-01-01")
          ])
          await decoding.succeed(["null"], [null])
        })

        it("optionalKey(Date)", async () => {
          const schema = Schema.Tuple([Schema.optionalKey(Schema.Date)])
          const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))

          const encoding = asserts.encoding()
          await encoding.succeed([new Date("2021-01-01")], [
            "2021-01-01T00:00:00.000Z"
          ])
          await encoding.succeed([], [])

          const decoding = asserts.decoding()
          await decoding.succeed(["2021-01-01T00:00:00.000Z"], [
            new Date("2021-01-01")
          ])
          await decoding.succeed([], [])
        })

        it("optional(Date)", async () => {
          const schema = Schema.Tuple([Schema.optional(Schema.Date)])
          const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))

          const encoding = asserts.encoding()
          await encoding.succeed([new Date("2021-01-01")], [
            "2021-01-01T00:00:00.000Z"
          ])
          await encoding.succeed([], [])
          await encoding.succeed([undefined])

          const decoding = asserts.decoding()
          await decoding.succeed(["2021-01-01T00:00:00.000Z"], [
            new Date("2021-01-01")
          ])
          await decoding.succeed([], [])
          await decoding.succeed([undefined])
        })
      })

      it("Array(Date)", async () => {
        const schema = Schema.Array(Schema.Date)
        const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))

        const encoding = asserts.encoding()
        await encoding.succeed(
          [new Date("2021-01-01"), new Date("2021-01-02")],
          ["2021-01-01T00:00:00.000Z", "2021-01-02T00:00:00.000Z"]
        )
      })

      it("Array(Finite) preserves the top-level AST", async () => {
        const serializer = Schema.toCodecStringTree(Schema.Array(Schema.Finite))
        strictEqual(serializer.ast._tag, "Arrays")

        const asserts = new TestSchema.Asserts(serializer)

        const encoding = asserts.encoding()
        await encoding.succeed([1, 2], ["1", "2"])

        const decoding = asserts.decoding()
        await decoding.fail("1,2", `Expected array, got "1,2"`)
        await decoding.succeed(["1", "2"], [1, 2])
      })

      describe("Union", () => {
        it("NullOr(Date)", async () => {
          const schema = Schema.NullOr(Schema.String)
          const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))

          const encoding = asserts.encoding()
          await encoding.succeed("a", "a")
          await encoding.succeed(null, "null")

          const decoding = asserts.decoding()
          await decoding.succeed("null", null)
          await decoding.succeed("a", "a")
        })

        it("NullOr(Number)", async () => {
          const schema = Schema.NullOr(Schema.Number)
          const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))

          const encoding = asserts.encoding()
          await encoding.succeed(1, "1")
          await encoding.succeed(null, "null")

          const decoding = asserts.decoding()
          await decoding.succeed("null", null)
          await decoding.succeed("1", 1)
        })

        it("Array(NullOr(Number))", async () => {
          const schema = Schema.Array(Schema.NullOr(Schema.Number))
          const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))

          const encoding = asserts.encoding()
          await encoding.succeed([1, null], ["1", "null"])

          const decoding = asserts.decoding()
          await decoding.succeed(["1", "null"], [1, null])
        })

        it("Union(Date, Finite)", async () => {
          const schema = Schema.Union([Schema.Date, Schema.Finite])
          const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))

          const encoding = asserts.encoding()
          await encoding.succeed(
            new Date("2021-01-01"),
            "2021-01-01T00:00:00.000Z"
          )
          await encoding.succeed(0, "0")
        })
      })

      it("Suspend", async () => {
        interface Category {
          readonly a: number
          readonly categories: ReadonlyArray<Category>
        }

        const schema = Schema.Struct({
          a: Schema.Finite.check(Schema.isGreaterThan(0)),
          categories: Schema.Array(Schema.suspend((): Schema.Codec<Category> => schema))
        })
        const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))

        const encoding = asserts.encoding()
        await encoding.succeed({ a: 1, categories: [] }, {
          a: "1",
          categories: []
        })
        await encoding.succeed({
          a: 1,
          categories: [{ a: 2, categories: [] }]
        }, {
          a: "1",
          categories: [
            { a: "2", categories: [] }
          ]
        })

        const decoding = asserts.decoding()
        await decoding.succeed({
          a: "1",
          categories: []
        }, { a: 1, categories: [] })
        await decoding.succeed({
          a: "1",
          categories: [
            { a: "2", categories: [] }
          ]
        }, { a: 1, categories: [{ a: 2, categories: [] }] })
      })

      it("Class", async () => {
        class A extends Schema.Class<A>("A")(Schema.Struct({
          a: Schema.Finite
        })) {}
        const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(Schema.toType(A)))

        const encoding = asserts.encoding()
        await encoding.succeed(new A({ a: 0 }), { a: "0" })

        const decoding = asserts.decoding()
        await decoding.succeed({ a: "0" }, new A({ a: 0 }))
      })

      it("ErrorClass", async () => {
        class E extends Schema.ErrorClass<E>("E")({
          a: Schema.Finite
        }) {}
        const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(Schema.toType(E)))

        const encoding = asserts.encoding()
        await encoding.succeed(new E({ a: 0 }), { a: "0" })

        const decoding = asserts.decoding()
        await decoding.succeed({ a: "0" }, new E({ a: 0 }))
      })

      it("Date", async () => {
        const schema = Schema.Date
        const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))

        const encoding = asserts.encoding()
        await encoding.succeed(
          new Date("2021-01-01"),
          "2021-01-01T00:00:00.000Z"
        )
      })

      it("Error", async () => {
        const schema = Schema.Error()
        const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))

        const encoding = asserts.encoding()
        await encoding.succeed(
          new Error("a"),
          { name: "Error", message: "a" }
        )

        const decoding = asserts.decoding()
        // Error: message only
        await decoding.succeed(
          { message: "a" },
          new Error("a")
        )
        // Error: message and name
        await decoding.succeed(
          { name: "b", message: "a" },
          (() => {
            const err = new Error("a")
            err.name = "b"
            return err
          })()
        )
        // Error: message, name, and stack
        await decoding.succeed(
          { name: "b", message: "a", stack: "c" },
          (() => {
            const err = new Error("a")
            err.name = "b"
            err.stack = "c"
            return err
          })()
        )
      })

      it("URL", async () => {
        const schema = Schema.URL
        const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))

        const encoding = asserts.encoding()
        await encoding.succeed(
          new URL("https://example.com"),
          "https://example.com/"
        )

        const decoding = asserts.decoding()
        await decoding.succeed(
          "https://example.com",
          new URL("https://example.com")
        )
        await decoding.succeed(
          "https://example.com/",
          new URL("https://example.com")
        )
        await decoding.fail(
          "not a url",
          `Invalid URL string: not a url`
        )
      })

      it("RegExp", async () => {
        const schema = Schema.RegExp
        const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))

        const encoding = asserts.encoding()
        await encoding.succeed(new RegExp("a"), { source: "a", flags: "" })
        await encoding.succeed(new RegExp("a", "i"), { source: "a", flags: "i" })

        const decoding = asserts.decoding()
        await decoding.succeed({ source: "a", flags: "" }, new RegExp("a"))
        await decoding.succeed({ source: "a", flags: "i" }, new RegExp("a", "i"))
        await decoding.fail(
          { source: "a", flags: "x" },
          `SyntaxError: Invalid flags supplied to RegExp constructor 'x'`
        )
      })

      it("Option(Date)", async () => {
        const schema = Schema.Option(Schema.Date)
        const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))

        const encoding = asserts.encoding()
        await encoding.succeed(Option.some(new Date("2021-01-01")), {
          _tag: "Some",
          value: "2021-01-01T00:00:00.000Z"
        })
        await encoding.succeed(Option.none(), { _tag: "None" })
      })

      it("Redacted(Option(String))", async () => {
        const schema = Schema.Redacted(Schema.Option(Schema.String))
        const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))

        const encoding = asserts.encoding()
        await encoding.succeed(
          Redacted.make(Option.none()),
          { _tag: "None" }
        )
        await encoding.succeed(
          Redacted.make(Option.some("a")),
          { _tag: "Some", value: "a" }
        )

        const decoding = asserts.decoding()
        await decoding.succeed(
          { _tag: "None" },
          Redacted.make(Option.none())
        )
        await decoding.succeed(
          { _tag: "Some", value: "a" },
          Redacted.make(Option.some("a"))
        )
      })

      it("Redacted(Option(String)) disallowJsonEncode", async () => {
        const schema = Schema.Redacted(Schema.Option(Schema.String), {
          disallowJsonEncode: true
        })
        const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))

        const encoding = asserts.encoding()
        await encoding.fail(
          Redacted.make(Option.none()),
          "Cannot serialize Redacted"
        )
        await encoding.fail(
          Redacted.make(Option.some("a")),
          "Cannot serialize Redacted"
        )

        const decoding = asserts.decoding()
        await decoding.succeed(
          { _tag: "None" },
          Redacted.make(Option.none())
        )
        await decoding.succeed(
          { _tag: "Some", value: "a" },
          Redacted.make(Option.some("a"))
        )
      })

      it("ReadonlySet", async () => {
        const schema = Schema.ReadonlySet(Schema.Option(Schema.Date))
        const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))

        const encoding = asserts.encoding()
        await encoding.succeed(new Set([Option.some(new Date("2021-01-01"))]), [{
          _tag: "Some",
          value: "2021-01-01T00:00:00.000Z"
        }])

        const decoding = asserts.decoding()
        await decoding.succeed(
          [{ _tag: "Some", value: "2021-01-01T00:00:00.000Z" }],
          new Set([Option.some(new Date("2021-01-01"))])
        )
      })

      it("ReadonlyMap", async () => {
        const schema = Schema.ReadonlyMap(Schema.Option(Schema.Date), Schema.Finite)
        const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))

        const encoding = asserts.encoding()
        await encoding.succeed(
          new Map([[Option.some(new Date("2021-01-01")), 0]]),
          [[
            { _tag: "Some", value: "2021-01-01T00:00:00.000Z" },
            "0"
          ]]
        )

        const decoding = asserts.decoding()
        await decoding.succeed(
          [[{ _tag: "Some", value: "2021-01-01T00:00:00.000Z" }, "0"]],
          new Map([[Option.some(new Date("2021-01-01")), 0]])
        )
      })

      it("HashMap", async () => {
        const schema = Schema.HashMap(Schema.Option(Schema.Date), Schema.Finite)
        const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))

        const encoding = asserts.encoding()
        await encoding.succeed(
          HashMap.make([Option.some(new Date("2021-01-01")), 0]),
          [[
            { _tag: "Some", value: "2021-01-01T00:00:00.000Z" },
            "0"
          ]]
        )

        const decoding = asserts.decoding()
        await decoding.succeed(
          [[{ _tag: "Some", value: "2021-01-01T00:00:00.000Z" }, "0"]],
          HashMap.make([Option.some(new Date("2021-01-01")), 0])
        )
      })
    })

    it("should handle optional keys", async () => {
      const schema = Schema.Struct({
        a: Schema.optionalKey(Schema.NonEmptyArray(Schema.String))
      })
      const serializer = Schema.toCodecStringTree(schema)
      const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(serializer))

      const decoding = asserts.decoding()
      await decoding.succeed({})
      await decoding.succeed({ a: ["a"] })
      await decoding.fail({ a: "a" }, `Expected array, got "a"\n  at ["a"]`)
    })
  })

  describe("toCodecArrayFromSingle", () => {
    it("accepts string and array inputs for a top-level array", async () => {
      const serializer = Schema.toCodecArrayFromSingle(Schema.toCodecStringTree(Schema.Array(Schema.Finite)))
      strictEqual(serializer.ast._tag, "Arrays")

      const asserts = new TestSchema.Asserts(serializer)

      const encoding = asserts.encoding()
      await encoding.succeed([1, 2], ["1", "2"])
      await encoding.fail(1 as any, "Expected array, got 1")

      const decoding = asserts.decoding()
      await decoding.succeed("1", [1])
      await decoding.fail("1,2", `Expected a string representing a finite number, got "1,2"\n  at [0]`)
      await decoding.succeed(["1", "2"], [1, 2])
    })

    it("accepts string and array inputs for required array fields", async () => {
      const schema = Schema.toCodecArrayFromSingle(Schema.toCodecStringTree(Schema.Struct({
        a: Schema.Array(Schema.Finite)
      })))
      const asserts = new TestSchema.Asserts(schema)

      const decoding = asserts.decoding()
      await decoding.succeed({ a: "1" }, { a: [1] })
      await decoding.succeed({ a: ["1", "2"] }, { a: [1, 2] })
    })

    it("accepts string and array inputs for nested optional array fields", async () => {
      const schema = Schema.toCodecArrayFromSingle(Schema.toCodecStringTree(Schema.Struct({
        a: Schema.optionalKey(Schema.NonEmptyArray(Schema.String))
      })))
      const asserts = new TestSchema.Asserts(schema)

      const decoding = asserts.decoding()
      await decoding.succeed({})
      await decoding.succeed({ a: ["a"] })
      await decoding.succeed({ a: "a" }, { a: ["a"] })
    })

    it("accepts string and array inputs for tuples with optional elements", async () => {
      const schema = Schema.toCodecArrayFromSingle(Schema.toCodecStringTree(Schema.Tuple([
        Schema.optionalKey(Schema.String)
      ])))
      const asserts = new TestSchema.Asserts(schema)

      const decoding = asserts.decoding()
      await decoding.succeed([])
      await decoding.succeed("a", ["a"])
      await decoding.succeed(["a"])
    })

    it("applies recursively to nested arrays", async () => {
      const schema = Schema.toCodecArrayFromSingle(Schema.toCodecStringTree(Schema.Array(Schema.Array(Schema.Finite))))
      const asserts = new TestSchema.Asserts(schema)

      const decoding = asserts.decoding()
      await decoding.succeed("1", [[1]])
      await decoding.succeed(["1", "2"], [[1], [2]])
      await decoding.succeed([["1", "2"]], [[1, 2]])
    })

    it("is idempotent", () => {
      const schema = Schema.toCodecArrayFromSingle(Schema.toCodecStringTree(Schema.Array(Schema.Finite)))
      strictEqual(schema.ast, Schema.toCodecArrayFromSingle(schema).ast)
    })
  })

  describe("toEncoderXml", () => {
    async function assertXml<T, E, RD>(schema: Schema.Codec<T, E, RD>, value: T, expected: string) {
      const serializer = Schema.toEncoderXml(Schema.toCodecStringTree(schema))
      strictEqual(await Effect.runPromise(serializer(value)), expected)
    }

    async function assertXmlFailure<T, E, RD>(schema: Schema.Codec<T, E, RD>, value: T, message: string) {
      const serializer = Schema.toEncoderXml(Schema.toCodecStringTree(schema))
      const r = await serializer(value).pipe(
        Effect.mapError((err) => err.issue.toString()),
        Effect.result,
        Effect.runPromise
      )
      deepStrictEqual(r, Result.fail(message))
    }

    describe("Schemas without annotations", () => {
      it("Declaration", async () => {
        await assertXml(Schema.instanceOf(URL), new URL("https://effect.website"), "<root/>")
      })

      it("Unknown", async () => {
        await assertXml(Schema.Unknown, "value", "<root/>")
      })

      it("ObjectKeyword", async () => {
        await assertXml(Schema.ObjectKeyword, { a: "value" }, "<root/>")
      })
    })

    it("should use the identifier as the root name", async () => {
      await assertXml(Schema.String.annotate({ identifier: "a" }), "value", "<a>value</a>")
      await assertXml(Schema.String.annotate({ identifier: "a b" }), "value", `<a_b data-name="a b">value</a_b>`)
      await assertXml(Schema.String.annotate({ identifier: "a", title: "b" }), "value", "<a>value</a>")
    })

    it("should use the title as the root name", async () => {
      await assertXml(Schema.String.annotate({ title: "a" }), "value", "<a>value</a>")
      await assertXml(Schema.String.annotate({ title: "a b" }), "value", `<a_b data-name="a b">value</a_b>`)
    })

    it("should escape the text", async () => {
      await assertXml(Schema.String, "value&", `<root>value&amp;</root>`)
      await assertXml(Schema.String, "<value/>", `<root>&lt;value/&gt;</root>`)
    })

    it("should escape the attributes", async () => {
      await assertXml(Schema.String.annotate({ title: "value&" }), "", `<value_ data-name="value&amp;"></value_>`)
      await assertXml(
        Schema.String.annotate({ title: "<value/>" }),
        "",
        `<__value__ data-name="&lt;value/&gt;"></__value__>`
      )
    })

    it("Never", async () => {
      await assertXmlFailure(Schema.Never, "test", `Expected never, got "test"`)
    })

    it("Any", async () => {
      await assertXml(Schema.Any, "test", "<root>test</root>")
      await assertXml(Schema.Any, 42, "<root>42</root>")
      await assertXml(
        Schema.Any,
        { a: 1 },
        `<root>
  <a>1</a>
</root>`
      )
    })

    it("Void", async () => {
      await assertXml(Schema.Void, undefined, "<root/>")
    })

    it("Undefined", async () => {
      await assertXml(Schema.Undefined, undefined, "<root/>")
    })

    it("Null", async () => {
      await assertXml(Schema.Null, null, "<root>null</root>")
    })

    it("Number", async () => {
      await assertXml(Schema.Number, 1, "<root>1</root>")
      await assertXml(Schema.Number, 0, "<root>0</root>")
      await assertXml(Schema.Number, -1.5, "<root>-1.5</root>")
      await assertXml(Schema.Number, Infinity, "<root>Infinity</root>")
      await assertXml(Schema.Number, -Infinity, "<root>-Infinity</root>")
      await assertXml(Schema.Number, NaN, "<root>NaN</root>")
    })

    it("Boolean", async () => {
      await assertXml(Schema.Boolean, true, "<root>true</root>")
      await assertXml(Schema.Boolean, false, "<root>false</root>")
    })

    it("BigInt", async () => {
      await assertXml(Schema.BigInt, BigInt(42), "<root>42</root>")
      await assertXml(Schema.BigInt, BigInt(0), "<root>0</root>")
      await assertXml(Schema.BigInt, BigInt(-123), "<root>-123</root>")
    })

    it("Symbol", async () => {
      const sym = Symbol.for("test")
      await assertXml(Schema.Symbol, sym, "<root>Symbol(test)</root>")
    })

    it("TemplateLiteral", async () => {
      const schema = Schema.TemplateLiteral([Schema.Literal("Hello "), Schema.String, Schema.Literal("!")])
      await assertXml(schema, "Hello World!", "<root>Hello World!</root>")
    })

    it("Struct", async () => {
      await assertXml(
        Schema.Struct({
          a: Schema.Number,
          "a b": Schema.Number
        }),
        { a: 1, "a b": 2 },
        `<root>
  <a>1</a>
  <a_b data-name="a b">2</a_b>
</root>`
      )
    })

    it("Array", async () => {
      await assertXml(Schema.Array(Schema.Number), [], "<root/>")
      await assertXml(
        Schema.Array(Schema.Number),
        [1, 2, 3],
        `<root>
  <item>1</item>
  <item>2</item>
  <item>3</item>
</root>`
      )
    })

    it("Array with custom item name", async () => {
      const serializer = Schema.toEncoderXml(Schema.toCodecStringTree(Schema.Array(Schema.Number)), {
        arrayItemName: "number"
      })
      strictEqual(
        await Effect.runPromise(serializer([1, 2, 3])),
        `<root>
  <number>1</number>
  <number>2</number>
  <number>3</number>
</root>`
      )
    })

    it("Union", async () => {
      await assertXml(Schema.Union([Schema.String, Schema.Number]), "test", "<root>test</root>")
      await assertXml(Schema.Union([Schema.String, Schema.Number]), 42, "<root>42</root>")
    })

    it("Tuple", async () => {
      await assertXml(Schema.Tuple([]), [], "<root/>")
      await assertXml(
        Schema.Tuple([Schema.String, Schema.Number, Schema.Boolean]),
        ["a", 1, true],
        `<root>
  <item>a</item>
  <item>1</item>
  <item>true</item>
</root>`
      )
    })

    it("Record", async () => {
      await assertXml(Schema.Record(Schema.String, Schema.Number), {}, "<root/>")
      await assertXml(
        Schema.Record(Schema.String, Schema.Number),
        { a: 1, b: 2 },
        `<root>
  <a>1</a>
  <b>2</b>
</root>`
      )
    })

    it("NullOr", async () => {
      await assertXml(Schema.NullOr(Schema.String), "test", "<root>test</root>")
      await assertXml(Schema.NullOr(Schema.String), null, "<root>null</root>")
    })

    it("TaggedUnion", async () => {
      const schema = Schema.TaggedUnion({
        A: { value: Schema.String },
        B: { value: Schema.Number }
      })
      await assertXml(
        schema,
        { _tag: "A", value: "test" },
        `<root>
  <_tag>A</_tag>
  <value>test</value>
</root>`
      )
      await assertXml(
        schema,
        { _tag: "B", value: 42 },
        `<root>
  <_tag>B</_tag>
  <value>42</value>
</root>`
      )
    })

    it("TaggedStruct", async () => {
      const schema = Schema.TaggedStruct("User", {
        name: Schema.String,
        age: Schema.Number
      })
      await assertXml(
        schema,
        { _tag: "User", name: "John", age: 30 },
        `<root>
  <_tag>User</_tag>
  <age>30</age>
  <name>John</name>
</root>`
      )
    })

    it("Enum", async () => {
      const schema = Schema.Enum({
        A: "a",
        B: "b"
      })
      await assertXml(schema, "a", "<root>a</root>")
      await assertXml(schema, "b", "<root>b</root>")
    })

    it("Literals", async () => {
      const schema = Schema.Literals(["a", 1, true, 1n])
      await assertXml(schema, "a", "<root>a</root>")
      await assertXml(schema, 1, "<root>1</root>")
      await assertXml(schema, true, "<root>true</root>")
      await assertXml(schema, 1n, "<root>1</root>")
    })

    it("Nested Structures", async () => {
      const schema = Schema.Struct({
        user: Schema.Struct({
          name: Schema.String,
          age: Schema.Number
        }),
        tags: Schema.Array(Schema.String)
      })
      await assertXml(
        schema,
        { user: { name: "John", age: 30 }, tags: ["admin", "user"] },
        `<root>
  <tags>
    <item>admin</item>
    <item>user</item>
  </tags>
  <user>
    <age>30</age>
    <name>John</name>
  </user>
</root>`
      )
    })

    it("Special Characters in Text", async () => {
      await assertXml(Schema.String, "&<>\"'", `<root>&amp;&lt;&gt;"'</root>`)
      await assertXml(
        Schema.String,
        "line1\nline2",
        `<root>line1
line2</root>`
      )
      await assertXml(Schema.String, "tab\there", "<root>tab	here</root>")
    })

    it("Special Characters in Attributes", async () => {
      await assertXml(
        Schema.String.annotate({ title: "test&value" }),
        "content",
        `<test_value data-name="test&amp;value">content</test_value>`
      )
      await assertXml(
        Schema.String.annotate({ title: "test<value>" }),
        "content",
        `<test_value_ data-name="test&lt;value&gt;">content</test_value_>`
      )
      await assertXml(
        Schema.String.annotate({ title: "test\"value" }),
        "content",
        `<test_value data-name="test&quot;value">content</test_value>`
      )
    })

    it("XML Reserved Names", async () => {
      await assertXml(
        Schema.String.annotate({ title: "xml" }),
        "content",
        `<_xml data-name="xml">content</_xml>`
      )
      await assertXml(
        Schema.String.annotate({ title: "XML" }),
        "content",
        `<_XML data-name="XML">content</_XML>`
      )
      await assertXml(
        Schema.String.annotate({ title: "xmlns" }),
        "content",
        `<_xmlns data-name="xmlns">content</_xmlns>`
      )
    })

    it("Invalid XML Tag Names", async () => {
      await assertXml(
        Schema.String.annotate({ title: "123invalid" }),
        "content",
        `<_123invalid data-name="123invalid">content</_123invalid>`
      )
      await assertXml(
        Schema.String.annotate({ title: "invalid name" }),
        "content",
        `<invalid_name data-name="invalid name">content</invalid_name>`
      )
    })

    it("Empty String", async () => {
      await assertXml(Schema.String, "", "<root></root>")
    })

    it("Whitespace Only String", async () => {
      await assertXml(Schema.String, "   ", "<root>   </root>")
      await assertXml(Schema.String, "\n\t", "<root>\n\t</root>")
    })

    it("Unicode Characters", async () => {
      await assertXml(Schema.String, "Hello 世界", "<root>Hello 世界</root>")
      await assertXml(Schema.String, "🚀🌟✨", "<root>🚀🌟✨</root>")
      await assertXml(Schema.String, "αβγδε", "<root>αβγδε</root>")
    })

    it("XML Encoder Options - rootName", async () => {
      const serializer = Schema.toEncoderXml(Schema.toCodecStringTree(Schema.String), {
        rootName: "custom"
      })
      strictEqual(await Effect.runPromise(serializer("test")), "<custom>test</custom>")
    })

    it("XML Encoder Options - pretty: false", async () => {
      const serializer = Schema.toEncoderXml(
        Schema.toCodecStringTree(Schema.Struct({
          a: Schema.Number,
          b: Schema.String
        })),
        {
          pretty: false
        }
      )
      strictEqual(await Effect.runPromise(serializer({ a: 1, b: "test" })), "<root><a>1</a><b>test</b></root>")
    })

    it("XML Encoder Options - custom indent", async () => {
      const serializer = Schema.toEncoderXml(
        Schema.toCodecStringTree(Schema.Struct({
          a: Schema.Number
        })),
        {
          indent: "    "
        }
      )
      strictEqual(
        await Effect.runPromise(serializer({ a: 1 })),
        `<root>
    <a>1</a>
</root>`
      )
    })

    it("XML Encoder Options - sortKeys: false", async () => {
      const serializer = Schema.toEncoderXml(
        Schema.toCodecStringTree(Schema.Struct({
          z: Schema.Number,
          a: Schema.Number,
          m: Schema.Number
        })),
        {
          sortKeys: false
        }
      )
      strictEqual(
        await Effect.runPromise(serializer({ z: 3, a: 1, m: 2 })),
        `<root>
  <z>3</z>
  <a>1</a>
  <m>2</m>
</root>`
      )
    })

    it("Circular Reference Detection", async () => {
      const obj: any = { name: "test" }
      obj.self = obj

      const serializer = Schema.toEncoderXml(Schema.toCodecStringTree(Schema.Any))
      try {
        await Effect.runPromise(serializer(obj))
        throw new Error("Expected error")
      } catch (error: any) {
        strictEqual(error.message, "Cycle detected while serializing to XML.")
      }
    })

    it("Nested Arrays", async () => {
      const schema = Schema.Array(Schema.Array(Schema.Number))
      await assertXml(
        schema,
        [[1, 2], [3, 4]],
        `<root>
  <item>
    <item>1</item>
    <item>2</item>
  </item>
  <item>
    <item>3</item>
    <item>4</item>
  </item>
</root>`
      )
    })

    it("Record with Number Keys", async () => {
      const schema = Schema.Record(Schema.Number, Schema.String)
      await assertXml(
        schema,
        { 1: "one", 2: "two" },
        `<root>
  <_1 data-name="1">one</_1>
  <_2 data-name="2">two</_2>
</root>`
      )
    })

    it("Record with Symbol Keys", async () => {
      const sym1 = Symbol.for("key1")
      const sym2 = Symbol.for("key2")
      const schema = Schema.Record(Schema.Symbol, Schema.String)
      await assertXml(
        schema,
        { [sym1]: "value1", [sym2]: "value2" },
        `<root>
  <Symbol_key1_ data-name="Symbol(key1)">value1</Symbol_key1_>
  <Symbol_key2_ data-name="Symbol(key2)">value2</Symbol_key2_>
</root>`
      )
    })

    it("Tuple with Rest", async () => {
      const schema = Schema.TupleWithRest(Schema.Tuple([Schema.String, Schema.Number]), [Schema.Boolean])
      await assertXml(
        schema,
        ["test", 42, true, false],
        `<root>
  <item>test</item>
  <item>42</item>
  <item>true</item>
  <item>false</item>
</root>`
      )
    })

    it("Suspend (Recursive Types)", async () => {
      interface Tree {
        readonly value: number
        readonly children: ReadonlyArray<Tree>
      }

      const Tree: Schema.Codec<Tree> = Schema.Struct({
        value: Schema.Number,
        children: Schema.Array(Schema.suspend(() => Tree))
      })

      const tree: Tree = {
        value: 1,
        children: [
          { value: 2, children: [] },
          { value: 3, children: [] }
        ]
      }

      await assertXml(
        Tree,
        tree,
        `<root>
  <children>
    <item>
      <children/>
      <value>2</value>
    </item>
    <item>
      <children/>
      <value>3</value>
    </item>
  </children>
  <value>1</value>
</root>`
      )
    })
  })
})
