/**
 * Describes data shapes and how unknown input becomes trusted values.
 *
 * A schema can validate input, decode it into an application type, and encode
 * that value back to another representation. This module contains the main
 * schema, codec, decoder, and encoder APIs, together with schemas for common
 * JavaScript values and Effect data types. It also supports refinements,
 * transformations, defaults, classes, JSON Schema generation, test data
 * generation, formatting, equivalence, optics, and differs derived from schema
 * definitions.
 *
 * @since 4.0.0
 */

/** @effect-diagnostics schemaStructWithTag:skip-file */
import type { StandardJSONSchemaV1, StandardSchemaV1 } from "@standard-schema/spec"
import * as Arr from "./Array.ts"
import * as BigDecimal_ from "./BigDecimal.ts"
import type * as Brand from "./Brand.ts"
import * as Cause_ from "./Cause.ts"
import * as Chunk_ from "./Chunk.ts"
import type * as Combiner from "./Combiner.ts"
import * as Data from "./Data.ts"
import * as DateTime from "./DateTime.ts"
import type { Differ } from "./Differ.ts"
import * as Duration_ from "./Duration.ts"
import * as Effect from "./Effect.ts"
import * as Encoding from "./Encoding.ts"
import * as Equal from "./Equal.ts"
import * as Equivalence from "./Equivalence.ts"
import * as Exit_ from "./Exit.ts"
import type { Formatter } from "./Formatter.ts"
import { format, formatPropertyKey } from "./Formatter.ts"
import { identity, memoize } from "./Function.ts"
import * as HashMap_ from "./HashMap.ts"
import * as HashSet_ from "./HashSet.ts"
import * as core from "./internal/core.ts"
import * as InternalAnnotations from "./internal/schema/annotations.ts"
import * as InternalArbitrary from "./internal/schema/arbitrary.ts"
import * as InternalEquivalence from "./internal/schema/equivalence.ts"
import * as InternalStandard from "./internal/schema/representation.ts"
import * as InternalSchema from "./internal/schema/schema.ts"
import * as JsonPatch from "./JsonPatch.ts"
import * as JsonSchema from "./JsonSchema.ts"
import { remainder } from "./Number.ts"
import * as Optic_ from "./Optic.ts"
import * as Option_ from "./Option.ts"
import * as Order from "./Order.ts"
import * as Pipeable from "./Pipeable.ts"
import * as Predicate from "./Predicate.ts"
import * as Record_ from "./Record.ts"
import * as Redacted_ from "./Redacted.ts"
import * as Result_ from "./Result.ts"
import * as Scheduler from "./Scheduler.ts"
import * as SchemaAST from "./SchemaAST.ts"
import { isSchemaError, SchemaError } from "./SchemaError.ts"
import * as SchemaGetter from "./SchemaGetter.ts"
import * as SchemaIssue from "./SchemaIssue.ts"
import * as SchemaParser from "./SchemaParser.ts"
import type * as SchemaRepresentation from "./SchemaRepresentation.ts"
import * as SchemaTransformation from "./SchemaTransformation.ts"
import type { Assign, Lambda, Mutable, Simplify } from "./Struct.ts"
import * as Struct_ from "./Struct.ts"
import * as FastCheck from "./testing/FastCheck.ts"
import type { RequiredKeys, UnionToIntersection } from "./Types.ts"
import type { Unify } from "./Unify.ts"

const TypeId = InternalSchema.TypeId

/**
 * Whether a schema field is required or optional within a struct.
 *
 * @see {@link optionalKey} — mark a struct field as optional
 * @see {@link optional} — mark a struct field as optional with `| undefined`
 *
 * @category models
 * @since 4.0.0
 */
export type Optionality = "required" | "optional"

/**
 * Whether a schema field is readonly or mutable within a struct.
 *
 * @see {@link mutableKey} — mark a struct field as mutable
 *
 * @category models
 * @since 4.0.0
 */
export type Mutability = "readonly" | "mutable"

/**
 * Whether a schema field has a constructor default value.
 *
 * @see {@link withConstructorDefault} — add a default to a schema field
 * @see {@link tag} — creates a literal field with a constructor default
 *
 * @category models
 * @since 4.0.0
 */
export type ConstructorDefault = "no-default" | "with-default"

/**
 * Options for `makeEffect`, `make`, and Class constructors.
 *
 * **When to use**
 *
 * Use when passing `disableChecks: true` to skip validation when you trust the data.
 * - Pass `parseOptions` to control error reporting behavior.
 *
 * @see {@link Bottom.makeEffect}
 * @see {@link Bottom.make}
 *
 * @category options
 * @since 3.13.4
 */
export interface MakeOptions {
  /**
   * The parse options to use for the schema.
   */
  readonly parseOptions?: SchemaAST.ParseOptions | undefined
  /**
   * Whether to disable validation for the schema.
   */
  readonly disableChecks?: boolean | undefined

  /** @internal */
  readonly "~payload"?: {
    readonly token: unknown
    readonly value: unknown
  }
}

/**
 * The fully-parameterized base interface for all schemas. Exposes all 14 type
 * parameters controlling type inference, mutability, optionality, services, and
 * transformation behavior.
 *
 * **When to use**
 *
 * Use when you are writing advanced generic schema utilities or performing
 *   schema introspection.
 *
 * @category models
 * @since 4.0.0
 */
export interface Bottom<
  out T,
  out E,
  out RD,
  out RE,
  out Ast extends SchemaAST.AST,
  out Rebuild extends Top,
  out TypeMakeIn = T,
  out Iso = T,
  in out TypeParameters extends ReadonlyArray<Constraint> = readonly [],
  out TypeMake = TypeMakeIn,
  out TypeMutability extends Mutability = "readonly",
  out TypeOptionality extends Optionality = "required",
  out TypeConstructorDefault extends ConstructorDefault = "no-default",
  out EncodedMutability extends Mutability = "readonly",
  out EncodedOptionality extends Optionality = "required"
> extends Pipeable.Pipeable {
  readonly [TypeId]: typeof TypeId

  readonly "ast": Ast
  readonly "Rebuild": Rebuild
  readonly "~type.parameters": TypeParameters

  readonly "Type": T
  readonly "Encoded": E
  readonly "DecodingServices": RD
  readonly "EncodingServices": RE

  readonly "~type.make.in": TypeMakeIn
  readonly "~type.make": TypeMake // useful to type the `refine` interface
  readonly "~type.constructor.default": TypeConstructorDefault
  readonly "Iso": Iso

  readonly "~type.mutability": TypeMutability
  readonly "~type.optionality": TypeOptionality
  readonly "~encoded.mutability": EncodedMutability
  readonly "~encoded.optionality": EncodedOptionality

  annotate(annotations: Annotations.Bottom<this["Type"], this["~type.parameters"]>): this["Rebuild"]
  annotateKey(annotations: Annotations.Key<this["Type"]>): this["Rebuild"]
  check(...checks: readonly [SchemaAST.Check<this["Type"]>, ...Array<SchemaAST.Check<this["Type"]>>]): this["Rebuild"]
  rebuild(ast: this["ast"]): this["Rebuild"]
  /**
   * Constructs a value from the make input representation synchronously.
   *
   * **When to use**
   *
   * Use when constructor input is trusted or when validation failure
   * should abort with a thrown `Error`.
   *
   * **Details**
   *
   * Applies constructor defaults and type-side validation according to
   * `MakeOptions`.
   *
   * **Gotchas**
   *
   * Throws an `Error` with the schema issue in its `cause` when validation
   * fails.
   * Causes that contain defects, interruptions, or other non-schema reasons
   * throw with the underlying `Cause` attached instead.
   *
   * @see {@link Bottom.makeOption} — construct synchronously and discard validation details
   * @see {@link Bottom.makeEffect} — construct through `Effect` when validation failure should stay in the error channel
   */
  make(input: this["~type.make.in"], options?: MakeOptions): this["Type"]
  /**
   * Constructs a value from the make input representation, returning `Option.none`
   * when validation fails.
   *
   * **When to use**
   *
   * Use when you only need to know whether construction succeeds
   * and do not need validation details.
   *
   * **Details**
   *
   * Applies constructor defaults and type-side validation according to
   * `MakeOptions`.
   *
   * **Gotchas**
   *
   * Only causes made entirely of schema issues are converted to `None`. Causes
   * that contain defects, interruptions, or other non-schema reasons throw
   * instead.
   *
   * @see {@link Bottom.make} — construct synchronously when validation failure should throw
   * @see {@link Bottom.makeEffect} — construct through `Effect` when validation details should stay in the error channel
   */
  makeOption(input: this["~type.make.in"], options?: MakeOptions): Option_.Option<this["Type"]>
  /**
   * Constructs a value from the make input representation, returning validation
   * failures in the `Effect` error channel.
   *
   * **When to use**
   *
   * Use when constructor input may fail validation and you want to
   * compose that failure with other `Effect` operations instead of throwing.
   *
   * @see {@link Bottom.make} — construct synchronously when validation failure should throw
   * @see {@link Bottom.makeOption} — construct synchronously and discard validation details
   */
  makeEffect(input: this["~type.make.in"], options?: MakeOptions): Effect.Effect<this["Type"], SchemaError>
}

/**
 * Lazy `Bottom` variant for schema implementations that compute their public
 * views on demand.
 *
 * **When to use**
 *
 * Use as an implementation base for schema interfaces that must expose
 * `Bottom` behavior without forcing TypeScript to eagerly evaluate expensive
 * `Type`, `Encoded`, or service views.
 *
 * **Details**
 *
 * The laziness is purely type-level; runtime behavior is unchanged.
 * `BottomLazy` keeps the structural operations inherited from `Bottom`, but
 * erases the expensive schema views to `unknown`. Concrete schema interfaces can
 * then redeclare the precise views they expose. This keeps wide schemas such as
 * `Struct` and `Union` cheaper when generic code reads a single view, while
 * preserving their exact public types.
 *
 * @see {@link Bottom} for the fully parameterized schema interface when every
 * view must be supplied directly.
 *
 * @category utility types
 * @since 4.0.0
 */
export interface BottomLazy<
  out Ast extends SchemaAST.AST,
  out Rebuild extends Top,
  in out TypeParameters extends ReadonlyArray<Constraint> = readonly [],
  out TypeMutability extends Mutability = "readonly",
  out TypeOptionality extends Optionality = "required",
  out TypeConstructorDefault extends ConstructorDefault = "no-default",
  out EncodedMutability extends Mutability = "readonly",
  out EncodedOptionality extends Optionality = "required"
> extends
  Bottom<
    unknown,
    unknown,
    unknown,
    unknown,
    Ast,
    Rebuild,
    unknown,
    unknown,
    TypeParameters,
    unknown,
    TypeMutability,
    TypeOptionality,
    TypeConstructorDefault,
    EncodedMutability,
    EncodedOptionality
  >
{}

/**
 * Type-level representation returned by {@link declareConstructor}.
 *
 * @category constructors
 * @since 4.0.0
 */
export interface declareConstructor<T, E, TypeParameters extends ReadonlyArray<Constraint>, Iso = T> extends
  Bottom<
    T,
    E,
    TypeParameters[number]["DecodingServices"],
    TypeParameters[number]["EncodingServices"],
    SchemaAST.Declaration,
    declareConstructor<T, E, TypeParameters, Iso>,
    T,
    Iso,
    TypeParameters
  >
{}

/**
 * Creates a schema for a **parametric** type (a generic container such as
 * `Array<A>`, `Option<A>`, etc.) by accepting a list of type-parameter schemas
 * and a decoder factory.
 *
 * **When to use**
 *
 * Use when you are defining a schema for a generic container whose validation
 * depends on one or more type-parameter schemas.
 *
 * **Details**
 *
 * The outer call `declareConstructor<T, E, Iso>()` fixes the decoded type `T`,
 * the encoded type `E`, and the optional iso type. The inner call receives:
 * - `typeParameters` — the concrete schemas for each type variable
 * - `run` — a factory that, given resolved codecs for each type parameter,
 *   returns a parsing function `(u, ast, options) => Effect<T, Issue>`
 * - `annotations` — optional metadata
 *
 * @see {@link declare} for creating schemas for non-parametric types.
 *
 * **Example** (Schema for a parametric `Box<A>` type)
 *
 * ```ts
 * import { Effect, Option, Schema, SchemaIssue as Issue, SchemaParser } from "effect"
 *
 * interface Box<A> {
 *   readonly value: A
 * }
 *
 * const isBox = (u: unknown): u is Box<unknown> =>
 *   typeof u === "object" && u !== null && "value" in u
 *
 * const Box = <A extends Schema.Constraint>(item: A) =>
 *   Schema.declareConstructor<Box<A["Type"]>, Box<A["Encoded"]>>()(
 *     [item],
 *     ([itemCodec]) =>
 *       (u, ast, options) => {
 *         if (!isBox(u)) {
 *           return Effect.fail(new SchemaIssue.InvalidType(ast, Option.some(u)))
 *         }
 *         return Effect.map(
 *           SchemaParser.decodeUnknownEffect(itemCodec)(u.value, options),
 *           (value) => ({ value })
 *         )
 *       }
 *   )
 *
 * const schema = Box(Schema.Number)
 * ```
 *
 * @category constructors
 * @since 4.0.0
 */
export function declareConstructor<T, E = T, Iso = T>() {
  return <const TypeParameters extends ReadonlyArray<Constraint>>(
    typeParameters: TypeParameters,
    run: (
      typeParameters: {
        readonly [K in keyof TypeParameters]: Codec<TypeParameters[K]["Type"], TypeParameters[K]["Encoded"]>
      }
    ) => (
      u: unknown,
      self: SchemaAST.Declaration,
      options: SchemaAST.ParseOptions
    ) => Effect.Effect<T, SchemaIssue.Issue>,
    annotations?: Annotations.Declaration<T, TypeParameters>
  ): declareConstructor<T, E, TypeParameters, Iso> => {
    return make(
      new SchemaAST.Declaration(
        typeParameters.map(SchemaAST.getAST),
        (typeParameters) => run(typeParameters.map((ast) => make(ast)) as any),
        annotations
      )
    )
  }
}

/**
 * Type-level representation returned by {@link declare}.
 *
 * @category constructors
 * @since 3.13.3
 */
export interface declare<T, Iso = T> extends declareConstructor<T, T, readonly [], Iso> {
  readonly "Rebuild": declare<T, Iso>
}

/**
 * Creates a schema for a **non-parametric** opaque type using a type-guard
 * function. The schema accepts any unknown value and succeeds when `is` returns
 * `true`, failing with an `InvalidType` issue otherwise.
 *
 * **When to use**
 *
 * Use when you are defining a schema for an opaque type with no type parameters
 * and validation can be expressed as a type guard.
 *
 * **Example** (Defining a schema for a custom `UserId` branded type)
 *
 * ```ts
 * import { Schema } from "effect"
 *
 * type UserId = string & { readonly _tag: "UserId" }
 *
 * const isUserId = (u: unknown): u is UserId =>
 *   typeof u === "string" && u.startsWith("user_")
 *
 * const UserId = Schema.declare<UserId>(isUserId, {
 *   title: "UserId",
 *   description: "A user identifier starting with 'user_'"
 * })
 * ```
 *
 * @see {@link declareConstructor} for creating schemas for parametric types.
 *
 * @category constructors
 * @since 3.10.0
 */
export function declare<T, Iso = T>(
  is: (u: unknown) => u is T,
  annotations?: Annotations.Declaration<T> | undefined
): declare<T, Iso> {
  return declareConstructor<T, T, Iso>()(
    [],
    () => (input, ast) =>
      is(input) ?
        Effect.succeed(input) :
        Effect.fail(new SchemaIssue.InvalidType(ast, Option_.some(input))),
    annotations
  )
}

/**
 * Returns a schema widened to the fully-parameterized {@link Bottom} interface,
 * making all 14 type parameters visible to TypeScript.
 *
 * **Details**
 *
 * Normally, concrete schema interfaces (e.g. `Schema<string>`) hide most type
 * parameters. `revealBottom` is useful when writing generic utilities that need
 * to inspect or propagate the complete set of type parameters.
 *
 * **Example** (Inspecting all type parameters of a schema)
 *
 * ```ts
 * import { Schema } from "effect"
 *
 * const schema = Schema.String
 *
 * // Widen to Bottom to access all 14 type parameters
 * const bottom = Schema.revealBottom(schema)
 *
 * // `bottom` now exposes Type, Encoded, DecodingServices, EncodingServices,
 * // ast, Rebuild, ~type.make.in, Iso, ~type.parameters, etc.
 * type T = typeof bottom["Type"]     // string
 * type E = typeof bottom["Encoded"]  // string
 * ```
 *
 * @category utility types
 * @since 4.0.0
 */
export function revealBottom<S extends Top>(
  bottom: S
): Bottom<
  S["Type"],
  S["Encoded"],
  S["DecodingServices"],
  S["EncodingServices"],
  S["ast"],
  S["Rebuild"],
  S["~type.make.in"],
  S["Iso"],
  S["~type.parameters"],
  S["~type.make"],
  S["~type.mutability"],
  S["~type.optionality"],
  S["~type.constructor.default"],
  S["~encoded.mutability"],
  S["~encoded.optionality"]
> {
  return bottom
}

/**
 * Adds metadata annotations to a schema without changing its runtime behavior.
 * This is the pipeable (curried) counterpart of the `.annotate` method.
 *
 * **Details**
 *
 * Annotations provide extra context used by documentation generators, JSON
 * Schema converters, error formatters, and other tooling. Common keys include
 * `title`, `description`, `examples`, `message`, and `identifier`.
 *
 * **Example** (Adding a title and description)
 *
 * ```ts
 * import { Schema } from "effect"
 *
 * const Age = Schema.Number.pipe(
 *   Schema.annotate({
 *     title: "Age",
 *     description: "A non-negative integer representing age in years"
 *   })
 * )
 * ```
 *
 * @see {@link annotateEncoded} to annotate the encoded side instead.
 *
 * @category annotations
 * @since 4.0.0
 */
export function annotate<S extends Top>(annotations: Annotations.Bottom<S["Type"], S["~type.parameters"]>) {
  return (self: S) => self.annotate(annotations)
}

/**
 * Adds metadata annotations to the **encoded** side of a schema without
 * changing its runtime behavior. This is the encoded-side counterpart of
 * `annotate`, which targets the decoded (Type) side.
 *
 * **Details**
 *
 * Internally the schema is flipped so that `Encoded` becomes `Type`,
 * annotated, and then flipped back.
 *
 * **Example** (Adding a title to the encoded representation)
 *
 * ```ts
 * import { Schema } from "effect"
 *
 * const schema = Schema.NumberFromString.pipe(
 *   Schema.annotateEncoded({
 *     title: "my title"
 *   })
 * )
 *
 * console.log(Schema.toEncoded(schema).ast.annotations?.title)
 * // "my title"
 * ```
 *
 * @see {@link annotate} to annotate the type side instead.
 *
 * @category annotations
 * @since 4.0.0
 */
export function annotateEncoded<S extends Top>(annotations: Annotations.Bottom<S["Encoded"], readonly []>) {
  return (self: S): S["Rebuild"] => flip(flip(self).annotate(annotations))
}

/**
 * Adds key-level annotations to a schema field. This is the pipeable
 * (curried) counterpart of the `.annotateKey` method.
 *
 * **Details**
 *
 * Key annotations apply to a field's position inside a `Struct` or `Tuple`
 * rather than to the field's value type. They can carry a
 * `messageMissingKey` to customise the error shown when the field is absent,
 * as well as standard documentation fields such as `title`, `description`,
 * and `examples`.
 *
 * **Example** (Customizing the missing-key message for a required field)
 *
 * ```ts
 * import { Schema } from "effect"
 *
 * const schema = Schema.Struct({
 *   username: Schema.String.pipe(
 *     Schema.annotateKey({
 *       description: "The username used to log in",
 *       messageMissingKey: "Username is required"
 *     })
 *   )
 * })
 * ```
 *
 * @category annotations
 * @since 4.0.0
 */
export function annotateKey<S extends Top>(annotations: Annotations.Key<S["Type"]>) {
  return (self: S): S["Rebuild"] => {
    return self.rebuild(SchemaAST.annotateKey(self.ast, annotations))
  }
}

/**
 * The existential "any schema" type — all type parameters are erased to `unknown`.
 *
 * **Details**
 *
 * Use `Top` as a constraint when writing generic utilities that must accept *any*
 * schema regardless of its `Type`, `Encoded`, or service requirements. It is the
 * widest possible schema type and therefore gives you the least static information.
 *
 * In user code prefer the narrower interfaces:
 * - {@link Schema}`<T>` — when you only care about the decoded type
 * - {@link Codec}`<T, E, RD, RE>` — when you need the encoded type and service requirements
 * - {@link ConstraintDecoder}`<T, RD>` — for decode-only APIs
 * - {@link ConstraintEncoder}`<E, RE>` — for encode-only APIs
 *
 * @category models
 * @since 4.0.0
 */
export interface Top extends
  Bottom<
    unknown,
    unknown,
    unknown,
    unknown,
    SchemaAST.AST,
    Top,
    unknown,
    unknown,
    any, // this is because TypeParameters is invariant
    unknown,
    Mutability,
    Optionality,
    ConstructorDefault,
    Mutability,
    Optionality
  >
{}

/**
 * Lightweight structural constraint for APIs that accept schema values but only
 * read their data and type-level views.
 *
 * **When to use**
 *
 * Use when you need to constrain a generic value to be a schema, but the API
 * only reads properties such as `ast`, `Type`, `Encoded`, service
 * requirements, constructor input views, or modifier flags.
 *
 * **Details**
 *
 * `Constraint` keeps the schema type identifier and the property surface needed
 * by schema constructors, while avoiding the full `Bottom` protocol. Use
 * {@link Top} when an API calls schema methods such as `annotate`, `check`,
 * `rebuild`, `make`, or `makeEffect`.
 *
 * @see {@link Top} for the complete schema protocol.
 *
 * @category models
 * @since 4.0.0
 */
export interface Constraint {
  readonly [TypeId]: typeof TypeId
  readonly "ast": SchemaAST.AST

  readonly "Type": unknown
  readonly "Encoded": unknown
  readonly "DecodingServices": unknown
  readonly "EncodingServices": unknown

  readonly "~type.parameters": any
  readonly "~type.make.in": unknown
  readonly "~type.make": unknown
  readonly "Iso": unknown

  readonly "~type.optionality": Optionality
  readonly "~type.mutability": Mutability
  readonly "~type.constructor.default": ConstructorDefault
  readonly "~encoded.optionality": Optionality
  readonly "~encoded.mutability": Mutability
}

/**
 * Lightweight structural constraint for APIs that need codec type views but do
 * not need the full schema protocol.
 *
 * **When to use**
 *
 * Use when you need to preserve decoded type, encoded type, and service
 * requirements for a schema value, but the API does not call schema methods
 * such as `annotate`, `check`, `rebuild`, `make`, or `makeEffect`.
 *
 * @see {@link Constraint} for the generic lightweight schema constraint.
 * @see {@link Codec} for the full schema protocol with codec type views.
 *
 * @category models
 * @since 4.0.0
 */
export interface ConstraintCodec<out T, out E = T, out RD = never, out RE = never> extends Constraint {
  readonly "Type": T
  readonly "Encoded": E
  readonly "DecodingServices": RD
  readonly "EncodingServices": RE
}

/**
 * Lightweight structural constraint for APIs that need decoder type views but
 * do not need the full schema protocol.
 *
 * **When to use**
 *
 * Use when you need to preserve a schema's decoded type and decoding services,
 * but the API does not constrain the encoded type, encoding services, or call
 * schema methods such as `annotate`, `check`, `rebuild`, `make`, or
 * `makeEffect`.
 *
 * @see {@link ConstraintCodec} for APIs that need both decoded and encoded codec views.
 * @see {@link Codec} for the full schema protocol with codec type views.
 *
 * @category models
 * @since 4.0.0
 */
export interface ConstraintDecoder<out T, out RD = never> extends ConstraintCodec<T, unknown, RD, unknown> {}

/**
 * Lightweight structural constraint for APIs that need encoder type views but
 * do not need the full schema protocol.
 *
 * **When to use**
 *
 * Use when you need to preserve a schema's encoded type and encoding services,
 * but the API does not constrain the decoded type, decoding services, or call
 * schema methods such as `annotate`, `check`, `rebuild`, `make`, or
 * `makeEffect`.
 *
 * @see {@link ConstraintCodec} for APIs that need both decoded and encoded codec views.
 * @see {@link Codec} for the full schema protocol with codec type views.
 *
 * @category models
 * @since 4.0.0
 */
export interface ConstraintEncoder<out E, out RE = never> extends ConstraintCodec<unknown, E, unknown, RE> {}

/**
 * Lightweight structural constraint for APIs that need schema views and the
 * rebuilt schema type, but do not call the full schema protocol.
 *
 * **When to use**
 *
 * Use when an API needs to read `Rebuild` in addition to the schema views
 * exposed by {@link Constraint}, but does not call methods such as `annotate`,
 * `check`, `rebuild`, `make`, or `makeEffect`.
 *
 * @category models
 * @since 4.0.0
 */
export interface ConstraintRebuildable extends Constraint {
  readonly "Rebuild": Constraint
}

/**
 * Namespace of type-level helpers for {@link Schema}.
 *
 * @since 3.10.0
 */
export declare namespace Schema {
  /**
   * Extracts the decoded `Type` from a schema.
   *
   * **Example** (Extracting the decoded type)
   *
   * ```ts
   * import { Schema } from "effect"
   *
   * const Person = Schema.Struct({ name: Schema.String, age: Schema.Number })
   * type Person = Schema.Schema.Type<typeof Person>
   * // { readonly name: string; readonly age: number }
   * ```
   *
   * @category utility types
   * @since 3.10.0
   */
  export type Type<S> = S extends { readonly "Type": infer T } ? T : never
}

/**
 * A typed view of a schema that tracks only the decoded (output) type `T`.
 *
 * **Details**
 *
 * Use `Schema<T>` as a constraint when you want to accept "any schema that
 * decodes to `T`" and do not need to know or constrain the encoded
 * representation, required services, or any other type parameters.
 *
 * This is a structural interface — concrete schema values are produced by the
 * constructors in this module (e.g. {@link Struct}, {@link String}, {@link Number}).
 * When you also need the encoded type or service requirements, use {@link Codec}.
 *
 * **Example** (Accepting any schema decoding to `string`)
 *
 * ```ts
 * import { Schema } from "effect"
 *
 * declare function print(schema: Schema.Schema<string>): void
 *
 * print(Schema.String)            // ok
 * print(Schema.NonEmptyString)    // ok
 * ```
 *
 * @see {@link Codec} — also tracks Encoded, DecodingServices, EncodingServices
 * @see {@link Schema.Type} — extract the decoded type at the type level
 *
 * @category models
 * @since 3.10.0
 */
export interface Schema<out T> extends Top {
  readonly "Type": T
  readonly "Rebuild": Schema<T>
}

/**
 * Namespace of type-level helpers for {@link Codec}.
 *
 * @since 4.0.0
 */
export declare namespace Codec {
  /**
   * Extracts the encoded (`Encoded`) type from a schema.
   *
   * **Example** (Extracting the encoded type)
   *
   * ```ts
   * import { Schema } from "effect"
   *
   * const schema = Schema.NumberFromString
   * type Enc = Schema.Codec.Encoded<typeof schema>
   * // string
   * ```
   *
   * @category utility types
   * @since 3.10.0
   */
  export type Encoded<S> = S extends { readonly "Encoded": infer E } ? E : never

  /**
   * Extracts the Effect services required during *decoding* from a schema.
   *
   * **Example** (Checking decoding service requirements)
   *
   * ```ts
   * import { Schema } from "effect"
   *
   * const schema = Schema.String
   * type RD = Schema.Codec.DecodingServices<typeof schema>
   * // never
   * ```
   *
   * @category utility types
   * @since 4.0.0
   */
  export type DecodingServices<S> = S extends { readonly "DecodingServices": infer R } ? R : never

  /**
   * Extracts the Effect services required during *encoding* from a schema.
   *
   * **Example** (Checking encoding service requirements)
   *
   * ```ts
   * import { Schema } from "effect"
   *
   * const schema = Schema.String
   * type RE = Schema.Codec.EncodingServices<typeof schema>
   * // never
   * ```
   *
   * @category utility types
   * @since 4.0.0
   */
  export type EncodingServices<S> = S extends { readonly "EncodingServices": infer R } ? R : never
}

/**
 * A schema that tracks the decoded type `T`, the encoded type `E`, and the
 * Effect services required during decoding (`RD`) and encoding (`RE`).
 *
 * **Details**
 *
 * Use `Codec<T, E, RD, RE>` when you need to preserve full type information
 * about a schema — both what it decodes to and what it serializes from/to.
 * Most concrete schemas produced by this module implement `Codec`.
 *
 * For APIs that only need one direction, prefer the narrower views:
 * - {@link Decoder}`<T, RD>` — decode-only
 * - {@link Encoder}`<E, RE>` — encode-only
 * - {@link Schema}`<T>` — type-only (no encoded representation)
 *
 * **Example** (Accepting a codec that decodes to `number` from `string`)
 *
 * ```ts
 * import { Schema } from "effect"
 *
 * declare function serialize<T>(codec: Schema.Codec<T, string>): string
 *
 * serialize(Schema.NumberFromString) // ok — decodes number, encoded as string
 * ```
 *
 * @see {@link Codec.Encoded} — extract the encoded type
 * @see {@link Codec.DecodingServices} — extract required decoding services
 * @see {@link Codec.EncodingServices} — extract required encoding services
 * @see {@link revealCodec} — helper to make TypeScript infer the full Codec type
 *
 * @category models
 * @since 4.0.0
 */
export interface Codec<out T, out E = T, out RD = never, out RE = never> extends Schema<T> {
  readonly "Encoded": E
  readonly "DecodingServices": RD
  readonly "EncodingServices": RE
  readonly "Rebuild": Codec<T, E, RD, RE>
}

/**
 * A schema that tracks the decoded type `T` and the Effect services required
 * during decoding (`RD`).
 *
 * **When to use**
 *
 * Use when you need to preserve a schema's decoded type and decoding service
 * requirements, but do not need to constrain its encoded representation or
 * encoding services.
 *
 * @see {@link Codec} for preserving both decoded and encoded type information.
 * @see {@link Encoder} for the encode-only view.
 *
 * @category models
 * @since 4.0.0
 */
export interface Decoder<out T, out RD = never> extends Schema<T> {
  readonly "Encoded": unknown
  readonly "DecodingServices": RD
  readonly "EncodingServices": unknown
  readonly "Rebuild": Decoder<T, RD>
}

/**
 * A schema that tracks the encoded type `E` and the Effect services required
 * during encoding (`RE`).
 *
 * **When to use**
 *
 * Use when you need to preserve a schema's encoded type and encoding service
 * requirements, but do not need to constrain its decoded representation or
 * decoding services.
 *
 * @see {@link Codec} for preserving both decoded and encoded type information.
 * @see {@link Decoder} for the decode-only view.
 *
 * @category models
 * @since 4.0.0
 */
export interface Encoder<out E, out RE = never> extends Schema<unknown> {
  readonly "Encoded": E
  readonly "DecodingServices": unknown
  readonly "EncodingServices": RE
  readonly "Rebuild": Encoder<E, RE>
}

/**
 * Returns a codec widened to the full {@link Codec} interface, prompting
 * TypeScript to infer all four type parameters (`T`, `E`, `RD`, `RE`).
 *
 * **Details**
 *
 * When a schema is stored in a variable typed as `Schema<T>` or `Top`, the
 * encoded type and service requirements are erased. Passing the value through
 * `revealCodec` recovers those parameters without any runtime cost.
 *
 * **Example** (Recovering encoded type from a schema variable)
 *
 * ```ts
 * import { Schema } from "effect"
 *
 * const schema: Schema.Schema<number> = Schema.NumberFromString
 *
 * // Without revealCodec, Encoded is unknown
 * const codec = Schema.revealCodec(schema)
 * type Enc = typeof codec["Encoded"] // string
 * ```
 *
 * @category utility types
 * @since 4.0.0
 */
export function revealCodec<T, E, RD, RE>(codec: Codec<T, E, RD, RE>) {
  return codec
}

/**
 * A schema that additionally supports optic (lens/prism) operations.
 *
 * **Details**
 *
 * `Optic<T, Iso>` extends {@link Schema}`<T>` with an `Iso` type that
 * describes the isomorphic counterpart used by the optic layer. Crucially,
 * decoding and encoding require *no* Effect services (`DecodingServices` and
 * `EncodingServices` are both `never`), which means the optic can operate
 * purely without an Effect runtime.
 *
 * Most primitive schemas (e.g. `Schema.String`, `Schema.Number`) implement
 * `Optic` automatically. You normally interact with this interface through
 * {@link Optic_} utilities rather than constructing it directly.
 *
 * @category models
 * @since 4.0.0
 */
export interface Optic<out T, out Iso> extends Schema<T> {
  readonly "Iso": Iso
  readonly "DecodingServices": never
  readonly "EncodingServices": never
  readonly "Rebuild": Optic<T, Iso>
}

export {
  /**
   * Returns `true` if `u` is a {@link SchemaError}.
   *
   * **Example** (Narrowing Schema errors in a catch block)
   *
   * ```ts
   * import { Schema } from "effect"
   *
   * try {
   *   Schema.decodeUnknownSync(Schema.Number)("oops")
   * } catch (err) {
   *   if (Schema.isSchemaError(err)) {
   *     console.log(err._tag) // "SchemaError"
   *   }
   * }
   * ```
   *
   * @category guards
   * @since 4.0.0
   */
  isSchemaError,
  /**
   * Error thrown (or returned as the error channel value) when schema decoding
   * or encoding fails.
   *
   * **Details**
   *
   * The `issue` field contains a structured {@link SchemaIssue.Issue} tree describing
   * every validation failure, including the path to the problematic value,
   * expected types, and actual values received. `message` renders the issue tree
   * as a human-readable string.
   *
   * Use {@link isSchemaError} to narrow an unknown value to `SchemaError`.
   *
   * **Example** (Catching a SchemaError)
   *
   * ```ts
   * import { Schema } from "effect"
   *
   * try {
   *   Schema.decodeUnknownSync(Schema.Number)("not a number")
   * } catch (err) {
   *   if (Schema.isSchemaError(err)) {
   *     console.log(err.message)
   *     // Expected number, actual "not a number"
   *   }
   * }
   * ```
   *
   * @category errors
   * @since 4.0.0
   */
  SchemaError
}

function makeStandardResult<A>(exit: Exit_.Exit<StandardSchemaV1.Result<A>>): StandardSchemaV1.Result<A> {
  return Exit_.isSuccess(exit) ? exit.value : {
    issues: [{ message: Cause_.pretty(exit.cause) }]
  }
}

/**
 * Returns a "Standard Schema" object conforming to the [Standard Schema
 * v1](https://standardschema.dev/) specification.
 *
 * **Details**
 *
 * This function creates a schema whose `validate` method attempts to decode and
 * validate the provided input synchronously. If the underlying `Schema`
 * includes any asynchronous components (e.g., asynchronous message resolutions
 * or checks), then validation will necessarily return a `Promise` instead.
 *
 * **Example** (Creating a standard schema from a regular schema)
 *
 * ```ts
 * import { Schema } from "effect"
 *
 * // Define custom hook functions for error formatting
 * const leafHook = (issue: any) => {
 *   switch (issue._tag) {
 *     case "InvalidType":
 *       return "Expected different type"
 *     case "InvalidValue":
 *       return "Invalid value provided"
 *     case "MissingKey":
 *       return "Required property missing"
 *     case "UnexpectedKey":
 *       return "Unexpected property found"
 *     case "Forbidden":
 *       return "Operation not allowed"
 *     case "OneOf":
 *       return "Multiple valid options available"
 *     default:
 *       return "Validation error"
 *   }
 * }
 *
 * // Create a standard schema from a regular schema
 * const PersonSchema = Schema.Struct({
 *   name: Schema.NonEmptyString,
 *   age: Schema.Number.check(Schema.isBetween({ minimum: 0, maximum: 150 }))
 * })
 *
 * const standardSchema = Schema.toStandardSchemaV1(PersonSchema, {
 *   leafHook
 * })
 *
 * // The standard schema can be used with any Standard Schema v1 compatible library
 * const validResult = standardSchema["~standard"].validate({
 *   name: "Alice",
 *   age: 30
 * })
 * console.log(validResult) // { value: { name: "Alice", age: 30 } }
 *
 * const invalidResult = standardSchema["~standard"].validate({
 *   name: "",
 *   age: 200
 * })
 * console.log(invalidResult) // { issues: [{ path: ["name"], message: "..." }, { path: ["age"], message: "..." }] }
 * ```
 *
 * @category Standard Schema
 * @since 4.0.0
 */
export function toStandardSchemaV1<S extends ConstraintDecoder<unknown>>(
  self: S,
  options?: {
    readonly leafHook?: SchemaIssue.LeafHook | undefined
    readonly checkHook?: SchemaIssue.CheckHook | undefined
    readonly parseOptions?: SchemaAST.ParseOptions | undefined
  }
): StandardSchemaV1<S["Encoded"], S["Type"]> & S {
  const decodeUnknownEffect = SchemaParser.decodeUnknownEffect(self) as (
    input: unknown,
    options?: SchemaAST.ParseOptions
  ) => Effect.Effect<S["Type"], SchemaIssue.Issue>
  const parseOptions: SchemaAST.ParseOptions = { errors: "all", ...options?.parseOptions }
  const formatter = SchemaIssue.makeFormatterStandardSchemaV1(options)
  const validate: StandardSchemaV1<S["Encoded"], S["Type"]>["~standard"]["validate"] = (value: unknown) => {
    const scheduler = new Scheduler.MixedScheduler()
    const fiber = Effect.runFork(
      Effect.match(decodeUnknownEffect(value, parseOptions), {
        onFailure: formatter,
        onSuccess: (value): StandardSchemaV1.Result<S["Type"]> => ({ value })
      }),
      { scheduler }
    )
    fiber.currentDispatcher?.flush()
    const exit = fiber.pollUnsafe()
    if (exit) {
      return makeStandardResult(exit)
    }
    return new Promise((resolve) => {
      fiber.addObserver((exit) => {
        resolve(makeStandardResult(exit))
      })
    })
  }
  if ("~standard" in self) {
    const out = self as any
    if ("validate" in out["~standard"]) return out
    Object.assign(out["~standard"], { validate })
    return out
  } else {
    return Object.assign(self, {
      "~standard": {
        version: 1,
        vendor: "effect",
        validate
      } as const
    })
  }
}

function toBaseStandardJSONSchemaV1(self: Constraint, target: StandardJSONSchemaV1.Target): JsonSchema.JsonSchema {
  const doc2020_12 = toJsonSchemaDocument(self)
  if (target === "draft-2020-12") {
    const schema = doc2020_12.schema
    if (Object.keys(doc2020_12.definitions).length > 0) {
      schema.$defs = doc2020_12.definitions
    }
    return schema
  } else if (target === "draft-07") {
    const doc07 = JsonSchema.toDocumentDraft07(doc2020_12)
    const schema = doc07.schema
    if (Object.keys(doc07.definitions).length > 0) {
      schema.definitions = doc07.definitions
    }
    return schema
  }
  throw new globalThis.Error(`Unsupported target: ${target}`)
}

/**
 * Converts a schema to an experimental Standard JSON Schema V1 representation.
 *
 * **Details**
 *
 * https://github.com/standard-schema/standard-schema/pull/134
 *
 * @category Standard Schema
 * @since 4.0.0
 */
export function toStandardJSONSchemaV1<S extends Constraint>(
  self: S
): StandardJSONSchemaV1<S["Encoded"], S["Type"]> & S {
  const jsonSchema: StandardJSONSchemaV1.Props<S["Encoded"], S["Type"]>["jsonSchema"] = {
    input(options) {
      return toBaseStandardJSONSchemaV1(self, options.target)
    },
    output(options) {
      return toBaseStandardJSONSchemaV1(toType(self), options.target)
    }
  }
  if ("~standard" in self) {
    const out = self as any
    if ("jsonSchema" in out["~standard"]) return out
    Object.assign(out["~standard"], { jsonSchema })
    return out
  } else {
    return Object.assign(self, {
      "~standard": {
        version: 1,
        vendor: "effect",
        jsonSchema
      } as const
    })
  }
}

/**
 * Creates a type guard function that checks if a value conforms to a given
 * schema.
 *
 * **Details**
 *
 * This function returns a predicate that performs a type-safe check, narrowing
 * the type of the input value if the check passes. The predicate returns `false`
 * for schema mismatches.
 *
 * **Gotchas**
 *
 * Only causes made entirely of schema issues are converted to `false`. Causes
 * that contain defects, interruptions, or other non-schema reasons throw
 * instead.
 *
 * **Example** (Defining a basic type guard)
 *
 * ```ts
 * import { Schema } from "effect"
 *
 * const isString = Schema.is(Schema.String)
 *
 * console.log(isString("hello")) // true
 * console.log(isString(42)) // false
 *
 * // Type narrowing in action
 * const value: unknown = "hello"
 * if (isString(value)) {
 *   // value is now typed as string
 *   console.log(value.toUpperCase()) // "HELLO"
 * }
 * ```
 *
 * @category guards
 * @since 3.10.0
 */
export const is = SchemaParser.is

/**
 * Creates an assertion function that throws an error if the input does not match
 * the schema.
 *
 * **When to use**
 *
 * Use to validate unknown input at runtime while narrowing the value with a
 * TypeScript assertion signature.
 *
 * **Details**
 *
 * The input is narrowed if the assertion succeeds. If schema validation fails,
 * the assertion throws an `Error` whose cause is `SchemaIssue.Issue`.
 *
 * **Gotchas**
 *
 * Causes that contain defects, interruptions, or other non-schema reasons throw
 * with the underlying `Cause` attached instead of being converted to schema
 * validation errors.
 *
 * **Example** (Asserting and narrowing an input)
 *
 * ```ts
 * import { Schema } from "effect"
 *
 * const input: unknown = "hello"
 *
 * // This will pass silently (no return value) and narrow input to string
 * Schema.asserts(Schema.String, input)
 * console.log(input.toUpperCase())
 *
 * // This will throw an error
 * try {
 *   const invalid: unknown = 123
 *   Schema.asserts(Schema.String, invalid)
 * } catch (error) {
 *   console.log("Non-string assertion failed as expected")
 * }
 * ```
 *
 * @category guards
 * @since 4.0.0
 */
export const asserts: <S extends Constraint, I>(schema: S, input: I) => asserts input is I & S["Type"] =
  SchemaParser.asserts

/**
 * Decodes an `unknown` input against a schema, returning an `Effect` that
 * succeeds with the decoded value or fails with a {@link SchemaError}.
 *
 * **When to use**
 *
 * Use when you need to decode unknown input in an `Effect` whose failure
 * channel is `SchemaError`.
 *
 * **Details**
 *
 * Prefer {@link decodeEffect} when the input is already typed as the schema's
 * `Encoded` type.
 * Options may be provided either when creating the decoder or when applying it;
 * application options override creation options.
 *
 * @see {@link SchemaParser.decodeUnknownEffect} for the adapter that fails with `SchemaIssue.Issue` directly
 *
 * @category decoding
 * @since 4.0.0
 */
export function decodeUnknownEffect<S extends Constraint>(schema: S, options?: SchemaAST.ParseOptions) {
  const parser = SchemaParser.decodeUnknownEffect(schema, options)
  return (
    input: unknown,
    options?: SchemaAST.ParseOptions
  ): Effect.Effect<S["Type"], SchemaError, S["DecodingServices"]> => {
    return InternalSchema.fromIssueEffect(parser(input, options))
  }
}

/**
 * Decodes a typed input (the schema's `Encoded` type) against a schema,
 * returning an `Effect` that succeeds with the decoded value or fails with a
 * {@link SchemaError}.
 *
 * **When to use**
 *
 * Use when you need to decode input already typed as the schema's `Encoded`
 * type in an `Effect` whose failure channel is `SchemaError`.
 *
 * **Details**
 *
 * For `unknown` input use {@link decodeUnknownEffect}.
 * Options may be provided either when creating the decoder or when applying it;
 * application options override creation options.
 *
 * @see {@link SchemaParser.decodeEffect} for the adapter that fails with `SchemaIssue.Issue` directly
 *
 * @category decoding
 * @since 4.0.0
 */
export const decodeEffect: <S extends Constraint>(
  schema: S,
  options?: SchemaAST.ParseOptions
) => (
  input: S["Encoded"],
  options?: SchemaAST.ParseOptions
) => Effect.Effect<S["Type"], SchemaError, S["DecodingServices"]> = decodeUnknownEffect

function getSchemaErrorOrThrow(
  cause: Cause_.Cause<SchemaError>,
  message: string
): SchemaError {
  let schemaError: SchemaError | undefined
  for (const reason of cause.reasons) {
    if (!Cause_.isFailReason(reason) || !isSchemaError(reason.error)) {
      throw new globalThis.Error(message, { cause })
    }
    schemaError ??= reason.error
  }
  if (schemaError === undefined) {
    throw new globalThis.Error(message, { cause })
  }
  return schemaError
}

function runSchemaErrorPromise<A>(
  self: Effect.Effect<A, SchemaError>
): Promise<A> {
  return Effect.runPromiseExit(self).then((exit) => {
    if (Exit_.isSuccess(exit)) {
      return exit.value
    }
    throw getSchemaErrorOrThrow(exit.cause, "Promise adapter can only reject schema errors")
  })
}

function runSchemaErrorSync<A>(
  self: Effect.Effect<A, SchemaError>
): A {
  const exit = Effect.runSyncExit(self)
  if (Exit_.isSuccess(exit)) {
    return exit.value
  }
  throw getSchemaErrorOrThrow(exit.cause, "Sync adapter can only throw schema errors")
}

/**
 * Decodes an `unknown` input against a schema synchronously, returning an
 * `Exit` that is either a `Success` with the decoded value or a `Failure`.
 *
 * **When to use**
 *
 * Use when you need to decode unknown input into an `Exit` and capture schema
 * mismatches as `SchemaError`.
 *
 * **Details**
 *
 * Only usable with schemas that have no `DecodingServices` requirement. Prefer
 * {@link decodeExit} when the input is already typed as the schema's `Encoded`
 * type.
 * Options may be provided either when creating the decoder or when applying it;
 * application options override creation options.
 * Schema mismatches are represented by a `Failure` cause containing
 * `SchemaError`.
 *
 * **Gotchas**
 *
 * Schema issue fail reasons are wrapped as `SchemaError`. Defects,
 * interruptions, and other non-schema reasons remain in the returned `Cause`,
 * including when they are mixed with schema issues.
 *
 * @see {@link SchemaParser.decodeUnknownExit} for the adapter whose failure contains `SchemaIssue.Issue` directly
 *
 * @category decoding
 * @since 4.0.0
 */
export function decodeUnknownExit<S extends ConstraintDecoder<unknown>>(schema: S, options?: SchemaAST.ParseOptions) {
  const parser = SchemaParser.decodeUnknownExit(schema, options)
  return (input: unknown, options?: SchemaAST.ParseOptions): Exit_.Exit<S["Type"], SchemaError> => {
    return fromIssueExit(parser(input, options))
  }
}

function fromIssueExit<A>(exit: Exit_.Exit<A, SchemaIssue.Issue>): Exit_.Exit<A, SchemaError> {
  return Exit_.isSuccess(exit)
    ? Exit_.succeed(exit.value)
    : Exit_.failCause(Cause_.map(exit.cause, (issue) => new SchemaError(issue)))
}

/**
 * Decodes a typed input (the schema's `Encoded` type) against a schema
 * synchronously, returning an `Exit` that is either a `Success` with the decoded
 * value or a `Failure`.
 *
 * **When to use**
 *
 * Use when you need to decode already typed `Encoded` input into an `Exit` and
 * capture schema mismatches as `SchemaError`.
 *
 * **Details**
 *
 * Only usable with schemas that have no `DecodingServices` requirement. For
 * `unknown` input use {@link decodeUnknownExit}.
 * Options may be provided either when creating the decoder or when applying it;
 * application options override creation options.
 * Schema mismatches are represented by a `Failure` cause containing
 * `SchemaError`.
 *
 * **Gotchas**
 *
 * Schema issue fail reasons are wrapped as `SchemaError`. Defects,
 * interruptions, and other non-schema reasons remain in the returned `Cause`,
 * including when they are mixed with schema issues.
 *
 * @see {@link SchemaParser.decodeExit} for the adapter whose failure contains `SchemaIssue.Issue` directly
 *
 * @category decoding
 * @since 4.0.0
 */
export const decodeExit: <S extends ConstraintDecoder<unknown>>(
  schema: S,
  options?: SchemaAST.ParseOptions
) => (input: S["Encoded"], options?: SchemaAST.ParseOptions) => Exit_.Exit<S["Type"], SchemaError> = decodeUnknownExit

/**
 * Decodes an `unknown` input against a schema, returning an `Option` that is
 * `Some` with the decoded value on success or `None` for schema mismatches.
 *
 * **When to use**
 *
 * Use when you do not know the input type statically and only need to know
 * whether decoding succeeded.
 *
 * **Details**
 *
 * Prefer this over {@link decodeUnknownExit} or {@link decodeUnknownEffect}
 * when you don't need error details. For input already typed as the schema's
 * `Encoded` type use {@link decodeOption}.
 * Options may be provided either when creating the decoder or when applying it;
 * application options override creation options.
 *
 * **Gotchas**
 *
 * Only causes made entirely of schema issues are converted to `None`. Causes
 * that contain defects, interruptions, or other non-schema reasons throw
 * instead.
 *
 * @category decoding
 * @since 3.10.0
 */
export const decodeUnknownOption: <S extends ConstraintDecoder<unknown>>(
  schema: S,
  options?: SchemaAST.ParseOptions
) => (input: unknown, options?: SchemaAST.ParseOptions) => Option_.Option<S["Type"]> = SchemaParser.decodeUnknownOption

/**
 * Decodes a typed input (the schema's `Encoded` type) against a schema,
 * returning an `Option` that is `Some` with the decoded value on success or
 * `None` for schema mismatches.
 *
 * **When to use**
 *
 * Use when you already have input typed as the schema's `Encoded` type and
 * only need to know whether decoding succeeded.
 *
 * **Details**
 *
 * For `unknown` input use {@link decodeUnknownOption}.
 * Options may be provided either when creating the decoder or when applying it;
 * application options override creation options.
 *
 * **Gotchas**
 *
 * Only causes made entirely of schema issues are converted to `None`. Causes
 * that contain defects, interruptions, or other non-schema reasons throw
 * instead.
 *
 * @category decoding
 * @since 3.10.0
 */
export const decodeOption: <S extends ConstraintDecoder<unknown>>(
  schema: S,
  options?: SchemaAST.ParseOptions
) => (input: S["Encoded"], options?: SchemaAST.ParseOptions) => Option_.Option<S["Type"]> = SchemaParser.decodeOption

/**
 * Decodes an `unknown` input against a schema, returning a `Result` that
 * succeeds with the decoded value or fails with a {@link SchemaError} for schema
 * mismatches.
 *
 * **When to use**
 *
 * Use when you do not know the input type statically and want schema mismatches
 * returned as `Result.fail` with `SchemaError`.
 *
 * **Details**
 *
 * For input already typed as the schema's `Encoded` type use
 * {@link decodeResult}.
 * Options may be provided either when creating the decoder or when applying it;
 * application options override creation options.
 * Schema mismatches are returned as `Result.fail` with `SchemaError`.
 *
 * **Gotchas**
 *
 * Only causes made entirely of schema issues are returned as `Result.fail`.
 * Causes that contain defects, interruptions, or other non-schema reasons throw
 * instead.
 *
 * @see {@link SchemaParser.decodeUnknownResult} for the adapter that fails with `SchemaIssue.Issue` directly
 *
 * @category decoding
 * @since 4.0.0
 */
export function decodeUnknownResult<S extends ConstraintDecoder<unknown>>(schema: S, options?: SchemaAST.ParseOptions) {
  const parser = SchemaParser.decodeUnknownResult(schema, options)
  return (input: unknown, options?: SchemaAST.ParseOptions): Result_.Result<S["Type"], SchemaError> => {
    return Result_.mapError(parser(input, options), (issue) => new SchemaError(issue))
  }
}

/**
 * Decodes a typed input (the schema's `Encoded` type) against a schema,
 * returning a `Result` that succeeds with the decoded value or fails with a
 * {@link SchemaError} for schema mismatches.
 *
 * **When to use**
 *
 * Use when you already have input typed as the schema's `Encoded` type and want
 * schema mismatches returned as `Result.fail` with `SchemaError`.
 *
 * **Details**
 *
 * For `unknown` input use {@link decodeUnknownResult}.
 * Options may be provided either when creating the decoder or when applying it;
 * application options override creation options.
 * Schema mismatches are returned as `Result.fail` with `SchemaError`.
 *
 * **Gotchas**
 *
 * Only causes made entirely of schema issues are returned as `Result.fail`.
 * Causes that contain defects, interruptions, or other non-schema reasons throw
 * instead.
 *
 * @see {@link SchemaParser.decodeResult} for the adapter that fails with `SchemaIssue.Issue` directly
 *
 * @category decoding
 * @since 4.0.0
 */
export const decodeResult: <S extends ConstraintDecoder<unknown>>(
  schema: S,
  options?: SchemaAST.ParseOptions
) => (input: S["Encoded"], options?: SchemaAST.ParseOptions) => Result_.Result<S["Type"], SchemaError> =
  decodeUnknownResult

/**
 * Decodes an `unknown` input against a schema, returning a `Promise` that
 * resolves with the decoded value or rejects with a {@link SchemaError} for
 * schema mismatches.
 *
 * **When to use**
 *
 * Use when you need decoding of unknown input to return a JavaScript `Promise`
 * that rejects with `SchemaError` for schema mismatches.
 *
 * **Details**
 *
 * For input already typed as the schema's `Encoded` type use
 * {@link decodePromise}.
 * Options may be provided either when creating the decoder or when applying it;
 * application options override creation options.
 *
 * **Gotchas**
 *
 * Non-schema failures may reject with a runtime failure instead of
 * `SchemaError`.
 *
 * @see {@link SchemaParser.decodeUnknownPromise} for the adapter that rejects with an `Error` whose cause is `SchemaIssue.Issue`
 *
 * @category decoding
 * @since 3.10.0
 */
export function decodeUnknownPromise<S extends ConstraintDecoder<unknown>>(
  schema: S,
  options?: SchemaAST.ParseOptions
) {
  const parser = decodeUnknownEffect(schema, options)
  return (input: unknown, options?: SchemaAST.ParseOptions): Promise<S["Type"]> => {
    return runSchemaErrorPromise(parser(input, options))
  }
}

/**
 * Decodes a typed input (the schema's `Encoded` type) against a schema,
 * returning a `Promise` that resolves with the decoded value or rejects with a
 * {@link SchemaError} for schema mismatches.
 *
 * **When to use**
 *
 * Use when you already have input typed as the schema's `Encoded` type and
 * need decoding to return a JavaScript `Promise` that rejects with
 * `SchemaError` for schema mismatches.
 *
 * **Details**
 *
 * For `unknown` input use `decodeUnknownPromise`.
 * Options may be provided either when creating the decoder or when applying it;
 * application options override creation options.
 *
 * **Gotchas**
 *
 * Non-schema failures may reject with a runtime failure instead of
 * `SchemaError`.
 *
 * @see {@link SchemaParser.decodePromise} for the adapter that rejects with an `Error` whose cause is `SchemaIssue.Issue`
 *
 * @category decoding
 * @since 3.10.0
 */
export const decodePromise: <S extends ConstraintDecoder<unknown>>(
  schema: S,
  options?: SchemaAST.ParseOptions
) => (input: S["Encoded"], options?: SchemaAST.ParseOptions) => Promise<S["Type"]> = decodeUnknownPromise

/**
 * Decodes an `unknown` input against a schema synchronously, returning the
 * decoded value or throwing a {@link SchemaError} for schema mismatches.
 *
 * **When to use**
 *
 * Use when you need to validate unknown data at a synchronous boundary and want
 * schema mismatches to throw `SchemaError`.
 *
 * **Details**
 *
 * For input already typed as the schema's `Encoded` type use `decodeSync`.
 * Only service-free schemas can be decoded synchronously. For alternatives that
 * do not throw on schema mismatches, see `decodeUnknownOption`,
 * `decodeUnknownExit`, or `decodeUnknownEffect`. Options may be provided either
 * when creating the decoder or when applying it; application options override
 * creation options.
 *
 * **Gotchas**
 *
 * Non-schema failures may throw a runtime failure instead of `SchemaError`.
 *
 * **Example** (Decoding with a transformation schema)
 *
 * ```ts
 * import { Schema } from "effect"
 *
 * const NumberFromString = Schema.NumberFromString
 *
 * console.log(Schema.decodeUnknownSync(NumberFromString)("42"))
 * // Output: 42
 *
 * Schema.decodeUnknownSync(NumberFromString)("not a number")
 * // throws SchemaError: NumberFromString
 * //   └─ Encoded side transformation failure
 * //      └─ NumberFromString
 * //         └─ Expected a numeric string, actual "not a number"
 * ```
 *
 * @see {@link SchemaParser.decodeUnknownSync} for the adapter that throws an `Error` whose cause is `SchemaIssue.Issue`
 *
 * @category decoding
 * @since 4.0.0
 */
export function decodeUnknownSync<S extends ConstraintDecoder<unknown>>(schema: S, options?: SchemaAST.ParseOptions) {
  const parser = decodeUnknownEffect(schema, options)
  return (input: unknown, options?: SchemaAST.ParseOptions): S["Type"] => {
    return runSchemaErrorSync(parser(input, options))
  }
}

/**
 * Decodes a typed input (the schema's `Encoded` type) against a schema
 * synchronously, returning the decoded value or throwing a {@link SchemaError}
 * for schema mismatches.
 *
 * **When to use**
 *
 * Use when you already have input typed as the schema's `Encoded` type and
 * want schema mismatches to throw `SchemaError` synchronously.
 *
 * **Details**
 *
 * For `unknown` input use `decodeUnknownSync`.
 * Only service-free schemas can be decoded synchronously. Options may be
 * provided either when creating the decoder or when applying it; application
 * options override creation options.
 *
 * **Gotchas**
 *
 * Non-schema failures may throw a runtime failure instead of `SchemaError`.
 *
 * @see {@link SchemaParser.decodeSync} for the adapter that throws an `Error` whose cause is `SchemaIssue.Issue`
 *
 * @category decoding
 * @since 4.0.0
 */
export const decodeSync: <S extends ConstraintDecoder<unknown>>(
  schema: S,
  options?: SchemaAST.ParseOptions
) => (input: S["Encoded"], options?: SchemaAST.ParseOptions) => S["Type"] = decodeUnknownSync

/**
 * Encodes an `unknown` input against a schema, returning an `Effect` that
 * succeeds with the encoded value or fails with a {@link SchemaError}.
 *
 * **When to use**
 *
 * Use when you need to encode unknown input in an `Effect` whose failure
 * channel is `SchemaError`.
 *
 * **Details**
 *
 * Prefer {@link encodeEffect} when the value is already typed as the schema's
 * `Type`.
 * Options may be provided either when creating the encoder or when applying it;
 * application options override creation options.
 *
 * **Example** (Encoding a value to a string)
 *
 * ```ts
 * import { Effect, Schema } from "effect"
 *
 * const NumberFromString = Schema.NumberFromString
 *
 * Effect.runPromise(Schema.encodeUnknownEffect(NumberFromString)(42)).then(console.log)
 * // Output: "42"
 * ```
 *
 * @see {@link SchemaParser.encodeUnknownEffect} for the adapter that fails with `SchemaIssue.Issue` directly
 *
 * @category encoding
 * @since 4.0.0
 */
export function encodeUnknownEffect<S extends Constraint>(schema: S, options?: SchemaAST.ParseOptions) {
  const parser = SchemaParser.encodeUnknownEffect(schema, options)
  return (
    input: unknown,
    options?: SchemaAST.ParseOptions
  ): Effect.Effect<S["Encoded"], SchemaError, S["EncodingServices"]> => {
    return InternalSchema.fromIssueEffect(parser(input, options))
  }
}

/**
 * Encodes a typed input (the schema's `Type`) against a schema, returning an
 * `Effect` that succeeds with the encoded value or fails with a
 * {@link SchemaError}.
 *
 * **When to use**
 *
 * Use when you need to encode input already typed as the schema's `Type` in
 * an `Effect` whose failure channel is `SchemaError`.
 *
 * **Details**
 *
 * For `unknown` input use {@link encodeUnknownEffect}.
 * Options may be provided either when creating the encoder or when applying it;
 * application options override creation options.
 *
 * @see {@link SchemaParser.encodeEffect} for the adapter that fails with `SchemaIssue.Issue` directly
 *
 * @category encoding
 * @since 4.0.0
 */
export const encodeEffect: <S extends Constraint>(
  schema: S,
  options?: SchemaAST.ParseOptions
) => (
  input: S["Type"],
  options?: SchemaAST.ParseOptions
) => Effect.Effect<S["Encoded"], SchemaError, S["EncodingServices"]> = encodeUnknownEffect

/**
 * Encodes an `unknown` input against a schema synchronously, returning an
 * `Exit` that is either a `Success` with the encoded value or a `Failure`.
 *
 * **When to use**
 *
 * Use when you need to encode unknown input into an `Exit` and capture schema
 * mismatches as `SchemaError`.
 *
 * **Details**
 *
 * Only usable with schemas that have no `EncodingServices` requirement. Prefer
 * {@link encodeExit} when the value is already typed as the schema's `Type`.
 * Options may be provided either when creating the encoder or when applying it;
 * application options override creation options.
 * Schema mismatches are represented by a `Failure` cause containing
 * `SchemaError`.
 *
 * **Gotchas**
 *
 * Schema issue fail reasons are wrapped as `SchemaError`. Defects,
 * interruptions, and other non-schema reasons remain in the returned `Cause`,
 * including when they are mixed with schema issues.
 *
 * @see {@link SchemaParser.encodeUnknownExit} for the adapter whose failure contains `SchemaIssue.Issue` directly
 *
 * @category encoding
 * @since 4.0.0
 */
export function encodeUnknownExit<S extends ConstraintEncoder<unknown>>(schema: S, options?: SchemaAST.ParseOptions) {
  const parser = SchemaParser.encodeUnknownExit(schema, options)
  return (input: unknown, options?: SchemaAST.ParseOptions): Exit_.Exit<S["Encoded"], SchemaError> => {
    return fromIssueExit(parser(input, options))
  }
}

/**
 * Encodes a typed input (the schema's `Type`) against a schema synchronously,
 * returning an `Exit` that is either a `Success` with the encoded value or a
 * `Failure`.
 *
 * **When to use**
 *
 * Use when you need to encode already typed schema values into an `Exit` and
 * capture schema mismatches as `SchemaError`.
 *
 * **Details**
 *
 * Only usable with schemas that have no `EncodingServices` requirement. For
 * `unknown` input use {@link encodeUnknownExit}.
 * Options may be provided either when creating the encoder or when applying it;
 * application options override creation options.
 * Schema mismatches are represented by a `Failure` cause containing
 * `SchemaError`.
 *
 * **Gotchas**
 *
 * Schema issue fail reasons are wrapped as `SchemaError`. Defects,
 * interruptions, and other non-schema reasons remain in the returned `Cause`,
 * including when they are mixed with schema issues.
 *
 * @see {@link SchemaParser.encodeExit} for the adapter whose failure contains `SchemaIssue.Issue` directly
 *
 * @category encoding
 * @since 4.0.0
 */
export const encodeExit: <S extends ConstraintEncoder<unknown>>(
  schema: S,
  options?: SchemaAST.ParseOptions
) => (input: S["Type"], options?: SchemaAST.ParseOptions) => Exit_.Exit<S["Encoded"], SchemaError> = encodeUnknownExit

/**
 * Encodes an `unknown` input against a schema, returning an `Option` that is
 * `Some` with the encoded value on success or `None` for schema mismatches.
 *
 * **When to use**
 *
 * Use when you do not know the input type statically and only need to know
 * whether encoding succeeded.
 *
 * **Details**
 *
 * Prefer this over {@link encodeUnknownExit} or {@link encodeUnknownEffect}
 * when you don't need error details. For values already typed as the schema's
 * `Type` use {@link encodeOption}.
 * Options may be provided either when creating the encoder or when applying it;
 * application options override creation options.
 *
 * **Gotchas**
 *
 * Only causes made entirely of schema issues are converted to `None`. Causes
 * that contain defects, interruptions, or other non-schema reasons throw
 * instead.
 *
 * @category encoding
 * @since 3.10.0
 */
export const encodeUnknownOption: <S extends ConstraintEncoder<unknown>>(
  schema: S,
  options?: SchemaAST.ParseOptions
) => (input: unknown, options?: SchemaAST.ParseOptions) => Option_.Option<S["Encoded"]> =
  SchemaParser.encodeUnknownOption

/**
 * Encodes a typed input (the schema's `Type`) against a schema, returning an
 * `Option` that is `Some` with the encoded value on success or `None` for schema
 * mismatches.
 *
 * **When to use**
 *
 * Use when you already have a value typed as the schema's `Type` and only need
 * to know whether encoding succeeded.
 *
 * **Details**
 *
 * For `unknown` input use {@link encodeUnknownOption}.
 * Options may be provided either when creating the encoder or when applying it;
 * application options override creation options.
 *
 * **Gotchas**
 *
 * Only causes made entirely of schema issues are converted to `None`. Causes
 * that contain defects, interruptions, or other non-schema reasons throw
 * instead.
 *
 * @category encoding
 * @since 3.10.0
 */
export const encodeOption: <S extends ConstraintEncoder<unknown>>(
  schema: S,
  options?: SchemaAST.ParseOptions
) => (input: S["Type"], options?: SchemaAST.ParseOptions) => Option_.Option<S["Encoded"]> = SchemaParser.encodeOption

/**
 * Encodes an `unknown` input against a schema, returning a `Result` that
 * succeeds with the encoded value or fails with a {@link SchemaError} for schema
 * mismatches.
 *
 * **When to use**
 *
 * Use when you do not know the input type statically and want schema mismatches
 * returned as `Result.fail` with `SchemaError`.
 *
 * **Details**
 *
 * For values already typed as the schema's `Type` use {@link encodeResult}.
 * Options may be provided either when creating the encoder or when applying it;
 * application options override creation options.
 * Schema mismatches are returned as `Result.fail` with `SchemaError`.
 *
 * **Gotchas**
 *
 * Only causes made entirely of schema issues are returned as `Result.fail`.
 * Causes that contain defects, interruptions, or other non-schema reasons throw
 * instead.
 *
 * @see {@link SchemaParser.encodeUnknownResult} for the adapter that fails with `SchemaIssue.Issue` directly
 *
 * @category encoding
 * @since 4.0.0
 */
export function encodeUnknownResult<S extends ConstraintEncoder<unknown>>(schema: S, options?: SchemaAST.ParseOptions) {
  const parser = SchemaParser.encodeUnknownResult(schema, options)
  return (input: unknown, options?: SchemaAST.ParseOptions): Result_.Result<S["Encoded"], SchemaError> => {
    return Result_.mapError(parser(input, options), (issue) => new SchemaError(issue))
  }
}

/**
 * Encodes a typed input (the schema's `Type`) against a schema, returning a
 * `Result` that succeeds with the encoded value or fails with a
 * {@link SchemaError} for schema mismatches.
 *
 * **When to use**
 *
 * Use when you already have a value typed as the schema's `Type` and want schema
 * mismatches returned as `Result.fail` with `SchemaError`.
 *
 * **Details**
 *
 * For `unknown` input use {@link encodeUnknownResult}.
 * Options may be provided either when creating the encoder or when applying it;
 * application options override creation options.
 * Schema mismatches are returned as `Result.fail` with `SchemaError`.
 *
 * **Gotchas**
 *
 * Only causes made entirely of schema issues are returned as `Result.fail`.
 * Causes that contain defects, interruptions, or other non-schema reasons throw
 * instead.
 *
 * @see {@link SchemaParser.encodeResult} for the adapter that fails with `SchemaIssue.Issue` directly
 *
 * @category encoding
 * @since 4.0.0
 */
export const encodeResult: <S extends ConstraintEncoder<unknown>>(
  schema: S,
  options?: SchemaAST.ParseOptions
) => (input: S["Type"], options?: SchemaAST.ParseOptions) => Result_.Result<S["Encoded"], SchemaError> =
  encodeUnknownResult

/**
 * Encodes an `unknown` input against a schema, returning a `Promise` that
 * resolves with the encoded value or rejects with a {@link SchemaError} for
 * schema mismatches.
 *
 * **When to use**
 *
 * Use when you need encoding of unknown input to return a JavaScript `Promise`
 * that rejects with `SchemaError` for schema mismatches.
 *
 * **Details**
 *
 * For values already typed as the schema's `Type` use {@link encodePromise}.
 * Options may be provided either when creating the encoder or when applying it;
 * application options override creation options.
 *
 * **Gotchas**
 *
 * Non-schema failures may reject with a runtime failure instead of
 * `SchemaError`.
 *
 * @see {@link SchemaParser.encodeUnknownPromise} for the adapter that rejects with an `Error` whose cause is `SchemaIssue.Issue`
 *
 * @category encoding
 * @since 3.10.0
 */
export function encodeUnknownPromise<S extends ConstraintEncoder<unknown>>(
  schema: S,
  options?: SchemaAST.ParseOptions
) {
  const parser = encodeUnknownEffect(schema, options)
  return (input: unknown, options?: SchemaAST.ParseOptions): Promise<S["Encoded"]> => {
    return runSchemaErrorPromise(parser(input, options))
  }
}

/**
 * Encodes a typed input (the schema's `Type`) against a schema, returning a
 * `Promise` that resolves with the encoded value or rejects with a
 * {@link SchemaError} for schema mismatches.
 *
 * **When to use**
 *
 * Use when you already have a value typed as the schema's `Type` and need
 * encoding to return a JavaScript `Promise` that rejects with `SchemaError` for
 * schema mismatches.
 *
 * **Details**
 *
 * For `unknown` input use {@link encodeUnknownPromise}.
 * Options may be provided either when creating the encoder or when applying it;
 * application options override creation options.
 *
 * **Gotchas**
 *
 * Non-schema failures may reject with a runtime failure instead of
 * `SchemaError`.
 *
 * @see {@link SchemaParser.encodePromise} for the adapter that rejects with an `Error` whose cause is `SchemaIssue.Issue`
 *
 * @category encoding
 * @since 3.10.0
 */
export const encodePromise: <S extends ConstraintEncoder<unknown>>(
  schema: S,
  options?: SchemaAST.ParseOptions
) => (input: S["Type"], options?: SchemaAST.ParseOptions) => Promise<S["Encoded"]> = encodeUnknownPromise

/**
 * Encodes an `unknown` input against a schema synchronously, throwing a
 * {@link SchemaError} for schema mismatches.
 *
 * **When to use**
 *
 * Use when you need to serialize unknown data at a synchronous boundary and
 * want schema mismatches to throw `SchemaError`.
 *
 * **Details**
 *
 * For alternatives that do not throw on schema mismatches, see
 * {@link encodeUnknownOption}, {@link encodeUnknownExit}, or
 * {@link encodeUnknownEffect}. For values already typed as the schema's `Type`
 * use {@link encodeSync}. Options may be provided either when creating the
 * encoder or when applying it; application options override creation options.
 *
 * **Gotchas**
 *
 * Non-schema failures may throw a runtime failure instead of `SchemaError`.
 *
 * @see {@link SchemaParser.encodeUnknownSync} for the adapter that throws an `Error` whose cause is `SchemaIssue.Issue`
 *
 * @category encoding
 * @since 4.0.0
 */
export function encodeUnknownSync<S extends ConstraintEncoder<unknown>>(schema: S, options?: SchemaAST.ParseOptions) {
  const parser = encodeUnknownEffect(schema, options)
  return (input: unknown, options?: SchemaAST.ParseOptions): S["Encoded"] => {
    return runSchemaErrorSync(parser(input, options) as Effect.Effect<S["Encoded"], SchemaError>)
  }
}

/**
 * Encodes a typed input (the schema's `Type`) against a schema synchronously,
 * throwing a {@link SchemaError} for schema mismatches.
 *
 * **When to use**
 *
 * Use when you already have a value typed as the schema's `Type` and want
 * schema mismatches to throw `SchemaError` synchronously.
 *
 * **Details**
 *
 * For `unknown` input use {@link encodeUnknownSync}.
 * Options may be provided either when creating the encoder or when applying it;
 * application options override creation options.
 *
 * **Gotchas**
 *
 * Non-schema failures may throw a runtime failure instead of `SchemaError`.
 *
 * @see {@link SchemaParser.encodeSync} for the adapter that throws an `Error` whose cause is `SchemaIssue.Issue`
 *
 * @category encoding
 * @since 4.0.0
 */
export const encodeSync: <S extends ConstraintEncoder<unknown>>(
  schema: S,
  options?: SchemaAST.ParseOptions
) => (input: S["Type"], options?: SchemaAST.ParseOptions) => S["Encoded"] = encodeUnknownSync

/**
 * Creates a schema from an AST (Abstract Syntax Tree) node.
 *
 * **Details**
 *
 * This is the fundamental constructor for all schemas in the Effect Schema
 * library. It takes an AST node and wraps it in a fully-typed schema that
 * preserves all type information and provides the complete schema API.
 *
 * The `make` function is used internally to create all primitive schemas like
 * `String`, `Number`, `Boolean`, etc., as well as more complex schemas. It's
 * the bridge between the untyped AST representation and the strongly-typed
 * schema.
 *
 * @category constructors
 * @since 3.10.0
 */
export const make: <S extends Constraint>(ast: S["ast"], options?: object) => S = InternalSchema.make

/**
 * Transforms a schema into a class that can be extended with `extends`. The
 * resulting class inherits the full schema API (e.g. `annotate`) and can define
 * static methods that reference `this`.
 *
 * **Example** (Wrapping a primitive schema)
 *
 * ```ts
 * import { Schema } from "effect"
 *
 * class MyString extends Schema.asClass(Schema.String) {
 *   static readonly decodeUnknownSync = Schema.decodeUnknownSync(this)
 * }
 *
 * console.log(MyString.decodeUnknownSync("a"))
 * // "a"
 * ```
 *
 * @category constructors
 * @since 4.0.0
 */
export function asClass<S extends Top>(schema: S): S & { new(_: never): {} } {
  // oxlint-disable-next-line @typescript-eslint/no-extraneous-class
  class Class {}
  return Object.setPrototypeOf(Class, schema)
}

/**
 * Checks whether a value is a `Schema`.
 *
 * @category guards
 * @since 3.10.0
 */
export function isSchema(u: unknown): u is Top {
  return Predicate.hasProperty(u, TypeId) && u[TypeId] === TypeId
}

/**
 * Type-level representation returned by {@link optionalKey}.
 *
 * @category models
 * @since 4.0.0
 */
export interface optionalKey<S extends Constraint> extends
  BottomLazy<
    S["ast"],
    optionalKey<S>,
    S["~type.parameters"],
    S["~type.mutability"],
    "optional",
    S["~type.constructor.default"],
    S["~encoded.mutability"],
    "optional"
  >
{
  readonly "Type": S["Type"]
  readonly "Encoded": S["Encoded"]
  readonly "DecodingServices": S["DecodingServices"]
  readonly "EncodingServices": S["EncodingServices"]
  readonly "~type.make.in": S["~type.make.in"]
  readonly "~type.make": S["~type.make"]
  readonly "Iso": S["Iso"]
  readonly schema: S
}

interface optionalKeyLambda extends Lambda {
  <S extends Constraint>(self: S): optionalKey<S>
  readonly "~lambda.out": this["~lambda.in"] extends Constraint ? optionalKey<this["~lambda.in"]> : never
}

/**
 * Creates an exact optional key schema for struct fields. Unlike `optional`,
 * this creates exact optional properties (not `| undefined`) that can be
 * completely omitted from the object.
 *
 * **Example** (Creating a struct with optional key)
 *
 * ```ts
 * import { Schema } from "effect"
 *
 * const schema = Schema.Struct({
 *   name: Schema.String,
 *   age: Schema.optionalKey(Schema.Number)
 * })
 *
 * // Type: { readonly name: string; readonly age?: number }
 * type Person = typeof schema["Type"]
 * ```
 *
 * @category combinators
 * @since 4.0.0
 */
export const optionalKey = Struct_.lambda<optionalKeyLambda>((schema) =>
  make(SchemaAST.optionalKey(schema.ast), { schema })
)

interface requiredKeyLambda extends Lambda {
  <S extends Constraint>(self: optionalKey<S>): S
  readonly "~lambda.out": this["~lambda.in"] extends optionalKey<Constraint> ? this["~lambda.in"]["schema"]
    : "Error: schema not eligible for requiredKey"
}

/**
 * Reverses `optionalKey` and returns the inner required schema.
 *
 * **When to use**
 *
 * Use to remove optional-key wrapping from a schema field that was previously
 * wrapped with {@link optionalKey}.
 *
 * @category combinators
 * @since 4.0.0
 */
export const requiredKey = Struct_.lambda<requiredKeyLambda>((self) => self.schema)

/**
 * Type-level representation returned by {@link optional}.
 *
 * @category models
 * @since 3.10.0
 */
export interface optional<S extends Constraint> extends optionalKey<UndefinedOr<S>> {
  readonly "Rebuild": optional<S>
}

interface optionalLambda extends Lambda {
  <S extends Constraint>(self: S): optional<S>
  readonly "~lambda.out": this["~lambda.in"] extends Constraint ? optional<this["~lambda.in"]> : never
}

/**
 * Marks a struct field as optional, allowing the key to be absent or
 * `undefined`.
 *
 * **Details**
 *
 * The resulting property may be absent or explicitly set to `undefined`.
 * Equivalent to `optionalKey(UndefinedOr(S))`.
 *
 * Use {@link optionalKey} instead if you want exact optional semantics (absent
 * only, not `undefined`).
 *
 * **Example** (Defining an optional field accepting undefined)
 *
 * ```ts
 * import { Schema } from "effect"
 *
 * const schema = Schema.Struct({
 *   name: Schema.String,
 *   age: Schema.optional(Schema.Number)
 * })
 *
 * // { readonly name: string; readonly age?: number | undefined }
 * type Person = typeof schema.Type
 * ```
 *
 * @category combinators
 * @since 3.10.0
 */
export const optional = Struct_.lambda<optionalLambda>((self) => optionalKey(UndefinedOr(self)))

interface requiredLambda extends Lambda {
  <S extends Constraint>(self: optional<S>): S
  readonly "~lambda.out": this["~lambda.in"] extends optional<Constraint> ? this["~lambda.in"]["schema"]["members"][0]
    : "Error: schema not eligible for required"
}

/**
 * Reverses `optional` and returns the inner schema.
 *
 * **When to use**
 *
 * Use to remove optional wrapping from a schema field that was previously
 * wrapped with {@link optional}.
 *
 * **Details**
 *
 * This also unwraps the `UndefinedOr` member added by `optional`.
 *
 * @category combinators
 * @since 3.10.0
 */
export const required = Struct_.lambda<requiredLambda>((self) => self.schema.members[0])

/**
 * Type-level representation returned by {@link mutableKey}.
 *
 * @category models
 * @since 4.0.0
 */
export interface mutableKey<S extends Constraint> extends
  BottomLazy<
    S["ast"],
    mutableKey<S>,
    S["~type.parameters"],
    "mutable",
    S["~type.optionality"],
    S["~type.constructor.default"],
    "mutable",
    S["~encoded.optionality"]
  >
{
  readonly "Type": S["Type"]
  readonly "Encoded": S["Encoded"]
  readonly "DecodingServices": S["DecodingServices"]
  readonly "EncodingServices": S["EncodingServices"]
  readonly "~type.make.in": S["~type.make.in"]
  readonly "~type.make": S["~type.make"]
  readonly "Iso": S["Iso"]
  readonly schema: S
}

interface mutableKeyLambda extends Lambda {
  <S extends Constraint>(self: S): mutableKey<S>
  readonly "~lambda.out": this["~lambda.in"] extends Constraint ? mutableKey<this["~lambda.in"]> : never
}

/**
 * Makes a struct field mutable (removes the `readonly` modifier on the property).
 * Use {@link readonlyKey} to reverse.
 *
 * @category combinators
 * @since 4.0.0
 */
export const mutableKey = Struct_.lambda<mutableKeyLambda>((schema) =>
  make(SchemaAST.mutableKey(schema.ast), { schema })
)

interface readonlyKeyLambda extends Lambda {
  <S extends Constraint>(self: mutableKey<S>): S
  readonly "~lambda.out": this["~lambda.in"] extends mutableKey<Constraint> ? this["~lambda.in"]["schema"]
    : "Error: schema not eligible for readonlyKey"
}

/**
 * Reverses `mutableKey` and returns the inner readonly schema.
 *
 * **When to use**
 *
 * Use to remove mutable-key wrapping from a schema field that was previously
 * wrapped with {@link mutableKey}.
 *
 * @category combinators
 * @since 4.0.0
 */
export const readonlyKey = Struct_.lambda<readonlyKeyLambda>((self) => self.schema)

/**
 * Type-level representation returned by {@link toType}.
 *
 * @category transforming
 * @since 4.0.0
 */
export interface toType<S extends Constraint> extends
  BottomLazy<
    S["ast"],
    toType<S>,
    S["~type.parameters"],
    S["~type.mutability"],
    S["~type.optionality"],
    S["~type.constructor.default"],
    S["~encoded.mutability"],
    S["~encoded.optionality"]
  >
{
  readonly "Type": S["Type"]
  readonly "Encoded": S["Type"]
  readonly "DecodingServices": never
  readonly "EncodingServices": never
  readonly "~type.make.in": S["~type.make.in"]
  readonly "~type.make": S["~type.make"]
  readonly "Iso": S["Iso"]
  readonly schema: S
}

interface toTypeLambda extends Lambda {
  <S extends Constraint>(self: S): toType<S>
  readonly "~lambda.out": this["~lambda.in"] extends Constraint ? toType<this["~lambda.in"]> : never
}

/**
 * Extracts the type-side schema: sets `Encoded` to equal the decoded `Type`,
 * discarding the encoding transformation path.
 *
 * @category transforming
 * @since 4.0.0
 */
export const toType = Struct_.lambda<toTypeLambda>((schema) => make(SchemaAST.toType(schema.ast), { schema }))

/**
 * Type-level representation returned by {@link toEncoded}.
 *
 * @category transforming
 * @since 4.0.0
 */
export interface toEncoded<S extends Constraint> extends
  BottomLazy<
    SchemaAST.AST,
    toEncoded<S>,
    ReadonlyArray<Constraint>,
    S["~type.mutability"],
    S["~type.optionality"],
    S["~type.constructor.default"],
    S["~encoded.mutability"],
    S["~encoded.optionality"]
  >
{
  readonly "Type": S["Encoded"]
  readonly "Encoded": S["Encoded"]
  readonly "DecodingServices": never
  readonly "EncodingServices": never
  readonly "~type.make.in": S["Encoded"]
  readonly "~type.make": S["Encoded"]
  readonly "Iso": S["Encoded"]
  readonly schema: S
}

interface toEncodedLambda extends Lambda {
  <S extends Constraint>(self: S): toEncoded<S>
  readonly "~lambda.out": this["~lambda.in"] extends Constraint ? toEncoded<this["~lambda.in"]> : never
}

/**
 * Extracts the encoded-side schema: sets `Type` to equal the `Encoded`,
 * discarding the decoding transformation path.
 *
 * @category transforming
 * @since 4.0.0
 */
export const toEncoded = Struct_.lambda<toEncodedLambda>((schema) => make(SchemaAST.toEncoded(schema.ast), { schema }))

const FlipTypeId = "~effect/Schema/flip"

/**
 * Type-level representation returned by {@link flip}.
 *
 * @category transforming
 * @since 4.0.0
 */
export interface flip<S extends Top> extends
  BottomLazy<
    SchemaAST.AST,
    flip<S>,
    ReadonlyArray<Constraint>,
    S["~encoded.mutability"],
    S["~encoded.optionality"],
    ConstructorDefault,
    S["~type.mutability"],
    S["~type.optionality"]
  >
{
  readonly "Type": S["Encoded"]
  readonly "Encoded": S["Type"]
  readonly "DecodingServices": S["EncodingServices"]
  readonly "EncodingServices": S["DecodingServices"]
  readonly "~type.make.in": S["Encoded"]
  readonly "~type.make": S["Encoded"]
  readonly "Iso": S["Encoded"]
  readonly [FlipTypeId]: typeof FlipTypeId
  readonly schema: S
}

function isFlip$(schema: Top): schema is flip<any> {
  return Predicate.hasProperty(schema, FlipTypeId) && schema[FlipTypeId] === FlipTypeId
}

/**
 * Swaps the decoded and encoded sides of a schema.
 *
 * **When to use**
 *
 * Use to invert a schema transformation direction.
 *
 * **Details**
 *
 * Calling `flip` twice returns the original schema.
 *
 * **Example** (Flipping a number-from-string schema)
 *
 * ```ts
 * import { Schema } from "effect"
 *
 * // NumberFromString: decodes string → number
 * const flipped = Schema.flip(Schema.NumberFromString)
 * // flipped: decodes number → string
 * ```
 *
 * @category transforming
 * @since 4.0.0
 */
export function flip<S extends Top>(schema: S): S extends flip<infer F> ? F["Rebuild"] : flip<S>
export function flip<S extends Top>(schema: S): flip<S> {
  if (isFlip$(schema)) {
    return schema.schema.rebuild(SchemaAST.flip(schema.ast))
  }
  return make(SchemaAST.flip(schema.ast), { [FlipTypeId]: FlipTypeId, schema })
}

/**
 * Type-level representation returned by {@link Literal}.
 *
 * @category models
 * @since 3.10.0
 */
export interface Literal<L extends SchemaAST.LiteralValue>
  extends Bottom<L, L, never, never, SchemaAST.Literal, Literal<L>>
{
  readonly literal: L
  transform<L2 extends SchemaAST.LiteralValue>(to: L2): decodeTo<Literal<L2>, Literal<L>>
}

/**
 * Creates a schema for a single literal value (string, number, bigint, boolean, or null).
 *
 * **Example** (Defining a string literal)
 *
 * ```ts
 * import { Schema } from "effect"
 *
 * const schema = Schema.Literal("hello")
 * // Type: Schema.Literal<"hello">
 * ```
 *
 * @see {@link Literals} for a schema that represents a union of literals.
 * @see {@link tag} for a schema that represents a literal value that can be
 * used as a discriminator field in tagged unions and has a constructor default.
 * @category constructors
 * @since 3.10.0
 */
export function Literal<L extends SchemaAST.LiteralValue>(literal: L): Literal<L> {
  const out = make<Literal<L>>(new SchemaAST.Literal(literal), {
    literal,
    transform<L2 extends SchemaAST.LiteralValue>(to: L2): decodeTo<Literal<L2>, Literal<L>> {
      return out.pipe(decodeTo(Literal(to), {
        decode: SchemaGetter.transform(() => to),
        encode: SchemaGetter.transform(() => literal)
      }))
    }
  })
  return out
}

/**
 * Namespace for {@link TemplateLiteral} helper types.
 *
 * @since 3.10.0
 */
export declare namespace TemplateLiteral {
  /**
   * Constraint for schema parts that can appear inside a `TemplateLiteral`.
   *
   * **Details**
   *
   * The schema's encoded value must be a `string`, `number`, or `bigint` so it can
   * be converted into a template literal string segment.
   *
   * @category utility types
   * @since 4.0.0
   */
  export interface SchemaPart extends Constraint {
    readonly Encoded: string | number | bigint
  }

  /**
   * Literal value that can be used directly as a part of a `TemplateLiteral`.
   *
   * @category utility types
   * @since 4.0.0
   */
  export type LiteralPart = string | number | bigint

  /**
   * A single part of a `TemplateLiteral`, either an interpolated schema part or a
   * literal `string`, `number`, or `bigint`.
   *
   * @category utility types
   * @since 4.0.0
   */
  export type Part = SchemaPart | LiteralPart

  /**
   * Ordered list of parts used to construct a `TemplateLiteral` schema.
   *
   * @category utility types
   * @since 4.0.0
   */
  export type Parts = ReadonlyArray<Part>

  type AppendType<
    Template extends string,
    Next
  > = Next extends LiteralPart ? `${Template}${Next}`
    : Next extends { readonly Encoded: infer E extends LiteralPart } ? `${Template}${E}`
    : never

  /**
   * Computes the encoded string literal type produced by concatenating the encoded
   * forms of all template literal parts.
   *
   * @category utility types
   * @since 3.10.0
   */
  export type Encoded<Parts> = Parts extends readonly [...infer Init, infer Last] ? AppendType<Encoded<Init>, Last>
    : ``
}

/**
 * Type-level representation returned by {@link TemplateLiteral}.
 *
 * @category models
 * @since 3.10.0
 */
export interface TemplateLiteral<Parts extends TemplateLiteral.Parts> extends
  Bottom<
    TemplateLiteral.Encoded<Parts>,
    TemplateLiteral.Encoded<Parts>,
    never,
    never,
    SchemaAST.TemplateLiteral,
    TemplateLiteral<Parts>
  >
{
  readonly parts: Parts
}

function templateLiteralFromParts<Parts extends TemplateLiteral.Parts>(parts: Parts) {
  return new SchemaAST.TemplateLiteral(
    parts.map((part) => isSchema(part) ? part.ast : new SchemaAST.Literal(part as TemplateLiteral.LiteralPart))
  )
}

/**
 * Creates a schema that validates strings by matching ordered template literal
 * parts.
 *
 * **When to use**
 *
 * Use when the decoded value should remain the matched string and you do not
 * need the individual template parts parsed into a tuple.
 *
 * **Details**
 *
 * Each part can be a literal `string`, `number`, or `bigint`, or a schema whose
 * encoded type is `string`, `number`, or `bigint`. Checks on string, number,
 * and bigint schema parts are applied while matching each segment.
 *
 * **Example** (Defining a URL path pattern)
 *
 * ```ts
 * import { Schema } from "effect"
 *
 * const schema = Schema.TemplateLiteral(["/user/", Schema.Number])
 * // matches strings like "/user/123", "/user/42", etc.
 * ```
 *
 * @see {@link TemplateLiteralParser} for a schema that also parses matched parts into a tuple.
 * @category constructors
 * @since 3.10.0
 */
export function TemplateLiteral<const Parts extends TemplateLiteral.Parts>(parts: Parts): TemplateLiteral<Parts> {
  return make(templateLiteralFromParts(parts), { parts })
}

/**
 * Namespace for {@link TemplateLiteralParser} helper types.
 *
 * @since 3.10.0
 */
export declare namespace TemplateLiteralParser {
  /**
   * Computes the decoded tuple type produced by `TemplateLiteralParser`.
   *
   * **Details**
   *
   * Literal parts contribute their literal value to the tuple. Schema parts
   * contribute their decoded `Type`.
   *
   * @category utility types
   * @since 3.10.0
   */
  export type Type<Parts> = Parts extends readonly [infer Head, ...infer Tail] ? readonly [
      Head extends TemplateLiteral.LiteralPart ? Head :
        Head extends ConstraintDecoder<infer T, unknown> ? T
        : never,
      ...Type<Tail>
    ]
    : []
}

/**
 * Type-level representation returned by {@link TemplateLiteralParser}.
 *
 * @category models
 * @since 3.10.0
 */
export interface TemplateLiteralParser<Parts extends TemplateLiteral.Parts> extends
  BottomLazy<
    SchemaAST.Arrays,
    TemplateLiteralParser<Parts>
  >
{
  readonly "Type": TemplateLiteralParser.Type<Parts>
  readonly "Encoded": TemplateLiteral.Encoded<Parts>
  readonly "DecodingServices": never
  readonly "EncodingServices": never
  readonly "~type.make.in": TemplateLiteralParser.Type<Parts>
  readonly "~type.make": TemplateLiteralParser.Type<Parts>
  readonly "Iso": TemplateLiteralParser.Type<Parts>
  readonly parts: Parts
}

/**
 * Schema for parsing matched template literal strings into typed tuple parts.
 *
 * **When to use**
 *
 * Use to validate a template literal string and decode the matched parts into
 * typed values.
 *
 * **Details**
 *
 * Unlike {@link TemplateLiteral}, this schema decodes the matched string into a
 * readonly tuple with one element per schema part. Checks on string, number,
 * and bigint schema parts are applied while matching each segment.
 *
 * **Example** (Parsing path parameters)
 *
 * ```ts
 * import { Schema } from "effect"
 *
 * const schema = Schema.TemplateLiteralParser(["/user/", Schema.NumberFromString])
 * // decodes "/user/42" => readonly ["/user/", 42]
 * ```
 *
 * @see {@link TemplateLiteral} for a validation-only version that keeps the string encoded.
 * @category constructors
 * @since 3.10.0
 */
export function TemplateLiteralParser<const Parts extends TemplateLiteral.Parts>(
  parts: Parts
): TemplateLiteralParser<Parts> {
  return make(templateLiteralFromParts(parts).asTemplateLiteralParser(), { parts })
}

/**
 * Type-level representation returned by {@link Enum}.
 *
 * @category models
 * @since 4.0.0
 */
export interface Enum<A extends { [x: string]: string | number }>
  extends Bottom<A[keyof A], A[keyof A], never, never, SchemaAST.Enum, Enum<A>>
{
  readonly enums: A
}

/**
 * Creates a schema from a TypeScript enum object. Validates that the input is one of the enum's values.
 *
 * **Example** (Defining a direction enum)
 *
 * ```ts
 * import { Schema } from "effect"
 *
 * enum Direction {
 *   Up = "Up",
 *   Down = "Down"
 * }
 *
 * const schema = Schema.Enum(Direction)
 * // accepts "Up" or "Down"
 * ```
 *
 * @category constructors
 * @since 4.0.0
 */
export function Enum<A extends { [x: string]: string | number }>(enums: A): Enum<A> {
  return make(
    new SchemaAST.Enum(
      Object.keys(enums).filter(
        (key) => typeof enums[enums[key]] !== "number"
      ).map((key) => [key, enums[key]])
    ),
    { enums }
  )
}

/**
 * Type-level representation of {@link Never}.
 *
 * @category models
 * @since 3.10.0
 */
export interface Never extends Bottom<never, never, never, never, SchemaAST.Never, Never> {}

/**
 * Schema for the `never` type. Always fails validation — no value satisfies it.
 *
 * @category schemas
 * @since 3.10.0
 */
export const Never: Never = make(SchemaAST.never)

/**
 * Type-level representation of {@link Any}.
 *
 * @category models
 * @since 3.10.0
 */
export interface Any extends Bottom<any, any, never, never, SchemaAST.Any, Any> {}

/**
 * Schema for the `any` type. Accepts any value without validation.
 *
 * @see {@link Unknown} for a safer alternative that uses `unknown`.
 * @category schemas
 * @since 3.10.0
 */
export const Any: Any = make(SchemaAST.any)

/**
 * Type-level representation of {@link Unknown}.
 *
 * @category models
 * @since 3.10.0
 */
export interface Unknown extends Bottom<unknown, unknown, never, never, SchemaAST.Unknown, Unknown> {}

/**
 * Schema for the `unknown` type. Accepts any value without validation.
 *
 * **When to use**
 *
 * Use as a top schema when you need to accept any input while preserving
 * TypeScript's `unknown` safety at use sites.
 *
 * @see {@link Any} for the `any` variant.
 * @category schemas
 * @since 3.10.0
 */
export const Unknown: Unknown = make(SchemaAST.unknown)

/**
 * Type-level representation of {@link Null}.
 *
 * @category models
 * @since 3.10.0
 */
export interface Null extends Bottom<null, null, never, never, SchemaAST.Null, Null> {}

/**
 * Schema for the `null` literal. Validates that the input is strictly `null`.
 *
 * @see {@link NullOr} for a union with another schema.
 * @category schemas
 * @since 3.10.0
 */
export const Null: Null = make(SchemaAST.null)

/**
 * Type-level representation of {@link Undefined}.
 *
 * @category models
 * @since 3.10.0
 */
export interface Undefined extends Bottom<undefined, undefined, never, never, SchemaAST.Undefined, Undefined> {}

/**
 * Schema for the `undefined` literal. Validates that the input is strictly `undefined`.
 *
 * @see {@link UndefinedOr} for a union with another schema.
 * @category schemas
 * @since 3.10.0
 */
export const Undefined: Undefined = make(SchemaAST.undefined)

/**
 * Type-level representation of {@link String}.
 *
 * @category models
 * @since 4.0.0
 */
export interface String extends Bottom<string, string, never, never, SchemaAST.String, String> {}

/**
 * Schema for `string` values. Validates that the input is `typeof` `"string"`.
 *
 * @category schemas
 * @since 4.0.0
 */
export const String: String = make(SchemaAST.string)

/**
 * Type-level representation of {@link Number}.
 *
 * @category models
 * @since 4.0.0
 */
export interface Number extends Bottom<number, number, never, never, SchemaAST.Number, Number> {}

/**
 * Schema for `number` values, including `NaN`, `Infinity`, and `-Infinity`.
 *
 * **Details**
 *
 * Default JSON serializer:
 *
 * - Finite numbers are serialized as numbers.
 * - Non-finite values are serialized as strings (`"NaN"`, `"Infinity"`, `"-Infinity"`).
 *
 * @see {@link Finite} for a schema that excludes non-finite values.
 * @category schemas
 * @since 4.0.0
 */
export const Number: Number = make(SchemaAST.number)

/**
 * Type-level representation of {@link Boolean}.
 *
 * @category models
 * @since 4.0.0
 */
export interface Boolean extends Bottom<boolean, boolean, never, never, SchemaAST.Boolean, Boolean> {}

/**
 * Schema for `boolean` values. Validates that the input is `typeof` `"boolean"`.
 *
 * **When to use**
 *
 * Use to validate values that are already JavaScript booleans.
 *
 * @see {@link BooleanFromBit} for a schema that decodes bit literals `0` or `1` into a boolean
 *
 * @category boolean
 * @since 4.0.0
 */
export const Boolean: Boolean = make(SchemaAST.boolean)

/**
 * Type-level representation of {@link Symbol}.
 *
 * @category models
 * @since 4.0.0
 */
export interface Symbol extends Bottom<symbol, symbol, never, never, SchemaAST.Symbol, Symbol> {}

/**
 * Schema for `symbol` values. Validates that the input is `typeof` `"symbol"`.
 *
 * @see {@link UniqueSymbol} for a schema that matches a specific symbol.
 * @category schemas
 * @since 4.0.0
 */
export const Symbol: Symbol = make(SchemaAST.symbol)

/**
 * Type-level representation of {@link BigInt}.
 *
 * @category models
 * @since 4.0.0
 */
export interface BigInt extends Bottom<bigint, bigint, never, never, SchemaAST.BigInt, BigInt> {}

/**
 * Schema for `bigint` values. Validates that the input is `typeof` `"bigint"`.
 *
 * **When to use**
 *
 * Use when the input is already a bigint and the schema should validate and
 * preserve bigint values without parsing from another representation.
 *
 * @see {@link BigIntFromString} for parsing string input into a bigint
 *
 * @category schemas
 * @since 4.0.0
 */
export const BigInt: BigInt = make(SchemaAST.bigInt)

/**
 * Type-level representation of {@link Void}.
 *
 * @category models
 * @since 3.10.0
 */
export interface Void extends Bottom<void, void, never, never, SchemaAST.Void, Void> {}

/**
 * Schema for a TypeScript `void` return value.
 *
 * **When to use**
 *
 * Use when you need to model the return value of a function, RPC, or endpoint
 * whose result is intentionally ignored.
 *
 * **Details**
 *
 * Runtime parsing accepts any present value and discards it, producing
 * `undefined`. The public decoded and encoded TypeScript representation remains
 * `void`, so typed construction, decoding, and encoding APIs are still modeled
 * as `void`.
 *
 * @see {@link Undefined} for a schema that matches only the exact `undefined` value.
 * @category schemas
 * @since 3.10.0
 */
export const Void: Void = make(SchemaAST.void)

/**
 * Type-level representation of {@link ObjectKeyword}.
 *
 * @category models
 * @since 4.0.0
 */
export interface ObjectKeyword extends Bottom<object, object, never, never, SchemaAST.ObjectKeyword, ObjectKeyword> {}

/**
 * Schema for the `object` type. Validates that the input is a non-null object or function
 * (i.e. `typeof value === "object" && value !== null || typeof value === "function"`).
 *
 * @category schemas
 * @since 4.0.0
 */
export const ObjectKeyword: ObjectKeyword = make(SchemaAST.objectKeyword)

/**
 * Type-level representation returned by {@link UniqueSymbol}.
 *
 * @category models
 * @since 4.0.0
 */
export interface UniqueSymbol<sym extends symbol>
  extends Bottom<sym, sym, never, never, SchemaAST.UniqueSymbol, UniqueSymbol<sym>>
{}

/**
 * Creates a schema for a specific symbol. Only that exact symbol satisfies the schema.
 *
 * **Example** (Defining a specific symbol)
 *
 * ```ts
 * import { Schema } from "effect"
 *
 * const mySymbol = Symbol.for("mySymbol")
 * const schema = Schema.UniqueSymbol(mySymbol)
 * ```
 *
 * @see {@link Symbol} for a schema that accepts any symbol.
 * @category constructors
 * @since 4.0.0
 */
export function UniqueSymbol<const sym extends symbol>(symbol: sym): UniqueSymbol<sym> {
  return make(new SchemaAST.UniqueSymbol(symbol))
}

/**
 * Namespace for struct field type utilities.
 *
 * **Details**
 *
 * These types compute the decoded `Type`, encoded `Encoded`, and constructor
 * input `MakeIn` of a {@link Struct} from its field map, handling optional,
 * mutable, and other field modifiers automatically.
 *
 * - `Struct.Fields` — constraint for the field map object
 * - `Struct.Type<F>` — decoded type of the struct
 * - `Struct.Encoded<F>` — encoded type of the struct
 * - `Struct.MakeIn<F>` — constructor input (optional/defaulted fields may be omitted)
 * - `Struct.DecodingServices<F>` / `Struct.EncodingServices<F>` — required services
 *
 * @since 3.10.0
 */
export declare namespace Struct {
  /**
   * Constraint for a struct field map: an object whose values are schemas.
   *
   * @category utility types
   * @since 3.10.0
   */
  export type Fields = { readonly [x: PropertyKey]: Constraint }

  type TypeOptionalKeys<Fields extends Struct.Fields> = {
    [K in keyof Fields]: Fields[K] extends { readonly "~type.optionality": "optional" } ? K
      : never
  }[keyof Fields]

  type TypeMutableKeys<Fields extends Struct.Fields> = {
    [K in keyof Fields]: Fields[K] extends { readonly "~type.mutability": "mutable" } ? K
      : never
  }[keyof Fields]

  type SetOptional<A, K extends keyof A> = Omit<A, K> & Partial<Pick<A, K>>

  type Mutable<A> = { -readonly [K in keyof A]: A[K] }

  type SetMutable<A, K extends keyof A> = Omit<A, K> & Mutable<Pick<A, K>>

  type Side = "Type" | "Iso" | "Encoded"

  type EncodedOptionalKeys<Fields extends Struct.Fields> = {
    [K in keyof Fields]: Fields[K] extends { readonly "~encoded.optionality": "optional" } ? K
      : never
  }[keyof Fields]

  type EncodedMutableKeys<Fields extends Struct.Fields> = {
    [K in keyof Fields]: Fields[K] extends { readonly "~encoded.mutability": "mutable" } ? K
      : never
  }[keyof Fields]

  type SideOptionalKeys<F extends Fields, S extends Side> = S extends "Encoded" ? EncodedOptionalKeys<F>
    : TypeOptionalKeys<F>

  type SideMutableKeys<F extends Fields, S extends Side> = S extends "Encoded" ? EncodedMutableKeys<F>
    : TypeMutableKeys<F>

  type ReadonlySide<F extends Fields, S extends Side> = { readonly [K in keyof F]: F[K][S] }

  type View<
    F extends Fields,
    S extends Side,
    O extends keyof F = SideOptionalKeys<F, S>,
    M extends keyof F = SideMutableKeys<F, S>
  > = [O | M] extends [never] ? ReadonlySide<F, S>
    : [M] extends [never] ? Simplify<SetOptional<ReadonlySide<F, S>, O>>
    : [O] extends [never] ? Simplify<SetMutable<ReadonlySide<F, S>, M>>
    : Simplify<
      SetMutable<
        SetOptional<ReadonlySide<F, S>, O>,
        Extract<keyof SetOptional<ReadonlySide<F, S>, O>, M>
      >
    >

  /**
   * Computes the decoded object type for a struct field map.
   *
   * **Details**
   *
   * Field schemas contribute their decoded `Type`. `optionalKey` and `optional`
   * produce optional properties, while `mutableKey` produces writable properties.
   *
   * @category utility types
   * @since 3.10.0
   */
  export type Type<F extends Fields> = View<F, "Type">

  /**
   * Computes the iso object type for a struct field map from each field schema's
   * `Iso` type.
   *
   * **Details**
   *
   * The resulting property optionality and mutability follow the same field
   * modifiers used by `Struct.Type`.
   *
   * @category utility types
   * @since 4.0.0
   */
  export type Iso<F extends Fields> = View<F, "Iso">

  /**
   * Computes the encoded object type for a struct field map.
   *
   * **Details**
   *
   * Field schemas contribute their `Encoded` type. Encoded-side optionality and
   * mutability modifiers determine whether properties are optional or writable in
   * the encoded shape.
   *
   * @category utility types
   * @since 3.10.0
   */
  export type Encoded<F extends Fields> = View<F, "Encoded">

  /**
   * Union of all decoding service requirements needed by the schemas in a struct
   * field map.
   *
   * @category utility types
   * @since 4.0.0
   */
  export type DecodingServices<F extends Fields> = { readonly [K in keyof F]: F[K]["DecodingServices"] }[keyof F]

  /**
   * Union of all encoding service requirements needed by the schemas in a struct
   * field map.
   *
   * @category utility types
   * @since 4.0.0
   */
  export type EncodingServices<F extends Fields> = { readonly [K in keyof F]: F[K]["EncodingServices"] }[keyof F]

  type TypeConstructorDefaultedKeys<Fields extends Struct.Fields> = {
    [K in keyof Fields]: Fields[K] extends { readonly "~type.constructor.default": "with-default" } ? K
      : never
  }[keyof Fields]

  type ReadonlyMakeIn<F extends Fields> = { readonly [K in keyof F]: F[K]["~type.make"] }

  type MakeInView<
    F extends Fields,
    O extends keyof F = TypeOptionalKeys<F> | TypeConstructorDefaultedKeys<F>
  > = [O] extends [never] ? ReadonlyMakeIn<F> : Simplify<SetOptional<ReadonlyMakeIn<F>, O>>

  /**
   * Computes the input object type accepted when constructing a struct value.
   *
   * **Details**
   *
   * Required fields use each field schema's `~type.make` input. Fields marked
   * optional or with a constructor default may be omitted.
   *
   * @category utility types
   * @since 4.0.0
   */
  export type MakeIn<F extends Fields> = MakeInView<F>
}

/**
 * Type-level representation returned by {@link Struct}.
 *
 * @category models
 * @since 3.10.0
 */
export interface Struct<Fields extends Struct.Fields> extends BottomLazy<SchemaAST.Objects, Struct<Fields>> {
  readonly "Type": Struct.Type<Fields>
  readonly "Encoded": Struct.Encoded<Fields>
  readonly "DecodingServices": Struct.DecodingServices<Fields>
  readonly "EncodingServices": Struct.EncodingServices<Fields>
  readonly "~type.make.in": Struct.MakeIn<Fields>
  readonly "~type.make": Struct.MakeIn<Fields>
  readonly "Iso": Struct.Iso<Fields>
  /**
   * The field definitions of this struct. Spread them into a new struct to
   * reuse fields across schemas.
   *
   * **Example** (Reusing fields across structs)
   *
   * ```ts
   * import { Schema } from "effect"
   *
   * const Timestamped = Schema.Struct({
   *   createdAt: Schema.Date,
   *   updatedAt: Schema.Date
   * })
   *
   * const User = Schema.Struct({
   *   ...Timestamped.fields,
   *   name: Schema.String,
   *   email: Schema.String
   * })
   * ```
   */
  readonly fields: Fields
  /**
   * Returns a new struct with the fields modified by the provided function.
   *
   * **Details**
   *
   * Options:
   *
   * - `unsafePreserveChecks` - if `true`, keep any `.check(...)` constraints
   *   that were attached to the original union. Defaults to `false`.
   *
   *   **Warning**: This is an unsafe operation. Since `mapFields`
   *   transformations change the schema type, the original refinement functions
   *   may no longer be valid or safe to apply to the transformed schema. Only
   *   use this option if you have verified that your refinements remain correct
   *   after the transformation.
   */
  mapFields<To extends Struct.Fields>(
    f: (fields: Fields) => To,
    options?: {
      readonly unsafePreserveChecks?: boolean | undefined
    } | undefined
  ): Struct<Simplify<Readonly<To>>>
}

function makeStruct<const Fields extends Struct.Fields>(ast: SchemaAST.Objects, fields: Fields): Struct<Fields> {
  return make(ast, {
    fields,
    mapFields<To extends Struct.Fields>(
      this: Struct<Fields>,
      f: (fields: Fields) => To,
      options?: {
        readonly unsafePreserveChecks?: boolean | undefined
      } | undefined
    ): Struct<To> {
      const fields = f(this.fields)
      return makeStruct(SchemaAST.struct(fields, options?.unsafePreserveChecks ? this.ast.checks : undefined), fields)
    }
  })
}

/**
 * Defines a struct schema from a map of field schemas.
 *
 * **Details**
 *
 * Each field value is a schema. Use {@link optionalKey} or {@link optional} to
 * mark fields as optional, and {@link mutableKey} to mark them as mutable.
 *
 * The resulting schema's `Type` is a readonly object type with the fields'
 * decoded types. The `Encoded` form mirrors the field schemas' encoded types.
 *
 * **Example** (Defining a basic struct)
 *
 * ```ts
 * import { Schema } from "effect"
 *
 * const Person = Schema.Struct({
 *   name: Schema.String,
 *   age: Schema.Number,
 *   email: Schema.optionalKey(Schema.String)
 * })
 *
 * // { readonly name: string; readonly age: number; readonly email?: string }
 * type Person = typeof Person.Type
 *
 * const alice = Schema.decodeUnknownSync(Person)({ name: "Alice", age: 30 })
 * console.log(alice)
 * // { name: 'Alice', age: 30 }
 * ```
 *
 * @category constructors
 * @since 3.10.0
 */
export function Struct<const Fields extends Struct.Fields>(fields: Fields): Struct<Fields> {
  return makeStruct(SchemaAST.struct(fields, undefined), fields)
}

interface fieldsAssign<NewFields extends Struct.Fields> extends Lambda {
  <Fields extends Struct.Fields>(
    struct: Struct<Fields>
  ): Struct<Struct_.Simplify<Struct_.Assign<Fields, NewFields>>>
  readonly "~lambda.out": this["~lambda.in"] extends Struct<Struct.Fields>
    ? Struct<Struct_.Simplify<Struct_.Assign<this["~lambda.in"]["fields"], NewFields>>>
    : "Error: schema not eligible for fieldsAssign"
}

/**
 * Adds fields to a struct schema through a struct-mapping lambda.
 *
 * **When to use**
 *
 * Use to add the same fields to an existing struct or every struct member of a
 * union.
 *
 * **Details**
 *
 * This is a shortcut for `MyStruct.mapFields(Struct.assign(fields))`.
 *
 * **Example** (Adding fields to a union of structs)
 *
 * ```ts
 * import { Schema, Tuple } from "effect"
 *
 * // Add a new field to all members of a union of structs
 * const schema = Schema.Union([
 *   Schema.Struct({ a: Schema.String }),
 *   Schema.Struct({ b: Schema.Number })
 * ]).mapMembers(Tuple.map(Schema.fieldsAssign({ c: Schema.Number })))
 * ```
 *
 * @category combinators
 * @since 4.0.0
 */
export function fieldsAssign<const NewFields extends Struct.Fields>(fields: NewFields) {
  return Struct_.lambda<fieldsAssign<NewFields>>((struct) => struct.mapFields(Struct_.assign(fields)))
}

/**
 * Type-level representation returned by {@link encodeKeys}.
 *
 * @category transforming
 * @since 4.0.0
 */
export interface encodeKeys<
  S extends Constraint & { readonly fields: Struct.Fields },
  M extends { readonly [K in keyof S["fields"]]?: PropertyKey }
> extends
  decodeTo<
    S,
    Struct<
      {
        [
          K in keyof S["fields"] as K extends keyof M ? M[K] extends PropertyKey ? M[K] : K : K
        ]: toEncoded<S["fields"][K]>
      }
    >
  >
{}

const canonicalPropertyKey = (key: PropertyKey): string | symbol =>
  typeof key === "symbol" ? key : globalThis.String(key)

/**
 * Renames struct keys in the encoded form without changing the decoded type.
 *
 * **Details**
 *
 * Takes a partial mapping `{ decodedKey: encodedKey }` and produces a
 * transformation schema that decodes from the renamed keys and encodes back to
 * the renamed keys. Keys not present in the mapping are left unchanged.
 * If two existing fields would produce the same encoded key, construction
 * fails.
 *
 * **Example** (Renaming `name` to `full_name` in the encoded form)
 *
 * ```ts
 * import { Schema } from "effect"
 *
 * const Person = Schema.Struct({ name: Schema.String, age: Schema.Number })
 * const Encoded = Person.pipe(Schema.encodeKeys({ name: "full_name" }))
 *
 * // Decodes { full_name: "Alice", age: 30 } → { name: "Alice", age: 30 }
 * const alice = Schema.decodeUnknownSync(Encoded)({ full_name: "Alice", age: 30 })
 * console.log(alice)
 * // { name: 'Alice', age: 30 }
 * ```
 *
 * @category transforming
 * @since 4.0.0
 */
export function encodeKeys<
  S extends Constraint & { readonly fields: Struct.Fields },
  const M extends { readonly [K in keyof S["fields"]]?: PropertyKey }
>(mapping: M) {
  return function(self: S): encodeKeys<S, M> {
    const fields: any = {}
    const appliedMapping: any = {}
    const reverseMapping: any = {}
    const seenEncodedKeys = new Set<string | symbol>()
    for (const k of Reflect.ownKeys(self.fields)) {
      const encoded = toEncoded(self.fields[k])
      const hasMapping = Object.hasOwn(mapping, k)
      const encodedKey = hasMapping ? (mapping as any)[k] as PropertyKey : k
      const canonical = canonicalPropertyKey(encodedKey)
      if (seenEncodedKeys.has(canonical)) {
        throw new globalThis.Error(`Duplicate encoded keys: ${formatPropertyKey(encodedKey)}`)
      }
      seenEncodedKeys.add(canonical)
      fields[encodedKey] = encoded
      if (hasMapping) {
        appliedMapping[k] = encodedKey
        reverseMapping[encodedKey] = k
      }
    }
    return Struct(fields).pipe(decodeTo(
      self,
      SchemaTransformation.transform<any, any>({
        decode: Struct_.renameKeys(reverseMapping),
        encode: Struct_.renameKeys(appliedMapping)
      })
    )) as any
  }
}

/**
 * Adds derived fields to a struct schema during decoding.
 *
 * **Details**
 *
 * Each new field is derived from the decoded struct value via a function that
 * returns `Option`. On encoding the derived fields are stripped. This allows
 * computed or enriched fields to live in the decoded type without appearing in
 * the encoded form.
 *
 * **Example** (Adding a computed `fullName` field)
 *
 * ```ts
 * import { Option, Schema } from "effect"
 *
 * const Person = Schema.Struct({ first: Schema.String, last: Schema.String })
 * const Extended = Person.pipe(
 *   Schema.extendTo(
 *     { fullName: Schema.String },
 *     { fullName: (p) => Option.some(`${p.first} ${p.last}`) }
 *   )
 * )
 *
 * const alice = Schema.decodeUnknownSync(Extended)({ first: "Alice", last: "Smith" })
 * console.log(alice.fullName)
 * // Alice Smith
 * ```
 *
 * @category transforming
 * @since 4.0.0
 */
export function extendTo<S extends Struct<Struct.Fields>, const Fields extends Struct.Fields>(
  /** The new fields to add */
  fields: Fields,
  /** A function per field to derive its value from the original input */
  derive: { readonly [K in keyof Fields]: (s: S["Type"]) => Option_.Option<Fields[K]["Type"]> }
) {
  return (
    self: S
  ): decodeTo<Struct<Simplify<{ [K in keyof S["fields"]]: toType<S["fields"][K]> } & Fields>>, S> => {
    const f = Record_.map(self.fields, toType)
    const to = Struct({ ...f, ...fields })
    return self.pipe(decodeTo(
      to,
      SchemaTransformation.transform({
        decode: (input) => {
          const out: any = { ...input }
          for (const k in fields) {
            const f = derive[k]
            const o = f(input)
            if (Option_.isSome(o)) {
              out[k] = o.value
            }
          }
          return out
        },
        encode: (input) => {
          const out = { ...input }
          for (const k in fields) {
            delete out[k]
          }
          return out
        }
      })
    )) as any
  }
}

/**
 * Namespace for `Record` type utilities.
 *
 * **Details**
 *
 * - `Record.Key` — constraint for the key schema (must encode to `PropertyKey`)
 * - `Record.Type<K, V>` — decoded type of the record
 * - `Record.Encoded<K, V>` — encoded type of the record
 *
 * @since 3.10.0
 */
export declare namespace Record {
  /**
   * Constraint for schemas that can be used as record keys.
   *
   * **Details**
   *
   * The key schema must decode and encode property keys (`string`, `number`, or
   * `symbol`) so it can describe object property names.
   *
   * @category utility types
   * @since 4.0.0
   */
  export interface Key extends Codec<PropertyKey, PropertyKey, unknown, unknown> {
    readonly "~type.make": PropertyKey
    readonly "Iso": PropertyKey
  }

  /**
   * Computes the decoded object type for a record schema from its key and value
   * schemas.
   *
   * **Details**
   *
   * The key schema supplies the property keys and the value schema supplies each
   * property's decoded `Type`. Optional and mutable value schemas affect the
   * resulting property optionality and writability.
   *
   * @category utility types
   * @since 3.10.0
   */
  export type Type<Key extends Record.Key, Value extends Constraint> = Value extends
    { readonly "~type.optionality": "optional" } ?
    Value extends { readonly "~type.mutability": "mutable" } ? { [P in Key["Type"]]?: Value["Type"] }
    : { readonly [P in Key["Type"]]?: Value["Type"] }
    : Value extends { readonly "~type.mutability": "mutable" } ? { [P in Key["Type"]]: Value["Type"] }
    : { readonly [P in Key["Type"]]: Value["Type"] }

  /**
   * Computes the iso object type for a record schema from the key schema's `Iso`
   * keys and the value schema's `Iso` values.
   *
   * @category utility types
   * @since 4.0.0
   */
  export type Iso<Key extends Record.Key, Value extends Constraint> = Value extends
    { readonly "~type.optionality": "optional" } ?
    Value extends { readonly "~type.mutability": "mutable" } ? { [P in Key["Iso"]]?: Value["Iso"] }
    : { readonly [P in Key["Iso"]]?: Value["Iso"] }
    : Value extends { readonly "~type.mutability": "mutable" } ? { [P in Key["Iso"]]: Value["Iso"] }
    : { readonly [P in Key["Iso"]]: Value["Iso"] }

  /**
   * Computes the encoded object type for a record schema from the key and value
   * schemas' encoded types.
   *
   * **Details**
   *
   * Encoded-side optionality and mutability on the value schema determine whether
   * the encoded record properties are optional or writable.
   *
   * @category utility types
   * @since 3.10.0
   */
  export type Encoded<Key extends Record.Key, Value extends Constraint> = Value extends
    { readonly "~encoded.optionality": "optional" } ?
    Value extends { readonly "~encoded.mutability": "mutable" } ? { [P in Key["Encoded"]]?: Value["Encoded"] }
    : { readonly [P in Key["Encoded"]]?: Value["Encoded"] }
    : Value extends { readonly "~encoded.mutability": "mutable" } ? { [P in Key["Encoded"]]: Value["Encoded"] }
    : { readonly [P in Key["Encoded"]]: Value["Encoded"] }

  /**
   * Union of the decoding service requirements of a record's key schema and value
   * schema.
   *
   * @category utility types
   * @since 4.0.0
   */
  export type DecodingServices<Key extends Record.Key, Value extends Constraint> =
    | Key["DecodingServices"]
    | Value["DecodingServices"]

  /**
   * Union of the encoding service requirements of a record's key schema and value
   * schema.
   *
   * @category utility types
   * @since 4.0.0
   */
  export type EncodingServices<Key extends Record.Key, Value extends Constraint> =
    | Key["EncodingServices"]
    | Value["EncodingServices"]

  /**
   * Computes the input object type accepted when constructing a record value.
   *
   * **Details**
   *
   * Keys use the key schema's `~type.make` type and values use the value schema's
   * `~type.make` type. Value optionality and mutability determine whether
   * properties are optional or writable.
   *
   * @category utility types
   * @since 4.0.0
   */
  export type MakeIn<Key extends Record.Key, Value extends Constraint> = Value extends
    { readonly "~encoded.optionality": "optional" } ?
    Value extends { readonly "~encoded.mutability": "mutable" } ? { [P in Key["~type.make"]]?: Value["~type.make"] }
    : { readonly [P in Key["~type.make"]]?: Value["~type.make"] }
    : Value extends { readonly "~encoded.mutability": "mutable" } ? { [P in Key["~type.make"]]: Value["~type.make"] }
    : { readonly [P in Key["~type.make"]]: Value["~type.make"] }
}

/**
 * Type-level representation returned by {@link Record}.
 *
 * @category models
 * @since 4.0.0
 */
export interface $Record<Key extends Record.Key, Value extends Constraint> extends
  BottomLazy<
    SchemaAST.Objects,
    $Record<Key, Value>
  >
{
  readonly "Type": Record.Type<Key, Value>
  readonly "Encoded": Record.Encoded<Key, Value>
  readonly "DecodingServices": Record.DecodingServices<Key, Value>
  readonly "EncodingServices": Record.EncodingServices<Key, Value>
  readonly "~type.make.in": Simplify<Record.MakeIn<Key, Value>>
  readonly "~type.make": Simplify<Record.MakeIn<Key, Value>>
  readonly "Iso": Record.Iso<Key, Value>
  readonly key: Key
  readonly value: Value
}

/**
 * Defines a record schema whose dynamic properties are selected by a key schema
 * and decoded with a value schema.
 *
 * **Details**
 *
 * For dynamic keys, the key schema selects matching own properties and the
 * value schema decodes or encodes only those selected properties. Checks on
 * string, number, symbol, and template literal key schemas narrow which
 * properties are selected.
 *
 * For transformed key schemas, property selection is based on encoded property
 * names before the selected key is decoded.
 *
 * **Example** (Defining a string-keyed record of numbers)
 *
 * ```ts
 * import { Schema } from "effect"
 *
 * const schema = Schema.Record(Schema.String, Schema.Number)
 *
 * // { readonly [x: string]: number }
 * type R = typeof schema.Type
 *
 * const result = Schema.decodeUnknownSync(schema)({ a: 1, b: 2 })
 * console.log(result)
 * // { a: 1, b: 2 }
 * ```
 *
 * @category constructors
 * @since 3.10.0
 */
export function Record<Key extends Record.Key, Value extends Constraint>(
  key: Key,
  value: Value,
  options?: {
    readonly keyValueCombiner: {
      readonly decode?: Combiner.Combiner<readonly [Key["Type"], Value["Type"]]> | undefined
      readonly encode?: Combiner.Combiner<readonly [Key["Encoded"], Value["Encoded"]]> | undefined
    }
  }
): $Record<Key, Value> {
  const keyValueCombiner = options?.keyValueCombiner?.decode || options?.keyValueCombiner?.encode
    ? new SchemaAST.KeyValueCombiner(options.keyValueCombiner.decode, options.keyValueCombiner.encode)
    : undefined
  return make(SchemaAST.record(key.ast, value.ast, keyValueCombiner), { key, value })
}

/**
 * Namespace for `StructWithRest` type utilities.
 *
 * **Details**
 *
 * - `StructWithRest.Type<S, R>` — decoded type (struct type intersected with record types)
 * - `StructWithRest.Encoded<S, R>` — encoded type
 *
 * @since 4.0.0
 */
export declare namespace StructWithRest {
  /**
   * Constraint for object-like schemas that can be used as the fixed portion of a
   * `StructWithRest` schema.
   *
   * @category utility types
   * @since 4.0.0
   */
  export type Objects = Constraint & { readonly ast: SchemaAST.Objects }

  /**
   * Readonly list of record schemas that provide the additional index signatures
   * for a `StructWithRest` schema.
   *
   * @category utility types
   * @since 3.10.0
   */
  export type Records = ReadonlyArray<$Record<Record.Key, Constraint>>

  type MergeTuple<T extends ReadonlyArray<unknown>> = T extends readonly [infer Head, ...infer Tail] ?
    Head & MergeTuple<Tail>
    : {}

  type Intersect<
    S extends Objects,
    Records extends StructWithRest.Records,
    Side extends "Type" | "Iso" | "Encoded" | "~type.make"
  > =
    & S[Side]
    & MergeTuple<{ readonly [K in keyof Records]: Records[K][Side] }>

  /**
   * Computes the decoded type for `StructWithRest` by intersecting the base object
   * schema's decoded `Type` with the decoded types of all rest record schemas.
   *
   * @category utility types
   * @since 3.10.0
   */
  export type Type<S extends Objects, Records extends StructWithRest.Records> = Intersect<S, Records, "Type">

  /**
   * Computes the iso type for `StructWithRest` by intersecting the base object
   * schema's `Iso` type with the `Iso` types of all rest record schemas.
   *
   * @category utility types
   * @since 4.0.0
   */
  export type Iso<S extends Objects, Records extends StructWithRest.Records> = Intersect<S, Records, "Iso">

  /**
   * Computes the encoded type for `StructWithRest` by intersecting the base object
   * schema's encoded type with the encoded types of all rest record schemas.
   *
   * @category utility types
   * @since 3.10.0
   */
  export type Encoded<S extends Objects, Records extends StructWithRest.Records> = Intersect<S, Records, "Encoded">

  /**
   * Computes the input type accepted when constructing a `StructWithRest` value by
   * intersecting the base object's make input with the make inputs of all rest
   * record schemas.
   *
   * @category utility types
   * @since 4.0.0
   */
  export type MakeIn<S extends Objects, Records extends StructWithRest.Records> = Intersect<S, Records, "~type.make">

  type Services<
    S extends Objects,
    Records extends StructWithRest.Records,
    Side extends "DecodingServices" | "EncodingServices"
  > =
    | S[Side]
    | { [K in keyof Records]: Records[K][Side] }[number]

  /**
   * Union of the decoding service requirements of the base object schema and all
   * rest record schemas.
   *
   * @category utility types
   * @since 4.0.0
   */
  export type DecodingServices<S extends Objects, Records extends StructWithRest.Records> = Services<
    S,
    Records,
    "DecodingServices"
  >

  /**
   * Union of the encoding service requirements of the base object schema and all
   * rest record schemas.
   *
   * @category utility types
   * @since 4.0.0
   */
  export type EncodingServices<S extends Objects, Records extends StructWithRest.Records> = Services<
    S,
    Records,
    "EncodingServices"
  >

  type IncompatibleKeys<A, B, OK extends (keyof A & keyof B) = Extract<keyof A, keyof B>> = {
    [K in OK]: Required<Pick<A, K>>[K] extends B[K] ? never : K
  }[OK]

  type IncompatibleSideKeys<
    S extends Objects,
    Records extends StructWithRest.Records,
    Side extends "Type" | "Encoded" | "Iso" | "~type.make"
  > = {
    [I in keyof Records]: Records[I][Side] extends object ? IncompatibleKeys<S[Side], Records[I][Side]> : never
  }[number]

  type IncompatibleRecords<S extends Objects, Records extends StructWithRest.Records> =
    | IncompatibleSideKeys<S, Records, "Type">
    | IncompatibleSideKeys<S, Records, "Encoded">
    | IncompatibleSideKeys<S, Records, "Iso">
    | IncompatibleSideKeys<S, Records, "~type.make">

  /**
   * Checks whether fixed fields are compatible with the rest record schemas.
   *
   * **Details**
   *
   * Returns `true` when all fixed fields can also satisfy the matching rest
   * index signatures. Returns a diagnostic object when TypeScript would make
   * the resulting intersection too narrow for one or more fixed keys.
   *
   * **Example** (Checking record compatibility)
   *
   * ```ts
   * import { Schema } from "effect"
   *
   * const user = Schema.Struct({ id: Schema.String })
   * const stringExtras = [Schema.Record(Schema.String, Schema.String)] as const
   *
   * type UserCheck = Schema.StructWithRest.ValidateRecords<typeof user, typeof stringExtras>
   *
   * const userCheck: UserCheck = true
   * void userCheck
   *
   * const counter = Schema.Struct({ count: Schema.NumberFromString })
   *
   * type CounterCheck = Schema.StructWithRest.ValidateRecords<typeof counter, typeof stringExtras>
   * //    ^? { "incompatible index signatures": "count" }
   *
   * const counterCheck = null as unknown as CounterCheck
   * void counterCheck
   * ```
   *
   * @category utility types
   * @since 4.0.0
   */
  export type ValidateRecords<
    S extends Objects,
    Records extends StructWithRest.Records
  > = [IncompatibleRecords<S, Records>] extends [never] ? true
    : {
      "incompatible index signatures": IncompatibleRecords<S, Records>
    }
}

/**
 * Type-level representation returned by {@link StructWithRest}.
 *
 * @category models
 * @since 4.0.0
 */
export interface StructWithRest<
  S extends StructWithRest.Objects,
  Records extends StructWithRest.Records
> extends
  BottomLazy<
    SchemaAST.Objects,
    StructWithRest<S, Records>
  >
{
  readonly "Type": Simplify<StructWithRest.Type<S, Records>>
  readonly "Encoded": Simplify<StructWithRest.Encoded<S, Records>>
  readonly "DecodingServices": StructWithRest.DecodingServices<S, Records>
  readonly "EncodingServices": StructWithRest.EncodingServices<S, Records>
  readonly "~type.make.in": Simplify<StructWithRest.MakeIn<S, Records>>
  readonly "~type.make": Simplify<StructWithRest.MakeIn<S, Records>>
  readonly "Iso": Simplify<StructWithRest.Iso<S, Records>>
  readonly schema: S
  readonly records: Records
}

/**
 * Extends a struct schema with one or more record (index-signature) schemas,
 * producing a schema whose decoded type intersects the struct and all records.
 *
 * **Gotchas**
 *
 * TypeScript index signatures also apply to fixed keys. `StructWithRest` does
 * not reject incompatible fixed fields at the call site; use
 * `StructWithRest.ValidateRecords` when you want an explicit type-level
 * compatibility check.
 *
 * **Example** (Defining structs with string-indexed extra keys)
 *
 * ```ts
 * import { Schema } from "effect"
 *
 * const schema = Schema.StructWithRest(
 *   Schema.Struct({ id: Schema.Number }),
 *   [Schema.Record(Schema.String, Schema.Number)]
 * )
 *
 * // { readonly id: number, readonly [x: string]: number }
 * type T = typeof schema.Type
 * ```
 *
 * @category constructors
 * @since 4.0.0
 */
export function StructWithRest<
  const S extends StructWithRest.Objects,
  const Records extends StructWithRest.Records
>(
  schema: S,
  records: Records
): StructWithRest<S, Records> {
  return make(SchemaAST.structWithRest(schema.ast, records.map(SchemaAST.getAST)), { schema, records })
}

/**
 * Namespace for `Tuple` type utilities.
 *
 * **Details**
 *
 * - `Tuple.Elements` — constraint for the element schema array
 * - `Tuple.Type<E>` — decoded tuple type
 * - `Tuple.Encoded<E>` — encoded tuple type
 * - `Tuple.MakeIn<E>` — constructor input tuple
 *
 * @since 3.10.0
 */
export declare namespace Tuple {
  /**
   * Constraint for the readonly array of element schemas used to define a
   * fixed-length `Tuple` schema.
   *
   * @category utility types
   * @since 3.10.0
   */
  export type Elements = ReadonlyArray<Constraint>

  type Type_<
    Elements,
    Out extends ReadonlyArray<any> = readonly []
  > = Elements extends readonly [infer Head, ...infer Tail] ?
    Head extends { readonly "Type": infer T } ?
      Head extends { readonly "~type.optionality": "optional" } ? Type_<Tail, readonly [...Out, T?]>
      : Type_<Tail, readonly [...Out, T]>
    : Out
    : Out

  /**
   * Computes the decoded tuple type for a tuple element schema array.
   *
   * **Details**
   *
   * Each element contributes its decoded `Type`; optional element schemas produce
   * optional tuple positions.
   *
   * @category utility types
   * @since 3.10.0
   */
  export type Type<E extends Elements> = Type_<E>

  type Iso_<
    Elements,
    Out extends ReadonlyArray<any> = readonly []
  > = Elements extends readonly [infer Head, ...infer Tail] ?
    Head extends { readonly "Iso": infer T } ?
      Head extends { readonly "~type.optionality": "optional" } ? Iso_<Tail, readonly [...Out, T?]>
      : Iso_<Tail, readonly [...Out, T]>
    : Out
    : Out

  /**
   * Computes the iso tuple type for a tuple element schema array from each
   * element schema's `Iso` type.
   *
   * @category utility types
   * @since 4.0.0
   */
  export type Iso<E extends Elements> = Iso_<E>

  type Encoded_<
    Elements,
    Out extends ReadonlyArray<any> = readonly []
  > = Elements extends readonly [infer Head, ...infer Tail] ?
    Head extends { readonly "Encoded": infer T } ?
      Head extends { readonly "~encoded.optionality": "optional" } ? Encoded_<Tail, readonly [...Out, T?]>
      : Encoded_<Tail, readonly [...Out, T]>
    : Out
    : Out

  /**
   * Computes the encoded tuple type for a tuple element schema array.
   *
   * **Details**
   *
   * Each element contributes its `Encoded` type; encoded-side optional element
   * schemas produce optional tuple positions.
   *
   * @category utility types
   * @since 3.10.0
   */
  export type Encoded<E extends Elements> = Encoded_<E>

  /**
   * Union of all decoding service requirements needed by the tuple element
   * schemas.
   *
   * @category utility types
   * @since 4.0.0
   */
  export type DecodingServices<E extends Elements> = E[number]["DecodingServices"]

  /**
   * Union of all encoding service requirements needed by the tuple element
   * schemas.
   *
   * @category utility types
   * @since 4.0.0
   */
  export type EncodingServices<E extends Elements> = E[number]["EncodingServices"]

  type MakeIn_<
    E,
    Out extends ReadonlyArray<any> = readonly []
  > = E extends readonly [infer Head, ...infer Tail] ?
    Head extends { "~type.make": infer T } ?
      Head extends
        { readonly "~type.optionality": "optional" } | { readonly "~type.constructor.default": "with-default" } ?
        MakeIn_<Tail, readonly [...Out, T?]> :
      MakeIn_<Tail, readonly [...Out, T]>
    : Out :
    Out

  /**
   * Computes the input tuple type accepted when constructing a tuple value.
   *
   * **Details**
   *
   * Each element uses its `~type.make` input type. Optional elements and elements
   * with constructor defaults produce optional tuple positions.
   *
   * @category utility types
   * @since 4.0.0
   */
  export type MakeIn<E extends Elements> = MakeIn_<E>
}

/**
 * Type-level representation returned by {@link Tuple}.
 *
 * @category models
 * @since 3.10.0
 */
export interface Tuple<Elements extends Tuple.Elements> extends
  BottomLazy<
    SchemaAST.Arrays,
    Tuple<Elements>
  >
{
  readonly "Type": Tuple.Type<Elements>
  readonly "Encoded": Tuple.Encoded<Elements>
  readonly "DecodingServices": Tuple.DecodingServices<Elements>
  readonly "EncodingServices": Tuple.EncodingServices<Elements>
  readonly "~type.make.in": Tuple.MakeIn<Elements>
  readonly "~type.make": Tuple.MakeIn<Elements>
  readonly "Iso": Tuple.Iso<Elements>
  readonly elements: Elements
  /**
   * Returns a new tuple with the elements modified by the provided function.
   *
   * **Details**
   *
   * Options:
   *
   * - `unsafePreserveChecks` - if `true`, keep any `.check(...)` constraints
   *   that were attached to the original union. Defaults to `false`.
   *
   *   **Warning**: This is an unsafe operation. Since `mapFields`
   *   transformations change the schema type, the original refinement functions
   *   may no longer be valid or safe to apply to the transformed schema. Only
   *   use this option if you have verified that your refinements remain correct
   *   after the transformation.
   */
  mapElements<To extends Tuple.Elements>(
    f: (elements: Elements) => To,
    options?: {
      readonly unsafePreserveChecks?: boolean | undefined
    } | undefined
  ): Tuple<Simplify<Readonly<To>>>
}

function makeTuple<Elements extends Tuple.Elements>(ast: SchemaAST.Arrays, elements: Elements): Tuple<Elements> {
  return make(ast, {
    elements,
    mapElements<To extends Tuple.Elements>(
      this: Tuple<Elements>,
      f: (elements: Elements) => To,
      options?: {
        readonly unsafePreserveChecks?: boolean | undefined
      } | undefined
    ): Tuple<Simplify<Readonly<To>>> {
      const elements = f(this.elements)
      return makeTuple(SchemaAST.tuple(elements, options?.unsafePreserveChecks ? this.ast.checks : undefined), elements)
    }
  })
}

/**
 * Defines a fixed-length tuple schema from an array of element schemas.
 *
 * **Example** (Defining a pair of string and number)
 *
 * ```ts
 * import { Schema } from "effect"
 *
 * const schema = Schema.Tuple([Schema.String, Schema.Number])
 *
 * const pair = Schema.decodeUnknownSync(schema)(["hello", 42])
 * console.log(pair)
 * // [ 'hello', 42 ]
 * ```
 *
 * @category constructors
 * @since 3.10.0
 */
export function Tuple<const Elements extends ReadonlyArray<Constraint>>(elements: Elements): Tuple<Elements> {
  return makeTuple(SchemaAST.tuple(elements), elements)
}

/**
 * Namespace for `TupleWithRest` type utilities.
 *
 * **Details**
 *
 * - `TupleWithRest.TupleType` — constraint for the leading tuple schema
 * - `TupleWithRest.Rest` — the rest element schema(s)
 * - `TupleWithRest.Type<T, R>` — decoded type (fixed elements + rest)
 * - `TupleWithRest.Encoded<T, R>` — encoded type
 *
 * @since 4.0.0
 */
export declare namespace TupleWithRest {
  /**
   * Constraint for tuple-like schemas that can be used as the fixed leading
   * portion of a `TupleWithRest` schema.
   *
   * @category utility types
   * @since 3.10.0
   */
  export type TupleType = Constraint & {
    readonly Type: ReadonlyArray<unknown>
    readonly Encoded: ReadonlyArray<unknown>
    readonly ast: SchemaAST.Arrays
    readonly "~type.make": ReadonlyArray<unknown>
    readonly "Iso": ReadonlyArray<unknown>
  }

  /**
   * Non-empty list of schemas used for the rest portion of a `TupleWithRest`.
   *
   * **Details**
   *
   * The first schema describes the repeated rest element. Additional schemas, when
   * present, describe trailing tuple elements after the repeated rest segment.
   *
   * @category utility types
   * @since 3.10.0
   */
  export type Rest = readonly [Constraint, ...Array<Constraint>]

  /**
   * Computes the decoded tuple type for a `TupleWithRest`.
   *
   * **Details**
   *
   * The output starts with the fixed tuple elements, continues with zero or more
   * values decoded by the first rest schema, and includes any trailing rest schemas
   * as fixed tuple positions.
   *
   * @category utility types
   * @since 3.10.0
   */
  export type Type<T extends ReadonlyArray<unknown>, Rest extends TupleWithRest.Rest> = Rest extends
    readonly [infer Head extends Constraint, ...infer Tail extends ReadonlyArray<Constraint>] ? Readonly<[
      ...T,
      ...Array<Head["Type"]>,
      ...{ readonly [K in keyof Tail]: Tail[K]["Type"] }
    ]> :
    T

  /**
   * Computes the iso tuple type for a `TupleWithRest`.
   *
   * **Details**
   *
   * The output starts with the fixed tuple's `Iso` elements, continues with zero
   * or more values using the first rest schema's `Iso`, and includes any trailing
   * rest schemas as fixed tuple positions.
   *
   * @category utility types
   * @since 4.0.0
   */
  export type Iso<T extends ReadonlyArray<unknown>, Rest extends TupleWithRest.Rest> = Rest extends
    readonly [infer Head extends Constraint, ...infer Tail extends ReadonlyArray<Constraint>] ? Readonly<[
      ...T,
      ...Array<Head["Iso"]>,
      ...{ readonly [K in keyof Tail]: Tail[K]["Iso"] }
    ]> :
    T

  /**
   * Computes the encoded tuple type for `TupleWithRest`.
   *
   * **Details**
   *
   * The leading tuple's encoded elements are kept first. The encoded type of the
   * first rest schema may repeat zero or more times, and the encoded types of any
   * additional rest schemas become required trailing tuple elements.
   *
   * @category utility types
   * @since 3.10.0
   */
  export type Encoded<E extends ReadonlyArray<unknown>, Rest extends TupleWithRest.Rest> = Rest extends
    readonly [infer Head extends Constraint, ...infer Tail extends ReadonlyArray<Constraint>] ? readonly [
      ...E,
      ...Array<Head["Encoded"]>,
      ...{ readonly [K in keyof Tail]: Tail[K]["Encoded"] }
    ] :
    E

  /**
   * Computes the constructor input tuple type for `TupleWithRest`.
   *
   * **Details**
   *
   * The leading tuple's make input elements are kept first. The make input type of
   * the first rest schema may repeat zero or more times, and the make input types
   * of any additional rest schemas become required trailing tuple elements.
   *
   * @category utility types
   * @since 4.0.0
   */
  export type MakeIn<M extends ReadonlyArray<unknown>, Rest extends TupleWithRest.Rest> = Rest extends
    readonly [infer Head extends Constraint, ...infer Tail extends ReadonlyArray<Constraint>] ? readonly [
      ...M,
      ...Array<Head["~type.make"]>,
      ...{ readonly [K in keyof Tail]: Tail[K]["~type.make"] }
    ] :
    M
}

/**
 * Type-level representation returned by {@link TupleWithRest}.
 *
 * @category models
 * @since 4.0.0
 */
export interface TupleWithRest<
  S extends TupleWithRest.TupleType,
  Rest extends TupleWithRest.Rest
> extends
  BottomLazy<
    SchemaAST.Arrays,
    TupleWithRest<S, Rest>
  >
{
  readonly "Type": TupleWithRest.Type<S["Type"], Rest>
  readonly "Encoded": TupleWithRest.Encoded<S["Encoded"], Rest>
  readonly "DecodingServices": S["DecodingServices"] | Rest[number]["DecodingServices"]
  readonly "EncodingServices": S["EncodingServices"] | Rest[number]["EncodingServices"]
  readonly "~type.make.in": TupleWithRest.MakeIn<S["~type.make"], Rest>
  readonly "~type.make": TupleWithRest.MakeIn<S["~type.make"], Rest>
  readonly "Iso": TupleWithRest.Iso<S["Iso"], Rest>
  readonly schema: S
  readonly rest: Rest
}

/**
 * Extends a fixed-length tuple schema with a variadic rest segment.
 *
 * **Details**
 *
 * The resulting tuple starts with the fixed elements from `schema`. The first
 * schema in `rest` is the repeatable element schema, and any additional schemas
 * in `rest` are required trailing tuple elements after the variadic segment. For
 * example, `[Schema.Boolean, Schema.String]` represents zero or more booleans
 * followed by a final string.
 *
 * **Example** (Defining tuples with rest elements)
 *
 * ```ts
 * import { Schema } from "effect"
 *
 * // [string, number, ...boolean[]]
 * const schema = Schema.TupleWithRest(
 *   Schema.Tuple([Schema.String, Schema.Number]),
 *   [Schema.Boolean]
 * )
 *
 * const result = Schema.decodeUnknownSync(schema)(["hello", 1, true, false])
 * console.log(result)
 * // [ 'hello', 1, true, false ]
 * ```
 *
 * @category constructors
 * @since 4.0.0
 */
export function TupleWithRest<S extends Tuple<Tuple.Elements>, const Rest extends TupleWithRest.Rest>(
  schema: S,
  rest: Rest
): TupleWithRest<S, Rest> {
  return make(SchemaAST.tupleWithRest(schema.ast, rest.map(SchemaAST.getAST)), { schema, rest })
}

/**
 * Type-level representation returned by {@link Array}.
 *
 * @category models
 * @since 4.0.0
 */
export interface $Array<S extends Constraint> extends
  BottomLazy<
    SchemaAST.Arrays,
    $Array<S>
  >
{
  readonly "Type": ReadonlyArray<S["Type"]>
  readonly "Encoded": ReadonlyArray<S["Encoded"]>
  readonly "DecodingServices": S["DecodingServices"]
  readonly "EncodingServices": S["EncodingServices"]
  readonly "~type.make.in": ReadonlyArray<S["~type.make"]>
  readonly "~type.make": ReadonlyArray<S["~type.make"]>
  readonly "Iso": ReadonlyArray<S["Iso"]>
  readonly value: S
}

interface ArrayLambda extends Lambda {
  <S extends Constraint>(self: S): $Array<S>
  readonly "~lambda.out": this["~lambda.in"] extends Constraint ? $Array<this["~lambda.in"]> : never
}

/**
 * @category constructors
 * @since 4.0.0
 */
const ArraySchema = Struct_.lambda<ArrayLambda>((schema) =>
  make(new SchemaAST.Arrays(false, [], [schema.ast]), { value: schema })
)

export {
  /**
   * Defines a `ReadonlyArray` schema for a given element schema.
   *
   * **Example** (Defining an array of strings)
   *
   * ```ts
   * import { Schema } from "effect"
   *
   * const schema = Schema.Array(Schema.String)
   *
   * const result = Schema.decodeUnknownSync(schema)(["a", "b", "c"])
   * console.log(result)
   * // [ 'a', 'b', 'c' ]
   * ```
   *
   * @category constructors
   * @since 4.0.0
   */
  ArraySchema as Array
}

/**
 * Type-level representation returned by {@link NonEmptyArray}.
 *
 * @category models
 * @since 3.10.0
 */
export interface NonEmptyArray<S extends Constraint> extends
  BottomLazy<
    SchemaAST.Arrays,
    NonEmptyArray<S>
  >
{
  readonly "Type": readonly [S["Type"], ...Array<S["Type"]>]
  readonly "Encoded": readonly [S["Encoded"], ...Array<S["Encoded"]>]
  readonly "DecodingServices": S["DecodingServices"]
  readonly "EncodingServices": S["EncodingServices"]
  readonly "~type.make.in": readonly [S["~type.make"], ...Array<S["~type.make"]>]
  readonly "~type.make": readonly [S["~type.make"], ...Array<S["~type.make"]>]
  readonly "Iso": readonly [S["Iso"], ...Array<S["Iso"]>]
  readonly value: S
}

interface NonEmptyArrayLambda extends Lambda {
  <S extends Constraint>(self: S): NonEmptyArray<S>
  readonly "~lambda.out": this["~lambda.in"] extends Constraint ? NonEmptyArray<this["~lambda.in"]> : never
}

/**
 * Defines a non-empty `ReadonlyArray` schema — at least one element required.
 * Type is `readonly [T, ...T[]]`.
 *
 * **Example** (Defining a non-empty array of numbers)
 *
 * ```ts
 * import { Schema } from "effect"
 *
 * const schema = Schema.NonEmptyArray(Schema.Number)
 *
 * Schema.decodeUnknownSync(schema)([1, 2, 3])  // ok
 * Schema.decodeUnknownSync(schema)([])          // throws
 * ```
 *
 * @category constructors
 * @since 3.10.0
 */
export const NonEmptyArray = Struct_.lambda<NonEmptyArrayLambda>((schema) =>
  make(new SchemaAST.Arrays(false, [schema.ast], [schema.ast]), { value: schema })
)

/**
 * Type-level representation returned by {@link ArrayEnsure}.
 *
 * @category constructors
 * @since 3.10.0
 */
export interface ArrayEnsure<S extends Constraint> extends decodeTo<$Array<toType<S>>, Union<readonly [S, $Array<S>]>> {
  readonly "Rebuild": ArrayEnsure<S>
}

/**
 * Creates a schema that accepts either a value decoded by `schema` or an array
 * decoded by `Schema.Array(schema)`, then returns an array.
 *
 * **When to use**
 *
 * Use to accept input that may be provided either as one item or as an array,
 * while normalizing decoded values to a readonly array.
 *
 * **Details**
 *
 * During encoding, one-element arrays are encoded as the single element. Empty
 * arrays and arrays with two or more elements are encoded as arrays.
 *
 * **Gotchas**
 *
 * The single-value branch is tried before the array branch. If `schema` itself
 * accepts arrays, an array input can be treated as one value and wrapped in a
 * one-element array.
 *
 * @see {@link Array} for accepting only array input
 * @see {@link NonEmptyArray} for requiring at least one decoded element
 *
 * @category constructors
 * @since 3.10.0
 */
export function ArrayEnsure<S extends Constraint>(schema: S): ArrayEnsure<S> {
  return Union([schema, ArraySchema(schema)]).pipe(decodeTo(
    ArraySchema(toType(schema)),
    SchemaTransformation.transform({
      decode: Arr.ensure,
      encode: (array) => array.length === 1 ? array[0] : array
    })
  ))
}

/**
 * Type-level representation returned by {@link UniqueArray}.
 *
 * @category models
 * @since 4.0.0
 */
export interface UniqueArray<S extends Constraint> extends $Array<S> {
  readonly "Rebuild": UniqueArray<S>
}

/**
 * Returns a new array schema that ensures all elements are unique.
 *
 * **Details**
 *
 * The equivalence used to determine uniqueness is the one provided by
 * `Schema.toEquivalence(item)`.
 *
 * @category constructors
 * @since 4.0.0
 */
export function UniqueArray<S extends Constraint>(item: S): UniqueArray<S> {
  return ArraySchema(item).check(isUnique())
}

/**
 * Type-level representation returned by {@link mutable}.
 *
 * @category transforming
 * @since 3.10.0
 */
export interface mutable<S extends Constraint & { readonly "ast": SchemaAST.Arrays }> extends
  BottomLazy<
    S["ast"],
    mutable<S>,
    S["~type.parameters"],
    S["~type.mutability"],
    S["~type.optionality"],
    S["~type.constructor.default"],
    S["~encoded.mutability"],
    S["~encoded.optionality"]
  >
{
  readonly "Type": Mutable<S["Type"]>
  readonly "Encoded": Mutable<S["Encoded"]>
  readonly "DecodingServices": S["DecodingServices"]
  readonly "EncodingServices": S["EncodingServices"]
  // "~type.make" and "~type.make.in" as they are because they are contravariant
  readonly "~type.make.in": S["~type.make.in"]
  readonly "~type.make": S["~type.make"]
  readonly "Iso": S["Iso"]
  readonly schema: S
}

interface mutableLambda extends Lambda {
  <S extends Constraint & { readonly "ast": SchemaAST.Arrays }>(self: S): mutable<S>
  readonly "~lambda.out": this["~lambda.in"] extends Constraint & { readonly "ast": SchemaAST.Arrays } ?
    mutable<this["~lambda.in"]>
    : "Error: schema not eligible for mutable"
}

/**
 * Makes an array or tuple schema mutable, removing the `readonly` modifier.
 *
 * **Example** (Defining mutable arrays)
 *
 * ```ts
 * import { Schema } from "effect"
 *
 * const schema = Schema.mutable(Schema.Array(Schema.Number))
 *
 * // number[]   (mutable)
 * type T = typeof schema.Type
 * ```
 *
 * @category transforming
 * @since 3.10.0
 */
export const mutable = Struct_.lambda<mutableLambda>((schema) => {
  return make(new SchemaAST.Arrays(true, schema.ast.elements, schema.ast.rest), { schema })
})

/**
 * Type-level representation returned by {@link Union}.
 *
 * @category models
 * @since 3.10.0
 */
export interface Union<Members extends ReadonlyArray<Constraint>> extends
  BottomLazy<
    SchemaAST.Union<{ [K in keyof Members]: Members[K]["ast"] }[number]>,
    Union<Members>
  >
{
  readonly "Type": { [K in keyof Members]: Members[K]["Type"] }[number]
  readonly "Encoded": { [K in keyof Members]: Members[K]["Encoded"] }[number]
  readonly "DecodingServices": { [K in keyof Members]: Members[K]["DecodingServices"] }[number]
  readonly "EncodingServices": { [K in keyof Members]: Members[K]["EncodingServices"] }[number]
  readonly "~type.make.in": { [K in keyof Members]: Members[K]["~type.make"] }[number]
  readonly "~type.make": { [K in keyof Members]: Members[K]["~type.make"] }[number]
  readonly "Iso": { [K in keyof Members]: Members[K]["Iso"] }[number]
  readonly members: Members
  /**
   * Returns a new union with the members modified by the provided function.
   *
   * **Details**
   *
   * Options:
   *
   * - `unsafePreserveChecks` - if `true`, keep any `.check(...)` constraints
   *   that were attached to the original union. Defaults to `false`.
   *
   *   **Warning**: This is an unsafe operation. Since `mapFields`
   *   transformations change the schema type, the original refinement functions
   *   may no longer be valid or safe to apply to the transformed schema. Only
   *   use this option if you have verified that your refinements remain correct
   *   after the transformation.
   */
  mapMembers<To extends ReadonlyArray<Constraint>>(
    f: (members: Members) => To,
    options?: {
      readonly unsafePreserveChecks?: boolean | undefined
    } | undefined
  ): Union<Simplify<Readonly<To>>>
}

function makeUnion<Members extends ReadonlyArray<Constraint>>(
  ast: SchemaAST.Union<Members[number]["ast"]>,
  members: Members
): Union<Members> {
  return make(ast, {
    members,
    mapMembers<To extends ReadonlyArray<Constraint>>(
      this: Union<Members>,
      f: (members: Members) => To,
      options?: {
        readonly unsafePreserveChecks?: boolean | undefined
      } | undefined
    ): Union<Simplify<Readonly<To>>> {
      const members = f(this.members)
      return makeUnion(
        SchemaAST.union(members, this.ast.mode, options?.unsafePreserveChecks ? this.ast.checks : undefined),
        members
      )
    }
  })
}

/**
 * Creates a union schema from an array of member schemas. Members are tested in
 * order; the first match is returned.
 *
 * **Details**
 *
 * Optionally, specify `mode`:
 * - `"anyOf"` (default) — matches if any member matches.
 * - `"oneOf"` — matches if exactly one member matches.
 *
 * **Example** (Defining a string or number union)
 *
 * ```ts
 * import { Schema } from "effect"
 *
 * const schema = Schema.Union([Schema.String, Schema.Number])
 *
 * Schema.decodeUnknownSync(schema)("hello") // "hello"
 * Schema.decodeUnknownSync(schema)(42)       // 42
 * ```
 *
 * @category constructors
 * @since 3.10.0
 */
export function Union<const Members extends ReadonlyArray<Constraint>>(
  members: Members,
  options?: { mode?: "anyOf" | "oneOf" }
): Union<Members> {
  return makeUnion(SchemaAST.union(members, options?.mode ?? "anyOf", undefined), members)
}

/**
 * Type-level representation returned by {@link Literals}.
 *
 * @category models
 * @since 4.0.0
 */
export interface Literals<L extends ReadonlyArray<SchemaAST.LiteralValue>>
  extends Bottom<L[number], L[number], never, never, SchemaAST.Union<SchemaAST.Literal>, Literals<L>>
{
  readonly literals: L
  readonly members: { readonly [K in keyof L]: Literal<L[K]> }
  /**
   * Map over the members of the union.
   */
  mapMembers<To extends ReadonlyArray<Constraint>>(f: (members: this["members"]) => To): Union<Simplify<Readonly<To>>>

  pick<const L2 extends ReadonlyArray<L[number]>>(literals: L2): Literals<L2>

  transform<const L2 extends { readonly [I in keyof L]: SchemaAST.LiteralValue }>(
    to: L2
  ): Union<{ [I in keyof L]: decodeTo<Literal<L2[I]>, Literal<L[I]>> }>
}

/**
 * Creates a union schema from an array of literal values.
 *
 * **Example** (Defining status codes)
 *
 * ```ts
 * import { Schema } from "effect"
 *
 * const schema = Schema.Literals(["active", "inactive", "pending"])
 * // accepts "active", "inactive", or "pending"
 * ```
 *
 * @see {@link Literal} for a schema that represents a single literal.
 * @category constructors
 * @since 4.0.0
 */
export function Literals<const L extends ReadonlyArray<SchemaAST.LiteralValue>>(literals: L): Literals<L> {
  const members = literals.map(Literal) as { readonly [K in keyof L]: Literal<L[K]> }
  return make(SchemaAST.union(members, "anyOf", undefined), {
    literals,
    members,
    mapMembers<To extends ReadonlyArray<Constraint>>(
      this: Literals<L>,
      f: (members: Literals<L>["members"]) => To
    ): Union<Simplify<Readonly<To>>> {
      return Union(f(this.members))
    },
    pick<const L2 extends ReadonlyArray<L[number]>>(literals: L2): Literals<L2> {
      return Literals(literals)
    },
    transform<const L2 extends { readonly [I in keyof L]: SchemaAST.LiteralValue }>(
      to: L2
    ): Union<{ [I in keyof L]: decodeTo<Literal<L2[I]>, Literal<L[I]>> }> {
      return Union(members.map((member, index) => member.transform(to[index]))) as any
    }
  })
}

/**
 * Type-level representation returned by {@link NullOr}.
 *
 * @category models
 * @since 3.10.0
 */
export interface NullOr<S extends Constraint> extends Union<readonly [S, Null]> {
  readonly "Rebuild": NullOr<S>
}

interface NullOrLambda extends Lambda {
  <S extends Constraint>(self: S): NullOr<S>
  readonly "~lambda.out": this["~lambda.in"] extends Constraint ? NullOr<this["~lambda.in"]> : never
}

/**
 * Creates a union schema of `S | null`.
 *
 * @category constructors
 * @since 3.10.0
 */
export const NullOr = Struct_.lambda<NullOrLambda>((self) => Union([self, Null]))

/**
 * Type-level representation returned by {@link UndefinedOr}.
 *
 * @category models
 * @since 3.10.0
 */
export interface UndefinedOr<S extends Constraint> extends Union<readonly [S, Undefined]> {
  readonly "Rebuild": UndefinedOr<S>
}

interface UndefinedOrLambda extends Lambda {
  <S extends Constraint>(self: S): UndefinedOr<S>
  readonly "~lambda.out": this["~lambda.in"] extends Constraint ? UndefinedOr<this["~lambda.in"]> : never
}

/**
 * Creates a union schema of `S | undefined`.
 *
 * @category constructors
 * @since 3.10.0
 */
export const UndefinedOr = Struct_.lambda<UndefinedOrLambda>((self) => Union([self, Undefined]))

/**
 * Type-level representation returned by {@link NullishOr}.
 *
 * @category models
 * @since 3.10.0
 */
export interface NullishOr<S extends Constraint> extends Union<readonly [S, Null, Undefined]> {
  readonly "Rebuild": NullishOr<S>
}

interface NullishOrLambda extends Lambda {
  <S extends Constraint>(self: S): NullishOr<S>
  readonly "~lambda.out": this["~lambda.in"] extends Constraint ? NullishOr<this["~lambda.in"]> : never
}

/**
 * Creates a union schema of `S | null | undefined`.
 *
 * @category constructors
 * @since 3.10.0
 */
export const NullishOr = Struct_.lambda<NullishOrLambda>((self) => Union([self, Null, Undefined]))

/**
 * Type-level representation returned by {@link suspend}.
 *
 * @category models
 * @since 3.10.0
 */
export interface suspend<S extends Constraint> extends
  BottomLazy<
    SchemaAST.Suspend,
    suspend<S>,
    S["~type.parameters"],
    S["~type.mutability"],
    S["~type.optionality"],
    S["~type.constructor.default"],
    S["~encoded.mutability"],
    S["~encoded.optionality"]
  >
{
  readonly "Type": S["Type"]
  readonly "Encoded": S["Encoded"]
  readonly "DecodingServices": S["DecodingServices"]
  readonly "EncodingServices": S["EncodingServices"]
  readonly "~type.make.in": S["~type.make.in"]
  readonly "~type.make": S["~type.make"]
  readonly "Iso": S["Iso"]
}

/**
 * Creates a suspended schema that defers evaluation until needed. This is
 * essential for creating recursive schemas where a schema references itself,
 * preventing infinite recursion during schema definition.
 *
 * **Example** (Defining recursive tree schemas)
 *
 * ```ts
 * import { Schema } from "effect"
 *
 * interface Tree {
 *   readonly value: number
 *   readonly children: ReadonlyArray<Tree>
 * }
 *
 * const Tree = Schema.Struct({
 *   value: Schema.Number,
 *   children: Schema.Array(Schema.suspend((): Schema.Codec<Tree> => Tree))
 * })
 * ```
 *
 * @category constructors
 * @since 3.10.0
 */
export function suspend<S extends Constraint>(f: () => S): suspend<S> {
  return make(new SchemaAST.Suspend(() => f().ast))
}

/**
 * Attaches one or more filter checks to a schema without changing the
 * TypeScript type.
 *
 * **Example** (Adding checks to a schema)
 *
 * ```ts
 * import { Schema } from "effect"
 *
 * const AgeSchema = Schema.Number.pipe(
 *   Schema.check(Schema.isGreaterThanOrEqualTo(0), Schema.isLessThanOrEqualTo(120))
 * )
 * ```
 *
 * @category filtering
 * @since 4.0.0
 */
export function check<S extends Top>(
  ...checks: readonly [SchemaAST.Check<S["Type"]>, ...Array<SchemaAST.Check<S["Type"]>>]
) {
  return (self: S): S["Rebuild"] => self.check(...checks)
}

/**
 * Type-level representation returned by {@link refine}.
 *
 * @category filtering
 * @since 3.10.0
 */
export interface refine<T extends S["Type"], S extends Constraint> extends
  BottomLazy<
    S["ast"],
    refine<T, S>,
    S["~type.parameters"],
    S["~type.mutability"],
    S["~type.optionality"],
    S["~type.constructor.default"],
    S["~encoded.mutability"],
    S["~encoded.optionality"]
  >
{
  readonly "Type": T
  readonly "Encoded": S["Encoded"]
  readonly "DecodingServices": S["DecodingServices"]
  readonly "EncodingServices": S["EncodingServices"]
  readonly "~type.make.in": S["~type.make.in"]
  readonly "~type.make": T
  readonly "Iso": T
  readonly schema: S
}

/**
 * Narrows the TypeScript type of a schema's output via a type guard predicate,
 * attaching the guard as a runtime filter check.
 *
 * **Details**
 *
 * The `annotations` parameter annotates the filter created by the refinement.
 * With the default formatter, failed refinements use `message` first,
 * `expected` second, and `<filter>` when neither is provided. `identifier`
 * names type-level failures before the refinement runs; it does not name the
 * failed refinement itself.
 *
 * @category filtering
 * @since 3.10.0
 */
export function refine<S extends Constraint, T extends S["Type"]>(
  refinement: (value: S["Type"]) => value is T,
  annotations?: Annotations.Filter
) {
  return (schema: S): refine<T, S> =>
    make(SchemaAST.appendChecks(schema.ast, [SchemaAST.makeFilterByGuard(refinement, annotations)]), { schema })
}

type DistributeBrands<B> = UnionToIntersection<B extends infer U extends string ? Brand.Brand<U> : never>

/**
 * Type-level representation returned by {@link brand}.
 *
 * @category branding
 * @since 3.10.0
 */
export interface brand<S extends Constraint, B> extends
  BottomLazy<
    S["ast"],
    brand<S, B>,
    S["~type.parameters"],
    S["~type.mutability"],
    S["~type.optionality"],
    S["~type.constructor.default"],
    S["~encoded.mutability"],
    S["~encoded.optionality"]
  >
{
  readonly "Type": S["Type"] & DistributeBrands<B>
  readonly "Encoded": S["Encoded"]
  readonly "DecodingServices": S["DecodingServices"]
  readonly "EncodingServices": S["EncodingServices"]
  readonly "~type.make.in": S["~type.make.in"]
  readonly "~type.make": S["Type"] & DistributeBrands<B>
  readonly "Iso": S["Type"] & DistributeBrands<B>
  readonly schema: S
  readonly identifier: string
}

/**
 * Adds a nominal brand to a schema, intersecting the output type with
 * `Brand.Brand<B>` to prevent accidental mixing of structurally identical types.
 *
 * **When to use**
 *
 * Use to make values decoded by an existing schema nominally distinct when the
 * schema already carries the runtime validation you need.
 *
 * **Gotchas**
 *
 * `brand` adds brand metadata and narrows the TypeScript output type, but it
 * does not add runtime checks.
 *
 * @see {@link fromBrand} for applying a Brand constructor's checks along with the brand tag
 *
 * @category branding
 * @since 3.10.0
 */
export function brand<B extends string>(identifier: B) {
  return <S extends ConstraintRebuildable>(schema: S): brand<S["Rebuild"], B> =>
    make(SchemaAST.brand(schema.ast, identifier), { schema, identifier })
}

/**
 * Creates a branded schema from a {@link Brand.Constructor}, applying the
 * constructor's checks and brand tag to the underlying schema.
 *
 * @category branding
 * @since 3.10.0
 */
export function fromBrand<A extends Brand.Brand<any>>(identifier: string, ctor: Brand.Constructor<A>) {
  return <S extends Top & { readonly "Type": Brand.Brand.Unbranded<A> }>(
    self: S
  ): brand<S["Rebuild"], Brand.Brand.Keys<A>> => {
    return (ctor.checks ? self.check(...ctor.checks) : self).pipe(brand(identifier))
  }
}

/**
 * Type-level representation returned by {@link middlewareDecoding}.
 *
 * @category decoding
 * @since 4.0.0
 */
export interface middlewareDecoding<S extends Constraint, RD> extends
  BottomLazy<
    S["ast"],
    middlewareDecoding<S, RD>,
    S["~type.parameters"],
    S["~type.mutability"],
    S["~type.optionality"],
    S["~type.constructor.default"],
    S["~encoded.mutability"],
    S["~encoded.optionality"]
  >
{
  readonly "Type": S["Type"]
  readonly "Encoded": S["Encoded"]
  readonly "DecodingServices": RD
  readonly "EncodingServices": S["EncodingServices"]
  readonly "~type.make.in": S["~type.make.in"]
  readonly "~type.make": S["~type.make"]
  readonly "Iso": S["Iso"]
  readonly schema: S
}

/**
 * Intercepts the decoding pipeline of a schema.
 *
 * **Details**
 *
 * The provided function receives the current decoding `Effect` and `ParseOptions`,
 * and returns a new `Effect` — potentially adding service requirements (`RD`),
 * recovering from errors, or augmenting the result.
 *
 * **Example** (Logging decode failures)
 *
 * ```ts
 * import { Effect, Schema } from "effect"
 *
 * const Logged = Schema.String.pipe(
 *   Schema.middlewareDecoding((effect) =>
 *     Effect.tapError(effect, (issue) => Effect.log("decode failed", issue))
 *   )
 * )
 * ```
 *
 * @see {@link catchDecoding} for a simpler error-recovery variant
 * @category decoding
 * @since 4.0.0
 */
export function middlewareDecoding<S extends Constraint, RD>(
  decode: (
    effect: Effect.Effect<Option_.Option<S["Type"]>, SchemaIssue.Issue, S["DecodingServices"]>,
    options: SchemaAST.ParseOptions
  ) => Effect.Effect<Option_.Option<S["Type"]>, SchemaIssue.Issue, RD>
) {
  return (schema: S): middlewareDecoding<S, RD> =>
    make(
      SchemaAST.middlewareDecoding(schema.ast, new SchemaTransformation.Middleware(decode, identity)),
      { schema }
    )
}

/**
 * Type-level representation returned by {@link middlewareEncoding}.
 *
 * @category encoding
 * @since 4.0.0
 */
export interface middlewareEncoding<S extends Constraint, RE> extends
  BottomLazy<
    S["ast"],
    middlewareEncoding<S, RE>,
    S["~type.parameters"],
    S["~type.mutability"],
    S["~type.optionality"],
    S["~type.constructor.default"],
    S["~encoded.mutability"],
    S["~encoded.optionality"]
  >
{
  readonly "Type": S["Type"]
  readonly "Encoded": S["Encoded"]
  readonly "DecodingServices": S["DecodingServices"]
  readonly "EncodingServices": RE
  readonly "~type.make.in": S["~type.make.in"]
  readonly "~type.make": S["~type.make"]
  readonly "Iso": S["Iso"]
  readonly schema: S
}

/**
 * Intercepts the encoding pipeline of a schema.
 *
 * **Details**
 *
 * The provided function receives the current encoding `Effect` and `ParseOptions`,
 * and returns a new `Effect` — potentially adding service requirements (`RE`),
 * recovering from errors, or augmenting the result.
 *
 * **Example** (Logging encode failures)
 *
 * ```ts
 * import { Effect, Schema } from "effect"
 *
 * const Logged = Schema.String.pipe(
 *   Schema.middlewareEncoding((effect) =>
 *     Effect.tapError(effect, (issue) => Effect.log("encode failed", issue))
 *   )
 * )
 * ```
 *
 * @see {@link catchEncoding} for a simpler error-recovery variant
 * @category encoding
 * @since 4.0.0
 */
export function middlewareEncoding<S extends Constraint, RE>(
  encode: (
    effect: Effect.Effect<Option_.Option<S["Encoded"]>, SchemaIssue.Issue, S["EncodingServices"]>,
    options: SchemaAST.ParseOptions
  ) => Effect.Effect<Option_.Option<S["Encoded"]>, SchemaIssue.Issue, RE>
) {
  return (schema: S): middlewareEncoding<S, RE> =>
    make(
      SchemaAST.middlewareEncoding(schema.ast, new SchemaTransformation.Middleware(identity, encode)),
      { schema }
    )
}

/**
 * Recovers from a decoding error by providing a fallback value.
 *
 * **Details**
 *
 * The handler receives the `Issue` and returns an `Effect` that either
 * succeeds with a fallback value or re-fails with a (possibly different) issue.
 *
 * **Example** (Returning a default on decode failure)
 *
 * ```ts
 * import { Effect, Option, Schema } from "effect"
 *
 * const schema = Schema.Number.pipe(
 *   Schema.catchDecoding((_issue) => Effect.succeed(Option.some(0)))
 * )
 * ```
 *
 * @see {@link catchDecodingWithContext} to add service requirements to the handler
 * @category error handling
 * @since 4.0.0
 */
export function catchDecoding<S extends Constraint>(
  f: (issue: SchemaIssue.Issue) => Effect.Effect<Option_.Option<S["Type"]>, SchemaIssue.Issue>
): (self: S) => middlewareDecoding<S, S["DecodingServices"]> {
  return catchDecodingWithContext(f)
}

/**
 * Recovers from a decoding error with a handler that may require Effect services.
 *
 * **When to use**
 *
 * Use when you need decoding fallback logic to require services from the Effect
 * context.
 *
 * **Details**
 *
 * The handler receives the `Issue` and returns an `Effect` that either succeeds
 * with a fallback value or re-fails with a (possibly different) issue. The
 * handler's services are added to the schema's decoding services.
 *
 * @see {@link catchDecoding} for recovery handlers that do not require services
 * @see {@link middlewareDecoding} for intercepting or replacing the full decoding pipeline
 *
 * @category error handling
 * @since 4.0.0
 */
export function catchDecodingWithContext<S extends Constraint, R = never>(
  f: (issue: SchemaIssue.Issue) => Effect.Effect<Option_.Option<S["Type"]>, SchemaIssue.Issue, R>
) {
  return (self: S): middlewareDecoding<S, S["DecodingServices"] | R> =>
    middlewareDecoding<S, S["DecodingServices"] | R>(Effect.catchEager(f))(self)
}

/**
 * Recovers from an encoding error by providing a fallback value.
 *
 * **Details**
 *
 * The handler receives the `Issue` and returns an `Effect` that either
 * succeeds with a fallback value or re-fails with a (possibly different) issue.
 *
 * @see {@link catchEncodingWithContext} to add service requirements to the handler
 * @category error handling
 * @since 4.0.0
 */
export function catchEncoding<S extends Constraint>(
  f: (issue: SchemaIssue.Issue) => Effect.Effect<Option_.Option<S["Encoded"]>, SchemaIssue.Issue>
): (self: S) => middlewareEncoding<S, S["EncodingServices"]> {
  return catchEncodingWithContext(f)
}

/**
 * Recovers from an encoding error with a handler that may require Effect services.
 *
 * **When to use**
 *
 * Use when you need encoding fallback logic to require services from the Effect
 * context.
 *
 * **Details**
 *
 * The handler receives the `Issue` and returns an `Effect` that either succeeds
 * with a fallback encoded value or re-fails with a (possibly different) issue.
 * The handler's services are added to the schema's encoding services.
 *
 * @see {@link catchEncoding} for recovery handlers that do not require services
 * @see {@link middlewareEncoding} for intercepting or replacing the full encoding pipeline
 *
 * @category error handling
 * @since 4.0.0
 */
export function catchEncodingWithContext<S extends Constraint, R = never>(
  f: (issue: SchemaIssue.Issue) => Effect.Effect<Option_.Option<S["Encoded"]>, SchemaIssue.Issue, R>
) {
  return (self: S): middlewareEncoding<S, S["EncodingServices"] | R> =>
    middlewareEncoding<S, S["EncodingServices"] | R>(Effect.catchEager(f))(self)
}

/**
 * Type-level representation returned by {@link decodeTo}.
 *
 * @category transforming
 * @since 4.0.0
 */
export interface decodeTo<To extends Constraint, From extends Constraint, RD = never, RE = never> extends
  BottomLazy<
    To["ast"],
    decodeTo<To, From, RD, RE>,
    To["~type.parameters"],
    To["~type.mutability"],
    To["~type.optionality"],
    To["~type.constructor.default"],
    From["~encoded.mutability"],
    From["~encoded.optionality"]
  >
{
  readonly "Type": To["Type"]
  readonly "Encoded": From["Encoded"]
  readonly "DecodingServices": To["DecodingServices"] | From["DecodingServices"] | RD
  readonly "EncodingServices": To["EncodingServices"] | From["EncodingServices"] | RE
  readonly "~type.make.in": To["~type.make.in"]
  readonly "~type.make": To["~type.make"]
  readonly "Iso": To["Iso"]
  readonly from: From
  readonly to: To
}

/**
 * Type-level representation returned by {@link decodeTo} without a custom transformation.
 *
 * @category transforming
 * @since 3.10.0
 */
export interface compose<To extends Constraint, From extends Constraint> extends decodeTo<To, From> {}

/**
 * Creates a schema that transforms from a source schema to a target schema.
 *
 * **When to use**
 *
 * Use when decoding should change the schema's decoded type or encoded shape,
 * with an optional custom bidirectional transformation.
 *
 * **Details**
 *
 * Call it with the target schema `to` and then pipe the source schema `from`
 * into the returned function. The resulting schema decodes from
 * `From["Encoded"]` to `To["Type"]` and encodes from `To["Type"]` back to
 * `From["Encoded"]`.
 *
 * When no transformation is provided, `SchemaTransformation.passthrough()` is
 * used, so `From["Type"]` must already be compatible with `To["Encoded"]`.
 * The resulting schema combines decoding and encoding services from both
 * schemas and any custom transformation.
 *
 * **Gotchas**
 *
 * In a custom transformation, `decode` maps `From["Type"]` to `To["Encoded"]`
 * and is used on the encoding path, while `encode` maps `To["Encoded"]` to
 * `From["Type"]` and is used on the decoding path.
 *
 * **Example** (Transforming strings to numbers with a schema transformation)
 *
 * ```ts
 * import { Schema, SchemaGetter } from "effect"
 *
 * const NumberFromString = Schema.String.pipe(
 *   Schema.decodeTo(
 *     Schema.Number,
 *     {
 *       decode: SchemaGetter.transform((s) => Number(s)),
 *       encode: SchemaGetter.transform((n) => String(n))
 *     }
 *   )
 * )
 *
 * const result = Schema.decodeUnknownSync(NumberFromString)("123")
 * // result: 123
 * ```
 *
 * @category transforming
 * @since 4.0.0
 */
export function decodeTo<To extends Constraint>(to: To): <From extends Constraint>(from: From) => compose<To, From>
export function decodeTo<To extends Constraint, From extends Constraint, RD = never, RE = never>(
  to: To,
  transformation: {
    readonly decode: SchemaGetter.Getter<NoInfer<To["Encoded"]>, NoInfer<From["Type"]>, RD>
    readonly encode: SchemaGetter.Getter<NoInfer<From["Type"]>, NoInfer<To["Encoded"]>, RE>
  }
): (from: From) => decodeTo<To, From, RD, RE>
export function decodeTo<To extends Constraint, From extends Constraint, RD = never, RE = never>(
  to: To,
  transformation?: {
    readonly decode: SchemaGetter.Getter<To["Encoded"], From["Type"], RD>
    readonly encode: SchemaGetter.Getter<From["Type"], To["Encoded"], RE>
  } | undefined
) {
  return (from: From) => {
    return make(
      SchemaAST.decodeTo(
        from.ast,
        to.ast,
        transformation ? SchemaTransformation.make(transformation) : SchemaTransformation.passthrough()
      ),
      {
        from,
        to
      }
    )
  }
}

/**
 * Applies a transformation to a schema, creating a new schema with the same type but transformed encoding/decoding.
 *
 * **When to use**
 *
 * Use when the decoded type stays the same and the transformation only
 * normalizes values during encoding and decoding.
 *
 * **Details**
 *
 * Call it with a transformation object and then pipe a schema into the returned
 * function. The resulting schema keeps the same `Type` and `Encoded` types as
 * the source schema, while applying the transformation during both decoding and
 * encoding.
 *
 * Internally this uses `toType(self)` as the target schema and combines service
 * requirements from the source schema and the transformation.
 *
 * **Gotchas**
 *
 * Use {@link decodeTo} instead when the transformation should change the
 * decoded type. For this helper, both transformation getters operate on
 * `S["Type"]` values.
 *
 * **Example** (Trimming string values during encoding/decoding)
 *
 * ```ts
 * import { Schema, SchemaGetter } from "effect"
 *
 * const Trimmed = Schema.String.pipe(
 *   Schema.decode({
 *     decode: SchemaGetter.transform((s) => s.trim()),
 *     encode: SchemaGetter.transform((s) => s.trim())
 *   })
 * )
 *
 * const result = Schema.decodeUnknownSync(Trimmed)("  hello  ")
 * // result: "hello"
 * ```
 *
 * @category transforming
 * @since 3.10.0
 */
export function decode<S extends Constraint, RD = never, RE = never>(transformation: {
  readonly decode: SchemaGetter.Getter<S["Type"], S["Type"], RD>
  readonly encode: SchemaGetter.Getter<S["Type"], S["Type"], RE>
}) {
  return (self: S): decodeTo<toType<S>, S, RD, RE> => {
    return decodeTo<toType<S>, S, RD, RE>(toType(self), transformation)(self)
  }
}

/**
 * Reverses a schema transformation so the encoded schema is supplied first.
 *
 * **When to use**
 *
 * Use to define a transformation by naming the encoded schema before the
 * decoded schema.
 *
 * **Details**
 *
 * `encodeTo(to)(from)` is equivalent to `to.pipe(decodeTo(from))`. The `from`
 * schema acts as the target decoded schema and `to` acts as the encoded source.
 *
 * **Example** (Encoding a number back to a string)
 *
 * ```ts
 * import { Schema, SchemaGetter } from "effect"
 *
 * const NumberFromString = Schema.Number.pipe(
 *   Schema.encodeTo(Schema.String, {
 *     decode: SchemaGetter.transform((s: string) => Number(s)),
 *     encode: SchemaGetter.transform((n: number) => String(n))
 *   })
 * )
 * ```
 *
 * @category transforming
 * @since 4.0.0
 */
export function encodeTo<To extends Constraint>(
  to: To
): <From extends Constraint>(from: From) => decodeTo<From, To>
export function encodeTo<To extends Constraint, From extends Constraint, RD = never, RE = never>(
  to: To,
  transformation: {
    readonly decode: SchemaGetter.Getter<NoInfer<From["Encoded"]>, NoInfer<To["Type"]>, RD>
    readonly encode: SchemaGetter.Getter<NoInfer<To["Type"]>, NoInfer<From["Encoded"]>, RE>
  }
): (from: From) => decodeTo<From, To, RD, RE>
export function encodeTo<To extends Constraint, From extends Constraint, RD = never, RE = never>(
  to: To,
  transformation?: {
    readonly decode: SchemaGetter.Getter<From["Encoded"], To["Type"], RD>
    readonly encode: SchemaGetter.Getter<To["Type"], From["Encoded"], RE>
  }
) {
  return (from: From): decodeTo<From, To, RD, RE> => {
    return transformation ?
      decodeTo<From, To, RD, RE>(from, transformation)(to) :
      decodeTo<From>(from)(to)
  }
}

/**
 * Applies a transformation to a schema's encoded type, creating a new schema where encoding/decoding
 * operate on `S["Encoded"]` rather than `S["Type"]`.
 *
 * **Details**
 *
 * The `decode` getter maps `S["Encoded"]` → `S["Encoded"]` (applied during decoding),
 * and the `encode` getter maps `S["Encoded"]` → `S["Encoded"]` (applied during encoding).
 *
 * **Example** (Upper-casing encoded strings)
 *
 * ```ts
 * import { Schema, SchemaGetter } from "effect"
 *
 * const UpperFromLower = Schema.String.pipe(
 *   Schema.encode({
 *     decode: SchemaGetter.transform((s: string) => s.toLowerCase()),
 *     encode: SchemaGetter.transform((s: string) => s.toUpperCase())
 *   })
 * )
 * ```
 *
 * @category transforming
 * @since 3.10.0
 */
export function encode<S extends Constraint, RD = never, RE = never>(transformation: {
  readonly decode: SchemaGetter.Getter<S["Encoded"], S["Encoded"], RD>
  readonly encode: SchemaGetter.Getter<S["Encoded"], S["Encoded"], RE>
}) {
  return (self: S): decodeTo<S, toEncoded<S>, RD, RE> => {
    return decodeTo<S, toEncoded<S>, RD, RE>(self, transformation)(toEncoded(self))
  }
}

/**
 * Constraint used to ensure a schema field does not already have a constructor default.
 *
 * **Details**
 *
 * Only schemas that satisfy this constraint can be passed to {@link withConstructorDefault}.
 *
 * @category models
 * @since 4.0.0
 */
export interface WithoutConstructorDefault {
  readonly "~type.constructor.default": "no-default"
}

/**
 * Type-level representation returned by {@link withConstructorDefault}.
 *
 * @category constructors
 * @since 3.10.0
 */
export interface withConstructorDefault<S extends Constraint & WithoutConstructorDefault> extends
  BottomLazy<
    S["ast"],
    withConstructorDefault<S>,
    S["~type.parameters"],
    S["~type.mutability"],
    S["~type.optionality"],
    "with-default",
    S["~encoded.mutability"],
    S["~encoded.optionality"]
  >
{
  readonly "Type": S["Type"]
  readonly "Encoded": S["Encoded"]
  readonly "DecodingServices": S["DecodingServices"]
  readonly "EncodingServices": S["EncodingServices"]
  readonly "~type.make.in": S["~type.make.in"]
  readonly "~type.make": S["~type.make"]
  readonly "Iso": S["Iso"]
  readonly schema: S
}

/**
 * Attaches a constructor default value to a schema field.
 *
 * **Details**
 *
 * Constructor defaults are applied only during `make*`, not during decoding or
 * encoding.
 *
 * **Example** (Defining an optional field with a static default)
 *
 * ```ts
 * import { Effect, Schema } from "effect"
 *
 * const MySchema = Schema.Struct({
 *   name: Schema.String.pipe(
 *     Schema.optionalKey,
 *     Schema.withConstructorDefault(Effect.succeed("anonymous"))
 *   )
 * })
 *
 * const value = MySchema.make({})
 * // value: { name: "anonymous" }
 * ```
 *
 * @category constructors
 * @since 3.10.0
 */
export function withConstructorDefault<S extends Constraint & WithoutConstructorDefault>(
  // `S["~type.make.in"]` instead of `S["Type"]` is intentional here because
  // it makes easier to define the default value if there are nested defaults
  defaultValue: Effect.Effect<S["~type.make.in"], SchemaError>
) {
  return (schema: S): withConstructorDefault<S> =>
    make(SchemaAST.withConstructorDefault(schema.ast, toIssueEffect(defaultValue)), { schema })
}

function toIssueEffect<A, R>(
  self: Effect.Effect<A, SchemaError, R>
): Effect.Effect<A, SchemaIssue.Issue, R> {
  return Effect.catchCause(self, (cause) => Effect.failCauseSync(() => Cause_.map(cause, (error) => error.issue)))
}

/**
 * Type-level representation returned by {@link withDecodingDefaultKey}.
 *
 * @category decoding
 * @since 4.0.0
 */
export interface withDecodingDefaultKey<S extends Constraint, R = never>
  extends decodeTo<S, optionalKey<toEncoded<S>>, R>
{
  readonly "Rebuild": withDecodingDefaultKey<S, R>
}

/**
 * Options for {@link withDecodingDefaultKey} and {@link withDecodingDefault}.
 *
 * **Details**
 *
 * - `encodingStrategy`:
 *   - `"passthrough"` (default): pass the value through during encoding
 *   - `"omit"`: omit the key from the encoded output
 *
 * @category options
 * @since 4.0.0
 */
export type DecodingDefaultOptions = {
  readonly encodingStrategy?: "omit" | "passthrough" | undefined
}

/**
 * Makes a struct key optional on the `Encoded` side and provides a default
 * `Encoded` value when the key is missing during decoding.
 *
 * **Details**
 *
 * The key uses `optionalKey` on the encoded side, so it may be absent from the
 * input object but **not** `undefined`. The default value is specified in terms
 * of the `Encoded` type (before any decoding transformations).
 *
 * Options:
 *
 * - `encodingStrategy`:
 *   - `"passthrough"` (default): include the value in the encoded output.
 *   - `"omit"`: omit the key from the encoded output.
 *
 * **Example** (Providing a default for a missing struct key)
 *
 * ```ts
 * import { Effect, Schema } from "effect"
 *
 * const MySchema = Schema.Struct({
 *   name: Schema.String.pipe(Schema.withDecodingDefaultKey(Effect.succeed("anonymous")))
 * })
 *
 * const result = Schema.decodeUnknownSync(MySchema)({})
 * // result: { name: "anonymous" }
 * ```
 *
 * @see {@link withDecodingDefault} for the value-level variant (key absent **or** `undefined`)
 * @see {@link withDecodingDefaultTypeKey} for the variant where the default is a `Type` value
 * @category decoding
 * @since 4.0.0
 */
export function withDecodingDefaultKey<S extends Constraint, R = never>(
  defaultValue: Effect.Effect<S["Encoded"], SchemaError, R>,
  options?: DecodingDefaultOptions
) {
  const encode = options?.encodingStrategy === "omit" ? SchemaGetter.omit() : SchemaGetter.passthrough()
  return (self: S): withDecodingDefaultKey<S, R> => {
    return optionalKey(toEncoded(self)).pipe(decodeTo(self, {
      decode: SchemaGetter.withDefault(toIssueEffect(defaultValue)),
      encode
    }))
  }
}

/**
 * Type-level representation returned by {@link withDecodingDefaultTypeKey}.
 *
 * @category decoding
 * @since 4.0.0
 */
export interface withDecodingDefaultTypeKey<S extends Constraint, R = never>
  extends decodeTo<withDecodingDefaultKey<toType<S>, R>, optionalKey<S>>
{
  readonly "Rebuild": withDecodingDefaultTypeKey<S, R>
}

/**
 * Makes a struct key optional on the `Encoded` side (`optionalKey`, so the
 * key may be absent but **not** `undefined`) and provides a default `Type`
 * value when the key is missing during decoding.
 *
 * **Details**
 *
 * Unlike {@link withDecodingDefaultKey}, the default value is specified in
 * terms of the `Type` (decoded) representation, so it does not need to go
 * through the decoding transformation.
 *
 * Options:
 *
 * - `encodingStrategy`:
 *   - `"passthrough"` (default): include the value in the encoded output.
 *   - `"omit"`: omit the key from the encoded output.
 *
 * @see {@link withDecodingDefaultKey} for the variant where the default is an `Encoded` value
 * @see {@link withDecodingDefaultType} for the value-level variant
 * @category decoding
 * @since 4.0.0
 */
export function withDecodingDefaultTypeKey<S extends Constraint, R = never>(
  defaultValue: Effect.Effect<S["Type"], SchemaError, R>,
  options?: DecodingDefaultOptions
) {
  return (self: S): withDecodingDefaultTypeKey<S, R> => {
    return toType(self).pipe(
      withDecodingDefaultKey<toType<S>, R>(defaultValue, options),
      encodeTo(optionalKey(self))
    )
  }
}

/**
 * Type-level representation returned by {@link withDecodingDefault}.
 *
 * @category decoding
 * @since 3.10.0
 */
export interface withDecodingDefault<S extends Constraint, R = never> extends decodeTo<S, optional<toEncoded<S>>, R> {
  readonly "Rebuild": withDecodingDefault<S, R>
}

/**
 * Wraps the `Encoded` side with `optional` (key absent **or** `undefined`)
 * and provides a default `Encoded` value when the field is missing or
 * `undefined` during decoding.
 *
 * **When to use**
 *
 * Use when the default is expressed in the encoded representation, before the
 * field's decoding transformation runs.
 *
 * **Details**
 *
 * The default value is specified in terms of the `Encoded` type (before any
 * decoding transformations).
 *
 * Options:
 *
 * - `encodingStrategy`:
 *   - `"passthrough"` (default): include the value in the encoded output.
 *   - `"omit"`: omit the key from the encoded output.
 *
 * **Example** (Providing a default for an optional field value)
 *
 * ```ts
 * import { Effect, Schema } from "effect"
 *
 * const MySchema = Schema.Struct({
 *   name: Schema.String.pipe(Schema.optional, Schema.withDecodingDefault(Effect.succeed("anonymous")))
 * })
 *
 * const result = Schema.decodeUnknownSync(MySchema)({ name: undefined })
 * // result: { name: "anonymous" }
 * ```
 *
 * @see {@link withDecodingDefaultKey} for the key-level variant (key absent only, not `undefined`)
 * @see {@link withDecodingDefaultType} for the variant where the default is a `Type` value
 * @category decoding
 * @since 3.10.0
 */
export function withDecodingDefault<S extends Constraint, R = never>(
  defaultValue: Effect.Effect<S["Encoded"], SchemaError, R>,
  options?: DecodingDefaultOptions
) {
  const encode = options?.encodingStrategy === "omit" ? SchemaGetter.omit() : SchemaGetter.passthrough()
  return (self: S): withDecodingDefault<S, R> => {
    return optional(toEncoded(self)).pipe(decodeTo(self, {
      decode: SchemaGetter.withDefault(toIssueEffect(defaultValue)),
      encode
    }))
  }
}

/**
 * Type-level representation returned by {@link withDecodingDefaultType}.
 *
 * @category decoding
 * @since 4.0.0
 */
export interface withDecodingDefaultType<S extends Constraint, R = never>
  extends decodeTo<withDecodingDefault<toType<S>, R>, optional<S>>
{
  readonly "Rebuild": withDecodingDefaultType<S, R>
}

/**
 * Wraps the `Encoded` side with `optional` (key absent **or** `undefined`)
 * and provides a default `Type` value when the field is missing or
 * `undefined` during decoding.
 *
 * **When to use**
 *
 * Use when the default is already in the decoded representation and should not
 * pass through the field's decoding transformation.
 *
 * **Details**
 *
 * Unlike {@link withDecodingDefault}, the default value is specified in terms
 * of the `Type` (decoded) representation, so it does not need to go through
 * the decoding transformation.
 *
 * Options:
 *
 * - `encodingStrategy`:
 *   - `"passthrough"` (default): include the value in the encoded output.
 *   - `"omit"`: omit the key from the encoded output.
 *
 * @see {@link withDecodingDefault} for the variant where the default is an `Encoded` value
 * @see {@link withDecodingDefaultTypeKey} for the key-level variant
 * @category decoding
 * @since 4.0.0
 */
export function withDecodingDefaultType<S extends Constraint, R = never>(
  defaultValue: Effect.Effect<S["Type"], SchemaError, R>,
  options?: DecodingDefaultOptions
) {
  return (self: S): withDecodingDefaultType<S, R> => {
    return toType(self).pipe(
      withDecodingDefault<toType<S>, R>(defaultValue, options),
      encodeTo(optional(self))
    )
  }
}

/**
 * Type-level representation returned by {@link tag}.
 *
 * @category constructors
 * @since 3.10.0
 */
export interface tag<Tag extends SchemaAST.LiteralValue> extends withConstructorDefault<Literal<Tag>> {}

/**
 * Combines a {@link Literal} schema with {@link withConstructorDefault}, making it ideal
 * for discriminator fields in tagged unions. When constructing via `make`, the
 * `_tag` field can be omitted and will be filled automatically.
 *
 * **Example** (Defining a discriminated union tag)
 *
 * ```ts
 * import { Schema } from "effect"
 *
 * const A = Schema.Struct({ _tag: Schema.tag("A"), value: Schema.Number })
 *
 * // _tag is optional in make, auto-filled to "A"
 * const a = A.make({ value: 42 })
 * // a: { _tag: "A", value: 42 }
 * ```
 *
 * @see {@link tagDefaultOmit} to also omit the tag during encoding
 * @see {@link TaggedStruct} for a shorthand that adds `_tag` automatically
 * @category constructors
 * @since 3.10.0
 */
export function tag<Tag extends SchemaAST.LiteralValue>(literal: Tag): tag<Tag> {
  return Literal(literal).pipe(withConstructorDefault(Effect.succeed(literal)))
}

/**
 * Creates a literal `_tag` schema that is omitted from encoded output.
 *
 * **When to use**
 *
 * Use to decode data that omits the discriminator field while still constructing
 * values with a `_tag` for tagged union matching.
 *
 * **Details**
 *
 * The tag is filled during decoding and construction, like {@link tag}, but is
 * omitted when encoding.
 *
 * **Example** (Omitting tags during encoding)
 *
 * ```ts
 * import { Schema } from "effect"
 *
 * const A = Schema.Struct({
 *   _tag: Schema.tagDefaultOmit("A"),
 *   value: Schema.Number
 * })
 *
 * // Encode strips the _tag field
 * const encoded = Schema.encodeUnknownSync(A)({ _tag: "A", value: 1 })
 * // encoded: { value: 1 }
 * ```
 *
 * @see {@link tag} for the variant that keeps the tag during encoding
 * @category constructors
 * @since 4.0.0
 */
export function tagDefaultOmit<Tag extends SchemaAST.LiteralValue>(literal: Tag) {
  return tag(literal).pipe(withDecodingDefaultKey(Effect.succeed(literal), { encodingStrategy: "omit" }))
}

/**
 * Type-level representation returned by {@link TaggedStruct}.
 *
 * @category models
 * @since 3.10.0
 */
export type TaggedStruct<Tag extends SchemaAST.LiteralValue, Fields extends Struct.Fields> = Struct<
  Simplify<{ readonly _tag: tag<Tag> } & Fields>
>

/**
 * Creates a struct schema with an automatically populated `_tag` field.
 *
 * **When to use**
 *
 * Use to define a tagged union case from a literal tag and a set of fields.
 *
 * **Details**
 *
 * When using the `make` method, the `_tag` field is optional and will be
 * added automatically. However, when decoding or encoding, the `_tag` field
 * must be present in the input.
 *
 * **Example** (Defining a tagged struct shorthand)
 *
 * ```ts
 * import { Schema } from "effect"
 *
 * // Defines a struct with a fixed `_tag` field
 * const tagged = Schema.TaggedStruct("A", {
 *   a: Schema.String
 * })
 *
 * // This is the same as writing:
 * const equivalent = Schema.Struct({
 *   _tag: Schema.tag("A"),
 *   a: Schema.String
 * })
 * ```
 *
 * **Example** (Accessing the literal value of the tag)
 *
 * ```ts
 * import { Schema } from "effect"
 *
 * const tagged = Schema.TaggedStruct("A", {
 *   a: Schema.String
 * })
 *
 * // literal: "A"
 * const literal = tagged.fields._tag.schema.literal
 * ```
 *
 * @category constructors
 * @since 3.10.0
 */
export function TaggedStruct<const Tag extends SchemaAST.LiteralValue, const Fields extends Struct.Fields>(
  value: Tag,
  fields: Fields
): TaggedStruct<Tag, Fields> {
  return Struct({ _tag: tag(value), ...fields })
}

/**
 * Recursively flatten any nested Schema.Union members into a single tuple of leaf schemas.
 */
type Flatten<Schemas> = Schemas extends readonly [infer Head, ...infer Tail]
  ? Head extends Union<infer Inner> ? [...Flatten<Inner>, ...Flatten<Tail>]
  : [Head, ...Flatten<Tail>]
  : []

type TaggedUnionUtils<
  Tag extends PropertyKey,
  Members extends ReadonlyArray<Constraint & { readonly Type: { readonly [K in Tag]: PropertyKey } }>,
  Flattened extends ReadonlyArray<Constraint & { readonly Type: { readonly [K in Tag]: PropertyKey } }> = Flatten<
    Members
  >
> = {
  readonly cases: Simplify<{ [M in Flattened[number] as M["Type"][Tag]]: M }>
  readonly isAnyOf: <const Keys>(
    keys: ReadonlyArray<Keys>
  ) => (value: Members[number]["Type"]) => value is Extract<Members[number]["Type"], { readonly [K in Tag]: Keys }>
  readonly guards: { [M in Flattened[number] as M["Type"][Tag]]: (u: unknown) => u is M["Type"] }
  readonly match: {
    <
      Cases extends { [M in Flattened[number] as M["Type"][Tag]]: (value: M["Type"]) => any }
    >(
      value: Members[number]["Type"],
      cases: Cases
    ): Cases[keyof Cases] extends (value: any) => infer R ? Unify<R>
      : never
    <
      Cases extends { [M in Flattened[number] as M["Type"][Tag]]: (value: M["Type"]) => any }
    >(
      cases: Cases
    ): (value: Members[number]["Type"]) => Cases[keyof Cases] extends (value: any) => infer R ? Unify<R>
      : never
  }
}

/**
 * Type-level representation returned by {@link toTaggedUnion}.
 *
 * @category combinators
 * @since 4.0.0
 */
export type toTaggedUnion<
  Tag extends PropertyKey,
  Members extends ReadonlyArray<Constraint & { readonly Type: { readonly [K in Tag]: PropertyKey } }>
> = Union<Members> & TaggedUnionUtils<Tag, Members>

/**
 * Augments an existing {@link Union} of tagged structs with utility methods keyed by the discriminant field.
 *
 * **Example** (Adding tagged-union utilities to an existing union)
 *
 * ```ts
 * import { Schema } from "effect"
 *
 * const A = Schema.TaggedStruct("A", { value: Schema.Number })
 * const B = Schema.TaggedStruct("B", { name: Schema.String })
 *
 * const MyUnion = Schema.Union([A, B]).pipe(Schema.toTaggedUnion("_tag"))
 *
 * // Pattern-match on the union
 * const result = MyUnion.match({ _tag: "A", value: 1 }, {
 *   A: (a) => `number: ${a.value}`,
 *   B: (b) => `name: ${b.name}`
 * })
 * ```
 *
 * @see {@link TaggedUnion} for a shorthand that builds the union from scratch
 * @category combinators
 * @since 4.0.0
 */
export function toTaggedUnion<const Tag extends PropertyKey>(tag: Tag) {
  return <const Members extends ReadonlyArray<Constraint & { readonly Type: { readonly [K in Tag]: PropertyKey } }>>(
    self: Union<Members>
  ): toTaggedUnion<Tag, Members> => {
    const cases: Record<PropertyKey, unknown> = {}
    const guards: Record<PropertyKey, (u: unknown) => boolean> = {}
    const isAnyOf = (keys: ReadonlyArray<PropertyKey>) => (value: Members[number]["Type"]) => keys.includes(value[tag])

    walk(self)

    return Object.assign(self, { cases, isAnyOf, guards, match }) as any

    function walk(schema: Constraint) {
      const ast = schema.ast

      if (
        SchemaAST.isUnion(ast) && "members" in schema && globalThis.Array.isArray(schema.members) &&
        schema.members.every(isSchema)
      ) {
        return schema.members.forEach(walk)
      }

      const sentinels = SchemaAST.collectSentinels(ast)
      if (sentinels.length > 0) {
        const literal = sentinels.find((s) => s.key === tag)?.literal
        if (Predicate.isPropertyKey(literal)) {
          cases[literal] = schema
          guards[literal] = is(toType(schema))
          return
        }
      }

      throw new globalThis.Error("No literal or unique symbol found")
    }

    function match() {
      if (arguments.length === 1) {
        const cases = arguments[0]
        return function(value: any) {
          return cases[value[tag]](value)
        }
      }
      const value = arguments[0]
      const cases = arguments[1]
      return cases[value[tag]](value)
    }
  }
}

/**
 * Type-level representation returned by {@link TaggedUnion}.
 *
 * @category models
 * @since 4.0.0
 */
export interface TaggedUnion<Cases extends Record<string, Constraint>> extends
  BottomLazy<
    SchemaAST.Union<SchemaAST.Objects>,
    TaggedUnion<Cases>
  >
{
  readonly "Type": { [K in keyof Cases]: Cases[K]["Type"] }[keyof Cases]
  readonly "Encoded": { [K in keyof Cases]: Cases[K]["Encoded"] }[keyof Cases]
  readonly "DecodingServices": { [K in keyof Cases]: Cases[K]["DecodingServices"] }[keyof Cases]
  readonly "EncodingServices": { [K in keyof Cases]: Cases[K]["EncodingServices"] }[keyof Cases]
  readonly "~type.make.in": { [K in keyof Cases]: Cases[K]["~type.make"] }[keyof Cases]
  readonly "~type.make": { [K in keyof Cases]: Cases[K]["~type.make"] }[keyof Cases]
  readonly "Iso": { [K in keyof Cases]: Cases[K]["Type"] }[keyof Cases]
  readonly cases: Cases
  readonly isAnyOf: <const Keys>(
    keys: ReadonlyArray<Keys>
  ) => (value: Cases[keyof Cases]["Type"]) => value is Extract<Cases[keyof Cases]["Type"], { _tag: Keys }>
  readonly guards: { [K in keyof Cases]: (u: unknown) => u is Cases[K]["Type"] }
  readonly match: {
    <Output>(
      cases: { [K in keyof Cases]: (value: Cases[K]["Type"]) => Output }
    ): (value: Cases[keyof Cases]["Type"]) => Output
    <Output>(
      value: Cases[keyof Cases]["Type"],
      cases: { [K in keyof Cases]: (value: Cases[K]["Type"]) => Output }
    ): Output
  }
}

/**
 * Builds a discriminated union from a record of field sets, one per variant.
 * Each key becomes the `_tag` literal and the value is passed to {@link TaggedStruct}.
 * The result includes `cases`, `guards`, `isAnyOf`, and `match` utilities.
 *
 * **Example** (Pattern matching a discriminated union)
 *
 * ```ts
 * import { Schema } from "effect"
 *
 * const Shape = Schema.TaggedUnion({
 *   Circle: { radius: Schema.Number },
 *   Rectangle: { width: Schema.Number, height: Schema.Number }
 * })
 *
 * // Pattern-match on a decoded value
 * const area = Shape.match({ _tag: "Circle", radius: 5 }, {
 *   Circle: (c) => Math.PI * c.radius ** 2,
 *   Rectangle: (r) => r.width * r.height
 * })
 * ```
 *
 * @see {@link toTaggedUnion} to augment an existing union instead
 * @category constructors
 * @since 4.0.0
 */
export function TaggedUnion<const CasesByTag extends Record<string, Struct.Fields>>(
  casesByTag: CasesByTag
): TaggedUnion<{ readonly [K in keyof CasesByTag & string]: TaggedStruct<K, CasesByTag[K]> }> {
  const cases: any = {}
  const members: any = []
  for (const key of Object.keys(casesByTag)) {
    members.push(cases[key] = TaggedStruct(key, casesByTag[key]))
  }
  const union = Union(members)
  const { guards, isAnyOf, match } = toTaggedUnion("_tag")(union)
  return make(union.ast, { cases, isAnyOf, guards, match })
}

/**
 * Type-level representation returned by {@link Opaque}.
 *
 * @category models
 * @since 4.0.0
 */
export interface Opaque<Self, S extends Top, Brand> extends
  BottomLazy<
    S["ast"],
    S["Rebuild"],
    S["~type.parameters"],
    S["~type.mutability"],
    S["~type.optionality"],
    S["~type.constructor.default"],
    S["~encoded.mutability"],
    S["~encoded.optionality"]
  >
{
  readonly "Type": Self
  readonly "Encoded": S["Encoded"]
  readonly "DecodingServices": S["DecodingServices"]
  readonly "EncodingServices": S["EncodingServices"]
  readonly "~type.make.in": S["~type.make.in"]
  readonly "~type.make": S["~type.make"]
  readonly "Iso": S["Iso"]
  new(_: never): S["Type"] & Brand
}

/**
 * Wraps a struct schema so that its decoded `Type` becomes a nominally distinct type `Self`.
 * Useful for creating opaque types that are structurally identical to a base struct
 * but type-incompatible with it.
 *
 * **Example** (Defining opaque structs)
 *
 * ```ts
 * import { Schema } from "effect"
 *
 * class Person extends Schema.Opaque<Person>()(
 *   Schema.Struct({
 *     name: Schema.String
 *   })
 * ) {}
 *
 * // Decoded value is Person, not { name: string }
 * const person = Schema.decodeUnknownSync(Person)({ name: "Alice" })
 * // person: Person
 * ```
 *
 * @category constructors
 * @since 4.0.0
 */
export function Opaque<Self, Brand = {}>() {
  return <S extends Top>(schema: S): Opaque<Self, S, Brand> & Omit<S, keyof Top> => {
    // oxlint-disable-next-line @typescript-eslint/no-extraneous-class
    class Opaque {}
    return Object.setPrototypeOf(Opaque, schema)
  }
}

/**
 * Type-level representation returned by {@link instanceOf}.
 *
 * @category models
 * @since 3.10.0
 */
export interface instanceOf<T, Iso = T> extends declare<T, Iso> {
  readonly "Rebuild": instanceOf<T, Iso>
}

/**
 * Creates a schema that validates values using `instanceof`.
 * Decoding and encoding pass the value through unchanged.
 *
 * **Example** (Defining a schema for a built-in class)
 *
 * ```ts
 * import { Schema } from "effect"
 *
 * const DateSchema = Schema.instanceOf(Date)
 *
 * const decoded = Schema.decodeUnknownSync(DateSchema)(new Date("2024-01-01"))
 * // decoded: Date
 * ```
 *
 * @category constructors
 * @since 3.10.0
 */
export function instanceOf<C extends abstract new(...args: any) => any, Iso = InstanceType<C>>(
  constructor: C,
  annotations?: Annotations.Declaration<InstanceType<C>> | undefined
): instanceOf<InstanceType<C>, Iso> {
  return declare((u): u is InstanceType<C> => u instanceof constructor, annotations)
}

/**
 * Constructs an `SchemaAST.Link` that describes how a value of type `T` encodes to and decodes from a `To` schema.
 * Used when building low-level AST transformations that bridge two schema types.
 *
 * @category transforming
 * @since 4.0.0
 */
export function link<T>() {
  return <To extends Constraint>(
    encodeTo: To,
    transformation: {
      readonly decode: SchemaGetter.Getter<T, NoInfer<To["Type"]>>
      readonly encode: SchemaGetter.Getter<NoInfer<To["Type"]>, T>
    }
  ): SchemaAST.Link => {
    return new SchemaAST.Link(encodeTo.ast, SchemaTransformation.make(transformation))
  }
}

// -----------------------------------------------------------------------------
// Checks
// -----------------------------------------------------------------------------

/**
 * Creates a custom validation filter from a predicate function.
 *
 * **Details**
 *
 * The predicate receives the decoded input value, the schema AST, and parse
 * options, and returns a `FilterOutput`. Non-success outputs are normalized into
 * schema issues. The `annotations` parameter annotates the filter itself; with
 * the default formatter, failures use `message` first, `expected` second, and
 * `<filter>` when neither is provided.
 *
 * When `abort` is `true`, parsing stops after this filter fails instead of
 * collecting later check failures.
 *
 * **Example** (Reporting failure at a nested path)
 *
 * ```ts
 * import { Schema } from "effect"
 *
 * const schema = Schema.Struct({ password: Schema.String, confirmPassword: Schema.String }).check(
 *   Schema.makeFilter((o) =>
 *     o.password === o.confirmPassword
 *       ? undefined
 *       : { path: ["password"], issue: "password and confirmPassword must match" }
 *   )
 * )
 *
 * console.log(String(Schema.decodeUnknownExit(schema)({ password: "123456", confirmPassword: "1234567" })))
 * // Failure(Cause([Fail(SchemaError: password and confirmPassword must match
 * //   at ["password"])]))
 * ```
 *
 * **Example** (Reporting multiple failures at once)
 *
 * ```ts
 * import { Schema } from "effect"
 *
 * const schema = Schema.Struct({ a: Schema.Finite, b: Schema.Finite, c: Schema.Finite }).check(
 *   Schema.makeFilter((o) => {
 *     const issues: Array<Schema.FilterIssue> = []
 *     if (o.a > 0) {
 *       if (o.b <= 0) issues.push({ path: ["b"], issue: "b must be greater than 0" })
 *       if (o.c <= 0) issues.push({ path: ["c"], issue: "c must be greater than 0" })
 *     }
 *     return issues
 *   })
 * )
 *
 * console.log(String(Schema.decodeUnknownExit(schema)({ a: 1, b: 0, c: 0 })))
 * // Failure(Cause([Fail(SchemaError: b must be greater than 0
 * //   at ["b"]
 * // c must be greater than 0
 * //   at ["c"])]))
 * ```
 *
 * @category constructors
 * @since 4.0.0
 */
export const makeFilter: <T>(
  filter: (input: T, ast: SchemaAST.AST, options: SchemaAST.ParseOptions) => FilterOutput,
  annotations?: Annotations.Filter | undefined,
  abort?: boolean
) => SchemaAST.Filter<T> = SchemaAST.makeFilter

/**
 * A single failure reported by a filter predicate. Used as the element type
 * of the array arm of {@link FilterOutput}, and also accepted on its own.
 *
 * **Details**
 *
 * - `string`: failure with that string as the message. Produces an
 *   {@link SchemaIssue.InvalidValue} wrapping the input, with the string used as
 *   the issue's `message` annotation.
 * - {@link SchemaIssue.Issue}: a fully-formed issue, returned as-is.
 * - `{ path, issue }`: failure attached to a nested path. `issue` is either
 *   a `string` (wrapped in an {@link SchemaIssue.InvalidValue}) or a full
 *   {@link SchemaIssue.Issue}; the result is wrapped in an {@link SchemaIssue.Pointer}
 *   at the given `path`.
 *
 * @category models
 * @since 3.10.0
 */
export type FilterIssue = string | SchemaIssue.Issue | {
  readonly path: ReadonlyArray<PropertyKey>
  readonly issue: string | SchemaIssue.Issue
}

/**
 * The value a filter predicate (see {@link makeFilter}) may return.
 *
 * **Details**
 *
 * Each shape is normalized into an {@link SchemaIssue.Issue} (or `undefined` for
 * success) before being attached to the parse result:
 *
 * - `undefined`: success. The input satisfies the filter.
 * - `true`: success. Equivalent to `undefined`, useful when the predicate is
 *   a plain boolean expression.
 * - `false`: generic failure. Produces an {@link SchemaIssue.InvalidValue} wrapping
 *   the input, with no custom message.
 * - {@link FilterIssue}: a single failure. See {@link FilterIssue} for the
 *   shapes (`string`, {@link SchemaIssue.Issue}, or `{ path, issue }`).
 * - `ReadonlyArray<FilterIssue>`: several failures reported together. An
 *   empty array is treated as success; a single-element array is equivalent
 *   to returning that element directly; otherwise the entries are grouped
 *   into an {@link SchemaIssue.Composite}.
 *
 * @category models
 * @since 3.10.0
 */
export type FilterOutput =
  | undefined
  | boolean
  | FilterIssue
  | ReadonlyArray<FilterIssue>

/**
 * Groups multiple checks into a single {@link SchemaAST.FilterGroup}, applying
 * optional shared annotations to the group as a whole.
 *
 * @category constructors
 * @since 4.0.0
 */
export function makeFilterGroup<T>(
  checks: readonly [SchemaAST.Check<T>, ...Array<SchemaAST.Check<T>>],
  annotations: Annotations.Filter | undefined = undefined
): SchemaAST.FilterGroup<T> {
  return new SchemaAST.FilterGroup(checks, annotations)
}

const TRIMMED_PATTERN = "^\\S[\\s\\S]*\\S$|^\\S$|^$"

/**
 * Validates that a string has no leading or trailing whitespace.
 *
 * **Details**
 *
 * JSON Schema:
 *
 * This check corresponds to a `pattern` constraint in JSON Schema that
 * matches strings without leading or trailing whitespace.
 *
 * Arbitrary:
 *
 * When generating test data with fast-check, this applies a `patterns`
 * constraint to ensure generated strings match the trimmed pattern.
 *
 * @category String checks
 * @since 4.0.0
 */
export function isTrimmed(annotations?: Annotations.Filter) {
  return makeFilter(
    (s: string) => s.trim() === s,
    {
      expected: "a string with no leading or trailing whitespace",
      meta: {
        _tag: "isTrimmed",
        regExp: new globalThis.RegExp(TRIMMED_PATTERN)
      },
      arbitrary: {
        constraint: {
          patterns: [TRIMMED_PATTERN]
        }
      },
      ...annotations
    }
  )
}

/**
 * Validates that a string matches the specified regular expression pattern.
 *
 * **Details**
 *
 * JSON Schema:
 *
 * This check corresponds to the `pattern` constraint in JSON Schema.
 *
 * Arbitrary:
 *
 * When generating test data with fast-check, this applies a `patterns`
 * constraint to ensure generated strings match the specified RegExp pattern.
 *
 * @category String checks
 * @since 4.0.0
 */
export const isPattern: (regExp: globalThis.RegExp, annotations?: Annotations.Filter) => SchemaAST.Filter<string> =
  SchemaAST.isPattern

/**
 * Validates that a string represents a finite number.
 *
 * **Details**
 *
 * JSON Schema:
 *
 * This check corresponds to a `pattern` constraint in JSON Schema that matches
 * strings representing finite numbers.
 *
 * Arbitrary:
 *
 * When generating test data with fast-check, this applies a `patterns`
 * constraint to ensure generated strings match the number string pattern.
 *
 * @category String checks
 * @since 4.0.0
 */
export const isStringFinite: (annotations?: Annotations.Filter) => SchemaAST.Filter<string> = SchemaAST.isStringFinite

/**
 * Validates that a string is a signed base-10 integer literal for Effect's
 * BigInt string encoding.
 *
 * **Details**
 *
 * The check uses the pattern `^-?\d+$`. It does not accept leading `+`, decimal
 * points, exponent notation, separators, or non-decimal inputs such as
 * hexadecimal strings.
 *
 * JSON Schema:
 * This check corresponds to a `pattern` constraint with the same signed
 * base-10 integer pattern.
 *
 * @category String checks
 * @since 4.0.0
 */
export const isStringBigInt: (annotations?: Annotations.Filter) => SchemaAST.Filter<string> = SchemaAST.isStringBigInt

/**
 * Validates that a string has the `Symbol(description)` format used by Effect's
 * symbol string encoding.
 *
 * **Details**
 *
 * The check uses the pattern `^Symbol\((.*)\)$`. It is not a general test for
 * whether a string can be passed to JavaScript's `Symbol()` function.
 *
 * @category String checks
 * @since 4.0.0
 */
export const isStringSymbol: (annotations?: Annotations.Filter) => SchemaAST.Filter<string> = SchemaAST.isStringSymbol

/**
 * Returns a RegExp for validating an RFC 9562 / RFC 4122 UUID.
 *
 * Optionally specify a version 1-8. If no version is specified (`undefined`), all versions are supported.
 */
const getUUIDRegExp = (version?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8): globalThis.RegExp => {
  if (version) {
    return new globalThis.RegExp(
      `^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-${version}[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$`
    )
  }
  return /^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|[fF]{8}-[fF]{4}-[fF]{4}-[fF]{4}-[fF]{12})$/
}

/**
 * Validates that a string is a strict Universally Unique Identifier (UUID).
 *
 * **When to use**
 *
 * Use when you need UUID semantics, including version and RFC variant bits,
 * rather than only the dashed hexadecimal shape.
 *
 * **Details**
 *
 * Without a version argument, this accepts UUID versions 1 through 8, the nil
 * UUID (`00000000-0000-0000-0000-000000000000`), and the max UUID
 * (`ffffffff-ffff-ffff-ffff-ffffffffffff`). With a version argument, this
 * accepts only UUIDs with that version and RFC variant bits; nil and max UUIDs
 * are not versioned UUIDs and do not match version-specific checks.
 *
 * JSON Schema:
 *
 * This check corresponds to a `pattern` constraint in JSON Schema that matches
 * UUID format, and includes a `format: "uuid"` annotation.
 *
 * Arbitrary:
 *
 * When generating test data with fast-check, this applies a `patterns`
 * constraint to ensure generated strings match the UUID pattern.
 *
 * @see {@link isGUID} for shape-only GUID validation.
 * @category String checks
 * @since 4.0.0
 */
export function isUUID(version?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8, annotations?: Annotations.Filter) {
  const regExp = getUUIDRegExp(version)
  return isPattern(
    regExp,
    {
      expected: version ? `a UUID v${version}` : "a UUID",
      meta: {
        _tag: "isUUID",
        regExp,
        version
      },
      ...annotations
    }
  )
}

const GUID_REGEXP = /^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$/

/**
 * Validates that a string has the GUID / UUID textual shape.
 *
 * **When to use**
 *
 * Use when you need to accept dashed hexadecimal identifiers without enforcing
 * UUID version or variant bits.
 *
 * **Details**
 *
 * This check accepts strings in the `8-4-4-4-12` hexadecimal form. JSON Schema
 * output includes the corresponding `pattern` constraint and intentionally does
 * not include `format: "uuid"` because GUID validation is looser than UUID
 * validation.
 *
 * Arbitrary:
 *
 * When generating test data with fast-check, this applies a `patterns`
 * constraint to ensure generated strings match the GUID pattern.
 *
 * @see {@link isUUID} for strict UUID validation.
 * @category String checks
 * @since 4.0.0
 */
export function isGUID(annotations?: Annotations.Filter) {
  return isPattern(
    GUID_REGEXP,
    {
      expected: "a GUID",
      meta: {
        _tag: "isGUID",
        regExp: GUID_REGEXP
      },
      ...annotations
    }
  )
}

/**
 * Validates that a string is a valid ULID (Universally Unique Lexicographically
 * Sortable Identifier).
 *
 * **Details**
 *
 * JSON Schema:
 *
 * This check corresponds to a `pattern` constraint in JSON Schema that matches
 * the ULID format.
 *
 * Arbitrary:
 *
 * When generating test data with fast-check, this applies a `patterns`
 * constraint to ensure generated strings match the ULID pattern.
 *
 * @category String checks
 * @since 4.0.0
 */
export function isULID(annotations?: Annotations.Filter) {
  const regExp = /^[0-9A-HJKMNP-TV-Za-hjkmnp-tv-z]{26}$/
  return isPattern(
    regExp,
    {
      meta: {
        _tag: "isULID",
        regExp
      },
      ...annotations
    }
  )
}

/**
 * Validates that a string is valid Base64 encoded data.
 *
 * **Details**
 *
 * JSON Schema:
 *
 * This check corresponds to a `pattern` constraint in JSON Schema that matches
 * Base64 format.
 *
 * Arbitrary:
 *
 * When generating test data with fast-check, this applies a `patterns`
 * constraint to ensure generated strings match the Base64 pattern.
 *
 * @category String checks
 * @since 4.0.0
 */
export function isBase64(annotations?: Annotations.Filter) {
  const regExp = /^([0-9a-zA-Z+/]{4})*(([0-9a-zA-Z+/]{2}==)|([0-9a-zA-Z+/]{3}=))?$/
  return isPattern(
    regExp,
    {
      expected: "a base64 encoded string",
      meta: {
        _tag: "isBase64",
        regExp
      },
      ...annotations
    }
  )
}

/**
 * Validates that a string is valid Base64URL encoded data (Base64 with URL-safe
 * characters).
 *
 * **Details**
 *
 * JSON Schema:
 *
 * This check corresponds to a `pattern` constraint in JSON Schema that matches
 * Base64URL format.
 *
 * Arbitrary:
 *
 * When generating test data with fast-check, this applies a `patterns`
 * constraint to ensure generated strings match the Base64URL pattern.
 *
 * @category String checks
 * @since 4.0.0
 */
export function isBase64Url(annotations?: Annotations.Filter) {
  const regExp = /^([0-9a-zA-Z-_]{4})*(([0-9a-zA-Z-_]{2}(==)?)|([0-9a-zA-Z-_]{3}(=)?))?$/
  return isPattern(
    regExp,
    {
      expected: "a base64url encoded string",
      meta: {
        _tag: "isBase64Url",
        regExp
      },
      ...annotations
    }
  )
}

/**
 * Validates at runtime that a string starts with the specified literal prefix.
 *
 * **Details**
 *
 * Notes:
 * The JSON Schema and arbitrary metadata are built from `^${startsWith}` without
 * escaping regexp metacharacters. If the prefix contains regexp syntax, generated
 * patterns may not be equivalent to the runtime `startsWith` check.
 *
 * @category String checks
 * @since 4.0.0
 */
export function isStartsWith(startsWith: string, annotations?: Annotations.Filter) {
  const formatted = JSON.stringify(startsWith)
  return makeFilter(
    (s: string) => s.startsWith(startsWith),
    {
      expected: `a string starting with ${formatted}`,
      meta: {
        _tag: "isStartsWith",
        startsWith,
        regExp: new globalThis.RegExp(`^${startsWith}`)
      },
      arbitrary: {
        constraint: {
          patterns: [`^${startsWith}`]
        }
      },
      ...annotations
    }
  )
}

/**
 * Validates at runtime that a string ends with the specified literal suffix.
 *
 * **Details**
 *
 * Notes:
 * The JSON Schema and arbitrary metadata are built from `${endsWith}$` without
 * escaping regexp metacharacters. If the suffix contains regexp syntax, generated
 * patterns may not be equivalent to the runtime `endsWith` check.
 *
 * @category String checks
 * @since 4.0.0
 */
export function isEndsWith(endsWith: string, annotations?: Annotations.Filter) {
  const formatted = JSON.stringify(endsWith)
  return makeFilter(
    (s: string) => s.endsWith(endsWith),
    {
      expected: `a string ending with ${formatted}`,
      meta: {
        _tag: "isEndsWith",
        endsWith,
        regExp: new globalThis.RegExp(`${endsWith}$`)
      },
      arbitrary: {
        constraint: {
          patterns: [`${endsWith}$`]
        }
      },
      ...annotations
    }
  )
}

/**
 * Validates at runtime that a string contains the specified literal substring.
 *
 * **Details**
 *
 * Notes:
 * The JSON Schema and arbitrary metadata use the substring as a raw regexp
 * pattern. If the substring contains regexp syntax, generated patterns may not be
 * equivalent to the runtime `includes` check.
 *
 * @category String checks
 * @since 4.0.0
 */
export function isIncludes(includes: string, annotations?: Annotations.Filter) {
  const formatted = JSON.stringify(includes)
  return makeFilter(
    (s: string) => s.includes(includes),
    {
      expected: `a string including ${formatted}`,
      meta: {
        _tag: "isIncludes",
        includes,
        regExp: new globalThis.RegExp(includes)
      },
      arbitrary: {
        constraint: {
          patterns: [includes]
        }
      },
      ...annotations
    }
  )
}

const UPPERCASED_PATTERN = "^[^a-z]*$"

/**
 * Validates that a string is unchanged by JavaScript's `toUpperCase()`.
 *
 * **Details**
 *
 * This accepts empty strings and characters that do not have lowercase forms,
 * such as digits, punctuation, and whitespace. It rejects strings that would
 * change when uppercased.
 *
 * @category String checks
 * @since 4.0.0
 */
export function isUppercased(annotations?: Annotations.Filter) {
  return makeFilter(
    (s: string) => s.toUpperCase() === s,
    {
      expected: "a string with all characters in uppercase",
      meta: {
        _tag: "isUppercased",
        regExp: new globalThis.RegExp(UPPERCASED_PATTERN)
      },
      arbitrary: {
        constraint: {
          patterns: [UPPERCASED_PATTERN]
        }
      },
      ...annotations
    }
  )
}

const LOWERCASED_PATTERN = "^[^A-Z]*$"

/**
 * Validates that a string is unchanged by JavaScript's `toLowerCase()`.
 *
 * **Details**
 *
 * This accepts empty strings and characters that do not have uppercase forms,
 * such as digits, punctuation, and whitespace. It rejects strings that would
 * change when lowercased.
 *
 * @category String checks
 * @since 4.0.0
 */
export function isLowercased(annotations?: Annotations.Filter) {
  return makeFilter(
    (s: string) => s.toLowerCase() === s,
    {
      expected: "a string with all characters in lowercase",
      meta: {
        _tag: "isLowercased",
        regExp: new globalThis.RegExp(LOWERCASED_PATTERN)
      },
      arbitrary: {
        constraint: {
          patterns: [LOWERCASED_PATTERN]
        }
      },
      ...annotations
    }
  )
}

const CAPITALIZED_PATTERN = "^[^a-z]?.*$"

/**
 * Validates that the first character of a string is unchanged by
 * `toUpperCase()`.
 *
 * **Details**
 *
 * Empty strings pass. Strings whose first character has no lowercase form, such
 * as a digit, punctuation mark, or whitespace, also pass.
 *
 * @category String checks
 * @since 4.0.0
 */
export function isCapitalized(annotations?: Annotations.Filter) {
  return makeFilter(
    (s: string) => s.charAt(0).toUpperCase() === s.charAt(0),
    {
      expected: "a string with the first character in uppercase",
      meta: {
        _tag: "isCapitalized",
        regExp: new globalThis.RegExp(CAPITALIZED_PATTERN)
      },
      arbitrary: {
        constraint: {
          patterns: [CAPITALIZED_PATTERN]
        }
      },
      ...annotations
    }
  )
}

const UNCAPITALIZED_PATTERN = "^[^A-Z]?.*$"

/**
 * Validates that the first character of a string is unchanged by
 * `toLowerCase()`.
 *
 * **Details**
 *
 * Empty strings pass. Strings whose first character has no uppercase form, such
 * as a digit, punctuation mark, or whitespace, also pass.
 *
 * @category String checks
 * @since 4.0.0
 */
export function isUncapitalized(annotations?: Annotations.Filter) {
  return makeFilter(
    (s: string) => s.charAt(0).toLowerCase() === s.charAt(0),
    {
      expected: "a string with the first character in lowercase",
      meta: {
        _tag: "isUncapitalized",
        regExp: new globalThis.RegExp(UNCAPITALIZED_PATTERN)
      },
      arbitrary: {
        constraint: {
          patterns: [UNCAPITALIZED_PATTERN]
        }
      },
      ...annotations
    }
  )
}

/**
 * Validates that a number is finite (not `Infinity`, `-Infinity`, or `NaN`).
 *
 * **Details**
 *
 * JSON Schema:
 *
 * This check does not have a direct JSON Schema equivalent, but ensures the
 * number is valid and finite.
 *
 * Arbitrary:
 *
 * When generating test data with fast-check, this applies `noNaN: true` and
 * `noInfinity: true` constraints to ensure generated numbers are finite.
 *
 * @category Number checks
 * @since 4.0.0
 */
export function isFinite(annotations?: Annotations.Filter) {
  return makeFilter(
    (n: number) => globalThis.Number.isFinite(n),
    {
      expected: "a finite number",
      meta: {
        _tag: "isFinite"
      },
      arbitrary: {
        constraint: {
          noInfinity: true,
          noNaN: true
        }
      },
      ...annotations
    }
  )
}

/**
 * Creates a greater-than (`>`) check for any ordered type from an
 * `Order.Order` instance.
 *
 * @category Order checks
 * @since 4.0.0
 */
export function makeIsGreaterThan<T>(options: {
  readonly order: Order.Order<T>
  readonly annotate?: ((exclusiveMinimum: T) => Annotations.Filter) | undefined
  readonly formatter?: Formatter<T> | undefined
}) {
  const gt = Order.isGreaterThan(options.order)
  const formatter = options.formatter ?? format
  return (exclusiveMinimum: T, annotations?: Annotations.Filter) => {
    return makeFilter<T>(
      (input) => gt(input, exclusiveMinimum),
      {
        expected: `a value greater than ${formatter(exclusiveMinimum)}`,
        arbitrary: {
          constraint: {
            ordered: {
              order: options.order,
              minimum: exclusiveMinimum,
              exclusiveMinimum: true
            }
          }
        },
        ...options.annotate?.(exclusiveMinimum),
        ...annotations
      }
    )
  }
}

/**
 * Creates a greater-than-or-equal-to (`>=`) check for any ordered type from an
 * `Order.Order` instance.
 *
 * @category Order checks
 * @since 4.0.0
 */
export function makeIsGreaterThanOrEqualTo<T>(options: {
  readonly order: Order.Order<T>
  readonly annotate?: ((exclusiveMinimum: T) => Annotations.Filter) | undefined
  readonly formatter?: Formatter<T> | undefined
}) {
  const gte = Order.isGreaterThanOrEqualTo(options.order)
  const formatter = options.formatter ?? format
  return (minimum: T, annotations?: Annotations.Filter) => {
    return makeFilter<T>(
      (input) => gte(input, minimum),
      {
        expected: `a value greater than or equal to ${formatter(minimum)}`,
        arbitrary: {
          constraint: {
            ordered: {
              order: options.order,
              minimum
            }
          }
        },
        ...options.annotate?.(minimum),
        ...annotations
      }
    )
  }
}

/**
 * Creates a less-than (`<`) check for any ordered type from an `Order.Order`
 * instance.
 *
 * @category Order checks
 * @since 4.0.0
 */
export function makeIsLessThan<T>(options: {
  readonly order: Order.Order<T>
  readonly annotate?: ((exclusiveMaximum: T) => Annotations.Filter) | undefined
  readonly formatter?: Formatter<T> | undefined
}) {
  const lt = Order.isLessThan(options.order)
  const formatter = options.formatter ?? format
  return (exclusiveMaximum: T, annotations?: Annotations.Filter) => {
    return makeFilter<T>(
      (input) => lt(input, exclusiveMaximum),
      {
        expected: `a value less than ${formatter(exclusiveMaximum)}`,
        arbitrary: {
          constraint: {
            ordered: {
              order: options.order,
              maximum: exclusiveMaximum,
              exclusiveMaximum: true
            }
          }
        },
        ...options.annotate?.(exclusiveMaximum),
        ...annotations
      }
    )
  }
}

/**
 * Creates a less-than-or-equal-to (`<=`) check for any ordered type from an
 * `Order.Order` instance.
 *
 * @category Order checks
 * @since 4.0.0
 */
export function makeIsLessThanOrEqualTo<T>(options: {
  readonly order: Order.Order<T>
  readonly annotate?: ((exclusiveMaximum: T) => Annotations.Filter) | undefined
  readonly formatter?: Formatter<T> | undefined
}) {
  const lte = Order.isLessThanOrEqualTo(options.order)
  const formatter = options.formatter ?? format
  return (maximum: T, annotations?: Annotations.Filter) => {
    return makeFilter<T>(
      (input) => lte(input, maximum),
      {
        expected: `a value less than or equal to ${formatter(maximum)}`,
        arbitrary: {
          constraint: {
            ordered: {
              order: options.order,
              maximum
            }
          }
        },
        ...options.annotate?.(maximum),
        ...annotations
      }
    )
  }
}

/**
 * Creates an inclusive or exclusive range check for any ordered type from an
 * `Order.Order` instance.
 *
 * @category Order checks
 * @since 4.0.0
 */
export function makeIsBetween<T>(deriveOptions: {
  readonly order: Order.Order<T>
  readonly annotate?:
    | ((options: {
      readonly minimum: T
      readonly maximum: T
      readonly exclusiveMinimum?: boolean | undefined
      readonly exclusiveMaximum?: boolean | undefined
    }) => Annotations.Filter)
    | undefined
  readonly formatter?: Formatter<T> | undefined
}) {
  const greaterThanOrEqualTo = Order.isGreaterThanOrEqualTo(deriveOptions.order)
  const greaterThan = Order.isGreaterThan(deriveOptions.order)
  const lessThanOrEqualTo = Order.isLessThanOrEqualTo(deriveOptions.order)
  const lessThan = Order.isLessThan(deriveOptions.order)
  const formatter = deriveOptions.formatter ?? format
  return (options: {
    readonly minimum: T
    readonly maximum: T
    readonly exclusiveMinimum?: boolean | undefined
    readonly exclusiveMaximum?: boolean | undefined
  }, annotations?: Annotations.Filter) => {
    const gte = options.exclusiveMinimum ? greaterThan : greaterThanOrEqualTo
    const lte = options.exclusiveMaximum ? lessThan : lessThanOrEqualTo
    return makeFilter<T>(
      (input) => gte(input, options.minimum) && lte(input, options.maximum),
      {
        expected: `a value between ${formatter(options.minimum)}${options.exclusiveMinimum ? " (excluded)" : ""} and ${
          formatter(options.maximum)
        }${options.exclusiveMaximum ? " (excluded)" : ""}`,
        arbitrary: {
          constraint: {
            ordered: {
              order: deriveOptions.order,
              minimum: options.minimum,
              maximum: options.maximum,
              ...(options.exclusiveMinimum && { exclusiveMinimum: true }),
              ...(options.exclusiveMaximum && { exclusiveMaximum: true })
            }
          }
        },
        ...deriveOptions.annotate?.(options),
        ...annotations
      }
    )
  }
}

/**
 * Creates a divisibility check for any numeric type from a remainder function
 * and a zero value.
 *
 * @category Numeric checks
 * @since 4.0.0
 */
export function makeIsMultipleOf<T>(options: {
  readonly remainder: (input: T, divisor: T) => T
  readonly zero: NoInfer<T>
  readonly annotate?: ((divisor: T) => Annotations.Filter) | undefined
  readonly formatter?: Formatter<T> | undefined
}) {
  return (divisor: T, annotations?: Annotations.Filter) => {
    const formatter = options.formatter ?? format
    return makeFilter<T>(
      (input) => options.remainder(input, divisor) === options.zero,
      {
        expected: `a value that is a multiple of ${formatter(divisor)}`,
        ...options.annotate?.(divisor),
        ...annotations
      }
    )
  }
}

/**
 * Validates that a number is greater than the specified value (exclusive).
 *
 * **Details**
 *
 * JSON Schema:
 *
 * This check corresponds to the `exclusiveMinimum` constraint in JSON Schema.
 *
 * Arbitrary:
 *
 * When generating test data with fast-check, this applies an
 * `exclusiveMinimum` constraint to ensure generated numbers are greater than
 * the specified value.
 *
 * @category Number checks
 * @since 4.0.0
 */
export const isGreaterThan = makeIsGreaterThan({
  order: Order.Number,
  annotate: (exclusiveMinimum) => ({
    meta: {
      _tag: "isGreaterThan",
      exclusiveMinimum
    }
  })
})

/**
 * Validates that a number is greater than or equal to the specified value
 * (inclusive).
 *
 * **Details**
 *
 * JSON Schema:
 *
 * This check corresponds to the `minimum` constraint in JSON Schema.
 *
 * Arbitrary:
 *
 * When generating test data with fast-check, this applies a `minimum` constraint
 * to ensure generated numbers are greater than or equal to the specified value.
 *
 * @category Number checks
 * @since 4.0.0
 */
export const isGreaterThanOrEqualTo = makeIsGreaterThanOrEqualTo({
  order: Order.Number,
  annotate: (minimum) => ({
    meta: {
      _tag: "isGreaterThanOrEqualTo",
      minimum
    }
  })
})

/**
 * Validates that a number is less than the specified value (exclusive).
 *
 * **Details**
 *
 * JSON Schema:
 *
 * This check corresponds to the `exclusiveMaximum` constraint in JSON Schema.
 *
 * Arbitrary:
 *
 * When generating test data with fast-check, this applies an
 * `exclusiveMaximum` constraint to ensure generated numbers are less than the
 * specified value.
 *
 * @category Number checks
 * @since 4.0.0
 */
export const isLessThan = makeIsLessThan({
  order: Order.Number,
  annotate: (exclusiveMaximum) => ({
    meta: {
      _tag: "isLessThan",
      exclusiveMaximum
    }
  })
})

/**
 * Validates that a number is less than or equal to the specified value
 * (inclusive).
 *
 * **Details**
 *
 * JSON Schema:
 *
 * This check corresponds to the `maximum` constraint in JSON Schema.
 *
 * Arbitrary:
 *
 * When generating test data with fast-check, this applies a `maximum` constraint
 * to ensure generated numbers are less than or equal to the specified value.
 *
 * @category Number checks
 * @since 4.0.0
 */
export const isLessThanOrEqualTo = makeIsLessThanOrEqualTo({
  order: Order.Number,
  annotate: (maximum) => ({
    meta: {
      _tag: "isLessThanOrEqualTo",
      maximum
    }
  })
})

/**
 * Validates that a number is within a specified range. The range boundaries can
 * be inclusive or exclusive based on the provided options.
 *
 * **Details**
 *
 * JSON Schema:
 *
 * This check corresponds to `minimum`/`maximum` or `exclusiveMinimum`/`exclusiveMaximum`
 * constraints in JSON Schema, depending on the options provided.
 *
 * Arbitrary:
 *
 * When generating test data with fast-check, this applies `minimum` and
 * `maximum` constraints with optional `exclusiveMinimum` and
 * `exclusiveMaximum` flags to ensure generated numbers fall within the
 * specified range.
 *
 * @category Number checks
 * @since 4.0.0
 */
export const isBetween = makeIsBetween({
  order: Order.Number,
  annotate: (options) => {
    return {
      meta: {
        _tag: "isBetween",
        ...options
      }
    }
  }
})

/**
 * Validates that a number is a multiple of the specified divisor.
 *
 * **Details**
 *
 * JSON Schema:
 *
 * This check corresponds to the `multipleOf` constraint in JSON Schema.
 *
 * Arbitrary:
 *
 * When generating test data with fast-check, this applies constraints to ensure
 * generated numbers are multiples of the specified divisor.
 *
 * @category Number checks
 * @since 4.0.0
 */
export const isMultipleOf = makeIsMultipleOf({
  remainder,
  zero: 0,
  annotate: (divisor) => ({
    expected: `a value that is a multiple of ${divisor}`,
    meta: {
      _tag: "isMultipleOf",
      divisor
    }
  })
})

/**
 * Validates that a number is a safe integer (within the safe integer range
 * that can be exactly represented in JavaScript).
 *
 * **Details**
 *
 * JSON Schema:
 *
 * This check corresponds to the `type: "integer"` constraint in JSON Schema.
 *
 * Arbitrary:
 *
 * When generating test data with fast-check, this applies an `integer: true`
 * constraint to ensure generated numbers are integers.
 *
 * @category Integer checks
 * @since 4.0.0
 */
export function isInt(annotations?: Annotations.Filter) {
  return makeFilter(
    (n: number) => globalThis.Number.isSafeInteger(n),
    {
      expected: "an integer",
      meta: {
        _tag: "isInt"
      },
      arbitrary: {
        constraint: {
          integer: true
        }
      },
      ...annotations
    }
  )
}

/**
 * Validates that a number is a 32-bit signed integer (range: -2,147,483,648 to
 * 2,147,483,647).
 *
 * **Details**
 *
 * JSON Schema:
 *
 * This check corresponds to the `format: "int32"` constraint in OpenAPI 3.1,
 * or `minimum`/`maximum` constraints in other JSON Schema targets.
 *
 * Arbitrary:
 *
 * When generating test data with fast-check, this applies integer and range
 * constraints to ensure generated numbers are 32-bit signed integers.
 *
 * @category Integer checks
 * @since 4.0.0
 */
export function isInt32(annotations?: Annotations.Filter) {
  return new SchemaAST.FilterGroup(
    [
      isInt(),
      isBetween({ minimum: -2147483648, maximum: 2147483647 })
    ],
    {
      expected: "a 32-bit integer",
      ...annotations
    }
  )
}

/**
 * Validates that a number is a 32-bit unsigned integer (range: 0 to
 * 4,294,967,295).
 *
 * **Details**
 *
 * JSON Schema:
 *
 * This check corresponds to the `format: "uint32"` constraint in OpenAPI 3.1,
 * or `minimum`/`maximum` constraints in other JSON Schema targets.
 *
 * Arbitrary:
 *
 * When generating test data with fast-check, this applies integer and range
 * constraints to ensure generated numbers are 32-bit unsigned integers.
 *
 * @category Integer checks
 * @since 4.0.0
 */
export function isUint32(annotations?: Annotations.Filter) {
  return new SchemaAST.FilterGroup(
    [
      isInt(),
      isBetween({ minimum: 0, maximum: 4294967295 })
    ],
    {
      expected: "a 32-bit unsigned integer",
      ...annotations
    }
  )
}

/**
 * Validates that a Date object represents a valid date (not an invalid date
 * like `new Date("invalid")`).
 *
 * **Details**
 *
 * JSON Schema:
 *
 * This check does not have a direct JSON Schema equivalent, as JSON Schema
 * validates date strings, not Date objects.
 *
 * Arbitrary:
 *
 * When generating test data with fast-check, this applies a `valid: true`
 * constraint to ensure generated Date objects are valid.
 *
 * @category Date checks
 * @since 4.0.0
 */
export function isDateValid(annotations?: Annotations.Filter) {
  return makeFilter<globalThis.Date>(
    (date) => !isNaN(date.getTime()),
    {
      expected: "a valid date",
      meta: {
        _tag: "isDateValid"
      },
      arbitrary: {
        constraint: {
          valid: true
        }
      },
      ...annotations
    }
  )
}

/**
 * Validates that a Date is greater than the specified value (exclusive).
 *
 * **Details**
 *
 * Arbitrary:
 *
 * When generating test data with fast-check, this applies a `min` constraint of
 * one millisecond after the specified value to ensure generated Date objects are
 * greater than it.
 *
 * @category Date checks
 * @since 4.0.0
 */
export const isGreaterThanDate = makeIsGreaterThan({
  order: Order.Date,
  annotate: (exclusiveMinimum) => ({
    meta: {
      _tag: "isGreaterThanDate",
      exclusiveMinimum
    }
  })
})

/**
 * Validates that a Date is greater than or equal to the specified date
 * (inclusive).
 *
 * **Details**
 *
 * JSON Schema:
 *
 * This check does not have a direct JSON Schema equivalent, as JSON Schema
 * validates date strings, not Date objects.
 *
 * Arbitrary:
 *
 * When generating test data with fast-check, this applies a `min` constraint
 * to ensure generated Date objects are greater than or equal to the specified
 * date.
 *
 * @category Date checks
 * @since 4.0.0
 */
export const isGreaterThanOrEqualToDate = makeIsGreaterThanOrEqualTo({
  order: Order.Date,
  annotate: (minimum) => ({
    meta: {
      _tag: "isGreaterThanOrEqualToDate",
      minimum
    }
  })
})

/**
 * Validates that a Date is less than the specified value (exclusive).
 *
 * **Details**
 *
 * Arbitrary:
 *
 * When generating test data with fast-check, this applies a `max` constraint of
 * one millisecond before the specified value to ensure generated Date objects
 * are less than it.
 *
 * @category Date checks
 * @since 4.0.0
 */
export const isLessThanDate = makeIsLessThan({
  order: Order.Date,
  annotate: (exclusiveMaximum) => ({
    meta: {
      _tag: "isLessThanDate",
      exclusiveMaximum
    }
  })
})

/**
 * Validates that a Date is less than or equal to the specified date
 * (inclusive).
 *
 * **Details**
 *
 * JSON Schema:
 *
 * This check does not have a direct JSON Schema equivalent, as JSON Schema
 * validates date strings, not Date objects.
 *
 * Arbitrary:
 *
 * When generating test data with fast-check, this applies a `max` constraint
 * to ensure generated Date objects are less than or equal to the specified
 * date.
 *
 * @category Date checks
 * @since 4.0.0
 */
export const isLessThanOrEqualToDate = makeIsLessThanOrEqualTo({
  order: Order.Date,
  annotate: (maximum) => ({
    meta: {
      _tag: "isLessThanOrEqualToDate",
      maximum
    }
  })
})

/**
 * Validates that a Date is within a specified range. The range boundaries can
 * be inclusive or exclusive based on the provided options.
 *
 * **Details**
 *
 * JSON Schema:
 *
 * This check does not have a direct JSON Schema equivalent, as JSON Schema
 * validates date strings, not Date objects.
 *
 * Arbitrary:
 *
 * When generating test data with fast-check, this applies `min` and `max`
 * constraints to ensure generated Date objects fall within the specified range,
 * shifting exclusive bounds by one millisecond.
 *
 * @category Date checks
 * @since 4.0.0
 */
export const isBetweenDate = makeIsBetween({
  order: Order.Date,
  annotate: (options) => ({
    meta: {
      _tag: "isBetweenDate",
      ...options
    }
  })
})

/**
 * Validates that a BigInt is greater than the specified value (exclusive).
 *
 * **Details**
 *
 * Arbitrary:
 *
 * When generating test data with fast-check, this applies a `min` constraint of
 * `exclusiveMinimum + 1n` to ensure generated BigInts are greater than the
 * specified value.
 *
 * @category BigInt checks
 * @since 4.0.0
 */
export const isGreaterThanBigInt = makeIsGreaterThan({
  order: Order.BigInt,
  annotate: (exclusiveMinimum) => ({
    meta: {
      _tag: "isGreaterThanBigInt",
      exclusiveMinimum
    }
  })
})

/**
 * Validates that a BigInt is greater than or equal to the specified value
 * (inclusive).
 *
 * **Details**
 *
 * Arbitrary:
 *
 * When generating test data with fast-check, this applies a `min` constraint
 * to ensure generated BigInt values are greater than or equal to the specified
 * value.
 *
 * @category BigInt checks
 * @since 4.0.0
 */
export const isGreaterThanOrEqualToBigInt = makeIsGreaterThanOrEqualTo({
  order: Order.BigInt,
  annotate: (minimum) => ({
    meta: {
      _tag: "isGreaterThanOrEqualToBigInt",
      minimum
    }
  })
})

/**
 * Validates that a BigInt is less than the specified value (exclusive).
 *
 * **Details**
 *
 * Arbitrary:
 *
 * When generating test data with fast-check, this applies a `max` constraint of
 * `exclusiveMaximum - 1n` to ensure generated BigInts are less than the
 * specified value.
 *
 * @category BigInt checks
 * @since 4.0.0
 */
export const isLessThanBigInt = makeIsLessThan({
  order: Order.BigInt,
  annotate: (exclusiveMaximum) => ({
    meta: {
      _tag: "isLessThanBigInt",
      exclusiveMaximum
    }
  })
})

/**
 * Validates that a BigInt is less than or equal to the specified value
 * (inclusive).
 *
 * **Details**
 *
 * Arbitrary:
 *
 * When generating test data with fast-check, this applies a `max` constraint
 * to ensure generated BigInt values are less than or equal to the specified
 * value.
 *
 * @category BigInt checks
 * @since 4.0.0
 */
export const isLessThanOrEqualToBigInt = makeIsLessThanOrEqualTo({
  order: Order.BigInt,
  annotate: (maximum) => ({
    meta: {
      _tag: "isLessThanOrEqualToBigInt",
      maximum
    }
  })
})

/**
 * Validates that a BigInt is within a specified range. The range boundaries can
 * be inclusive or exclusive based on the provided options.
 *
 * **Details**
 *
 * Arbitrary:
 *
 * When generating test data with fast-check, this applies `min` and `max`
 * constraints to ensure generated BigInt values fall within the specified
 * range.
 *
 * @category BigInt checks
 * @since 4.0.0
 */
export const isBetweenBigInt = makeIsBetween({
  order: Order.BigInt,
  annotate: (options) => ({
    meta: {
      _tag: "isBetweenBigInt",
      ...options
    }
  })
})

/**
 * Validates that a BigDecimal is greater than the specified value (exclusive).
 *
 * @category BigDecimal checks
 * @since 4.0.0
 */
export const isGreaterThanBigDecimal = makeIsGreaterThan({
  order: BigDecimal_.Order,
  formatter: (bd) => BigDecimal_.format(bd)
})

/**
 * Validates that a BigDecimal is greater than or equal to the specified value
 * (inclusive).
 *
 * @category BigDecimal checks
 * @since 4.0.0
 */
export const isGreaterThanOrEqualToBigDecimal = makeIsGreaterThanOrEqualTo({
  order: BigDecimal_.Order,
  formatter: (bd) => BigDecimal_.format(bd)
})

/**
 * Validates that a BigDecimal is less than the specified value (exclusive).
 *
 * @category BigDecimal checks
 * @since 4.0.0
 */
export const isLessThanBigDecimal = makeIsLessThan({
  order: BigDecimal_.Order,
  formatter: (bd) => BigDecimal_.format(bd)
})

/**
 * Validates that a BigDecimal is less than or equal to the specified value
 * (inclusive).
 *
 * @category BigDecimal checks
 * @since 4.0.0
 */
export const isLessThanOrEqualToBigDecimal = makeIsLessThanOrEqualTo({
  order: BigDecimal_.Order,
  formatter: (bd) => BigDecimal_.format(bd)
})

/**
 * Validates that a `BigDecimal` is within a specified range.
 *
 * **Details**
 *
 * The minimum and maximum boundaries are inclusive by default. Pass
 * `exclusiveMinimum` or `exclusiveMaximum` to exclude either boundary.
 *
 * @category BigDecimal checks
 * @since 4.0.0
 */
export const isBetweenBigDecimal = makeIsBetween({
  order: BigDecimal_.Order,
  formatter: (bd) => BigDecimal_.format(bd)
})

/**
 * Validates that a value has at least the specified length. Works with strings
 * and arrays.
 *
 * **Details**
 *
 * JSON Schema:
 *
 * This check corresponds to the `minLength` constraint for strings or the
 * `minItems` constraint for arrays in JSON Schema.
 *
 * Arbitrary:
 *
 * When generating test data with fast-check, this applies a `minLength`
 * constraint to ensure generated strings or arrays have at least the required
 * length.
 *
 * **Example** (Checking minimum length)
 *
 * ```ts
 * import { Schema } from "effect"
 *
 * const NonEmptyStringSchema = Schema.String.check(Schema.isMinLength(1))
 * const NonEmptyArraySchema = Schema.Array(Schema.Number).check(Schema.isMinLength(1))
 * ```
 *
 * @category Length checks
 * @since 4.0.0
 */
export function isMinLength(minLength: number, annotations?: Annotations.Filter) {
  minLength = Math.max(0, Math.floor(minLength))
  return makeFilter<{ readonly length: number }>(
    (input) => input.length >= minLength,
    {
      expected: `a value with a length of at least ${minLength}`,
      meta: {
        _tag: "isMinLength",
        minLength
      },
      [SchemaAST.STRUCTURAL_ANNOTATION_KEY]: true,
      arbitrary: {
        constraint: {
          minLength
        }
      },
      ...annotations
    }
  )
}

/**
 * Validates that a value has at least one element. Works with strings and arrays.
 * This is equivalent to `isMinLength(1)`.
 *
 * **Details**
 *
 * JSON Schema:
 *
 * This check corresponds to the `minLength: 1` constraint for strings or the
 * `minItems: 1` constraint for arrays in JSON Schema.
 *
 * Arbitrary:
 *
 * When generating test data with fast-check, this applies a `minLength: 1`
 * constraint to ensure generated strings or arrays are non-empty.
 *
 * @category Length checks
 * @since 4.0.0
 */
export function isNonEmpty(annotations?: Annotations.Filter) {
  return isMinLength(1, annotations)
}

/**
 * Validates that a value has at most the specified length. Works with strings
 * and arrays.
 *
 * **Details**
 *
 * JSON Schema:
 *
 * This check corresponds to the `maxLength` constraint for strings or the
 * `maxItems` constraint for arrays in JSON Schema.
 *
 * Arbitrary:
 *
 * When generating test data with fast-check, this applies a `maxLength`
 * constraint to ensure generated strings or arrays have at most the required
 * length.
 *
 * @category Length checks
 * @since 4.0.0
 */
export function isMaxLength(maxLength: number, annotations?: Annotations.Filter) {
  maxLength = Math.max(0, Math.floor(maxLength))
  return makeFilter<{ readonly length: number }>(
    (input) => input.length <= maxLength,
    {
      expected: `a value with a length of at most ${maxLength}`,
      meta: {
        _tag: "isMaxLength",
        maxLength
      },
      [SchemaAST.STRUCTURAL_ANNOTATION_KEY]: true,
      arbitrary: {
        constraint: {
          maxLength
        }
      },
      ...annotations
    }
  )
}

/**
 * Validates that a value's length is within the specified range. Works with
 * strings and arrays.
 *
 * **Details**
 *
 * JSON Schema:
 *
 * This check corresponds to `minLength`/`maxLength` constraints for strings
 * or `minItems`/`maxItems` constraints for arrays in JSON Schema.
 *
 * Arbitrary:
 *
 * When generating test data with fast-check, this applies `minLength` and
 * `maxLength` constraints to ensure generated strings or arrays have a length
 * within the specified range.
 *
 * @category Length checks
 * @since 4.0.0
 */
export function isLengthBetween(minimum: number, maximum: number, annotations?: Annotations.Filter) {
  minimum = Math.max(0, Math.floor(minimum))
  maximum = Math.max(0, Math.floor(maximum))
  return makeFilter<{ readonly length: number }>(
    (input) => input.length >= minimum && input.length <= maximum,
    {
      expected: minimum === maximum
        ? `a value with a length of ${minimum}`
        : `a value with a length between ${minimum} and ${maximum}`,
      meta: {
        _tag: "isLengthBetween",
        minimum,
        maximum
      },
      [SchemaAST.STRUCTURAL_ANNOTATION_KEY]: true,
      arbitrary: {
        constraint: {
          minLength: minimum,
          maxLength: maximum
        }
      },
      ...annotations
    }
  )
}

/**
 * Validates that a value has at least the specified size. Works with values
 * that have a `size` property, such as `Set` or `Map`.
 *
 * **Details**
 *
 * JSON Schema:
 *
 * This check does not have a direct JSON Schema equivalent, as it applies to
 * values with a `size` property rather than standard JSON Schema types.
 *
 * Arbitrary:
 *
 * When generating test data with fast-check, this applies a node-local
 * `minLength` constraint. Generators for values with a final `.size`, such as
 * sets and maps, interpret it as final cardinality.
 *
 * @category Size checks
 * @since 4.0.0
 */
export function isMinSize(minSize: number, annotations?: Annotations.Filter) {
  minSize = Math.max(0, Math.floor(minSize))
  return makeFilter<{ readonly size: number }>(
    (input) => input.size >= minSize,
    {
      expected: `a value with a size of at least ${minSize}`,
      meta: {
        _tag: "isMinSize",
        minSize
      },
      [SchemaAST.STRUCTURAL_ANNOTATION_KEY]: true,
      arbitrary: {
        constraint: {
          minLength: minSize
        }
      },
      ...annotations
    }
  )
}

/**
 * Validates that a value has at most the specified size. Works with values
 * that have a `size` property, such as `Set` or `Map`.
 *
 * **Details**
 *
 * JSON Schema:
 *
 * This check does not have a direct JSON Schema equivalent, as it applies to
 * values with a `size` property rather than standard JSON Schema types.
 *
 * Arbitrary:
 *
 * When generating test data with fast-check, this applies a node-local
 * `maxLength` constraint. Generators for values with a final `.size`, such as
 * sets and maps, interpret it as final cardinality.
 *
 * @category Size checks
 * @since 4.0.0
 */
export function isMaxSize(maxSize: number, annotations?: Annotations.Filter) {
  maxSize = Math.max(0, Math.floor(maxSize))
  return makeFilter<{ readonly size: number }>(
    (input) => input.size <= maxSize,
    {
      expected: `a value with a size of at most ${maxSize}`,
      meta: {
        _tag: "isMaxSize",
        maxSize
      },
      [SchemaAST.STRUCTURAL_ANNOTATION_KEY]: true,
      arbitrary: {
        constraint: {
          maxLength: maxSize
        }
      },
      ...annotations
    }
  )
}

/**
 * Validates that a value's size is within the specified range. Works with
 * values that have a `size` property, such as `Set` or `Map`.
 *
 * **Details**
 *
 * JSON Schema:
 *
 * This check does not have a direct JSON Schema equivalent, as it applies to
 * values with a `size` property rather than standard JSON Schema types.
 *
 * Arbitrary:
 *
 * When generating test data with fast-check, this applies node-local
 * `minLength` and `maxLength` constraints. Generators for values with a final
 * `.size`, such as sets and maps, interpret them as final cardinality.
 *
 * @category Size checks
 * @since 4.0.0
 */
export function isSizeBetween(minimum: number, maximum: number, annotations?: Annotations.Filter) {
  minimum = Math.max(0, Math.floor(minimum))
  maximum = Math.max(0, Math.floor(maximum))
  return makeFilter<{ readonly size: number }>(
    (input) => input.size >= minimum && input.size <= maximum,
    {
      expected: minimum === maximum
        ? `a value with a size of ${minimum}`
        : `a value with a size between ${minimum} and ${maximum}`,
      meta: {
        _tag: "isSizeBetween",
        minimum,
        maximum
      },
      [SchemaAST.STRUCTURAL_ANNOTATION_KEY]: true,
      arbitrary: {
        constraint: {
          minLength: minimum,
          maxLength: maximum
        }
      },
      ...annotations
    }
  )
}

/**
 * Validates that an object contains at least the specified number of
 * properties. This includes both string and symbol keys when counting
 * properties.
 *
 * **Details**
 *
 * JSON Schema:
 *
 * This check corresponds to the `minProperties` constraint in JSON Schema.
 *
 * Arbitrary:
 *
 * When generating test data with fast-check, this applies a node-local
 * `minLength` constraint. Object generators interpret it as the final number
 * of own properties.
 *
 * @category Object checks
 * @since 4.0.0
 */
export function isMinProperties(minProperties: number, annotations?: Annotations.Filter) {
  minProperties = Math.max(0, Math.floor(minProperties))
  return makeFilter<object>(
    (input) => Reflect.ownKeys(input).length >= minProperties,
    {
      expected: `a value with at least ${minProperties === 1 ? "1 entry" : `${minProperties} entries`}`,
      meta: {
        _tag: "isMinProperties",
        minProperties
      },
      [SchemaAST.STRUCTURAL_ANNOTATION_KEY]: true,
      arbitrary: {
        constraint: {
          minLength: minProperties
        }
      },
      ...annotations
    }
  )
}

/**
 * Validates that an object contains at most the specified number of properties.
 * This includes both string and symbol keys when counting properties.
 *
 * **Details**
 *
 * JSON Schema:
 *
 * This check corresponds to the `maxProperties` constraint in JSON Schema.
 *
 * Arbitrary:
 *
 * When generating test data with fast-check, this applies a node-local
 * `maxLength` constraint. Object generators interpret it as the final number
 * of own properties.
 *
 * @category Object checks
 * @since 4.0.0
 */
export function isMaxProperties(maxProperties: number, annotations?: Annotations.Filter) {
  maxProperties = Math.max(0, Math.floor(maxProperties))
  return makeFilter<object>(
    (input) => Reflect.ownKeys(input).length <= maxProperties,
    {
      expected: `a value with at most ${maxProperties === 1 ? "1 entry" : `${maxProperties} entries`}`,
      meta: {
        _tag: "isMaxProperties",
        maxProperties
      },
      [SchemaAST.STRUCTURAL_ANNOTATION_KEY]: true,
      arbitrary: {
        constraint: {
          maxLength: maxProperties
        }
      },
      ...annotations
    }
  )
}

/**
 * Validates that an object contains between `minimum` and `maximum` properties (inclusive).
 * This includes both string and symbol keys when counting properties.
 *
 * **Details**
 *
 * JSON Schema:
 *
 * This check corresponds to `minProperties` and `maxProperties`
 * constraints in JSON Schema.
 *
 * Arbitrary:
 *
 * When generating test data with fast-check, this applies node-local
 * `minLength` and `maxLength` constraints. Object generators interpret them as
 * the final number of own properties.
 *
 * @category Object checks
 * @since 4.0.0
 */
export function isPropertiesLengthBetween(minimum: number, maximum: number, annotations?: Annotations.Filter) {
  minimum = Math.max(0, Math.floor(minimum))
  maximum = Math.max(0, Math.floor(maximum))
  return makeFilter<object>(
    (input) => Reflect.ownKeys(input).length >= minimum && Reflect.ownKeys(input).length <= maximum,
    {
      expected: minimum === maximum
        ? `a value with exactly ${minimum === 1 ? "1 entry" : `${minimum} entries`}`
        : `a value with between ${minimum} and ${maximum} entries`,
      meta: {
        _tag: "isPropertiesLengthBetween",
        minimum,
        maximum
      },
      [SchemaAST.STRUCTURAL_ANNOTATION_KEY]: true,
      arbitrary: {
        constraint: {
          minLength: minimum,
          maxLength: maximum
        }
      },
      ...annotations
    }
  )
}

/**
 * Validates that every own property key of an object satisfies the encoded side
 * of the provided key schema.
 *
 * **Details**
 *
 * This check uses `Reflect.ownKeys`, so symbol keys are validated in addition to
 * string property names.
 *
 * JSON Schema:
 * For string property names, this corresponds to the `propertyNames` constraint
 * in JSON Schema.
 *
 * @category Object checks
 * @since 4.0.0
 */
export function isPropertyNames(keySchema: Constraint, annotations?: Annotations.Filter) {
  const propertyNames = toEncoded(keySchema)
  const parser = SchemaParser._issue(propertyNames.ast)
  return makeFilter<object>(
    (input, ast, options) => {
      const keys = Reflect.ownKeys(input)
      const issues: Array<SchemaIssue.Issue> = []
      for (const key of keys) {
        const issue = parser(key, options)
        if (issue !== undefined) {
          issues.push(new SchemaIssue.Pointer([key], issue))
          if (options.errors === "first") break
        }
      }
      if (Arr.isArrayNonEmpty(issues)) {
        return new SchemaIssue.Composite(ast, Option_.some(input), issues)
      }
      return true
    },
    {
      expected: "an object with property names matching the schema",
      meta: {
        _tag: "isPropertyNames",
        propertyNames: propertyNames.ast
      },
      [SchemaAST.STRUCTURAL_ANNOTATION_KEY]: true,
      ...annotations
    }
  )
}

/**
 * Validates that all items in an array are unique according to Effect equality.
 *
 * **Details**
 *
 * JSON Schema:
 * This check corresponds to the `uniqueItems: true` constraint in JSON Schema.
 *
 * Arbitrary:
 * When generating test data with fast-check, this applies a node-local
 * `unique: true` constraint. Array generators translate it to `fast-check`
 * `uniqueArray` using Effect equality.
 *
 * @category Array checks
 * @since 4.0.0
 */
export function isUnique<T>(annotations?: Annotations.Filter) {
  const equivalence = Equal.asEquivalence<T>()
  return makeFilter<ReadonlyArray<T>>(
    (input) => Arr.dedupeWith(input, equivalence).length === input.length,
    {
      expected: "an array with unique items",
      meta: {
        _tag: "isUnique"
      },
      arbitrary: {
        constraint: {
          unique: true
        }
      },
      ...annotations
    }
  )
}

// -----------------------------------------------------------------------------
// Built-in Schemas
// -----------------------------------------------------------------------------

/**
 * Type-level representation of {@link NonEmptyString}.
 *
 * @category string
 * @since 3.10.0
 */
export interface NonEmptyString extends String {
  readonly "Rebuild": NonEmptyString
}

/**
 * Schema for non-empty strings. Validates that a string has at least one
 * character.
 *
 * @category string
 * @since 3.10.0
 */
export const NonEmptyString: NonEmptyString = String.check(isNonEmpty())

/**
 * Type-level representation of {@link Char}.
 *
 * @category string
 * @since 3.10.0
 */
export interface Char extends String {
  readonly "Rebuild": Char
}

/**
 * Schema for strings whose JavaScript `length` is exactly `1`.
 *
 * **When to use**
 *
 * Use to validate string values that must have `length === 1`.
 *
 * **Gotchas**
 *
 * This schema uses JavaScript `String.length`, so visible characters made from
 * multiple UTF-16 code units do not satisfy `length === 1`.
 *
 * @see {@link String} for unconstrained string values
 * @see {@link NonEmptyString} for strings with length greater than zero
 * @see {@link isLengthBetween} for the underlying length check
 *
 * @category string
 * @since 3.10.0
 */
export const Char: Char = String.check(isLengthBetween(1, 1))

/**
 * Type-level representation returned by {@link Option}.
 *
 * @category Option
 * @since 3.10.0
 */
export interface Option<A extends Constraint> extends
  declareConstructor<
    Option_.Option<A["Type"]>,
    Option_.Option<A["Encoded"]>,
    readonly [A],
    OptionIso<A>
  >
{
  readonly "Rebuild": Option<A>
  readonly value: A
}

/**
 * Iso representation used for `Option` schemas.
 *
 * **Details**
 *
 * `None` is represented as `{ _tag: "None" }`, while `Some` is represented as
 * `{ _tag: "Some", value }` using the wrapped schema's `Iso` type.
 *
 * @category Option
 * @since 4.0.0
 */
export type OptionIso<A extends Constraint> =
  | { readonly _tag: "None" }
  | { readonly _tag: "Some"; readonly value: A["Iso"] }

/**
 * Schema for `Option<A>` values.
 *
 * @category Option
 * @since 3.10.0
 */
export function Option<A extends Constraint>(value: A): Option<A> {
  const schema = declareConstructor<
    Option_.Option<A["Type"]>,
    Option_.Option<A["Encoded"]>,
    OptionIso<A>
  >()(
    [value],
    ([value]) => (input, ast, options) => {
      if (Option_.isOption(input)) {
        if (Option_.isNone(input)) {
          return Effect.succeedNone
        }
        return Effect.mapBothEager(
          SchemaParser.decodeUnknownEffect(value)(input.value, options),
          {
            onSuccess: Option_.some,
            onFailure: (issue) =>
              new SchemaIssue.Composite(ast, Option_.some(input), [new SchemaIssue.Pointer(["value"], issue)])
          }
        )
      }
      return Effect.fail(new SchemaIssue.InvalidType(ast, Option_.some(input)))
    },
    {
      typeConstructor: {
        _tag: "effect/Option"
      },
      generation: {
        runtime: `Schema.Option(?)`,
        Type: `Option.Option<?>`,
        importDeclaration: `import * as Option from "effect/Option"`
      },
      expected: "Option",
      toCodec: ([value]) =>
        link<Option_.Option<A["Encoded"]>>()(
          Union([
            Struct({ _tag: Literal("Some"), value }),
            Struct({ _tag: Literal("None") })
          ]),
          SchemaTransformation.transform({
            decode: (e) => e._tag === "None" ? Option_.none() : Option_.some(e.value),
            encode: (o) => (Option_.isSome(o) ? { _tag: "Some", value: o.value } as const : { _tag: "None" } as const)
          })
        ),
      toArbitrary: ([value]) => (fc, ctx) => {
        const terminal = fc.constant(Option_.none())
        const arbitrary = fc.oneof(
          terminal,
          value.arbitrary.map(Option_.some)
        )
        return withRecursion(fc, ctx, terminal, arbitrary)
      },
      toEquivalence: ([value]) => Option_.makeEquivalence(value),
      toFormatter: ([value]) =>
        Option_.match({
          onNone: () => "none()",
          onSome: (t) => `some(${value(t)})`
        })
    }
  )
  return make(schema.ast, { value })
}

/**
 * Type-level representation returned by {@link OptionFromNullOr}.
 *
 * @category Option
 * @since 3.10.0
 */
export interface OptionFromNullOr<S extends Constraint> extends decodeTo<Option<toType<S>>, NullOr<S>> {
  readonly "Rebuild": OptionFromNullOr<S>
}

/**
 * Decodes a nullable, required value `T` to a required `Option<T>` value.
 *
 * **Details**
 *
 * Decoding maps `null` to `None` and all other values to `Some`. Encoding maps
 * `None` to `null` and maps `Some` to its value.
 *
 * @category Option
 * @since 3.10.0
 */
export function OptionFromNullOr<S extends Constraint>(schema: S): OptionFromNullOr<S> {
  return NullOr(schema).pipe(decodeTo(
    Option(toType(schema)),
    SchemaTransformation.optionFromNullOr()
  ))
}

/**
 * Type-level representation returned by {@link OptionFromUndefinedOr}.
 *
 * @category Option
 * @since 3.10.0
 */
export interface OptionFromUndefinedOr<S extends Constraint> extends decodeTo<Option<toType<S>>, UndefinedOr<S>> {
  readonly "Rebuild": OptionFromUndefinedOr<S>
}

/**
 * Decodes a required value that may be `undefined` to a required `Option<T>`
 * value.
 *
 * **Details**
 *
 * Decoding maps `undefined` to `None` and all other values to `Some`. Encoding
 * maps `None` to `undefined` and maps `Some` to its value.
 *
 * @category Option
 * @since 3.10.0
 */
export function OptionFromUndefinedOr<S extends Constraint>(schema: S): OptionFromUndefinedOr<S> {
  return UndefinedOr(schema).pipe(decodeTo(
    Option(toType(schema)),
    SchemaTransformation.optionFromUndefinedOr()
  ))
}

/**
 * Type-level representation returned by {@link OptionFromNullishOr}.
 *
 * @category Option
 * @since 3.10.0
 */
export interface OptionFromNullishOr<S extends Constraint> extends decodeTo<Option<toType<S>>, NullishOr<S>> {
  readonly "Rebuild": OptionFromNullishOr<S>
}

/**
 * Decodes a nullish value `T` to a required `Option<T>` value.
 *
 * **Details**
 *
 * Decoding maps `null` and `undefined` to `None` and all other values to
 * `Some`. Encoding maps `None` to `null` or `undefined` depending on
 * `options.onNoneEncoding`, which defaults to `undefined`, and maps `Some` to
 * its value.
 *
 * @category Option
 * @since 3.10.0
 */
export function OptionFromNullishOr<S extends Constraint>(
  schema: S,
  options?: {
    onNoneEncoding: null | undefined
  }
): OptionFromNullishOr<S> {
  return NullishOr(schema).pipe(decodeTo(
    Option(toType(schema)),
    SchemaTransformation.optionFromNullishOr(options)
  ))
}

/**
 * Type-level representation returned by {@link OptionFromOptionalKey}.
 *
 * @category Option
 * @since 4.0.0
 */
export interface OptionFromOptionalKey<S extends Constraint> extends decodeTo<Option<toType<S>>, optionalKey<S>> {
  readonly "Rebuild": OptionFromOptionalKey<S>
}

/**
 * Decodes an optional value `A` to a required `Option<A>` value.
 *
 * **Details**
 *
 * Decoding maps a missing key to `None` and a present value to `Some`.
 * Encoding maps `None` to a missing key and maps `Some` to its value.
 *
 * @category Option
 * @since 4.0.0
 */
export function OptionFromOptionalKey<S extends Constraint>(schema: S): OptionFromOptionalKey<S> {
  return optionalKey(schema).pipe(decodeTo(
    Option(toType(schema)),
    SchemaTransformation.optionFromOptionalKey()
  ))
}

/**
 * Type-level representation returned by {@link OptionFromOptional}.
 *
 * @category Option
 * @since 4.0.0
 */
export interface OptionFromOptional<S extends Constraint> extends decodeTo<Option<toType<S>>, optional<S>> {
  readonly "Rebuild": OptionFromOptional<S>
}

/**
 * Decodes an optional or `undefined` value `A` to a required `Option<A>`
 * value.
 *
 * **Details**
 *
 * Decoding maps a missing key or a present `undefined` value to `None`, and
 * maps all other values to `Some`. Encoding maps `None` to a missing key and
 * maps `Some` to its value.
 *
 * @category Option
 * @since 4.0.0
 */
export function OptionFromOptional<S extends Constraint>(schema: S): OptionFromOptional<S> {
  return optional(schema).pipe(decodeTo(
    Option(toType(schema)),
    SchemaTransformation.optionFromOptional<any>()
  ))
}

/**
 * Type-level representation returned by {@link OptionFromOptionalNullOr}.
 *
 * @category Option
 * @since 4.0.0
 */
export interface OptionFromOptionalNullOr<S extends Constraint>
  extends decodeTo<Option<toType<S>>, optional<NullOr<S>>>
{
  readonly "Rebuild": OptionFromOptionalNullOr<S>
}

/**
 * Decodes an optional or `null` or `undefined` value `A` to a required `Option<A>`
 * value.
 *
 * **Details**
 *
 * Decoding maps a missing key, `undefined`, or `null` to `None`, and maps all
 * other values to `Some`. Encoding maps `Some` to its value. `None` is encoded
 * according to `options.onNoneEncoding`: `"omit"` encodes a missing key,
 * `null` encodes `null`, and `undefined` encodes `undefined`.
 *
 * @category Option
 * @since 4.0.0
 */
export function OptionFromOptionalNullOr<S extends Constraint>(
  schema: S,
  options?: {
    readonly onNoneEncoding: "omit" | null | undefined
  }
): OptionFromOptionalNullOr<S> {
  const onNoneEncoding = options === undefined ? "omit" : options.onNoneEncoding
  const noneValue = onNoneEncoding === null
    ? null as S["Type"] | null | undefined
    : undefined as S["Type"] | null | undefined
  return optional(NullOr(schema)).pipe(decodeTo(
    Option(toType(schema)),
    SchemaTransformation.transformOptional<Option_.Option<S["Type"]>, S["Type"] | null | undefined>({
      decode: (oe) => oe.pipe(Option_.filter(Predicate.isNotNullish), Option_.some),
      encode: onNoneEncoding === "omit"
        ? Option_.flatten
        : (ot) => Option_.some(Option_.getOrElse(Option_.flatten(ot), () => noneValue))
    })
  ))
}

/**
 * Type-level representation returned by {@link Result}.
 *
 * @category schemas
 * @since 4.0.0
 */
export interface Result<A extends Constraint, E extends Constraint> extends
  declareConstructor<
    Result_.Result<A["Type"], E["Type"]>,
    Result_.Result<A["Encoded"], E["Encoded"]>,
    readonly [A, E],
    ResultIso<A, E>
  >
{
  readonly "Rebuild": Result<A, E>
  readonly success: A
  readonly failure: E
}

/**
 * Iso representation used for `Result` schemas.
 *
 * **Details**
 *
 * Successful results are represented as `{ _tag: "Success", success }`, while
 * failed results are represented as `{ _tag: "Failure", failure }`.
 *
 * @category schemas
 * @since 4.0.0
 */
export type ResultIso<A extends Constraint, E extends Constraint> =
  | { readonly _tag: "Success"; readonly success: A["Iso"] }
  | { readonly _tag: "Failure"; readonly failure: E["Iso"] }

/**
 * Schema for `Result<A, E>` values.
 *
 * @category schemas
 * @since 4.0.0
 */
export function Result<A extends Constraint, E extends Constraint>(
  success: A,
  failure: E
): Result<A, E> {
  const schema = declareConstructor<
    Result_.Result<A["Type"], E["Type"]>,
    Result_.Result<A["Encoded"], E["Encoded"]>,
    ResultIso<A, E>
  >()(
    [success, failure],
    ([success, failure]) => (input, ast, options) => {
      if (!Result_.isResult(input)) {
        return Effect.fail(new SchemaIssue.InvalidType(ast, Option_.some(input)))
      }
      switch (input._tag) {
        case "Success":
          return Effect.mapBothEager(SchemaParser.decodeEffect(success)(input.success, options), {
            onSuccess: Result_.succeed,
            onFailure: (issue) =>
              new SchemaIssue.Composite(ast, Option_.some(input), [new SchemaIssue.Pointer(["success"], issue)])
          })
        case "Failure":
          return Effect.mapBothEager(SchemaParser.decodeEffect(failure)(input.failure, options), {
            onSuccess: Result_.fail,
            onFailure: (issue) =>
              new SchemaIssue.Composite(ast, Option_.some(input), [new SchemaIssue.Pointer(["failure"], issue)])
          })
      }
    },
    {
      typeConstructor: {
        _tag: "effect/Result"
      },
      generation: {
        runtime: `Schema.Result(?, ?)`,
        Type: `Result.Result<?, ?>`,
        importDeclaration: `import * as Result from "effect/Result"`
      },
      expected: "Result",
      toCodec: ([success, failure]) =>
        link<Result_.Result<A["Encoded"], E["Encoded"]>>()(
          Union([
            Struct({ _tag: Literal("Success"), success }),
            Struct({ _tag: Literal("Failure"), failure })
          ]),
          SchemaTransformation.transform({
            decode: (e): Result_.Result<A["Encoded"], E["Encoded"]> =>
              e._tag === "Success" ? Result_.succeed(e.success) : Result_.fail(e.failure),
            encode: (r) =>
              Result_.isSuccess(r)
                ? { _tag: "Success", success: r.success } as const
                : { _tag: "Failure", failure: r.failure } as const
          })
        ),
      toArbitrary: ([success, failure]) => (fc, ctx) => {
        const terminal = oneOfArbitraries(
          fc,
          success.terminal?.map((a): Result_.Result<A["Type"], E["Type"]> => Result_.succeed(a)),
          failure.terminal?.map((e): Result_.Result<A["Type"], E["Type"]> => Result_.fail(e))
        )
        const arbitrary = fc.oneof(
          success.arbitrary.map((a): Result_.Result<A["Type"], E["Type"]> => Result_.succeed(a)),
          failure.arbitrary.map((e): Result_.Result<A["Type"], E["Type"]> => Result_.fail(e))
        )
        return withRecursion(fc, ctx, terminal, arbitrary)
      },
      toEquivalence: ([success, failure]) => Result_.makeEquivalence(success, failure),
      toFormatter: ([success, failure]) =>
        Result_.match({
          onSuccess: (t) => `success(${success(t)})`,
          onFailure: (t) => `failure(${failure(t)})`
        })
    }
  )
  return make(schema.ast, { success, failure })
}

/**
 * Type-level representation returned by {@link Redacted}.
 *
 * @category Redacted
 * @since 3.10.0
 */
export interface Redacted<S extends Constraint> extends
  declareConstructor<
    Redacted_.Redacted<S["Type"]>,
    Redacted_.Redacted<S["Encoded"]>,
    readonly [S]
  >
{
  readonly "Rebuild": Redacted<S>
  readonly value: S
}

/**
 * Schema for values that hide sensitive information from error output and
 * inspection.
 *
 * **Details**
 *
 * If the wrapped schema fails, the issue will be redacted to prevent both
 * the actual value and the schema details from being exposed.
 *
 * Options:
 *
 * - `label`: When provided, the schema will behave as follows:
 *   - Values will be validated against the label in addition to the wrapped schema
 *   - The default JSON serializer will deserialize into a `Redacted` instance with the label
 *   - The arbitrary generator will produce a `Redacted` instance with the label
 *   - The formatter will return the label
 * - `disallowJsonEncode`: When set to `true`, when attempting to encode a `Redacted` instance
 *   into JSON, it will fail with an error. This is useful when the wrapped schema is
 *   sensitive and should not be exposed in JSON.
 *
 * @see {@link RedactedFromValue} for decoding raw values and wrapping them in `Redacted`.
 * @category Redacted
 * @since 3.10.0
 */
export function Redacted<S extends Constraint>(value: S, options?: {
  readonly label?: string | undefined
  readonly disallowJsonEncode?: boolean | undefined
}): Redacted<S> {
  const decodeLabel = typeof options?.label === "string"
    ? SchemaParser.decodeUnknownEffect(Literal(options.label))
    : undefined
  const schema = declareConstructor<Redacted_.Redacted<S["Type"]>, Redacted_.Redacted<S["Encoded"]>>()(
    [value],
    ([value]) => (input, ast, poptions) => {
      if (Redacted_.isRedacted(input)) {
        const label: Effect.Effect<void, SchemaIssue.Issue, never> = decodeLabel !== undefined
          ? Effect.mapErrorEager(
            decodeLabel(input.label, poptions),
            (issue) => new SchemaIssue.Pointer(["label"], issue)
          )
          : Effect.void
        return Effect.flatMapEager(
          label,
          () =>
            Effect.mapBothEager(
              SchemaParser.decodeUnknownEffect(value)(Redacted_.value(input), poptions),
              {
                onSuccess: () => input,
                onFailure: (/** ignore the actual issue because of security reasons */) => {
                  const oinput = Option_.some(input)
                  return new SchemaIssue.Composite(ast, oinput, [
                    new SchemaIssue.Pointer(["value"], new SchemaIssue.InvalidValue(oinput))
                  ])
                }
              }
            )
        )
      }
      return Effect.fail(new SchemaIssue.InvalidType(ast, Option_.some(input)))
    },
    {
      typeConstructor: {
        _tag: "effect/Redacted",
        options
      },
      generation: {
        runtime: options !== undefined ? `Schema.Redacted(?, ${format(options)})` : `Schema.Redacted(?)`,
        Type: `Redacted.Redacted<?>`,
        importDeclaration: `import * as Redacted from "effect/Redacted"`
      },
      expected: "Redacted",
      toCodecJson: ([value]) =>
        link<Redacted_.Redacted<S["Encoded"]>>()(
          redact(value),
          {
            decode: SchemaGetter.transform((e) => Redacted_.make(e, { label: options?.label })),
            encode: options?.disallowJsonEncode ?
              SchemaGetter.forbidden((oe) =>
                "Cannot serialize Redacted" +
                (Option_.isSome(oe) && typeof oe.value.label === "string" ? ` with label: "${oe.value.label}"` : "")
              ) :
              SchemaGetter.transform(Redacted_.value)
          }
        ),
      toArbitrary: ([value]) => () => ({
        arbitrary: value.arbitrary.map((a) => Redacted_.make(a, { label: options?.label })),
        terminal: value.terminal?.map((a) => Redacted_.make(a, { label: options?.label }))
      }),
      toFormatter: () => globalThis.String,
      toEquivalence: ([value]) => Redacted_.makeEquivalence(value)
    }
  )
  return make(schema.ast, { value })
}

/**
 * Type-level representation returned by {@link RedactedFromValue}.
 *
 * @category Redacted
 * @since 4.0.0
 */
export interface RedactedFromValue<S extends Constraint>
  extends decodeTo<Redacted<toType<S>>, middlewareDecoding<S, S["DecodingServices"]>>
{
  readonly "Rebuild": RedactedFromValue<S>
}

/**
 * Middleware that wraps decoded errors in `Redacted`, preventing sensitive
 * schema details from leaking in error messages.
 *
 * @category Redacted
 * @since 4.0.0
 */
export function redact<S extends Constraint>(schema: S): middlewareDecoding<S, S["DecodingServices"]> {
  return middlewareDecoding<S, S["DecodingServices"]>(Effect.mapErrorEager(SchemaIssue.redact))(schema)
}

/**
 * Decodes a value and wraps it in `Redacted<A>`. Unlike {@link Redacted} which
 * expects the input to already be a `Redacted` instance, this schema decodes
 * the raw value and wraps it.
 *
 * @see {@link Redacted} for schemas whose input is already a `Redacted` value.
 * @category Redacted
 * @since 4.0.0
 */
export function RedactedFromValue<S extends Constraint>(value: S, options?: {
  readonly label?: string | undefined
  readonly disallowEncode?: boolean | undefined
}): RedactedFromValue<S> {
  return redact(value).pipe(
    decodeTo(
      Redacted(toType(value), {
        label: options?.label,
        disallowJsonEncode: options?.disallowEncode
      }),
      {
        decode: SchemaGetter.transform((t) => Redacted_.make(t, { label: options?.label })),
        encode: options?.disallowEncode ?
          SchemaGetter.forbidden((oe) =>
            "Cannot encode Redacted" +
            (Option_.isSome(oe) && typeof oe.value.label === "string" ? ` with label: "${oe.value.label}"` : "")
          ) :
          SchemaGetter.transform(Redacted_.value)
      }
    )
  )
}

/**
 * Type-level representation returned by {@link CauseReason}.
 *
 * @category CauseReason
 * @since 4.0.0
 */
export interface CauseReason<E extends Constraint, D extends Constraint> extends
  declareConstructor<
    Cause_.Reason<E["Type"]>,
    Cause_.Reason<E["Encoded"]>,
    readonly [E, D],
    CauseReasonIso<E, D>
  >
{
  readonly "Rebuild": CauseReason<E, D>
  readonly error: E
  readonly defect: D
}

/**
 * Iso representation used for `CauseReason` schemas.
 *
 * **Details**
 *
 * Failures are represented with a `Fail` tag and encoded error, defects with a
 * `Die` tag and encoded defect, and interrupts with an optional `fiberId`.
 *
 * @category CauseReason
 * @since 4.0.0
 */
export type CauseReasonIso<E extends Constraint, D extends Constraint> = {
  readonly _tag: "Fail"
  readonly error: E["Iso"]
} | {
  readonly _tag: "Die"
  readonly error: D["Iso"]
} | {
  readonly _tag: "Interrupt"
  readonly fiberId: number | undefined
}

/**
 * Creates a schema for `Cause.Reason` values using separate schemas for typed
 * failures and unexpected defects.
 *
 * **When to use**
 *
 * Use when serializing or decoding individual cause reasons separately from a
 * full failure cause, with distinct schemas for typed errors and defects.
 *
 * **Details**
 *
 * `Fail` reasons use the `error` schema, `Die` reasons use the `defect` schema,
 * and `Interrupt` reasons carry only an optional fiber id.
 *
 * @see {@link Cause} for constructing schemas for full Cause values
 * @see {@link CauseReasonIso} for the ISO shape of each cause reason
 *
 * @category CauseReason
 * @since 4.0.0
 */
export function CauseReason<E extends Constraint, D extends Constraint>(error: E, defect: D): CauseReason<E, D> {
  const schema = declareConstructor<Cause_.Reason<E["Type"]>, Cause_.Reason<E["Encoded"]>, CauseReasonIso<E, D>>()(
    [error, defect],
    ([error, defect]) => (input, ast, options) => {
      if (!Cause_.isReason(input)) {
        return Effect.fail(new SchemaIssue.InvalidType(ast, Option_.some(input)))
      }
      switch (input._tag) {
        case "Fail":
          return Effect.mapBothEager(
            SchemaParser.decodeUnknownEffect(error)(input.error, options),
            {
              onSuccess: Cause_.makeFailReason,
              onFailure: (issue) =>
                new SchemaIssue.Composite(ast, Option_.some(input), [new SchemaIssue.Pointer(["error"], issue)])
            }
          )
        case "Die":
          return Effect.mapBothEager(
            SchemaParser.decodeUnknownEffect(defect)(input.defect, options),
            {
              onSuccess: Cause_.makeDieReason,
              onFailure: (issue) =>
                new SchemaIssue.Composite(ast, Option_.some(input), [new SchemaIssue.Pointer(["defect"], issue)])
            }
          )
        case "Interrupt":
          return Effect.succeed(input)
      }
    },
    {
      typeConstructor: {
        _tag: "effect/Cause/Failure"
      },
      generation: {
        runtime: `Schema.CauseReason(?, ?)`,
        Type: `Cause.Failure<?, ?>`,
        importDeclaration: `import * as Cause from "effect/Cause"`
      },
      expected: "Cause.Failure",
      toCodec: ([error, defect]) =>
        link<Cause_.Reason<E["Encoded"]>>()(
          Union([
            Struct({ _tag: Literal("Fail"), error }),
            Struct({ _tag: Literal("Die"), defect }),
            Struct({ _tag: Literal("Interrupt"), fiberId: UndefinedOr(Finite) })
          ]),
          SchemaTransformation.transform({
            decode: (e) => {
              switch (e._tag) {
                case "Fail":
                  return Cause_.makeFailReason(e.error)
                case "Die":
                  return Cause_.makeDieReason(e.defect)
                case "Interrupt":
                  return Cause_.makeInterruptReason(e.fiberId)
              }
            },
            encode: identity
          })
        ),
      toArbitrary: ([error, defect]) => causeReasonToArbitrary(error, defect),
      toEquivalence: ([error, defect]) => causeReasonToEquivalence(error, defect),
      toFormatter: ([error, defect]) => causeReasonToFormatter(error, defect)
    }
  )
  return make(schema.ast, { error, defect })
}

function causeReasonToArbitrary<E, D>(
  error: Annotations.ToArbitrary.TypeParameter<E>,
  defect: Annotations.ToArbitrary.TypeParameter<D>
) {
  return (fc: typeof FastCheck, ctx: Annotations.ToArbitrary.Context) => {
    const terminal = fc.constant(Cause_.makeInterruptReason())
    const arbitrary = fc.oneof(
      terminal,
      fc.integer({ min: 1 }).map(Cause_.makeInterruptReason),
      error.arbitrary.map((e) => Cause_.makeFailReason(e)),
      defect.arbitrary.map((d) => Cause_.makeDieReason(d))
    )
    return withRecursion(fc, ctx, terminal, arbitrary)
  }
}

function causeReasonToEquivalence<E>(error: Equivalence.Equivalence<E>, defect: Equivalence.Equivalence<unknown>) {
  return (a: Cause_.Reason<E>, b: Cause_.Reason<E>) => {
    if (a._tag !== b._tag) return false
    switch (a._tag) {
      case "Fail":
        return error(a.error, (b as Cause_.Fail<E>).error)
      case "Die":
        return defect(a.defect, (b as Cause_.Die).defect)
      case "Interrupt":
        return a.fiberId === (b as Cause_.Interrupt).fiberId
    }
  }
}

function causeReasonToFormatter<E>(error: Formatter<E>, defect: Formatter<unknown>) {
  return (t: Cause_.Reason<E>) => {
    switch (t._tag) {
      case "Fail":
        return `Fail(${error(t.error)})`
      case "Die":
        return `Die(${defect(t.defect)})`
      case "Interrupt":
        return "Interrupt"
    }
  }
}

/**
 * Type-level representation returned by {@link Cause}.
 *
 * @category Cause
 * @since 3.10.0
 */
export interface Cause<E extends Constraint, D extends Constraint> extends
  declareConstructor<
    Cause_.Cause<E["Type"]>,
    Cause_.Cause<E["Encoded"]>,
    readonly [E, D],
    CauseIso<E, D>
  >
{
  readonly "Rebuild": Cause<E, D>
  readonly error: E
  readonly defect: D
}

/**
 * Iso representation used for `Cause` schemas: an ordered array of
 * `CauseReasonIso` values.
 *
 * **When to use**
 *
 * Use when working with the ISO shape of a `Cause` schema, such as `toIso`
 * optics or codecs that expose a cause as its ordered array of encoded reasons.
 *
 * @see {@link Cause} for constructing schemas for full Cause values
 * @see {@link CauseReasonIso} for the ISO shape of each array element
 *
 * @category Cause
 * @since 4.0.0
 */
export type CauseIso<E extends Constraint, D extends Constraint> = ReadonlyArray<CauseReasonIso<E, D>>

/**
 * Creates a schema for `Cause` values using separate schemas for typed failures
 * and unexpected defects.
 *
 * **When to use**
 *
 * Use to validate, transform, or serialize Effect failure causes when typed
 * failures and unexpected defects need separate schemas.
 *
 * **Details**
 *
 * The `error` schema is applied to `Fail` reasons and the `defect` schema is
 * applied to `Die` reasons. Interrupt reasons do not use either schema and
 * carry only an optional fiber id.
 *
 * @see {@link CauseReason} for the schema used by each individual cause reason
 * @see {@link CauseIso} for the ordered array representation used by the schema ISO
 *
 * @category Cause
 * @since 3.10.0
 */
export function Cause<E extends Constraint, D extends Constraint>(error: E, defect: D): Cause<E, D> {
  const schema = declareConstructor<Cause_.Cause<E["Type"]>, Cause_.Cause<E["Encoded"]>, CauseIso<E, D>>()(
    [error, defect],
    ([error, defect]) => {
      const failures = ArraySchema(CauseReason(error, defect))
      return (input, ast, options) => {
        if (!Cause_.isCause(input)) {
          return Effect.fail(new SchemaIssue.InvalidType(ast, Option_.some(input)))
        }
        return Effect.mapBothEager(SchemaParser.decodeUnknownEffect(failures)(input.reasons, options), {
          onSuccess: Cause_.fromReasons,
          onFailure: (issue) =>
            new SchemaIssue.Composite(ast, Option_.some(input), [new SchemaIssue.Pointer(["failures"], issue)])
        })
      }
    },
    {
      typeConstructor: {
        _tag: "effect/Cause"
      },
      generation: {
        runtime: `Schema.Cause(?, ?)`,
        Type: `Cause.Cause<?, ?>`,
        importDeclaration: `import * as Cause from "effect/Cause"`
      },
      expected: "Cause",
      toCodec: ([error, defect]) =>
        link<Cause_.Cause<E["Encoded"]>>()(
          ArraySchema(CauseReason(error, defect)),
          SchemaTransformation.transform({
            decode: Cause_.fromReasons,
            encode: ({ reasons: failures }) => failures
          })
        ),
      toArbitrary: ([error, defect]) => causeToArbitrary(error, defect),
      toEquivalence: ([error, defect]) => causeToEquivalence(error, defect),
      toFormatter: ([error, defect]) => causeToFormatter(error, defect)
    }
  )
  return make(schema.ast, { error, defect })
}

function causeToArbitrary<E, D>(
  error: Annotations.ToArbitrary.TypeParameter<E>,
  defect: Annotations.ToArbitrary.TypeParameter<D>
) {
  return (fc: typeof FastCheck, ctx: Annotations.ToArbitrary.Context) => {
    const reason = causeReasonToArbitrary(error, defect)(fc, ctx)
    const terminal = fc.constant(Cause_.empty)
    const arbitrary = fc.array(reason.arbitrary).map(Cause_.fromReasons)
    return withRecursion(fc, ctx, terminal, arbitrary)
  }
}

function causeToEquivalence<E>(error: Equivalence.Equivalence<E>, defect: Equivalence.Equivalence<unknown>) {
  const failures = Equivalence.Array(causeReasonToEquivalence(error, defect))
  return (a: Cause_.Cause<E>, b: Cause_.Cause<E>) => failures(a.reasons, b.reasons)
}

function causeToFormatter<E>(error: Formatter<E>, defect: Formatter<unknown>) {
  const causeReason = causeReasonToFormatter(error, defect)
  return (t: Cause_.Cause<E>) => `Cause([${t.reasons.map(causeReason).join(", ")}])`
}

/**
 * Type-level representation of {@link Error}.
 *
 * @category Error
 * @since 4.0.0
 */
export interface Error extends instanceOf<globalThis.Error> {
  readonly "Rebuild": Error
}

/**
 * Options for {@link Error} and {@link Defect}.
 *
 * @category options
 * @since 4.0.0
 */
export interface ErrorOptions {
  /**
   * Includes string stack traces in encoded `Error` values when set to `true`.
   *
   * @default false
   */
  readonly includeStack?: boolean | undefined
  /**
   * Excludes `Error.cause` values from encoded `Error` values when set to
   * `true`.
   *
   * @default false
   */
  readonly excludeCause?: boolean | undefined
}

type ErrorOptionsKey = 0 | 1 | 2 | 3

const getErrorOptionsKey = (options?: ErrorOptions): ErrorOptionsKey =>
  ((options?.includeStack === true ? 1 : 0) |
    (options?.excludeCause === true ? 2 : 0)) as ErrorOptionsKey

const getErrorOptions = (key: ErrorOptionsKey): ErrorOptions | undefined => {
  switch (key) {
    case 0:
      return undefined
    case 1:
      return { includeStack: true }
    case 2:
      return { excludeCause: true }
    case 3:
      return { includeStack: true, excludeCause: true }
  }
}

const errorSchemaCache: Array<Error | undefined> = []

/**
 * Schema for JavaScript `Error` objects.
 *
 * **Details**
 *
 * Default JSON serializer:
 *
 * Encodes an `Error` as an object with `message`, optional `name`, and optional
 * `cause` properties, and decodes that object back into an `Error`. Stack
 * traces are omitted by default for security. Pass `{ includeStack: true }` to
 * include stack traces, or `{ excludeCause: true }` to omit causes.
 *
 * @category constructors
 * @since 4.0.0
 */
export function Error(options?: ErrorOptions): Error {
  const key = getErrorOptionsKey(options)
  const cached = errorSchemaCache[key]
  if (cached !== undefined) {
    return cached
  }
  const normalizedOptions = getErrorOptions(key)
  const schema = instanceOf(globalThis.Error, {
    typeConstructor: {
      _tag: "Error",
      ...(normalizedOptions === undefined ? {} : { options: normalizedOptions })
    },
    generation: {
      runtime: normalizedOptions !== undefined ? `Schema.Error(${format(normalizedOptions)})` : `Schema.Error()`,
      Type: `globalThis.Error`
    },
    expected: "Error",
    toCodecJson: () => link<globalThis.Error>()(JsonError, SchemaTransformation.errorFromJsonError(normalizedOptions)),
    toArbitrary: () => (fc) => fc.string().map((message) => new globalThis.Error(message))
  })
  errorSchemaCache[key] = schema
  return schema
}

/**
 * Type-level representation of {@link Defect}.
 *
 * @category Defect
 * @since 3.10.0
 */
export interface Defect extends decodeTo<Unknown, typeof Json> {
  readonly "Rebuild": Defect
}

const defectSchemaCache: Array<Defect | undefined> = []

/**
 * Schema for unexpected defect values represented as `unknown` with a JSON
 * encoded form.
 *
 * **When to use**
 *
 * Use when you need a schema for `Cause` defects or other unexpected failures
 * whose runtime value may be any value.
 *
 * **Details**
 *
 * The encoded side is {@link Json}. During decoding, JSON objects with a string
 * `message` property are decoded into JavaScript `Error` values, preserving a
 * non-default `name` and any string `stack`. Other JSON values decode
 * unchanged.
 *
 * During encoding, JavaScript `Error` values encode to JSON objects with
 * `name`, `message`, and optional `cause` properties. Pass
 * `{ includeStack: true }` to include string stack traces in encoded `Error`
 * defects, or `{ excludeCause: true }` to omit causes. Other values are
 * serialized through Effect's JSON formatter and then parsed back into JSON
 * when possible.
 *
 * **Gotchas**
 *
 * This schema is for carrying defects across JSON boundaries, not for
 * preserving every JavaScript value exactly. Some values cannot round-trip
 * unchanged:
 *
 * - A non-`Error` object such as `{ message: "boom" }` encodes as an
 *   error-shaped JSON object and decodes back as an `Error`.
 * - JSON serialization normalizes unsupported values. For example,
 *   `undefined` array elements encode as `null`, unsupported object properties
 *   are omitted, and circular references are dropped.
 * - Values that cannot be represented as JSON fall back to Effect's formatted
 *   string representation.
 *
 * @see {@link Error} for a schema that only accepts JavaScript `Error` values.
 * @category constructors
 * @since 4.0.0
 */
export function Defect(options?: ErrorOptions): Defect {
  const key = getErrorOptionsKey(options)
  const cached = defectSchemaCache[key]
  if (cached !== undefined) {
    return cached
  }
  const schema = Json.pipe(decodeTo(Unknown, SchemaTransformation.defectFromJson(getErrorOptions(key))))
  defectSchemaCache[key] = schema
  return schema
}

/**
 * Type-level representation returned by {@link Exit}.
 *
 * @category Exit
 * @since 3.10.0
 */
export interface Exit<A extends Constraint, E extends Constraint, D extends Constraint> extends
  declareConstructor<
    Exit_.Exit<A["Type"], E["Type"]>,
    Exit_.Exit<A["Encoded"], E["Encoded"]>,
    readonly [A, E, D],
    ExitIso<A, E, D>
  >
{
  readonly "Rebuild": Exit<A, E, D>
  readonly value: A
  readonly error: E
  readonly defect: D
}

/**
 * Iso representation used for `Exit` schemas.
 *
 * **Details**
 *
 * Successful exits are represented as `{ _tag: "Success", value }`, while failed
 * exits are represented as `{ _tag: "Failure", cause }`.
 *
 * @category Exit
 * @since 4.0.0
 */
export type ExitIso<A extends Constraint, E extends Constraint, D extends Constraint> = {
  readonly _tag: "Success"
  readonly value: A["Iso"]
} | {
  readonly _tag: "Failure"
  readonly cause: CauseIso<E, D>
}

/**
 * Creates a schema for `Exit` values using schemas for the success value, typed
 * failure, and unexpected defect channels.
 *
 * **When to use**
 *
 * Use when serializing or validating an effect outcome where success, typed
 * failure, and defects each need their own schema.
 *
 * @category Exit
 * @since 3.10.0
 */
export function Exit<A extends Constraint, E extends Constraint, D extends Constraint>(
  value: A,
  error: E,
  defect: D
): Exit<A, E, D> {
  const schema = declareConstructor<
    Exit_.Exit<A["Type"], E["Type"]>,
    Exit_.Exit<A["Encoded"], E["Encoded"]>,
    ExitIso<A, E, D>
  >()(
    [value, error, defect],
    ([value, error, defect]) => {
      const cause = Cause(error, defect)
      return (input, ast, options) => {
        if (!Exit_.isExit(input)) {
          return Effect.fail(new SchemaIssue.InvalidType(ast, Option_.some(input)))
        }
        switch (input._tag) {
          case "Success":
            return Effect.mapBothEager(
              SchemaParser.decodeUnknownEffect(value)(input.value, options),
              {
                onSuccess: Exit_.succeed,
                onFailure: (issue) =>
                  new SchemaIssue.Composite(ast, Option_.some(input), [new SchemaIssue.Pointer(["value"], issue)])
              }
            )
          case "Failure":
            return Effect.mapBothEager(
              SchemaParser.decodeUnknownEffect(cause)(input.cause, options),
              {
                onSuccess: Exit_.failCause,
                onFailure: (issue) =>
                  new SchemaIssue.Composite(ast, Option_.some(input), [new SchemaIssue.Pointer(["cause"], issue)])
              }
            )
        }
      }
    },
    {
      typeConstructor: {
        _tag: "effect/Exit"
      },
      generation: {
        runtime: `Schema.Exit(?, ?, ?)`,
        Type: `Exit.Exit<?, ?, ?>`,
        importDeclaration: `import * as Exit from "effect/Exit"`
      },
      expected: "Exit",
      toCodec: ([value, error, defect]) =>
        link<Exit_.Exit<A["Encoded"], E["Encoded"]>>()(
          Union([
            Struct({ _tag: Literal("Success"), value }),
            Struct({ _tag: Literal("Failure"), cause: Cause(error, defect) })
          ]),
          SchemaTransformation.transform({
            decode: (e): Exit_.Exit<A["Encoded"], E["Encoded"]> =>
              e._tag === "Success" ? Exit_.succeed(e.value) : Exit_.failCause(e.cause),
            encode: (exit) =>
              Exit_.isSuccess(exit)
                ? { _tag: "Success", value: exit.value } as const
                : { _tag: "Failure", cause: exit.cause } as const
          })
        ),
      toArbitrary: ([value, error, defect]) => (fc, ctx) => {
        const cause = causeToArbitrary(error, defect)(fc, ctx)
        const terminal = oneOfArbitraries(
          fc,
          value.terminal?.map((v): Exit_.Exit<A["Type"], E["Type"]> => Exit_.succeed(v)),
          cause.terminal?.map((cause): Exit_.Exit<A["Type"], E["Type"]> => Exit_.failCause(cause))
        )
        const arbitrary = fc.oneof(
          value.arbitrary.map((v): Exit_.Exit<A["Type"], E["Type"]> => Exit_.succeed(v)),
          cause.arbitrary.map((cause): Exit_.Exit<A["Type"], E["Type"]> => Exit_.failCause(cause))
        )
        return withRecursion(fc, ctx, terminal, arbitrary)
      },
      toEquivalence: ([value, error, defect]) => {
        const cause = causeToEquivalence(error, defect)
        return (a, b) => {
          if (a._tag !== b._tag) return false
          switch (a._tag) {
            case "Success":
              return value(a.value, (b as Exit_.Success<A["Type"]>).value)
            case "Failure":
              return cause(a.cause, (b as Exit_.Failure<E["Type"], D["Type"]>).cause)
          }
        }
      },
      toFormatter: ([value, error, defect]) => {
        const cause = causeToFormatter(error, defect)
        return (t) => {
          switch (t._tag) {
            case "Success":
              return `Exit.Success(${value(t.value)})`
            case "Failure":
              return `Exit.Failure(${cause(t.cause)})`
          }
        }
      }
    }
  )
  return make(schema.ast, { value, error, defect })
}

/**
 * Type-level representation returned by {@link ReadonlyMap}.
 *
 * @category ReadonlyMap
 * @since 4.0.0
 */
export interface $ReadonlyMap<Key extends Constraint, Value extends Constraint> extends
  declareConstructor<
    globalThis.ReadonlyMap<Key["Type"], Value["Type"]>,
    globalThis.ReadonlyMap<Key["Encoded"], Value["Encoded"]>,
    readonly [Key, Value],
    ReadonlyMapIso<Key, Value>
  >
{
  readonly "Rebuild": $ReadonlyMap<Key, Value>
  readonly key: Key
  readonly value: Value
}

/**
 * Iso representation used for `ReadonlyMap` schemas: an array of readonly
 * `[key, value]` tuples using each entry schema's `Iso` type.
 *
 * @category ReadonlyMap
 * @since 4.0.0
 */
export type ReadonlyMapIso<Key extends Constraint, Value extends Constraint> = ReadonlyArray<
  readonly [Key["Iso"], Value["Iso"]]
>

function oneOfArbitraries<T>(
  fc: typeof FastCheck,
  a: FastCheck.Arbitrary<T> | undefined,
  b: FastCheck.Arbitrary<T> | undefined
) {
  return a === undefined ? b : b === undefined ? a : fc.oneof(a, b)
}

function withRecursion<T>(
  fc: typeof FastCheck,
  ctx: Annotations.ToArbitrary.Context,
  terminal: FastCheck.Arbitrary<T> | undefined,
  arbitrary: FastCheck.Arbitrary<T>
) {
  return {
    arbitrary: terminal === undefined || ctx.recursion === undefined
      ? arbitrary
      : fc.oneof(ctx.recursion, terminal, arbitrary),
    terminal
  }
}

function arrayFromItems<T>(
  fc: typeof FastCheck,
  item: FastCheck.Arbitrary<T>,
  constraints: FastCheck.ArrayConstraints | undefined,
  comparator?: ((a: T, b: T) => boolean) | undefined
) {
  return comparator === undefined
    ? fc.array(item, constraints)
    : fc.uniqueArray(item, { ...constraints, comparator })
}

function collectionArbitrary<T, Out>(
  fc: typeof FastCheck,
  ctx: Annotations.ToArbitrary.Context,
  item: FastCheck.Arbitrary<T>,
  terminalItem: FastCheck.Arbitrary<T> | undefined,
  fromIterable: (items: Array<T>) => Out,
  comparator?: ((a: T, b: T) => boolean) | undefined
) {
  const constraint = ctx.constraint
  const constraints = constraint === undefined ||
      (constraint.minLength === undefined && constraint.maxLength === undefined)
    ? undefined
    : {
      ...(constraint.minLength !== undefined ? { minLength: constraint.minLength } : {}),
      ...(constraint.maxLength !== undefined ? { maxLength: constraint.maxLength } : {})
    }
  if (
    constraints?.minLength !== undefined && constraints.maxLength !== undefined &&
    constraints.minLength > constraints.maxLength
  ) {
    throw new globalThis.Error("Unable to derive an arbitrary for size constraints")
  }
  const minLength = constraints?.minLength ?? 0
  const terminal = minLength === 0
    ? fc.constant<Array<T>>([])
    : terminalItem === undefined
    ? undefined
    : arrayFromItems(fc, terminalItem, { ...constraints, maxLength: minLength }, comparator)
  const arrays = withRecursion(
    fc,
    ctx,
    terminal,
    arrayFromItems(fc, item, constraints, comparator)
  )
  return {
    arbitrary: arrays.arbitrary.map(fromIterable),
    terminal: arrays.terminal?.map(fromIterable)
  }
}

function entriesArbitrary<K, V, Out>(
  fc: typeof FastCheck,
  ctx: Annotations.ToArbitrary.Context,
  key: Annotations.ToArbitrary.TypeParameter<K>,
  value: Annotations.ToArbitrary.TypeParameter<V>,
  fromIterable: (items: Array<[K, V]>) => Out
) {
  return collectionArbitrary(
    fc,
    ctx,
    fc.tuple(key.arbitrary, value.arbitrary),
    key.terminal === undefined || value.terminal === undefined ? undefined : fc.tuple(key.terminal, value.terminal),
    fromIterable,
    ([a], [b]) => Equal.equals(a, b)
  )
}

/**
 * Schema for readonly maps whose keys and values conform to the provided
 * schemas.
 *
 * @category ReadonlyMap
 * @since 3.10.0
 */
export function ReadonlyMap<Key extends Constraint, Value extends Constraint>(
  key: Key,
  value: Value
): $ReadonlyMap<Key, Value> {
  const schema = declareConstructor<
    globalThis.ReadonlyMap<Key["Type"], Value["Type"]>,
    globalThis.ReadonlyMap<Key["Encoded"], Value["Encoded"]>,
    ReadonlyMapIso<Key, Value>
  >()(
    [key, value],
    ([key, value]) => {
      const array = ArraySchema(Tuple([key, value]))
      return (input, ast, options) => {
        if (input instanceof globalThis.Map) {
          return Effect.mapBothEager(
            SchemaParser.decodeUnknownEffect(array)([...input], options),
            {
              onSuccess: (array: ReadonlyArray<readonly [Key["Type"], Value["Type"]]>) => new globalThis.Map(array),
              onFailure: (issue) =>
                new SchemaIssue.Composite(ast, Option_.some(input), [new SchemaIssue.Pointer(["entries"], issue)])
            }
          )
        }
        return Effect.fail(new SchemaIssue.InvalidType(ast, Option_.some(input)))
      }
    },
    {
      typeConstructor: {
        _tag: "ReadonlyMap"
      },
      generation: {
        runtime: `Schema.ReadonlyMap(?, ?)`,
        Type: `globalThis.ReadonlyMap<?, ?>`
      },
      expected: "ReadonlyMap",
      toCodec: ([key, value]) =>
        link<globalThis.Map<Key["Encoded"], Value["Encoded"]>>()(
          ArraySchema(Tuple([key, value])),
          SchemaTransformation.transform({
            decode: (e) => new globalThis.Map(e),
            encode: (map) => [...map.entries()]
          })
        ),
      toArbitrary: ([key, value]) => (fc, ctx) => entriesArbitrary(fc, ctx, key, value, (as) => new globalThis.Map(as)),
      toEquivalence: ([key, value]) => Equal.makeCompareMap(key, value),
      toFormatter: ([key, value]) => (t) => {
        const size = t.size
        if (size === 0) {
          return "ReadonlyMap(0) {}"
        }
        const entries = globalThis.Array.from(t.entries()).sort().map(([k, v]) => `${key(k)} => ${value(v)}`)
        return `ReadonlyMap(${size}) { ${entries.join(", ")} }`
      }
    }
  )
  return make(schema.ast, { key, value })
}

/**
 * Type-level representation returned by {@link HashMap}.
 *
 * @category HashMap
 * @since 3.10.0
 */
export interface HashMap<Key extends Constraint, Value extends Constraint> extends
  declareConstructor<
    HashMap_.HashMap<Key["Type"], Value["Type"]>,
    HashMap_.HashMap<Key["Encoded"], Value["Encoded"]>,
    readonly [Key, Value],
    HashMapIso<Key, Value>
  >
{
  readonly "Rebuild": HashMap<Key, Value>
  readonly key: Key
  readonly value: Value
}

/**
 * Iso representation used for `HashMap` schemas: an array of readonly
 * `[key, value]` tuples using each entry schema's `Iso` type.
 *
 * @category HashMap
 * @since 4.0.0
 */
export type HashMapIso<Key extends Constraint, Value extends Constraint> = ReadonlyArray<
  readonly [Key["Iso"], Value["Iso"]]
>

/**
 * Schema for hash maps whose keys and values conform to the provided schemas.
 *
 * @category HashMap
 * @since 3.10.0
 */
export function HashMap<Key extends Constraint, Value extends Constraint>(key: Key, value: Value): HashMap<Key, Value> {
  const schema = declareConstructor<
    HashMap_.HashMap<Key["Type"], Value["Type"]>,
    HashMap_.HashMap<Key["Encoded"], Value["Encoded"]>,
    HashMapIso<Key, Value>
  >()(
    [key, value],
    ([key, value]) => {
      const entries = ArraySchema(Tuple([key, value]))
      return (input, ast, options) => {
        if (HashMap_.isHashMap(input)) {
          return Effect.mapBothEager(
            SchemaParser.decodeUnknownEffect(entries)(HashMap_.toEntries(input), options),
            {
              onSuccess: HashMap_.fromIterable,
              onFailure: (issue) =>
                new SchemaIssue.Composite(ast, Option_.some(input), [new SchemaIssue.Pointer(["entries"], issue)])
            }
          )
        }
        return Effect.fail(new SchemaIssue.InvalidType(ast, Option_.some(input)))
      }
    },
    {
      typeConstructor: {
        _tag: "effect/HashMap"
      },
      generation: {
        runtime: `Schema.HashMap(?, ?)`,
        Type: `HashMap.HashMap<?, ?>`,
        importDeclaration: `import * as HashMap from "effect/HashMap"`
      },
      expected: "HashMap",
      toCodec: ([key, value]) =>
        link<HashMap_.HashMap<Key["Encoded"], Value["Encoded"]>>()(
          ArraySchema(Tuple([key, value])),
          SchemaTransformation.transform({
            decode: HashMap_.fromIterable,
            encode: HashMap_.toEntries
          })
        ),
      toArbitrary: ([key, value]) => (fc, ctx) => entriesArbitrary(fc, ctx, key, value, HashMap_.fromIterable),
      toEquivalence: ([key, value]) => Equal.makeCompareMap(key, value),
      toFormatter: ([key, value]) => (t) => {
        const size = HashMap_.size(t)
        if (size === 0) {
          return "HashMap(0) {}"
        }
        const entries = HashMap_.toEntries(t).sort().map(([k, v]) => `${key(k)} => ${value(v)}`)
        return `HashMap(${size}) { ${entries.join(", ")} }`
      }
    }
  )
  return make(schema.ast, { key, value })
}

/**
 * Type-level representation returned by {@link ReadonlySet}.
 *
 * @category ReadonlySet
 * @since 4.0.0
 */
export interface $ReadonlySet<Value extends Constraint> extends
  declareConstructor<
    globalThis.ReadonlySet<Value["Type"]>,
    globalThis.ReadonlySet<Value["Encoded"]>,
    readonly [Value],
    ReadonlySetIso<Value>
  >
{
  readonly "Rebuild": $ReadonlySet<Value>
  readonly value: Value
}

/**
 * Iso representation used for `ReadonlySet` schemas: an array of element values
 * using the element schema's `Iso` type.
 *
 * @category ReadonlySet
 * @since 4.0.0
 */
export type ReadonlySetIso<Value extends Constraint> = ReadonlyArray<Value["Iso"]>

/**
 * Schema for readonly sets whose values conform to the provided element schema.
 *
 * @category ReadonlySet
 * @since 3.10.0
 */
export function ReadonlySet<Value extends Constraint>(value: Value): $ReadonlySet<Value> {
  const schema = declareConstructor<
    globalThis.ReadonlySet<Value["Type"]>,
    globalThis.ReadonlySet<Value["Encoded"]>,
    ReadonlySetIso<Value>
  >()(
    [value],
    ([value]) => {
      const array = ArraySchema(value)
      return (input, ast, options) => {
        if (input instanceof globalThis.Set) {
          return Effect.mapBothEager(
            SchemaParser.decodeUnknownEffect(array)([...input], options),
            {
              onSuccess: (array: ReadonlyArray<Value["Type"]>) => new globalThis.Set(array),
              onFailure: (issue) =>
                new SchemaIssue.Composite(ast, Option_.some(input), [new SchemaIssue.Pointer(["values"], issue)])
            }
          )
        }
        return Effect.fail(new SchemaIssue.InvalidType(ast, Option_.some(input)))
      }
    },
    {
      typeConstructor: {
        _tag: "ReadonlySet"
      },
      generation: {
        runtime: `Schema.ReadonlySet(?)`,
        Type: `globalThis.ReadonlySet<?>`
      },
      expected: "ReadonlySet",
      toCodec: ([value]) =>
        link<globalThis.Set<Value["Encoded"]>>()(
          ArraySchema(value),
          SchemaTransformation.transform({
            decode: (e) => new globalThis.Set(e),
            encode: (set) => [...set.values()]
          })
        ),
      toArbitrary: ([value]) => (fc, ctx) =>
        collectionArbitrary(fc, ctx, value.arbitrary, value.terminal, (as) => new globalThis.Set(as), Equal.equals),
      toEquivalence: ([value]) => Equal.makeCompareSet(value),
      toFormatter: ([value]) => (t) => {
        const size = t.size
        if (size === 0) {
          return "ReadonlySet(0) {}"
        }
        const values = globalThis.Array.from(t.values()).sort().map((v) => `${value(v)}`)
        return `ReadonlySet(${size}) { ${values.join(", ")} }`
      }
    }
  )
  return make(schema.ast, { value })
}

/**
 * Type-level representation returned by {@link HashSet}.
 *
 * @category HashSet
 * @since 3.10.0
 */
export interface HashSet<Value extends Constraint> extends
  declareConstructor<
    HashSet_.HashSet<Value["Type"]>,
    HashSet_.HashSet<Value["Encoded"]>,
    readonly [Value],
    HashSetIso<Value>
  >
{
  readonly "Rebuild": HashSet<Value>
  readonly value: Value
}

/**
 * Iso representation used for `HashSet` schemas: an array of element values
 * using the element schema's `Iso` type.
 *
 * @category HashSet
 * @since 4.0.0
 */
export type HashSetIso<Value extends Constraint> = ReadonlyArray<Value["Iso"]>

/**
 * Schema for hash sets whose values conform to the provided element schema.
 *
 * @category HashSet
 * @since 3.10.0
 */
export function HashSet<Value extends Constraint>(value: Value): HashSet<Value> {
  const schema = declareConstructor<
    HashSet_.HashSet<Value["Type"]>,
    HashSet_.HashSet<Value["Encoded"]>,
    HashSetIso<Value>
  >()(
    [value],
    ([value]) => {
      const values = ArraySchema(value)
      return (input, ast, options) => {
        if (HashSet_.isHashSet(input)) {
          return Effect.mapBothEager(
            SchemaParser.decodeUnknownEffect(values)(Arr.fromIterable(input), options),
            {
              onSuccess: HashSet_.fromIterable,
              onFailure: (issue) =>
                new SchemaIssue.Composite(ast, Option_.some(input), [new SchemaIssue.Pointer(["values"], issue)])
            }
          )
        }
        return Effect.fail(new SchemaIssue.InvalidType(ast, Option_.some(input)))
      }
    },
    {
      typeConstructor: {
        _tag: "effect/HashSet"
      },
      generation: {
        runtime: `Schema.HashSet(?)`,
        Type: `HashSet.HashSet<?>`
      },
      expected: "HashSet",
      toCodec: ([value]) =>
        link<HashSet_.HashSet<Value["Encoded"]>>()(
          ArraySchema(value),
          SchemaTransformation.transform({
            decode: HashSet_.fromIterable,
            encode: Arr.fromIterable
          })
        ),
      toArbitrary: ([value]) => (fc, ctx) =>
        collectionArbitrary(fc, ctx, value.arbitrary, value.terminal, HashSet_.fromIterable, Equal.equals),
      toEquivalence: ([value]) => Equal.makeCompareSet(value),
      toFormatter: ([value]) => (t) => {
        const size = HashSet_.size(t)
        if (size === 0) {
          return "HashSet(0) {}"
        }
        const values = globalThis.Array.from(t).sort().map((v) => `${value(v)}`)
        return `HashSet(${size}) { ${values.join(", ")} }`
      }
    }
  )
  return make(schema.ast, { value })
}

/**
 * Type-level representation returned by {@link Chunk}.
 *
 * @category Chunk
 * @since 3.10.0
 */
export interface Chunk<Value extends Constraint> extends
  declareConstructor<
    Chunk_.Chunk<Value["Type"]>,
    Chunk_.Chunk<Value["Encoded"]>,
    readonly [Value],
    ChunkIso<Value>
  >
{
  readonly "Rebuild": Chunk<Value>
  readonly value: Value
}

/**
 * Iso representation used for `Chunk` schemas: an array of element values using
 * the element schema's `Iso` type.
 *
 * **When to use**
 *
 * Use when annotating type-level helpers that work with the readonly-array ISO
 * shape of a `Chunk` schema.
 *
 * @see {@link Chunk} for the schema interface and constructor that use this ISO representation
 *
 * @category Chunk
 * @since 4.0.0
 */
export type ChunkIso<Value extends Constraint> = ReadonlyArray<Value["Iso"]>

/**
 * Schema for chunks whose values conform to the provided element schema.
 *
 * @category Chunk
 * @since 3.10.0
 */
export function Chunk<Value extends Constraint>(value: Value): Chunk<Value> {
  const schema = declareConstructor<
    Chunk_.Chunk<Value["Type"]>,
    Chunk_.Chunk<Value["Encoded"]>,
    ChunkIso<Value>
  >()(
    [value],
    ([value]) => {
      const values = ArraySchema(value)
      return (input, ast, options) => {
        if (Chunk_.isChunk(input)) {
          return Effect.mapBothEager(
            SchemaParser.decodeUnknownEffect(values)(Arr.fromIterable(input), options),
            {
              onSuccess: Chunk_.fromIterable,
              onFailure: (issue) =>
                new SchemaIssue.Composite(ast, Option_.some(input), [new SchemaIssue.Pointer(["values"], issue)])
            }
          )
        }
        return Effect.fail(new SchemaIssue.InvalidType(ast, Option_.some(input)))
      }
    },
    {
      typeConstructor: {
        _tag: "effect/Chunk"
      },
      generation: {
        runtime: `Schema.Chunk(?)`,
        Type: `Chunk.Chunk<?>`
      },
      expected: "Chunk",
      toCodec: ([value]) =>
        link<Chunk_.Chunk<Value["Encoded"]>>()(
          ArraySchema(value),
          SchemaTransformation.transform({
            decode: Chunk_.fromIterable,
            encode: Arr.fromIterable
          })
        ),
      toArbitrary: ([value]) => (fc, ctx) =>
        collectionArbitrary(fc, ctx, value.arbitrary, value.terminal, Chunk_.fromIterable),
      toEquivalence: ([value]) => Chunk_.makeEquivalence(value),
      toFormatter: ([value]) => (t) => {
        const size = Chunk_.size(t)
        if (size === 0) {
          return "Chunk(0) {}"
        }
        const values = globalThis.Array.from(t).sort().map((v) => `${value(v)}`)
        return `Chunk(${size}) { ${values.join(", ")} }`
      }
    }
  )
  return make(schema.ast, { value })
}

/**
 * Type-level representation of {@link RegExp}.
 *
 * @category RegExp
 * @since 4.0.0
 */
export interface RegExp extends instanceOf<globalThis.RegExp> {
  readonly "Rebuild": RegExp
}

/**
 * Schema for JavaScript `RegExp` objects.
 *
 * **Details**
 *
 * The default JSON serializer encodes a `RegExp` as `{ source, flags }`.
 *
 * @category RegExp
 * @since 4.0.0
 */
export const RegExp: RegExp = instanceOf(
  globalThis.RegExp,
  {
    typeConstructor: {
      _tag: "RegExp"
    },
    generation: {
      runtime: `Schema.RegExp`,
      Type: `globalThis.RegExp`
    },
    expected: "RegExp",
    toCodecJson: () =>
      link<globalThis.RegExp>()(
        Struct({
          source: String,
          flags: String
        }),
        SchemaTransformation.transformOrFail({
          decode: (e) =>
            Effect.try({
              try: () => new globalThis.RegExp(e.source, e.flags),
              catch: (e) => new SchemaIssue.InvalidValue(Option_.some(e), { message: globalThis.String(e) })
            }),
          encode: (regExp) =>
            Effect.succeed({
              source: regExp.source,
              flags: regExp.flags
            })
        })
      ),
    toArbitrary: () => (fc) =>
      fc
        .tuple(
          fc.constantFrom(
            ".",
            ".*",
            "\\d+",
            "\\w+",
            "[a-z]+",
            "[A-Z]+",
            "[0-9]+",
            "^[a-zA-Z0-9]+$",
            "^\\d{4}-\\d{2}-\\d{2}$" // date pattern
          ),
          fc
            .uniqueArray(fc.constantFrom("g", "i", "m", "s", "u", "y"), {
              minLength: 0,
              maxLength: 6
            })
            .map((flags) => flags.join(""))
        )
        .map(([source, flags]) => new globalThis.RegExp(source, flags)),
    toEquivalence: () => (a, b) => a.source === b.source && a.flags === b.flags
  }
)

/**
 * Type-level representation of {@link URL}.
 *
 * @category URL
 * @since 4.0.0
 */
export interface URL extends instanceOf<globalThis.URL> {
  readonly "Rebuild": URL
}

const URLString = String.annotate({ expected: "a string that will be decoded as a URL" })

/**
 * Schema for JavaScript `URL` objects.
 *
 * **Details**
 *
 * Default JSON serializer:
 *
 * - encodes `URL` as a `string`
 *
 * @category URL
 * @since 4.0.0
 */
export const URL: URL = instanceOf(
  globalThis.URL,
  {
    typeConstructor: {
      _tag: "URL"
    },
    generation: {
      runtime: `Schema.URL`,
      Type: `globalThis.URL`
    },
    expected: "URL",
    toCodecJson: () =>
      link<globalThis.URL>()(
        URLString,
        SchemaTransformation.urlFromString
      ),
    toArbitrary: () => (fc) => fc.webUrl().map((s) => new globalThis.URL(s)),
    toEquivalence: () => (a, b) => a.toString() === b.toString()
  }
)

/**
 * Type-level representation of {@link URLFromString}.
 *
 * @category URL
 * @since 4.0.0
 */
export interface URLFromString extends decodeTo<URL, String> {
  readonly "Rebuild": URLFromString
}

/**
 * Schema that decodes a `string` into a `URL`.
 *
 * **Details**
 *
 * Decoding:
 * - A **valid** URL `string` is decoded as a `URL`
 *
 * Encoding:
 * - A `URL` is encoded as a `string`
 *
 * @category URL
 * @since 4.0.0
 */
export const URLFromString: URLFromString = URLString.pipe(decodeTo(URL, SchemaTransformation.urlFromString))

/**
 * Type-level representation of {@link Date}.
 *
 * @category Date
 * @since 4.0.0
 */
export interface Date extends instanceOf<globalThis.Date> {
  readonly "Rebuild": Date
}

type DateArbitraryConstraints = FastCheck.DateConstraints & {
  readonly valid?: boolean | undefined
}

function dateArbitraryConstraints<T = globalThis.Date>(
  constraint: Annotations.ToArbitrary.GenerationConstraint | undefined,
  ordered: Annotations.ToArbitrary.OrderedConstraint<T> | undefined,
  base?: DateArbitraryConstraints | undefined,
  toDate?: (value: T) => globalThis.Date
): FastCheck.DateConstraints {
  const out: FastCheck.DateConstraints = { ...base }
  delete (out as any).valid
  if (base?.valid || constraint?.valid) {
    out.noInvalidDate = true
  }
  if (ordered?.minimum !== undefined) {
    const minimum = toDate === undefined ? ordered.minimum as globalThis.Date : toDate(ordered.minimum)
    const nextMin = ordered.exclusiveMinimum ? new globalThis.Date(minimum.getTime() + 1) : minimum
    if (out.min === undefined || nextMin.getTime() > out.min.getTime()) {
      out.min = nextMin
    }
  }
  if (ordered?.maximum !== undefined) {
    const maximum = toDate === undefined ? ordered.maximum as globalThis.Date : toDate(ordered.maximum)
    const nextMax = ordered.exclusiveMaximum ? new globalThis.Date(maximum.getTime() - 1) : maximum
    if (out.max === undefined || nextMax.getTime() < out.max.getTime()) {
      out.max = nextMax
    }
  }
  return out
}

const DateString = String.annotate({ expected: "a string in ISO 8601 format that will be decoded as a Date" })

/**
 * Schema for JavaScript `Date` objects.
 *
 * **When to use**
 *
 * Use to validate in-memory values that must already be JavaScript date
 * objects.
 *
 * **Details**
 *
 * This schema accepts any `Date` instance, including invalid dates. The default
 * JSON serializer encodes valid dates as ISO 8601 strings; invalid dates encode
 * as `"Invalid Date"`.
 *
 * **Example** (Defining a Date schema)
 *
 * ```ts
 * import { Schema } from "effect"
 *
 * Schema.decodeUnknownSync(Schema.Date)(new Date("2024-01-01"))
 * // => Date { 2024-01-01T00:00:00.000Z }
 * ```
 *
 * @see {@link DateValid} for accepting only valid Date instances
 * @see {@link DateFromString} for decoding strings into Date instances
 * @see {@link DateFromMillis} for decoding epoch milliseconds into Date instances
 *
 * @category Date
 * @since 4.0.0
 */
export const Date: Date = instanceOf(
  globalThis.Date,
  {
    typeConstructor: {
      _tag: "Date"
    },
    generation: {
      runtime: `Schema.Date`,
      Type: `globalThis.Date`
    },
    expected: "Date",
    toCodecJson: () =>
      link<globalThis.Date>()(
        DateString,
        SchemaTransformation.dateFromString
      ),
    toArbitrary: () => (fc, ctx) =>
      fc.date(dateArbitraryConstraints(
        ctx?.constraint,
        ctx?.constraint?.ordered?.order === Order.Date ? ctx.constraint.ordered : undefined
      ))
  }
)

/**
 * Type-level representation of {@link DateFromString}.
 *
 * @category Date
 * @since 3.10.0
 */
export interface DateFromString extends decodeTo<Date, String> {
  readonly "Rebuild": DateFromString
}

/**
 * Schema that decodes a string into a JavaScript `Date`.
 *
 * **When to use**
 *
 * Use to model string-encoded dates that decode to JavaScript `Date` objects
 * and encode back to strings.
 *
 * **Details**
 *
 * Decoding:
 * The string is passed to JavaScript `Date` construction.
 *
 * Encoding:
 * A valid `Date` is encoded as an ISO string; an invalid `Date` is encoded as
 * `"Invalid Date"`.
 *
 * **Gotchas**
 *
 * Invalid date strings can decode to invalid `Date` instances.
 *
 * @see {@link DateFromMillis} for decoding epoch milliseconds into Date instances
 * @see {@link DateTimeUtcFromString} for decoding date-time strings into UTC values
 * @see {@link Date} for accepting Date instances directly
 * @see {@link DateValid} for rejecting invalid Date instances
 *
 * @category Date
 * @since 3.10.0
 */
export const DateFromString: DateFromString = DateString.pipe(decodeTo(Date, SchemaTransformation.dateFromString))

/**
 * Type-level representation of {@link DateFromMillis}.
 *
 * @category Date
 * @since 4.0.0
 */
export interface DateFromMillis extends decodeTo<Date, Number> {
  readonly "Rebuild": DateFromMillis
}

/**
 * Schema that decodes epoch milliseconds into a JavaScript `Date`.
 *
 * **When to use**
 *
 * Use to model numeric millisecond timestamps that decode to JavaScript `Date`
 * objects and encode back to numbers.
 *
 * **Details**
 *
 * Decoding:
 * A number of milliseconds since the Unix epoch is decoded as a `Date`.
 *
 * Encoding:
 * A `Date` is encoded as its millisecond timestamp.
 *
 * **Gotchas**
 *
 * This schema accepts any number, including `NaN`, `Infinity`, and `-Infinity`.
 * Those values decode to invalid `Date` instances.
 *
 * @see {@link DateFromString} for decoding string-encoded dates
 * @see {@link DateTimeUtcFromMillis} for decoding epoch milliseconds into UTC values
 *
 * @category Date
 * @since 4.0.0
 */
export const DateFromMillis: DateFromMillis = Number.pipe(
  decodeTo(Date, SchemaTransformation.dateFromMillis)
)

/**
 * Type-level representation of {@link DateValid}.
 *
 * @category Date
 * @since 4.0.0
 */
export interface DateValid extends Date {
  readonly "Rebuild": DateValid
}

/**
 * Schema for **valid** JavaScript `Date` objects.
 *
 * **Details**
 *
 * This schema accepts `Date` instances but rejects invalid dates (such as `new
 * Date("invalid")`).
 *
 * @category Date
 * @since 4.0.0
 */
export const DateValid: DateValid = Date.check(isDateValid())

/**
 * Type-level representation of {@link Duration}.
 *
 * @category Duration
 * @since 3.10.0
 */
export interface Duration extends declare<Duration_.Duration> {
  readonly "Rebuild": Duration
}

/**
 * Schema for `Duration` values.
 *
 * **Details**
 *
 * The default JSON serializer encodes `Duration` as a tagged object with the
 * duration type and value.
 *
 * **Example** (Defining a Duration schema)
 *
 * ```ts
 * import { Duration, Schema } from "effect"
 *
 * Schema.decodeUnknownSync(Schema.Duration)(Duration.seconds(5))
 * // => Duration(5s)
 * ```
 *
 * @category Duration
 *
 * @since 3.10.0
 */
export const Duration: Duration = declare(
  Duration_.isDuration,
  {
    typeConstructor: {
      _tag: "effect/Duration"
    },
    generation: {
      runtime: `Schema.Duration`,
      Type: `Duration.Duration`,
      importDeclaration: `import * as Duration from "effect/Duration"`
    },
    expected: "Duration",
    toCodecJson: () =>
      link<Duration_.Duration>()(
        Union([
          Struct({ _tag: Literal("Infinity") }),
          Struct({ _tag: Literal("NegativeInfinity") }),
          Struct({ _tag: Literal("Nanos"), value: BigInt }),
          Struct({ _tag: Literal("Millis"), value: Int })
        ]),
        SchemaTransformation.transform({
          decode: (e) => {
            switch (e._tag) {
              case "Infinity":
                return Duration_.infinity
              case "NegativeInfinity":
                return Duration_.negativeInfinity
              case "Nanos":
                return Duration_.nanos(e.value)
              case "Millis":
                return Duration_.millis(e.value)
            }
          },
          encode: (duration) => {
            switch (duration.value._tag) {
              case "Infinity":
                return { _tag: "Infinity" } as const
              case "NegativeInfinity":
                return { _tag: "NegativeInfinity" } as const
              case "Nanos":
                return { _tag: "Nanos", value: duration.value.nanos } as const
              case "Millis":
                return { _tag: "Millis", value: duration.value.millis } as const
            }
          }
        })
      ),
    toArbitrary: () => (fc) =>
      fc.oneof(
        fc.constant(Duration_.infinity),
        fc.constant(Duration_.negativeInfinity),
        fc.bigInt().map(Duration_.nanos),
        fc.maxSafeInteger().map(Duration_.millis)
      ),
    toFormatter: () => globalThis.String,
    toEquivalence: () => Duration_.Equivalence
  }
)

const DurationString = String.annotate({ expected: "a string that will be decoded as a Duration" })

/**
 * Type-level representation of {@link DurationFromString}.
 *
 * @category Duration
 * @since 4.0.0
 */
export interface DurationFromString extends decodeTo<Duration, String> {
  readonly "Rebuild": DurationFromString
}

/**
 * Schema that parses a string into a `Duration`.
 *
 * **Details**
 *
 * Decoding:
 * - A `string` is decoded as a `Duration`, accepting any format that
 *   `Duration.fromInput` can parse.
 *
 * Encoding:
 * - A `Duration` is encoded as a parseable `string`.
 *
 * @category Duration
 * @since 4.0.0
 */
export const DurationFromString: DurationFromString = DurationString.pipe(
  decodeTo(Duration, SchemaTransformation.durationFromString)
)

/**
 * Type-level representation of {@link DurationFromNanos}.
 *
 * @category Duration
 * @since 3.10.0
 */
export interface DurationFromNanos extends decodeTo<Duration, BigInt> {
  readonly "Rebuild": DurationFromNanos
}

const bigint0 = globalThis.BigInt(0)

/**
 * Schema that decodes a non-negative `bigint` into a
 * `Duration`, treating the bigint as nanoseconds.
 *
 * **Details**
 *
 * Decoding:
 * A non-negative `bigint` representing nanoseconds is decoded as a `Duration`.
 *
 * Encoding:
 * Finite durations are encoded as a non-negative `bigint` number of nanoseconds.
 * Encoding fails when the duration cannot be represented as nanoseconds, such as
 * `Duration.infinity`.
 *
 * @category Duration
 * @since 3.10.0
 */
export const DurationFromNanos: DurationFromNanos = BigInt.check(isGreaterThanOrEqualToBigInt(bigint0)).pipe(
  decodeTo(Duration, SchemaTransformation.durationFromNanos)
)

/**
 * Type-level representation of {@link DurationFromMillis}.
 *
 * @category Duration
 * @since 3.10.0
 */
export interface DurationFromMillis extends decodeTo<Duration, Number> {
  readonly "Rebuild": DurationFromMillis
}

/**
 * Schema that decodes a non-negative (possibly infinite)
 * integer into a `Duration`, treating the integer value as the duration in
 * milliseconds.
 *
 * **Details**
 *
 * Decoding:
 * - A non-negative (possibly infinite) integer representing milliseconds is
 *   decoded as a `Duration`
 *
 * Encoding:
 * - A `Duration` is encoded to a non-negative (possibly infinite) integer
 *   representing milliseconds
 *
 * @category Duration
 * @since 3.10.0
 */
export const DurationFromMillis: DurationFromMillis = Number.check(isGreaterThanOrEqualTo(0)).pipe(
  decodeTo(Duration, SchemaTransformation.durationFromMillis)
)

/**
 * Type-level representation of {@link BigDecimal}.
 *
 * @category BigDecimal
 * @since 3.10.0
 */
export interface BigDecimal extends declare<BigDecimal_.BigDecimal> {
  readonly "Rebuild": BigDecimal
}

const BigDecimalString = String.annotate({ expected: "a string that will be decoded as a BigDecimal" })

const bigDecimalDefaultMaxScale = 20
const bigDecimalInvalidOrderedConstraintsError = "Unable to derive an arbitrary for the ordered BigDecimal constraints"

function bigDecimalScaleValueAtScale(bd: BigDecimal_.BigDecimal, scale: number): bigint {
  return BigDecimal_.scale(bd, scale).value
}

function bigDecimalMinValueAtScale(
  minimum: BigDecimal_.BigDecimal,
  scale: number,
  excluded: boolean
): bigint {
  return excluded
    ? bigDecimalScaleValueAtScale(BigDecimal_.floor(minimum, scale), scale) + globalThis.BigInt(1)
    : bigDecimalScaleValueAtScale(BigDecimal_.ceil(minimum, scale), scale)
}

function bigDecimalMaxValueAtScale(
  maximum: BigDecimal_.BigDecimal,
  scale: number,
  excluded: boolean
): bigint {
  return excluded
    ? bigDecimalScaleValueAtScale(BigDecimal_.ceil(maximum, scale), scale) - globalThis.BigInt(1)
    : bigDecimalScaleValueAtScale(BigDecimal_.floor(maximum, scale), scale)
}

function bigDecimalMaxScale(ordered: Annotations.ToArbitrary.OrderedConstraint<BigDecimal_.BigDecimal>): number {
  return Math.max(
    bigDecimalDefaultMaxScale,
    ordered.minimum?.scale ?? 0,
    ordered.maximum?.scale ?? 0,
    ordered.exclusiveMinimum && ordered.minimum !== undefined ? ordered.minimum.scale + 1 : 0,
    ordered.exclusiveMaximum && ordered.maximum !== undefined ? ordered.maximum.scale + 1 : 0
  )
}

function bigDecimalValueConstraintsAtScale(
  ordered: Annotations.ToArbitrary.OrderedConstraint<BigDecimal_.BigDecimal>,
  scale: number
): FastCheck.BigIntConstraints | undefined {
  const constraints: FastCheck.BigIntConstraints = {}
  if (ordered.minimum !== undefined) {
    constraints.min = bigDecimalMinValueAtScale(ordered.minimum, scale, ordered.exclusiveMinimum === true)
  }
  if (ordered.maximum !== undefined) {
    constraints.max = bigDecimalMaxValueAtScale(ordered.maximum, scale, ordered.exclusiveMaximum === true)
  }
  if (constraints.min !== undefined && constraints.max !== undefined && constraints.min > constraints.max) {
    return undefined
  }
  return constraints
}

function bigDecimalScaleConstraints(
  ordered: Annotations.ToArbitrary.OrderedConstraint<BigDecimal_.BigDecimal>
): FastCheck.IntegerConstraints {
  const max = bigDecimalMaxScale(ordered)
  if (bigDecimalValueConstraintsAtScale(ordered, max) === undefined) {
    throw new globalThis.Error(bigDecimalInvalidOrderedConstraintsError)
  }

  let min = 0
  let high = max
  while (min < high) {
    const scale = min + Math.floor((high - min) / 2)
    if (bigDecimalValueConstraintsAtScale(ordered, scale) === undefined) {
      min = scale + 1
    } else {
      high = scale
    }
  }
  return { min, max }
}

/**
 * Schema for `BigDecimal` values.
 *
 * **When to use**
 *
 * Use when you already have Effect decimal instances and need schema
 * validation, formatting, equivalence, and JSON string serialization.
 *
 * **Details**
 *
 * Default JSON serializer:
 *
 * - encodes `BigDecimal` as a `string`
 *
 * @see {@link BigDecimalFromString} for parsing string input into a BigDecimal
 *
 * @category BigDecimal
 * @since 3.10.0
 */
export const BigDecimal: BigDecimal = declare(
  BigDecimal_.isBigDecimal,
  {
    typeConstructor: {
      _tag: "effect/BigDecimal"
    },
    generation: {
      runtime: `Schema.BigDecimal`,
      Type: `BigDecimal.BigDecimal`,
      importDeclaration: `import * as BigDecimal from "effect/BigDecimal"`
    },
    expected: "BigDecimal",
    toCodecJson: () =>
      link<BigDecimal_.BigDecimal>()(
        BigDecimalString,
        SchemaTransformation.bigDecimalFromString
      ),
    toArbitrary: () => (fc, ctx) => {
      const ordered = ctx.constraint?.ordered?.order === BigDecimal_.Order
        ? ctx.constraint.ordered as Annotations.ToArbitrary.OrderedConstraint<BigDecimal_.BigDecimal>
        : undefined
      if (ordered === undefined) {
        return fc.tuple(fc.bigInt(), fc.integer({ min: 0, max: bigDecimalDefaultMaxScale }))
          .map(([value, scale]) => BigDecimal_.make(value, scale))
      }

      return fc.integer(bigDecimalScaleConstraints(ordered)).chain((scale) => {
        const constraints = bigDecimalValueConstraintsAtScale(ordered, scale)
        if (constraints === undefined) {
          throw new globalThis.Error(bigDecimalInvalidOrderedConstraintsError)
        }
        return fc.bigInt(constraints).map((value) => BigDecimal_.make(value, scale))
      })
    },
    toFormatter: () => (bd) => BigDecimal_.format(bd),
    toEquivalence: () => BigDecimal_.Equivalence
  }
)

/**
 * Type-level representation of {@link BigDecimalFromString}.
 *
 * @category BigDecimal
 * @since 4.0.0
 */
export interface BigDecimalFromString extends decodeTo<BigDecimal, String> {
  readonly "Rebuild": BigDecimalFromString
}

/**
 * Schema that parses a string into a `BigDecimal`.
 *
 * **When to use**
 *
 * Use to parse decimal or exponent-notation strings into arbitrary-precision
 * BigDecimal values while encoding them back to strings.
 *
 * **Details**
 *
 * Decoding:
 * - A `string` is decoded with `BigDecimal.fromString`.
 *
 * Encoding:
 * - A `BigDecimal` is encoded with `BigDecimal.format`.
 *
 * **Gotchas**
 *
 * An empty string decodes as zero.
 *
 * @see {@link BigDecimal} for validating values that are already BigDecimal values
 * @see {@link BigIntFromString} for parsing base-10 integer strings into bigint values
 * @see {@link NumberFromString} for parsing JavaScript number strings
 *
 * @category BigDecimal
 * @since 4.0.0
 */
export const BigDecimalFromString: BigDecimalFromString = BigDecimalString.pipe(
  decodeTo(BigDecimal, SchemaTransformation.bigDecimalFromString)
)

/**
 * Type-level representation of {@link UnknownFromJsonString}.
 *
 * @category models
 * @since 4.0.0
 */
export interface UnknownFromJsonString extends fromJsonString<Unknown> {
  readonly "Rebuild": UnknownFromJsonString
}

/**
 * Schema that decodes a JSON-encoded string into an `unknown` value.
 *
 * **Details**
 *
 * Decoding:
 * - A `string` is decoded as an `unknown` value.
 * - If the string is not valid JSON, decoding fails.
 *
 * Encoding:
 * - Any value is encoded as a JSON string using `JSON.stringify`.
 * - If the value is not a valid JSON value, encoding fails.
 *
 * **Example** (Decoding unknown JSON strings)
 *
 * ```ts
 * import { Schema } from "effect"
 *
 * Schema.decodeUnknownSync(Schema.UnknownFromJsonString)(`{"a":1,"b":2}`)
 * // => { a: 1, b: 2 }
 * ```
 *
 * @category schemas
 * @since 4.0.0
 */
export const UnknownFromJsonString: UnknownFromJsonString = fromJsonString(Unknown)

/**
 * Type-level representation returned by {@link fromJsonString}.
 *
 * @category models
 * @since 4.0.0
 */
export interface fromJsonString<S extends Constraint> extends decodeTo<S, String> {
  readonly "Rebuild": fromJsonString<S>
}

/**
 * Returns a schema that decodes a JSON string and then decodes the parsed value
 * using the given schema.
 *
 * **Details**
 *
 * This is useful when working with JSON-encoded strings where the actual
 * structure of the value is known and described by an existing schema.
 *
 * The resulting schema first parses the input string as JSON, and then runs the
 * provided schema on the parsed result.
 *
 * JSON Schema generation:
 *
 * When using `fromJsonString` with `draft-2020-12` or `openApi3.1`, the
 * resulting schema will be a JSON Schema with a `contentSchema` property that
 * contains the JSON Schema for the given schema.
 *
 * **Example** (Decoding JSON strings with a schema)
 *
 * ```ts
 * import { Schema } from "effect"
 *
 * const schema = Schema.Struct({ a: Schema.Number })
 * const schemaFromJsonString = Schema.fromJsonString(schema)
 *
 * Schema.decodeUnknownSync(schemaFromJsonString)(`{"a":1,"b":2}`)
 * // => { a: 1 }
 * ```
 *
 * **Example** (Emitting JSON Schema for a JSON string decoder)
 *
 * ```ts
 * import { Schema } from "effect"
 *
 * const original = Schema.Struct({ a: Schema.String })
 * const schema = Schema.fromJsonString(original)
 *
 * const document = Schema.toJsonSchemaDocument(schema)
 *
 * console.log(JSON.stringify(document, null, 2))
 * // {
 * //   "source": "draft-2020-12",
 * //   "schema": {
 * //     "type": "string",
 * //     "contentMediaType": "application/json",
 * //     "contentSchema": {
 * //       "type": "object",
 * //       "properties": {
 * //         "a": {
 * //           "type": "string"
 * //         }
 * //       },
 * //       "required": [
 * //         "a"
 * //       ],
 * //       "additionalProperties": false
 * //     }
 * //   },
 * //   "definitions": {}
 * // }
 * ```
 *
 * @category constructors
 * @since 4.0.0
 */
export function fromJsonString<S extends Constraint>(schema: S): fromJsonString<S> {
  const identifier = SchemaAST.resolveIdentifier(schema.ast)
  return String.annotate({
    // Give the transport wrapper its own name so the decoded payload keeps its identifier.
    identifier: identifier === undefined ? undefined : `${identifier}JsonString`,
    expected: "a string that will be decoded as JSON",
    contentMediaType: "application/json",
    contentSchema: SchemaAST.toEncoded(schema.ast)
  }).pipe(decodeTo(schema, SchemaTransformation.fromJsonString))
}

/**
 * Type-level representation of {@link File}.
 *
 * @category file
 * @since 4.0.0
 */
export interface File extends instanceOf<globalThis.File> {
  readonly "Rebuild": File
}

/**
 * Schema for JavaScript `File` objects.
 *
 * **Details**
 *
 * The default JSON serializer encodes a `File` as `{ data, type, name, lastModified }`
 * where `data` is base64-encoded.
 *
 * @category file
 * @since 4.0.0
 */
export const File: File = instanceOf(globalThis.File, {
  typeConstructor: {
    _tag: "File"
  },
  generation: {
    runtime: `Schema.File`,
    Type: `globalThis.File`
  },
  expected: "File",
  toCodecJson: () =>
    link<globalThis.File>()(
      Struct({
        data: String.check(isBase64()),
        type: String,
        name: String,
        lastModified: Number
      }),
      SchemaTransformation.transformOrFail({
        decode: (e) =>
          Result_.match(Encoding.decodeBase64(e.data), {
            onFailure: (error) =>
              Effect.fail(
                new SchemaIssue.InvalidValue(Option_.some(e.data), {
                  message: error.message
                })
              ),
            onSuccess: (bytes) => {
              const buffer = new globalThis.Uint8Array(bytes)
              return Effect.succeed(
                new globalThis.File([buffer], e.name, { type: e.type, lastModified: e.lastModified })
              )
            }
          }),
        encode: (file) =>
          Effect.tryPromise({
            try: async () => {
              const bytes = new globalThis.Uint8Array(await file.arrayBuffer())
              return {
                data: Encoding.encodeBase64(bytes),
                type: file.type,
                name: file.name,
                lastModified: file.lastModified
              }
            },
            catch: (e) =>
              new SchemaIssue.InvalidValue(Option_.some(file), {
                message: globalThis.String(e)
              })
          })
      })
    )
})

/**
 * Type-level representation of {@link FormData}.
 *
 * @category FormData
 * @since 4.0.0
 */
export interface FormData extends instanceOf<globalThis.FormData> {
  readonly "Rebuild": FormData
}

/**
 * Schema for JavaScript `FormData` objects.
 *
 * **Details**
 *
 * The default JSON serializer encodes a `FormData` as an array of `[key, entry]`
 * pairs where each entry is tagged as `"String"` or `"File"`.
 *
 * @category FormData
 * @since 4.0.0
 */
export const FormData: FormData = instanceOf(globalThis.FormData, {
  typeConstructor: {
    _tag: "FormData"
  },
  generation: {
    runtime: `Schema.FormData`,
    Type: `globalThis.FormData`
  },
  expected: "FormData",
  toCodecJson: () =>
    link<globalThis.FormData>()(
      ArraySchema(
        Tuple([
          String,
          Union([
            Struct({ _tag: tag("String"), value: String }),
            Struct({ _tag: tag("File"), value: File })
          ])
        ])
      ),
      SchemaTransformation.transformOrFail({
        decode: (e) => {
          const out = new globalThis.FormData()
          for (const [key, entry] of e) {
            out.append(key, entry.value)
          }
          return Effect.succeed(out)
        },
        encode: (formData) => {
          return Effect.succeed(
            globalThis.Array.from(formData.entries()).map(([key, value]) => {
              if (typeof value === "string") {
                return [key, { _tag: "String", value }] as const
              } else {
                return [key, { _tag: "File", value }] as const
              }
            })
          )
        }
      })
    )
})

/**
 * Type-level representation returned by {@link fromFormData}.
 *
 * @category FormData
 * @since 4.0.0
 */
export interface fromFormData<S extends Constraint> extends decodeTo<S, FormData> {
  readonly "Rebuild": fromFormData<S>
}

/**
 * Schema for decoding `FormData` through a bracket-notation tree.
 *
 * **When to use**
 *
 * Use to decode browser or multipart form data into a structured schema value.
 *
 * **Details**
 *
 * The decoding process has two steps:
 *
 * 1. Parse `FormData` into a nested tree record.
 * 2. Decode the parsed value with the given schema.
 *
 * You can express nested values using bracket notation.
 *
 * If you want to decode string fields into non-string primitive values, use
 * `Schema.toCodecStringTree`.
 *
 * **Example** (Decoding a flat structure)
 *
 * ```ts
 * import { Schema } from "effect"
 *
 * const schema = Schema.fromFormData(
 *   Schema.Struct({
 *     a: Schema.String
 *   })
 * )
 *
 * const formData = new FormData()
 * formData.append("a", "1")
 * formData.append("b", "2")
 *
 * console.log(String(Schema.decodeUnknownExit(schema)(formData)))
 * // Success({"a":"1"})
 * ```
 *
 * **Example** (Decoding nested fields)
 *
 * ```ts
 * import { Schema } from "effect"
 *
 * const schema = Schema.fromFormData(
 *   Schema.Struct({
 *     a: Schema.String,
 *     b: Schema.Struct({
 *       c: Schema.String,
 *       d: Schema.String
 *     })
 *   })
 * )
 *
 * const formData = new FormData()
 * formData.append("a", "1")
 * formData.append("b[c]", "2")
 * formData.append("b[d]", "3")
 *
 * console.log(String(Schema.decodeUnknownExit(schema)(formData)))
 * // Success({"a":"1","b":{"c":"2","d":"3"}})
 * ```
 *
 * **Example** (Parsing non-string values)
 *
 * ```ts
 * import { Schema } from "effect"
 *
 * const schema = Schema.fromFormData(
 *   Schema.toCodecStringTree(
 *     Schema.Struct({
 *       a: Schema.Int
 *     })
 *   )
 * )
 *
 * const formData = new FormData()
 * formData.append("a", "1")
 *
 * console.log(String(Schema.decodeUnknownExit(schema)(formData)))
 * // Success({"a":1}) // Note: the value is a number
 * ```
 *
 * @category decoding
 * @since 4.0.0
 */
export function fromFormData<S extends Constraint>(schema: S): fromFormData<S> {
  return FormData.pipe(decodeTo(schema, SchemaTransformation.fromFormData))
}

/**
 * Type-level representation of {@link URLSearchParams}.
 *
 * @category search params
 * @since 4.0.0
 */
export interface URLSearchParams extends instanceOf<globalThis.URLSearchParams> {
  readonly "Rebuild": URLSearchParams
}

/**
 * Schema for JavaScript `URLSearchParams` objects.
 *
 * **Details**
 *
 * The default JSON serializer encodes a `URLSearchParams` as a query string.
 *
 * @category search params
 * @since 4.0.0
 */
export const URLSearchParams: URLSearchParams = instanceOf(globalThis.URLSearchParams, {
  typeConstructor: {
    _tag: "URLSearchParams"
  },
  generation: {
    runtime: `Schema.URLSearchParams`,
    Type: `globalThis.URLSearchParams`
  },
  expected: "URLSearchParams",
  toCodecJson: () =>
    link<globalThis.URLSearchParams>()(
      String.annotate({ expected: "a query string that will be decoded as URLSearchParams" }),
      SchemaTransformation.transform({
        decode: (e) => new globalThis.URLSearchParams(e),
        encode: (params) => params.toString()
      })
    )
})

/**
 * Type-level representation returned by {@link fromURLSearchParams}.
 *
 * @category search params
 * @since 4.0.0
 */
export interface fromURLSearchParams<S extends Constraint> extends decodeTo<S, URLSearchParams> {
  readonly "Rebuild": fromURLSearchParams<S>
}

/**
 * Schema for decoding `URLSearchParams` through a bracket-notation tree.
 *
 * **When to use**
 *
 * Use to decode query parameters into a structured schema value.
 *
 * **Details**
 *
 * The decoding process has two steps:
 *
 * 1. Parse `URLSearchParams` into a nested tree record.
 * 2. Decode the parsed value with the given schema.
 *
 * You can express nested values using bracket notation.
 *
 * If you want to decode values that are not strings, use
 * `Schema.toCodecStringTree`. This serializer preserves values such as
 * numbers when compatible with the schema.
 *
 * **Example** (Decoding a flat structure)
 *
 * ```ts
 * import { Schema } from "effect"
 *
 * const schema = Schema.fromURLSearchParams(
 *   Schema.Struct({
 *     a: Schema.String
 *   })
 * )
 *
 * const urlSearchParams = new URLSearchParams("a=1&b=2")
 *
 * console.log(String(Schema.decodeUnknownExit(schema)(urlSearchParams)))
 * // Success({"a":"1"})
 * ```
 *
 * **Example** (Decoding nested fields)
 *
 * ```ts
 * import { Schema } from "effect"
 *
 * const schema = Schema.fromURLSearchParams(
 *   Schema.Struct({
 *     a: Schema.String,
 *     b: Schema.Struct({
 *       c: Schema.String,
 *       d: Schema.String
 *     })
 *   })
 * )
 *
 * const urlSearchParams = new URLSearchParams("a=1&b[c]=2&b[d]=3")
 *
 * console.log(String(Schema.decodeUnknownExit(schema)(urlSearchParams)))
 * // Success({"a":"1","b":{"c":"2","d":"3"}})
 * ```
 *
 * **Example** (Parsing non-string values)
 *
 * ```ts
 * import { Schema } from "effect"
 *
 * const schema = Schema.fromURLSearchParams(
 *   Schema.toCodecStringTree(
 *     Schema.Struct({
 *       a: Schema.Int
 *     })
 *   )
 * )
 *
 * const urlSearchParams = new URLSearchParams("a=1&b=2")
 *
 * console.log(String(Schema.decodeUnknownExit(schema)(urlSearchParams)))
 * // Success({"a":1}) // Note: the value is a number
 * ```
 *
 * @category decoding
 * @since 4.0.0
 */
export function fromURLSearchParams<S extends Constraint>(schema: S): fromURLSearchParams<S> {
  return URLSearchParams.pipe(decodeTo(schema, SchemaTransformation.fromURLSearchParams))
}

/**
 * Type-level representation of {@link Finite}.
 *
 * @category Number
 * @since 3.10.0
 */
export interface Finite extends Number {
  readonly "Rebuild": Finite
}

/**
 * Schema for finite numbers, rejecting `NaN`, `Infinity`, and `-Infinity`.
 *
 * @category Number
 * @since 3.10.0
 */
export const Finite: Finite = Number.check(isFinite())

/**
 * Type-level representation of {@link Int}.
 *
 * @category Number
 * @since 3.10.0
 */
export interface Int extends Number {
  readonly "Rebuild": Int
}

/**
 * Schema for integers, rejecting `NaN`, `Infinity`, and `-Infinity`.
 *
 * @category Number
 * @since 3.10.0
 */
export const Int: Int = Number.check(isInt())

/**
 * Type-level representation of {@link NumberFromString}.
 *
 * @category Number
 * @since 3.10.0
 */
export interface NumberFromString extends decodeTo<Finite, String> {
  readonly "Rebuild": NumberFromString
}

/**
 * Schema that parses a string into a `number` using JavaScript
 * number coercion.
 *
 * **Details**
 *
 * Decoding:
 * A `string` is decoded as a number, including possible non-finite values such as
 * `NaN`, `Infinity`, and `-Infinity`. Use `FiniteFromString` to reject non-finite
 * numbers.
 *
 * Encoding:
 * A number is encoded as a `string`.
 *
 * @category Number
 * @since 3.10.0
 */
export const NumberFromString: NumberFromString = String.annotate({
  expected: "a string that will be decoded as a number"
}).pipe(decodeTo(Number, SchemaTransformation.numberFromString))

/**
 * Type-level representation of {@link FiniteFromString}.
 *
 * @category Number
 * @since 4.0.0
 */
export interface FiniteFromString extends decodeTo<Finite, String> {
  readonly "Rebuild": FiniteFromString
}

/**
 * Schema that parses a string into a finite number.
 *
 * **Details**
 *
 * Decoding:
 * - A `string` is decoded as a finite number, rejecting `NaN`, `Infinity`, and
 *   `-Infinity` values.
 *
 * Encoding:
 * - A finite number is encoded as a `string`.
 *
 * @category Number
 * @since 4.0.0
 */
export const FiniteFromString: FiniteFromString = String.annotate({
  expected: "a string that will be decoded as a finite number"
}).pipe(decodeTo(Finite, SchemaTransformation.numberFromString))

/**
 * Type-level representation of {@link BigIntFromString}.
 *
 * @category BigInt
 * @since 4.0.0
 */
export interface BigIntFromString extends decodeTo<BigInt, String> {
  readonly "Rebuild": BigIntFromString
}

/**
 * Schema that parses a string into a `bigint`.
 *
 * **When to use**
 *
 * Use to parse signed base-10 integer strings into bigint values while encoding
 * bigint values back to decimal strings.
 *
 * **Details**
 *
 * Decoding:
 * - A `string` is decoded as a `bigint`.
 *
 * Encoding:
 * - A `bigint` is encoded as a `string`.
 *
 * **Gotchas**
 *
 * Decoding accepts only strings matching `^-?\d+$`.
 *
 * @see {@link isStringBigInt} for the string predicate used by this schema
 * @see {@link BigInt} for validating values that are already bigint values
 * @see {@link NumberFromString} for parsing JavaScript number strings, including non-finite values
 * @see {@link BigDecimalFromString} for parsing decimal number strings
 *
 * @category BigInt
 * @since 4.0.0
 */
export const BigIntFromString: BigIntFromString = make<String>(SchemaAST.bigIntString).pipe(
  decodeTo(BigInt, SchemaTransformation.bigintFromString)
)

/**
 * Type-level representation of {@link Trimmed}.
 *
 * @category string
 * @since 3.10.0
 */
export interface Trimmed extends String {
  readonly "Rebuild": Trimmed
}

/**
 * Schema for strings that contains no leading or trailing whitespaces.
 *
 * @category string
 * @since 3.10.0
 */
export const Trimmed: Trimmed = String.check(isTrimmed())

/**
 * Type-level representation of {@link Trim}.
 *
 * @category string
 * @since 3.10.0
 */
export interface Trim extends decodeTo<Trimmed, String> {
  readonly "Rebuild": Trim
}

/**
 * Schema that trims whitespace from a string.
 *
 * **Details**
 *
 * Decoding:
 * - A `string` is decoded as a string with no leading or trailing whitespaces.
 *
 * Encoding:
 * - The trimmed string is encoded as is.
 *
 * @category string
 * @since 3.10.0
 */
export const Trim: Trim = String.annotate({
  expected: "a string that will be decoded as a trimmed string"
}).pipe(decodeTo(Trimmed, SchemaTransformation.trim()))

/**
 * Type-level representation of {@link StringFromBase64}.
 *
 * @category string
 * @since 3.10.0
 */
export interface StringFromBase64 extends decodeTo<String, String> {
  readonly "Rebuild": StringFromBase64
}

/**
 * Decodes a base64 (RFC4648) encoded string into a UTF-8 string.
 *
 * **Details**
 *
 * Decoding:
 * - A **valid** base64 encoded string is decoded as a UTF-8 `string`.
 *
 * Encoding:
 * - A `string` is encoded as a base64-encoded string.
 *
 * @category string
 * @since 3.10.0
 */
export const StringFromBase64: StringFromBase64 = String.annotate({
  expected: "a base64 encoded string that will be decoded as a UTF-8 string"
}).pipe(
  decodeTo(String, SchemaTransformation.stringFromBase64String)
)

/**
 * Type-level representation of {@link StringFromBase64Url}.
 *
 * @category string
 * @since 3.10.0
 */
export interface StringFromBase64Url extends decodeTo<String, String> {
  readonly "Rebuild": StringFromBase64Url
}

/**
 * Decodes a base64 (URL) encoded string into a UTF-8 string.
 *
 * **Details**
 *
 * Decoding:
 * - A **valid** base64 (URL) encoded string is decoded as a UTF-8 `string`.
 *
 * Encoding:
 * - A `string` is encoded as a base64 (URL) encoded string.
 *
 * @category string
 * @since 3.10.0
 */
export const StringFromBase64Url: StringFromBase64Url = String.annotate({
  expected: "a base64 (URL) encoded string that will be decoded as a UTF-8 string"
}).pipe(
  decodeTo(String, SchemaTransformation.stringFromBase64UrlString)
)

/**
 * Type-level representation of {@link StringFromHex}.
 *
 * @category string
 * @since 3.10.0
 */
export interface StringFromHex extends decodeTo<String, String> {
  readonly "Rebuild": StringFromHex
}

/**
 * Decodes a hex encoded string into a UTF-8 string.
 *
 * **Details**
 *
 * Decoding:
 * - A **valid** hex encoded string is decoded as a UTF-8 `string`.
 *
 * Encoding:
 * - A `string` is encoded as a hex string.
 *
 * @category string
 * @since 3.10.0
 */
export const StringFromHex: StringFromHex = String.annotate({
  expected: "a hex encoded string that will be decoded as a UTF-8 string"
}).pipe(
  decodeTo(String, SchemaTransformation.stringFromHexString)
)

/**
 * Type-level representation of {@link StringFromUriComponent}.
 *
 * @category string
 * @since 3.12.0
 */
export interface StringFromUriComponent extends decodeTo<String, String> {
  readonly "Rebuild": StringFromUriComponent
}

/**
 * Decodes a URI component encoded string into a UTF-8 string.
 * Can be used to store data in a URL.
 *
 * **Details**
 *
 * Decoding:
 * - A **valid** URI component encoded string is decoded as a UTF-8 `string`.
 *
 * Encoding:
 * - A `string` is encoded as a URI component encoded string.
 *
 * **Example** (Decoding URI component strings)
 *
 * ```ts
 * import { Schema } from "effect"
 *
 * const PaginationSchema = Schema.Struct({
 *   maxItemPerPage: Schema.Number,
 *   page: Schema.Number
 * })
 *
 * const UrlSchema = Schema.StringFromUriComponent.pipe(
 *   Schema.decodeTo(Schema.fromJsonString(PaginationSchema))
 * )
 *
 * console.log(Schema.encodeSync(UrlSchema)({ maxItemPerPage: 10, page: 1 }))
 * // %7B%22maxItemPerPage%22%3A10%2C%22page%22%3A1%7D
 * ```
 *
 * @category string
 * @since 3.12.0
 */
export const StringFromUriComponent: StringFromUriComponent = String.annotate({
  expected: "a URI component encoded string that will be decoded as a UTF-8 string"
}).pipe(
  decodeTo(String, SchemaTransformation.stringFromUriComponent)
)

/**
 * Schema for property keys accepted by Effect schemas: finite `number`,
 * `symbol`, or `string`.
 *
 * @category PropertyKey
 * @since 4.0.0
 */
export const PropertyKey = Union([Finite, Symbol, String])

/**
 * Schema for a Standard Schema v1 failure result.
 *
 * **Details**
 *
 * The result contains an `issues` array where each issue has a message and an
 * optional path made of property keys or keyed path segments.
 *
 * @category Standard Schema
 * @since 4.0.0
 */
export const StandardSchemaV1FailureResult = Struct({
  issues: ArraySchema(Struct({
    message: String,
    path: optional(ArraySchema(Union([PropertyKey, Struct({ key: PropertyKey })])))
  }))
})

/**
 * Type-level representation of {@link BooleanFromBit}.
 *
 * @category boolean
 * @since 4.0.0
 */
export interface BooleanFromBit extends decodeTo<Boolean, Literals<readonly [0, 1]>> {
  readonly "Rebuild": BooleanFromBit
}

/**
 * Schema for a boolean parsed from 0 or 1.
 *
 * **When to use**
 *
 * Use when decoding data sources that represent booleans as `0 | 1` while
 * keeping boolean values in the decoded model.
 *
 * **Details**
 *
 * Decoding accepts only `0 | 1`, maps `1` to `true`, and maps `0` to `false`.
 * Encoding maps `true` to `1` and `false` to `0`.
 *
 * @see {@link Boolean} for validating values that are already booleans
 * @see {@link Literals} for keeping bit literals instead of decoding them
 *
 * @category boolean
 * @since 4.0.0
 */
export const BooleanFromBit: BooleanFromBit = Literals([0, 1]).pipe(
  decodeTo(
    Boolean,
    SchemaTransformation.transform({
      decode: (bit) => bit === 1,
      encode: (bool) => bool ? 1 : 0
    })
  )
)

/**
 * Type-level representation of {@link Uint8Array}.
 *
 * @category Uint8Array
 * @since 4.0.0
 */
export interface Uint8Array extends instanceOf<globalThis.Uint8Array<ArrayBufferLike>> {
  readonly "Rebuild": Uint8Array
}

const Base64String = String.annotate({
  expected: "a base64 encoded string that will be decoded as Uint8Array",
  format: "byte",
  contentEncoding: "base64"
})

/**
 * Schema for JavaScript `Uint8Array` objects.
 *
 * **Details**
 *
 * Default JSON serializer:
 *
 * The default JSON serializer encodes Uint8Array as a Base64 encoded string.
 *
 * @category Uint8Array
 * @since 4.0.0
 */
export const Uint8Array: Uint8Array = instanceOf(globalThis.Uint8Array<ArrayBufferLike>, {
  typeConstructor: {
    _tag: "Uint8Array"
  },
  generation: {
    runtime: `Schema.Uint8Array`,
    Type: `globalThis.Uint8Array`
  },
  expected: "Uint8Array",
  toCodecJson: () =>
    link<globalThis.Uint8Array<ArrayBufferLike>>()(
      Base64String,
      SchemaTransformation.uint8ArrayFromBase64String
    ),
  toArbitrary: () => (fc) => fc.uint8Array()
})

/**
 * Type-level representation of {@link Uint8ArrayFromBase64}.
 *
 * @category Uint8Array
 * @since 3.10.0
 */
export interface Uint8ArrayFromBase64 extends decodeTo<Uint8Array, String> {
  readonly "Rebuild": Uint8ArrayFromBase64
}

/**
 * Schema that decodes a base64 encoded string into a
 * `Uint8Array`.
 *
 * **Details**
 *
 * Decoding:
 * - A **valid** base64 encoded string is decoded as a `Uint8Array`.
 *
 * Encoding:
 * - A `Uint8Array` is encoded as a base64-encoded string.
 *
 * @category Uint8Array
 * @since 3.10.0
 */
export const Uint8ArrayFromBase64: Uint8ArrayFromBase64 = Base64String.pipe(
  decodeTo(Uint8Array, SchemaTransformation.uint8ArrayFromBase64String)
)

/**
 * Type-level representation of {@link Uint8ArrayFromBase64Url}.
 *
 * @category Uint8Array
 * @since 3.10.0
 */
export interface Uint8ArrayFromBase64Url extends decodeTo<Uint8Array, String> {
  readonly "Rebuild": Uint8ArrayFromBase64Url
}

/**
 * Schema that decodes a base64 (URL) encoded string into a
 * `Uint8Array`.
 *
 * **Details**
 *
 * Decoding:
 * - A **valid** base64 (URL) encoded string is decoded as a `Uint8Array`.
 *
 * Encoding:
 * - A `Uint8Array` is encoded as a base64 (URL) encoded string.
 *
 * @category Uint8Array
 * @since 3.10.0
 */
export const Uint8ArrayFromBase64Url: Uint8ArrayFromBase64Url = String.annotate({
  expected: "a base64 (URL) encoded string that will be decoded as a Uint8Array"
}).pipe(
  decodeTo(Uint8Array, {
    decode: SchemaGetter.decodeBase64Url(),
    encode: SchemaGetter.encodeBase64Url()
  })
)

/**
 * Type-level representation of {@link Uint8ArrayFromHex}.
 *
 * @category Uint8Array
 * @since 3.10.0
 */
export interface Uint8ArrayFromHex extends decodeTo<Uint8Array, String> {
  readonly "Rebuild": Uint8ArrayFromHex
}

/**
 * Schema that decodes a hex encoded string into a
 * `Uint8Array`.
 *
 * **Details**
 *
 * Decoding:
 * - A **valid** hex encoded string is decoded as a `Uint8Array`.
 *
 * Encoding:
 * - A `Uint8Array` is encoded as a hex encoded string.
 *
 * @category Uint8Array
 * @since 3.10.0
 */
export const Uint8ArrayFromHex: Uint8ArrayFromHex = String.annotate({
  expected: "a hex encoded string that will be decoded as a Uint8Array"
}).pipe(
  decodeTo(Uint8Array, {
    decode: SchemaGetter.decodeHex(),
    encode: SchemaGetter.encodeHex()
  })
)

/**
 * Type-level representation of {@link DateTimeUtc}.
 *
 * @category DateTime
 * @since 3.10.0
 */
export interface DateTimeUtc extends declare<DateTime.Utc> {
  readonly "Rebuild": DateTimeUtc
}

/**
 * Schema for `DateTime.Utc` values.
 *
 * **When to use**
 *
 * Use to validate existing `DateTime.Utc` schema values and use the default JSON
 * codec that represents them as UTC ISO strings.
 *
 * **Details**
 *
 * The default JSON codec decodes UTC ISO strings into `DateTime.Utc` values and
 * encodes `DateTime.Utc` values as UTC ISO strings.
 *
 * @see {@link DateTimeUtcFromString} for decoding date-time strings into UTC values
 * @see {@link DateTimeUtcFromDate} for decoding JavaScript Date values into UTC values
 * @see {@link DateTimeUtcFromMillis} for decoding epoch milliseconds into UTC values
 * @see {@link DateTimeZoned} for preserving zoned DateTime values
 *
 * @category DateTime
 * @since 3.10.0
 */
export const DateTimeUtc: DateTimeUtc = declare(
  (u) => DateTime.isDateTime(u) && DateTime.isUtc(u),
  {
    typeConstructor: {
      _tag: "effect/DateTime.Utc"
    },
    generation: {
      runtime: `Schema.DateTimeUtc`,
      Type: `DateTime.Utc`,
      importDeclaration: `import * as DateTime from "effect/DateTime"`
    },
    expected: "DateTime.Utc",
    toCodecJson: () =>
      link<DateTime.Utc>()(
        String,
        SchemaTransformation.dateTimeUtcFromString
      ),
    toArbitrary: () => (fc, ctx) =>
      fc.date(dateArbitraryConstraints(
        ctx?.constraint,
        ctx?.constraint?.ordered?.order === DateTime.Order ? ctx.constraint.ordered : undefined,
        { valid: true },
        DateTime.toDateUtc
      ))
        .map((date) => DateTime.fromDateUnsafe(date)),
    toFormatter: () => (utc) => utc.toString(),
    toEquivalence: () => DateTime.Equivalence
  }
)

/**
 * Type-level representation of {@link DateTimeUtcFromDate}.
 *
 * @category DateTime
 * @since 3.12.0
 */
export interface DateTimeUtcFromDate extends decodeTo<DateTimeUtc, Date> {
  readonly "Rebuild": DateTimeUtcFromDate
}

/**
 * Schema that decodes a `Date` into a `DateTime.Utc`.
 *
 * **When to use**
 *
 * Use when you need to decode valid JavaScript `Date` objects into
 * `DateTime.Utc` values.
 *
 * **Details**
 *
 * Decoding:
 * - A **valid** `Date` is decoded as a `DateTime.Utc`
 *
 * Encoding:
 * - A `DateTime.Utc` is encoded as a `Date`
 *
 * @see {@link DateTimeUtc} for validating values that are already `DateTime.Utc`
 * @see {@link DateTimeUtcFromString} for decoding date-time strings into UTC values
 * @see {@link DateTimeUtcFromMillis} for decoding epoch milliseconds into UTC values
 * @see {@link DateValid} for validating Date instances without converting them
 *
 * @category DateTime
 * @since 3.12.0
 */
export const DateTimeUtcFromDate: DateTimeUtcFromDate = DateValid.pipe(
  decodeTo(DateTimeUtc, {
    decode: SchemaGetter.dateTimeUtcFromInput(),
    encode: SchemaGetter.transform(DateTime.toDateUtc)
  })
)

/**
 * Type-level representation of {@link DateTimeUtcFromString}.
 *
 * @category DateTime
 * @since 4.0.0
 */
export interface DateTimeUtcFromString extends decodeTo<DateTimeUtc, String> {
  readonly "Rebuild": DateTimeUtcFromString
}

/**
 * Schema that decodes a date-time string into a `DateTime.Utc`.
 *
 * **Details**
 *
 * Decoding:
 *
 * - A string accepted by `DateTime.make` is parsed and normalized to UTC. Strings
 *   without an explicit zone are interpreted as UTC.
 *
 * Encoding:
 *
 * - A `DateTime.Utc` is encoded as a UTC ISO 8601 string.
 *
 * @see {@link DateTimeUtcFromDate} for decoding JavaScript Date values into UTC values
 * @see {@link DateTimeUtcFromMillis} for decoding epoch milliseconds into UTC values
 * @see {@link DateFromString} for decoding strings into JavaScript Date instances
 *
 * @category DateTime
 * @since 4.0.0
 */
export const DateTimeUtcFromString: DateTimeUtcFromString = String.annotate({
  expected: "a string that will be decoded as a DateTime.Utc"
}).pipe(
  decodeTo(
    DateTimeUtc,
    SchemaTransformation.dateTimeUtcFromString
  )
)

/**
 * Type-level representation of {@link DateTimeUtcFromMillis}.
 *
 * @category DateTime
 * @since 4.0.0
 */
export interface DateTimeUtcFromMillis extends decodeTo<instanceOf<DateTime.Utc>, Number> {
  readonly "Rebuild": DateTimeUtcFromMillis
}

/**
 * Schema that decodes a number into a `DateTime.Utc`.
 *
 * **Details**
 *
 * Decoding:
 * - A number of milliseconds since the Unix epoch is decoded as a `DateTime.Utc`
 *
 * Encoding:
 * - A `DateTime.Utc` is encoded as a number of milliseconds since the Unix epoch.
 *
 * @see {@link DateTimeUtcFromDate} for decoding JavaScript Date values into UTC values
 * @see {@link DateTimeUtcFromString} for decoding date-time strings into UTC values
 * @see {@link DateFromMillis} for decoding epoch milliseconds into JavaScript Date instances
 *
 * @category DateTime
 * @since 4.0.0
 */
export const DateTimeUtcFromMillis: DateTimeUtcFromMillis = Number.pipe(
  decodeTo(DateTimeUtc, {
    decode: SchemaGetter.dateTimeUtcFromInput(),
    encode: SchemaGetter.transform(DateTime.toEpochMillis)
  })
)

/**
 * Type-level representation of {@link TimeZoneOffset}.
 *
 * @category DateTime
 * @since 3.10.0
 */
export interface TimeZoneOffset extends declare<DateTime.TimeZone.Offset> {
  readonly "Rebuild": TimeZoneOffset
}

/**
 * Schema for `DateTime.TimeZone.Offset` values.
 *
 * **Details**
 *
 * Default JSON serializer:
 *
 * - encodes `DateTime.TimeZone.Offset` as a number (offset in milliseconds)
 *
 * @category DateTime
 * @since 3.10.0
 */
export const TimeZoneOffset: TimeZoneOffset = declare(
  DateTime.isTimeZoneOffset,
  {
    typeConstructor: {
      _tag: "effect/DateTime.TimeZone.Offset"
    },
    generation: {
      runtime: `Schema.TimeZoneOffset`,
      Type: `DateTime.TimeZone.Offset`,
      importDeclaration: `import * as DateTime from "effect/DateTime"`
    },
    expected: "DateTime.TimeZone.Offset",
    toCodecJson: () =>
      link<DateTime.TimeZone.Offset>()(
        Number,
        SchemaTransformation.timeZoneOffsetFromNumber
      ),
    toArbitrary: () => (fc) =>
      fc.integer({ min: -12 * 60 * 60 * 1000, max: 14 * 60 * 60 * 1000 }).map((n) => DateTime.zoneMakeOffset(n)),
    toFormatter: () => (tz) => DateTime.zoneToString(tz),
    toEquivalence: () => (a, b) => a.offset === b.offset
  }
)

/**
 * Type-level representation of {@link TimeZoneNamed}.
 *
 * @category DateTime
 * @since 3.10.0
 */
export interface TimeZoneNamed extends declare<DateTime.TimeZone.Named> {
  readonly "Rebuild": TimeZoneNamed
}

const TimeZoneNamedString = String.annotate({ expected: "an IANA time zone identifier" })

/**
 * Schema for `DateTime.TimeZone.Named` values.
 *
 * **Details**
 *
 * Default JSON serializer:
 *
 * - encodes `DateTime.TimeZone.Named` as a string (IANA time zone identifier)
 *
 * @category DateTime
 * @since 3.10.0
 */
export const TimeZoneNamed: TimeZoneNamed = declare(
  DateTime.isTimeZoneNamed,
  {
    typeConstructor: {
      _tag: "effect/DateTime.TimeZone.Named"
    },
    generation: {
      runtime: `Schema.TimeZoneNamed`,
      Type: `DateTime.TimeZone.Named`,
      importDeclaration: `import * as DateTime from "effect/DateTime"`
    },
    expected: "DateTime.TimeZone.Named",
    toCodecJson: () =>
      link<DateTime.TimeZone.Named>()(
        TimeZoneNamedString,
        SchemaTransformation.timeZoneNamedFromString
      ),
    toArbitrary: () => (fc) =>
      fc.constantFrom(
        ...["UTC", "Europe/London", "America/New_York", "Asia/Tokyo", "Australia/Sydney"].map(
          DateTime.zoneMakeNamedUnsafe
        )
      ),
    toFormatter: () => (tz) => DateTime.zoneToString(tz),
    toEquivalence: () => (a, b) => a.id === b.id
  }
)

/**
 * Type-level representation of {@link TimeZoneNamedFromString}.
 *
 * @category DateTime
 * @since 4.0.0
 */
export interface TimeZoneNamedFromString extends decodeTo<TimeZoneNamed, String> {
  readonly "Rebuild": TimeZoneNamedFromString
}

/**
 * Schema that parses an IANA time zone identifier string into a `DateTime.TimeZone.Named`.
 *
 * **Details**
 *
 * Decoding:
 * - A `string` is decoded as a `DateTime.TimeZone.Named`.
 *
 * Encoding:
 * - A `DateTime.TimeZone.Named` is encoded as a `string`.
 *
 * @category DateTime
 * @since 4.0.0
 */
export const TimeZoneNamedFromString: TimeZoneNamedFromString = TimeZoneNamedString.pipe(
  decodeTo(TimeZoneNamed, SchemaTransformation.timeZoneNamedFromString)
)

/**
 * Type-level representation of {@link TimeZone}.
 *
 * @category DateTime
 * @since 3.10.0
 */
export interface TimeZone extends declare<DateTime.TimeZone> {
  readonly "Rebuild": TimeZone
}

const TimeZoneString = String.annotate({
  expected: "a time zone string (IANA identifier or offset like +03:00)"
})

/**
 * Schema for `DateTime.TimeZone` values.
 *
 * **Details**
 *
 * Default JSON serializer:
 *
 * - encodes `DateTime.TimeZone` as a string (IANA identifier or offset like
 *   `+03:00`)
 *
 * @category DateTime
 * @since 3.10.0
 */
export const TimeZone: TimeZone = declare(
  DateTime.isTimeZone,
  {
    typeConstructor: {
      _tag: "effect/DateTime.TimeZone"
    },
    generation: {
      runtime: `Schema.TimeZone`,
      Type: `DateTime.TimeZone`,
      importDeclaration: `import * as DateTime from "effect/DateTime"`
    },
    expected: "DateTime.TimeZone",
    toCodecJson: () =>
      link<DateTime.TimeZone>()(
        TimeZoneString,
        SchemaTransformation.timeZoneFromString
      ),
    toArbitrary: () => (fc) =>
      fc.oneof(
        fc.integer({ min: -12 * 60 * 60 * 1000, max: 14 * 60 * 60 * 1000 }).map((n) => DateTime.zoneMakeOffset(n)),
        fc.constantFrom(
          ...["UTC", "Europe/London", "America/New_York", "Asia/Tokyo", "Australia/Sydney"].map(
            DateTime.zoneMakeNamedUnsafe
          )
        )
      ),
    toFormatter: () => (tz) => DateTime.zoneToString(tz),
    toEquivalence: () => (a, b) => DateTime.zoneToString(a) === DateTime.zoneToString(b)
  }
)

/**
 * Type-level representation of {@link TimeZoneFromString}.
 *
 * @category DateTime
 * @since 4.0.0
 */
export interface TimeZoneFromString extends decodeTo<TimeZone, String> {
  readonly "Rebuild": TimeZoneFromString
}

/**
 * Schema that parses a time zone string into a `DateTime.TimeZone`.
 *
 * **Details**
 *
 * Decoding:
 * - A `string` (IANA identifier or offset like `+03:00`) is decoded as a `DateTime.TimeZone`.
 *
 * Encoding:
 * - A `DateTime.TimeZone` is encoded as a `string`.
 *
 * @category DateTime
 * @since 4.0.0
 */
export const TimeZoneFromString: TimeZoneFromString = TimeZoneString.pipe(
  decodeTo(TimeZone, SchemaTransformation.timeZoneFromString)
)

/**
 * Type-level representation of {@link DateTimeZoned}.
 *
 * @category DateTime
 * @since 3.10.0
 */
export interface DateTimeZoned extends declare<DateTime.Zoned> {
  readonly "Rebuild": DateTimeZoned
}

const DateTimeZonedString = String.annotate({
  expected: "a zoned DateTime string (e.g. 2024-01-01T00:00:00.000+00:00[Europe/London])"
})

/**
 * Schema for `DateTime.Zoned` values.
 *
 * **Details**
 *
 * Default JSON serializer:
 *
 * - encodes offset zones as an ISO date-time with a numeric offset, such as
 *   `YYYY-MM-DDTHH:mm:ss.sss+HH:MM`
 * - encodes named zones by appending the IANA identifier in brackets, such as
 *   `YYYY-MM-DDTHH:mm:ss.sss+HH:MM[Time/Zone]`
 *
 * @category DateTime
 * @since 3.10.0
 */
export const DateTimeZoned: DateTimeZoned = declare(
  (u) => DateTime.isDateTime(u) && DateTime.isZoned(u),
  {
    typeConstructor: {
      _tag: "effect/DateTime.Zoned"
    },
    generation: {
      runtime: `Schema.DateTimeZoned`,
      Type: `DateTime.Zoned`,
      importDeclaration: `import * as DateTime from "effect/DateTime"`
    },
    expected: "DateTime.Zoned",
    toCodecJson: () =>
      link<DateTime.Zoned>()(
        DateTimeZonedString,
        SchemaTransformation.dateTimeZonedFromString
      ),
    toArbitrary: () => (fc, ctx) =>
      fc.tuple(
        fc.date(dateArbitraryConstraints(
          ctx?.constraint,
          ctx?.constraint?.ordered?.order === DateTime.Order ? ctx.constraint.ordered : undefined,
          {
            max: new globalThis.Date(8640000000000000 - 14 * 60 * 60 * 1000),
            min: new globalThis.Date(-8640000000000000 + 14 * 60 * 60 * 1000),
            valid: true
          },
          DateTime.toDateUtc
        )),
        fc.constantFrom("UTC", "Europe/London", "America/New_York", "Asia/Tokyo", "Australia/Sydney")
      ).map(([date, zone]) => DateTime.makeZonedUnsafe(date, { timeZone: zone })),
    toFormatter: () => (zoned) => DateTime.formatIsoZoned(zoned),
    toEquivalence: () => DateTime.Equivalence
  }
)

/**
 * Type-level representation of {@link DateTimeZonedFromString}.
 *
 * @category DateTime
 * @since 4.0.0
 */
export interface DateTimeZonedFromString extends decodeTo<DateTimeZoned, String> {
  readonly "Rebuild": DateTimeZonedFromString
}

/**
 * Schema that parses a zoned DateTime string into a `DateTime.Zoned`.
 *
 * **Details**
 *
 * Decoding:
 * - A `string` (e.g. `2024-01-01T00:00:00.000+00:00[Europe/London]`) is decoded as a `DateTime.Zoned`.
 *
 * Encoding:
 * - A `DateTime.Zoned` is encoded as a `string`.
 *
 * @category DateTime
 * @since 4.0.0
 */
export const DateTimeZonedFromString: DateTimeZonedFromString = DateTimeZonedString.pipe(
  decodeTo(DateTimeZoned, SchemaTransformation.dateTimeZonedFromString)
)

// -----------------------------------------------------------------------------
// Class
// -----------------------------------------------------------------------------

/**
 * Type-level representation returned by {@link Class}.
 *
 * @category models
 * @since 3.10.0
 */
export interface Class<Self, S extends Constraint & { readonly fields: Struct.Fields }, Inherited> extends
  BottomLazy<
    SchemaAST.Declaration,
    decodeTo<declareConstructor<Self, S["Encoded"], readonly [S], S["Iso"]>, S>,
    readonly [S],
    S["~type.mutability"],
    S["~type.optionality"],
    S["~type.constructor.default"],
    S["~encoded.mutability"],
    S["~encoded.optionality"]
  >
{
  readonly "Type": Self
  readonly "Encoded": S["Encoded"]
  readonly "DecodingServices": S["DecodingServices"]
  readonly "EncodingServices": S["EncodingServices"]
  readonly "~type.make.in": RequiredKeys<S["~type.make.in"]> extends never ? void | S["~type.make.in"]
    : S["~type.make.in"]
  readonly "~type.make": Self
  readonly "Iso": S["Iso"]
  new(
    ...args: {} extends S["~type.make.in"] ? [props?: S["~type.make.in"], options?: MakeOptions]
      : [props: S["~type.make.in"], options?: MakeOptions]
  ): S["Type"] & Inherited
  readonly identifier: string
  readonly fields: S["fields"]

  /**
   * Returns a new struct with the fields modified by the provided function.
   *
   * **Details**
   *
   * Options:
   *
   * - `unsafePreserveChecks` - if `true`, keep any `.check(...)` constraints
   *   that were attached to the original struct. Defaults to `false`.
   *
   *   **Warning**: This is an unsafe operation. Since `mapFields`
   *   transformations change the schema type, the original refinement functions
   *   may no longer be valid or safe to apply to the transformed schema. Only
   *   use this option if you have verified that your refinements remain correct
   *   after the transformation.
   */
  mapFields<To extends Struct.Fields>(
    f: (fields: S["fields"]) => To,
    options?: {
      readonly unsafePreserveChecks?: boolean | undefined
    } | undefined
  ): Struct<Simplify<Readonly<To>>>

  /**
   * Returns a function that creates a schema-backed subclass with this class's
   * fields plus additional fields.
   *
   * **When to use**
   *
   * Use when you need a subclass whose constructor validates both inherited
   * fields and newly added fields.
   *
   * **Details**
   *
   * The returned function accepts either a field map or a `Struct`. When you
   * pass a `Struct`, checks attached to that extension schema are preserved and
   * combined with checks from the base class schema.
   *
   * **Gotchas**
   *
   * Checks from a `Struct` argument are evaluated against the full subclass
   * value after inherited and extension fields are merged. Object-wide checks
   * such as `isMaxProperties` count inherited fields too.
   */
  extend<Extended = never, Static = {}, Brand = {}>(
    identifier: string
  ): {
    <NewFields extends Struct.Fields>(
      fields: NewFields,
      annotations?: Annotations.Declaration<Extended, readonly [Struct<Simplify<Assign<S["fields"], NewFields>>>]>
    ): [Extended] extends [never] ? MissingSelfGeneric<"Base.extend"> : InheritStaticMembers<
      Class<Extended, Struct<Simplify<Assign<S["fields"], NewFields>>>, Self & Brand>,
      Static
    >
    <Extension extends Struct<Struct.Fields>>(
      schema: Extension,
      annotations?: Annotations.Declaration<
        Extended,
        readonly [Struct<Simplify<Assign<S["fields"], Extension["fields"]>>>]
      >
    ): [Extended] extends [never] ? MissingSelfGeneric<"Base.extend"> : InheritStaticMembers<
      Class<Extended, Struct<Simplify<Assign<S["fields"], Extension["fields"]>>>, Self & Brand>,
      Static
    >
  }
}

// Merges custom static members from a parent class onto the extended class,
// giving priority to the extended class's own members (e.g. schema-generated statics).
type InheritStaticMembers<C, Static> = C & Pick<Static, Exclude<keyof Static, keyof C>>

const immerable: unique symbol = globalThis.Symbol.for("immer-draftable") as any

const payloadToken = {}

function makeClass<
  Self,
  S extends Struct<Struct.Fields>,
  Inherited extends new(...args: ReadonlyArray<any>) => any
>(
  Inherited: Inherited,
  identifier: string,
  struct: S,
  annotations: Annotations.Declaration<Self, readonly [S]> | undefined,
  proto: ((identifier: string) => object) | undefined
): any {
  const getClassSchema = getClassSchemaFactory(struct, identifier, annotations)
  const ClassTypeId = getClassTypeId(identifier) // HMR support

  const out = class extends Inherited {
    constructor(...[input, options]: ReadonlyArray<any>) {
      const internalOptions = options as MakeOptions | undefined
      const payload = internalOptions?.["~payload"]
      const value = payload?.token === payloadToken
        ? payload.value
        : struct.make(input ?? {}, options)
      super(value, { ...options, disableChecks: true, "~payload": { token: payloadToken, value } })
    }

    static readonly [TypeId] = TypeId

    get [ClassTypeId]() {
      return ClassTypeId
    }

    static readonly [immerable] = true

    static readonly identifier = identifier
    static readonly fields = struct.fields

    static get ast(): SchemaAST.Declaration {
      return getClassSchema(this).ast
    }
    static pipe() {
      return Pipeable.pipeArguments(this, arguments)
    }
    static rebuild(ast: SchemaAST.Declaration) {
      return getClassSchema(this).rebuild(ast)
    }
    static make(input: S["~type.make.in"], options?: MakeOptions): Self {
      return new this(input, options)
    }
    static makeOption(input: S["~type.make.in"], options?: MakeOptions): Option_.Option<Self> {
      return SchemaParser.makeOption(getClassSchema(this) as any)(input ?? {}, options) as any
    }
    static makeEffect(input: S["~type.make.in"], options?: MakeOptions): Effect.Effect<Self, SchemaError> {
      return (getClassSchema(this) as any).makeEffect(input ?? {}, options)
    }
    static annotate(annotations: Annotations.Declaration<Self, readonly [S]>) {
      return this.rebuild(SchemaAST.annotate(this.ast, annotations))
    }
    static annotateKey(annotations: Annotations.Key<Self>) {
      return this.rebuild(SchemaAST.annotateKey(this.ast, annotations))
    }
    static check(...checks: readonly [SchemaAST.Check<Self>, ...Array<SchemaAST.Check<Self>>]) {
      return this.rebuild(SchemaAST.appendChecks(this.ast, checks))
    }
    static extend(
      identifier: string
    ) {
      return (
        schema: Struct.Fields | Struct<Struct.Fields>,
        annotations?: Annotations.Declaration<any, readonly [any]>
      ) => {
        const extension = isStruct(schema) ? schema : Struct(schema)
        const fields = { ...struct.fields, ...extension.fields }
        const ast = SchemaAST.struct(fields, struct.ast.checks, { identifier })
        return makeClass(
          this,
          identifier,
          makeStruct(SchemaAST.appendChecks(ast, extension.ast.checks), fields),
          annotations,
          proto
        )
      }
    }
    static mapFields<To extends Struct.Fields>(
      f: (fields: S["fields"]) => To,
      options?: {
        readonly unsafePreserveChecks?: boolean | undefined
      } | undefined
    ): Struct<Simplify<Readonly<To>>> {
      return struct.mapFields(f, options)
    }
  }

  if (proto !== undefined) {
    Object.assign(out.prototype, proto(identifier))
  }

  return out
}

function getClassTransformation(self: new(...args: ReadonlyArray<any>) => any) {
  return new SchemaTransformation.Transformation<any, any, never, never>(
    SchemaGetter.transform((input) => new self(input)),
    SchemaGetter.passthrough()
  )
}

function getClassTypeId(identifier: string) {
  return `~effect/Schema/Class/${identifier}`
}

function getClassSchemaFactory<S extends Constraint>(
  from: S,
  identifier: string,
  annotations: Annotations.Declaration<any, readonly [S]> | undefined
) {
  let memo: decodeTo<declareConstructor<any, S["Encoded"], readonly [S]>, S> | undefined
  return <Self extends (new(...args: ReadonlyArray<any>) => any) & { readonly identifier: string }>(
    self: Self
  ): decodeTo<declareConstructor<Self, S["Encoded"], readonly [S]>, S> => {
    if (memo !== undefined) {
      return memo
    }
    const transformation = getClassTransformation(self)
    const to = make<declareConstructor<Self, S["Encoded"], readonly [S]>>(
      new SchemaAST.Declaration(
        [from.ast],
        () => (input, ast) => {
          return input instanceof self ||
              Predicate.hasProperty(input, getClassTypeId(identifier)) ?
            Effect.succeed(input) :
            Effect.fail(new SchemaIssue.InvalidType(ast, Option_.some(input)))
        },
        {
          identifier,
          [SchemaAST.ClassTypeId]: ([from]: readonly [SchemaAST.AST]) => new SchemaAST.Link(from, transformation),
          toCodec: ([from]: readonly [ConstraintCodec<S["Encoded"], S["Encoded"]>]) =>
            new SchemaAST.Link(from.ast, transformation),
          toArbitrary: ([from]: readonly [Annotations.ToArbitrary.TypeParameter<S["Type"]>]) => () => ({
            arbitrary: from.arbitrary.map((args: S["Type"]) => new self(args)),
            terminal: from.terminal?.map((args: S["Type"]) => new self(args))
          }),
          toFormatter: ([from]: readonly [Formatter<S["Type"]>]) => (t: Self) => `${self.identifier}(${from(t)})`,
          "~sentinels": SchemaAST.collectSentinels(from.ast),
          ...annotations
        }
      )
    )
    return memo = decodeTo<declareConstructor<Self, S["Encoded"], readonly [S]>, S>(to, transformation)(from)
  }
}

function isStruct(schema: Struct.Fields | Struct<Struct.Fields>): schema is Struct<Struct.Fields> {
  return isSchema(schema)
}

type MissingSelfGeneric<Usage extends string> =
  `Missing \`Self\` generic - use \`class Self extends ${Usage}<Self>(...)\``

/**
 * Creates a schema-backed class whose constructor validates input against a
 * {@link Struct} schema. Construction throws a {@link SchemaError} on invalid
 * input.
 *
 * **When to use**
 *
 * Use when you need a schema-backed data class with validated construction,
 * schema-derived decoding/encoding, and class-style methods or inheritance.
 *
 * **Details**
 *
 * Pass the desired class type as the first type parameter. The second optional
 * type parameter can be used to add nominal brands.
 *
 * **Gotchas**
 *
 * Passing `disableChecks` in the options skips constructor validation.
 *
 * **Example** (Defining a basic class)
 *
 * ```ts
 * import { Schema } from "effect"
 *
 * class Person extends Schema.Class<Person>("Person")({
 *   name: Schema.String,
 *   age: Schema.Number
 * }) {}
 *
 * const alice = new Person({ name: "Alice", age: 30 })
 * console.log(alice.name) // "Alice"
 * console.log(`${alice}`) // "Person({ name: Alice, age: 30 })"
 * ```
 *
 * **Example** (Extending a class)
 *
 * ```ts
 * import { Schema } from "effect"
 *
 * class Animal extends Schema.Class<Animal>("Animal")({
 *   name: Schema.String
 * }) {}
 *
 * class Dog extends Animal.extend<Dog>("Dog")({
 *   breed: Schema.String
 * }) {}
 *
 * const dog = new Dog({ name: "Rex", breed: "Labrador" })
 * console.log(dog.name) // "Rex"
 * console.log(dog.breed) // "Labrador"
 * ```
 *
 * @see {@link TaggedClass} for adding a `_tag` literal field to the class schema
 * @see {@link ErrorClass} for defining schema-backed error classes
 * @see {@link TaggedErrorClass} for defining tagged schema-backed error classes
 *
 * @category constructors
 * @since 3.10.0
 */
export const Class: {
  <Self = never, Brand = {}>(identifier: string): {
    <const Fields extends Struct.Fields>(
      fields: Fields,
      annotations?: Annotations.Declaration<Self, readonly [Struct<Fields>]>
    ): [Self] extends [never] ? MissingSelfGeneric<"Schema.Class"> : Class<Self, Struct<Fields>, Brand>
    <S extends Struct<Struct.Fields>>(
      schema: S,
      annotations?: Annotations.Declaration<Self, readonly [S]>
    ): [Self] extends [never] ? MissingSelfGeneric<"Schema.Class"> : Class<Self, S, Brand>
  }
} = <Self, Brand = {}>(identifier: string) =>
(
  schema: Struct.Fields | Struct<Struct.Fields>,
  annotations?: Annotations.Declaration<Self, readonly [Struct<Struct.Fields>]>
): [Self] extends [never] ? MissingSelfGeneric<"Schema.Class"> : Class<Self, Struct<Struct.Fields>, Brand> => {
  const struct = isStruct(schema) ? schema : Struct(schema)
  return makeClass(
    Data.Class,
    identifier,
    struct,
    annotations,
    (identifier) => ({
      toString() {
        return `${identifier}(${format({ ...this })})`
      }
    })
  )
}

/**
 * Defines a schema-backed class with an automatically populated `_tag` field.
 *
 * **When to use**
 *
 * Use to define class instances that are validated by a schema and participate
 * in tagged union matching.
 *
 * **Details**
 *
 * The optional `identifier` parameter overrides the schema identifier;
 * it defaults to the `tag` value.
 *
 * **Example** (Defining a tagged class)
 *
 * ```ts
 * import { Schema } from "effect"
 *
 * class Circle extends Schema.TaggedClass<Circle>()("Circle", {
 *   radius: Schema.Number
 * }) {}
 *
 * const c = new Circle({ radius: 5 })
 * console.log(c._tag) // "Circle"
 * console.log(c.radius) // 5
 * ```
 *
 * @category constructors
 * @since 3.10.0
 */
export const TaggedClass: {
  <Self = never, Brand = {}>(identifier?: string): {
    <Tag extends string, const Fields extends Struct.Fields>(
      tag: Tag,
      fields: Fields,
      annotations?: Annotations.Declaration<Self, readonly [TaggedStruct<Tag, Fields>]>
    ): [Self] extends [never] ? MissingSelfGeneric<"Schema.TaggedClass"> : Class<Self, TaggedStruct<Tag, Fields>, Brand>
    <Tag extends string, S extends Struct<Struct.Fields>>(
      tag: Tag,
      schema: S,
      annotations?: Annotations.Declaration<
        Self,
        readonly [Struct<Simplify<{ readonly _tag: tag<Tag> } & S["fields"]>>]
      >
    ): [Self] extends [never] ? MissingSelfGeneric<"Schema.TaggedClass">
      : Class<Self, Struct<Simplify<{ readonly _tag: tag<Tag> } & S["fields"]>>, Brand>
  }
} = (identifier?: string) => {
  return (
    tagValue: string,
    schema: Struct.Fields | Struct<Struct.Fields>,
    annotations?: Annotations.Declaration<any, readonly [Struct<Struct.Fields>]>
  ): any => {
    const struct = isStruct(schema) ?
      schema.mapFields((fields) => ({ _tag: tag(tagValue), ...fields }), {
        unsafePreserveChecks: true
      }) :
      TaggedStruct(tagValue, schema)
    return Class<any, {}>(identifier ?? tagValue)(
      struct,
      annotations as Annotations.Declaration<any, readonly [typeof struct]>
    )
  }
}

/**
 * Creates a schema-backed error class that can be used as a typed,
 * yieldable error in Effect programs. Combines {@link Class} validation with
 * the `YieldableError` interface so instances can be yielded directly inside
 * `Effect.gen`.
 *
 * **Example** (Schema-backed error)
 *
 * ```ts
 * import { Effect, Schema } from "effect"
 *
 * class NotFound extends Schema.ErrorClass<NotFound>("NotFound")({
 *   id: Schema.Number
 * }) {}
 *
 * const program = Effect.gen(function*() {
 *   yield* new NotFound({ id: 1 })
 * })
 * ```
 *
 * @category constructors
 * @since 4.0.0
 */
export const ErrorClass: {
  <Self = never, Brand = {}>(identifier: string): {
    <const Fields extends Struct.Fields>(
      fields: Fields,
      annotations?: Annotations.Declaration<Self, readonly [Struct<Fields>]>
    ): [Self] extends [never] ? MissingSelfGeneric<"Schema.ErrorClass">
      : Class<Self, Struct<Fields>, Cause_.YieldableError & Brand>
    <S extends Struct<Struct.Fields>>(
      schema: S,
      annotations?: Annotations.Declaration<Self, readonly [S]>
    ): [Self] extends [never] ? MissingSelfGeneric<"Schema.ErrorClass"> : Class<Self, S, Cause_.YieldableError & Brand>
  }
} = <Self, Brand = {}>(identifier: string) =>
(
  schema: Struct.Fields | Struct<Struct.Fields>,
  annotations?: Annotations.Declaration<Self, readonly [Struct<Struct.Fields>]>
): [Self] extends [never] ? MissingSelfGeneric<"Schema.ErrorClass">
  : Class<Self, Struct<Struct.Fields>, Cause_.YieldableError & Brand> =>
{
  const struct = isStruct(schema) ? schema : Struct(schema)
  const self = makeClass(
    core.Error,
    identifier,
    struct,
    annotations,
    (identifier) => ({
      name: identifier
    })
  )
  return self
}

/**
 * Defines a schema-backed yieldable error class with an automatically populated
 * `_tag` field.
 *
 * **When to use**
 *
 * Use to define typed errors that are schema validated, yielded in `Effect.gen`,
 * and matched as tagged union members.
 *
 * **Example** (Defining a tagged error class)
 *
 * ```ts
 * import { Effect, Schema } from "effect"
 *
 * class NotFound extends Schema.TaggedErrorClass<NotFound>()("NotFound", {
 *   id: Schema.Number
 * }) {}
 *
 * const program = Effect.gen(function*() {
 *   yield* new NotFound({ id: 42 })
 * })
 * ```
 *
 * @category constructors
 * @since 3.10.0
 */
export const TaggedErrorClass: {
  <Self = never, Brand = {}>(identifier?: string): {
    <Tag extends string, const Fields extends Struct.Fields>(
      tag: Tag,
      fields: Fields,
      annotations?: Annotations.Declaration<Self, readonly [TaggedStruct<Tag, Fields>]>
    ): [Self] extends [never] ? MissingSelfGeneric<"Schema.TaggedErrorClass">
      : Class<Self, TaggedStruct<Tag, Fields>, Cause_.YieldableError & Brand>
    <Tag extends string, S extends Struct<Struct.Fields>>(
      tag: Tag,
      schema: S,
      annotations?: Annotations.Declaration<
        Self,
        readonly [Struct<Simplify<{ readonly _tag: tag<Tag> } & S["fields"]>>]
      >
    ): [Self] extends [never] ? MissingSelfGeneric<"Schema.TaggedErrorClass">
      : Class<Self, Struct<Simplify<{ readonly _tag: tag<Tag> } & S["fields"]>>, Cause_.YieldableError & Brand>
  }
} = (identifier?: string) => {
  return (
    tagValue: string,
    schema: Struct.Fields | Struct<Struct.Fields>,
    annotations?: Annotations.Declaration<any, readonly [Struct<Struct.Fields>]>
  ): any => {
    const struct = isStruct(schema) ?
      schema.mapFields((fields) => ({ _tag: tag(tagValue), ...fields }), {
        unsafePreserveChecks: true
      }) :
      TaggedStruct(tagValue, schema)
    return ErrorClass<any, {}>(identifier ?? tagValue)(
      struct,
      annotations as Annotations.Declaration<any, readonly [typeof struct]>
    )
  }
}

// -----------------------------------------------------------------------------
// Arbitrary
// -----------------------------------------------------------------------------

/**
 * A thunk that, given the `fast-check` module, returns an `Arbitrary<T>`.
 * Use this type when you need to defer instantiation of the arbitrary, for
 * example to support recursive schemas.
 *
 * @category Arbitrary
 * @since 4.0.0
 */
export type LazyArbitrary<T> = (fc: typeof FastCheck) => FastCheck.Arbitrary<T>

/**
 * Derives a {@link LazyArbitrary} from a schema. The result is memoized so
 * repeated calls with the same schema are cheap.
 *
 * **Details**
 *
 * Prefer {@link toArbitrary} when you need the arbitrary directly, or when you
 * want derivation diagnostics via `{ report: true }`. Unsupported schema
 * nodes, impossible constraints, invalid candidates, and recursive schemas
 * without a finite terminal path fail immediately.
 *
 * @category Arbitrary
 * @since 4.0.0
 */
export function toArbitraryLazy<S extends Constraint>(schema: S): LazyArbitrary<S["Type"]> {
  const lawc = InternalArbitrary.memoized(schema.ast)
  return (fc) => lawc(fc, {})
}

/**
 * Derives a `fast-check` `Arbitrary` from a schema for property-based
 * testing. The derived arbitrary generates values that satisfy the schema.
 *
 * **Details**
 *
 * Constraints refine base generators; candidates add weighted sources while
 * filters still validate every value. `{ report: true }` returns warnings such
 * as `OpaqueFilter`, while derivation errors remain fail-fast. Recursive
 * schemas use terminal branches and fail when no finite terminal path exists.
 *
 * **Example** (Generating arbitrary values)
 *
 * ```ts
 * import { Schema } from "effect"
 * import * as FastCheck from "fast-check"
 *
 * const PersonArb = Schema.toArbitrary(
 *   Schema.Struct({ name: Schema.String, age: Schema.Number })
 * )
 *
 * // Sample a random value
 * const sample = FastCheck.sample(PersonArb, 1)[0]
 * console.log(typeof sample.name) // "string"
 * ```
 *
 * @category Arbitrary
 * @since 4.0.0
 */
export function toArbitrary<S extends Constraint>(schema: S): FastCheck.Arbitrary<S["Type"]>
export function toArbitrary<S extends Constraint>(
  schema: S,
  options: { readonly report: true }
): Annotations.ToArbitrary.WithReport<FastCheck.Arbitrary<S["Type"]>>
export function toArbitrary<S extends Constraint>(
  schema: S,
  options?: { readonly report?: boolean }
): FastCheck.Arbitrary<S["Type"]> | Annotations.ToArbitrary.WithReport<FastCheck.Arbitrary<S["Type"]>> {
  if (options?.report === true) {
    const lawc = InternalArbitrary.memoized(schema.ast)
    const report = InternalArbitrary.makeReport()
    InternalArbitrary.collectReport(schema.ast, report)
    return {
      value: lawc(FastCheck, {}),
      report: InternalArbitrary.toReport(report)
    }
  }
  return toArbitraryLazy(schema)(FastCheck)
}

// -----------------------------------------------------------------------------
// Formatter
// -----------------------------------------------------------------------------

/**
 * Attaches a custom formatter used by `toFormatter`.
 *
 * **Details**
 *
 * Use this when the formatter derived from the schema structure is not suitable.
 * The annotation is applied through this helper because adding it directly to
 * `Annotations.Bottom` would make schemas invariant.
 *
 * @category Formatter
 * @since 4.0.0
 */
export function overrideToFormatter<S extends Top>(toFormatter: () => Formatter<S["Type"]>) {
  return (self: S): S["Rebuild"] => {
    return self.annotate({ toFormatter })
  }
}

/**
 * Derives a string formatter function from a schema. The formatter converts
 * a value to its human-readable string representation, recursing into structs,
 * arrays, and unions.
 *
 * **Details**
 *
 * The optional `onBefore` hook lets you intercept specific AST nodes before
 * the default formatting logic runs.
 *
 * @category Formatter
 * @since 4.0.0
 */
export function toFormatter<S extends Constraint>(schema: S, options?: {
  readonly onBefore?:
    | ((ast: SchemaAST.AST, recur: (ast: SchemaAST.AST) => Formatter<any>) => Formatter<any> | undefined)
    | undefined
}): Formatter<S["Type"]> {
  return recur(schema.ast)

  function recur(ast: SchemaAST.AST): Formatter<S["Type"]> {
    // ---------------------------------------------
    // handle annotation
    // ---------------------------------------------
    const annotation = InternalAnnotations.resolve(ast)?.["toFormatter"]
    if (typeof annotation === "function") {
      return annotation(SchemaAST.isDeclaration(ast) ? ast.typeParameters.map(recur) : [])
    }
    // ---------------------------------------------
    // handle onBefore
    // ---------------------------------------------
    if (options?.onBefore) {
      const onBefore = options.onBefore(ast, recur)
      if (onBefore !== undefined) {
        return onBefore
      }
    }
    // ---------------------------------------------
    // handle base case
    // ---------------------------------------------
    return on(ast)
  }

  function on(ast: SchemaAST.AST): Formatter<any> {
    switch (ast._tag) {
      default:
        return format
      case "Never":
        return () => "never"
      case "Void":
        return () => "void"
      case "Arrays": {
        const elements = ast.elements.map(recur)
        const rest = ast.rest.map(recur)
        return (t) => {
          const out: Array<string> = []
          let i = 0
          // ---------------------------------------------
          // handle elements
          // ---------------------------------------------
          for (; i < elements.length; i++) {
            if (t.length < i + 1) {
              if (SchemaAST.isOptional(ast.elements[i])) {
                continue
              }
            } else {
              out.push(elements[i](t[i]))
            }
          }
          // ---------------------------------------------
          // handle rest element
          // ---------------------------------------------
          if (rest.length > 0) {
            const [head, ...tail] = rest
            for (; i < t.length - tail.length; i++) {
              out.push(head(t[i]))
            }
            // ---------------------------------------------
            // handle post rest elements
            // ---------------------------------------------
            for (let j = 0; j < tail.length; j++) {
              out.push(tail[j](t[i + j]))
            }
          }

          return "[" + out.join(", ") + "]"
        }
      }
      case "Objects": {
        const propertySignatures = ast.propertySignatures.map((ps) => recur(ps.type))
        const indexSignatures = ast.indexSignatures.map((is) => recur(is.type))
        if (ast.propertySignatures.length === 0 && ast.indexSignatures.length === 0) {
          return format
        }
        return (t) => {
          const out: Array<string> = []
          const visited = new Set<PropertyKey>()
          // ---------------------------------------------
          // handle property signatures
          // ---------------------------------------------
          for (let i = 0; i < propertySignatures.length; i++) {
            const ps = ast.propertySignatures[i]
            const name = ps.name
            visited.add(name)
            if (SchemaAST.isOptional(ps.type) && !Object.hasOwn(t, name)) {
              continue
            }
            out.push(`${formatPropertyKey(name)}: ${propertySignatures[i](t[name])}`)
          }
          // ---------------------------------------------
          // handle index signatures
          // ---------------------------------------------
          for (let i = 0; i < indexSignatures.length; i++) {
            const keys = SchemaAST.getIndexSignatureKeys(t, ast.indexSignatures[i].parameter)
            for (const key of keys) {
              if (visited.has(key)) {
                continue
              }
              visited.add(key)
              out.push(`${formatPropertyKey(key)}: ${indexSignatures[i](t[key])}`)
            }
          }

          return out.length > 0 ? "{ " + out.join(", ") + " }" : "{}"
        }
      }
      case "Union": {
        const getCandidates = (t: any) => SchemaAST.getCandidates(t, ast.types)
        return (t) => {
          const candidates = getCandidates(t)
          const refinements = candidates.map(SchemaParser._is)
          for (let i = 0; i < candidates.length; i++) {
            const is = refinements[i]
            if (is(t)) {
              return recur(candidates[i])(t)
            }
          }
          return format(t)
        }
      }
      case "Suspend": {
        const get = SchemaAST.memoizeThunk(() => recur(ast.thunk()))
        return (t) => get()(t)
      }
    }
  }
}

// -----------------------------------------------------------------------------
// Equivalence
// -----------------------------------------------------------------------------

/**
 * Overrides the equivalence derivation for a schema by supplying a custom
 * `Equivalence`.
 *
 * **When to use**
 *
 * Use when you need a custom equivalence instead of the default structural
 * equivalence derived by {@link toEquivalence}.
 *
 * @category instances
 * @since 4.0.0
 */
export function overrideToEquivalence<S extends Top>(toEquivalence: () => Equivalence.Equivalence<S["Type"]>) {
  return (self: S): S["Rebuild"] => self.annotate({ toEquivalence })
}

/**
 * Derives an `Equivalence` from a schema. Two values are considered equal when
 * every field (and nested field) compares equal according to the schema
 * structure.
 *
 * **Example** (Comparing structs)
 *
 * ```ts
 * import { Schema } from "effect"
 *
 * const eq = Schema.toEquivalence(Schema.Struct({ id: Schema.Number, name: Schema.String }))
 *
 * console.log(eq({ id: 1, name: "Alice" }, { id: 1, name: "Alice" })) // true
 * console.log(eq({ id: 1, name: "Alice" }, { id: 2, name: "Alice" })) // false
 * ```
 *
 * @category instances
 * @since 4.0.0
 */
export function toEquivalence<T>(schema: Schema<T>): Equivalence.Equivalence<T> {
  return InternalEquivalence.toEquivalence(schema.ast)
}

// -----------------------------------------------------------------------------
// Representation
// -----------------------------------------------------------------------------

/**
 * Derives an intermediate `SchemaRepresentation.Document` from a schema. This
 * document is used internally by {@link toJsonSchemaDocument} and related
 * functions to produce JSON Schema output.
 *
 * @category Representation
 * @since 4.0.0
 */
export function toRepresentation(schema: Constraint): SchemaRepresentation.Document {
  return InternalStandard.fromAST(schema.ast)
}

// -----------------------------------------------------------------------------
// JsonSchema
// -----------------------------------------------------------------------------

/**
 * Options for {@link toJsonSchemaDocument}.
 *
 * @category options
 * @since 4.0.0
 */
export interface ToJsonSchemaOptions {
  /**
   * Controls how additional properties are handled while resolving the JSON
   * schema.
   *
   * **Details**
   *
   * Possible values include:
   * - `false`: Disallow additional properties (default)
   * - `true`: Allow additional properties
   * - `JsonSchema`: Use the provided JSON Schema for additional properties
   */
  readonly additionalProperties?: boolean | JsonSchema.JsonSchema | undefined
  /**
   * Controls whether to generate descriptions for checks (if the user has not
   * provided them) based on the `expected` annotation of the check.
   */
  readonly generateDescriptions?: boolean | undefined
  /**
   * A predicate that controls which additional annotation keys (beyond the
   * standard JSON Schema keys) are included in the generated output.
   *
   * **When to use**
   *
   * Use when you need to include non-standard annotation keys in the generated
   * JSON Schema, such as Monaco Editor properties (`markdownDescription`,
   * `defaultSnippets`) or vendor extensions (`x-*`).
   *
   * **Details**
   *
   * Standard JSON Schema keys (`title`, `description`, `default`, `examples`,
   * `readOnly`, `writeOnly`, `format`, `contentEncoding`, `contentMediaType`,
   * `contentSchema`) are always included. This predicate is checked for any
   * *other* annotation key.
   *
   * **Gotchas**
   *
   * Prefer whitelisting the custom annotation keys you want to emit instead of
   * using a broad predicate such as `() => true`, because broad predicates can
   * include Effect-specific annotations that are preserved for internal schema
   * generation.
   *
   * **Example** (Including custom annotations)
   *
   * ```ts
   * import { Schema } from "effect"
   *
   * const schema = Schema.String.annotate({
   *   description: "A name",
   *   markdownDescription: "The **name** field"
   * })
   *
   * const doc = Schema.toJsonSchemaDocument(schema, {
   *   includeAnnotationKey: (key) =>
   *     key === "markdownDescription" || key.startsWith("x-")
   * })
   *
   * console.log(doc.schema)
   * // {
   * //   type: "string",
   * //   description: "A name",
   * //   markdownDescription: "The **name** field"
   * // }
   * ```
   */
  readonly includeAnnotationKey?: ((key: string) => boolean) | undefined
}

/**
 * Returns a JSON Schema document using draft 2020-12.
 *
 * **Details**
 *
 * The `options` parameter controls generation details such as additional
 * properties and synthesized check descriptions; it does not change the draft
 * target.
 *
 * **Gotchas**
 *
 * JSON Schema generation is best-effort. Some Effect schema semantics cannot
 * be represented exactly in JSON Schema, and importing an emitted JSON Schema
 * may produce an equivalent approximation rather than the original schema
 * shape.
 *
 * @category converting
 * @since 4.0.0
 */
export function toJsonSchemaDocument(
  schema: Constraint,
  options?: ToJsonSchemaOptions
): JsonSchema.Document<"draft-2020-12"> {
  const sd = toRepresentation(schema)
  const jd = InternalStandard.toJsonSchemaDocument(sd, options)
  return {
    dialect: "draft-2020-12",
    schema: jd.schema,
    definitions: jd.definitions
  }
}

// -----------------------------------------------------------------------------
// Canonical Codecs
// -----------------------------------------------------------------------------

/**
 * Type-level representation returned by {@link toCodecJson}.
 *
 * @category Canonical Codecs
 * @since 4.0.0
 */
export interface toCodecJson<S extends Constraint> extends
  BottomLazy<
    S["ast"],
    toCodecJson<S>,
    S["~type.parameters"],
    S["~type.mutability"],
    S["~type.optionality"],
    S["~type.constructor.default"],
    S["~encoded.mutability"],
    S["~encoded.optionality"]
  >
{
  readonly "Type": S["Type"]
  readonly "Encoded": Json
  readonly "DecodingServices": S["DecodingServices"]
  readonly "EncodingServices": S["EncodingServices"]
  readonly "~type.make.in": S["~type.make.in"]
  readonly "~type.make": S["~type.make"]
  readonly "Iso": S["Iso"]
  readonly schema: S
}

/**
 * Derives a canonical JSON codec from a schema. The encoded form is `Json`, and
 * decoding produces the schema's `Type`.
 *
 * @category Canonical Codecs
 * @since 4.0.0
 */
export function toCodecJson<S extends Constraint>(schema: S): toCodecJson<S> {
  return make(toCodecJsonTop(schema.ast), { schema })
}

const toCodecJsonTop = SchemaAST.applyToSelfOrLastLinkEncoding((ast) => {
  const out = toCodecJsonBase(ast, toCodecJsonTop)
  return out !== ast && SchemaAST.isOptional(ast) ? SchemaAST.optionalKeyLastLink(out) : out
})

function toCodecJsonBase(ast: SchemaAST.AST, recur: (ast: SchemaAST.AST) => SchemaAST.AST): SchemaAST.AST {
  switch (ast._tag) {
    case "Declaration": {
      const getLink = ast.annotations?.toCodecJson ?? ast.annotations?.toCodec
      if (Predicate.isFunction(getLink)) {
        const tps = SchemaAST.isDeclaration(ast)
          ? ast.typeParameters.map((tp) => InternalSchema.make(SchemaAST.toEncoded(tp)))
          : []
        const link = getLink(tps)
        const to = recur(link.to)
        return SchemaAST.replaceEncoding(ast, to === link.to ? [link] : [new SchemaAST.Link(to, link.transformation)])
      }
      return SchemaAST.replaceEncoding(ast, [SchemaAST.unknownToNull])
    }
    case "Unknown":
    case "ObjectKeyword":
      return SchemaAST.replaceEncoding(ast, [SchemaAST.unknownToJson])
    case "Undefined":
    case "Void":
    case "Literal":
    case "Number":
      return ast.toCodecJson()
    case "UniqueSymbol":
    case "Symbol":
    case "BigInt":
      return ast.toCodecStringTree()
    case "Objects": {
      if (ast.propertySignatures.some((ps) => typeof ps.name !== "string")) {
        throw new globalThis.Error("Objects property names must be strings", { cause: ast })
      }
      return ast.recur(recur, SchemaAST.parameterFromString)
    }
    case "Union": {
      const sortedTypes = InternalSchema.jsonReorder(ast.types)
      if (sortedTypes !== ast.types) {
        return new SchemaAST.Union(
          sortedTypes,
          ast.mode,
          ast.annotations,
          ast.checks,
          ast.encoding,
          ast.context,
          ast.encodingChecks
        ).recur(recur)
      }
      return ast.recur(recur)
    }
    case "Arrays":
    case "Suspend":
      return ast.recur(recur)
  }
  // `Schema.Any` is used as an escape hatch
  return ast
}

/**
 * Derives an isomorphism codec from a schema. The encoded form is the
 * schema's `Iso` type — the intermediate representation used for round-tripping.
 *
 * @category Canonical Codecs
 * @since 4.0.0
 */
export function toCodecIso<S extends Constraint>(schema: S): Codec<S["Type"], S["Iso"]> {
  return make(toCodecIsoTop(SchemaAST.toType(schema.ast)))
}

const toCodecIsoTop = memoize((ast: SchemaAST.AST): SchemaAST.AST => {
  const out = toCodecIsoBase(ast, toCodecIsoTop)
  return out !== ast && SchemaAST.isOptional(ast) ? SchemaAST.optionalKeyLastLink(out) : out
})

function toCodecIsoBase(ast: SchemaAST.AST, recur: (ast: SchemaAST.AST) => SchemaAST.AST): SchemaAST.AST {
  switch (ast._tag) {
    case "Declaration": {
      const getLink = ast.annotations?.toCodecIso ?? ast.annotations?.toCodec
      if (Predicate.isFunction(getLink)) {
        const link = getLink(ast.typeParameters.map((tp) => InternalSchema.make(tp)))
        const to = recur(link.to)
        return SchemaAST.replaceEncoding(ast, to === link.to ? [link] : [new SchemaAST.Link(to, link.transformation)])
      }
      return ast
    }
    case "Arrays":
    case "Objects":
    case "Union":
    case "Suspend":
      return ast.recur(recur)
  }
  return ast
}

/**
 * A {@link Tree} of `string | undefined` nodes. Leaf values are either a
 * string representation or `undefined` for opaque/declaration types.
 *
 * @category Canonical Codecs
 * @since 4.0.0
 */
export type StringTree = Tree<string | undefined>

/**
 * Type-level representation returned by {@link toCodecStringTree}.
 *
 * @category Canonical Codecs
 * @since 4.0.0
 */
export interface toCodecStringTree<S extends Constraint> extends
  BottomLazy<
    S["ast"],
    toCodecStringTree<S>,
    ReadonlyArray<Constraint>,
    S["~type.mutability"],
    S["~type.optionality"],
    S["~type.constructor.default"],
    S["~encoded.mutability"],
    S["~encoded.optionality"]
  >
{
  readonly "Type": S["Type"]
  readonly "Encoded": StringTree
  readonly "DecodingServices": S["DecodingServices"]
  readonly "EncodingServices": S["EncodingServices"]
  readonly "~type.make.in": S["~type.make.in"]
  readonly "~type.make": S["~type.make"]
  readonly "Iso": S["Iso"]
  readonly schema: S
}

/**
 * Converts a schema to the StringTree canonical codec, where every leaf value
 * becomes a string while preserving the original structure.
 *
 * **Details**
 *
 * Declarations are converted to `undefined` (unless they have a
 * `toCodecJson` or `toCodec` annotation).
 *
 * @category Canonical Codecs
 * @since 4.0.0
 */
export function toCodecStringTree<S extends Constraint>(schema: S): toCodecStringTree<S> {
  return make(serializerStringTree(schema.ast), { schema })
}

/**
 * Type-level representation returned by {@link toCodecArrayFromSingle}.
 *
 * @category Canonical Codecs
 * @since 4.0.0
 */
export interface toCodecArrayFromSingle<S extends Constraint> extends
  BottomLazy<
    S["ast"],
    toCodecArrayFromSingle<S>,
    S["~type.parameters"],
    S["~type.mutability"],
    S["~type.optionality"],
    S["~type.constructor.default"],
    S["~encoded.mutability"],
    S["~encoded.optionality"]
  >
{
  readonly "Type": S["Type"]
  readonly "Encoded": S["Encoded"]
  readonly "DecodingServices": S["DecodingServices"]
  readonly "EncodingServices": S["EncodingServices"]
  readonly "~type.make.in": S["~type.make.in"]
  readonly "~type.make": S["~type.make"]
  readonly "Iso": S["Iso"]
}

/**
 * Allows array schemas to decode from either an array input or a single value
 * input.
 *
 * **When to use**
 *
 * Use when you need to accept transport formats that may represent a
 * single-item array as a bare value, such as query-string or form-data adapters.
 *
 * **Gotchas**
 *
 * This combinator is intentionally not part of `toCodecStringTree`; it adds a
 * decoding convenience rather than a canonical StringTree representation. It
 * does not parse comma-separated strings.
 *
 * @category Canonical Codecs
 * @since 4.0.0
 */
export function toCodecArrayFromSingle<S extends Constraint>(schema: S): toCodecArrayFromSingle<S> {
  return make(toCodecArrayFromSingleTop(schema.ast))
}

type XmlEncoderOptions = {
  /** Root element name for the returned XML string. Default: "root" */
  readonly rootName?: string | undefined
  /** When an array doesn't have a natural item name, use this. Default: "item" */
  readonly arrayItemName?: string | undefined
  /** Pretty-print output. Default: true */
  readonly pretty?: boolean | undefined
  /** Indentation used when pretty-printing. Default: "  " (two spaces) */
  readonly indent?: string | undefined
  /** Sort object keys for stable output. Default: true */
  readonly sortKeys?: boolean | undefined
}

/**
 * Derives an XML encoder from a codec.
 *
 * **Details**
 *
 * The returned function encodes a value through `toCodecStringTree` and returns
 * an `Effect` that succeeds with the XML string or fails with `SchemaError` if
 * codec encoding fails.
 *
 * @category Canonical Codecs
 * @since 4.0.0
 */
export function toEncoderXml<T, RE>(
  codec: ConstraintCodec<T, unknown, unknown, RE>,
  options?: XmlEncoderOptions
) {
  const rootName = InternalAnnotations.resolveIdentifier(codec.ast) ?? InternalAnnotations.resolveTitle(codec.ast)
  const serialize = encodeEffect(toCodecStringTree(codec))
  return (t: T): Effect.Effect<string, SchemaError, RE> =>
    serialize(t).pipe(Effect.map((stringTree) => stringTreeToXml(stringTree, { rootName, ...options })))
}

function stringTreeToXml(value: StringTree, options: XmlEncoderOptions): string {
  const rootName = options.rootName ?? "root"
  const arrayItemName = options.arrayItemName ?? "item"
  const pretty = options.pretty ?? true
  const indent = options.indent ?? "  "
  const sortKeys = options.sortKeys ?? true

  const seen = new Set<object>()
  const lines: Array<string> = []

  recur(rootName, value, 0)
  return lines.join(pretty ? "\n" : "")

  function push(depth: number, text: string): void {
    lines.push(pretty ? indent.repeat(depth) + text : text)
  }

  function recur(tagName: string, node: StringTree, depth: number, originalNameForMeta?: string): void {
    const { attrs, safe } = xml.tagInfo(tagName, originalNameForMeta)

    if (node === undefined) {
      push(depth, `<${safe}${attrs}/>`)
    } else if (typeof node === "string") {
      push(depth, `<${safe}${attrs}>${xml.escapeText(node)}</${safe}>`)
    } else if (typeof node !== "object" || node === null) {
      push(depth, `<${safe}${attrs}>${xml.escapeText(format(node))}</${safe}>`)
    } else {
      if (seen.has(node)) throw new globalThis.Error("Cycle detected while serializing to XML.", { cause: node })
      seen.add(node)
      try {
        if (globalThis.globalThis.Array.isArray(node)) {
          if (node.length === 0) {
            push(depth, `<${safe}${attrs}/>`)
            return
          }
          push(depth, `<${safe}${attrs}>`)
          for (const item of node) recur(arrayItemName, item, depth + 1)
          push(depth, `</${safe}>`)
          return
        }

        const obj = node as Record<string, StringTree>
        const keys = Object.keys(obj)
        if (sortKeys) keys.sort()

        if (keys.length === 0) {
          push(depth, `<${safe}${attrs}/>`)
          return
        }

        push(depth, `<${safe}${attrs}>`)
        for (const k of keys) {
          recur(xml.parseTagName(k).safe, obj[k], depth + 1, k)
        }
        push(depth, `</${safe}>`)
      } finally {
        seen.delete(node)
      }
    }
  }
}

const xml = {
  escapeText(s: string): string {
    return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;")
  },
  escapeAttribute(s: string): string {
    return s.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;")
  },
  parseTagName(name: string): { safe: string; changed: boolean } {
    const original = name
    let safe = name
    if (!/^[A-Za-z_]/.test(safe)) safe = "_" + safe
    safe = safe.replace(/[^A-Za-z0-9._-]/g, "_")
    if (/^xml/i.test(safe)) safe = "_" + safe
    return { safe, changed: safe !== original }
  },
  tagInfo(name: string, original?: string): { safe: string; attrs: string } {
    const { changed, safe } = xml.parseTagName(name)
    const needsMeta = changed || (original && original !== name)
    const attrs = needsMeta ? ` data-name="${xml.escapeAttribute(original ?? name)}"` : ""
    return { safe, attrs }
  }
}

function getStringTreePriority(ast: SchemaAST.AST): number {
  switch (ast._tag) {
    case "Null":
    case "Boolean":
    case "Number":
    case "BigInt":
    case "Symbol":
    case "UniqueSymbol":
      return 0
    default:
      return 1
  }
}

const treeReorder = InternalSchema.makeReorder(getStringTreePriority)

function serializerTree(
  ast: SchemaAST.AST,
  recur: (ast: SchemaAST.AST) => SchemaAST.AST,
  onMissingAnnotation: (ast: SchemaAST.AST) => SchemaAST.AST
): SchemaAST.AST {
  switch (ast._tag) {
    case "Declaration": {
      const getLink = ast.annotations?.toCodecJson ?? ast.annotations?.toCodec
      if (Predicate.isFunction(getLink)) {
        const tps = SchemaAST.isDeclaration(ast)
          ? ast.typeParameters.map((tp) => make(recur(SchemaAST.toEncoded(tp))))
          : []
        const link = getLink(tps)
        const to = recur(link.to)
        return SchemaAST.replaceEncoding(ast, to === link.to ? [link] : [new SchemaAST.Link(to, link.transformation)])
      }
      return onMissingAnnotation(ast)
    }
    case "Null":
      return SchemaAST.replaceEncoding(ast, [nullToString])
    case "Boolean":
      return SchemaAST.replaceEncoding(ast, [booleanToString])
    case "Unknown":
    case "ObjectKeyword":
      return SchemaAST.replaceEncoding(ast, [SchemaAST.unknownToStringTree])
    case "Enum":
    case "Number":
    case "Literal":
    case "UniqueSymbol":
    case "Symbol":
    case "BigInt":
      return ast.toCodecStringTree()
    case "Objects": {
      if (ast.propertySignatures.some((ps) => typeof ps.name !== "string")) {
        throw new globalThis.Error("Objects property names must be strings", { cause: ast })
      }
      return ast.recur(recur, SchemaAST.parameterFromString)
    }
    case "Union": {
      const sortedTypes = treeReorder(ast.types)
      if (sortedTypes !== ast.types) {
        return new SchemaAST.Union(
          sortedTypes,
          ast.mode,
          ast.annotations,
          ast.checks,
          ast.encoding,
          ast.context,
          ast.encodingChecks
        ).recur(recur)
      }
      return ast.recur(recur)
    }
    case "Arrays":
    case "Suspend":
      return ast.recur(recur)
  }
  // `Schema.Any` is used as an escape hatch
  return ast
}

const nullToString = new SchemaAST.Link(
  new SchemaAST.Literal("null"),
  new SchemaTransformation.Transformation(
    SchemaGetter.transform(() => null),
    SchemaGetter.transform(() => "null")
  )
)

const booleanToString = new SchemaAST.Link(
  new SchemaAST.Union([new SchemaAST.Literal("true"), new SchemaAST.Literal("false")], "anyOf"),
  new SchemaTransformation.Transformation(
    SchemaGetter.transform((s) => s === "true"),
    SchemaGetter.String()
  )
)

const SERIALIZER_ENSURE_ARRAY = "~effect/Schema/SERIALIZER_ENSURE_ARRAY"

const isSerializerArrayFromSingle = (ast: SchemaAST.AST): boolean =>
  SchemaAST.isUnion(ast) && ast.annotations?.[SERIALIZER_ENSURE_ARRAY] === true

const serializerStringTree = SchemaAST.applyToSelfOrLastLinkEncoding((ast) => {
  if (isSerializerArrayFromSingle(ast)) {
    return ast
  }
  const out = serializerTree(ast, serializerStringTree, (ast) => SchemaAST.replaceEncoding(ast, [unknownToUndefined]))
  if (out !== ast && SchemaAST.isOptional(ast)) {
    return SchemaAST.optionalKeyLastLink(out)
  }
  return out
})

const unknownToUndefined = new SchemaAST.Link(
  SchemaAST.undefined,
  new SchemaTransformation.Transformation(
    SchemaGetter.passthrough(),
    SchemaGetter.transform(() => undefined)
  )
)

const toArrayFromSingleInputElement = (ast: SchemaAST.AST): SchemaAST.AST =>
  SchemaAST.isOptional(ast) ? SchemaAST.optionalKey(SchemaAST.unknown) : SchemaAST.unknown

const arrayFromSingleTransformation = new SchemaTransformation.Transformation(
  SchemaGetter.transform((input: ReadonlyArray<unknown> | string) => typeof input === "string" ? [input] : input),
  SchemaGetter.passthrough()
)

const toCodecArrayFromSingleTop = SchemaAST.applyToSelfOrLastLinkEncoding((ast) => {
  if (isSerializerArrayFromSingle(ast)) {
    return ast
  }
  const out = onSerializerArrayFromSingle(ast)
  if (SchemaAST.isArrays(out)) {
    const ensure = SchemaAST.decodeTo(
      new SchemaAST.Union(
        [
          new SchemaAST.Arrays(
            out.isMutable,
            out.elements.map(toArrayFromSingleInputElement),
            out.rest.map(toArrayFromSingleInputElement)
          ),
          SchemaAST.string
        ],
        "anyOf",
        { [SERIALIZER_ENSURE_ARRAY]: true }
      ),
      out,
      arrayFromSingleTransformation
    )
    return SchemaAST.isOptional(ast) ? SchemaAST.optionalKey(ensure) : ensure
  }
  return out
})

function onSerializerArrayFromSingle(ast: SchemaAST.AST): SchemaAST.AST {
  return ast._tag === "Declaration" || ast._tag === "Arrays" || ast._tag === "Objects" || ast._tag === "Union" ||
      ast._tag === "Suspend"
    ? ast.recur(toCodecArrayFromSingleTop)
    : ast
}

// -----------------------------------------------------------------------------
// Optic APIs
// -----------------------------------------------------------------------------

/**
 * Derives an `Iso` optic from a schema that isomorphically converts between
 * the schema's `Type` and its `Iso` (intermediate / serialized form).
 *
 * @category Optic
 * @since 4.0.0
 */
export function toIso<S extends Constraint>(schema: S): Optic_.Iso<S["Type"], S["Iso"]> {
  const serializer = toCodecIso(schema)
  return Optic_.makeIso(SchemaParser.encodeSync(serializer), SchemaParser.decodeSync(serializer))
}

/**
 * Returns an identity `Iso` over the schema's source (`Type`) side.
 *
 * @category Optic
 * @since 4.0.0
 */
export function toIsoSource<S extends Constraint>(_: S): Optic_.Iso<S["Type"], S["Type"]> {
  return Optic_.id()
}

/**
 * Returns an identity `Iso` over the schema's focus (`Iso`) side.
 *
 * @category Optic
 * @since 4.0.0
 */
export function toIsoFocus<S extends Constraint>(_: S): Optic_.Iso<S["Iso"], S["Iso"]> {
  return Optic_.id()
}

/**
 * Type-level representation returned by {@link overrideToCodecIso}.
 *
 * @category Optic
 * @since 4.0.0
 */
export interface overrideToCodecIso<S extends Constraint, Iso> extends
  BottomLazy<
    S["ast"],
    overrideToCodecIso<S, Iso>,
    S["~type.parameters"],
    S["~type.mutability"],
    S["~type.optionality"],
    S["~type.constructor.default"],
    S["~encoded.mutability"],
    S["~encoded.optionality"]
  >
{
  readonly "Type": S["Type"]
  readonly "Encoded": S["Encoded"]
  readonly "DecodingServices": S["DecodingServices"]
  readonly "EncodingServices": S["EncodingServices"]
  readonly "~type.make.in": S["~type.make.in"]
  readonly "~type.make": S["~type.make"]
  readonly "Iso": Iso
  readonly schema: S
}

/**
 * Overrides a schema's derived ISO codec with an explicit target codec.
 *
 * **When to use**
 *
 * Use to provide a custom ISO transformation when the default derivation is not
 * appropriate.
 *
 * **Details**
 *
 * The resulting schema carries a custom `Iso` type parameter and uses the
 * provided `decode` and `encode` getters to transform between the schema type
 * and the target codec.
 *
 * @category Optic
 * @since 4.0.0
 */
export function overrideToCodecIso<S extends Constraint, Iso>(
  to: ConstraintCodec<Iso>,
  transformation: {
    readonly decode: SchemaGetter.Getter<S["Type"], Iso>
    readonly encode: SchemaGetter.Getter<Iso, S["Type"]>
  }
) {
  return (schema: S): overrideToCodecIso<S, Iso> => {
    return make(
      SchemaAST.annotate(schema.ast, {
        toCodecIso: () => new SchemaAST.Link(to.ast, SchemaTransformation.make(transformation))
      }),
      { schema }
    )
  }
}

// -----------------------------------------------------------------------------
// Differ APIs
// -----------------------------------------------------------------------------

/**
 * Derives a JSON Patch differ from a codec. Serializes values to JSON (via
 * {@link toCodecJson}), computes RFC 6902 JSON Patch operations between old
 * and new values, and can apply patches back to the typed value.
 *
 * @category converting
 * @since 4.0.0
 */
export function toDifferJsonPatch<T>(schema: ConstraintCodec<T, unknown>): Differ<T, JsonPatch.JsonPatch> {
  const serializer = toCodecJson(schema)
  const get = SchemaParser.encodeSync(serializer)
  const set = SchemaParser.decodeSync(serializer)
  return {
    empty: [],
    diff: (oldValue, newValue) => JsonPatch.get(get(oldValue), get(newValue)),
    combine: (first, second) => [...first, ...second],
    patch: (oldValue, patch) => {
      const value = get(oldValue)
      const patched = JsonPatch.apply(patch, value)
      return Object.is(patched, value) ? oldValue : set(patched)
    }
  }
}

/**
 * Recursive tree type whose leaves are `Node` values and whose branches are
 * readonly arrays or string-keyed records of child trees.
 *
 * @category Tree
 * @since 4.0.0
 */
export type Tree<Node> = Node | TreeRecord<Node> | ReadonlyArray<Tree<Node>>

/**
 * A record node in a {@link Tree}: an object mapping string keys to child
 * `Tree` nodes.
 *
 * @category Tree
 * @since 4.0.0
 */
export interface TreeRecord<A> {
  readonly [x: string]: Tree<A>
}

/**
 * Creates a recursive schema for a {@link Tree} of values described by `node`.
 * The resulting schema accepts a single node value, an array of trees, or an
 * object whose values are trees.
 *
 * @category Tree
 * @since 4.0.0
 */
export function Tree<S extends Constraint>(node: S) {
  const Tree$ref = suspend((): Codec<
    Tree<S["Type"]>,
    Tree<S["Encoded"]>,
    S["DecodingServices"],
    S["EncodingServices"]
  > => Tree)
  const Tree = Union([
    node,
    ArraySchema(Tree$ref),
    Record(String, Tree$ref)
  ])
  return Tree
}

/**
 * Recursive TypeScript type for any valid immutable JSON value: `null`,
 * `number`, `boolean`, `string`, a readonly array of `Json` values, or a
 * readonly record of `string → Json`. For the corresponding schema, see the
 * {@link Json} const.
 *
 * @category models
 * @since 4.0.0
 */
export type Json = null | number | boolean | string | JsonArray | JsonObject

/**
 * A readonly array of {@link Json} values.
 *
 * @category models
 * @since 4.0.0
 */
export interface JsonArray extends ReadonlyArray<Json> {}

/**
 * A readonly record whose values are {@link Json} values.
 *
 * @category models
 * @since 4.0.0
 */
export interface JsonObject {
  readonly [x: string]: Json
}

/**
 * Schema that accepts and validates any immutable JSON-compatible value.
 *
 * **Example** (Validating a JSON value)
 *
 * ```ts
 * import { Schema } from "effect"
 *
 * const result = Schema.decodeUnknownOption(Schema.Json)({ key: [1, true, null] })
 * console.log(result._tag) // "Some"
 * ```
 *
 * @category schemas
 * @since 4.0.0
 */
export const Json: Codec<Json> = make(SchemaAST.Json)

const JsonError = Struct({
  message: String,
  name: optionalKey(String),
  stack: optionalKey(String),
  cause: optionalKey(Json)
})

/**
 * Recursive TypeScript type for mutable JSON values: `null`, `number`,
 * `boolean`, `string`, mutable arrays, or mutable string-keyed records.
 *
 * @category models
 * @since 4.0.0
 */
export type MutableJson = null | number | boolean | string | MutableJsonArray | MutableJsonObject

/**
 * A mutable array of {@link MutableJson} values.
 *
 * @category models
 * @since 4.0.0
 */
export interface MutableJsonArray extends Array<MutableJson> {}

/**
 * A mutable record whose values are {@link MutableJson} values.
 *
 * @category models
 * @since 4.0.0
 */
export interface MutableJsonObject {
  [x: string]: MutableJson
}

/**
 * Schema that accepts any mutable JSON-compatible value. See {@link Json} for
 * the immutable variant.
 *
 * @category schemas
 * @since 4.0.0
 */
export const MutableJson: Codec<MutableJson> = make(SchemaAST.MutableJson)

// -----------------------------------------------------------------------------
// Annotations
// -----------------------------------------------------------------------------

/**
 * Resolves the typed annotations from a schema. The term "resolve" (rather
 * than "get") reflects the lookup strategy: if the schema has checks, the
 * annotations are taken from the last check; otherwise they are taken from
 * the base schema instance.
 *
 * @category Schema Resolvers
 * @since 4.0.0
 */
export function resolveAnnotations<S extends Constraint>(
  schema: S
): Annotations.Bottom<S["Type"], S["~type.parameters"]> | undefined {
  return InternalAnnotations.resolve(schema.ast)
}

/**
 * Resolves the context (key-level) annotations from a schema. Context
 * annotations are those attached via `annotateKey` and live on the AST's
 * `context` rather than on the schema node itself.
 *
 * @category Schema Resolvers
 * @since 4.0.0
 */
export function resolveAnnotationsKey<S extends Constraint>(schema: S): Annotations.Key<S["Type"]> | undefined {
  return schema.ast.context?.annotations
}

/**
 * The `Annotations` namespace groups all annotation interfaces used to attach
 * metadata to schemas. Annotations control documentation, validation messages,
 * JSON Schema generation, equivalence, arbitrary generation, and more.
 *
 * **Details**
 *
 * Use {@link resolveAnnotations} to read the annotations attached to a schema at
 * runtime.
 *
 * @since 4.0.0
 */
export declare namespace Annotations {
  /**
   * This interface is used to define the annotations that can be attached to a
   * schema. You can extend this interface to define your own annotations.
   *
   * **Details**
   *
   * Note that both a missing key or `undefined` is used to indicate that the
   * annotation is not present.
   *
   * This means that can remove any annotation by setting it to `undefined`.
   *
   * **Example** (Defining your own annotations)
   *
   * ```ts
   * import { Schema } from "effect"
   *
   * // Extend the Annotations interface with a custom `version` annotation
   * declare module "effect/Schema" {
   *   namespace Annotations {
   *     interface Annotations {
   *       readonly version?:
   *         | readonly [major: number, minor: number, patch: number]
   *         | undefined
   *     }
   *   }
   * }
   *
   * // The `version` annotation is now recognized by the TypeScript compiler
   * const schema = Schema.String.annotate({ version: [1, 2, 0] })
   *
   * // const version: readonly [major: number, minor: number, patch: number] | undefined
   * const version = Schema.resolveAnnotations(schema)?.["version"]
   *
   * if (version) {
   *   // Access individual parts of the version
   *   console.log(version[1])
   *   // Output: 2
   * }
   * ```
   *
   * @category models
   * @since 4.0.0
   */
  export interface Annotations {
    readonly [x: string]: unknown
  }

  /**
   * Annotations shared by all schema nodes. These map to common JSON Schema /
   * OpenAPI fields: `title`, `description`, `format`, etc.
   *
   * @category models
   * @since 4.0.0
   */
  export interface Augment extends Annotations {
    /**
     * Human-readable description of what a value is expected to satisfy.
     *
     * **Details**
     *
     * For filter and refinement failures, the default formatter uses
     * `message` first, then `expected`, and finally falls back to `<filter>`.
     *
     * Use this to name a failed filter in the default message:
     * `Expected <expected>, got <actual>`.
     */
    readonly expected?: string | undefined
    readonly title?: string | undefined
    readonly description?: string | undefined
    readonly documentation?: string | undefined
    readonly readOnly?: boolean | undefined
    readonly writeOnly?: boolean | undefined
    readonly format?: string | undefined
    readonly contentEncoding?: string | undefined
    readonly contentMediaType?: string | undefined
  }

  /**
   * Extends {@link Augment} with type-parametric `default` and `examples` fields.
   *
   * @category models
   * @since 4.0.0
   */
  export interface Documentation<T> extends Augment {
    readonly default?: T | undefined
    readonly examples?: ReadonlyArray<T> | undefined
  }

  /**
   * Annotations for struct property schemas. Extends {@link Documentation}
   * with an optional `messageMissingKey` to override the error message when
   * the property key is absent during decoding.
   *
   * @category models
   * @since 4.0.0
   */
  export interface Key<T> extends Documentation<T> {
    /**
     * The message to use when a key is missing.
     */
    readonly messageMissingKey?: string | undefined
  }

  /**
   * Base annotations shared by all composite schema nodes. Extends
   * {@link Documentation} with error messages, branding, parse options, and
   * arbitrary generation hooks. {@link Declaration} and other annotation
   * interfaces build on top of this.
   *
   * @category models
   * @since 4.0.0
   */
  export interface Bottom<T, TypeParameters extends ReadonlyArray<Constraint>> extends Documentation<T> {
    /**
     * Complete message to use when this schema node reports an issue.
     *
     * **Details**
     *
     * This replaces the default message for matching issue types instead of
     * only changing the expected label. For a filter or refinement failure,
     * annotate the filter with `message` to replace the whole filter failure
     * message, or `expected` to keep the default
     * `Expected <expected>, got <actual>` shape.
     */
    readonly message?: string | undefined
    /**
     * The message to use when a key is unexpected.
     */
    readonly messageUnexpectedKey?: string | undefined
    /**
     * Stable identifier for this schema node.
     *
     * **Details**
     *
     * Identifiers are used by schema tooling, including JSON Schema
     * generation, to name references. The default formatter also uses
     * `identifier` as the expected label for type-level failures, such as
     * `Expected UserId, got null`.
     *
     * `identifier` does not name a failed filter or refinement. If the base
     * type matches and a filter fails, put `expected` or `message` on the
     * filter/refinement instead.
     */
    readonly identifier?: string | undefined
    readonly parseOptions?: SchemaAST.ParseOptions | undefined
    /**
     * Optional metadata used to identify or extend the filter with custom data.
     */
    readonly meta?: Meta | undefined
    /**
     * Accumulated brands when multiple brands are added with `Schema.brand`.
     */
    readonly brands?: ReadonlyArray<string> | undefined
    readonly toArbitrary?:
      | ToArbitrary.Declaration<T, TypeParameters>
      | undefined
  }

  /**
   * Helpers for projecting declaration type-parameter schemas into decoded or
   * encoded codec arrays used by annotation hooks.
   *
   * @since 4.0.0
   */
  export namespace TypeParameters {
    /**
     * Maps declaration type-parameter schemas to codecs for their decoded `Type`
     * values.
     *
     * @category utility types
     * @since 4.0.0
     */
    export type Type<TypeParameters extends ReadonlyArray<Constraint>> = {
      readonly [K in keyof TypeParameters]: Codec<TypeParameters[K]["Type"]>
    }
    /**
     * Maps declaration type-parameter schemas to codecs for their `Encoded` values.
     *
     * @category utility types
     * @since 4.0.0
     */
    export type Encoded<TypeParameters extends ReadonlyArray<Constraint>> = {
      readonly [K in keyof TypeParameters]: Codec<TypeParameters[K]["Encoded"]>
    }
  }

  /**
   * Full annotation set for `Declaration` schema nodes — used when defining
   * custom, opaque schema types via `Schema.declare`. Extends {@link Bottom}
   * with optional codec, arbitrary, equivalence, and formatter hooks so that
   * derived capabilities (JSON encoding, property testing, etc.) can be
   * provided for the custom type.
   *
   * @category models
   * @since 4.0.0
   */
  export interface Declaration<T, TypeParameters extends ReadonlyArray<Constraint> = readonly []>
    extends Bottom<T, TypeParameters>
  {
    readonly toCodec?:
      | ((typeParameters: TypeParameters.Encoded<TypeParameters>) => SchemaAST.Link)
      | undefined
    readonly toCodecJson?:
      | ((typeParameters: TypeParameters.Encoded<TypeParameters>) => SchemaAST.Link)
      | undefined
    readonly toCodecIso?:
      | ((typeParameters: TypeParameters.Type<TypeParameters>) => SchemaAST.Link)
      | undefined
    readonly toArbitrary?: ToArbitrary.Declaration<T, TypeParameters> | undefined
    readonly toEquivalence?: ToEquivalence.Declaration<T, TypeParameters> | undefined
    readonly toFormatter?: ToFormatter.Declaration<T, TypeParameters> | undefined
    readonly typeConstructor?: {
      readonly _tag: string
      readonly [key: string]: unknown
    } | undefined
    readonly generation?: {
      readonly runtime: string
      readonly Type: string
      readonly Encoded?: string | undefined
      readonly importDeclaration?: string | undefined
    } | undefined
    /**
     * Used to collect sentinels from a Declaration SchemaAST.
     *
     * @internal
     */
    readonly "~sentinels"?: ReadonlyArray<SchemaAST.Sentinel> | undefined
  }

  /**
   * Annotations for filter schema nodes (created via `Schema.filter`). Extends
   * {@link Augment} with an optional error message, identifier, and metadata.
   * Filters are intentionally non-parametric to keep them covariant.
   *
   * @category models
   * @since 4.0.0
   */
  export interface Filter extends Augment {
    /**
     * Complete message to use when this filter or refinement fails.
     *
     * **Details**
     *
     * The default formatter checks filter annotations in this order:
     * `message`, then `expected`, then `<filter>`.
     */
    readonly message?: string | undefined
    /**
     * Stable identifier for the schema after this filter is attached.
     *
     * **Details**
     *
     * This can affect schema tooling such as JSON Schema generation and
     * type-level failures before the filter runs, but it does not name the
     * failed filter itself. For filter failure messages, use `expected` or
     * `message`.
     */
    readonly identifier?: string | undefined
    /**
     * Optional metadata used to identify or extend the filter with custom data.
     */
    readonly meta?: Meta | undefined
    /**
     * Optional hints used by arbitrary derivation for this filter.
     *
     * **Details**
     *
     * The same annotation can be attached to a single filter or a
     * `FilterGroup`. Group hints apply to the same schema node while child
     * filters are still collected and checked normally.
     */
    readonly arbitrary?:
      | ToArbitrary.Filter
      | undefined
    /**
     * Marks the filter as *structural*, meaning it applies to the shape or
     * structure of the container (e.g., array length, object keys) rather than
     * the contents.
     *
     * **Details**
     *
     * Example: `minLength` on an array is a structural filter.
     */
    readonly "~structural"?: boolean | undefined
  }

  /**
   * Types used by arbitrary-derivation annotations to configure `toArbitrary`
   * hooks, filter hints, candidate sources, diagnostics, and merged generation
   * constraints.
   *
   * @since 4.0.0
   */
  export namespace ToArbitrary {
    /**
     * Arbitrary-generation hints attached to a filter or filter group.
     *
     * **Details**
     *
     * `constraint` refines the schema node's base generator. `candidate` adds a
     * weighted source before all filters run. If neither hint is provided, the
     * filter does not guide generation; generated values are still checked by
     * the filter predicate. With `{ report: true }`, this is reported as
     * `OpaqueFilter`.
     *
     * @category models
     * @since 4.0.0
     */
    export interface Filter {
      readonly constraint?: GenerationConstraint | undefined
      readonly candidate?: Candidate | undefined
    }

    /**
     * Additional arbitrary source used before final filter checks run.
     *
     * **Details**
     *
     * The base generator keeps weight `1`; candidates default to weight `1`
     * and must use a positive integer weight. `make` receives the merged
     * constraint for the current node and may return `undefined` to opt out,
     * including for recursive terminal branches. Candidate values are still
     * checked by every schema filter, so invalid candidates affect efficiency but
     * not validity.
     *
     * @category models
     * @since 4.0.0
     */
    export interface Candidate {
      readonly weight?: number | undefined
      readonly make: (
        fc: typeof FastCheck,
        context: Context
      ) => FastCheck.Arbitrary<unknown> | undefined
    }

    /**
     * Ordered constraint accumulated from range checks.
     *
     * **Details**
     *
     * Generators consume these constraints only when they recognize `order`,
     * such as `Order.Number`, `Order.BigInt`, DateTime, or BigDecimal. Merging
     * constraints with different `Order` instances fails fast.
     *
     * @category models
     * @since 4.0.0
     */
    export interface OrderedConstraint<T> {
      readonly order: Order.Order<T>
      readonly minimum?: T | undefined
      readonly exclusiveMinimum?: boolean | undefined
      readonly maximum?: T | undefined
      readonly exclusiveMaximum?: boolean | undefined
    }

    /**
     * Node-local arbitrary-generation constraint accumulated from schema checks.
     *
     * **Details**
     *
     * `GenerationConstraint` is a generation hint for the current schema AST
     * node, not a self-describing validation contract. Each generator consumes
     * the fields it understands for the current node and ignores the rest;
     * final schema filters still validate every generated value.
     *
     * `minLength` and `maxLength` represent node-local cardinality: string
     * length for strings, array length for arrays, final own-property count for
     * objects, and final size/cardinality for sets, maps, hash collections, and
     * chunks. `patterns` are concatenated and used by string generators.
     * `integer`, `noNaN`, `noInfinity`, `valid`, and `unique` are true when any
     * contributing filter sets them. Range bounds live in `ordered` so ordered
     * values can share the same representation.
     *
     * @category models
     * @since 4.0.0
     */
    export interface GenerationConstraint {
      readonly minLength?: number | undefined
      readonly maxLength?: number | undefined
      readonly patterns?: readonly [string, ...Array<string>]
      readonly integer?: boolean | undefined
      readonly noInfinity?: boolean | undefined
      readonly noNaN?: boolean | undefined
      readonly valid?: boolean | undefined
      readonly unique?: boolean | undefined
      readonly ordered?: OrderedConstraint<any> | undefined
    }

    /**
     * Recursion budget passed to arbitrary-derivation hooks.
     *
     * **Details**
     *
     * Pass this object to `fc.oneof` when combining terminal and recursive
     * branches. Put the terminal branch first because fast-check uses only the
     * first branch once `maxDepth` is reached for `depthIdentifier`.
     *
     * @category models
     * @since 4.0.0
     */
    export interface Recursion {
      readonly maxDepth: number
      readonly depthIdentifier: FastCheck.DepthIdentifier | string
    }

    /**
     * Context passed to arbitrary-derivation hooks and candidate factories.
     *
     * **Details**
     *
     * `constraint` contains the merged constraint for the current schema
     * node. `recursion` is present while deriving through a suspended schema;
     * hooks that build recursive alternatives should pass it to `fc.oneof` with
     * the finite branch first.
     *
     * @category models
     * @since 4.0.0
     */
    export interface Context {
      readonly constraint?: ToArbitrary.GenerationConstraint | undefined
      readonly recursion?: ToArbitrary.Recursion | undefined
    }

    /**
     * Arbitrary generators derived for a declaration type parameter.
     *
     * **Details**
     *
     * `arbitrary` is the normal generator. `terminal` is the finite generator
     * used while building recursive terminal branches and is `undefined` when
     * no finite path is known. Optional containers can ignore it; non-empty
     * containers need it for their terminal branch.
     *
     * @category models
     * @since 4.0.0
     */
    export interface TypeParameter<T> {
      readonly arbitrary: FastCheck.Arbitrary<T>
      readonly terminal: FastCheck.Arbitrary<T> | undefined
    }

    /**
     * Arbitrary derivation returned by declaration hooks.
     *
     * **Details**
     *
     * `arbitrary` is the normal generator. `terminal` is an optional finite
     * branch for recursive schemas. If omitted, it defaults to `arbitrary` only
     * for declarations without type parameters.
     *
     * @category models
     * @since 4.0.0
     */
    export interface Derivation<T> {
      readonly arbitrary: FastCheck.Arbitrary<T>
      readonly terminal?: FastCheck.Arbitrary<T> | undefined
    }

    /**
     * Output accepted from declaration arbitrary hooks.
     *
     * **Details**
     *
     * A bare fast-check arbitrary is shorthand for `{ arbitrary }`, useful for
     * atomic declarations such as URLs. Generic declarations that need precise
     * recursive behavior should return a {@link Derivation} with `terminal`.
     *
     * @category models
     * @since 4.0.0
     */
    export type Output<T> = FastCheck.Arbitrary<T> | Derivation<T>

    /**
     * Hook signature for declaration schema arbitrary annotations.
     *
     * **Details**
     *
     * Type parameters expose normal and terminal generators. A declaration with
     * no type parameters can return a bare arbitrary; a generic declaration
     * must return `terminal` explicitly when it has a finite branch depending on
     * parameters.
     *
     * @category models
     * @since 4.0.0
     */
    export interface Declaration<T, TypeParameters extends ReadonlyArray<Constraint>> {
      (
        /* Arbitrary derivations for any type parameters of the schema (if present) */
        typeParameters: { readonly [K in keyof TypeParameters]: TypeParameter<TypeParameters[K]["Type"]> }
      ): (fc: typeof FastCheck, context: Context) => Output<T>
    }

    /**
     * Wraps a derived value together with arbitrary-derivation diagnostics.
     *
     * @category models
     * @since 4.0.0
     */
    export interface WithReport<A> {
      readonly value: A
      readonly report: Report
    }

    /**
     * Diagnostics collected while deriving an arbitrary.
     *
     * **Details**
     *
     * Reports contain warnings only. Unsupported schema nodes, impossible
     * constraints, invalid candidate weights, and throwing candidate factories
     * fail immediately.
     *
     * @category models
     * @since 4.0.0
     */
    export interface Report {
      readonly warnings: ReadonlyArray<Warning>
    }

    /**
     * Non-fatal arbitrary-derivation warning.
     *
     * @category models
     * @since 4.0.0
     */
    export type Warning = OpaqueFilterWarning

    /**
     * Warning emitted when a filter is handled only by the final `.filter`.
     *
     * **Details**
     *
     * The filter is still enforced. The warning means it did not contribute
     * a constraint or candidate, so generation may rely on fast-check discards.
     *
     * @category models
     * @since 4.0.0
     */
    export interface OpaqueFilterWarning {
      readonly _tag: "OpaqueFilter"
      readonly path: ReadonlyArray<PropertyKey>
      readonly description?: string | undefined
    }
  }

  /**
   * Types used by formatter annotations to customize formatter derivation for
   * declaration schemas.
   *
   * @since 4.0.0
   */
  export namespace ToFormatter {
    /**
     * Hook signature for declaration schema formatter annotations.
     *
     * **Details**
     *
     * Given formatters for any type parameters, returns a formatter for `T`.
     *
     * @category models
     * @since 4.0.0
     */
    export interface Declaration<T, TypeParameters extends ReadonlyArray<Constraint>> {
      (
        /* Formatters for any type parameters of the schema (if present) */
        typeParameters: { readonly [K in keyof TypeParameters]: Formatter<TypeParameters[K]["Type"]> }
      ): Formatter<T>
    }
  }

  /**
   * Types used by equivalence annotations to customize equivalence derivation for
   * declaration schemas.
   *
   * @since 4.0.0
   */
  export namespace ToEquivalence {
    /**
     * Hook signature for declaration schema equivalence annotations.
     *
     * **Details**
     *
     * Given equivalences for any type parameters, returns an `Equivalence` for `T`.
     *
     * @category models
     * @since 4.0.0
     */
    export interface Declaration<T, TypeParameters extends ReadonlyArray<Constraint>> {
      (
        /* Equivalences for any type parameters of the schema (if present) */
        typeParameters: { readonly [K in keyof TypeParameters]: Equivalence.Equivalence<TypeParameters[K]["Type"]> }
      ): Equivalence.Equivalence<T>
    }
  }

  /**
   * Annotations that can be attached to schema issues.
   *
   * **Details**
   *
   * The optional `message` field overrides the default issue message.
   *
   * @category models
   * @since 4.0.0
   */
  export interface Issue extends Annotations {
    readonly message?: string | undefined
  }

  /**
   * Registry of metadata payloads emitted by built-in schema filters and checks.
   *
   * **Details**
   *
   * Do not augment this interface with custom metadata; extend `MetaDefinitions`
   * instead.
   *
   * @category models
   * @since 4.0.0
   */
  export interface BuiltInMetaDefinitions {
    // String Meta
    readonly isStringFinite: {
      readonly _tag: "isStringFinite"
      readonly regExp: globalThis.RegExp
    }
    readonly isStringBigInt: {
      readonly _tag: "isStringBigInt"
      readonly regExp: globalThis.RegExp
    }
    readonly isStringSymbol: {
      readonly _tag: "isStringSymbol"
      readonly regExp: globalThis.RegExp
    }
    readonly isMinLength: {
      readonly _tag: "isMinLength"
      readonly minLength: number
    }
    readonly isMaxLength: {
      readonly _tag: "isMaxLength"
      readonly maxLength: number
    }
    readonly isLengthBetween: {
      readonly _tag: "isLengthBetween"
      readonly minimum: number
      readonly maximum: number
    }
    readonly isPattern: {
      readonly _tag: "isPattern"
      readonly regExp: globalThis.RegExp
    }
    readonly isTrimmed: {
      readonly _tag: "isTrimmed"
      readonly regExp: globalThis.RegExp
    }
    readonly isUUID: {
      readonly _tag: "isUUID"
      readonly regExp: globalThis.RegExp
      readonly version: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | undefined
    }
    readonly isGUID: {
      readonly _tag: "isGUID"
      readonly regExp: globalThis.RegExp
    }
    readonly isULID: {
      readonly _tag: "isULID"
      readonly regExp: globalThis.RegExp
    }
    readonly isBase64: {
      readonly _tag: "isBase64"
      readonly regExp: globalThis.RegExp
    }
    readonly isBase64Url: {
      readonly _tag: "isBase64Url"
      readonly regExp: globalThis.RegExp
    }
    readonly isStartsWith: {
      readonly _tag: "isStartsWith"
      readonly startsWith: string
      readonly regExp: globalThis.RegExp
    }
    readonly isEndsWith: {
      readonly _tag: "isEndsWith"
      readonly endsWith: string
      readonly regExp: globalThis.RegExp
    }
    readonly isIncludes: {
      readonly _tag: "isIncludes"
      readonly includes: string
      readonly regExp: globalThis.RegExp
    }
    readonly isUppercased: {
      readonly _tag: "isUppercased"
      readonly regExp: globalThis.RegExp
    }
    readonly isLowercased: {
      readonly _tag: "isLowercased"
      readonly regExp: globalThis.RegExp
    }
    readonly isCapitalized: {
      readonly _tag: "isCapitalized"
      readonly regExp: globalThis.RegExp
    }
    readonly isUncapitalized: {
      readonly _tag: "isUncapitalized"
      readonly regExp: globalThis.RegExp
    }
    // Number Meta
    readonly isFinite: {
      readonly _tag: "isFinite"
    }
    readonly isInt: {
      readonly _tag: "isInt"
    }
    readonly isMultipleOf: {
      readonly _tag: "isMultipleOf"
      readonly divisor: number
    }
    readonly isGreaterThan: {
      readonly _tag: "isGreaterThan"
      readonly exclusiveMinimum: number
    }
    readonly isGreaterThanOrEqualTo: {
      readonly _tag: "isGreaterThanOrEqualTo"
      readonly minimum: number
    }
    readonly isLessThan: {
      readonly _tag: "isLessThan"
      readonly exclusiveMaximum: number
    }
    readonly isLessThanOrEqualTo: {
      readonly _tag: "isLessThanOrEqualTo"
      readonly maximum: number
    }
    readonly isBetween: {
      readonly _tag: "isBetween"
      readonly minimum: number
      readonly maximum: number
      readonly exclusiveMinimum?: boolean | undefined
      readonly exclusiveMaximum?: boolean | undefined
    }
    // BigInt Meta
    readonly isGreaterThanBigInt: {
      readonly _tag: "isGreaterThanBigInt"
      readonly exclusiveMinimum: bigint
    }
    readonly isGreaterThanOrEqualToBigInt: {
      readonly _tag: "isGreaterThanOrEqualToBigInt"
      readonly minimum: bigint
    }
    readonly isLessThanBigInt: {
      readonly _tag: "isLessThanBigInt"
      readonly exclusiveMaximum: bigint
    }
    readonly isLessThanOrEqualToBigInt: {
      readonly _tag: "isLessThanOrEqualToBigInt"
      readonly maximum: bigint
    }
    readonly isBetweenBigInt: {
      readonly _tag: "isBetweenBigInt"
      readonly minimum: bigint
      readonly maximum: bigint
      readonly exclusiveMinimum?: boolean | undefined
      readonly exclusiveMaximum?: boolean | undefined
    }
    // Date Meta
    readonly isDateValid: {
      readonly _tag: "isDateValid"
    }
    readonly isGreaterThanDate: {
      readonly _tag: "isGreaterThanDate"
      readonly exclusiveMinimum: globalThis.Date
    }
    readonly isGreaterThanOrEqualToDate: {
      readonly _tag: "isGreaterThanOrEqualToDate"
      readonly minimum: globalThis.Date
    }
    readonly isLessThanDate: {
      readonly _tag: "isLessThanDate"
      readonly exclusiveMaximum: globalThis.Date
    }
    readonly isLessThanOrEqualToDate: {
      readonly _tag: "isLessThanOrEqualToDate"
      readonly maximum: globalThis.Date
    }
    readonly isBetweenDate: {
      readonly _tag: "isBetweenDate"
      readonly minimum: globalThis.Date
      readonly maximum: globalThis.Date
      readonly exclusiveMinimum?: boolean | undefined
      readonly exclusiveMaximum?: boolean | undefined
    }
    // Objects Meta
    readonly isMinProperties: {
      readonly _tag: "isMinProperties"
      readonly minProperties: number
    }
    readonly isMaxProperties: {
      readonly _tag: "isMaxProperties"
      readonly maxProperties: number
    }
    readonly isPropertiesLengthBetween: {
      readonly _tag: "isPropertiesLengthBetween"
      readonly minimum: number
      readonly maximum: number
    }
    readonly isPropertyNames: {
      readonly _tag: "isPropertyNames"
      readonly propertyNames: SchemaAST.AST
    }
    // Arrays Meta
    readonly isUnique: {
      readonly _tag: "isUnique"
    }
    // Declaration Meta
    readonly isMinSize: {
      readonly _tag: "isMinSize"
      readonly minSize: number
    }
    readonly isMaxSize: {
      readonly _tag: "isMaxSize"
      readonly maxSize: number
    }
    readonly isSizeBetween: {
      readonly _tag: "isSizeBetween"
      readonly minimum: number
      readonly maximum: number
    }
  }

  /**
   * Union of all metadata payloads defined by `BuiltInMetaDefinitions`.
   *
   * @category utility types
   * @since 4.0.0
   */
  export type BuiltInMeta = BuiltInMetaDefinitions[keyof BuiltInMetaDefinitions]

  /**
   * Augmentable registry of schema filter metadata payloads.
   *
   * **Details**
   *
   * Extend this interface to add custom values accepted by annotation `meta`
   * fields.
   *
   * @category models
   * @since 4.0.0
   */
  export interface MetaDefinitions extends BuiltInMetaDefinitions {}

  /**
   * Union of built-in and user-augmented schema filter metadata payloads.
   *
   * @category utility types
   * @since 4.0.0
   */
  export type Meta = MetaDefinitions[keyof MetaDefinitions]
}
