T.ME/BIBIL_0DAY
CasperSecurity


Server : Apache/2
System : Linux server-15-235-50-60 5.15.0-164-generic #174-Ubuntu SMP Fri Nov 14 20:25:16 UTC 2025 x86_64
User : gositeme ( 1004)
PHP Version : 8.2.29
Disable Function : exec,system,passthru,shell_exec,proc_close,proc_open,dl,popen,show_source,posix_kill,posix_mkfifo,posix_getpwuid,posix_setpgid,posix_setsid,posix_setuid,posix_setgid,posix_seteuid,posix_setegid,posix_uname
Directory :  /home/gositeme/domains/lavocat.quebec/private_html/node_modules/effect/src/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Current File : /home/gositeme/domains/lavocat.quebec/private_html/node_modules/effect/src/Schema.ts
/**
 * @since 3.10.0
 */

import type { StandardSchemaV1 } from "@standard-schema/spec"
import type { ArbitraryAnnotation, ArbitraryGenerationContext, LazyArbitrary } from "./Arbitrary.js"
import * as array_ from "./Array.js"
import * as bigDecimal_ from "./BigDecimal.js"
import * as bigInt_ from "./BigInt.js"
import * as boolean_ from "./Boolean.js"
import type { Brand } from "./Brand.js"
import * as cause_ from "./Cause.js"
import * as chunk_ from "./Chunk.js"
import * as config_ from "./Config.js"
import * as configError_ from "./ConfigError.js"
import * as data_ from "./Data.js"
import * as dateTime from "./DateTime.js"
import * as duration_ from "./Duration.js"
import * as Effect from "./Effect.js"
import * as either_ from "./Either.js"
import * as Encoding from "./Encoding.js"
import * as Equal from "./Equal.js"
import * as Equivalence from "./Equivalence.js"
import * as exit_ from "./Exit.js"
import * as fastCheck_ from "./FastCheck.js"
import * as fiberId_ from "./FiberId.js"
import type { LazyArg } from "./Function.js"
import { dual, identity } from "./Function.js"
import { globalValue } from "./GlobalValue.js"
import * as hashMap_ from "./HashMap.js"
import * as hashSet_ from "./HashSet.js"
import * as internalCause_ from "./internal/cause.js"
import * as errors_ from "./internal/schema/errors.js"
import * as schemaId_ from "./internal/schema/schemaId.js"
import * as util_ from "./internal/schema/util.js"
import * as list_ from "./List.js"
import * as number_ from "./Number.js"
import * as option_ from "./Option.js"
import type * as Order from "./Order.js"
import * as ParseResult from "./ParseResult.js"
import type { Pipeable } from "./Pipeable.js"
import { pipeArguments } from "./Pipeable.js"
import * as Predicate from "./Predicate.js"
import type * as pretty_ from "./Pretty.js"
import * as redacted_ from "./Redacted.js"
import * as Request from "./Request.js"
import * as scheduler_ from "./Scheduler.js"
import type { ParseOptions } from "./SchemaAST.js"
import * as AST from "./SchemaAST.js"
import * as sortedSet_ from "./SortedSet.js"
import * as string_ from "./String.js"
import * as struct_ from "./Struct.js"
import type * as Types from "./Types.js"

/**
 * @since 3.10.0
 */
export type Simplify<A> = { [K in keyof A]: A[K] } & {}

/**
 * @since 3.10.0
 */
export type SimplifyMutable<A> = {
  -readonly [K in keyof A]: A[K]
} extends infer B ? B : never

/**
 * @since 3.10.0
 * @category symbol
 */
export const TypeId: unique symbol = Symbol.for("effect/Schema")

/**
 * @since 3.10.0
 * @category symbol
 */
export type TypeId = typeof TypeId

/**
 * @category model
 * @since 3.10.0
 */
export interface Schema<in out A, in out I = A, out R = never> extends Schema.Variance<A, I, R>, Pipeable {
  readonly Type: A
  readonly Encoded: I
  readonly Context: R
  readonly ast: AST.AST
  /**
   * Merges a set of new annotations with existing ones, potentially overwriting
   * any duplicates.
   */
  annotations(annotations: Annotations.GenericSchema<A>): Schema<A, I, R>
}

/**
 * @category annotations
 * @since 3.10.0
 */
export interface Annotable<Self extends Schema<A, I, R>, A, I = A, R = never> extends Schema<A, I, R> {
  annotations(annotations: Annotations.GenericSchema<A>): Self
}

/**
 * @category annotations
 * @since 3.10.0
 */
export interface AnnotableClass<Self extends Schema<A, I, R>, A, I = A, R = never> extends Annotable<Self, A, I, R> {
  new(_: never): Schema.Variance<A, I, R>
}

/**
 * @category model
 * @since 3.10.0
 */
export interface SchemaClass<A, I = A, R = never> extends AnnotableClass<SchemaClass<A, I, R>, A, I, R> {}

/**
 * @category constructors
 * @since 3.10.0
 */
export function make<A, I = A, R = never>(ast: AST.AST): SchemaClass<A, I, R> {
  return class SchemaClass {
    [TypeId] = variance
    static ast = ast
    static annotations(annotations: Annotations.GenericSchema<A>) {
      return make<A, I, R>(mergeSchemaAnnotations(this.ast, annotations))
    }
    static pipe() {
      return pipeArguments(this, arguments)
    }
    static toString() {
      return String(ast)
    }
    static Type: A
    static Encoded: I
    static Context: R
    static [TypeId] = variance
  }
}

const variance = {
  /* c8 ignore next */
  _A: (_: any) => _,
  /* c8 ignore next */
  _I: (_: any) => _,
  /* c8 ignore next */
  _R: (_: never) => _
}

const makeStandardResult = <A>(exit: exit_.Exit<StandardSchemaV1.Result<A>>): StandardSchemaV1.Result<A> =>
  exit_.isSuccess(exit) ? exit.value : makeStandardFailureResult(cause_.pretty(exit.cause))

const makeStandardFailureResult = (message: string): StandardSchemaV1.FailureResult => ({
  issues: [{ message }]
})

const makeStandardFailureFromParseIssue = (
  issue: ParseResult.ParseIssue
): Effect.Effect<StandardSchemaV1.FailureResult> =>
  Effect.map(ParseResult.ArrayFormatter.formatIssue(issue), (issues) => ({
    issues: issues.map((issue) => ({
      path: issue.path,
      message: issue.message
    }))
  }))

/**
 * Returns a "Standard Schema" object conforming to the [Standard Schema
 * v1](https://standardschema.dev/) specification.
 *
 * 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.
 *
 * Any detected defects will be reported via a single issue containing no
 * `path`.
 *
 * @example
 * ```ts
 * import { Schema } from "effect"
 *
 * const schema = Schema.Struct({
 *   name: Schema.String
 * })
 *
 * //      ┌─── StandardSchemaV1<{ readonly name: string; }>
 * //      ▼
 * const standardSchema = Schema.standardSchemaV1(schema)
 * ```
 *
 * @category Standard Schema
 * @since 3.13.0
 */
export const standardSchemaV1 = <A, I>(
  schema: Schema<A, I, never>,
  overrideOptions?: AST.ParseOptions
): StandardSchemaV1<I, A> & SchemaClass<A, I, never> => {
  const decodeUnknown = ParseResult.decodeUnknown(schema, { errors: "all" })
  return class StandardSchemaV1Class extends make<A, I, never>(schema.ast) {
    static "~standard" = {
      version: 1,
      vendor: "effect",
      validate(value) {
        const scheduler = new scheduler_.SyncScheduler()
        const fiber = Effect.runFork(
          Effect.matchEffect(decodeUnknown(value, overrideOptions), {
            onFailure: makeStandardFailureFromParseIssue,
            onSuccess: (value) => Effect.succeed({ value })
          }),
          { scheduler }
        )
        scheduler.flush()
        const exit = fiber.unsafePoll()
        if (exit) {
          return makeStandardResult(exit)
        }
        return new Promise((resolve) => {
          fiber.addObserver((exit) => {
            resolve(makeStandardResult(exit))
          })
        })
      }
    }
  }
}

interface AllAnnotations<A, TypeParameters extends ReadonlyArray<any>>
  extends Annotations.Schema<A, TypeParameters>, PropertySignature.Annotations<A>
{}

const builtInAnnotations = {
  schemaId: AST.SchemaIdAnnotationId,
  message: AST.MessageAnnotationId,
  missingMessage: AST.MissingMessageAnnotationId,
  identifier: AST.IdentifierAnnotationId,
  title: AST.TitleAnnotationId,
  description: AST.DescriptionAnnotationId,
  examples: AST.ExamplesAnnotationId,
  default: AST.DefaultAnnotationId,
  documentation: AST.DocumentationAnnotationId,
  jsonSchema: AST.JSONSchemaAnnotationId,
  arbitrary: AST.ArbitraryAnnotationId,
  pretty: AST.PrettyAnnotationId,
  equivalence: AST.EquivalenceAnnotationId,
  concurrency: AST.ConcurrencyAnnotationId,
  batching: AST.BatchingAnnotationId,
  parseIssueTitle: AST.ParseIssueTitleAnnotationId,
  parseOptions: AST.ParseOptionsAnnotationId,
  decodingFallback: AST.DecodingFallbackAnnotationId
}

const toASTAnnotations = <A, TypeParameters extends ReadonlyArray<any>>(
  annotations?: AllAnnotations<A, TypeParameters>
): AST.Annotations => {
  if (!annotations) {
    return {}
  }
  const out: Types.Mutable<AST.Annotations> = { ...annotations }

  for (const key in builtInAnnotations) {
    if (key in annotations) {
      const id = builtInAnnotations[key as keyof typeof builtInAnnotations]
      out[id] = annotations[key as keyof typeof annotations]
      delete out[key]
    }
  }

  return out
}

const mergeSchemaAnnotations = <A>(ast: AST.AST, annotations: Annotations.Schema<A>): AST.AST =>
  AST.annotations(ast, toASTAnnotations(annotations))

/**
 * @category annotations
 * @since 3.10.0
 */
export declare namespace Annotable {
  /**
   * @since 3.10.0
   */
  export type Self<S extends All> = ReturnType<S["annotations"]>

  /**
   * @since 3.10.0
   */
  export type Any = Annotable<any, any, any, unknown>

  /**
   * @since 3.10.0
   */
  export type All =
    | Any
    | Annotable<any, any, never, unknown>
    | Annotable<any, never, any, unknown>
    | Annotable<any, never, never, unknown>
}

/**
 * @since 3.10.0
 */
export function asSchema<S extends Schema.All>(
  schema: S
): Schema<Schema.Type<S>, Schema.Encoded<S>, Schema.Context<S>> {
  return schema as any
}

/**
 * @category formatting
 * @since 3.10.0
 */
export const format = <S extends Schema.All>(schema: S): string => String(schema.ast)

/**
 * @since 3.10.0
 */
export declare namespace Schema {
  /**
   * @since 3.10.0
   */
  export interface Variance<A, I, R> {
    readonly [TypeId]: {
      readonly _A: Types.Invariant<A>
      readonly _I: Types.Invariant<I>
      readonly _R: Types.Covariant<R>
    }
  }

  /**
   * @since 3.10.0
   */
  export type Type<S> = S extends Schema.Variance<infer A, infer _I, infer _R> ? A : never

  /**
   * @since 3.10.0
   */
  export type Encoded<S> = S extends Schema.Variance<infer _A, infer I, infer _R> ? I : never

  /**
   * @since 3.10.0
   */
  export type Context<S> = S extends Schema.Variance<infer _A, infer _I, infer R> ? R : never

  /**
   * @since 3.10.0
   */
  export type ToAsserts<S extends AnyNoContext> = (
    input: unknown,
    options?: AST.ParseOptions
  ) => asserts input is Schema.Type<S>

  /**
   * Any schema, except for `never`.
   *
   * @since 3.10.0
   */
  export type Any = Schema<any, any, unknown>

  /**
   * Any schema with `Context = never`, except for `never`.
   *
   * @since 3.10.0
   */
  export type AnyNoContext = Schema<any, any, never>

  /**
   * Any schema, including `never`.
   *
   * @since 3.10.0
   */
  export type All =
    | Any
    | Schema<any, never, unknown>
    | Schema<never, any, unknown>
    | Schema<never, never, unknown>

  /**
   * Type-level counterpart of `Schema.asSchema` function.
   *
   * @since 3.10.0
   */
  export type AsSchema<S extends All> = Schema<Type<S>, Encoded<S>, Context<S>>
}

/**
 * The `encodedSchema` function allows you to extract the `Encoded` portion of a
 * schema, creating a new schema that conforms to the properties defined in the
 * original schema without retaining any refinements or transformations that
 * were applied previously.
 *
 * @since 3.10.0
 */
export const encodedSchema = <A, I, R>(schema: Schema<A, I, R>): SchemaClass<I> => make(AST.encodedAST(schema.ast))

/**
 * The `encodedBoundSchema` function is similar to `encodedSchema` but preserves
 * the refinements up to the first transformation point in the original schema.
 *
 * @since 3.10.0
 */
export const encodedBoundSchema = <A, I, R>(schema: Schema<A, I, R>): SchemaClass<I> =>
  make(AST.encodedBoundAST(schema.ast))

/**
 * The `typeSchema` function allows you to extract the `Type` portion of a
 * schema, creating a new schema that conforms to the properties defined in the
 * original schema without considering the initial encoding or transformation
 * processes.
 *
 * @since 3.10.0
 */
export const typeSchema = <A, I, R>(schema: Schema<A, I, R>): SchemaClass<A> => make(AST.typeAST(schema.ast))

/* c8 ignore start */
export {
  /**
   * By default the option `exact` is set to `true`.
   *
   * @throws `ParseError`
   * @category validation
   * @since 3.10.0
   */
  asserts,
  /**
   * @category decoding
   * @since 3.10.0
   */
  decodeOption,
  /**
   * @throws `ParseError`
   * @category decoding
   * @since 3.10.0
   */
  decodeSync,
  /**
   * @category decoding
   * @since 3.10.0
   */
  decodeUnknownOption,
  /**
   * @throws `ParseError`
   * @category decoding
   * @since 3.10.0
   */
  decodeUnknownSync,
  /**
   * @category encoding
   * @since 3.10.0
   */
  encodeOption,
  /**
   * @throws `ParseError`
   * @category encoding
   * @since 3.10.0
   */
  encodeSync,
  /**
   * @category encoding
   * @since 3.10.0
   */
  encodeUnknownOption,
  /**
   * @throws `ParseError`
   * @category encoding
   * @since 3.10.0
   */
  encodeUnknownSync,
  /**
   * By default the option `exact` is set to `true`.
   *
   * @category validation
   * @since 3.10.0
   */
  is,
  /**
   * @category validation
   * @since 3.10.0
   */
  validateOption,
  /**
   * @throws `ParseError`
   * @category validation
   * @since 3.10.0
   */
  validateSync
} from "./ParseResult.js"
/* c8 ignore end */

/**
 * @category encoding
 * @since 3.10.0
 */
export const encodeUnknown = <A, I, R>(
  schema: Schema<A, I, R>,
  options?: ParseOptions
) => {
  const encodeUnknown = ParseResult.encodeUnknown(schema, options)
  return (u: unknown, overrideOptions?: ParseOptions): Effect.Effect<I, ParseResult.ParseError, R> =>
    ParseResult.mapError(encodeUnknown(u, overrideOptions), ParseResult.parseError)
}

/**
 * @category encoding
 * @since 3.10.0
 */
export const encodeUnknownEither = <A, I>(
  schema: Schema<A, I, never>,
  options?: ParseOptions
) => {
  const encodeUnknownEither = ParseResult.encodeUnknownEither(schema, options)
  return (u: unknown, overrideOptions?: ParseOptions): either_.Either<I, ParseResult.ParseError> =>
    either_.mapLeft(encodeUnknownEither(u, overrideOptions), ParseResult.parseError)
}

/**
 * @category encoding
 * @since 3.10.0
 */
export const encodeUnknownPromise = <A, I>(
  schema: Schema<A, I, never>,
  options?: ParseOptions
) => {
  const parser = encodeUnknown(schema, options)
  return (u: unknown, overrideOptions?: ParseOptions): Promise<I> => Effect.runPromise(parser(u, overrideOptions))
}

/**
 * @category encoding
 * @since 3.10.0
 */
export const encode: <A, I, R>(
  schema: Schema<A, I, R>,
  options?: ParseOptions
) => (a: A, overrideOptions?: ParseOptions) => Effect.Effect<I, ParseResult.ParseError, R> = encodeUnknown

/**
 * @category encoding
 * @since 3.10.0
 */
export const encodeEither: <A, I>(
  schema: Schema<A, I, never>,
  options?: ParseOptions
) => (a: A, overrideOptions?: ParseOptions) => either_.Either<I, ParseResult.ParseError> = encodeUnknownEither

/**
 * @category encoding
 * @since 3.10.0
 */
export const encodePromise: <A, I>(
  schema: Schema<A, I, never>,
  options?: ParseOptions
) => (a: A, overrideOptions?: ParseOptions) => Promise<I> = encodeUnknownPromise

/**
 * @category decoding
 * @since 3.10.0
 */
export const decodeUnknown = <A, I, R>(
  schema: Schema<A, I, R>,
  options?: ParseOptions
) => {
  const decodeUnknown = ParseResult.decodeUnknown(schema, options)
  return (u: unknown, overrideOptions?: ParseOptions): Effect.Effect<A, ParseResult.ParseError, R> =>
    ParseResult.mapError(decodeUnknown(u, overrideOptions), ParseResult.parseError)
}

/**
 * @category decoding
 * @since 3.10.0
 */
export const decodeUnknownEither = <A, I>(
  schema: Schema<A, I, never>,
  options?: ParseOptions
) => {
  const decodeUnknownEither = ParseResult.decodeUnknownEither(schema, options)
  return (u: unknown, overrideOptions?: ParseOptions): either_.Either<A, ParseResult.ParseError> =>
    either_.mapLeft(decodeUnknownEither(u, overrideOptions), ParseResult.parseError)
}

/**
 * @category decoding
 * @since 3.10.0
 */
export const decodeUnknownPromise = <A, I>(
  schema: Schema<A, I, never>,
  options?: ParseOptions
) => {
  const parser = decodeUnknown(schema, options)
  return (u: unknown, overrideOptions?: ParseOptions): Promise<A> => Effect.runPromise(parser(u, overrideOptions))
}

/**
 * @category decoding
 * @since 3.10.0
 */
export const decode: <A, I, R>(
  schema: Schema<A, I, R>,
  options?: ParseOptions
) => (i: I, overrideOptions?: ParseOptions) => Effect.Effect<A, ParseResult.ParseError, R> = decodeUnknown

/**
 * @category decoding
 * @since 3.10.0
 */
export const decodeEither: <A, I>(
  schema: Schema<A, I, never>,
  options?: ParseOptions
) => (i: I, overrideOptions?: ParseOptions) => either_.Either<A, ParseResult.ParseError> = decodeUnknownEither

/**
 * @category decoding
 * @since 3.10.0
 */
export const decodePromise: <A, I>(
  schema: Schema<A, I, never>,
  options?: ParseOptions
) => (i: I, overrideOptions?: ParseOptions) => Promise<A> = decodeUnknownPromise

/**
 * @category validation
 * @since 3.10.0
 */
export const validate = <A, I, R>(
  schema: Schema<A, I, R>,
  options?: ParseOptions
) => {
  const validate = ParseResult.validate(schema, options)
  return (u: unknown, overrideOptions?: ParseOptions): Effect.Effect<A, ParseResult.ParseError, R> =>
    ParseResult.mapError(validate(u, overrideOptions), ParseResult.parseError)
}

/**
 * @category validation
 * @since 3.10.0
 */
export const validateEither = <A, I, R>(
  schema: Schema<A, I, R>,
  options?: ParseOptions
) => {
  const validateEither = ParseResult.validateEither(schema, options)
  return (u: unknown, overrideOptions?: ParseOptions): either_.Either<A, ParseResult.ParseError> =>
    either_.mapLeft(validateEither(u, overrideOptions), ParseResult.parseError)
}

/**
 * @category validation
 * @since 3.10.0
 */
export const validatePromise = <A, I>(
  schema: Schema<A, I, never>,
  options?: ParseOptions
) => {
  const parser = validate(schema, options)
  return (u: unknown, overrideOptions?: ParseOptions): Promise<A> => Effect.runPromise(parser(u, overrideOptions))
}

/**
 * Tests if a value is a `Schema`.
 *
 * @category guards
 * @since 3.10.0
 */
export const isSchema = (u: unknown): u is Schema.Any =>
  Predicate.hasProperty(u, TypeId) && Predicate.isObject(u[TypeId])

/**
 * @category api interface
 * @since 3.10.0
 */
export interface Literal<Literals extends array_.NonEmptyReadonlyArray<AST.LiteralValue>>
  extends AnnotableClass<Literal<Literals>, Literals[number]>
{
  readonly literals: Readonly<Literals>
}

function getDefaultLiteralAST<Literals extends array_.NonEmptyReadonlyArray<AST.LiteralValue>>(
  literals: Literals
): AST.AST {
  return AST.isMembers(literals)
    ? AST.Union.make(AST.mapMembers(literals, (literal) => new AST.Literal(literal)))
    : new AST.Literal(literals[0])
}

function makeLiteralClass<Literals extends array_.NonEmptyReadonlyArray<AST.LiteralValue>>(
  literals: Literals,
  ast: AST.AST = getDefaultLiteralAST(literals)
): Literal<Literals> {
  return class LiteralClass extends make<Literals[number]>(ast) {
    static override annotations(annotations: Annotations.Schema<Literals[number]>): Literal<Literals> {
      return makeLiteralClass(this.literals, mergeSchemaAnnotations(this.ast, annotations))
    }
    static literals = [...literals] as Literals
  }
}

/**
 * @category constructors
 * @since 3.10.0
 */
export function Literal<Literals extends array_.NonEmptyReadonlyArray<AST.LiteralValue>>(
  ...literals: Literals
): Literal<Literals>
export function Literal(): Never
export function Literal<Literals extends ReadonlyArray<AST.LiteralValue>>(
  ...literals: Literals
): SchemaClass<Literals[number]>
export function Literal<Literals extends ReadonlyArray<AST.LiteralValue>>(
  ...literals: Literals
): SchemaClass<Literals[number]> | Never {
  return array_.isNonEmptyReadonlyArray(literals) ? makeLiteralClass(literals) : Never
}

/**
 * Creates a new `Schema` from a literal schema.
 *
 * @example
 * ```ts
 * import * as assert from "node:assert"
 * import { Either, Schema } from "effect"
 *
 * const schema = Schema.Literal("a", "b", "c").pipe(Schema.pickLiteral("a", "b"))
 *
 * assert.deepStrictEqual(Schema.decodeSync(schema)("a"), "a")
 * assert.deepStrictEqual(Schema.decodeSync(schema)("b"), "b")
 * assert.strictEqual(Either.isLeft(Schema.decodeUnknownEither(schema)("c")), true)
 * ```
 *
 * @category constructors
 * @since 3.10.0
 */
export const pickLiteral =
  <A extends AST.LiteralValue, L extends array_.NonEmptyReadonlyArray<A>>(...literals: L) =>
  <I, R>(_schema: Schema<A, I, R>): Literal<[...L]> => Literal(...literals)

/**
 * @category constructors
 * @since 3.10.0
 */
export const UniqueSymbolFromSelf = <S extends symbol>(symbol: S): SchemaClass<S> => make(new AST.UniqueSymbol(symbol))

/**
 * @category api interface
 * @since 3.10.0
 */
export interface Enums<A extends EnumsDefinition> extends AnnotableClass<Enums<A>, A[keyof A]> {
  readonly enums: A
}

/**
 * @since 3.10.0
 */
export type EnumsDefinition = { [x: string]: string | number }

const getDefaultEnumsAST = <A extends EnumsDefinition>(enums: A) =>
  new AST.Enums(
    Object.keys(enums).filter(
      (key) => typeof enums[enums[key]] !== "number"
    ).map((key) => [key, enums[key]])
  )

const makeEnumsClass = <A extends EnumsDefinition>(
  enums: A,
  ast: AST.AST = getDefaultEnumsAST(enums)
): Enums<A> => (class EnumsClass extends make<A[keyof A]>(ast) {
  static override annotations(annotations: Annotations.Schema<A[keyof A]>) {
    return makeEnumsClass(this.enums, mergeSchemaAnnotations(this.ast, annotations))
  }

  static enums = { ...enums }
})

/**
 * @category constructors
 * @since 3.10.0
 */
export const Enums = <A extends EnumsDefinition>(enums: A): Enums<A> => makeEnumsClass(enums)

type AppendType<
  Template extends string,
  Next
> = Next extends AST.LiteralValue ? `${Template}${Next}`
  : Next extends Schema<infer A extends AST.LiteralValue, infer _I, infer _R> ? `${Template}${A}`
  : never

type GetTemplateLiteralType<Params> = Params extends [...infer Init, infer Last] ?
  AppendType<GetTemplateLiteralType<Init>, Last>
  : ``

/**
 * @category API interface
 * @since 3.10.0
 */
export interface TemplateLiteral<A> extends SchemaClass<A> {}

type TemplateLiteralParameter = Schema.AnyNoContext | AST.LiteralValue

/**
 * @category template literal
 * @since 3.10.0
 */
export const TemplateLiteral = <Params extends array_.NonEmptyReadonlyArray<TemplateLiteralParameter>>(
  ...[head, ...tail]: Params
): TemplateLiteral<GetTemplateLiteralType<Params>> => {
  const spans: Array<AST.TemplateLiteralSpan> = []
  let h = ""
  let ts = tail

  if (isSchema(head)) {
    if (AST.isLiteral(head.ast)) {
      h = String(head.ast.literal)
    } else {
      ts = [head, ...ts]
    }
  } else {
    h = String(head)
  }

  for (let i = 0; i < ts.length; i++) {
    const item = ts[i]
    if (isSchema(item)) {
      if (i < ts.length - 1) {
        const next = ts[i + 1]
        if (isSchema(next)) {
          if (AST.isLiteral(next.ast)) {
            spans.push(new AST.TemplateLiteralSpan(item.ast, String(next.ast.literal)))
            i++
            continue
          }
        } else {
          spans.push(new AST.TemplateLiteralSpan(item.ast, String(next)))
          i++
          continue
        }
      }
      spans.push(new AST.TemplateLiteralSpan(item.ast, ""))
    } else {
      spans.push(new AST.TemplateLiteralSpan(new AST.Literal(item), ""))
    }
  }

  if (array_.isNonEmptyArray(spans)) {
    return make(new AST.TemplateLiteral(h, spans))
  } else {
    return make(new AST.TemplateLiteral("", [new AST.TemplateLiteralSpan(new AST.Literal(h), "")]))
  }
}

type TemplateLiteralParserParameters = Schema.Any | AST.LiteralValue

type GetTemplateLiteralParserType<Params> = Params extends [infer Head, ...infer Tail] ? readonly [
    Head extends Schema<infer A, infer _I, infer _R> ? A : Head,
    ...GetTemplateLiteralParserType<Tail>
  ]
  : []

type AppendEncoded<
  Template extends string,
  Next
> = Next extends AST.LiteralValue ? `${Template}${Next}`
  : Next extends Schema<infer _A, infer I extends AST.LiteralValue, infer _R> ? `${Template}${I}`
  : never

type GetTemplateLiteralParserEncoded<Params> = Params extends [...infer Init, infer Last] ?
  AppendEncoded<GetTemplateLiteralParserEncoded<Init>, Last>
  : ``

/**
 * @category API interface
 * @since 3.10.0
 */
export interface TemplateLiteralParser<Params extends array_.NonEmptyReadonlyArray<TemplateLiteralParserParameters>>
  extends
    Schema<
      GetTemplateLiteralParserType<Params>,
      GetTemplateLiteralParserEncoded<Params>,
      Schema.Context<Params[number]>
    >
{
  readonly params: Params
}

function getTemplateLiteralParserCoercedElement(encoded: Schema.Any, schema: Schema.Any): Schema.Any | undefined {
  const ast = encoded.ast
  switch (ast._tag) {
    case "Literal": {
      const literal = ast.literal
      if (!Predicate.isString(literal)) {
        const s = String(literal)
        return transform(Literal(s), schema, {
          strict: true,
          decode: () => literal,
          encode: () => s
        })
      }
      break
    }
    case "NumberKeyword":
      return compose(NumberFromString, schema)
    case "Union": {
      const members: Array<Schema.Any> = []
      let hasCoercions = false
      for (const member of ast.types) {
        const schema = make(member)
        const encoded = encodedSchema(schema)
        const coerced = getTemplateLiteralParserCoercedElement(encoded, schema)
        if (coerced) {
          hasCoercions = true
        }
        members.push(coerced ?? schema)
      }
      return hasCoercions ? compose(Union(...members), schema) : schema
    }
  }
}

/**
 * @category template literal
 * @since 3.10.0
 */
export const TemplateLiteralParser = <Params extends array_.NonEmptyReadonlyArray<TemplateLiteralParserParameters>>(
  ...params: Params
): TemplateLiteralParser<Params> => {
  const encodedSchemas: Array<Schema.Any> = []
  const elements: Array<Schema.Any> = []
  const schemas: Array<Schema.Any> = []
  let coerced = false
  for (let i = 0; i < params.length; i++) {
    const param = params[i]
    const schema = isSchema(param) ? param : Literal(param)
    schemas.push(schema)
    const encoded = encodedSchema(schema)
    encodedSchemas.push(encoded)
    const element = getTemplateLiteralParserCoercedElement(encoded, schema)
    if (element) {
      elements.push(element)
      coerced = true
    } else {
      elements.push(schema)
    }
  }
  const from = TemplateLiteral(...encodedSchemas as any)
  const re = AST.getTemplateLiteralCapturingRegExp(from.ast as AST.TemplateLiteral)
  let to = Tuple(...elements)
  if (coerced) {
    to = to.annotations({ [AST.AutoTitleAnnotationId]: format(Tuple(...schemas)) })
  }
  return class TemplateLiteralParserClass extends transformOrFail(from, to, {
    strict: false,
    decode: (i, _, ast) => {
      const match = re.exec(i)
      return match
        ? ParseResult.succeed(match.slice(1, params.length + 1))
        : ParseResult.fail(new ParseResult.Type(ast, i, `${re.source}: no match for ${JSON.stringify(i)}`))
    },
    encode: (tuple) => ParseResult.succeed(tuple.join(""))
  }) {
    static params = params.slice()
  } as any
}

const declareConstructor = <
  const TypeParameters extends ReadonlyArray<Schema.Any>,
  I,
  A
>(
  typeParameters: TypeParameters,
  options: {
    readonly decode: (
      ...typeParameters: {
        readonly [K in keyof TypeParameters]: Schema<
          Schema.Type<TypeParameters[K]>,
          Schema.Encoded<TypeParameters[K]>,
          never
        >
      }
    ) => (
      input: unknown,
      options: ParseOptions,
      ast: AST.Declaration
    ) => Effect.Effect<A, ParseResult.ParseIssue, never>
    readonly encode: (
      ...typeParameters: {
        readonly [K in keyof TypeParameters]: Schema<
          Schema.Type<TypeParameters[K]>,
          Schema.Encoded<TypeParameters[K]>,
          never
        >
      }
    ) => (
      input: unknown,
      options: ParseOptions,
      ast: AST.Declaration
    ) => Effect.Effect<I, ParseResult.ParseIssue, never>
  },
  annotations?: Annotations.Schema<A, TypeParameters>
): SchemaClass<A, I, Schema.Context<TypeParameters[number]>> =>
  makeDeclareClass(
    typeParameters,
    new AST.Declaration(
      typeParameters.map((tp) => tp.ast),
      (...typeParameters) => options.decode(...typeParameters.map(make) as any),
      (...typeParameters) => options.encode(...typeParameters.map(make) as any),
      toASTAnnotations(annotations)
    )
  )

const declarePrimitive = <A>(
  is: (input: unknown) => input is A,
  annotations?: Annotations.Schema<A>
): SchemaClass<A> => {
  const decodeUnknown = () => (input: unknown, _: ParseOptions, ast: AST.Declaration) =>
    is(input) ? ParseResult.succeed(input) : ParseResult.fail(new ParseResult.Type(ast, input))
  const encodeUnknown = decodeUnknown
  return makeDeclareClass([], new AST.Declaration([], decodeUnknown, encodeUnknown, toASTAnnotations(annotations)))
}

/**
 * @category api interface
 * @since 3.13.3
 */
export interface declare<
  A,
  I = A,
  P extends ReadonlyArray<Schema.All> = readonly [],
  R = Schema.Context<P[number]>
> extends AnnotableClass<declare<A, I, P, R>, A, I, R> {
  readonly typeParameters: Readonly<P>
}

/**
 * @category api interface
 * @since 3.13.3
 */
export interface AnnotableDeclare<
  Self extends declare<A, I, P, R>,
  A,
  I = A,
  P extends ReadonlyArray<Schema.All> = readonly [],
  R = Schema.Context<P[number]>
> extends declare<A, I, P, R> {
  annotations(annotations: Annotations.Schema<A>): Self
}

function makeDeclareClass<P extends ReadonlyArray<Schema.All>, A, I, R>(
  typeParameters: P,
  ast: AST.AST
): declare<A, I, P, R> {
  return class DeclareClass extends make<A, I, R>(ast) {
    static override annotations(annotations: Annotations.Schema<A>): declare<A, I, P, R> {
      return makeDeclareClass(this.typeParameters, mergeSchemaAnnotations(this.ast, annotations))
    }
    static typeParameters = [...typeParameters] as any as P
  }
}

/**
 * The constraint `R extends Schema.Context<P[number]>` enforces dependencies solely from `typeParameters`.
 * This ensures that when you call `Schema.to` or `Schema.from`, you receive a schema with a `never` context.
 *
 * @category constructors
 * @since 3.10.0
 */
export const declare: {
  /**
   * The constraint `R extends Schema.Context<P[number]>` enforces dependencies solely from `typeParameters`.
   * This ensures that when you call `Schema.to` or `Schema.from`, you receive a schema with a `never` context.
   *
   * @category constructors
   * @since 3.10.0
   */
  <A>(is: (input: unknown) => input is A, annotations?: Annotations.Schema<A>): declare<A>
  /**
   * The constraint `R extends Schema.Context<P[number]>` enforces dependencies solely from `typeParameters`.
   * This ensures that when you call `Schema.to` or `Schema.from`, you receive a schema with a `never` context.
   *
   * @category constructors
   * @since 3.10.0
   */
  <A, I, const P extends ReadonlyArray<Schema.All>>(
    typeParameters: P,
    options: {
      readonly decode: (
        ...typeParameters: { readonly [K in keyof P]: Schema<Schema.Type<P[K]>, Schema.Encoded<P[K]>, never> }
      ) => (
        input: unknown,
        options: ParseOptions,
        ast: AST.Declaration
      ) => Effect.Effect<A, ParseResult.ParseIssue, never>
      readonly encode: (
        ...typeParameters: { readonly [K in keyof P]: Schema<Schema.Type<P[K]>, Schema.Encoded<P[K]>, never> }
      ) => (
        input: unknown,
        options: ParseOptions,
        ast: AST.Declaration
      ) => Effect.Effect<I, ParseResult.ParseIssue, never>
    },
    annotations?: Annotations.Schema<A, { readonly [K in keyof P]: Schema.Type<P[K]> }>
  ): declare<A, I, P>
} = function() {
  if (Array.isArray(arguments[0])) {
    const typeParameters = arguments[0]
    const options = arguments[1]
    const annotations = arguments[2]
    return declareConstructor(typeParameters, options, annotations)
  }
  const is = arguments[0]
  const annotations = arguments[1]
  return declarePrimitive(is, annotations)
} as any

/**
 * @category schema id
 * @since 3.10.0
 */
export const BrandSchemaId: unique symbol = Symbol.for("effect/SchemaId/Brand")

/**
 * @category constructors
 * @since 3.10.0
 */
export const fromBrand = <C extends Brand<string | symbol>, A extends Brand.Unbranded<C>>(
  constructor: Brand.Constructor<C>,
  annotations?: Annotations.Filter<C, A>
) =>
<I, R>(self: Schema<A, I, R>): BrandSchema<A & C, I, R> => {
  const out = makeBrandClass(
    self,
    new AST.Refinement(
      self.ast,
      function predicate(a: A, _: ParseOptions, ast: AST.AST): option_.Option<ParseResult.ParseIssue> {
        const either = constructor.either(a)
        return either_.isLeft(either) ?
          option_.some(new ParseResult.Type(ast, a, either.left.map((v) => v.message).join(", "))) :
          option_.none()
      },
      toASTAnnotations({
        schemaId: BrandSchemaId,
        [BrandSchemaId]: { constructor },
        ...annotations
      })
    )
  )
  return out as any
}

/**
 * @category schema id
 * @since 3.10.0
 */
export const InstanceOfSchemaId: unique symbol = Symbol.for("effect/SchemaId/InstanceOf")

/**
 * @category api interface
 * @since 3.10.0
 */
export interface instanceOf<A> extends AnnotableDeclare<instanceOf<A>, A> {}

/**
 * @category constructors
 * @since 3.10.0
 */
export const instanceOf = <A extends abstract new(...args: any) => any>(
  constructor: A,
  annotations?: Annotations.Schema<InstanceType<A>>
): instanceOf<InstanceType<A>> =>
  declare(
    (u): u is InstanceType<A> => u instanceof constructor,
    {
      title: constructor.name,
      description: `an instance of ${constructor.name}`,
      pretty: (): pretty_.Pretty<InstanceType<A>> => String,
      schemaId: InstanceOfSchemaId,
      [InstanceOfSchemaId]: { constructor },
      ...annotations
    }
  )

/**
 * @category primitives
 * @since 3.10.0
 */
export class Undefined extends make<undefined>(AST.undefinedKeyword) {}

/**
 * @category primitives
 * @since 3.10.0
 */
export class Void extends make<void>(AST.voidKeyword) {}

/**
 * @category primitives
 * @since 3.10.0
 */
export class Null extends make<null>(AST.null) {}

/**
 * @category primitives
 * @since 3.10.0
 */
export class Never extends make<never>(AST.neverKeyword) {}

/**
 * @category primitives
 * @since 3.10.0
 */
export class Unknown extends make<unknown>(AST.unknownKeyword) {}

/**
 * @category primitives
 * @since 3.10.0
 */
export class Any extends make<any>(AST.anyKeyword) {}

/**
 * @category primitives
 * @since 3.10.0
 */
export class BigIntFromSelf extends make<bigint>(AST.bigIntKeyword) {}

/**
 * @category primitives
 * @since 3.10.0
 */
export class SymbolFromSelf extends make<symbol>(AST.symbolKeyword) {}

/** @ignore */
class String$ extends make<string>(AST.stringKeyword) {}

/** @ignore */
class Number$ extends make<number>(AST.numberKeyword) {}

/** @ignore */
class Boolean$ extends make<boolean>(AST.booleanKeyword) {}

/** @ignore */
class Object$ extends make<object>(AST.objectKeyword) {}

export {
  /**
   * @category primitives
   * @since 3.10.0
   */
  Boolean$ as Boolean,
  /**
   * @category primitives
   * @since 3.10.0
   */
  Number$ as Number,
  /**
   * @category primitives
   * @since 3.10.0
   */
  Object$ as Object,
  /**
   * @category primitives
   * @since 3.10.0
   */
  String$ as String
}

/**
 * @category api interface
 * @since 3.10.0
 */
export interface Union<Members extends ReadonlyArray<Schema.All>> extends
  AnnotableClass<
    Union<Members>,
    Schema.Type<Members[number]>,
    Schema.Encoded<Members[number]>,
    Schema.Context<Members[number]>
  >
{
  readonly members: Readonly<Members>
}

const getDefaultUnionAST = <Members extends AST.Members<Schema.All>>(members: Members): AST.AST =>
  AST.Union.make(members.map((m) => m.ast))

function makeUnionClass<Members extends AST.Members<Schema.All>>(
  members: Members,
  ast: AST.AST = getDefaultUnionAST(members)
): Union<Members> {
  return class UnionClass extends make<
    Schema.Type<Members[number]>,
    Schema.Encoded<Members[number]>,
    Schema.Context<Members[number]>
  >(ast) {
    static override annotations(annotations: Annotations.Schema<Schema.Type<Members[number]>>): Union<Members> {
      return makeUnionClass(this.members, mergeSchemaAnnotations(this.ast, annotations))
    }

    static members = [...members]
  }
}

/**
 * @category combinators
 * @since 3.10.0
 */
export function Union<Members extends AST.Members<Schema.All>>(...members: Members): Union<Members>
export function Union<Member extends Schema.All>(member: Member): Member
export function Union(): typeof Never
export function Union<Members extends ReadonlyArray<Schema.All>>(
  ...members: Members
): Schema<Schema.Type<Members[number]>, Schema.Encoded<Members[number]>, Schema.Context<Members[number]>>
export function Union<Members extends ReadonlyArray<Schema.All>>(
  ...members: Members
) {
  return AST.isMembers(members)
    ? makeUnionClass(members)
    : array_.isNonEmptyReadonlyArray(members)
    ? members[0]
    : Never
}

/**
 * @category api interface
 * @since 3.10.0
 */
export interface NullOr<S extends Schema.All> extends Union<[S, typeof Null]> {
  annotations(annotations: Annotations.Schema<Schema.Type<S> | null>): NullOr<S>
}

/**
 * @category combinators
 * @since 3.10.0
 */
export const NullOr = <S extends Schema.All>(self: S): NullOr<S> => Union(self, Null)

/**
 * @category api interface
 * @since 3.10.0
 */
export interface UndefinedOr<S extends Schema.All> extends Union<[S, typeof Undefined]> {
  annotations(annotations: Annotations.Schema<Schema.Type<S> | undefined>): UndefinedOr<S>
}

/**
 * @category combinators
 * @since 3.10.0
 */
export const UndefinedOr = <S extends Schema.All>(self: S): UndefinedOr<S> => Union(self, Undefined)

/**
 * @category api interface
 * @since 3.10.0
 */
export interface NullishOr<S extends Schema.All> extends Union<[S, typeof Null, typeof Undefined]> {
  annotations(annotations: Annotations.Schema<Schema.Type<S> | null | undefined>): NullishOr<S>
}

/**
 * @category combinators
 * @since 3.10.0
 */
export const NullishOr = <S extends Schema.All>(self: S): NullishOr<S> => Union(self, Null, Undefined)

/**
 * @category combinators
 * @since 3.10.0
 */
export const keyof = <A, I, R>(self: Schema<A, I, R>): SchemaClass<keyof A> => make<keyof A>(AST.keyof(self.ast))

/**
 * @since 3.10.0
 */
export declare namespace Element {
  /**
   * @since 3.10.0
   */
  export interface Annotations<A> extends Annotations.Doc<A> {
    readonly missingMessage?: AST.MissingMessageAnnotation
  }

  /**
   * @since 3.10.0
   */
  export type Token = "" | "?"
}

/**
 * @category API interface
 * @since 3.10.0
 */
export interface Element<S extends Schema.Any, Token extends Element.Token>
  extends Schema.Variance<Schema.Type<S>, Schema.Encoded<S>, Schema.Context<S>>
{
  readonly _Token: Token
  readonly ast: AST.OptionalType
  readonly from: S
  annotations(annotations: Element.Annotations<Schema.Type<S>>): Element<S, Token>
}

/**
 * @since 3.10.0
 */
export const element = <S extends Schema.Any>(self: S): Element<S, ""> =>
  new ElementImpl(new AST.OptionalType(self.ast, false), self)

/**
 * @since 3.10.0
 */
export const optionalElement = <S extends Schema.Any>(self: S): Element<S, "?"> =>
  new ElementImpl(new AST.OptionalType(self.ast, true), self)

class ElementImpl<S extends Schema.Any, Token extends Element.Token> implements Element<S, Token> {
  readonly [TypeId]!: Schema.Variance<Schema.Type<S>, Schema.Encoded<S>, Schema.Context<S>>[TypeId]
  readonly _Token!: Token
  constructor(
    readonly ast: AST.OptionalType,
    readonly from: S
  ) {}
  annotations(
    annotations: Annotations.Schema<Schema.Type<S>>
  ): ElementImpl<S, Token> {
    return new ElementImpl(
      new AST.OptionalType(
        this.ast.type,
        this.ast.isOptional,
        { ...this.ast.annotations, ...toASTAnnotations(annotations) }
      ),
      this.from
    )
  }
  toString() {
    return `${this.ast.type}${this.ast.isOptional ? "?" : ""}`
  }
}

/**
 * @since 3.10.0
 */
export declare namespace TupleType {
  type ElementsType<
    Elements,
    Out extends ReadonlyArray<any> = readonly []
  > = Elements extends readonly [infer Head, ...infer Tail] ?
    Head extends Element<infer T, "?"> ? ElementsType<Tail, readonly [...Out, Schema.Type<T>?]>
    : ElementsType<Tail, readonly [...Out, Schema.Type<Head>]>
    : Out

  type ElementsEncoded<
    Elements,
    Out extends ReadonlyArray<any> = readonly []
  > = Elements extends readonly [infer Head, ...infer Tail] ?
    Head extends Element<infer T, "?"> ? ElementsEncoded<Tail, readonly [...Out, Schema.Encoded<T>?]>
    : ElementsEncoded<Tail, readonly [...Out, Schema.Encoded<Head>]>
    : Out

  /**
   * @since 3.10.0
   */
  export type Elements = ReadonlyArray<Schema.Any | Element<Schema.Any, Element.Token>>

  /**
   * @since 3.10.0
   */
  export type Rest = ReadonlyArray<Schema.Any | Element<Schema.Any, "">>

  /**
   * @since 3.10.0
   */
  export type Type<Elements extends TupleType.Elements, Rest extends TupleType.Rest> = Rest extends
    [infer Head, ...infer Tail] ? Readonly<[
      ...ElementsType<Elements>,
      ...ReadonlyArray<Schema.Type<Head>>,
      ...{ readonly [K in keyof Tail]: Schema.Type<Tail[K]> }
    ]> :
    ElementsType<Elements>

  /**
   * @since 3.10.0
   */
  export type Encoded<Elements extends TupleType.Elements, Rest extends TupleType.Rest> = Rest extends
    [infer Head, ...infer Tail] ? Readonly<[
      ...ElementsEncoded<Elements>,
      ...ReadonlyArray<Schema.Encoded<Head>>,
      ...{ readonly [K in keyof Tail]: Schema.Encoded<Tail[K]> }
    ]> :
    ElementsEncoded<Elements>
}

/**
 * @category api interface
 * @since 3.10.0
 */
export interface TupleType<Elements extends TupleType.Elements, Rest extends TupleType.Rest> extends
  AnnotableClass<
    TupleType<Elements, Rest>,
    TupleType.Type<Elements, Rest>,
    TupleType.Encoded<Elements, Rest>,
    Schema.Context<Elements[number]> | Schema.Context<Rest[number]>
  >
{
  readonly elements: Readonly<Elements>
  readonly rest: Readonly<Rest>
}

const getDefaultTupleTypeAST = <Elements extends TupleType.Elements, Rest extends TupleType.Rest>(
  elements: Elements,
  rest: Rest
) =>
  new AST.TupleType(
    elements.map((el) => isSchema(el) ? new AST.OptionalType(el.ast, false) : el.ast),
    rest.map((el) => isSchema(el) ? new AST.Type(el.ast) : el.ast),
    true
  )

function makeTupleTypeClass<Elements extends TupleType.Elements, Rest extends TupleType.Rest>(
  elements: Elements,
  rest: Rest,
  ast: AST.AST = getDefaultTupleTypeAST(elements, rest)
) {
  return class TupleTypeClass extends make<
    TupleType.Type<Elements, Rest>,
    TupleType.Encoded<Elements, Rest>,
    Schema.Context<Elements[number]> | Schema.Context<Rest[number]>
  >(ast) {
    static override annotations(
      annotations: Annotations.Schema<TupleType.Type<Elements, Rest>>
    ): TupleType<Elements, Rest> {
      return makeTupleTypeClass(this.elements, this.rest, mergeSchemaAnnotations(this.ast, annotations))
    }

    static elements = [...elements] as any as Elements

    static rest = [...rest] as any as Rest
  }
}

/**
 * @category api interface
 * @since 3.10.0
 */
export interface Tuple<Elements extends TupleType.Elements> extends TupleType<Elements, []> {
  annotations(annotations: Annotations.Schema<TupleType.Type<Elements, []>>): Tuple<Elements>
}

/**
 * @category api interface
 * @since 3.13.3
 */
export interface Tuple2<Fst extends Schema.Any, Snd extends Schema.Any> extends
  AnnotableClass<
    Tuple2<Fst, Snd>,
    readonly [Schema.Type<Fst>, Schema.Type<Snd>],
    readonly [Schema.Encoded<Fst>, Schema.Encoded<Snd>],
    Schema.Context<Fst> | Schema.Context<Snd>
  >
{
  readonly elements: readonly [Fst, Snd]
  readonly rest: readonly []
}

/**
 * @category constructors
 * @since 3.10.0
 */
export function Tuple<
  const Elements extends TupleType.Elements,
  Rest extends array_.NonEmptyReadonlyArray<TupleType.Rest[number]>
>(elements: Elements, ...rest: Rest): TupleType<Elements, Rest>
export function Tuple<Fst extends Schema.Any, Snd extends Schema.Any>(fst: Fst, snd: Snd): Tuple2<Fst, Snd>
export function Tuple<Elements extends TupleType.Elements>(...elements: Elements): Tuple<Elements>
export function Tuple(...args: ReadonlyArray<any>): any {
  return Array.isArray(args[0])
    ? makeTupleTypeClass(args[0], args.slice(1))
    : makeTupleTypeClass(args, [])
}

/**
 * @category api interface
 * @since 3.10.0
 */
export interface Array$<Value extends Schema.Any> extends TupleType<[], [Value]> {
  readonly value: Value
  annotations(annotations: Annotations.Schema<TupleType.Type<[], [Value]>>): Array$<Value>
}

function makeArrayClass<Value extends Schema.Any>(
  value: Value,
  ast?: AST.AST
): Array$<Value> {
  return class ArrayClass extends makeTupleTypeClass<[], [Value]>([], [value], ast) {
    static override annotations(annotations: Annotations.Schema<TupleType.Type<[], [Value]>>) {
      return makeArrayClass(this.value, mergeSchemaAnnotations(this.ast, annotations))
    }

    static value = value
  }
}

const Array$ = <Value extends Schema.Any>(value: Value): Array$<Value> => makeArrayClass(value)

export {
  /**
   * @category constructors
   * @since 3.10.0
   */
  Array$ as Array
}

/**
 * @category api interface
 * @since 3.10.0
 */
export interface NonEmptyArray<Value extends Schema.Any> extends
  AnnotableClass<
    NonEmptyArray<Value>,
    array_.NonEmptyReadonlyArray<Schema.Type<Value>>,
    array_.NonEmptyReadonlyArray<Schema.Encoded<Value>>,
    Schema.Context<Value>
  >
{
  readonly elements: readonly [Value]
  readonly rest: readonly [Value]
  readonly value: Value
}

function makeNonEmptyArrayClass<Value extends Schema.Any>(
  value: Value,
  ast?: AST.AST
) {
  return class NonEmptyArrayClass extends makeTupleTypeClass<[Value], [Value]>([value], [value], ast) {
    static override annotations(annotations: Annotations.Schema<TupleType.Type<[Value], [Value]>>) {
      return makeNonEmptyArrayClass(this.value, mergeSchemaAnnotations(this.ast, annotations))
    }

    static value = value
  }
}

/**
 * @category constructors
 * @since 3.10.0
 */
export const NonEmptyArray = <Value extends Schema.Any>(value: Value): NonEmptyArray<Value> =>
  makeNonEmptyArrayClass(value) as any

/**
 * @category api interface
 * @since 3.10.0
 */
export interface ArrayEnsure<Value extends Schema.Any>
  extends transform<Union<[Value, Array$<Value>]>, Array$<SchemaClass<Schema.Type<Value>>>>
{}

/**
 * @category constructors
 * @since 3.10.0
 */
export function ArrayEnsure<Value extends Schema.Any>(value: Value): ArrayEnsure<Value> {
  return transform(Union(value, Array$(value)), Array$(typeSchema(asSchema(value))), {
    strict: true,
    decode: (i) => array_.ensure(i),
    encode: (a) => a.length === 1 ? a[0] : a
  })
}

/**
 * @category api interface
 * @since 3.10.0
 */
export interface NonEmptyArrayEnsure<Value extends Schema.Any>
  extends transform<Union<[Value, NonEmptyArray<Value>]>, NonEmptyArray<SchemaClass<Schema.Type<Value>>>>
{}

/**
 * @category constructors
 * @since 3.10.0
 */
export function NonEmptyArrayEnsure<Value extends Schema.Any>(value: Value): NonEmptyArrayEnsure<Value> {
  return transform(Union(value, NonEmptyArray(value)), NonEmptyArray(typeSchema(asSchema(value))), {
    strict: true,
    decode: (i) => array_.isNonEmptyReadonlyArray(i) ? i : array_.of(i),
    encode: (a) => a.length === 1 ? a[0] : a
  })
}

/**
 * @since 3.10.0
 */
export declare namespace PropertySignature {
  /**
   * @since 3.10.0
   */
  export type Token = "?:" | ":"

  /**
   * @since 3.10.0
   */
  export type Any<Key extends PropertyKey = PropertyKey> = PropertySignature<
    Token,
    any,
    Key,
    Token,
    any,
    boolean,
    unknown
  >

  /**
   * @since 3.10.0
   */
  export type All<Key extends PropertyKey = PropertyKey> =
    | Any<Key>
    | PropertySignature<Token, never, Key, Token, any, boolean, unknown>
    | PropertySignature<Token, any, Key, Token, never, boolean, unknown>
    | PropertySignature<Token, never, Key, Token, never, boolean, unknown>

  /**
   * @since 3.10.0
   */
  export type AST =
    | PropertySignatureDeclaration
    | PropertySignatureTransformation

  /**
   * @since 3.10.0
   */
  export interface Annotations<A> extends Annotations.Doc<A> {
    readonly missingMessage?: AST.MissingMessageAnnotation
  }
}

const formatPropertySignatureToken = (isOptional: boolean): string => isOptional ? "\"?:\"" : "\":\""

/**
 * @category PropertySignature
 * @since 3.10.0
 */
export class PropertySignatureDeclaration extends AST.OptionalType {
  /**
   * @since 3.10.0
   */
  readonly _tag = "PropertySignatureDeclaration"
  constructor(
    type: AST.AST,
    isOptional: boolean,
    readonly isReadonly: boolean,
    annotations: AST.Annotations,
    readonly defaultValue: (() => unknown) | undefined
  ) {
    super(type, isOptional, annotations)
  }
  /**
   * @since 3.10.0
   */
  toString() {
    const token = formatPropertySignatureToken(this.isOptional)
    const type = String(this.type)
    return `PropertySignature<${token}, ${type}, never, ${token}, ${type}>`
  }
}

/**
 * @category PropertySignature
 * @since 3.10.0
 */
export class FromPropertySignature extends AST.OptionalType {
  constructor(
    type: AST.AST,
    isOptional: boolean,
    readonly isReadonly: boolean,
    annotations: AST.Annotations,
    readonly fromKey?: PropertyKey | undefined
  ) {
    super(type, isOptional, annotations)
  }
}

/**
 * @category PropertySignature
 * @since 3.10.0
 */
export class ToPropertySignature extends AST.OptionalType {
  constructor(
    type: AST.AST,
    isOptional: boolean,
    readonly isReadonly: boolean,
    annotations: AST.Annotations,
    readonly defaultValue: (() => unknown) | undefined
  ) {
    super(type, isOptional, annotations)
  }
}

const formatPropertyKey = (p: PropertyKey | undefined): string => {
  if (p === undefined) {
    return "never"
  }
  if (Predicate.isString(p)) {
    return JSON.stringify(p)
  }
  return String(p)
}

/**
 * @category PropertySignature
 * @since 3.10.0
 */
export class PropertySignatureTransformation {
  /**
   * @since 3.10.0
   */
  readonly _tag = "PropertySignatureTransformation"
  constructor(
    readonly from: FromPropertySignature,
    readonly to: ToPropertySignature,
    readonly decode: AST.PropertySignatureTransformation["decode"],
    readonly encode: AST.PropertySignatureTransformation["encode"]
  ) {}
  /**
   * @since 3.10.0
   */
  toString() {
    return `PropertySignature<${formatPropertySignatureToken(this.to.isOptional)}, ${this.to.type}, ${
      formatPropertyKey(this.from.fromKey)
    }, ${formatPropertySignatureToken(this.from.isOptional)}, ${this.from.type}>`
  }
}

const mergeSignatureAnnotations = (
  ast: PropertySignature.AST,
  annotations: AST.Annotations
): PropertySignature.AST => {
  switch (ast._tag) {
    case "PropertySignatureDeclaration": {
      return new PropertySignatureDeclaration(
        ast.type,
        ast.isOptional,
        ast.isReadonly,
        { ...ast.annotations, ...annotations },
        ast.defaultValue
      )
    }
    case "PropertySignatureTransformation": {
      return new PropertySignatureTransformation(
        ast.from,
        new ToPropertySignature(ast.to.type, ast.to.isOptional, ast.to.isReadonly, {
          ...ast.to.annotations,
          ...annotations
        }, ast.to.defaultValue),
        ast.decode,
        ast.encode
      )
    }
  }
}

/**
 * @since 3.10.0
 * @category symbol
 */
export const PropertySignatureTypeId: unique symbol = Symbol.for("effect/PropertySignature")

/**
 * @since 3.10.0
 * @category symbol
 */
export type PropertySignatureTypeId = typeof PropertySignatureTypeId

/**
 * @since 3.10.0
 * @category guards
 */
export const isPropertySignature = (u: unknown): u is PropertySignature.All =>
  Predicate.hasProperty(u, PropertySignatureTypeId)

/**
 * @category PropertySignature
 * @since 3.10.0
 */
export interface PropertySignature<
  TypeToken extends PropertySignature.Token,
  Type,
  Key extends PropertyKey,
  EncodedToken extends PropertySignature.Token,
  Encoded,
  HasDefault extends boolean = false,
  R = never
> extends Schema.Variance<Type, Encoded, R>, Pipeable {
  readonly [PropertySignatureTypeId]: null
  readonly _TypeToken: TypeToken
  readonly _EncodedToken: EncodedToken
  readonly _HasDefault: HasDefault
  readonly _Key: Key
  readonly ast: PropertySignature.AST

  annotations(
    annotations: PropertySignature.Annotations<Type>
  ): PropertySignature<TypeToken, Type, Key, EncodedToken, Encoded, HasDefault, R>
}

class PropertySignatureImpl<
  TypeToken extends PropertySignature.Token,
  Type,
  Key extends PropertyKey,
  EncodedToken extends PropertySignature.Token,
  Encoded,
  HasDefault extends boolean = false,
  R = never
> implements PropertySignature<TypeToken, Type, Key, EncodedToken, Encoded, HasDefault, R> {
  readonly [TypeId]!: Schema.Variance<Type, Encoded, R>[TypeId]
  readonly [PropertySignatureTypeId] = null
  readonly _TypeToken!: TypeToken
  readonly _Key!: Key
  readonly _EncodedToken!: EncodedToken
  readonly _HasDefault!: HasDefault

  constructor(
    readonly ast: PropertySignature.AST
  ) {}

  pipe() {
    return pipeArguments(this, arguments)
  }

  annotations(
    annotations: PropertySignature.Annotations<Type>
  ): PropertySignature<TypeToken, Type, Key, EncodedToken, Encoded, HasDefault, R> {
    return new PropertySignatureImpl(mergeSignatureAnnotations(this.ast, toASTAnnotations(annotations)))
  }

  toString() {
    return String(this.ast)
  }
}

/**
 * @category PropertySignature
 * @since 3.10.0
 */
export const makePropertySignature = <
  TypeToken extends PropertySignature.Token,
  Type,
  Key extends PropertyKey,
  EncodedToken extends PropertySignature.Token,
  Encoded,
  HasDefault extends boolean = false,
  R = never
>(ast: PropertySignature.AST) =>
  new PropertySignatureImpl<TypeToken, Type, Key, EncodedToken, Encoded, HasDefault, R>(ast)

class PropertySignatureWithFromImpl<
  From extends Schema.All,
  TypeToken extends PropertySignature.Token,
  Type,
  Key extends PropertyKey,
  EncodedToken extends PropertySignature.Token,
  Encoded,
  HasDefault extends boolean = false,
  R = never
> extends PropertySignatureImpl<TypeToken, Type, Key, EncodedToken, Encoded, HasDefault, R> {
  constructor(ast: PropertySignature.AST, readonly from: From) {
    super(ast)
  }
  annotations(
    annotations: PropertySignature.Annotations<Type>
  ): PropertySignatureWithFromImpl<From, TypeToken, Type, Key, EncodedToken, Encoded, HasDefault, R> {
    return new PropertySignatureWithFromImpl(
      mergeSignatureAnnotations(this.ast, toASTAnnotations(annotations)),
      this.from
    )
  }
}

/**
 * @category API interface
 * @since 1.0.0
 */
export interface propertySignature<S extends Schema.All>
  extends PropertySignature<":", Schema.Type<S>, never, ":", Schema.Encoded<S>, false, Schema.Context<S>>
{
  readonly from: S
  annotations(annotations: PropertySignature.Annotations<Schema.Type<S>>): propertySignature<S>
}

/**
 * Lifts a `Schema` into a `PropertySignature`.
 *
 * @category PropertySignature
 * @since 3.10.0
 */
export const propertySignature = <S extends Schema.All>(
  self: S
): propertySignature<S> =>
  new PropertySignatureWithFromImpl(
    new PropertySignatureDeclaration(self.ast, false, true, {}, undefined),
    self
  )

/**
 * Enhances a property signature with a default constructor value.
 *
 * @category PropertySignature
 * @since 3.10.0
 */
export const withConstructorDefault: {
  /**
   * Enhances a property signature with a default constructor value.
   *
   * @category PropertySignature
   * @since 3.10.0
   */
  <Type>(defaultValue: () => Types.NoInfer<Type>): <
    TypeToken extends PropertySignature.Token,
    Key extends PropertyKey,
    EncodedToken extends PropertySignature.Token,
    Encoded,
    R
  >(
    self: PropertySignature<TypeToken, Type, Key, EncodedToken, Encoded, boolean, R>
  ) => PropertySignature<TypeToken, Type, Key, EncodedToken, Encoded, true, R>
  /**
   * Enhances a property signature with a default constructor value.
   *
   * @category PropertySignature
   * @since 3.10.0
   */
  <
    TypeToken extends PropertySignature.Token,
    Type,
    Key extends PropertyKey,
    EncodedToken extends PropertySignature.Token,
    Encoded,
    R
  >(
    self: PropertySignature<TypeToken, Type, Key, EncodedToken, Encoded, boolean, R>,
    defaultValue: () => Types.NoInfer<Type>
  ): PropertySignature<TypeToken, Type, Key, EncodedToken, Encoded, true, R>
} = dual(2, <
  TypeToken extends PropertySignature.Token,
  Type,
  Key extends PropertyKey,
  EncodedToken extends PropertySignature.Token,
  Encoded,
  R
>(
  self: PropertySignature<TypeToken, Type, Key, EncodedToken, Encoded, boolean, R>,
  defaultValue: () => Types.NoInfer<Type>
): PropertySignature<TypeToken, Type, Key, EncodedToken, Encoded, true, R> => {
  const ast = self.ast
  switch (ast._tag) {
    case "PropertySignatureDeclaration":
      return makePropertySignature(
        new PropertySignatureDeclaration(ast.type, ast.isOptional, ast.isReadonly, ast.annotations, defaultValue)
      )
    case "PropertySignatureTransformation":
      return makePropertySignature(
        new PropertySignatureTransformation(
          ast.from,
          new ToPropertySignature(ast.to.type, ast.to.isOptional, ast.to.isReadonly, ast.to.annotations, defaultValue),
          ast.decode,
          ast.encode
        )
      )
  }
})

const applyDefaultValue = <A>(o: option_.Option<A>, defaultValue: () => A) =>
  option_.match(o, {
    onNone: () => option_.some(defaultValue()),
    onSome: (value) => option_.some(value === undefined ? defaultValue() : value)
  })

const pruneUndefined = (ast: AST.AST): AST.AST | undefined =>
  AST.pruneUndefined(ast, pruneUndefined, (ast) => {
    const pruned = pruneUndefined(ast.to)
    if (pruned) {
      return new AST.Transformation(ast.from, pruned, ast.transformation)
    }
  })

/**
 * Enhances a property signature with a default decoding value.
 *
 * @category PropertySignature
 * @since 3.10.0
 */
export const withDecodingDefault: {
  /**
   * Enhances a property signature with a default decoding value.
   *
   * @category PropertySignature
   * @since 3.10.0
   */
  <Type>(defaultValue: () => Types.NoInfer<Exclude<Type, undefined>>): <
    Key extends PropertyKey,
    Encoded,
    R
  >(
    self: PropertySignature<"?:", Type, Key, "?:", Encoded, false, R>
  ) => PropertySignature<":", Exclude<Type, undefined>, Key, "?:", Encoded, false, R>
  /**
   * Enhances a property signature with a default decoding value.
   *
   * @category PropertySignature
   * @since 3.10.0
   */
  <
    Type,
    Key extends PropertyKey,
    Encoded,
    R
  >(
    self: PropertySignature<"?:", Type, Key, "?:", Encoded, false, R>,
    defaultValue: () => Types.NoInfer<Exclude<Type, undefined>>
  ): PropertySignature<":", Exclude<Type, undefined>, Key, "?:", Encoded, false, R>
} = dual(2, <
  Type,
  Key extends PropertyKey,
  Encoded,
  R
>(
  self: PropertySignature<"?:", Type, Key, "?:", Encoded, false, R>,
  defaultValue: () => Types.NoInfer<Exclude<Type, undefined>>
): PropertySignature<":", Exclude<Type, undefined>, Key, "?:", Encoded, false, R> => {
  const ast = self.ast
  switch (ast._tag) {
    case "PropertySignatureDeclaration": {
      const to = AST.typeAST(ast.type)
      return makePropertySignature(
        new PropertySignatureTransformation(
          new FromPropertySignature(ast.type, ast.isOptional, ast.isReadonly, ast.annotations),
          new ToPropertySignature(pruneUndefined(to) ?? to, false, true, {}, ast.defaultValue),
          (o) => applyDefaultValue(o, defaultValue),
          identity
        )
      )
    }
    case "PropertySignatureTransformation": {
      const to = ast.to.type
      return makePropertySignature(
        new PropertySignatureTransformation(
          ast.from,
          new ToPropertySignature(
            pruneUndefined(to) ?? to,
            false,
            ast.to.isReadonly,
            ast.to.annotations,
            ast.to.defaultValue
          ),
          (o) => applyDefaultValue(ast.decode(o), defaultValue),
          ast.encode
        )
      )
    }
  }
})

/**
 * Enhances a property signature with a default decoding value and a default constructor value.
 *
 * @category PropertySignature
 * @since 3.10.0
 */
export const withDefaults: {
  /**
   * Enhances a property signature with a default decoding value and a default constructor value.
   *
   * @category PropertySignature
   * @since 3.10.0
   */
  <Type>(
    defaults: {
      constructor: () => Types.NoInfer<Exclude<Type, undefined>>
      decoding: () => Types.NoInfer<Exclude<Type, undefined>>
    }
  ): <
    Key extends PropertyKey,
    Encoded,
    R
  >(
    self: PropertySignature<"?:", Type, Key, "?:", Encoded, boolean, R>
  ) => PropertySignature<":", Exclude<Type, undefined>, Key, "?:", Encoded, true, R>
  /**
   * Enhances a property signature with a default decoding value and a default constructor value.
   *
   * @category PropertySignature
   * @since 3.10.0
   */
  <
    Type,
    Key extends PropertyKey,
    Encoded,
    R
  >(
    self: PropertySignature<"?:", Type, Key, "?:", Encoded, boolean, R>,
    defaults: {
      constructor: () => Types.NoInfer<Exclude<Type, undefined>>
      decoding: () => Types.NoInfer<Exclude<Type, undefined>>
    }
  ): PropertySignature<":", Exclude<Type, undefined>, Key, "?:", Encoded, true, R>
} = dual(2, <
  Type,
  Key extends PropertyKey,
  Encoded,
  R
>(
  self: PropertySignature<"?:", Type, Key, "?:", Encoded, false, R>,
  defaults: {
    constructor: () => Types.NoInfer<Exclude<Type, undefined>>
    decoding: () => Types.NoInfer<Exclude<Type, undefined>>
  }
): PropertySignature<":", Exclude<Type, undefined>, Key, "?:", Encoded, true, R> =>
  self.pipe(withDecodingDefault(defaults.decoding), withConstructorDefault(defaults.constructor)))

/**
 * Enhances a property signature by specifying a different key for it in the Encoded type.
 *
 * @category PropertySignature
 * @since 3.10.0
 */
export const fromKey: {
  /**
   * Enhances a property signature by specifying a different key for it in the Encoded type.
   *
   * @category PropertySignature
   * @since 3.10.0
   */
  <Key extends PropertyKey>(key: Key): <
    TypeToken extends PropertySignature.Token,
    Type,
    EncodedToken extends PropertySignature.Token,
    Encoded,
    HasDefault extends boolean,
    R
  >(
    self: PropertySignature<TypeToken, Type, PropertyKey, EncodedToken, Encoded, HasDefault, R>
  ) => PropertySignature<TypeToken, Type, Key, EncodedToken, Encoded, HasDefault, R>
  /**
   * Enhances a property signature by specifying a different key for it in the Encoded type.
   *
   * @category PropertySignature
   * @since 3.10.0
   */
  <
    Type,
    TypeToken extends PropertySignature.Token,
    Encoded,
    EncodedToken extends PropertySignature.Token,
    HasDefault extends boolean,
    R,
    Key extends PropertyKey
  >(
    self: PropertySignature<TypeToken, Type, PropertyKey, EncodedToken, Encoded, HasDefault, R>,
    key: Key
  ): PropertySignature<TypeToken, Type, Key, EncodedToken, Encoded, HasDefault, R>
} = dual(2, <
  Type,
  TypeToken extends PropertySignature.Token,
  Encoded,
  EncodedToken extends PropertySignature.Token,
  HasDefault extends boolean,
  R,
  Key extends PropertyKey
>(
  self: PropertySignature<TypeToken, Type, PropertyKey, EncodedToken, Encoded, HasDefault, R>,
  key: Key
): PropertySignature<TypeToken, Type, Key, EncodedToken, Encoded, HasDefault, R> => {
  const ast = self.ast
  switch (ast._tag) {
    case "PropertySignatureDeclaration": {
      return makePropertySignature(
        new PropertySignatureTransformation(
          new FromPropertySignature(
            ast.type,
            ast.isOptional,
            ast.isReadonly,
            ast.annotations,
            key
          ),
          new ToPropertySignature(AST.typeAST(ast.type), ast.isOptional, ast.isReadonly, {}, ast.defaultValue),
          identity,
          identity
        )
      )
    }
    case "PropertySignatureTransformation":
      return makePropertySignature(
        new PropertySignatureTransformation(
          new FromPropertySignature(
            ast.from.type,
            ast.from.isOptional,
            ast.from.isReadonly,
            ast.from.annotations,
            key
          ),
          ast.to,
          ast.decode,
          ast.encode
        )
      )
  }
})

/**
 * Converts an optional property to a required one through a transformation `Option -> Type`.
 *
 * - `decode`: `none` as argument means the value is missing in the input.
 * - `encode`: `none` as return value means the value will be missing in the output.
 *
 * @category PropertySignature
 * @since 3.10.0
 */
export const optionalToRequired = <FA, FI, FR, TA, TI, TR>(
  from: Schema<FA, FI, FR>,
  to: Schema<TA, TI, TR>,
  options: {
    readonly decode: (o: option_.Option<FA>) => TI
    readonly encode: (ti: TI) => option_.Option<FA>
  }
): PropertySignature<":", TA, never, "?:", FI, false, FR | TR> =>
  makePropertySignature(
    new PropertySignatureTransformation(
      new FromPropertySignature(from.ast, true, true, {}, undefined),
      new ToPropertySignature(to.ast, false, true, {}, undefined),
      (o) => option_.some(options.decode(o)),
      option_.flatMap(options.encode)
    )
  )

/**
 * Converts an optional property to a required one through a transformation `Type -> Option`.
 *
 * - `decode`: `none` as return value means the value will be missing in the output.
 * - `encode`: `none` as argument means the value is missing in the input.
 *
 * @category PropertySignature
 * @since 3.10.0
 */
export const requiredToOptional = <FA, FI, FR, TA, TI, TR>(
  from: Schema<FA, FI, FR>,
  to: Schema<TA, TI, TR>,
  options: {
    readonly decode: (fa: FA) => option_.Option<TI>
    readonly encode: (o: option_.Option<TI>) => FA
  }
): PropertySignature<"?:", TA, never, ":", FI, false, FR | TR> =>
  makePropertySignature(
    new PropertySignatureTransformation(
      new FromPropertySignature(from.ast, false, true, {}, undefined),
      new ToPropertySignature(to.ast, true, true, {}, undefined),
      option_.flatMap(options.decode),
      (o) => option_.some(options.encode(o))
    )
  )

/**
 * Converts an optional property to another optional property through a transformation `Option -> Option`.
 *
 * - `decode`:
 *   - `none` as argument means the value is missing in the input.
 *   - `none` as return value means the value will be missing in the output.
 * - `encode`:
 *   - `none` as argument means the value is missing in the input.
 *   - `none` as return value means the value will be missing in the output.
 *
 * @category PropertySignature
 * @since 3.10.0
 */
export const optionalToOptional = <FA, FI, FR, TA, TI, TR>(
  from: Schema<FA, FI, FR>,
  to: Schema<TA, TI, TR>,
  options: {
    readonly decode: (o: option_.Option<FA>) => option_.Option<TI>
    readonly encode: (o: option_.Option<TI>) => option_.Option<FA>
  }
): PropertySignature<"?:", TA, never, "?:", FI, false, FR | TR> =>
  makePropertySignature(
    new PropertySignatureTransformation(
      new FromPropertySignature(from.ast, true, true, {}, undefined),
      new ToPropertySignature(to.ast, true, true, {}, undefined),
      options.decode,
      options.encode
    )
  )

/**
 * @since 3.10.0
 */
export type OptionalOptions<A> = {
  readonly default?: never
  readonly as?: never
  readonly exact?: true
  readonly nullable?: true
} | {
  readonly default: LazyArg<A>
  readonly as?: never
  readonly exact?: true
  readonly nullable?: true
} | {
  readonly as: "Option"
  readonly default?: never
  readonly exact?: never
  readonly nullable?: never
  readonly onNoneEncoding?: LazyArg<option_.Option<undefined>>
} | {
  readonly as: "Option"
  readonly default?: never
  readonly exact?: never
  readonly nullable: true
  readonly onNoneEncoding?: LazyArg<option_.Option<null | undefined>>
} | {
  readonly as: "Option"
  readonly default?: never
  readonly exact: true
  readonly nullable?: never
  readonly onNoneEncoding?: never
} | {
  readonly as: "Option"
  readonly default?: never
  readonly exact: true
  readonly nullable: true
  readonly onNoneEncoding?: LazyArg<option_.Option<null>>
} | undefined

/**
 * @category api interface
 * @since 3.10.0
 */
export interface optional<S extends Schema.All> extends
  PropertySignature<
    "?:",
    Schema.Type<S> | undefined,
    never,
    "?:",
    Schema.Encoded<S> | undefined,
    false,
    Schema.Context<S>
  >
{
  readonly from: S
  annotations(annotations: PropertySignature.Annotations<Schema.Type<S> | undefined>): optional<S>
}

/**
 * @category api interface
 * @since 3.10.0
 */
export interface optionalWith<S extends Schema.All, Options> extends
  PropertySignature<
    Types.Has<Options, "as" | "default"> extends true ? ":" : "?:",
    | (Types.Has<Options, "as"> extends true ? option_.Option<Schema.Type<S>> : Schema.Type<S>)
    | (Types.Has<Options, "as" | "default" | "exact"> extends true ? never : undefined),
    never,
    "?:",
    | Schema.Encoded<S>
    | (Types.Has<Options, "nullable"> extends true ? null : never)
    | (Types.Has<Options, "exact"> extends true ? never : undefined),
    Types.Has<Options, "default">,
    Schema.Context<S>
  >
{
  readonly from: S
  annotations(
    annotations: PropertySignature.Annotations<
      | (Types.Has<Options, "as"> extends true ? option_.Option<Schema.Type<S>> : Schema.Type<S>)
      | (Types.Has<Options, "as" | "default" | "exact"> extends true ? never : undefined)
    >
  ): optionalWith<S, Options>
}

const optionalPropertySignatureAST = <A, I, R>(
  self: Schema<A, I, R>,
  options?: {
    readonly exact?: true
    readonly default?: () => A
    readonly nullable?: true
    readonly as?: "Option"
    readonly onNoneEncoding?: () => option_.Option<never>
  }
): PropertySignature.AST => {
  const isExact = options?.exact
  const defaultValue = options?.default
  const isNullable = options?.nullable
  const asOption = options?.as == "Option"
  const asOptionEncode = options?.onNoneEncoding ? option_.orElse(options.onNoneEncoding) : identity

  if (isExact) {
    if (defaultValue) {
      if (isNullable) {
        return withConstructorDefault(
          optionalToRequired(
            NullOr(self),
            typeSchema(self),
            {
              decode: option_.match({ onNone: defaultValue, onSome: (a) => a === null ? defaultValue() : a }),
              encode: option_.some
            }
          ),
          defaultValue
        ).ast
      } else {
        return withConstructorDefault(
          optionalToRequired(
            self,
            typeSchema(self),
            { decode: option_.match({ onNone: defaultValue, onSome: identity }), encode: option_.some }
          ),
          defaultValue
        ).ast
      }
    } else if (asOption) {
      if (isNullable) {
        return optionalToRequired(
          NullOr(self),
          OptionFromSelf(typeSchema(self)),
          {
            decode: option_.filter(Predicate.isNotNull<A | null>),
            encode: asOptionEncode
          }
        ).ast
      } else {
        return optionalToRequired(
          self,
          OptionFromSelf(typeSchema(self)),
          { decode: identity, encode: identity }
        ).ast
      }
    } else {
      if (isNullable) {
        return optionalToOptional(
          NullOr(self),
          typeSchema(self),
          { decode: option_.filter(Predicate.isNotNull<A | null>), encode: identity }
        ).ast
      } else {
        return new PropertySignatureDeclaration(self.ast, true, true, {}, undefined)
      }
    }
  } else {
    if (defaultValue) {
      if (isNullable) {
        return withConstructorDefault(
          optionalToRequired(
            NullishOr(self),
            typeSchema(self),
            {
              decode: option_.match({ onNone: defaultValue, onSome: (a) => (a == null ? defaultValue() : a) }),
              encode: option_.some
            }
          ),
          defaultValue
        ).ast
      } else {
        return withConstructorDefault(
          optionalToRequired(
            UndefinedOr(self),
            typeSchema(self),
            {
              decode: option_.match({ onNone: defaultValue, onSome: (a) => (a === undefined ? defaultValue() : a) }),
              encode: option_.some
            }
          ),
          defaultValue
        ).ast
      }
    } else if (asOption) {
      if (isNullable) {
        return optionalToRequired(
          NullishOr(self),
          OptionFromSelf(typeSchema(self)),
          {
            decode: option_.filter<A | null | undefined, A>((a): a is A => a != null),
            encode: asOptionEncode
          }
        ).ast
      } else {
        return optionalToRequired(
          UndefinedOr(self),
          OptionFromSelf(typeSchema(self)),
          {
            decode: option_.filter(Predicate.isNotUndefined<A | undefined>),
            encode: asOptionEncode
          }
        ).ast
      }
    } else {
      if (isNullable) {
        return optionalToOptional(
          NullishOr(self),
          UndefinedOr(typeSchema(self)),
          { decode: option_.filter(Predicate.isNotNull<A | null | undefined>), encode: identity }
        ).ast
      } else {
        return new PropertySignatureDeclaration(UndefinedOr(self).ast, true, true, {}, undefined)
      }
    }
  }
}

/**
 * @category PropertySignature
 * @since 3.10.0
 */
export const optional = <S extends Schema.All>(self: S): optional<S> => {
  const ast = self.ast === AST.undefinedKeyword || self.ast === AST.neverKeyword
    ? AST.undefinedKeyword
    : UndefinedOr(self).ast
  return new PropertySignatureWithFromImpl(new PropertySignatureDeclaration(ast, true, true, {}, undefined), self)
}

/**
 * @category PropertySignature
 * @since 3.10.0
 */
export const optionalWith: {
  /**
   * @category PropertySignature
   * @since 3.10.0
   */
  <S extends Schema.All, Options extends OptionalOptions<Schema.Type<S>>>(options: Options): (self: S) => optionalWith<S, Options>
  /**
   * @category PropertySignature
   * @since 3.10.0
   */
  <S extends Schema.All, Options extends OptionalOptions<Schema.Type<S>>>(self: S, options: Options): optionalWith<S, Options>
} = dual((args) => isSchema(args[0]), (self, options) => {
  return new PropertySignatureWithFromImpl(optionalPropertySignatureAST(self, options), self)
})

/**
 * @since 3.10.0
 */
export declare namespace Struct {
  /**
   * Useful for creating a type that can be used to add custom constraints to the fields of a struct.
   *
   * ```ts
   * import { Schema } from "effect"
   *
   * const f = <Fields extends Record<"a" | "b", Schema.Struct.Field>>(
   *   schema: Schema.Struct<Fields>
   * ) => {
   *   return schema.omit("a")
   * }
   *
   * //      ┌─── Schema.Struct<{ b: typeof Schema.Number; }>
   * //      ▼
   * const result = f(Schema.Struct({ a: Schema.String, b: Schema.Number }))
   * ```
   * @since 3.13.11
   */
  export type Field =
    | Schema.All
    | PropertySignature.All

  /**
   * @since 3.10.0
   */
  export type Fields = { readonly [x: PropertyKey]: Field }

  type OptionalEncodedPropertySignature =
    | PropertySignature<PropertySignature.Token, any, PropertyKey, "?:", any, boolean, unknown>
    | PropertySignature<PropertySignature.Token, any, PropertyKey, "?:", never, boolean, unknown>
    | PropertySignature<PropertySignature.Token, never, PropertyKey, "?:", any, boolean, unknown>
    | PropertySignature<PropertySignature.Token, never, PropertyKey, "?:", never, boolean, unknown>

  type EncodedOptionalKeys<Fields extends Struct.Fields> = {
    [K in keyof Fields]: Fields[K] extends OptionalEncodedPropertySignature ? K
      : never
  }[keyof Fields]

  type OptionalTypePropertySignature =
    | PropertySignature<"?:", any, PropertyKey, PropertySignature.Token, any, boolean, unknown>
    | PropertySignature<"?:", any, PropertyKey, PropertySignature.Token, never, boolean, unknown>
    | PropertySignature<"?:", never, PropertyKey, PropertySignature.Token, any, boolean, unknown>
    | PropertySignature<"?:", never, PropertyKey, PropertySignature.Token, never, boolean, unknown>

  // type TypeOptionalKeys<Fields extends Struct.Fields> = {
  //   [K in keyof Fields]: Fields[K] extends OptionalTypePropertySignature ? K : never
  // }[keyof Fields]

  /**
   * @since 3.10.0
   */
  export type Type<F extends Fields> = Types.UnionToIntersection<
    {
      [K in keyof F]: F[K] extends OptionalTypePropertySignature ? { readonly [H in K]?: Schema.Type<F[H]> } :
        { readonly [h in K]: Schema.Type<F[h]> }
    }[keyof F]
  > extends infer Q ? Q : never

  type Key<F extends Fields, K extends keyof F> = [K] extends [never] ? never :
    F[K] extends PropertySignature.All<infer Key> ? [Key] extends [never] ? K : Key :
    K

  /**
   * @since 3.10.0
   */
  export type Encoded<F extends Fields> =
    & { readonly [K in Exclude<keyof F, EncodedOptionalKeys<F>> as Key<F, K>]: Schema.Encoded<F[K]> }
    & { readonly [K in EncodedOptionalKeys<F> as Key<F, K>]?: Schema.Encoded<F[K]> }

  /**
   * @since 3.10.0
   */
  export type Context<F extends Fields> = Schema.Context<F[keyof F]>

  type PropertySignatureWithDefault =
    | PropertySignature<PropertySignature.Token, any, PropertyKey, PropertySignature.Token, any, true, unknown>
    | PropertySignature<PropertySignature.Token, any, PropertyKey, PropertySignature.Token, never, true, unknown>
    | PropertySignature<PropertySignature.Token, never, PropertyKey, PropertySignature.Token, any, true, unknown>
    | PropertySignature<PropertySignature.Token, never, PropertyKey, PropertySignature.Token, never, true, unknown>

  /**
   * @since 3.10.0
   */
  export type Constructor<F extends Fields> = Types.UnionToIntersection<
    {
      [K in keyof F]: F[K] extends OptionalTypePropertySignature ? { readonly [H in K]?: Schema.Type<F[H]> } :
        F[K] extends PropertySignatureWithDefault ? { readonly [H in K]?: Schema.Type<F[H]> } :
        { readonly [h in K]: Schema.Type<F[h]> }
    }[keyof F]
  > extends infer Q ? Q : never
}

/**
 * @since 3.10.0
 */
export declare namespace IndexSignature {
  /**
   * @since 3.10.0
   */
  export type Record = { readonly key: Schema.All; readonly value: Schema.All }

  /**
   * @since 3.10.0
   */
  export type Records = ReadonlyArray<Record>

  /**
   * @since 3.10.0
   */
  export type NonEmptyRecords = array_.NonEmptyReadonlyArray<Record>

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

  /**
   * @since 3.10.0
   */
  export type Type<Records extends IndexSignature.Records> = MergeTuple<
    {
      readonly [K in keyof Records]: {
        readonly [P in Schema.Type<Records[K]["key"]>]: Schema.Type<Records[K]["value"]>
      }
    }
  >

  /**
   * @since 3.10.0
   */
  export type Encoded<Records extends IndexSignature.Records> = MergeTuple<
    {
      readonly [K in keyof Records]: {
        readonly [P in Schema.Encoded<Records[K]["key"]>]: Schema.Encoded<Records[K]["value"]>
      }
    }
  >

  /**
   * @since 3.10.0
   */
  export type Context<Records extends IndexSignature.Records> = {
    [K in keyof Records]: Schema.Context<Records[K]["key"]> | Schema.Context<Records[K]["value"]>
  }[number]
}

/**
 * @since 3.10.0
 */
export declare namespace TypeLiteral {
  /**
   * @since 3.10.0
   */
  export type Type<Fields extends Struct.Fields, Records extends IndexSignature.Records> =
    & Struct.Type<Fields>
    & IndexSignature.Type<Records>

  /**
   * @since 3.10.0
   */
  export type Encoded<Fields extends Struct.Fields, Records extends IndexSignature.Records> =
    & Struct.Encoded<Fields>
    & IndexSignature.Encoded<Records>

  /**
   * @since 3.10.0
   */
  export type Constructor<Fields extends Struct.Fields, Records extends IndexSignature.Records> =
    & Struct.Constructor<Fields>
    & IndexSignature.Type<Records>
}

/**
 * @category api interface
 * @since 3.10.0
 */
export interface TypeLiteral<
  Fields extends Struct.Fields,
  Records extends IndexSignature.Records
> extends
  AnnotableClass<
    TypeLiteral<Fields, Records>,
    Simplify<TypeLiteral.Type<Fields, Records>>,
    Simplify<TypeLiteral.Encoded<Fields, Records>>,
    | Struct.Context<Fields>
    | IndexSignature.Context<Records>
  >
{
  readonly fields: Readonly<Fields>
  readonly records: Readonly<Records>
  annotations(
    annotations: Annotations.Schema<Simplify<TypeLiteral.Type<Fields, Records>>>
  ): TypeLiteral<Fields, Records>
  make(
    props: RequiredKeys<TypeLiteral.Constructor<Fields, Records>> extends never
      ? void | Simplify<TypeLiteral.Constructor<Fields, Records>>
      : Simplify<TypeLiteral.Constructor<Fields, Records>>,
    options?: MakeOptions
  ): Simplify<TypeLiteral.Type<Fields, Records>>
}

const preserveMissingMessageAnnotation = AST.pickAnnotations([AST.MissingMessageAnnotationId])

const getDefaultTypeLiteralAST = <
  Fields extends Struct.Fields,
  const Records extends IndexSignature.Records
>(fields: Fields, records: Records) => {
  const ownKeys = util_.ownKeys(fields)
  const pss: Array<AST.PropertySignature> = []
  if (ownKeys.length > 0) {
    const from: Array<AST.PropertySignature> = []
    const to: Array<AST.PropertySignature> = []
    const transformations: Array<AST.PropertySignatureTransformation> = []
    for (let i = 0; i < ownKeys.length; i++) {
      const key = ownKeys[i]
      const field = fields[key]
      if (isPropertySignature(field)) {
        const ast: PropertySignature.AST = field.ast
        switch (ast._tag) {
          case "PropertySignatureDeclaration": {
            const type = ast.type
            const isOptional = ast.isOptional
            const toAnnotations = ast.annotations
            from.push(new AST.PropertySignature(key, type, isOptional, true, preserveMissingMessageAnnotation(ast)))
            to.push(new AST.PropertySignature(key, AST.typeAST(type), isOptional, true, toAnnotations))
            pss.push(
              new AST.PropertySignature(key, type, isOptional, true, toAnnotations)
            )
            break
          }
          case "PropertySignatureTransformation": {
            const fromKey = ast.from.fromKey ?? key
            from.push(
              new AST.PropertySignature(fromKey, ast.from.type, ast.from.isOptional, true, ast.from.annotations)
            )
            to.push(
              new AST.PropertySignature(key, ast.to.type, ast.to.isOptional, true, ast.to.annotations)
            )
            transformations.push(new AST.PropertySignatureTransformation(fromKey, key, ast.decode, ast.encode))
            break
          }
        }
      } else {
        from.push(new AST.PropertySignature(key, field.ast, false, true))
        to.push(new AST.PropertySignature(key, AST.typeAST(field.ast), false, true))
        pss.push(new AST.PropertySignature(key, field.ast, false, true))
      }
    }
    if (array_.isNonEmptyReadonlyArray(transformations)) {
      const issFrom: Array<AST.IndexSignature> = []
      const issTo: Array<AST.IndexSignature> = []
      for (const r of records) {
        const { indexSignatures, propertySignatures } = AST.record(r.key.ast, r.value.ast)
        propertySignatures.forEach((ps) => {
          from.push(ps)
          to.push(
            new AST.PropertySignature(ps.name, AST.typeAST(ps.type), ps.isOptional, ps.isReadonly, ps.annotations)
          )
        })
        indexSignatures.forEach((is) => {
          issFrom.push(is)
          issTo.push(new AST.IndexSignature(is.parameter, AST.typeAST(is.type), is.isReadonly))
        })
      }
      return new AST.Transformation(
        new AST.TypeLiteral(from, issFrom, { [AST.AutoTitleAnnotationId]: "Struct (Encoded side)" }),
        new AST.TypeLiteral(to, issTo, { [AST.AutoTitleAnnotationId]: "Struct (Type side)" }),
        new AST.TypeLiteralTransformation(transformations)
      )
    }
  }
  const iss: Array<AST.IndexSignature> = []
  for (const r of records) {
    const { indexSignatures, propertySignatures } = AST.record(r.key.ast, r.value.ast)
    propertySignatures.forEach((ps) => pss.push(ps))
    indexSignatures.forEach((is) => iss.push(is))
  }
  return new AST.TypeLiteral(pss, iss)
}

const lazilyMergeDefaults = (
  fields: Struct.Fields,
  out: Record<PropertyKey, unknown>
): { [x: string | symbol]: unknown } => {
  const ownKeys = util_.ownKeys(fields)
  for (const key of ownKeys) {
    const field = fields[key]
    if (out[key] === undefined && isPropertySignature(field)) {
      const ast = field.ast
      const defaultValue = ast._tag === "PropertySignatureDeclaration" ? ast.defaultValue : ast.to.defaultValue
      if (defaultValue !== undefined) {
        out[key] = defaultValue()
      }
    }
  }
  return out
}

function makeTypeLiteralClass<Fields extends Struct.Fields, const Records extends IndexSignature.Records>(
  fields: Fields,
  records: Records,
  ast: AST.AST = getDefaultTypeLiteralAST(fields, records)
): TypeLiteral<Fields, Records> {
  return class TypeLiteralClass extends make<
    Simplify<TypeLiteral.Type<Fields, Records>>,
    Simplify<TypeLiteral.Encoded<Fields, Records>>,
    | Struct.Context<Fields>
    | IndexSignature.Context<Records>
  >(ast) {
    static override annotations(
      annotations: Annotations.Schema<Simplify<TypeLiteral.Type<Fields, Records>>>
    ): TypeLiteral<Fields, Records> {
      return makeTypeLiteralClass(this.fields, this.records, mergeSchemaAnnotations(this.ast, annotations))
    }

    static fields = { ...fields }

    static records = [...records] as Records

    static make = (
      props: Simplify<TypeLiteral.Constructor<Fields, Records>>,
      options?: MakeOptions
    ): Simplify<TypeLiteral.Type<Fields, Records>> => {
      const propsWithDefaults: any = lazilyMergeDefaults(fields, { ...props as any })
      return getDisableValidationMakeOption(options)
        ? propsWithDefaults
        : ParseResult.validateSync(this)(propsWithDefaults)
    }

    static pick(...keys: Array<keyof Fields>): Struct<Simplify<Pick<Fields, typeof keys[number]>>> {
      return Struct(struct_.pick(fields, ...keys) as any)
    }

    static omit(...keys: Array<keyof Fields>): Struct<Simplify<Omit<Fields, typeof keys[number]>>> {
      return Struct(struct_.omit(fields, ...keys) as any)
    }
  }
}

/**
 * @category api interface
 * @since 3.10.0
 */
export interface Struct<Fields extends Struct.Fields> extends
  AnnotableClass<
    Struct<Fields>,
    Simplify<Struct.Type<Fields>>,
    Simplify<Struct.Encoded<Fields>>,
    Struct.Context<Fields>
  >
{
  readonly fields: Readonly<Fields>
  readonly records: readonly []
  make(
    props: RequiredKeys<Struct.Constructor<Fields>> extends never ? void | Simplify<Struct.Constructor<Fields>>
      : Simplify<Struct.Constructor<Fields>>,
    options?: MakeOptions
  ): Simplify<Struct.Type<Fields>>

  annotations(annotations: Annotations.Schema<Simplify<Struct.Type<Fields>>>): Struct<Fields>
  pick<Keys extends ReadonlyArray<keyof Fields>>(...keys: Keys): Struct<Simplify<Pick<Fields, Keys[number]>>>
  omit<Keys extends ReadonlyArray<keyof Fields>>(...keys: Keys): Struct<Simplify<Omit<Fields, Keys[number]>>>
}

/**
 * @category constructors
 * @since 3.10.0
 */
export function Struct<Fields extends Struct.Fields, const Records extends IndexSignature.NonEmptyRecords>(
  fields: Fields,
  ...records: Records
): TypeLiteral<Fields, Records>
export function Struct<Fields extends Struct.Fields>(fields: Fields): Struct<Fields>
export function Struct<Fields extends Struct.Fields, const Records extends IndexSignature.Records>(
  fields: Fields,
  ...records: Records
): TypeLiteral<Fields, Records> {
  return makeTypeLiteralClass(fields, records)
}

/**
 * @category api interface
 * @since 3.10.0
 */
export interface tag<Tag extends AST.LiteralValue> extends PropertySignature<":", Tag, never, ":", Tag, true, never> {}

/**
 * Returns a property signature that represents a tag.
 * A tag is a literal value that is used to distinguish between different types of objects.
 * The tag is optional when using the `make` method.
 *
 * @example
 * ```ts
 * import * as assert from "node:assert"
 * import { Schema } from "effect"
 *
 * const User = Schema.Struct({
 *   _tag: Schema.tag("User"),
 *   name: Schema.String,
 *   age: Schema.Number
 * })
 *
 * assert.deepStrictEqual(User.make({ name: "John", age: 44 }), { _tag: "User", name: "John", age: 44 })
 * ```
 *
 * @see {@link TaggedStruct}
 *
 * @since 3.10.0
 */
export const tag = <Tag extends AST.LiteralValue>(tag: Tag): tag<Tag> =>
  Literal(tag).pipe(propertySignature, withConstructorDefault(() => tag))

/**
 * @category api interface
 * @since 3.10.0
 */
export type TaggedStruct<Tag extends AST.LiteralValue, Fields extends Struct.Fields> = Struct<
  { _tag: tag<Tag> } & Fields
>

/**
 * A tagged struct is a struct that has a tag property that is used to distinguish between different types of objects.
 *
 * The tag is optional when using the `make` method.
 *
 * @example
 * ```ts
 * import * as assert from "node:assert"
 * import { Schema } from "effect"
 *
 * const User = Schema.TaggedStruct("User", {
 *   name: Schema.String,
 *   age: Schema.Number
 * })
 *
 * assert.deepStrictEqual(User.make({ name: "John", age: 44 }), { _tag: "User", name: "John", age: 44 })
 * ```
 *
 * @category constructors
 * @since 3.10.0
 */
export const TaggedStruct = <Tag extends AST.LiteralValue, Fields extends Struct.Fields>(
  value: Tag,
  fields: Fields
): TaggedStruct<Tag, Fields> => Struct({ _tag: tag(value), ...fields })

/**
 * @category api interface
 * @since 3.10.0
 */
export interface Record$<K extends Schema.All, V extends Schema.All> extends
  AnnotableClass<
    Record$<K, V>,
    { readonly [P in Schema.Type<K>]: Schema.Type<V> },
    { readonly [P in Schema.Encoded<K>]: Schema.Encoded<V> },
    | Schema.Context<K>
    | Schema.Context<V>
  >
{
  readonly fields: {}
  readonly records: readonly [{ readonly key: K; readonly value: V }]
  readonly key: K
  readonly value: V
  make(
    props: void | { readonly [P in Schema.Type<K>]: Schema.Type<V> },
    options?: MakeOptions
  ): { readonly [P in Schema.Type<K>]: Schema.Type<V> }
  annotations(annotations: Annotations.Schema<{ readonly [P in Schema.Type<K>]: Schema.Type<V> }>): Record$<K, V>
}

function makeRecordClass<K extends Schema.All, V extends Schema.All>(
  key: K,
  value: V,
  ast?: AST.AST
): Record$<K, V> {
  return class RecordClass extends makeTypeLiteralClass({}, [{ key, value }], ast) {
    static override annotations(
      annotations: Annotations.Schema<{ readonly [P in Schema.Type<K>]: Schema.Type<V> }>
    ): Record$<K, V> {
      return makeRecordClass(key, value, mergeSchemaAnnotations(this.ast, annotations))
    }

    static key = key

    static value = value
  }
}

/**
 * @category constructors
 * @since 3.10.0
 */
export const Record = <K extends Schema.All, V extends Schema.All>(
  options: { readonly key: K; readonly value: V }
): Record$<K, V> => makeRecordClass(options.key, options.value)

/**
 * @category struct transformations
 * @since 3.10.0
 */
export const pick = <A, I, Keys extends ReadonlyArray<keyof A & keyof I>>(...keys: Keys) =>
<R>(
  self: Schema<A, I, R>
): SchemaClass<Simplify<Pick<A, Keys[number]>>, Simplify<Pick<I, Keys[number]>>, R> => make(AST.pick(self.ast, keys))

/**
 * @category struct transformations
 * @since 3.10.0
 */
export const omit = <A, I, Keys extends ReadonlyArray<keyof A & keyof I>>(...keys: Keys) =>
<R>(
  self: Schema<A, I, R>
): SchemaClass<Simplify<Omit<A, Keys[number]>>, Simplify<Omit<I, Keys[number]>>, R> => make(AST.omit(self.ast, keys))

/**
 * Given a schema `Schema<A, I, R>` and a key `key: K`, this function extracts a specific field from the `A` type,
 * producing a new schema that represents a transformation from the `{ readonly [key]: I[K] }` type to `A[K]`.
 *
 * @example
 * ```ts
 * import * as Schema from "effect/Schema"
 *
 * // ---------------------------------------------
 * // use case: pull out a single field from a
 * // struct through a transformation
 * // ---------------------------------------------
 *
 * const mytable = Schema.Struct({
 *   column1: Schema.NumberFromString,
 *   column2: Schema.Number
 * })
 *
 * // const pullOutColumn: S.Schema<number, {
 * //     readonly column1: string;
 * // }, never>
 * const pullOutColumn = mytable.pipe(Schema.pluck("column1"))
 *
 * console.log(Schema.decodeUnknownEither(Schema.Array(pullOutColumn))([{ column1: "1", column2: 100 }, { column1: "2", column2: 300 }]))
 * // Output: { _id: 'Either', _tag: 'Right', right: [ 1, 2 ] }
 * ```
 *
 * @category struct transformations
 * @since 3.10.0
 */
export const pluck: {
  /**
   * Given a schema `Schema<A, I, R>` and a key `key: K`, this function extracts a specific field from the `A` type,
   * producing a new schema that represents a transformation from the `{ readonly [key]: I[K] }` type to `A[K]`.
   *
   * @example
   * ```ts
   * import * as Schema from "effect/Schema"
   *
   * // ---------------------------------------------
   * // use case: pull out a single field from a
   * // struct through a transformation
   * // ---------------------------------------------
   *
   * const mytable = Schema.Struct({
   *   column1: Schema.NumberFromString,
   *   column2: Schema.Number
   * })
   *
   * // const pullOutColumn: S.Schema<number, {
   * //     readonly column1: string;
   * // }, never>
   * const pullOutColumn = mytable.pipe(Schema.pluck("column1"))
   *
   * console.log(Schema.decodeUnknownEither(Schema.Array(pullOutColumn))([{ column1: "1", column2: 100 }, { column1: "2", column2: 300 }]))
   * // Output: { _id: 'Either', _tag: 'Right', right: [ 1, 2 ] }
   * ```
   *
   * @category struct transformations
   * @since 3.10.0
   */
  <A, I, K extends keyof A & keyof I>(key: K): <R>(schema: Schema<A, I, R>) => SchemaClass<A[K], Simplify<Pick<I, K>>, R>
  /**
   * Given a schema `Schema<A, I, R>` and a key `key: K`, this function extracts a specific field from the `A` type,
   * producing a new schema that represents a transformation from the `{ readonly [key]: I[K] }` type to `A[K]`.
   *
   * @example
   * ```ts
   * import * as Schema from "effect/Schema"
   *
   * // ---------------------------------------------
   * // use case: pull out a single field from a
   * // struct through a transformation
   * // ---------------------------------------------
   *
   * const mytable = Schema.Struct({
   *   column1: Schema.NumberFromString,
   *   column2: Schema.Number
   * })
   *
   * // const pullOutColumn: S.Schema<number, {
   * //     readonly column1: string;
   * // }, never>
   * const pullOutColumn = mytable.pipe(Schema.pluck("column1"))
   *
   * console.log(Schema.decodeUnknownEither(Schema.Array(pullOutColumn))([{ column1: "1", column2: 100 }, { column1: "2", column2: 300 }]))
   * // Output: { _id: 'Either', _tag: 'Right', right: [ 1, 2 ] }
   * ```
   *
   * @category struct transformations
   * @since 3.10.0
   */
  <A, I, R, K extends keyof A & keyof I>(schema: Schema<A, I, R>, key: K): SchemaClass<A[K], Simplify<Pick<I, K>>, R>
} = dual(
  2,
  <A, I, R, K extends keyof A & keyof I>(
    schema: Schema<A, I, R>,
    key: K
  ): Schema<A[K], Pick<I, K>, R> => {
    const ps = AST.getPropertyKeyIndexedAccess(AST.typeAST(schema.ast), key)
    const value = make</**
     * Given a schema `Schema<A, I, R>` and a key `key: K`, this function extracts a specific field from the `A` type,
     * producing a new schema that represents a transformation from the `{ readonly [key]: I[K] }` type to `A[K]`.
     *
     * @example
     * ```ts
     * import * as Schema from "effect/Schema"
     *
     * // ---------------------------------------------
     * // use case: pull out a single field from a
     * // struct through a transformation
     * // ---------------------------------------------
     *
     * const mytable = Schema.Struct({
     *   column1: Schema.NumberFromString,
     *   column2: Schema.Number
     * })
     *
     * // const pullOutColumn: S.Schema<number, {
     * //     readonly column1: string;
     * // }, never>
     * const pullOutColumn = mytable.pipe(Schema.pluck("column1"))
     *
     * console.log(Schema.decodeUnknownEither(Schema.Array(pullOutColumn))([{ column1: "1", column2: 100 }, { column1: "2", column2: 300 }]))
     * // Output: { _id: 'Either', _tag: 'Right', right: [ 1, 2 ] }
     * ```
     *
     * @category struct transformations
     * @since 3.10.0
     */
    A[K], /**
     * Given a schema `Schema<A, I, R>` and a key `key: K`, this function extracts a specific field from the `A` type,
     * producing a new schema that represents a transformation from the `{ readonly [key]: I[K] }` type to `A[K]`.
     *
     * @example
     * ```ts
     * import * as Schema from "effect/Schema"
     *
     * // ---------------------------------------------
     * // use case: pull out a single field from a
     * // struct through a transformation
     * // ---------------------------------------------
     *
     * const mytable = Schema.Struct({
     *   column1: Schema.NumberFromString,
     *   column2: Schema.Number
     * })
     *
     * // const pullOutColumn: S.Schema<number, {
     * //     readonly column1: string;
     * // }, never>
     * const pullOutColumn = mytable.pipe(Schema.pluck("column1"))
     *
     * console.log(Schema.decodeUnknownEither(Schema.Array(pullOutColumn))([{ column1: "1", column2: 100 }, { column1: "2", column2: 300 }]))
     * // Output: { _id: 'Either', _tag: 'Right', right: [ 1, 2 ] }
     * ```
     *
     * @category struct transformations
     * @since 3.10.0
     */
    A[K], /**
     * Given a schema `Schema<A, I, R>` and a key `key: K`, this function extracts a specific field from the `A` type,
     * producing a new schema that represents a transformation from the `{ readonly [key]: I[K] }` type to `A[K]`.
     *
     * @example
     * ```ts
     * import * as Schema from "effect/Schema"
     *
     * // ---------------------------------------------
     * // use case: pull out a single field from a
     * // struct through a transformation
     * // ---------------------------------------------
     *
     * const mytable = Schema.Struct({
     *   column1: Schema.NumberFromString,
     *   column2: Schema.Number
     * })
     *
     * // const pullOutColumn: S.Schema<number, {
     * //     readonly column1: string;
     * // }, never>
     * const pullOutColumn = mytable.pipe(Schema.pluck("column1"))
     *
     * console.log(Schema.decodeUnknownEither(Schema.Array(pullOutColumn))([{ column1: "1", column2: 100 }, { column1: "2", column2: 300 }]))
     * // Output: { _id: 'Either', _tag: 'Right', right: [ 1, 2 ] }
     * ```
     *
     * @category struct transformations
     * @since 3.10.0
     */
    R>(ps.isOptional ? AST.orUndefined(ps.type) : ps.type)
    const out = transform(
      schema.pipe(pick(key)),
      value,
      {
        strict: true,
        decode: (i) => i[key],
        encode: (a) => ps.isOptional && a === undefined ? {} : { [key]: a } as any
      }
    )
    return out
  }
)

/**
 * @category branding
 * @since 3.10.0
 */
export interface BrandSchema<A extends Brand<any>, I = A, R = never>
  extends AnnotableClass<BrandSchema<A, I, R>, A, I, R>
{
  make(a: Brand.Unbranded<A>, options?: MakeOptions): A
}

/**
 * @category api interface
 * @since 3.10.0
 */
export interface brand<S extends Schema.Any, B extends string | symbol>
  extends BrandSchema<Schema.Type<S> & Brand<B>, Schema.Encoded<S>, Schema.Context<S>>
{
  readonly from: S
  annotations(annotations: Annotations.Schema<Schema.Type<S> & Brand<B>>): brand<S, B>
}

function makeBrandClass<S extends Schema.Any, B extends string | symbol>(
  from: S,
  ast: AST.AST
): brand<S, B> {
  return class BrandClass extends make<Schema.Type<S> & Brand<B>, Schema.Encoded<S>, Schema.Context<S>>(ast) {
    static override annotations(annotations: Annotations.Schema<Schema.Type<S> & Brand<B>>): brand<S, B> {
      return makeBrandClass(this.from, mergeSchemaAnnotations(this.ast, annotations))
    }

    static make = (a: Brand.Unbranded<Schema.Type<S> & Brand<B>>, options?: MakeOptions): Schema.Type<S> & Brand<B> => {
      return getDisableValidationMakeOption(options) ? a : ParseResult.validateSync(this)(a)
    }

    static from = from
  }
}

/**
 * Returns a nominal branded schema by applying a brand to a given schema.
 *
 * ```
 * Schema<A> + B -> Schema<A & Brand<B>>
 * ```
 *
 * @example
 * ```ts
 * import * as Schema from "effect/Schema"
 *
 * const Int = Schema.Number.pipe(Schema.int(), Schema.brand("Int"))
 * type Int = Schema.Schema.Type<typeof Int> // number & Brand<"Int">
 * ```
 *
 * @category branding
 * @since 3.10.0
 */
export const brand = <S extends Schema.Any, B extends string | symbol>(
  brand: B,
  annotations?: Annotations.Schema<Schema.Type<S> & Brand<B>>
) =>
(self: S): brand<S, B> => {
  const annotation: AST.BrandAnnotation = option_.match(AST.getBrandAnnotation(self.ast), {
    onNone: () => [brand],
    onSome: (brands) => [...brands, brand]
  })
  const ast = AST.annotations(
    self.ast,
    toASTAnnotations({
      [AST.BrandAnnotationId]: annotation,
      ...annotations
    })
  )
  return makeBrandClass(self, ast)
}

/**
 * @category combinators
 * @since 3.10.0
 */
export const partial = <A, I, R>(
  self: Schema<A, I, R>
): SchemaClass<{ [K in keyof A]?: A[K] | undefined }, { [K in keyof I]?: I[K] | undefined }, R> =>
  make(AST.partial(self.ast))

/**
 * @category combinators
 * @since 3.10.0
 */
export const partialWith: {
  /**
   * @category combinators
   * @since 3.10.0
   */
  <const Options extends { readonly exact: true }>(options: Options): <A, I, R>(
    self: Schema<A, I, R>
  ) => SchemaClass<{ [K in keyof A]?: A[K] }, { [K in keyof I]?: I[K] }, R>
  /**
   * @category combinators
   * @since 3.10.0
   */
  <A, I, R, const Options extends { readonly exact: true } | undefined>(self: Schema<A, I, R>, options: Options): SchemaClass<{ [K in keyof A]?: A[K] }, { [K in keyof I]?: I[K] }, R>
} = dual((args) => isSchema(args[0]), <A, I, R>(
  self: Schema<A, I, R>,
  options: { readonly exact: true }
): SchemaClass<Partial<A>, Partial<I>, R> => make(AST.partial(self.ast, options)))

/**
 * @category combinators
 * @since 3.10.0
 */
export const required = <A, I, R>(
  self: Schema<A, I, R>
): SchemaClass<{ [K in keyof A]-?: A[K] }, { [K in keyof I]-?: I[K] }, R> => make(AST.required(self.ast))

/**
 * @category api interface
 * @since 3.10.0
 */
export interface mutable<S extends Schema.Any> extends
  AnnotableClass<
    mutable<S>,
    SimplifyMutable<Schema.Type<S>>,
    SimplifyMutable<Schema.Encoded<S>>,
    Schema.Context<S>
  >
{}

/**
 * Creates a new schema with shallow mutability applied to its properties.
 *
 * @category combinators
 * @since 3.10.0
 */
export const mutable = <S extends Schema.Any>(schema: S): mutable<S> => make(AST.mutable(schema.ast))

const intersectTypeLiterals = (
  x: AST.AST,
  y: AST.AST,
  path: ReadonlyArray<PropertyKey>
): AST.TypeLiteral => {
  if (AST.isTypeLiteral(x) && AST.isTypeLiteral(y)) {
    const propertySignatures = [...x.propertySignatures]
    for (const ps of y.propertySignatures) {
      const name = ps.name
      const i = propertySignatures.findIndex((ps) => ps.name === name)
      if (i === -1) {
        propertySignatures.push(ps)
      } else {
        const { isOptional, type } = propertySignatures[i]
        propertySignatures[i] = new AST.PropertySignature(
          name,
          extendAST(type, ps.type, path.concat(name)),
          isOptional,
          true
        )
      }
    }
    return new AST.TypeLiteral(
      propertySignatures,
      x.indexSignatures.concat(y.indexSignatures)
    )
  }
  throw new Error(errors_.getSchemaExtendErrorMessage(x, y, path))
}

const preserveRefinementAnnotations = AST.omitAnnotations([AST.IdentifierAnnotationId])

const addRefinementToMembers = (refinement: AST.Refinement, asts: ReadonlyArray<AST.AST>): Array<AST.Refinement> =>
  asts.map((ast) => new AST.Refinement(ast, refinement.filter, preserveRefinementAnnotations(refinement)))

const extendAST = (x: AST.AST, y: AST.AST, path: ReadonlyArray<PropertyKey>): AST.AST =>
  AST.Union.make(intersectUnionMembers([x], [y], path))

const getTypes = (ast: AST.AST): ReadonlyArray<AST.AST> => AST.isUnion(ast) ? ast.types : [ast]

const intersectUnionMembers = (
  xs: ReadonlyArray<AST.AST>,
  ys: ReadonlyArray<AST.AST>,
  path: ReadonlyArray<PropertyKey>
): Array<AST.AST> =>
  array_.flatMap(xs, (x) =>
    array_.flatMap(ys, (y) => {
      switch (y._tag) {
        case "Literal": {
          if (
            (Predicate.isString(y.literal) && AST.isStringKeyword(x) ||
              (Predicate.isNumber(y.literal) && AST.isNumberKeyword(x)) ||
              (Predicate.isBoolean(y.literal) && AST.isBooleanKeyword(x)))
          ) {
            return [y]
          }
          break
        }
        case "StringKeyword": {
          if (y === AST.stringKeyword) {
            if (AST.isStringKeyword(x) || (AST.isLiteral(x) && Predicate.isString(x.literal))) {
              return [x]
            } else if (AST.isRefinement(x)) {
              return addRefinementToMembers(x, intersectUnionMembers(getTypes(x.from), [y], path))
            }
          } else if (x === AST.stringKeyword) {
            return [y]
          }
          break
        }
        case "NumberKeyword": {
          if (y === AST.numberKeyword) {
            if (AST.isNumberKeyword(x) || (AST.isLiteral(x) && Predicate.isNumber(x.literal))) {
              return [x]
            } else if (AST.isRefinement(x)) {
              return addRefinementToMembers(x, intersectUnionMembers(getTypes(x.from), [y], path))
            }
          } else if (x === AST.numberKeyword) {
            return [y]
          }
          break
        }
        case "BooleanKeyword": {
          if (y === AST.booleanKeyword) {
            if (AST.isBooleanKeyword(x) || (AST.isLiteral(x) && Predicate.isBoolean(x.literal))) {
              return [x]
            } else if (AST.isRefinement(x)) {
              return addRefinementToMembers(x, intersectUnionMembers(getTypes(x.from), [y], path))
            }
          } else if (x === AST.booleanKeyword) {
            return [y]
          }
          break
        }
        case "Union":
          return intersectUnionMembers(getTypes(x), y.types, path)
        case "Suspend":
          return [new AST.Suspend(() => extendAST(x, y.f(), path))]
        case "Refinement":
          return addRefinementToMembers(y, intersectUnionMembers(getTypes(x), getTypes(y.from), path))
        case "TypeLiteral": {
          switch (x._tag) {
            case "Union":
              return intersectUnionMembers(x.types, [y], path)
            case "Suspend":
              return [new AST.Suspend(() => extendAST(x.f(), y, path))]
            case "Refinement":
              return addRefinementToMembers(x, intersectUnionMembers(getTypes(x.from), [y], path))
            case "TypeLiteral":
              return [intersectTypeLiterals(x, y, path)]
            case "Transformation": {
              const transformation = x.transformation
              const from = intersectTypeLiterals(x.from, y, path)
              const to = intersectTypeLiterals(x.to, AST.typeAST(y), path)
              switch (transformation._tag) {
                case "TypeLiteralTransformation":
                  return [
                    new AST.Transformation(
                      from,
                      to,
                      new AST.TypeLiteralTransformation(transformation.propertySignatureTransformations)
                    )
                  ]
                case "ComposeTransformation":
                  return [new AST.Transformation(from, to, AST.composeTransformation)]
                case "FinalTransformation":
                  return [
                    new AST.Transformation(
                      from,
                      to,
                      new AST.FinalTransformation(
                        (fromA, options, ast, fromI) =>
                          ParseResult.map(
                            transformation.decode(fromA, options, ast, fromI),
                            (partial) => ({ ...fromA, ...partial })
                          ),
                        (toI, options, ast, toA) =>
                          ParseResult.map(
                            transformation.encode(toI, options, ast, toA),
                            (partial) => ({ ...toI, ...partial })
                          )
                      )
                    )
                  ]
              }
            }
          }
          break
        }
        case "Transformation": {
          if (AST.isTransformation(x)) {
            if (
              AST.isTypeLiteralTransformation(y.transformation) && AST.isTypeLiteralTransformation(x.transformation)
            ) {
              return [
                new AST.Transformation(
                  intersectTypeLiterals(x.from, y.from, path),
                  intersectTypeLiterals(x.to, y.to, path),
                  new AST.TypeLiteralTransformation(
                    y.transformation.propertySignatureTransformations.concat(
                      x.transformation.propertySignatureTransformations
                    )
                  )
                )
              ]
            }
          } else {
            return intersectUnionMembers([y], [x], path)
          }
          break
        }
      }
      throw new Error(errors_.getSchemaExtendErrorMessage(x, y, path))
    }))

/**
 * @category api interface
 * @since 3.10.0
 */
export interface extend<Self extends Schema.Any, That extends Schema.Any> extends
  AnnotableClass<
    extend<Self, That>,
    Schema.Type<Self> & Schema.Type<That>,
    Schema.Encoded<Self> & Schema.Encoded<That>,
    Schema.Context<Self> | Schema.Context<That>
  >
{}

/**
 * Extends a schema with another schema.
 *
 * Not all extensions are supported, and their support depends on the nature of
 * the involved schemas.
 *
 * Possible extensions include:
 * - `Schema.String` with another `Schema.String` refinement or a string literal
 * - `Schema.Number` with another `Schema.Number` refinement or a number literal
 * - `Schema.Boolean` with another `Schema.Boolean` refinement or a boolean
 *   literal
 * - A struct with another struct where overlapping fields support extension
 * - A struct with in index signature
 * - A struct with a union of supported schemas
 * - A refinement of a struct with a supported schema
 * - A suspend of a struct with a supported schema
 * - A transformation between structs where the “from” and “to” sides have no
 *   overlapping fields with the target struct
 *
 * @example
 * ```ts
 * import * as Schema from "effect/Schema"
 *
 * const schema = Schema.Struct({
 *   a: Schema.String,
 *   b: Schema.String
 * })
 *
 * // const extended: Schema<
 * //   {
 * //     readonly a: string
 * //     readonly b: string
 * //   } & {
 * //     readonly c: string
 * //   } & {
 * //     readonly [x: string]: string
 * //   }
 * // >
 * const extended = Schema.asSchema(schema.pipe(
 *   Schema.extend(Schema.Struct({ c: Schema.String })), // <= you can add more fields
 *   Schema.extend(Schema.Record({ key: Schema.String, value: Schema.String })) // <= you can add index signatures
 * ))
 * ```
 *
 * @category combinators
 * @since 3.10.0
 */
export const extend: {
  /**
   * Extends a schema with another schema.
   *
   * Not all extensions are supported, and their support depends on the nature of
   * the involved schemas.
   *
   * Possible extensions include:
   * - `Schema.String` with another `Schema.String` refinement or a string literal
   * - `Schema.Number` with another `Schema.Number` refinement or a number literal
   * - `Schema.Boolean` with another `Schema.Boolean` refinement or a boolean
   *   literal
   * - A struct with another struct where overlapping fields support extension
   * - A struct with in index signature
   * - A struct with a union of supported schemas
   * - A refinement of a struct with a supported schema
   * - A suspend of a struct with a supported schema
   * - A transformation between structs where the “from” and “to” sides have no
   *   overlapping fields with the target struct
   *
   * @example
   * ```ts
   * import * as Schema from "effect/Schema"
   *
   * const schema = Schema.Struct({
   *   a: Schema.String,
   *   b: Schema.String
   * })
   *
   * // const extended: Schema<
   * //   {
   * //     readonly a: string
   * //     readonly b: string
   * //   } & {
   * //     readonly c: string
   * //   } & {
   * //     readonly [x: string]: string
   * //   }
   * // >
   * const extended = Schema.asSchema(schema.pipe(
   *   Schema.extend(Schema.Struct({ c: Schema.String })), // <= you can add more fields
   *   Schema.extend(Schema.Record({ key: Schema.String, value: Schema.String })) // <= you can add index signatures
   * ))
   * ```
   *
   * @category combinators
   * @since 3.10.0
   */
  <That extends Schema.Any>(that: That): <Self extends Schema.Any>(self: Self) => extend<Self, That>
  /**
   * Extends a schema with another schema.
   *
   * Not all extensions are supported, and their support depends on the nature of
   * the involved schemas.
   *
   * Possible extensions include:
   * - `Schema.String` with another `Schema.String` refinement or a string literal
   * - `Schema.Number` with another `Schema.Number` refinement or a number literal
   * - `Schema.Boolean` with another `Schema.Boolean` refinement or a boolean
   *   literal
   * - A struct with another struct where overlapping fields support extension
   * - A struct with in index signature
   * - A struct with a union of supported schemas
   * - A refinement of a struct with a supported schema
   * - A suspend of a struct with a supported schema
   * - A transformation between structs where the “from” and “to” sides have no
   *   overlapping fields with the target struct
   *
   * @example
   * ```ts
   * import * as Schema from "effect/Schema"
   *
   * const schema = Schema.Struct({
   *   a: Schema.String,
   *   b: Schema.String
   * })
   *
   * // const extended: Schema<
   * //   {
   * //     readonly a: string
   * //     readonly b: string
   * //   } & {
   * //     readonly c: string
   * //   } & {
   * //     readonly [x: string]: string
   * //   }
   * // >
   * const extended = Schema.asSchema(schema.pipe(
   *   Schema.extend(Schema.Struct({ c: Schema.String })), // <= you can add more fields
   *   Schema.extend(Schema.Record({ key: Schema.String, value: Schema.String })) // <= you can add index signatures
   * ))
   * ```
   *
   * @category combinators
   * @since 3.10.0
   */
  <Self extends Schema.Any, That extends Schema.Any>(self: Self, that: That): extend<Self, That>
} = dual(
  2,
  <Self extends Schema.Any, That extends Schema.Any>(self: Self, that: That) => make(extendAST(self.ast, that.ast, []))
)

/**
 * @category combinators
 * @since 3.10.0
 */
export const compose: {
  /**
   * @category combinators
   * @since 3.10.0
   */
  <To extends Schema.Any, From extends Schema.Any, C extends Schema.Type<From>>(to: To & Schema<Schema.Type<To>, C, Schema.Context<To>>): (from: From) => transform<From, To>
  /**
   * @category combinators
   * @since 3.10.0
   */
  <To extends Schema.Any>(to: To): <From extends Schema.Any, B extends Schema.Encoded<To>>(
    from: From & Schema<B, Schema.Encoded<From>, Schema.Context<From>>
  ) => transform<From, To>
  /**
   * @category combinators
   * @since 3.10.0
   */
  <To extends Schema.Any>(to: To, options?: { readonly strict: true }): <From extends Schema.Any>(
    from: From & Schema<Schema.Encoded<To>, Schema.Encoded<From>, Schema.Context<From>>
  ) => transform<From, To>
  /**
   * @category combinators
   * @since 3.10.0
   */
  <To extends Schema.Any>(to: To, options: { readonly strict: false }): <From extends Schema.Any>(from: From) => transform<From, To>

  /**
   * @category combinators
   * @since 3.10.0
   */
  <From extends Schema.Any, To extends Schema.Any, C extends Schema.Type<From>>(from: From, to: To & Schema<Schema.Type<To>, C, Schema.Context<To>>): transform<From, To>
  /**
   * @category combinators
   * @since 3.10.0
   */
  <From extends Schema.Any, B extends Schema.Encoded<To>, To extends Schema.Any>(from: From & Schema<B, Schema.Encoded<From>, Schema.Context<From>>, to: To): transform<From, To>
  /**
   * @category combinators
   * @since 3.10.0
   */
  <From extends Schema.Any, To extends Schema.Any>(
    from: From & Schema<Schema.Encoded<To>, Schema.Encoded<From>, Schema.Context<From>>,
    to: To,
    options?: { readonly strict: true }
  ): transform<From, To>
  /**
   * @category combinators
   * @since 3.10.0
   */
  <From extends Schema.Any, To extends Schema.Any>(from: From, to: To, options: { readonly strict: false }): transform<From, To>
} = dual(
  (args) => isSchema(args[1]),
  <B, A, R1, D, C, R2>(from: Schema<B, A, R1>, to: Schema<D, C, R2>): SchemaClass<D, A, R1 | R2> =>
    makeTransformationClass(from, to, AST.compose(from.ast, to.ast))
)

/**
 * @category api interface
 * @since 3.10.0
 */
export interface suspend<A, I, R> extends AnnotableClass<suspend<A, I, R>, A, I, R> {}

/**
 * @category constructors
 * @since 3.10.0
 */
export const suspend = <A, I, R>(f: () => Schema<A, I, R>): suspend<A, I, R> => make(new AST.Suspend(() => f().ast))

/**
 * @since 3.10.0
 * @category symbol
 */
export const RefineSchemaId: unique symbol = Symbol.for("effect/SchemaId/Refine")

/**
 * @since 3.10.0
 * @category symbol
 */
export type RefineSchemaId = typeof RefineSchemaId

/**
 * @category api interface
 * @since 3.10.0
 */
export interface refine<A, From extends Schema.Any>
  extends AnnotableClass<refine<A, From>, A, Schema.Encoded<From>, Schema.Context<From>>
{
  /** The following is required for {@link HasFields} to work */
  readonly [RefineSchemaId]: From
  readonly from: From
  readonly filter: (
    a: Schema.Type<From>,
    options: ParseOptions,
    self: AST.Refinement
  ) => option_.Option<ParseResult.ParseIssue>
  make(a: Schema.Type<From>, options?: MakeOptions): A
}

function makeRefineClass<From extends Schema.Any, A>(
  from: From,
  filter: (a: Schema.Type<From>, options: ParseOptions, self: AST.Refinement) => option_.Option<ParseResult.ParseIssue>,
  ast: AST.AST
): refine<A, From> {
  return class RefineClass extends make<A, Schema.Encoded<From>, Schema.Context<From>>(ast) {
    static override annotations(annotations: Annotations.Schema<A>): refine<A, From> {
      return makeRefineClass(this.from, this.filter, mergeSchemaAnnotations(this.ast, annotations))
    }

    static [RefineSchemaId] = from

    static from = from

    static filter = filter

    static make = (a: Schema.Type<From>, options?: MakeOptions): A => {
      return getDisableValidationMakeOption(options) ? a : ParseResult.validateSync(this)(a)
    }
  }
}

/**
 * @category api interface
 * @since 3.10.0
 */
export interface filter<From extends Schema.Any> extends refine<Schema.Type<From>, From> {}

const fromFilterPredicateReturnTypeItem = (
  item: FilterOutput,
  ast: AST.Refinement | AST.Transformation,
  input: unknown
): option_.Option<ParseResult.ParseIssue> => {
  if (Predicate.isBoolean(item)) {
    return item
      ? option_.none()
      : option_.some(new ParseResult.Type(ast, input))
  }
  if (Predicate.isString(item)) {
    return option_.some(new ParseResult.Type(ast, input, item))
  }
  if (item !== undefined) {
    if ("_tag" in item) {
      return option_.some(item)
    }
    const issue = new ParseResult.Type(ast, input, item.message)
    return option_.some(
      array_.isNonEmptyReadonlyArray(item.path) ? new ParseResult.Pointer(item.path, input, issue) : issue
    )
  }
  return option_.none()
}

const toFilterParseIssue = (
  out: FilterReturnType,
  ast: AST.Refinement | AST.Transformation,
  input: unknown
): option_.Option<ParseResult.ParseIssue> => {
  if (util_.isSingle(out)) {
    return fromFilterPredicateReturnTypeItem(out, ast, input)
  }
  if (array_.isNonEmptyReadonlyArray(out)) {
    const issues = array_.filterMap(out, (issue) => fromFilterPredicateReturnTypeItem(issue, ast, input))
    if (array_.isNonEmptyReadonlyArray(issues)) {
      return option_.some(issues.length === 1 ? issues[0] : new ParseResult.Composite(ast, input, issues))
    }
  }
  return option_.none()
}

/**
 * @category filtering
 * @since 3.10.0
 */
export interface FilterIssue {
  readonly path: ReadonlyArray<PropertyKey>
  readonly message: string
}

/**
 * @category filtering
 * @since 3.10.0
 */
export type FilterOutput = undefined | boolean | string | ParseResult.ParseIssue | FilterIssue

type FilterReturnType = FilterOutput | ReadonlyArray<FilterOutput>

/**
 * @category filtering
 * @since 3.10.0
 */
export function filter<C extends A, B extends A, A = C>(
  refinement: (a: A, options: ParseOptions, self: AST.Refinement) => a is B,
  annotations?: Annotations.Filter<C & B, C>
): <I, R>(self: Schema<C, I, R>) => refine<C & B, Schema<A, I, R>>
export function filter<A, B extends A>(
  refinement: (a: A, options: ParseOptions, self: AST.Refinement) => a is B,
  annotations?: Annotations.Filter<B, A>
): <I, R>(self: Schema<A, I, R>) => refine<B, Schema<A, I, R>>
export function filter<S extends Schema.Any>(
  predicate: (
    a: Types.NoInfer<Schema.Type<S>>,
    options: ParseOptions,
    self: AST.Refinement
  ) => FilterReturnType,
  annotations?: Annotations.Filter<Types.NoInfer<Schema.Type<S>>>
): (self: S) => filter<S>
export function filter<A>(
  predicate: (
    a: A,
    options: ParseOptions,
    self: AST.Refinement
  ) => FilterReturnType,
  annotations?: Annotations.Filter<A>
): <I, R>(self: Schema<A, I, R>) => refine<A, Schema<A, I, R>> {
  return <I, R>(self: Schema<A, I, R>) => {
    function filter(input: A, options: AST.ParseOptions, ast: AST.Refinement) {
      return toFilterParseIssue(predicate(input, options, ast), ast, input)
    }
    const ast = new AST.Refinement(
      self.ast,
      filter,
      toASTAnnotations(annotations)
    )
    return makeRefineClass(self, filter, ast)
  }
}

/**
 * @category api interface
 * @since 3.10.0
 */
export interface filterEffect<S extends Schema.Any, FD = never>
  extends transformOrFail<S, SchemaClass<Schema.Type<S>>, FD>
{}

/**
 * @category transformations
 * @since 3.10.0
 */
export const filterEffect: {
  /**
   * @category transformations
   * @since 3.10.0
   */
  <S extends Schema.Any, FD>(
    f: (
      a: Types.NoInfer<Schema.Type<S>>,
      options: ParseOptions,
      self: AST.Transformation
    ) => Effect.Effect<FilterReturnType, never, FD>
  ): (self: S) => filterEffect<S, FD>
  /**
   * @category transformations
   * @since 3.10.0
   */
  <S extends Schema.Any, RD>(
    self: S,
    f: (
      a: Types.NoInfer<Schema.Type<S>>,
      options: ParseOptions,
      self: AST.Transformation
    ) => Effect.Effect<FilterReturnType, never, RD>
  ): filterEffect<S, RD>
} = dual(2, <S extends Schema.Any, FD>(
  self: S,
  f: (
    a: Types.NoInfer<Schema.Type<S>>,
    options: ParseOptions,
    self: AST.Transformation
  ) => Effect.Effect<FilterReturnType, never, FD>
): filterEffect<S, FD> =>
  transformOrFail(
    self,
    typeSchema(self),
    {
      strict: true,
      decode: (i, options, ast) =>
        ParseResult.flatMap(
          f(i, options, ast),
          (filterReturnType) =>
            option_.match(toFilterParseIssue(filterReturnType, ast, i), {
              onNone: () => ParseResult.succeed(i),
              onSome: ParseResult.fail
            })
        ),
      encode: (a) => ParseResult.succeed(a)
    }
  ))

/**
 * @category api interface
 * @since 3.10.0
 */
export interface transformOrFail<From extends Schema.All, To extends Schema.All, R = never> extends
  AnnotableClass<
    transformOrFail<From, To, R>,
    Schema.Type<To>,
    Schema.Encoded<From>,
    Schema.Context<From> | Schema.Context<To> | R
  >
{
  readonly from: From
  readonly to: To
}

function makeTransformationClass<From extends Schema.Any, To extends Schema.Any, R>(
  from: From,
  to: To,
  ast: AST.AST
): transformOrFail<From, To, R> {
  return class TransformationClass
    extends make<Schema.Type<To>, Schema.Encoded<From>, Schema.Context<From> | Schema.Context<To> | R>(ast)
  {
    static override annotations(annotations: Annotations.Schema<Schema.Type<To>>) {
      return makeTransformationClass<From, To, R>(
        this.from,
        this.to,
        mergeSchemaAnnotations(this.ast, annotations)
      )
    }

    static from = from

    static to = to
  }
}

/**
 * Create a new `Schema` by transforming the input and output of an existing `Schema`
 * using the provided decoding functions.
 *
 * @category transformations
 * @since 3.10.0
 */
export const transformOrFail: {
  /**
   * Create a new `Schema` by transforming the input and output of an existing `Schema`
   * using the provided decoding functions.
   *
   * @category transformations
   * @since 3.10.0
   */
  <To extends Schema.Any, From extends Schema.Any, RD, RE>(
    to: To,
    options: {
      readonly decode: (
        fromA: Schema.Type<From>,
        options: ParseOptions,
        ast: AST.Transformation,
        fromI: Schema.Encoded<From>
      ) => Effect.Effect<Schema.Encoded<To>, ParseResult.ParseIssue, RD>
      readonly encode: (
        toI: Schema.Encoded<To>,
        options: ParseOptions,
        ast: AST.Transformation,
        toA: Schema.Type<To>
      ) => Effect.Effect<Schema.Type<From>, ParseResult.ParseIssue, RE>
      readonly strict?: true
    } | {
      readonly decode: (
        fromA: Schema.Type<From>,
        options: ParseOptions,
        ast: AST.Transformation,
        fromI: Schema.Encoded<From>
      ) => Effect.Effect<unknown, ParseResult.ParseIssue, RD>
      readonly encode: (
        toI: Schema.Encoded<To>,
        options: ParseOptions,
        ast: AST.Transformation,
        toA: Schema.Type<To>
      ) => Effect.Effect<unknown, ParseResult.ParseIssue, RE>
      readonly strict: false
    }
  ): (from: From) => transformOrFail<From, To, RD | RE>
  /**
   * Create a new `Schema` by transforming the input and output of an existing `Schema`
   * using the provided decoding functions.
   *
   * @category transformations
   * @since 3.10.0
   */
  <To extends Schema.Any, From extends Schema.Any, RD, RE>(
    from: From,
    to: To,
    options: {
      readonly decode: (
        fromA: Schema.Type<From>,
        options: ParseOptions,
        ast: AST.Transformation,
        fromI: Schema.Encoded<From>
      ) => Effect.Effect<Schema.Encoded<To>, ParseResult.ParseIssue, RD>
      readonly encode: (
        toI: Schema.Encoded<To>,
        options: ParseOptions,
        ast: AST.Transformation,
        toA: Schema.Type<To>
      ) => Effect.Effect<Schema.Type<From>, ParseResult.ParseIssue, RE>
      readonly strict?: true
    } | {
      readonly decode: (
        fromA: Schema.Type<From>,
        options: ParseOptions,
        ast: AST.Transformation,
        fromI: Schema.Encoded<From>
      ) => Effect.Effect<unknown, ParseResult.ParseIssue, RD>
      readonly encode: (
        toI: Schema.Encoded<To>,
        options: ParseOptions,
        ast: AST.Transformation,
        toA: Schema.Type<To>
      ) => Effect.Effect<unknown, ParseResult.ParseIssue, RE>
      readonly strict: false
    }
  ): transformOrFail<From, To, RD | RE>
} = dual((args) => isSchema(args[0]) && isSchema(args[1]), <FromA, FromI, FromR, ToA, ToI, ToR, RD, RE>(
  from: Schema<FromA, FromI, FromR>,
  to: Schema<ToA, ToI, ToR>,
  options: {
    readonly decode: (
      fromA: FromA,
      options: ParseOptions,
      ast: AST.Transformation,
      fromI: FromI
    ) => Effect.Effect<ToI, ParseResult.ParseIssue, RD>
    readonly encode: (
      toI: ToI,
      options: ParseOptions,
      ast: AST.Transformation,
      toA: ToA
    ) => Effect.Effect<FromA, ParseResult.ParseIssue, RE>
  }
): Schema<ToA, FromI, FromR | ToR | RD | RE> =>
  makeTransformationClass(
    from,
    to,
    new AST.Transformation(
      from.ast,
      to.ast,
      new AST.FinalTransformation(options.decode, options.encode)
    )
  ))

/**
 * @category api interface
 * @since 3.10.0
 */
export interface transform<From extends Schema.All, To extends Schema.All> extends transformOrFail<From, To> {
  annotations(annotations: Annotations.Schema<Schema.Type<To>>): transform<From, To>
}

/**
 * Create a new `Schema` by transforming the input and output of an existing `Schema`
 * using the provided mapping functions.
 *
 * @category transformations
 * @since 3.10.0
 */
export const transform: {
  /**
   * Create a new `Schema` by transforming the input and output of an existing `Schema`
   * using the provided mapping functions.
   *
   * @category transformations
   * @since 3.10.0
   */
  <To extends Schema.Any, From extends Schema.Any>(
    to: To,
    options: {
      readonly decode: (fromA: Schema.Type<From>, fromI: Schema.Encoded<From>) => Schema.Encoded<To>
      readonly encode: (toI: Schema.Encoded<To>, toA: Schema.Type<To>) => Schema.Type<From>
      readonly strict?: true
    } | {
      readonly decode: (fromA: Schema.Type<From>, fromI: Schema.Encoded<From>) => unknown
      readonly encode: (toI: Schema.Encoded<To>, toA: Schema.Type<To>) => unknown
      readonly strict: false
    }
  ): (from: From) => transform<From, To>
  /**
   * Create a new `Schema` by transforming the input and output of an existing `Schema`
   * using the provided mapping functions.
   *
   * @category transformations
   * @since 3.10.0
   */
  <To extends Schema.Any, From extends Schema.Any>(
    from: From,
    to: To,
    options: {
      readonly decode: (fromA: Schema.Type<From>, fromI: Schema.Encoded<From>) => Schema.Encoded<To>
      readonly encode: (toI: Schema.Encoded<To>, toA: Schema.Type<To>) => Schema.Type<From>
      readonly strict?: true
    } | {
      readonly decode: (fromA: Schema.Type<From>, fromI: Schema.Encoded<From>) => unknown
      readonly encode: (toI: Schema.Encoded<To>, toA: Schema.Type<To>) => unknown
      readonly strict: false
    }
  ): transform<From, To>
} = dual(
  (args) => isSchema(args[0]) && isSchema(args[1]),
  <FromA, FromI, FromR, ToA, ToI, ToR>(
    from: Schema<FromA, FromI, FromR>,
    to: Schema<ToA, ToI, ToR>,
    options: {
      readonly decode: (fromA: FromA, fromI: FromI) => ToI
      readonly encode: (toI: ToI, toA: ToA) => FromA
    }
  ): Schema<ToA, FromI, FromR | ToR> =>
    transformOrFail(
      from,
      to,
      {
        strict: true,
        decode: (fromA, _options, _ast, toA) => ParseResult.succeed(options.decode(fromA, toA)),
        encode: (toI, _options, _ast, toA) => ParseResult.succeed(options.encode(toI, toA))
      }
    )
)

/**
 * @category api interface
 * @since 3.10.0
 */
export interface transformLiteral<Type extends AST.LiteralValue, Encoded extends AST.LiteralValue>
  extends transform<Literal<[Encoded]>, Literal<[Type]>>
{
  annotations(annotations: Annotations.Schema<Type>): transformLiteral<Type, Encoded>
}

/**
 * Creates a new `Schema` which transforms literal values.
 *
 * @example
 * ```ts
 * import * as assert from "node:assert"
 * import * as S from "effect/Schema"
 *
 * const schema = S.transformLiteral(0, "a")
 *
 * assert.deepStrictEqual(S.decodeSync(schema)(0), "a")
 * ```
 *
 * @category constructors
 * @since 3.10.0
 */
export function transformLiteral<Encoded extends AST.LiteralValue, Type extends AST.LiteralValue>(
  from: Encoded,
  to: Type
): transformLiteral<Type, Encoded> {
  return transform(Literal(from), Literal(to), {
    strict: true,
    decode: () => to,
    encode: () => from
  })
}

/**
 * Creates a new `Schema` which maps between corresponding literal values.
 *
 * @example
 * ```ts
 * import * as assert from "node:assert"
 * import * as S from "effect/Schema"
 *
 * const Animal = S.transformLiterals(
 *   [0, "cat"],
 *   [1, "dog"],
 *   [2, "cow"]
 * )
 *
 * assert.deepStrictEqual(S.decodeSync(Animal)(1), "dog")
 * ```
 *
 * @category constructors
 * @since 3.10.0
 */
export function transformLiterals<const A extends AST.Members<readonly [from: AST.LiteralValue, to: AST.LiteralValue]>>(
  ...pairs: A
): Union<{ -readonly [I in keyof A]: transformLiteral<A[I][1], A[I][0]> }>
export function transformLiterals<Encoded extends AST.LiteralValue, Type extends AST.LiteralValue>(
  pairs: [Encoded, Type]
): transformLiteral<Type, Encoded>
export function transformLiterals<
  const A extends ReadonlyArray<readonly [from: AST.LiteralValue, to: AST.LiteralValue]>
>(...pairs: A): Schema<A[number][1], A[number][0]>
export function transformLiterals<
  const A extends ReadonlyArray<readonly [from: AST.LiteralValue, to: AST.LiteralValue]>
>(...pairs: A): Schema<A[number][1], A[number][0]> {
  return Union(...pairs.map(([from, to]) => transformLiteral(from, to)))
}

/**
 * Attaches a property signature with the specified key and value to the schema.
 * This API is useful when you want to add a property to your schema which doesn't describe the shape of the input,
 * but rather maps to another schema, for example when you want to add a discriminant to a simple union.
 *
 * @example
 * ```ts
 * import * as assert from "node:assert"
 * import * as S from "effect/Schema"
 * import { pipe } from "effect/Function"
 *
 * const Circle = S.Struct({ radius: S.Number })
 * const Square = S.Struct({ sideLength: S.Number })
 * const Shape = S.Union(
 *   Circle.pipe(S.attachPropertySignature("kind", "circle")),
 *   Square.pipe(S.attachPropertySignature("kind", "square"))
 * )
 *
 * assert.deepStrictEqual(S.decodeSync(Shape)({ radius: 10 }), {
 *   kind: "circle",
 *   radius: 10
 * })
 * ```
 *
 * @category combinators
 * @since 3.10.0
 */
export const attachPropertySignature: {
  /**
   * Attaches a property signature with the specified key and value to the schema.
   * This API is useful when you want to add a property to your schema which doesn't describe the shape of the input,
   * but rather maps to another schema, for example when you want to add a discriminant to a simple union.
   *
   * @example
   * ```ts
   * import * as assert from "node:assert"
   * import * as S from "effect/Schema"
   * import { pipe } from "effect/Function"
   *
   * const Circle = S.Struct({ radius: S.Number })
   * const Square = S.Struct({ sideLength: S.Number })
   * const Shape = S.Union(
   *   Circle.pipe(S.attachPropertySignature("kind", "circle")),
   *   Square.pipe(S.attachPropertySignature("kind", "square"))
   * )
   *
   * assert.deepStrictEqual(S.decodeSync(Shape)({ radius: 10 }), {
   *   kind: "circle",
   *   radius: 10
   * })
   * ```
   *
   * @category combinators
   * @since 3.10.0
   */
  <K extends PropertyKey, V extends AST.LiteralValue | symbol, A>(
    key: K,
    value: V,
    annotations?: Annotations.Schema<Simplify<A & { readonly [k in K]: V }>>
  ): <I, R>(
    schema: SchemaClass<A, I, R>
  ) => SchemaClass<Simplify<A & { readonly [k in K]: V }>, I, R>
  /**
   * Attaches a property signature with the specified key and value to the schema.
   * This API is useful when you want to add a property to your schema which doesn't describe the shape of the input,
   * but rather maps to another schema, for example when you want to add a discriminant to a simple union.
   *
   * @example
   * ```ts
   * import * as assert from "node:assert"
   * import * as S from "effect/Schema"
   * import { pipe } from "effect/Function"
   *
   * const Circle = S.Struct({ radius: S.Number })
   * const Square = S.Struct({ sideLength: S.Number })
   * const Shape = S.Union(
   *   Circle.pipe(S.attachPropertySignature("kind", "circle")),
   *   Square.pipe(S.attachPropertySignature("kind", "square"))
   * )
   *
   * assert.deepStrictEqual(S.decodeSync(Shape)({ radius: 10 }), {
   *   kind: "circle",
   *   radius: 10
   * })
   * ```
   *
   * @category combinators
   * @since 3.10.0
   */
  <A, I, R, K extends PropertyKey, V extends AST.LiteralValue | symbol>(
    schema: Schema<A, I, R>,
    key: K,
    value: V,
    annotations?: Annotations.Schema<Simplify<A & { readonly [k in K]: V }>>
  ): SchemaClass<Simplify<A & { readonly [k in K]: V }>, I, R>
} = dual(
  (args) => isSchema(args[0]),
  <A, I, R, K extends PropertyKey, V extends AST.LiteralValue | symbol>(
    schema: Schema<A, I, R>,
    key: K,
    value: V,
    annotations?: Annotations.Schema<Simplify<A & { readonly [k in K]: V }>>
  ): SchemaClass<Simplify<A & { readonly [k in K]: V }>, I, R> => {
    const ast = extend(
      typeSchema(schema),
      Struct({ [key]: Predicate.isSymbol(value) ? UniqueSymbolFromSelf(value) : Literal(value) })
    ).ast
    return make(
      new AST.Transformation(
        schema.ast,
        annotations ? mergeSchemaAnnotations(ast, annotations) : ast,
        new AST.TypeLiteralTransformation(
          [
            new AST.PropertySignatureTransformation(
              key,
              key,
              () => option_.some(value),
              () => option_.none()
            )
          ]
        )
      )
    )
  }
)

/**
 * @category annotations
 * @since 3.10.0
 */
export declare namespace Annotations {
  /**
   * @category annotations
   * @since 3.10.0
   */
  export interface Doc<A> extends AST.Annotations {
    readonly title?: AST.TitleAnnotation
    readonly description?: AST.DescriptionAnnotation
    readonly documentation?: AST.DocumentationAnnotation
    readonly examples?: AST.ExamplesAnnotation<A>
    readonly default?: AST.DefaultAnnotation<A>
  }

  /**
   * @since 3.10.0
   */
  export interface Schema<A, TypeParameters extends ReadonlyArray<any> = readonly []> extends Doc<A> {
    readonly identifier?: AST.IdentifierAnnotation
    readonly message?: AST.MessageAnnotation
    readonly schemaId?: AST.SchemaIdAnnotation
    readonly jsonSchema?: AST.JSONSchemaAnnotation
    readonly arbitrary?: ArbitraryAnnotation<A, TypeParameters>
    readonly pretty?: pretty_.PrettyAnnotation<A, TypeParameters>
    readonly equivalence?: AST.EquivalenceAnnotation<A, TypeParameters>
    readonly concurrency?: AST.ConcurrencyAnnotation
    readonly batching?: AST.BatchingAnnotation
    readonly parseIssueTitle?: AST.ParseIssueTitleAnnotation
    readonly parseOptions?: AST.ParseOptions
    readonly decodingFallback?: AST.DecodingFallbackAnnotation<A>
  }

  /**
   * @since 3.11.6
   */
  export interface GenericSchema<A> extends Schema<A> {
    readonly arbitrary?: (..._: any) => LazyArbitrary<A>
    readonly pretty?: (..._: any) => pretty_.Pretty<A>
    readonly equivalence?: (..._: any) => Equivalence.Equivalence<A>
  }

  // TODO(4.0): replace `readonly [P]` with `readonly []`
  /**
   * @since 3.10.0
   */
  export interface Filter<A, P = A> extends Schema<A, readonly [P]> {}
}

/**
 * Merges a set of new annotations with existing ones, potentially overwriting
 * any duplicates.
 *
 * @category annotations
 * @since 3.10.0
 */
export const annotations: {
  /**
   * Merges a set of new annotations with existing ones, potentially overwriting
   * any duplicates.
   *
   * @category annotations
   * @since 3.10.0
   */
  <S extends Annotable.All>(annotations: Annotations.GenericSchema<Schema.Type<S>>): (self: S) => Annotable.Self<S>
  /**
   * Merges a set of new annotations with existing ones, potentially overwriting
   * any duplicates.
   *
   * @category annotations
   * @since 3.10.0
   */
  <S extends Annotable.All>(self: S, annotations: Annotations.GenericSchema<Schema.Type<S>>): Annotable.Self<S>
} = dual(
  2,
  <A, I, R>(self: Schema<A, I, R>, annotations: Annotations.GenericSchema<A>): Schema<A, I, R> =>
    self.annotations(annotations)
)

type Rename<A, M> = {
  [
    K in keyof A as K extends keyof M ? M[K] extends PropertyKey ? M[K]
      : never
      : K
  ]: A[K]
}

/**
 * @category renaming
 * @since 3.10.0
 */
export const rename: {
  /**
   * @category renaming
   * @since 3.10.0
   */
  <
    A,
    const M extends
      & { readonly [K in keyof A]?: PropertyKey }
      & { readonly [K in Exclude<keyof M, keyof A>]: never }
  >(mapping: M): <I, R>(self: Schema<A, I, R>) => SchemaClass<Simplify<Rename<A, M>>, I, R>
  /**
   * @category renaming
   * @since 3.10.0
   */
  <
    A,
    I,
    R,
    const M extends
      & { readonly [K in keyof A]?: PropertyKey }
      & { readonly [K in Exclude<keyof M, keyof A>]: never }
  >(self: Schema<A, I, R>, mapping: M): SchemaClass<Simplify<Rename<A, M>>, I, R>
} = dual(
  2,
  <
    A,
    I,
    R,
    const M extends
      & { readonly [K in keyof A]?: PropertyKey }
      & { readonly [K in Exclude<keyof M, keyof A>]: never }
  >(
    self: Schema<A, I, R>,
    mapping: M
  ): SchemaClass<Simplify<Rename<A, M>>, I, R> => make(AST.rename(self.ast, mapping))
)

/**
 * @category schema id
 * @since 3.10.0
 */
export const TrimmedSchemaId: unique symbol = Symbol.for("effect/SchemaId/Trimmed")

/**
 * Verifies that a string contains no leading or trailing whitespaces.
 *
 * Note. This combinator does not make any transformations, it only validates.
 * If what you were looking for was a combinator to trim strings, then check out the `trim` combinator.
 *
 * @category string filters
 * @since 3.10.0
 */
export const trimmed = <S extends Schema.Any>(
  annotations?: Annotations.Filter<Schema.Type<S>>
) =>
<A extends string>(self: S & Schema<A, Schema.Encoded<S>, Schema.Context<S>>): filter<S> =>
  self.pipe(
    filter((a) => a === a.trim(), {
      schemaId: TrimmedSchemaId,
      title: "trimmed",
      description: "a string with no leading or trailing whitespace",
      jsonSchema: { pattern: "^\\S[\\s\\S]*\\S$|^\\S$|^$" },
      ...annotations
    })
  )

/**
 * @category schema id
 * @since 3.10.0
 */
export const MaxLengthSchemaId: unique symbol = schemaId_.MaxLengthSchemaId

/**
 * @category schema id
 * @since 3.10.0
 */
export type MaxLengthSchemaId = typeof MaxLengthSchemaId

/**
 * @category string filters
 * @since 3.10.0
 */
export const maxLength =
  <S extends Schema.Any>(maxLength: number, annotations?: Annotations.Filter<Schema.Type<S>>) =>
  <A extends string>(self: S & Schema<A, Schema.Encoded<S>, Schema.Context<S>>): filter<S> =>
    self.pipe(
      filter(
        (a) => a.length <= maxLength,
        {
          schemaId: MaxLengthSchemaId,
          title: `maxLength(${maxLength})`,
          description: `a string at most ${maxLength} character(s) long`,
          jsonSchema: { maxLength },
          ...annotations
        }
      )
    )

/**
 * @category schema id
 * @since 3.10.0
 */
export const MinLengthSchemaId: unique symbol = schemaId_.MinLengthSchemaId

/**
 * @category schema id
 * @since 3.10.0
 */
export type MinLengthSchemaId = typeof MinLengthSchemaId

/**
 * @category string filters
 * @since 3.10.0
 */
export const minLength = <S extends Schema.Any>(
  minLength: number,
  annotations?: Annotations.Filter<Schema.Type<S>>
) =>
<A extends string>(self: S & Schema<A, Schema.Encoded<S>, Schema.Context<S>>): filter<S> =>
  self.pipe(
    filter(
      (a) => a.length >= minLength,
      {
        schemaId: MinLengthSchemaId,
        title: `minLength(${minLength})`,
        description: `a string at least ${minLength} character(s) long`,
        jsonSchema: { minLength },
        ...annotations
      }
    )
  )

/**
 * @category schema id
 * @since 3.10.0
 */
export const LengthSchemaId: unique symbol = schemaId_.LengthSchemaId

/**
 * @category schema id
 * @since 3.10.0
 */
export type LengthSchemaId = typeof LengthSchemaId

/**
 * @category string filters
 * @since 3.10.0
 */
export const length = <S extends Schema.Any>(
  length: number | { readonly min: number; readonly max: number },
  annotations?: Annotations.Filter<Schema.Type<S>>
) =>
<A extends string>(self: S & Schema<A, Schema.Encoded<S>, Schema.Context<S>>): filter<S> => {
  const minLength = Predicate.isObject(length) ? Math.max(0, Math.floor(length.min)) : Math.max(0, Math.floor(length))
  const maxLength = Predicate.isObject(length) ? Math.max(minLength, Math.floor(length.max)) : minLength
  if (minLength !== maxLength) {
    return self.pipe(
      filter((a) => a.length >= minLength && a.length <= maxLength, {
        schemaId: LengthSchemaId,
        title: `length({ min: ${minLength}, max: ${maxLength})`,
        description: `a string at least ${minLength} character(s) and at most ${maxLength} character(s) long`,
        jsonSchema: { minLength, maxLength },
        ...annotations
      })
    )
  }
  return self.pipe(
    filter((a) => a.length === minLength, {
      schemaId: LengthSchemaId,
      title: `length(${minLength})`,
      description: minLength === 1 ? `a single character` : `a string ${minLength} character(s) long`,
      jsonSchema: { minLength, maxLength: minLength },
      ...annotations
    })
  )
}

/**
 * @category schema id
 * @since 3.10.0
 */
export const PatternSchemaId: unique symbol = Symbol.for("effect/SchemaId/Pattern")

/**
 * @category string filters
 * @since 3.10.0
 */
export const pattern = <S extends Schema.Any>(
  regex: RegExp,
  annotations?: Annotations.Filter<Schema.Type<S>>
) =>
<A extends string>(self: S & Schema<A, Schema.Encoded<S>, Schema.Context<S>>): filter<S> => {
  const source = regex.source
  return self.pipe(
    filter(
      (a) => {
        // The following line ensures that `lastIndex` is reset to `0` in case the user has specified the `g` flag
        regex.lastIndex = 0
        return regex.test(a)
      },
      {
        schemaId: PatternSchemaId,
        [PatternSchemaId]: { regex },
        // title: `pattern(/${source}/)`, // avoiding this because it can be very long
        description: `a string matching the pattern ${source}`,
        jsonSchema: { pattern: source },
        ...annotations
      }
    )
  )
}

/**
 * @category schema id
 * @since 3.10.0
 */
export const StartsWithSchemaId: unique symbol = Symbol.for("effect/SchemaId/StartsWith")

/**
 * @category string filters
 * @since 3.10.0
 */
export const startsWith = <S extends Schema.Any>(
  startsWith: string,
  annotations?: Annotations.Filter<Schema.Type<S>>
) =>
<A extends string>(self: S & Schema<A, Schema.Encoded<S>, Schema.Context<S>>): filter<S> => {
  const formatted = JSON.stringify(startsWith)
  return self.pipe(
    filter(
      (a) => a.startsWith(startsWith),
      {
        schemaId: StartsWithSchemaId,
        [StartsWithSchemaId]: { startsWith },
        title: `startsWith(${formatted})`,
        description: `a string starting with ${formatted}`,
        jsonSchema: { pattern: `^${startsWith}` },
        ...annotations
      }
    )
  )
}

/**
 * @category schema id
 * @since 3.10.0
 */
export const EndsWithSchemaId: unique symbol = Symbol.for("effect/SchemaId/EndsWith")

/**
 * @category string filters
 * @since 3.10.0
 */
export const endsWith = <S extends Schema.Any>(
  endsWith: string,
  annotations?: Annotations.Filter<Schema.Type<S>>
) =>
<A extends string>(self: S & Schema<A, Schema.Encoded<S>, Schema.Context<S>>): filter<S> => {
  const formatted = JSON.stringify(endsWith)
  return self.pipe(
    filter(
      (a) => a.endsWith(endsWith),
      {
        schemaId: EndsWithSchemaId,
        [EndsWithSchemaId]: { endsWith },
        title: `endsWith(${formatted})`,
        description: `a string ending with ${formatted}`,
        jsonSchema: { pattern: `^.*${endsWith}$` },
        ...annotations
      }
    )
  )
}

/**
 * @category schema id
 * @since 3.10.0
 */
export const IncludesSchemaId: unique symbol = Symbol.for("effect/SchemaId/Includes")

/**
 * @category string filters
 * @since 3.10.0
 */
export const includes = <S extends Schema.Any>(
  searchString: string,
  annotations?: Annotations.Filter<Schema.Type<S>>
) =>
<A extends string>(self: S & Schema<A, Schema.Encoded<S>, Schema.Context<S>>): filter<S> => {
  const formatted = JSON.stringify(searchString)
  return self.pipe(
    filter(
      (a) => a.includes(searchString),
      {
        schemaId: IncludesSchemaId,
        [IncludesSchemaId]: { includes: searchString },
        title: `includes(${formatted})`,
        description: `a string including ${formatted}`,
        jsonSchema: { pattern: `.*${searchString}.*` },
        ...annotations
      }
    )
  )
}

/**
 * @category schema id
 * @since 3.10.0
 */
export const LowercasedSchemaId: unique symbol = Symbol.for("effect/SchemaId/Lowercased")

/**
 * Verifies that a string is lowercased.
 *
 * @category string filters
 * @since 3.10.0
 */
export const lowercased =
  <S extends Schema.Any>(annotations?: Annotations.Filter<Schema.Type<S>>) =>
  <A extends string>(self: S & Schema<A, Schema.Encoded<S>, Schema.Context<S>>): filter<S> =>
    self.pipe(
      filter((a) => a === a.toLowerCase(), {
        schemaId: LowercasedSchemaId,
        title: "lowercased",
        description: "a lowercase string",
        jsonSchema: { pattern: "^[^A-Z]*$" },
        ...annotations
      })
    )

/**
 * @category string constructors
 * @since 3.10.0
 */
export class Lowercased extends String$.pipe(
  lowercased({ identifier: "Lowercased" })
) {}

/**
 * @category schema id
 * @since 3.10.0
 */
export const UppercasedSchemaId: unique symbol = Symbol.for("effect/SchemaId/Uppercased")

/**
 * Verifies that a string is uppercased.
 *
 * @category string filters
 * @since 3.10.0
 */
export const uppercased =
  <S extends Schema.Any>(annotations?: Annotations.Filter<Schema.Type<S>>) =>
  <A extends string>(self: S & Schema<A, Schema.Encoded<S>, Schema.Context<S>>): filter<S> =>
    self.pipe(
      filter((a) => a === a.toUpperCase(), {
        schemaId: UppercasedSchemaId,
        title: "uppercased",
        description: "an uppercase string",
        jsonSchema: { pattern: "^[^a-z]*$" },
        ...annotations
      })
    )

/**
 * @category string constructors
 * @since 3.10.0
 */
export class Uppercased extends String$.pipe(
  uppercased({ identifier: "Uppercased" })
) {}

/**
 * @category schema id
 * @since 3.10.0
 */
export const CapitalizedSchemaId: unique symbol = Symbol.for("effect/SchemaId/Capitalized")

/**
 * Verifies that a string is capitalized.
 *
 * @category string filters
 * @since 3.10.0
 */
export const capitalized =
  <S extends Schema.Any>(annotations?: Annotations.Filter<Schema.Type<S>>) =>
  <A extends string>(self: S & Schema<A, Schema.Encoded<S>, Schema.Context<S>>): filter<S> =>
    self.pipe(
      filter((a) => a[0]?.toUpperCase() === a[0], {
        schemaId: CapitalizedSchemaId,
        title: "capitalized",
        description: "a capitalized string",
        jsonSchema: { pattern: "^[^a-z]?.*$" },
        ...annotations
      })
    )

/**
 * @category string constructors
 * @since 3.10.0
 */
export class Capitalized extends String$.pipe(
  capitalized({ identifier: "Capitalized" })
) {}

/**
 * @category schema id
 * @since 3.10.0
 */
export const UncapitalizedSchemaId: unique symbol = Symbol.for("effect/SchemaId/Uncapitalized")

/**
 * Verifies that a string is uncapitalized.
 *
 * @category string filters
 * @since 3.10.0
 */
export const uncapitalized =
  <S extends Schema.Any>(annotations?: Annotations.Filter<Schema.Type<S>>) =>
  <A extends string>(self: S & Schema<A, Schema.Encoded<S>, Schema.Context<S>>): filter<S> =>
    self.pipe(
      filter((a) => a[0]?.toLowerCase() === a[0], {
        schemaId: UncapitalizedSchemaId,
        title: "uncapitalized",
        description: "a uncapitalized string",
        jsonSchema: { pattern: "^[^A-Z]?.*$" },
        ...annotations
      })
    )

/**
 * @category string constructors
 * @since 3.10.0
 */
export class Uncapitalized extends String$.pipe(
  uncapitalized({ identifier: "Uncapitalized" })
) {}

/**
 * A schema representing a single character.
 *
 * @category string constructors
 * @since 3.10.0
 */
export class Char extends String$.pipe(length(1, { identifier: "Char" })) {}

/**
 * @category string filters
 * @since 3.10.0
 */
export const nonEmptyString = <S extends Schema.Any>(
  annotations?: Annotations.Filter<Schema.Type<S>>
): <A extends string>(self: S & Schema<A, Schema.Encoded<S>, Schema.Context<S>>) => filter<S> =>
  minLength(1, {
    title: "nonEmptyString",
    description: "a non empty string",
    ...annotations
  })

/**
 * This schema converts a string to lowercase.
 *
 * @category string transformations
 * @since 3.10.0
 */
export class Lowercase extends transform(
  String$.annotations({ description: "a string that will be converted to lowercase" }),
  Lowercased,
  {
    strict: true,
    decode: (i) => i.toLowerCase(),
    encode: identity
  }
).annotations({ identifier: "Lowercase" }) {}

/**
 * This schema converts a string to uppercase.
 *
 * @category string transformations
 * @since 3.10.0
 */
export class Uppercase extends transform(
  String$.annotations({ description: "a string that will be converted to uppercase" }),
  Uppercased,
  {
    strict: true,
    decode: (i) => i.toUpperCase(),
    encode: identity
  }
).annotations({ identifier: "Uppercase" }) {}

/**
 * This schema converts a string to capitalized one.
 *
 * @category string transformations
 * @since 3.10.0
 */
export class Capitalize extends transform(
  String$.annotations({ description: "a string that will be converted to a capitalized format" }),
  Capitalized,
  {
    strict: true,
    decode: (i) => string_.capitalize(i),
    encode: identity
  }
).annotations({ identifier: "Capitalize" }) {}

/**
 * This schema converts a string to uncapitalized one.
 *
 * @category string transformations
 * @since 3.10.0
 */
export class Uncapitalize extends transform(
  String$.annotations({ description: "a string that will be converted to an uncapitalized format" }),
  Uncapitalized,
  {
    strict: true,
    decode: (i) => string_.uncapitalize(i),
    encode: identity
  }
).annotations({ identifier: "Uncapitalize" }) {}

/**
 * @category string constructors
 * @since 3.10.0
 */
export class Trimmed extends String$.pipe(
  trimmed({ identifier: "Trimmed" })
) {}

/**
 * Useful for validating strings that must contain meaningful characters without
 * leading or trailing whitespace.
 *
 * @example
 * ```ts
 * import { Schema } from "effect"
 *
 * console.log(Schema.decodeOption(Schema.NonEmptyTrimmedString)("")) // Option.none()
 * console.log(Schema.decodeOption(Schema.NonEmptyTrimmedString)(" a ")) // Option.none()
 * console.log(Schema.decodeOption(Schema.NonEmptyTrimmedString)("a")) // Option.some("a")
 * ```
 *
 * @category string constructors
 * @since 3.10.0
 */
export class NonEmptyTrimmedString extends Trimmed.pipe(
  nonEmptyString({ identifier: "NonEmptyTrimmedString" })
) {}

/**
 * This schema allows removing whitespaces from the beginning and end of a string.
 *
 * @category string transformations
 * @since 3.10.0
 */
export class Trim extends transform(
  String$.annotations({ description: "a string that will be trimmed" }),
  Trimmed,
  {
    strict: true,
    decode: (i) => i.trim(),
    encode: identity
  }
).annotations({ identifier: "Trim" }) {}

/**
 * Returns a schema that allows splitting a string into an array of strings.
 *
 * @category string transformations
 * @since 3.10.0
 */
export const split = (separator: string): transform<SchemaClass<string>, Array$<typeof String$>> =>
  transform(
    String$.annotations({ description: "a string that will be split" }),
    Array$(String$),
    {
      strict: true,
      decode: (i) => i.split(separator),
      encode: (a) => a.join(separator)
    }
  )

/**
 * @since 3.10.0
 */
export type ParseJsonOptions = {
  readonly reviver?: Parameters<typeof JSON.parse>[1]
  readonly replacer?: Parameters<typeof JSON.stringify>[1]
  readonly space?: Parameters<typeof JSON.stringify>[2]
}

const getErrorMessage = (e: unknown): string => e instanceof Error ? e.message : String(e)

const getParseJsonTransformation = (options?: ParseJsonOptions): SchemaClass<unknown, string> =>
  transformOrFail(
    String$.annotations({ description: "a string to be decoded into JSON" }),
    Unknown,
    {
      strict: true,
      decode: (i, _, ast) =>
        ParseResult.try({
          try: () => JSON.parse(i, options?.reviver),
          catch: (e) => new ParseResult.Type(ast, i, getErrorMessage(e))
        }),
      encode: (a, _, ast) =>
        ParseResult.try({
          try: () => JSON.stringify(a, options?.replacer, options?.space),
          catch: (e) => new ParseResult.Type(ast, a, getErrorMessage(e))
        })
    }
  ).annotations({
    title: "parseJson",
    schemaId: AST.ParseJsonSchemaId
  })

/**
 * The `ParseJson` combinator provides a method to convert JSON strings into the `unknown` type using the underlying
 * functionality of `JSON.parse`. It also utilizes `JSON.stringify` for encoding.
 *
 * You can optionally provide a `ParseJsonOptions` to configure both `JSON.parse` and `JSON.stringify` executions.
 *
 * Optionally, you can pass a schema `Schema<A, I, R>` to obtain an `A` type instead of `unknown`.
 *
 * @example
 * ```ts
 * import * as assert from "node:assert"
 * import * as Schema from "effect/Schema"
 *
 * assert.deepStrictEqual(Schema.decodeUnknownSync(Schema.parseJson())(`{"a":"1"}`), { a: "1" })
 * assert.deepStrictEqual(Schema.decodeUnknownSync(Schema.parseJson(Schema.Struct({ a: Schema.NumberFromString })))(`{"a":"1"}`), { a: 1 })
 * ```
 *
 * @category string transformations
 * @since 3.10.0
 */
export const parseJson: {
  /**
   * The `ParseJson` combinator provides a method to convert JSON strings into the `unknown` type using the underlying
   * functionality of `JSON.parse`. It also utilizes `JSON.stringify` for encoding.
   *
   * You can optionally provide a `ParseJsonOptions` to configure both `JSON.parse` and `JSON.stringify` executions.
   *
   * Optionally, you can pass a schema `Schema<A, I, R>` to obtain an `A` type instead of `unknown`.
   *
   * @example
   * ```ts
   * import * as assert from "node:assert"
   * import * as Schema from "effect/Schema"
   *
   * assert.deepStrictEqual(Schema.decodeUnknownSync(Schema.parseJson())(`{"a":"1"}`), { a: "1" })
   * assert.deepStrictEqual(Schema.decodeUnknownSync(Schema.parseJson(Schema.Struct({ a: Schema.NumberFromString })))(`{"a":"1"}`), { a: 1 })
   * ```
   *
   * @category string transformations
   * @since 3.10.0
   */
  <S extends Schema.Any>(schema: S, options?: ParseJsonOptions): transform<SchemaClass<unknown, string>, S>
  /**
   * The `ParseJson` combinator provides a method to convert JSON strings into the `unknown` type using the underlying
   * functionality of `JSON.parse`. It also utilizes `JSON.stringify` for encoding.
   *
   * You can optionally provide a `ParseJsonOptions` to configure both `JSON.parse` and `JSON.stringify` executions.
   *
   * Optionally, you can pass a schema `Schema<A, I, R>` to obtain an `A` type instead of `unknown`.
   *
   * @example
   * ```ts
   * import * as assert from "node:assert"
   * import * as Schema from "effect/Schema"
   *
   * assert.deepStrictEqual(Schema.decodeUnknownSync(Schema.parseJson())(`{"a":"1"}`), { a: "1" })
   * assert.deepStrictEqual(Schema.decodeUnknownSync(Schema.parseJson(Schema.Struct({ a: Schema.NumberFromString })))(`{"a":"1"}`), { a: 1 })
   * ```
   *
   * @category string transformations
   * @since 3.10.0
   */
  (options?: ParseJsonOptions): SchemaClass<unknown, string>
} = <A, I, R>(schemaOrOptions?: Schema<A, I, R> | ParseJsonOptions, o?: ParseJsonOptions) =>
  isSchema(schemaOrOptions)
    ? compose(parseJson(o), schemaOrOptions) as any
    : getParseJsonTransformation(schemaOrOptions as ParseJsonOptions | undefined)

/**
 * @category string constructors
 * @since 3.10.0
 */
export class NonEmptyString extends String$.pipe(
  nonEmptyString({ identifier: "NonEmptyString" })
) {}

/**
 * @category schema id
 * @since 3.10.0
 */
export const UUIDSchemaId: unique symbol = Symbol.for("effect/SchemaId/UUID")

const uuidRegexp = /^[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}$/i

/**
 * Represents a Universally Unique Identifier (UUID).
 *
 * This schema ensures that the provided string adheres to the standard UUID format.
 *
 * @category string constructors
 * @since 3.10.0
 */
export class UUID extends String$.pipe(
  pattern(uuidRegexp, {
    schemaId: UUIDSchemaId,
    identifier: "UUID",
    jsonSchema: {
      format: "uuid",
      pattern: uuidRegexp.source
    },
    description: "a Universally Unique Identifier",
    arbitrary: (): LazyArbitrary<string> => (fc) => fc.uuid()
  })
) {}

/**
 * @category schema id
 * @since 3.10.0
 */
export const ULIDSchemaId: unique symbol = Symbol.for("effect/SchemaId/ULID")

const ulidRegexp = /^[0-7][0-9A-HJKMNP-TV-Z]{25}$/i

/**
 * Represents a Universally Unique Lexicographically Sortable Identifier (ULID).
 *
 * ULIDs are designed to be compact, URL-safe, and ordered, making them suitable for use as identifiers.
 * This schema ensures that the provided string adheres to the standard ULID format.
 *
 * @category string constructors
 * @since 3.10.0
 */
export class ULID extends String$.pipe(
  pattern(ulidRegexp, {
    schemaId: ULIDSchemaId,
    identifier: "ULID",
    description: "a Universally Unique Lexicographically Sortable Identifier",
    arbitrary: (): LazyArbitrary<string> => (fc) => fc.ulid()
  })
) {}

/**
 * Defines a schema that represents a `URL` object.
 *
 * @category URL constructors
 * @since 3.11.0
 */
export class URLFromSelf extends instanceOf(URL, {
  identifier: "URLFromSelf",
  arbitrary: (): LazyArbitrary<URL> => (fc) => fc.webUrl().map((s) => new URL(s)),
  pretty: () => (url) => url.toString()
}) {}

/** @ignore */
class URL$ extends transformOrFail(
  String$.annotations({ description: "a string to be decoded into a URL" }),
  URLFromSelf,
  {
    strict: true,
    decode: (i, _, ast) =>
      ParseResult.try({
        try: () => new URL(i),
        catch: (e) =>
          new ParseResult.Type(
            ast,
            i,
            `Unable to decode ${JSON.stringify(i)} into a URL. ${getErrorMessage(e)}`
          )
      }),
    encode: (a) => ParseResult.succeed(a.toString())
  }
).annotations({
  identifier: "URL",
  pretty: () => (url) => url.toString()
}) {}

export {
  /**
   * Defines a schema that attempts to convert a `string` to a `URL` object using
   * the `new URL` constructor.
   *
   * @category URL transformations
   * @since 3.11.0
   */
  URL$ as URL
}

/**
 * @category schema id
 * @since 3.10.0
 */
export const FiniteSchemaId: unique symbol = schemaId_.FiniteSchemaId

/**
 * @category schema id
 * @since 3.10.0
 */
export type FiniteSchemaId = typeof FiniteSchemaId

/**
 * Ensures that the provided value is a finite number (excluding NaN, +Infinity, and -Infinity).
 *
 * @category number filters
 * @since 3.10.0
 */
export const finite =
  <S extends Schema.Any>(annotations?: Annotations.Filter<Schema.Type<S>>) =>
  <A extends number>(self: S & Schema<A, Schema.Encoded<S>, Schema.Context<S>>): filter<S> =>
    self.pipe(
      filter(Number.isFinite, {
        schemaId: FiniteSchemaId,
        title: "finite",
        description: "a finite number",
        jsonSchema: { "type": "number" },
        ...annotations
      })
    )

/**
 * @category schema id
 * @since 3.10.0
 */
export const GreaterThanSchemaId: unique symbol = schemaId_.GreaterThanSchemaId

/**
 * @category schema id
 * @since 3.10.0
 */
export type GreaterThanSchemaId = typeof GreaterThanSchemaId

/**
 * This filter checks whether the provided number is greater than the specified minimum.
 *
 * @category number filters
 * @since 3.10.0
 */
export const greaterThan = <S extends Schema.Any>(
  exclusiveMinimum: number,
  annotations?: Annotations.Filter<Schema.Type<S>>
) =>
<A extends number>(self: S & Schema<A, Schema.Encoded<S>, Schema.Context<S>>): filter<S> =>
  self.pipe(
    filter((a) => a > exclusiveMinimum, {
      schemaId: GreaterThanSchemaId,
      title: `greaterThan(${exclusiveMinimum})`,
      description: exclusiveMinimum === 0 ? "a positive number" : `a number greater than ${exclusiveMinimum}`,
      jsonSchema: { exclusiveMinimum },
      ...annotations
    })
  )

/**
 * @category schema id
 * @since 3.10.0
 */
export const GreaterThanOrEqualToSchemaId: unique symbol = schemaId_.GreaterThanOrEqualToSchemaId

/**
 * @category schema id
 * @since 3.10.0
 */
export type GreaterThanOrEqualToSchemaId = typeof GreaterThanOrEqualToSchemaId

/**
 * This filter checks whether the provided number is greater than or equal to the specified minimum.
 *
 * @category number filters
 * @since 3.10.0
 */
export const greaterThanOrEqualTo = <S extends Schema.Any>(
  minimum: number,
  annotations?: Annotations.Filter<Schema.Type<S>>
) =>
<A extends number>(self: S & Schema<A, Schema.Encoded<S>, Schema.Context<S>>): filter<S> =>
  self.pipe(
    filter((a) => a >= minimum, {
      schemaId: GreaterThanOrEqualToSchemaId,
      title: `greaterThanOrEqualTo(${minimum})`,
      description: minimum === 0 ? "a non-negative number" : `a number greater than or equal to ${minimum}`,
      jsonSchema: { minimum },
      ...annotations
    })
  )

/**
 * @category schema id
 * @since 3.10.0
 */
export const MultipleOfSchemaId: unique symbol = Symbol.for("effect/SchemaId/MultipleOf")

/**
 * @category number filters
 * @since 3.10.0
 */
export const multipleOf = <S extends Schema.Any>(
  divisor: number,
  annotations?: Annotations.Filter<Schema.Type<S>>
) =>
<A extends number>(self: S & Schema<A, Schema.Encoded<S>, Schema.Context<S>>): filter<S> => {
  const positiveDivisor = Math.abs(divisor) // spec requires positive divisor
  return self.pipe(
    filter((a) => number_.remainder(a, divisor) === 0, {
      schemaId: MultipleOfSchemaId,
      title: `multipleOf(${positiveDivisor})`,
      description: `a number divisible by ${positiveDivisor}`,
      jsonSchema: { multipleOf: positiveDivisor },
      ...annotations
    })
  )
}

/**
 * @category schema id
 * @since 3.10.0
 */
export const IntSchemaId: unique symbol = schemaId_.IntSchemaId

/**
 * @category schema id
 * @since 3.10.0
 */
export type IntSchemaId = typeof IntSchemaId

/**
 * Ensures that the provided value is an integer number (excluding NaN, +Infinity, and -Infinity).
 *
 * @category number filters
 * @since 3.10.0
 */
export const int =
  <S extends Schema.Any>(annotations?: Annotations.Filter<Schema.Type<S>>) =>
  <A extends number>(self: S & Schema<A, Schema.Encoded<S>, Schema.Context<S>>): filter<S> =>
    self.pipe(
      filter((a) => Number.isSafeInteger(a), {
        schemaId: IntSchemaId,
        title: "int",
        description: "an integer",
        jsonSchema: { type: "integer" },
        ...annotations
      })
    )

/**
 * @category schema id
 * @since 3.10.0
 */
export const LessThanSchemaId: unique symbol = schemaId_.LessThanSchemaId

/**
 * @category schema id
 * @since 3.10.0
 */
export type LessThanSchemaId = typeof LessThanSchemaId

/**
 * This filter checks whether the provided number is less than the specified maximum.
 *
 * @category number filters
 * @since 3.10.0
 */
export const lessThan =
  <S extends Schema.Any>(exclusiveMaximum: number, annotations?: Annotations.Filter<Schema.Type<S>>) =>
  <A extends number>(self: S & Schema<A, Schema.Encoded<S>, Schema.Context<S>>): filter<S> =>
    self.pipe(
      filter((a) => a < exclusiveMaximum, {
        schemaId: LessThanSchemaId,
        title: `lessThan(${exclusiveMaximum})`,
        description: exclusiveMaximum === 0 ? "a negative number" : `a number less than ${exclusiveMaximum}`,
        jsonSchema: { exclusiveMaximum },
        ...annotations
      })
    )

/**
 * @category schema id
 * @since 3.10.0
 */
export const LessThanOrEqualToSchemaId: unique symbol = schemaId_.LessThanOrEqualToSchemaId

/**
 * @category schema id
 * @since 3.10.0
 */
export type LessThanOrEqualToSchemaId = typeof LessThanOrEqualToSchemaId

/**
 * This schema checks whether the provided number is less than or equal to the specified maximum.
 *
 * @category number filters
 * @since 3.10.0
 */
export const lessThanOrEqualTo = <S extends Schema.Any>(
  maximum: number,
  annotations?: Annotations.Filter<Schema.Type<S>>
) =>
<A extends number>(self: S & Schema<A, Schema.Encoded<S>, Schema.Context<S>>): filter<S> =>
  self.pipe(
    filter((a) => a <= maximum, {
      schemaId: LessThanOrEqualToSchemaId,
      title: `lessThanOrEqualTo(${maximum})`,
      description: maximum === 0 ? "a non-positive number" : `a number less than or equal to ${maximum}`,
      jsonSchema: { maximum },
      ...annotations
    })
  )

/**
 * @category schema id
 * @since 3.10.0
 */
export const BetweenSchemaId: unique symbol = schemaId_.BetweenSchemaId

/**
 * @category schema id
 * @since 3.10.0
 */
export type BetweenSchemaId = typeof BetweenSchemaId

/**
 * This filter checks whether the provided number falls within the specified minimum and maximum values.
 *
 * @category number filters
 * @since 3.10.0
 */
export const between = <S extends Schema.Any>(
  minimum: number,
  maximum: number,
  annotations?: Annotations.Filter<Schema.Type<S>>
) =>
<A extends number>(self: S & Schema<A, Schema.Encoded<S>, Schema.Context<S>>): filter<S> =>
  self.pipe(
    filter((a) => a >= minimum && a <= maximum, {
      schemaId: BetweenSchemaId,
      title: `between(${minimum}, ${maximum})`,
      description: `a number between ${minimum} and ${maximum}`,
      jsonSchema: { minimum, maximum },
      ...annotations
    })
  )

/**
 * @category schema id
 * @since 3.10.0
 */
export const NonNaNSchemaId: unique symbol = schemaId_.NonNaNSchemaId

/**
 * @category schema id
 * @since 3.10.0
 */
export type NonNaNSchemaId = typeof NonNaNSchemaId

/**
 * @category number filters
 * @since 3.10.0
 */
export const nonNaN =
  <S extends Schema.Any>(annotations?: Annotations.Filter<Schema.Type<S>>) =>
  <A extends number>(self: S & Schema<A, Schema.Encoded<S>, Schema.Context<S>>): filter<S> =>
    self.pipe(
      filter((a) => !Number.isNaN(a), {
        schemaId: NonNaNSchemaId,
        title: "nonNaN",
        description: "a number excluding NaN",
        ...annotations
      })
    )

/**
 * @category number filters
 * @since 3.10.0
 */
export const positive = <S extends Schema.Any>(
  annotations?: Annotations.Filter<Schema.Type<S>>
): <A extends number>(self: S & Schema<A, Schema.Encoded<S>, Schema.Context<S>>) => filter<S> =>
  greaterThan(0, { title: "positive", ...annotations })

/**
 * @category number filters
 * @since 3.10.0
 */
export const negative = <S extends Schema.Any>(
  annotations?: Annotations.Filter<Schema.Type<S>>
): <A extends number>(self: S & Schema<A, Schema.Encoded<S>, Schema.Context<S>>) => filter<S> =>
  lessThan(0, { title: "negative", ...annotations })

/**
 * @category number filters
 * @since 3.10.0
 */
export const nonPositive = <S extends Schema.Any>(
  annotations?: Annotations.Filter<Schema.Type<S>>
): <A extends number>(self: S & Schema<A, Schema.Encoded<S>, Schema.Context<S>>) => filter<S> =>
  lessThanOrEqualTo(0, { title: "nonPositive", ...annotations })

/**
 * @category number filters
 * @since 3.10.0
 */
export const nonNegative = <S extends Schema.Any>(
  annotations?: Annotations.Filter<Schema.Type<S>>
): <A extends number>(self: S & Schema<A, Schema.Encoded<S>, Schema.Context<S>>) => filter<S> =>
  greaterThanOrEqualTo(0, { title: "nonNegative", ...annotations })

/**
 * Clamps a number between a minimum and a maximum value.
 *
 * @category number transformations
 * @since 3.10.0
 */
export const clamp = (minimum: number, maximum: number) =>
<S extends Schema.Any, A extends number>(
  self: S & Schema<A, Schema.Encoded<S>, Schema.Context<S>>
): transform<S, filter<SchemaClass<A>>> => {
  return transform(
    self,
    typeSchema(self).pipe(between(minimum, maximum)),
    {
      strict: false,
      decode: (i) => number_.clamp(i, { minimum, maximum }),
      encode: identity
    }
  )
}

/**
 * Transforms a `string` into a `number` by parsing the string using the `parse`
 * function of the `effect/Number` module.
 *
 * It returns an error if the value can't be converted (for example when
 * non-numeric characters are provided).
 *
 * The following special string values are supported: "NaN", "Infinity",
 * "-Infinity".
 *
 * @category number transformations
 * @since 3.10.0
 */
export function parseNumber<S extends Schema.Any, A extends string>(
  self: S & Schema<A, Schema.Encoded<S>, Schema.Context<S>>
): transformOrFail<S, typeof Number$> {
  return transformOrFail(
    self,
    Number$,
    {
      strict: false,
      decode: (i, _, ast) =>
        ParseResult.fromOption(
          number_.parse(i),
          () => new ParseResult.Type(ast, i, `Unable to decode ${JSON.stringify(i)} into a number`)
        ),
      encode: (a) => ParseResult.succeed(String(a))
    }
  )
}

/**
 * This schema transforms a `string` into a `number` by parsing the string using the `parse` function of the `effect/Number` module.
 *
 * It returns an error if the value can't be converted (for example when non-numeric characters are provided).
 *
 * The following special string values are supported: "NaN", "Infinity", "-Infinity".
 *
 * @category number transformations
 * @since 3.10.0
 */
export class NumberFromString extends parseNumber(String$.annotations({
  description: "a string to be decoded into a number"
})).annotations({ identifier: "NumberFromString" }) {}

/**
 * @category number constructors
 * @since 3.10.0
 */
export class Finite extends Number$.pipe(finite({ identifier: "Finite" })) {}

/**
 * @category number constructors
 * @since 3.10.0
 */
export class Int extends Number$.pipe(int({ identifier: "Int" })) {}

/**
 * @category number constructors
 * @since 3.10.0
 */
export class NonNaN extends Number$.pipe(nonNaN({ identifier: "NonNaN" })) {}

/**
 * @category number constructors
 * @since 3.10.0
 */
export class Positive extends Number$.pipe(
  positive({ identifier: "Positive" })
) {}

/**
 * @category number constructors
 * @since 3.10.0
 */
export class Negative extends Number$.pipe(
  negative({ identifier: "Negative" })
) {}

/**
 * @category number constructors
 * @since 3.10.0
 */
export class NonPositive extends Number$.pipe(
  nonPositive({ identifier: "NonPositive" })
) {}

/**
 * @category number constructors
 * @since 3.10.0
 */
export class NonNegative extends Number$.pipe(
  nonNegative({ identifier: "NonNegative" })
) {}

/**
 * @category schema id
 * @since 3.10.0
 */
export const JsonNumberSchemaId: unique symbol = schemaId_.JsonNumberSchemaId

/**
 * @category schema id
 * @since 3.10.0
 */
export type JsonNumberSchemaId = typeof JsonNumberSchemaId

/**
 * The `JsonNumber` is a schema for representing JSON numbers. It ensures that the provided value is a valid
 * number by filtering out `NaN` and `(+/-) Infinity`. This is useful when you want to validate and represent numbers in JSON
 * format.
 *
 * @example
 * ```ts
 * import * as assert from "node:assert"
 * import * as Schema from "effect/Schema"
 *
 * const is = Schema.is(S.JsonNumber)
 *
 * assert.deepStrictEqual(is(42), true)
 * assert.deepStrictEqual(is(Number.NaN), false)
 * assert.deepStrictEqual(is(Number.POSITIVE_INFINITY), false)
 * assert.deepStrictEqual(is(Number.NEGATIVE_INFINITY), false)
 * ```
 *
 * @category number constructors
 * @since 3.10.0
 */
export class JsonNumber extends Number$.pipe(
  finite({
    schemaId: JsonNumberSchemaId,
    identifier: "JsonNumber"
  })
) {}

/**
 * @category boolean transformations
 * @since 3.10.0
 */
export class Not extends transform(Boolean$.annotations({ description: "a boolean that will be negated" }), Boolean$, {
  strict: true,
  decode: (i) => boolean_.not(i),
  encode: (a) => boolean_.not(a)
}) {}

const encodeSymbol = (sym: symbol, ast: AST.AST) => {
  const key = Symbol.keyFor(sym)
  return key === undefined
    ? ParseResult.fail(
      new ParseResult.Type(ast, sym, `Unable to encode a unique symbol ${String(sym)} into a string`)
    )
    : ParseResult.succeed(key)
}

const decodeSymbol = (s: string) => ParseResult.succeed(Symbol.for(s))

/** @ignore */
class Symbol$ extends transformOrFail(
  String$.annotations({ description: "a string to be decoded into a globally shared symbol" }),
  SymbolFromSelf,
  {
    strict: false,
    decode: (i) => decodeSymbol(i),
    encode: (a, _, ast) => encodeSymbol(a, ast)
  }
).annotations({ identifier: "Symbol" }) {}

export {
  /**
   * Converts a string key into a globally shared symbol.
   *
   * @category symbol transformations
   * @since 3.10.0
   */
  Symbol$ as Symbol
}

/**
 * @category schema id
 * @since 3.10.0
 */
export const GreaterThanBigIntSchemaId: unique symbol = schemaId_.GreaterThanBigintSchemaId

/**
 * @category schema id
 * @since 3.10.0
 */
export type GreaterThanBigIntSchemaId = typeof GreaterThanBigIntSchemaId

/**
 * @category bigint filters
 * @since 3.10.0
 */
export const greaterThanBigInt = <S extends Schema.Any>(
  min: bigint,
  annotations?: Annotations.Filter<Schema.Type<S>>
) =>
<A extends bigint>(self: S & Schema<A, Schema.Encoded<S>, Schema.Context<S>>): filter<S> =>
  self.pipe(
    filter((a) => a > min, {
      schemaId: GreaterThanBigIntSchemaId,
      [GreaterThanBigIntSchemaId]: { min },
      title: `greaterThanBigInt(${min})`,
      description: min === 0n ? "a positive bigint" : `a bigint greater than ${min}n`,
      ...annotations
    })
  )

/**
 * @category schema id
 * @since 3.10.0
 */
export const GreaterThanOrEqualToBigIntSchemaId: unique symbol = schemaId_.GreaterThanOrEqualToBigIntSchemaId

/**
 * @category schema id
 * @since 3.10.0
 */
export type GreaterThanOrEqualToBigIntSchemaId = typeof GreaterThanOrEqualToBigIntSchemaId

/**
 * @category bigint filters
 * @since 3.10.0
 */
export const greaterThanOrEqualToBigInt = <S extends Schema.Any>(
  min: bigint,
  annotations?: Annotations.Filter<Schema.Type<S>>
) =>
<A extends bigint>(self: S & Schema<A, Schema.Encoded<S>, Schema.Context<S>>): filter<S> =>
  self.pipe(
    filter((a) => a >= min, {
      schemaId: GreaterThanOrEqualToBigIntSchemaId,
      [GreaterThanOrEqualToBigIntSchemaId]: { min },
      title: `greaterThanOrEqualToBigInt(${min})`,
      description: min === 0n
        ? "a non-negative bigint"
        : `a bigint greater than or equal to ${min}n`,
      ...annotations
    })
  )

/**
 * @category schema id
 * @since 3.10.0
 */
export const LessThanBigIntSchemaId: unique symbol = schemaId_.LessThanBigIntSchemaId

/**
 * @category schema id
 * @since 3.10.0
 */
export type LessThanBigIntSchemaId = typeof LessThanBigIntSchemaId

/**
 * @category bigint filters
 * @since 3.10.0
 */
export const lessThanBigInt = <S extends Schema.Any>(
  max: bigint,
  annotations?: Annotations.Filter<Schema.Type<S>>
) =>
<A extends bigint>(self: S & Schema<A, Schema.Encoded<S>, Schema.Context<S>>): filter<S> =>
  self.pipe(
    filter((a) => a < max, {
      schemaId: LessThanBigIntSchemaId,
      [LessThanBigIntSchemaId]: { max },
      title: `lessThanBigInt(${max})`,
      description: max === 0n ? "a negative bigint" : `a bigint less than ${max}n`,
      ...annotations
    })
  )

/**
 * @category schema id
 * @since 3.10.0
 */
export const LessThanOrEqualToBigIntSchemaId: unique symbol = schemaId_.LessThanOrEqualToBigIntSchemaId

/**
 * @category schema id
 * @since 3.10.0
 */
export type LessThanOrEqualToBigIntSchemaId = typeof LessThanOrEqualToBigIntSchemaId

/**
 * @category bigint filters
 * @since 3.10.0
 */
export const lessThanOrEqualToBigInt = <S extends Schema.Any>(
  max: bigint,
  annotations?: Annotations.Filter<Schema.Type<S>>
) =>
<A extends bigint>(self: S & Schema<A, Schema.Encoded<S>, Schema.Context<S>>): filter<S> =>
  self.pipe(
    filter((a) => a <= max, {
      schemaId: LessThanOrEqualToBigIntSchemaId,
      [LessThanOrEqualToBigIntSchemaId]: { max },
      title: `lessThanOrEqualToBigInt(${max})`,
      description: max === 0n ? "a non-positive bigint" : `a bigint less than or equal to ${max}n`,
      ...annotations
    })
  )

/**
 * @category schema id
 * @since 3.10.0
 */
export const BetweenBigIntSchemaId: unique symbol = schemaId_.BetweenBigintSchemaId

/**
 * @category schema id
 * @since 3.10.0
 */
export type BetweenBigIntSchemaId = typeof BetweenBigIntSchemaId

/**
 * @category bigint filters
 * @since 3.10.0
 */
export const betweenBigInt = <S extends Schema.Any>(
  min: bigint,
  max: bigint,
  annotations?: Annotations.Filter<Schema.Type<S>>
) =>
<A extends bigint>(self: S & Schema<A, Schema.Encoded<S>, Schema.Context<S>>): filter<S> =>
  self.pipe(
    filter((a) => a >= min && a <= max, {
      schemaId: BetweenBigIntSchemaId,
      [BetweenBigIntSchemaId]: { min, max },
      title: `betweenBigInt(${min}, ${max})`,
      description: `a bigint between ${min}n and ${max}n`,
      ...annotations
    })
  )

/**
 * @category bigint filters
 * @since 3.10.0
 */
export const positiveBigInt = <S extends Schema.Any>(
  annotations?: Annotations.Filter<Schema.Type<S>>
): <A extends bigint>(self: S & Schema<A, Schema.Encoded<S>, Schema.Context<S>>) => filter<S> =>
  greaterThanBigInt(0n, { title: "positiveBigInt", ...annotations })

/**
 * @category bigint filters
 * @since 3.10.0
 */
export const negativeBigInt = <S extends Schema.Any>(
  annotations?: Annotations.Filter<Schema.Type<S>>
): <A extends bigint>(self: S & Schema<A, Schema.Encoded<S>, Schema.Context<S>>) => filter<S> =>
  lessThanBigInt(0n, { title: "negativeBigInt", ...annotations })

/**
 * @category bigint filters
 * @since 3.10.0
 */
export const nonNegativeBigInt = <S extends Schema.Any>(
  annotations?: Annotations.Filter<Schema.Type<S>>
): <A extends bigint>(self: S & Schema<A, Schema.Encoded<S>, Schema.Context<S>>) => filter<S> =>
  greaterThanOrEqualToBigInt(0n, { title: "nonNegativeBigInt", ...annotations })

/**
 * @category bigint filters
 * @since 3.10.0
 */
export const nonPositiveBigInt = <S extends Schema.Any>(
  annotations?: Annotations.Filter<Schema.Type<S>>
): <A extends bigint>(self: S & Schema<A, Schema.Encoded<S>, Schema.Context<S>>) => filter<S> =>
  lessThanOrEqualToBigInt(0n, { title: "nonPositiveBigInt", ...annotations })

/**
 * Clamps a bigint between a minimum and a maximum value.
 *
 * @category bigint transformations
 * @since 3.10.0
 */
export const clampBigInt = (minimum: bigint, maximum: bigint) =>
<S extends Schema.Any, A extends bigint>(
  self: S & Schema<A, Schema.Encoded<S>, Schema.Context<S>>
): transform<S, filter<SchemaClass<A>>> =>
  transform(
    self,
    self.pipe(typeSchema, betweenBigInt(minimum, maximum)),
    {
      strict: false,
      decode: (i) => bigInt_.clamp(i, { minimum, maximum }),
      encode: identity
    }
  )

/** @ignore */
class BigInt$ extends transformOrFail(
  String$.annotations({ description: "a string to be decoded into a bigint" }),
  BigIntFromSelf,
  {
    strict: true,
    decode: (i, _, ast) =>
      ParseResult.fromOption(
        bigInt_.fromString(i),
        () => new ParseResult.Type(ast, i, `Unable to decode ${JSON.stringify(i)} into a bigint`)
      ),
    encode: (a) => ParseResult.succeed(String(a))
  }
).annotations({ identifier: "BigInt" }) {}

export {
  /**
   * This schema transforms a `string` into a `bigint` by parsing the string using the `BigInt` function.
   *
   * It returns an error if the value can't be converted (for example when non-numeric characters are provided).
   *
   * @category bigint transformations
   * @since 3.10.0
   */
  BigInt$ as BigInt
}

/**
 * @category bigint constructors
 * @since 3.10.0
 */
export const PositiveBigIntFromSelf: filter<Schema<bigint>> = BigIntFromSelf.pipe(
  positiveBigInt({ identifier: "PositiveBigintFromSelf" })
)

/**
 * @category bigint constructors
 * @since 3.10.0
 */
export const PositiveBigInt: filter<Schema<bigint, string>> = BigInt$.pipe(
  positiveBigInt({ identifier: "PositiveBigint" })
)

/**
 * @category bigint constructors
 * @since 3.10.0
 */
export const NegativeBigIntFromSelf: filter<Schema<bigint>> = BigIntFromSelf.pipe(
  negativeBigInt({ identifier: "NegativeBigintFromSelf" })
)

/**
 * @category bigint constructors
 * @since 3.10.0
 */
export const NegativeBigInt: filter<Schema<bigint, string>> = BigInt$.pipe(
  negativeBigInt({ identifier: "NegativeBigint" })
)

/**
 * @category bigint constructors
 * @since 3.10.0
 */
export const NonPositiveBigIntFromSelf: filter<Schema<bigint>> = BigIntFromSelf.pipe(
  nonPositiveBigInt({ identifier: "NonPositiveBigintFromSelf" })
)

/**
 * @category bigint constructors
 * @since 3.10.0
 */
export const NonPositiveBigInt: filter<Schema<bigint, string>> = BigInt$.pipe(
  nonPositiveBigInt({ identifier: "NonPositiveBigint" })
)

/**
 * @category bigint constructors
 * @since 3.10.0
 */
export const NonNegativeBigIntFromSelf: filter<Schema<bigint>> = BigIntFromSelf.pipe(
  nonNegativeBigInt({ identifier: "NonNegativeBigintFromSelf" })
)

/**
 * @category bigint constructors
 * @since 3.10.0
 */
export const NonNegativeBigInt: filter<Schema<bigint, string>> = BigInt$.pipe(
  nonNegativeBigInt({ identifier: "NonNegativeBigint" })
)

/**
 * This schema transforms a `number` into a `bigint` by parsing the number using the `BigInt` function.
 *
 * It returns an error if the value can't be safely encoded as a `number` due to being out of range.
 *
 * @category bigint transformations
 * @since 3.10.0
 */
export class BigIntFromNumber extends transformOrFail(
  Number$.annotations({ description: "a number to be decoded into a bigint" }),
  BigIntFromSelf.pipe(betweenBigInt(BigInt(Number.MIN_SAFE_INTEGER), BigInt(Number.MAX_SAFE_INTEGER))),
  {
    strict: true,
    decode: (i, _, ast) =>
      ParseResult.fromOption(
        bigInt_.fromNumber(i),
        () => new ParseResult.Type(ast, i, `Unable to decode ${i} into a bigint`)
      ),
    encode: (a, _, ast) =>
      ParseResult.fromOption(
        bigInt_.toNumber(a),
        () => new ParseResult.Type(ast, a, `Unable to encode ${a}n into a number`)
      )
  }
).annotations({ identifier: "BigIntFromNumber" }) {}

const redactedArbitrary = <A>(value: LazyArbitrary<A>): LazyArbitrary<redacted_.Redacted<A>> => (fc) =>
  value(fc).map(redacted_.make)

const toComposite = <A, R, B>(
  eff: Effect.Effect<A, ParseResult.ParseIssue, R>,
  onSuccess: (a: A) => B,
  ast: AST.AST,
  actual: unknown
): Effect.Effect<B, ParseResult.Composite, R> =>
  ParseResult.mapBoth(eff, {
    onFailure: (e) => new ParseResult.Composite(ast, actual, e),
    onSuccess
  })

const redactedParse = <A, R>(
  decodeUnknown: ParseResult.DecodeUnknown<A, R>
): ParseResult.DeclarationDecodeUnknown<redacted_.Redacted<A>, R> =>
(u, options, ast) =>
  redacted_.isRedacted(u) ?
    toComposite(decodeUnknown(redacted_.value(u), options), redacted_.make, ast, u) :
    ParseResult.fail(new ParseResult.Type(ast, u))

/**
 * @category api interface
 * @since 3.10.0
 */
export interface RedactedFromSelf<Value extends Schema.Any> extends
  AnnotableDeclare<
    RedactedFromSelf<Value>,
    redacted_.Redacted<Schema.Type<Value>>,
    redacted_.Redacted<Schema.Encoded<Value>>,
    [Value]
  >
{}

/**
 * @category Redacted constructors
 * @since 3.10.0
 */
export const RedactedFromSelf = <Value extends Schema.Any>(value: Value): RedactedFromSelf<Value> =>
  declare(
    [value],
    {
      decode: (value) => redactedParse(ParseResult.decodeUnknown(value)),
      encode: (value) => redactedParse(ParseResult.encodeUnknown(value))
    },
    {
      description: "Redacted(<redacted>)",
      pretty: () => () => "Redacted(<redacted>)",
      arbitrary: redactedArbitrary,
      equivalence: redacted_.getEquivalence
    }
  )

/**
 * @category api interface
 * @since 3.10.0
 */
export interface Redacted<Value extends Schema.Any>
  extends transform<Value, RedactedFromSelf<SchemaClass<Schema.Type<Value>>>>
{}

/**
 * A transformation that transform a `Schema<A, I, R>` into a
 * `RedactedFromSelf<A>`.
 *
 * @category Redacted transformations
 * @since 3.10.0
 */
export function Redacted<Value extends Schema.Any>(value: Value): Redacted<Value> {
  return transform(
    value,
    RedactedFromSelf(typeSchema(asSchema(value))),
    {
      strict: true,
      decode: (i) => redacted_.make(i),
      encode: (a) => redacted_.value(a)
    }
  )
}

/**
 * @category Duration constructors
 * @since 3.10.0
 */
export class DurationFromSelf extends declare(
  duration_.isDuration,
  {
    identifier: "DurationFromSelf",
    pretty: (): pretty_.Pretty<duration_.Duration> => String,
    arbitrary: (): LazyArbitrary<duration_.Duration> => (fc) =>
      fc.oneof(
        fc.constant(duration_.infinity),
        fc.bigInt({ min: 0n }).map((_) => duration_.nanos(_)),
        fc.maxSafeNat().map((_) => duration_.millis(_))
      ),
    equivalence: (): Equivalence.Equivalence<duration_.Duration> => duration_.Equivalence
  }
) {}

/**
 * A schema that transforms a non negative `bigint` into a `Duration`. Treats
 * the value as the number of nanoseconds.
 *
 * @category Duration transformations
 * @since 3.10.0
 */
export class DurationFromNanos extends transformOrFail(
  NonNegativeBigIntFromSelf.annotations({ description: "a bigint to be decoded into a Duration" }),
  DurationFromSelf.pipe(filter((duration) => duration_.isFinite(duration), { description: "a finite duration" })),
  {
    strict: true,
    decode: (i) => ParseResult.succeed(duration_.nanos(i)),
    encode: (a, _, ast) =>
      option_.match(duration_.toNanos(a), {
        onNone: () => ParseResult.fail(new ParseResult.Type(ast, a, `Unable to encode ${a} into a bigint`)),
        onSome: (nanos) => ParseResult.succeed(nanos)
      })
  }
).annotations({ identifier: "DurationFromNanos" }) {}

/**
 * A non-negative integer. +Infinity is excluded.
 *
 * @category number constructors
 * @since 3.11.10
 */
export const NonNegativeInt = NonNegative.pipe(int()).annotations({ identifier: "NonNegativeInt" })

/**
 * A schema that transforms a (possibly Infinite) non negative number into a
 * `Duration`. Treats the value as the number of milliseconds.
 *
 * @category Duration transformations
 * @since 3.10.0
 */
export class DurationFromMillis extends transform(
  NonNegative.annotations({
    description: "a non-negative number to be decoded into a Duration"
  }),
  DurationFromSelf,
  {
    strict: true,
    decode: (i) => duration_.millis(i),
    encode: (a) => duration_.toMillis(a)
  }
).annotations({ identifier: "DurationFromMillis" }) {}

const DurationValueMillis = TaggedStruct("Millis", { millis: NonNegativeInt })
const DurationValueNanos = TaggedStruct("Nanos", { nanos: BigInt$ })
const DurationValueInfinity = TaggedStruct("Infinity", {})
const durationValueInfinity = DurationValueInfinity.make({})

/**
 * @category Duration utils
 * @since 3.12.8
 */
export type DurationEncoded =
  | {
    readonly _tag: "Millis"
    readonly millis: number
  }
  | {
    readonly _tag: "Nanos"
    readonly nanos: string
  }
  | {
    readonly _tag: "Infinity"
  }

const DurationValue: Schema<duration_.DurationValue, DurationEncoded> = Union(
  DurationValueMillis,
  DurationValueNanos,
  DurationValueInfinity
).annotations({
  identifier: "DurationValue",
  description: "an JSON-compatible tagged union to be decoded into a Duration"
})

const FiniteHRTime = Tuple(
  element(NonNegativeInt).annotations({ title: "seconds" }),
  element(NonNegativeInt).annotations({ title: "nanos" })
).annotations({ identifier: "FiniteHRTime" })

const InfiniteHRTime = Tuple(Literal(-1), Literal(0)).annotations({ identifier: "InfiniteHRTime" })

const HRTime: Schema<readonly [seconds: number, nanos: number]> = Union(FiniteHRTime, InfiniteHRTime).annotations({
  identifier: "HRTime",
  description: "a tuple of seconds and nanos to be decoded into a Duration"
})

const isDurationValue = (u: duration_.DurationValue | typeof HRTime.Type): u is duration_.DurationValue =>
  typeof u === "object"

// TODO(4.0): remove HRTime union member
/**
 * A schema that converts a JSON-compatible tagged union into a `Duration`.
 *
 * @category Duration transformations
 * @since 3.10.0
 */
export class Duration extends transform(
  Union(DurationValue, HRTime),
  DurationFromSelf,
  {
    strict: true,
    decode: (i) => {
      if (isDurationValue(i)) {
        switch (i._tag) {
          case "Millis":
            return duration_.millis(i.millis)
          case "Nanos":
            return duration_.nanos(i.nanos)
          case "Infinity":
            return duration_.infinity
        }
      }
      const [seconds, nanos] = i
      return seconds === -1 ? duration_.infinity : duration_.nanos(BigInt(seconds) * BigInt(1e9) + BigInt(nanos))
    },
    encode: (a) => {
      switch (a.value._tag) {
        case "Millis":
          return DurationValueMillis.make({ millis: a.value.millis })
        case "Nanos":
          return DurationValueNanos.make({ nanos: a.value.nanos })
        case "Infinity":
          return durationValueInfinity
      }
    }
  }
).annotations({ identifier: "Duration" }) {}

/**
 * Clamps a `Duration` between a minimum and a maximum value.
 *
 * @category Duration transformations
 * @since 3.10.0
 */
export const clampDuration =
  (minimum: duration_.DurationInput, maximum: duration_.DurationInput) =>
  <S extends Schema.Any, A extends duration_.Duration>(
    self: S & Schema<A, Schema.Encoded<S>, Schema.Context<S>>
  ): transform<S, filter<SchemaClass<A>>> =>
    transform(
      self,
      self.pipe(typeSchema, betweenDuration(minimum, maximum)),
      {
        strict: false,
        decode: (i) => duration_.clamp(i, { minimum, maximum }),
        encode: identity
      }
    )

/**
 * @category schema id
 * @since 3.10.0
 */
export const LessThanDurationSchemaId: unique symbol = Symbol.for("effect/SchemaId/LessThanDuration")

/**
 * @category Duration filters
 * @since 3.10.0
 */
export const lessThanDuration = <S extends Schema.Any>(
  max: duration_.DurationInput,
  annotations?: Annotations.Filter<Schema.Type<S>>
) =>
<A extends duration_.Duration>(self: S & Schema<A, Schema.Encoded<S>, Schema.Context<S>>): filter<S> =>
  self.pipe(
    filter((a) => duration_.lessThan(a, max), {
      schemaId: LessThanDurationSchemaId,
      [LessThanDurationSchemaId]: { max },
      title: `lessThanDuration(${max})`,
      description: `a Duration less than ${duration_.decode(max)}`,
      ...annotations
    })
  )

/**
 * @category schema id
 * @since 3.10.0
 */
export const LessThanOrEqualToDurationSchemaId: unique symbol = Symbol.for(
  "effect/schema/LessThanOrEqualToDuration"
)

/**
 * @category Duration filters
 * @since 3.10.0
 */
export const lessThanOrEqualToDuration = <S extends Schema.Any>(
  max: duration_.DurationInput,
  annotations?: Annotations.Filter<Schema.Type<S>>
) =>
<A extends duration_.Duration>(self: S & Schema<A, Schema.Encoded<S>, Schema.Context<S>>): filter<S> =>
  self.pipe(
    filter((a) => duration_.lessThanOrEqualTo(a, max), {
      schemaId: LessThanDurationSchemaId,
      [LessThanDurationSchemaId]: { max },
      title: `lessThanOrEqualToDuration(${max})`,
      description: `a Duration less than or equal to ${duration_.decode(max)}`,
      ...annotations
    })
  )

/**
 * @category schema id
 * @since 3.10.0
 */
export const GreaterThanDurationSchemaId: unique symbol = Symbol.for("effect/SchemaId/GreaterThanDuration")

/**
 * @category Duration filters
 * @since 3.10.0
 */
export const greaterThanDuration = <S extends Schema.Any>(
  min: duration_.DurationInput,
  annotations?: Annotations.Filter<Schema.Type<S>>
) =>
<A extends duration_.Duration>(self: S & Schema<A, Schema.Encoded<S>, Schema.Context<S>>): filter<S> =>
  self.pipe(
    filter((a) => duration_.greaterThan(a, min), {
      schemaId: GreaterThanDurationSchemaId,
      [GreaterThanDurationSchemaId]: { min },
      title: `greaterThanDuration(${min})`,
      description: `a Duration greater than ${duration_.decode(min)}`,
      ...annotations
    })
  )

/**
 * @category schema id
 * @since 3.10.0
 */
export const GreaterThanOrEqualToDurationSchemaId: unique symbol = Symbol.for(
  "effect/schema/GreaterThanOrEqualToDuration"
)

/**
 * @category Duration filters
 * @since 3.10.0
 */
export const greaterThanOrEqualToDuration = <S extends Schema.Any>(
  min: duration_.DurationInput,
  annotations?: Annotations.Filter<Schema.Type<S>>
) =>
<A extends duration_.Duration>(self: S & Schema<A, Schema.Encoded<S>, Schema.Context<S>>): filter<S> =>
  self.pipe(
    filter((a) => duration_.greaterThanOrEqualTo(a, min), {
      schemaId: GreaterThanOrEqualToDurationSchemaId,
      [GreaterThanOrEqualToDurationSchemaId]: { min },
      title: `greaterThanOrEqualToDuration(${min})`,
      description: `a Duration greater than or equal to ${duration_.decode(min)}`,
      ...annotations
    })
  )

/**
 * @category schema id
 * @since 3.10.0
 */
export const BetweenDurationSchemaId: unique symbol = Symbol.for("effect/SchemaId/BetweenDuration")

/**
 * @category Duration filters
 * @since 3.10.0
 */
export const betweenDuration = <S extends Schema.Any>(
  minimum: duration_.DurationInput,
  maximum: duration_.DurationInput,
  annotations?: Annotations.Filter<Schema.Type<S>>
) =>
<A extends duration_.Duration>(self: S & Schema<A, Schema.Encoded<S>, Schema.Context<S>>): filter<S> =>
  self.pipe(
    filter((a) => duration_.between(a, { minimum, maximum }), {
      schemaId: BetweenDurationSchemaId,
      [BetweenDurationSchemaId]: { maximum, minimum },
      title: `betweenDuration(${minimum}, ${maximum})`,
      description: `a Duration between ${duration_.decode(minimum)} and ${duration_.decode(maximum)}`,
      ...annotations
    })
  )

/**
 * @category Uint8Array constructors
 * @since 3.10.0
 */
export class Uint8ArrayFromSelf extends declare(
  Predicate.isUint8Array,
  {
    identifier: "Uint8ArrayFromSelf",
    pretty: (): pretty_.Pretty<Uint8Array> => (u8arr) => `new Uint8Array(${JSON.stringify(Array.from(u8arr))})`,
    arbitrary: (): LazyArbitrary<Uint8Array> => (fc) => fc.uint8Array(),
    equivalence: (): Equivalence.Equivalence<Uint8Array> => array_.getEquivalence(Equal.equals) as any
  }
) {}

/**
 * @category number constructors
 * @since 3.11.10
 */
export class Uint8 extends Number$.pipe(
  between(0, 255, {
    identifier: "Uint8",
    description: "a 8-bit unsigned integer"
  })
) {}

/** @ignore */
class Uint8Array$ extends transform(
  Array$(Uint8).annotations({
    description: "an array of 8-bit unsigned integers to be decoded into a Uint8Array"
  }),
  Uint8ArrayFromSelf,
  {
    strict: true,
    decode: (i) => Uint8Array.from(i),
    encode: (a) => Array.from(a)
  }
).annotations({ identifier: "Uint8Array" }) {}

export {
  /**
   * A schema that transforms an array of numbers into a `Uint8Array`.
   *
   * @category Uint8Array transformations
   * @since 3.10.0
   */
  Uint8Array$ as Uint8Array
}

const makeUint8ArrayTransformation = (
  id: string,
  decode: (s: string) => either_.Either<Uint8Array, Encoding.DecodeException>,
  encode: (u: Uint8Array) => string
) =>
  transformOrFail(
    String$.annotations({ description: "a string to be decoded into a Uint8Array" }),
    Uint8ArrayFromSelf,
    {
      strict: true,
      decode: (i, _, ast) =>
        either_.mapLeft(
          decode(i),
          (decodeException) => new ParseResult.Type(ast, i, decodeException.message)
        ),
      encode: (a) => ParseResult.succeed(encode(a))
    }
  ).annotations({ identifier: id })

/**
 * Decodes a base64 (RFC4648) encoded string into a `Uint8Array`.
 *
 * @category Uint8Array transformations
 * @since 3.10.0
 */
export const Uint8ArrayFromBase64: Schema<Uint8Array, string> = makeUint8ArrayTransformation(
  "Uint8ArrayFromBase64",
  Encoding.decodeBase64,
  Encoding.encodeBase64
)

/**
 * Decodes a base64 (URL) encoded string into a `Uint8Array`.
 *
 * @category Uint8Array transformations
 * @since 3.10.0
 */
export const Uint8ArrayFromBase64Url: Schema<Uint8Array, string> = makeUint8ArrayTransformation(
  "Uint8ArrayFromBase64Url",
  Encoding.decodeBase64Url,
  Encoding.encodeBase64Url
)

/**
 * Decodes a hex encoded string into a `Uint8Array`.
 *
 * @category Uint8Array transformations
 * @since 3.10.0
 */
export const Uint8ArrayFromHex: Schema<Uint8Array, string> = makeUint8ArrayTransformation(
  "Uint8ArrayFromHex",
  Encoding.decodeHex,
  Encoding.encodeHex
)

const makeEncodingTransformation = (
  id: string,
  decode: (s: string) => either_.Either<string, Encoding.DecodeException>,
  encode: (u: string) => string
) =>
  transformOrFail(
    String$.annotations({
      description: `A string that is interpreted as being ${id}-encoded and will be decoded into a UTF-8 string`
    }),
    String$,
    {
      strict: true,
      decode: (i, _, ast) =>
        either_.mapLeft(
          decode(i),
          (decodeException) => new ParseResult.Type(ast, i, decodeException.message)
        ),
      encode: (a) => ParseResult.succeed(encode(a))
    }
  ).annotations({ identifier: `StringFrom${id}` })

/**
 * Decodes a base64 (RFC4648) encoded string into a UTF-8 string.
 *
 * @category string transformations
 * @since 3.10.0
 */
export const StringFromBase64: Schema<string> = makeEncodingTransformation(
  "Base64",
  Encoding.decodeBase64String,
  Encoding.encodeBase64
)

/**
 * Decodes a base64 (URL) encoded string into a UTF-8 string.
 *
 * @category string transformations
 * @since 3.10.0
 */
export const StringFromBase64Url: Schema<string> = makeEncodingTransformation(
  "Base64Url",
  Encoding.decodeBase64UrlString,
  Encoding.encodeBase64Url
)

/**
 * Decodes a hex encoded string into a UTF-8 string.
 *
 * @category string transformations
 * @since 3.10.0
 */
export const StringFromHex: Schema<string> = makeEncodingTransformation(
  "Hex",
  Encoding.decodeHexString,
  Encoding.encodeHex
)

/**
 * Decodes a URI component encoded string into a UTF-8 string.
 * Can be used to store data in a URL.
 *
 * @example
 * ```ts
 * import { Schema } from "effect"
 *
 * const PaginationSchema = Schema.Struct({
 *   maxItemPerPage: Schema.Number,
 *   page: Schema.Number
 * })
 *
 * const UrlSchema = Schema.compose(Schema.StringFromUriComponent, Schema.parseJson(PaginationSchema))
 *
 * console.log(Schema.encodeSync(UrlSchema)({ maxItemPerPage: 10, page: 1 }))
 * // Output: %7B%22maxItemPerPage%22%3A10%2C%22page%22%3A1%7D
 * ```
 *
 * @category string transformations
 * @since 3.12.0
 */
export const StringFromUriComponent = transformOrFail(
  String$.annotations({
    description: `A string that is interpreted as being UriComponent-encoded and will be decoded into a UTF-8 string`
  }),
  String$,
  {
    strict: true,
    decode: (i, _, ast) =>
      either_.mapLeft(
        Encoding.decodeUriComponent(i),
        (decodeException) => new ParseResult.Type(ast, i, decodeException.message)
      ),
    encode: (a, _, ast) =>
      either_.mapLeft(
        Encoding.encodeUriComponent(a),
        (encodeException) => new ParseResult.Type(ast, a, encodeException.message)
      )
  }
).annotations({ identifier: `StringFromUriComponent` })

/**
 * @category schema id
 * @since 3.10.0
 */
export const MinItemsSchemaId: unique symbol = schemaId_.MinItemsSchemaId

/**
 * @category schema id
 * @since 3.10.0
 */
export type MinItemsSchemaId = typeof MinItemsSchemaId

/**
 * @category ReadonlyArray filters
 * @since 3.10.0
 */
export const minItems = <S extends Schema.Any>(
  n: number,
  annotations?: Annotations.Filter<Schema.Type<S>>
) =>
<A extends ReadonlyArray<any>>(self: S & Schema<A, Schema.Encoded<S>, Schema.Context<S>>): filter<S> => {
  const minItems = Math.floor(n)
  if (minItems < 1) {
    throw new Error(
      errors_.getInvalidArgumentErrorMessage(`Expected an integer greater than or equal to 1, actual ${n}`)
    )
  }
  return self.pipe(
    filter(
      (a) => a.length >= minItems,
      {
        schemaId: MinItemsSchemaId,
        title: `minItems(${minItems})`,
        description: `an array of at least ${minItems} item(s)`,
        jsonSchema: { minItems },
        [AST.StableFilterAnnotationId]: true,
        ...annotations
      }
    )
  )
}

/**
 * @category schema id
 * @since 3.10.0
 */
export const MaxItemsSchemaId: unique symbol = schemaId_.MaxItemsSchemaId

/**
 * @category schema id
 * @since 3.10.0
 */
export type MaxItemsSchemaId = typeof MaxItemsSchemaId

/**
 * @category ReadonlyArray filters
 * @since 3.10.0
 */
export const maxItems = <S extends Schema.Any>(
  n: number,
  annotations?: Annotations.Filter<Schema.Type<S>>
) =>
<A extends ReadonlyArray<any>>(self: S & Schema<A, Schema.Encoded<S>, Schema.Context<S>>): filter<S> => {
  const maxItems = Math.floor(n)
  if (maxItems < 1) {
    throw new Error(
      errors_.getInvalidArgumentErrorMessage(`Expected an integer greater than or equal to 1, actual ${n}`)
    )
  }
  return self.pipe(
    filter((a) => a.length <= maxItems, {
      schemaId: MaxItemsSchemaId,
      title: `maxItems(${maxItems})`,
      description: `an array of at most ${maxItems} item(s)`,
      jsonSchema: { maxItems },
      [AST.StableFilterAnnotationId]: true,
      ...annotations
    })
  )
}

/**
 * @category schema id
 * @since 3.10.0
 */
export const ItemsCountSchemaId: unique symbol = schemaId_.ItemsCountSchemaId

/**
 * @category schema id
 * @since 3.10.0
 */
export type ItemsCountSchemaId = typeof ItemsCountSchemaId

/**
 * @category ReadonlyArray filters
 * @since 3.10.0
 */
export const itemsCount = <S extends Schema.Any>(
  n: number,
  annotations?: Annotations.Filter<Schema.Type<S>>
) =>
<A extends ReadonlyArray<any>>(self: S & Schema<A, Schema.Encoded<S>, Schema.Context<S>>): filter<S> => {
  const itemsCount = Math.floor(n)
  if (itemsCount < 0) {
    throw new Error(
      errors_.getInvalidArgumentErrorMessage(`Expected an integer greater than or equal to 0, actual ${n}`)
    )
  }
  return self.pipe(
    filter((a) => a.length === itemsCount, {
      schemaId: ItemsCountSchemaId,
      title: `itemsCount(${itemsCount})`,
      description: `an array of exactly ${itemsCount} item(s)`,
      jsonSchema: { minItems: itemsCount, maxItems: itemsCount },
      [AST.StableFilterAnnotationId]: true,
      ...annotations
    })
  )
}

/**
 * @category ReadonlyArray transformations
 * @since 3.10.0
 */
export const getNumberIndexedAccess = <A extends ReadonlyArray<any>, I extends ReadonlyArray<any>, R>(
  self: Schema<A, I, R>
): SchemaClass<A[number], I[number], R> => make(AST.getNumberIndexedAccess(self.ast))

/**
 * Get the first element of a `ReadonlyArray`, or `None` if the array is empty.
 *
 * @category ReadonlyArray transformations
 * @since 3.10.0
 */
export function head<S extends Schema.Any, A extends ReadonlyArray<unknown>>(
  self: S & Schema<A, Schema.Encoded<S>, Schema.Context<S>>
): transform<S, OptionFromSelf<SchemaClass<A[number]>>> {
  return transform(
    self,
    OptionFromSelf(getNumberIndexedAccess(typeSchema(self))),
    {
      strict: false,
      decode: (i) => array_.head(i),
      encode: (a) =>
        option_.match(a, {
          onNone: () => [],
          onSome: array_.of
        })
    }
  )
}

/**
 * Get the first element of a `NonEmptyReadonlyArray`.
 *
 * @category NonEmptyReadonlyArray transformations
 * @since 3.12.0
 */
export function headNonEmpty<S extends Schema.Any, A extends array_.NonEmptyReadonlyArray<unknown>>(
  self: S & Schema<A, Schema.Encoded<S>, Schema.Context<S>>
): transform<S, SchemaClass<A[number]>> {
  return transform(
    self,
    getNumberIndexedAccess(typeSchema(self)),
    {
      strict: false,
      decode: (i) => array_.headNonEmpty(i),
      encode: (a) => array_.of(a)
    }
  )
}

/**
 * Retrieves the first element of a `ReadonlyArray`.
 *
 * If the array is empty, it returns the `fallback` argument if provided; otherwise, it fails.
 *
 * @category ReadonlyArray transformations
 * @since 3.10.0
 */
export const headOrElse: {
  /**
   * Retrieves the first element of a `ReadonlyArray`.
   *
   * If the array is empty, it returns the `fallback` argument if provided; otherwise, it fails.
   *
   * @category ReadonlyArray transformations
   * @since 3.10.0
   */
  <S extends Schema.Any, A extends ReadonlyArray<unknown>>(fallback?: LazyArg<A[number]>): (
    self: S & Schema<A, Schema.Encoded<S>, Schema.Context<S>>
  ) => transform<S, SchemaClass<A[number]>>
  /**
   * Retrieves the first element of a `ReadonlyArray`.
   *
   * If the array is empty, it returns the `fallback` argument if provided; otherwise, it fails.
   *
   * @category ReadonlyArray transformations
   * @since 3.10.0
   */
  <S extends Schema.Any, A extends ReadonlyArray<unknown>>(
    self: S & Schema<A, Schema.Encoded<S>, Schema.Context<S>>,
    fallback?: LazyArg<A[number]>
  ): transform<S, SchemaClass<A[number]>>
} = dual(
  (args) => isSchema(args[0]),
  <A, I, R>(
    self: Schema<ReadonlyArray<A>, I, R>,
    fallback?: LazyArg<A>
  ): transform<Schema<ReadonlyArray<A>, I, R>, SchemaClass<A>> =>
    transformOrFail(
      self,
      getNumberIndexedAccess(typeSchema(self)),
      {
        strict: true,
        decode: (i, _, ast) =>
          i.length > 0
            ? ParseResult.succeed(i[0])
            : fallback
            ? ParseResult.succeed(fallback())
            : ParseResult.fail(new ParseResult.Type(ast, i, "Unable to retrieve the first element of an empty array")),
        encode: (a) => ParseResult.succeed(array_.of(a))
      }
    )
)

/**
 * @category schema id
 * @since 3.10.0
 */
export const ValidDateSchemaId: unique symbol = Symbol.for("effect/SchemaId/ValidDate")

/**
 * Defines a filter that specifically rejects invalid dates, such as `new
 * Date("Invalid Date")`. This filter ensures that only properly formatted and
 * valid date objects are accepted, enhancing data integrity by preventing
 * erroneous date values from being processed.
 *
 * @category Date filters
 * @since 3.10.0
 */
export const validDate =
  <S extends Schema.Any>(annotations?: Annotations.Filter<Schema.Type<S>>) =>
  <A extends Date>(self: S & Schema<A, Schema.Encoded<S>, Schema.Context<S>>): filter<S> =>
    self.pipe(
      filter((a) => !Number.isNaN(a.getTime()), {
        schemaId: ValidDateSchemaId,
        [ValidDateSchemaId]: { noInvalidDate: true },
        title: "validDate",
        description: "a valid Date",
        ...annotations
      })
    )

/**
 * @category schema id
 * @since 3.10.0
 */
export const LessThanDateSchemaId: unique symbol = Symbol.for("effect/SchemaId/LessThanDate")

/**
 * @category Date filters
 * @since 3.10.0
 */
export const lessThanDate = <S extends Schema.Any>(
  max: Date,
  annotations?: Annotations.Filter<Schema.Type<S>>
) =>
<A extends Date>(self: S & Schema<A, Schema.Encoded<S>, Schema.Context<S>>): filter<S> =>
  self.pipe(
    filter((a: Date) => a < max, {
      schemaId: LessThanDateSchemaId,
      [LessThanDateSchemaId]: { max },
      title: `lessThanDate(${util_.formatDate(max)})`,
      description: `a date before ${util_.formatDate(max)}`,
      ...annotations
    })
  )

/**
 * @category schema id
 * @since 3.10.0
 */
export const LessThanOrEqualToDateSchemaId: unique symbol = Symbol.for(
  "effect/schema/LessThanOrEqualToDate"
)

/**
 * @category Date filters
 * @since 3.10.0
 */
export const lessThanOrEqualToDate = <S extends Schema.Any>(
  max: Date,
  annotations?: Annotations.Filter<Schema.Type<S>>
) =>
<A extends Date>(self: S & Schema<A, Schema.Encoded<S>, Schema.Context<S>>): filter<S> =>
  self.pipe(
    filter((a: Date) => a <= max, {
      schemaId: LessThanDateSchemaId,
      [LessThanDateSchemaId]: { max },
      title: `lessThanOrEqualToDate(${util_.formatDate(max)})`,
      description: `a date before or equal to ${util_.formatDate(max)}`,
      ...annotations
    })
  )

/**
 * @category schema id
 * @since 3.10.0
 */
export const GreaterThanDateSchemaId: unique symbol = Symbol.for("effect/SchemaId/GreaterThanDate")

/**
 * @category Date filters
 * @since 3.10.0
 */
export const greaterThanDate = <S extends Schema.Any>(
  min: Date,
  annotations?: Annotations.Filter<Schema.Type<S>>
) =>
<A extends Date>(self: S & Schema<A, Schema.Encoded<S>, Schema.Context<S>>): filter<S> =>
  self.pipe(
    filter((a: Date) => a > min, {
      schemaId: GreaterThanDateSchemaId,
      [GreaterThanDateSchemaId]: { min },
      title: `greaterThanDate(${util_.formatDate(min)})`,
      description: `a date after ${util_.formatDate(min)}`,
      ...annotations
    })
  )

/**
 * @category schema id
 * @since 3.10.0
 */
export const GreaterThanOrEqualToDateSchemaId: unique symbol = Symbol.for(
  "effect/schema/GreaterThanOrEqualToDate"
)

/**
 * @category Date filters
 * @since 3.10.0
 */
export const greaterThanOrEqualToDate = <S extends Schema.Any>(
  min: Date,
  annotations?: Annotations.Filter<Schema.Type<S>>
) =>
<A extends Date>(self: S & Schema<A, Schema.Encoded<S>, Schema.Context<S>>): filter<S> =>
  self.pipe(
    filter((a: Date) => a >= min, {
      schemaId: GreaterThanOrEqualToDateSchemaId,
      [GreaterThanOrEqualToDateSchemaId]: { min },
      title: `greaterThanOrEqualToDate(${util_.formatDate(min)})`,
      description: `a date after or equal to ${util_.formatDate(min)}`,
      ...annotations
    })
  )

/**
 * @category schema id
 * @since 3.10.0
 */
export const BetweenDateSchemaId: unique symbol = Symbol.for("effect/SchemaId/BetweenDate")

/**
 * @category Date filters
 * @since 3.10.0
 */
export const betweenDate = <S extends Schema.Any>(
  min: Date,
  max: Date,
  annotations?: Annotations.Filter<Schema.Type<S>>
) =>
<A extends Date>(self: S & Schema<A, Schema.Encoded<S>, Schema.Context<S>>): filter<S> =>
  self.pipe(
    filter((a: Date) => a <= max && a >= min, {
      schemaId: BetweenDateSchemaId,
      [BetweenDateSchemaId]: { max, min },
      title: `betweenDate(${util_.formatDate(min)}, ${util_.formatDate(max)})`,
      description: `a date between ${util_.formatDate(min)} and ${util_.formatDate(max)}`,
      ...annotations
    })
  )

/**
 * @category schema id
 * @since 3.11.8
 */
export const DateFromSelfSchemaId: unique symbol = schemaId_.DateFromSelfSchemaId

/**
 * @category schema id
 * @since 3.11.8
 */
export type DateFromSelfSchemaId = typeof DateFromSelfSchemaId

/**
 * Describes a schema that accommodates potentially invalid `Date` instances,
 * such as `new Date("Invalid Date")`, without rejection.
 *
 * @category Date constructors
 * @since 3.10.0
 */
export class DateFromSelf extends declare(
  Predicate.isDate,
  {
    identifier: "DateFromSelf",
    schemaId: DateFromSelfSchemaId,
    [DateFromSelfSchemaId]: { noInvalidDate: false },
    description: "a potentially invalid Date instance",
    pretty: () => (date) => `new Date(${JSON.stringify(date)})`,
    arbitrary: () => (fc) => fc.date({ noInvalidDate: false }),
    equivalence: () => Equivalence.Date
  }
) {}

/**
 * Defines a schema that ensures only valid dates are accepted. This schema
 * rejects values like `new Date("Invalid Date")`, which, despite being a `Date`
 * instance, represents an invalid date. Such stringent validation ensures that
 * all date objects processed through this schema are properly formed and
 * represent real dates.
 *
 * @category Date constructors
 * @since 3.10.0
 */
export class ValidDateFromSelf extends DateFromSelf.pipe(
  validDate({
    identifier: "ValidDateFromSelf",
    description: "a valid Date instance"
  })
) {}

/**
 * Defines a schema that attempts to convert a `string` to a `Date` object using
 * the `new Date` constructor. This conversion is lenient, meaning it does not
 * reject strings that do not form valid dates (e.g., using `new Date("Invalid
 * Date")` results in a `Date` object, despite being invalid).
 *
 * @category Date transformations
 * @since 3.10.0
 */
export class DateFromString extends transform(
  String$.annotations({ description: "a string to be decoded into a Date" }),
  DateFromSelf,
  {
    strict: true,
    decode: (i) => new Date(i),
    encode: (a) => util_.formatDate(a)
  }
).annotations({ identifier: "DateFromString" }) {}

/** @ignore */
class Date$ extends DateFromString.pipe(
  validDate({ identifier: "Date" })
) {}

export {
  /**
   * This schema converts a `string` into a `Date` object using the `new Date`
   * constructor. It ensures that only valid date strings are accepted,
   * rejecting any strings that would result in an invalid date, such as `new
   * Date("Invalid Date")`.
   *
   * @category Date transformations
   * @since 3.10.0
   */
  Date$ as Date
}

/**
 * Defines a schema that converts a `number` into a `Date` object using the `new
 * Date` constructor. This schema does not validate the numerical input,
 * allowing potentially invalid values such as `NaN`, `Infinity`, and
 * `-Infinity` to be converted into `Date` objects. During the encoding process,
 * any invalid `Date` object will be encoded to `NaN`.
 *
 * @category Date transformations
 * @since 3.10.0
 */
export class DateFromNumber extends transform(
  Number$.annotations({ description: "a number to be decoded into a Date" }),
  DateFromSelf,
  {
    strict: true,
    decode: (i) => new Date(i),
    encode: (a) => a.getTime()
  }
).annotations({ identifier: "DateFromNumber" }) {}

/**
 * Describes a schema that represents a `DateTime.Utc` instance.
 *
 * @category DateTime.Utc constructors
 * @since 3.10.0
 */
export class DateTimeUtcFromSelf extends declare(
  (u) => dateTime.isDateTime(u) && dateTime.isUtc(u),
  {
    identifier: "DateTimeUtcFromSelf",
    description: "a DateTime.Utc instance",
    pretty: (): pretty_.Pretty<dateTime.Utc> => (dateTime) => dateTime.toString(),
    arbitrary: (): LazyArbitrary<dateTime.Utc> => (fc) =>
      fc.date({ noInvalidDate: true }).map((date) => dateTime.unsafeFromDate(date)),
    equivalence: () => dateTime.Equivalence
  }
) {}

const decodeDateTimeUtc = <A extends dateTime.DateTime.Input>(input: A, ast: AST.AST) =>
  ParseResult.try({
    try: () => dateTime.unsafeMake(input),
    catch: () => new ParseResult.Type(ast, input, `Unable to decode ${util_.formatUnknown(input)} into a DateTime.Utc`)
  })

/**
 * Defines a schema that attempts to convert a `number` to a `DateTime.Utc` instance using the `DateTime.unsafeMake` constructor.
 *
 * @category DateTime.Utc transformations
 * @since 3.10.0
 */
export class DateTimeUtcFromNumber extends transformOrFail(
  Number$.annotations({ description: "a number to be decoded into a DateTime.Utc" }),
  DateTimeUtcFromSelf,
  {
    strict: true,
    decode: (i, _, ast) => decodeDateTimeUtc(i, ast),
    encode: (a) => ParseResult.succeed(dateTime.toEpochMillis(a))
  }
).annotations({ identifier: "DateTimeUtcFromNumber" }) {}

/**
 * Defines a schema that attempts to convert a `Date` to a `DateTime.Utc` instance using the `DateTime.unsafeMake` constructor.
 *
 * @category DateTime.Utc transformations
 * @since 3.12.0
 */
export class DateTimeUtcFromDate extends transformOrFail(
  DateFromSelf.annotations({ description: "a Date to be decoded into a DateTime.Utc" }),
  DateTimeUtcFromSelf,
  {
    strict: true,
    decode: (i, _, ast) => decodeDateTimeUtc(i, ast),
    encode: (a) => ParseResult.succeed(dateTime.toDateUtc(a))
  }
).annotations({ identifier: "DateTimeUtcFromDate" }) {}

/**
 * Defines a schema that attempts to convert a `string` to a `DateTime.Utc` instance using the `DateTime.unsafeMake` constructor.
 *
 * @category DateTime.Utc transformations
 * @since 3.10.0
 */
export class DateTimeUtc extends transformOrFail(
  String$.annotations({ description: "a string to be decoded into a DateTime.Utc" }),
  DateTimeUtcFromSelf,
  {
    strict: true,
    decode: (i, _, ast) => decodeDateTimeUtc(i, ast),
    encode: (a) => ParseResult.succeed(dateTime.formatIso(a))
  }
).annotations({ identifier: "DateTimeUtc" }) {}

const timeZoneOffsetArbitrary = (): LazyArbitrary<dateTime.TimeZone.Offset> => (fc) =>
  fc.integer({ min: -12 * 60 * 60 * 1000, max: 14 * 60 * 60 * 1000 }).map(dateTime.zoneMakeOffset)

/**
 * Describes a schema that represents a `TimeZone.Offset` instance.
 *
 * @category TimeZone constructors
 * @since 3.10.0
 */
export class TimeZoneOffsetFromSelf extends declare(
  dateTime.isTimeZoneOffset,
  {
    identifier: "TimeZoneOffsetFromSelf",
    description: "a TimeZone.Offset instance",
    pretty: (): pretty_.Pretty<dateTime.TimeZone.Offset> => (zone) => zone.toString(),
    arbitrary: timeZoneOffsetArbitrary
  }
) {}

/**
 * Defines a schema that converts a `number` to a `TimeZone.Offset` instance using the `DateTime.zoneMakeOffset` constructor.
 *
 * @category TimeZone transformations
 * @since 3.10.0
 */
export class TimeZoneOffset extends transform(
  Number$.annotations({ description: "a number to be decoded into a TimeZone.Offset" }),
  TimeZoneOffsetFromSelf,
  {
    strict: true,
    decode: (i) => dateTime.zoneMakeOffset(i),
    encode: (a) => a.offset
  }
).annotations({ identifier: "TimeZoneOffset" }) {}

const timeZoneNamedArbitrary = (): LazyArbitrary<dateTime.TimeZone.Named> => (fc) =>
  fc.constantFrom(...Intl.supportedValuesOf("timeZone")).map(dateTime.zoneUnsafeMakeNamed)

/**
 * Describes a schema that represents a `TimeZone.Named` instance.
 *
 * @category TimeZone constructors
 * @since 3.10.0
 */
export class TimeZoneNamedFromSelf extends declare(
  dateTime.isTimeZoneNamed,
  {
    identifier: "TimeZoneNamedFromSelf",
    description: "a TimeZone.Named instance",
    pretty: (): pretty_.Pretty<dateTime.TimeZone.Named> => (zone) => zone.toString(),
    arbitrary: timeZoneNamedArbitrary
  }
) {}

/**
 * Defines a schema that attempts to convert a `string` to a `TimeZone.Named` instance using the `DateTime.zoneUnsafeMakeNamed` constructor.
 *
 * @category TimeZone transformations
 * @since 3.10.0
 */
export class TimeZoneNamed extends transformOrFail(
  String$.annotations({ description: "a string to be decoded into a TimeZone.Named" }),
  TimeZoneNamedFromSelf,
  {
    strict: true,
    decode: (i, _, ast) =>
      ParseResult.try({
        try: () => dateTime.zoneUnsafeMakeNamed(i),
        catch: () => new ParseResult.Type(ast, i, `Unable to decode ${JSON.stringify(i)} into a TimeZone.Named`)
      }),
    encode: (a) => ParseResult.succeed(a.id)
  }
).annotations({ identifier: "TimeZoneNamed" }) {}

/**
 * @category TimeZone constructors
 * @since 3.10.0
 */
export class TimeZoneFromSelf extends Union(TimeZoneOffsetFromSelf, TimeZoneNamedFromSelf) {}

/**
 * Defines a schema that attempts to convert a `string` to a `TimeZone` using the `DateTime.zoneFromString` constructor.
 *
 * @category TimeZone transformations
 * @since 3.10.0
 */
export class TimeZone extends transformOrFail(
  String$.annotations({ description: "a string to be decoded into a TimeZone" }),
  TimeZoneFromSelf,
  {
    strict: true,
    decode: (i, _, ast) =>
      option_.match(dateTime.zoneFromString(i), {
        onNone: () =>
          ParseResult.fail(new ParseResult.Type(ast, i, `Unable to decode ${JSON.stringify(i)} into a TimeZone`)),
        onSome: ParseResult.succeed
      }),
    encode: (a) => ParseResult.succeed(dateTime.zoneToString(a))
  }
).annotations({ identifier: "TimeZone" }) {}

const timeZoneArbitrary: LazyArbitrary<dateTime.TimeZone> = (fc) =>
  fc.oneof(
    timeZoneOffsetArbitrary()(fc),
    timeZoneNamedArbitrary()(fc)
  )

/**
 * Describes a schema that represents a `DateTime.Zoned` instance.
 *
 * @category DateTime.Zoned constructors
 * @since 3.10.0
 */
export class DateTimeZonedFromSelf extends declare(
  (u) => dateTime.isDateTime(u) && dateTime.isZoned(u),
  {
    identifier: "DateTimeZonedFromSelf",
    description: "a DateTime.Zoned instance",
    pretty: (): pretty_.Pretty<dateTime.Zoned> => (dateTime) => dateTime.toString(),
    arbitrary: (): LazyArbitrary<dateTime.Zoned> => (fc) =>
      fc.tuple(
        fc.integer({
          // time zone db supports +/- 1000 years or so
          min: -31536000000000,
          max: 31536000000000
        }),
        timeZoneArbitrary(fc)
      ).map(([millis, timeZone]) => dateTime.unsafeMakeZoned(millis, { timeZone })),
    equivalence: () => dateTime.Equivalence
  }
) {}

/**
 * Defines a schema that attempts to convert a `string` to a `DateTime.Zoned` instance.
 *
 * @category DateTime.Zoned transformations
 * @since 3.10.0
 */
export class DateTimeZoned extends transformOrFail(
  String$.annotations({ description: "a string to be decoded into a DateTime.Zoned" }),
  DateTimeZonedFromSelf,
  {
    strict: true,
    decode: (i, _, ast) =>
      option_.match(dateTime.makeZonedFromString(i), {
        onNone: () =>
          ParseResult.fail(new ParseResult.Type(ast, i, `Unable to decode ${JSON.stringify(i)} into a DateTime.Zoned`)),
        onSome: ParseResult.succeed
      }),
    encode: (a) => ParseResult.succeed(dateTime.formatIsoZoned(a))
  }
).annotations({ identifier: "DateTimeZoned" }) {}

/**
 * @category Option utils
 * @since 3.10.0
 */
export type OptionEncoded<I> =
  | {
    readonly _tag: "None"
  }
  | {
    readonly _tag: "Some"
    readonly value: I
  }

const OptionNoneEncoded = Struct({
  _tag: Literal("None")
}).annotations({ description: "NoneEncoded" })

const optionSomeEncoded = <Value extends Schema.Any>(value: Value) =>
  Struct({
    _tag: Literal("Some"),
    value
  }).annotations({ description: `SomeEncoded<${format(value)}>` })

const optionEncoded = <Value extends Schema.Any>(value: Value) =>
  Union(
    OptionNoneEncoded,
    optionSomeEncoded(value)
  ).annotations({
    description: `OptionEncoded<${format(value)}>`
  })

const optionDecode = <A>(input: OptionEncoded<A>): option_.Option<A> =>
  input._tag === "None" ? option_.none() : option_.some(input.value)

const optionArbitrary =
  <A>(value: LazyArbitrary<A>, ctx: ArbitraryGenerationContext): LazyArbitrary<option_.Option<A>> => (fc) =>
    fc.oneof(
      ctx,
      fc.record({ _tag: fc.constant("None" as const) }),
      fc.record({ _tag: fc.constant("Some" as const), value: value(fc) })
    ).map(optionDecode)

const optionPretty = <A>(value: pretty_.Pretty<A>): pretty_.Pretty<option_.Option<A>> =>
  option_.match({
    onNone: () => "none()",
    onSome: (a) => `some(${value(a)})`
  })

const optionParse =
  <A, R>(decodeUnknown: ParseResult.DecodeUnknown<A, R>): ParseResult.DeclarationDecodeUnknown<option_.Option<A>, R> =>
  (u, options, ast) =>
    option_.isOption(u) ?
      option_.isNone(u) ?
        ParseResult.succeed(option_.none())
        : toComposite(decodeUnknown(u.value, options), option_.some, ast, u)
      : ParseResult.fail(new ParseResult.Type(ast, u))

/**
 * @category api interface
 * @since 3.10.0
 */
export interface OptionFromSelf<Value extends Schema.Any> extends
  AnnotableDeclare<
    OptionFromSelf<Value>,
    option_.Option<Schema.Type<Value>>,
    option_.Option<Schema.Encoded<Value>>,
    [Value]
  >
{}

/**
 * @category Option transformations
 * @since 3.10.0
 */
export const OptionFromSelf = <Value extends Schema.Any>(value: Value): OptionFromSelf<Value> => {
  return declare(
    [value],
    {
      decode: (value) => optionParse(ParseResult.decodeUnknown(value)),
      encode: (value) => optionParse(ParseResult.encodeUnknown(value))
    },
    {
      description: `Option<${format(value)}>`,
      pretty: optionPretty,
      arbitrary: optionArbitrary,
      equivalence: option_.getEquivalence
    }
  )
}

/**
 * @category api interface
 * @since 3.10.0
 */
export interface Option<Value extends Schema.Any> extends
  transform<
    Union<[
      Struct<{ _tag: Literal<["None"]> }>,
      Struct<{ _tag: Literal<["Some"]>; value: Value }>
    ]>,
    OptionFromSelf<SchemaClass<Schema.Type<Value>>>
  >
{}

const makeNoneEncoded = {
  _tag: "None"
} as const

const makeSomeEncoded = <A>(value: A) => ({
  _tag: "Some",
  value
} as const)

/**
 * @category Option transformations
 * @since 3.10.0
 */
export function Option<Value extends Schema.Any>(value: Value): Option<Value> {
  const value_ = asSchema(value)
  const out = transform(
    optionEncoded(value_),
    OptionFromSelf(typeSchema(value_)),
    {
      strict: true,
      decode: (i) => optionDecode(i),
      encode: (a) =>
        option_.match(a, {
          onNone: () => makeNoneEncoded,
          onSome: makeSomeEncoded
        })
    }
  )
  return out as any
}

/**
 * @category api interface
 * @since 3.10.0
 */
export interface OptionFromNullOr<Value extends Schema.Any>
  extends transform<NullOr<Value>, OptionFromSelf<SchemaClass<Schema.Type<Value>>>>
{}

/**
 * @category Option transformations
 * @since 3.10.0
 */
export function OptionFromNullOr<Value extends Schema.Any>(value: Value): OptionFromNullOr<Value> {
  return transform(NullOr(value), OptionFromSelf(typeSchema(asSchema(value))), {
    strict: true,
    decode: (i) => option_.fromNullable(i),
    encode: (a) => option_.getOrNull(a)
  })
}

/**
 * @category api interface
 * @since 3.10.0
 */
export interface OptionFromNullishOr<Value extends Schema.Any>
  extends transform<NullishOr<Value>, OptionFromSelf<SchemaClass<Schema.Type<Value>>>>
{}

/**
 * @category Option transformations
 * @since 3.10.0
 */
export function OptionFromNullishOr<Value extends Schema.Any>(
  value: Value,
  onNoneEncoding: null | undefined
): OptionFromNullishOr<Value> {
  return transform(
    NullishOr(value),
    OptionFromSelf(typeSchema(asSchema(value))),
    {
      strict: true,
      decode: (i) => option_.fromNullable(i),
      encode: onNoneEncoding === null ?
        (a) => option_.getOrNull(a) :
        (a) => option_.getOrUndefined(a)
    }
  )
}

/**
 * @category api interface
 * @since 3.10.0
 */
export interface OptionFromUndefinedOr<Value extends Schema.Any>
  extends transform<UndefinedOr<Value>, OptionFromSelf<SchemaClass<Schema.Type<Value>>>>
{}

/**
 * @category Option transformations
 * @since 3.10.0
 */
export function OptionFromUndefinedOr<Value extends Schema.Any>(value: Value): OptionFromUndefinedOr<Value> {
  return transform(UndefinedOr(value), OptionFromSelf(typeSchema(asSchema(value))), {
    strict: true,
    decode: (i) => option_.fromNullable(i),
    encode: (a) => option_.getOrUndefined(a)
  })
}

/**
 * Transforms strings into an Option type, effectively filtering out empty or
 * whitespace-only strings by trimming them and checking their length. Returns
 * `none` for invalid inputs and `some` for valid non-empty strings.
 *
 * @example
 * ```ts
 * import { Schema } from "effect"
 *
 * console.log(Schema.decodeSync(Schema.OptionFromNonEmptyTrimmedString)("")) // Option.none()
 * console.log(Schema.decodeSync(Schema.OptionFromNonEmptyTrimmedString)(" a ")) // Option.some("a")
 * console.log(Schema.decodeSync(Schema.OptionFromNonEmptyTrimmedString)("a")) // Option.some("a")
 * ```
 *
 * @category Option transformations
 * @since 3.10.0
 */
export class OptionFromNonEmptyTrimmedString extends transform(String$, OptionFromSelf(NonEmptyTrimmedString), {
  strict: true,
  decode: (i) => option_.filter(option_.some(i.trim()), string_.isNonEmpty),
  encode: (a) => option_.getOrElse(a, () => "")
}) {}

/**
 * @category Either utils
 * @since 3.10.0
 */
export type RightEncoded<IA> = {
  readonly _tag: "Right"
  readonly right: IA
}

/**
 * @category Either utils
 * @since 3.10.0
 */
export type LeftEncoded<IE> = {
  readonly _tag: "Left"
  readonly left: IE
}

/**
 * @category Either utils
 * @since 3.10.0
 */
export type EitherEncoded<IR, IL> = RightEncoded<IR> | LeftEncoded<IL>

const rightEncoded = <Right extends Schema.All>(right: Right) =>
  Struct({
    _tag: Literal("Right"),
    right
  }).annotations({ description: `RightEncoded<${format(right)}>` })

const leftEncoded = <Left extends Schema.All>(left: Left) =>
  Struct({
    _tag: Literal("Left"),
    left
  }).annotations({ description: `LeftEncoded<${format(left)}>` })

const eitherEncoded = <Right extends Schema.All, Left extends Schema.All>(
  right: Right,
  left: Left
) =>
  Union(rightEncoded(right), leftEncoded(left)).annotations({
    description: `EitherEncoded<${format(left)}, ${format(right)}>`
  })

const eitherDecode = <R, L>(input: EitherEncoded<R, L>): either_.Either<R, L> =>
  input._tag === "Left" ? either_.left(input.left) : either_.right(input.right)

const eitherArbitrary = <R, L>(
  right: LazyArbitrary<R>,
  left: LazyArbitrary<L>
): LazyArbitrary<either_.Either<R, L>> =>
(fc) =>
  fc.oneof(
    fc.record({ _tag: fc.constant("Left" as const), left: left(fc) }),
    fc.record({ _tag: fc.constant("Right" as const), right: right(fc) })
  ).map(eitherDecode)

const eitherPretty = <R, L>(
  right: pretty_.Pretty<R>,
  left: pretty_.Pretty<L>
): pretty_.Pretty<either_.Either<R, L>> =>
  either_.match({
    onLeft: (e) => `left(${left(e)})`,
    onRight: (a) => `right(${right(a)})`
  })

const eitherParse = <RR, R, LR, L>(
  parseRight: ParseResult.DecodeUnknown<R, RR>,
  decodeUnknownLeft: ParseResult.DecodeUnknown<L, LR>
): ParseResult.DeclarationDecodeUnknown<either_.Either<R, L>, LR | RR> =>
(u, options, ast) =>
  either_.isEither(u) ?
    either_.match(u, {
      onLeft: (left) => toComposite(decodeUnknownLeft(left, options), either_.left, ast, u),
      onRight: (right) => toComposite(parseRight(right, options), either_.right, ast, u)
    })
    : ParseResult.fail(new ParseResult.Type(ast, u))

/**
 * @category api interface
 * @since 3.10.0
 */
export interface EitherFromSelf<R extends Schema.All, L extends Schema.All> extends
  AnnotableDeclare<
    EitherFromSelf<R, L>,
    either_.Either<Schema.Type<R>, Schema.Type<L>>,
    either_.Either<Schema.Encoded<R>, Schema.Encoded<L>>,
    [R, L]
  >
{}

/**
 * @category Either transformations
 * @since 3.10.0
 */
export const EitherFromSelf = <R extends Schema.All, L extends Schema.All>({ left, right }: {
  readonly left: L
  readonly right: R
}): EitherFromSelf<R, L> => {
  return declare(
    [right, left],
    {
      decode: (right, left) => eitherParse(ParseResult.decodeUnknown(right), ParseResult.decodeUnknown(left)),
      encode: (right, left) => eitherParse(ParseResult.encodeUnknown(right), ParseResult.encodeUnknown(left))
    },
    {
      description: `Either<${format(right)}, ${format(left)}>`,
      pretty: eitherPretty,
      arbitrary: eitherArbitrary,
      equivalence: (right, left) => either_.getEquivalence({ left, right })
    }
  )
}

const makeLeftEncoded = <E>(left: E) => (({
  _tag: "Left",
  left
}) as const)
const makeRightEncoded = <A>(right: A) => (({
  _tag: "Right",
  right
}) as const)

/**
 * @category api interface
 * @since 3.10.0
 */
export interface Either<Right extends Schema.All, Left extends Schema.All> extends
  transform<
    Union<[
      Struct<{
        _tag: Literal<["Right"]>
        right: Right
      }>,
      Struct<{
        _tag: Literal<["Left"]>
        left: Left
      }>
    ]>,
    EitherFromSelf<SchemaClass<Schema.Type<Right>>, SchemaClass<Schema.Type<Left>>>
  >
{}

/**
 * @category Either transformations
 * @since 3.10.0
 */
export const Either = <R extends Schema.All, L extends Schema.All>({ left, right }: {
  readonly left: L
  readonly right: R
}): Either<R, L> => {
  const right_ = asSchema(right)
  const left_ = asSchema(left)
  const out = transform(
    eitherEncoded(right_, left_),
    EitherFromSelf({ left: typeSchema(left_), right: typeSchema(right_) }),
    {
      strict: true,
      decode: (i) => eitherDecode(i),
      encode: (a) =>
        either_.match(a, {
          onLeft: makeLeftEncoded,
          onRight: makeRightEncoded
        })
    }
  )
  return out as any
}

/**
 * @category api interface
 * @since 3.10.0
 */
export interface EitherFromUnion<Right extends Schema.All, Left extends Schema.All> extends
  transform<
    Union<[
      transform<Right, Struct<{ _tag: Literal<["Right"]>; right: SchemaClass<Schema.Type<Right>> }>>,
      transform<Left, Struct<{ _tag: Literal<["Left"]>; right: SchemaClass<Schema.Type<Left>> }>>
    ]>,
    EitherFromSelf<SchemaClass<Schema.Type<Right>>, SchemaClass<Schema.Type<Left>>>
  >
{}

/**
 * @example
 * ```ts
 * import * as Schema from "effect/Schema"
 *
 * // Schema<string | number, Either<string, number>>
 * Schema.EitherFromUnion({ left: Schema.String, right: Schema.Number })
 * ```
 *
 * @category Either transformations
 * @since 3.10.0
 */
export const EitherFromUnion = <Right extends Schema.All, Left extends Schema.All>({ left, right }: {
  readonly left: Left
  readonly right: Right
}): EitherFromUnion<Right, Left> => {
  const right_ = asSchema(right)
  const left_ = asSchema(left)
  const toright = typeSchema(right_)
  const toleft = typeSchema(left_)
  const fromRight = transform(right_, rightEncoded(toright), {
    strict: true,
    decode: (i) => makeRightEncoded(i),
    encode: (a) => a.right
  })
  const fromLeft = transform(left_, leftEncoded(toleft), {
    strict: true,
    decode: (i) => makeLeftEncoded(i),
    encode: (a) => a.left
  })
  const out = transform(
    Union(fromRight, fromLeft),
    EitherFromSelf({ left: toleft, right: toright }),
    {
      strict: true,
      decode: (i) => i._tag === "Left" ? either_.left(i.left) : either_.right(i.right),
      encode: (a) =>
        either_.match(a, {
          onLeft: makeLeftEncoded,
          onRight: makeRightEncoded
        })
    }
  )
  return out as any
}

const mapArbitrary = <K, V>(
  key: LazyArbitrary<K>,
  value: LazyArbitrary<V>,
  ctx: ArbitraryGenerationContext
): LazyArbitrary<Map<K, V>> => {
  return (fc) => {
    const items = fc.array(fc.tuple(key(fc), value(fc)))
    return (ctx.depthIdentifier !== undefined ? fc.oneof(ctx, fc.constant([]), items) : items).map((as) => new Map(as))
  }
}

const readonlyMapPretty = <K, V>(
  key: pretty_.Pretty<K>,
  value: pretty_.Pretty<V>
): pretty_.Pretty<ReadonlyMap<K, V>> =>
(map) =>
  `new Map([${
    Array.from(map.entries())
      .map(([k, v]) => `[${key(k)}, ${value(v)}]`)
      .join(", ")
  }])`

const readonlyMapEquivalence = <K, V>(
  key: Equivalence.Equivalence<K>,
  value: Equivalence.Equivalence<V>
): Equivalence.Equivalence<ReadonlyMap<K, V>> => {
  const arrayEquivalence = array_.getEquivalence(
    Equivalence.make<[K, V]>(([ka, va], [kb, vb]) => key(ka, kb) && value(va, vb))
  )
  return Equivalence.make((a, b) => arrayEquivalence(Array.from(a.entries()), Array.from(b.entries())))
}

const readonlyMapParse = <R, K, V>(
  decodeUnknown: ParseResult.DecodeUnknown<ReadonlyArray<readonly [K, V]>, R>
): ParseResult.DeclarationDecodeUnknown<ReadonlyMap<K, V>, R> =>
(u, options, ast) =>
  Predicate.isMap(u) ?
    toComposite(decodeUnknown(Array.from(u.entries()), options), (as) => new Map(as), ast, u)
    : ParseResult.fail(new ParseResult.Type(ast, u))

/**
 * @category api interface
 * @since 3.10.0
 */
export interface ReadonlyMapFromSelf<K extends Schema.Any, V extends Schema.Any> extends
  AnnotableDeclare<
    ReadonlyMapFromSelf<K, V>,
    ReadonlyMap<Schema.Type<K>, Schema.Type<V>>,
    ReadonlyMap<Schema.Encoded<K>, Schema.Encoded<V>>,
    [K, V]
  >
{}

const mapFromSelf_ = <K extends Schema.Any, V extends Schema.Any>(
  key: K,
  value: V,
  description: string
): ReadonlyMapFromSelf<K, V> =>
  declare(
    [key, value],
    {
      decode: (Key, Value) => readonlyMapParse(ParseResult.decodeUnknown(Array$(Tuple(Key, Value)))),
      encode: (Key, Value) => readonlyMapParse(ParseResult.encodeUnknown(Array$(Tuple(Key, Value))))
    },
    {
      description,
      pretty: readonlyMapPretty,
      arbitrary: mapArbitrary,
      equivalence: readonlyMapEquivalence
    }
  )

/**
 * @category ReadonlyMap
 * @since 3.10.0
 */
export const ReadonlyMapFromSelf = <K extends Schema.Any, V extends Schema.Any>({ key, value }: {
  readonly key: K
  readonly value: V
}): ReadonlyMapFromSelf<K, V> => mapFromSelf_(key, value, `ReadonlyMap<${format(key)}, ${format(value)}>`)

/**
 * @category api interface
 * @since 3.10.0
 */
export interface MapFromSelf<K extends Schema.Any, V extends Schema.Any> extends
  AnnotableDeclare<
    MapFromSelf<K, V>,
    Map<Schema.Type<K>, Schema.Type<V>>,
    ReadonlyMap<Schema.Encoded<K>, Schema.Encoded<V>>,
    [K, V]
  >
{}

/**
 * @category Map
 * @since 3.10.0
 */
export const MapFromSelf = <K extends Schema.Any, V extends Schema.Any>({ key, value }: {
  readonly key: K
  readonly value: V
}): MapFromSelf<K, V> => mapFromSelf_(key, value, `Map<${format(key)}, ${format(value)}>`) as any

/**
 * @category api interface
 * @since 3.10.0
 */
export interface ReadonlyMap$<K extends Schema.Any, V extends Schema.Any>
  extends transform<Array$<Tuple2<K, V>>, ReadonlyMapFromSelf<SchemaClass<Schema.Type<K>>, SchemaClass<Schema.Type<V>>>>
{}

/**
 * @category ReadonlyMap transformations
 * @since 3.10.0
 */
export function ReadonlyMap<K extends Schema.Any, V extends Schema.Any>({ key, value }: {
  readonly key: K
  readonly value: V
}): ReadonlyMap$<K, V> {
  return transform(
    Array$(Tuple(key, value)),
    ReadonlyMapFromSelf({ key: typeSchema(asSchema(key)), value: typeSchema(asSchema(value)) }),
    {
      strict: true,
      decode: (i) => new Map(i),
      encode: (a) => Array.from(a.entries())
    }
  )
}

/**
 * @category api interface
 * @since 3.10.0
 */
export interface Map$<K extends Schema.Any, V extends Schema.Any>
  extends transform<Array$<Tuple2<K, V>>, MapFromSelf<SchemaClass<Schema.Type<K>>, SchemaClass<Schema.Type<V>>>>
{}

/** @ignore */
function map<K extends Schema.Any, V extends Schema.Any>({ key, value }: {
  readonly key: K
  readonly value: V
}): Map$<K, V> {
  return transform(
    Array$(Tuple(key, value)),
    MapFromSelf({ key: typeSchema(asSchema(key)), value: typeSchema(asSchema(value)) }),
    {
      strict: true,
      decode: (i) => new Map(i),
      encode: (a) => Array.from(a.entries())
    }
  )
}

export {
  /**
   * @category Map transformations
   * @since 3.10.0
   */
  map as Map
}

/**
 * @category ReadonlyMap transformations
 * @since 3.10.0
 */
export const ReadonlyMapFromRecord = <KA, KR, VA, VI, VR>({ key, value }: {
  key: Schema<KA, string, KR>
  value: Schema<VA, VI, VR>
}): SchemaClass<ReadonlyMap<KA, VA>, { readonly [x: string]: VI }, KR | VR> =>
  transform(
    Record({ key: encodedBoundSchema(key), value }).annotations({
      description: "a record to be decoded into a ReadonlyMap"
    }),
    ReadonlyMapFromSelf({ key, value: typeSchema(value) }),
    {
      strict: true,
      decode: (i) => new Map(Object.entries(i)),
      encode: (a) => Object.fromEntries(a)
    }
  )

/**
 * @category Map transformations
 * @since 3.10.0
 */
export const MapFromRecord = <KA, KR, VA, VI, VR>({ key, value }: {
  key: Schema<KA, string, KR>
  value: Schema<VA, VI, VR>
}): SchemaClass<Map<KA, VA>, { readonly [x: string]: VI }, KR | VR> =>
  transform(
    Record({ key: encodedBoundSchema(key), value }).annotations({
      description: "a record to be decoded into a Map"
    }),
    MapFromSelf({ key, value: typeSchema(value) }),
    {
      strict: true,
      decode: (i) => new Map(Object.entries(i)),
      encode: (a) => Object.fromEntries(a)
    }
  )

const setArbitrary =
  <A>(item: LazyArbitrary<A>, ctx: ArbitraryGenerationContext): LazyArbitrary<ReadonlySet<A>> => (fc) => {
    const items = fc.array(item(fc))
    return (ctx.depthIdentifier !== undefined ? fc.oneof(ctx, fc.constant([]), items) : items).map((as) => new Set(as))
  }

const readonlySetPretty = <A>(item: pretty_.Pretty<A>): pretty_.Pretty<ReadonlySet<A>> => (set) =>
  `new Set([${Array.from(set.values()).map((a) => item(a)).join(", ")}])`

const readonlySetEquivalence = <A>(
  item: Equivalence.Equivalence<A>
): Equivalence.Equivalence<ReadonlySet<A>> => {
  const arrayEquivalence = array_.getEquivalence(item)
  return Equivalence.make((a, b) => arrayEquivalence(Array.from(a.values()), Array.from(b.values())))
}

const readonlySetParse = <A, R>(
  decodeUnknown: ParseResult.DecodeUnknown<ReadonlyArray<A>, R>
): ParseResult.DeclarationDecodeUnknown<ReadonlySet<A>, R> =>
(u, options, ast) =>
  Predicate.isSet(u) ?
    toComposite(decodeUnknown(Array.from(u.values()), options), (as) => new Set(as), ast, u)
    : ParseResult.fail(new ParseResult.Type(ast, u))

/**
 * @category api interface
 * @since 3.10.0
 */
export interface ReadonlySetFromSelf<Value extends Schema.Any> extends
  AnnotableDeclare<
    ReadonlySetFromSelf<Value>,
    ReadonlySet<Schema.Type<Value>>,
    ReadonlySet<Schema.Encoded<Value>>,
    [Value]
  >
{}

const setFromSelf_ = <Value extends Schema.Any>(value: Value, description: string): ReadonlySetFromSelf<Value> =>
  declare(
    [value],
    {
      decode: (item) => readonlySetParse(ParseResult.decodeUnknown(Array$(item))),
      encode: (item) => readonlySetParse(ParseResult.encodeUnknown(Array$(item)))
    },
    {
      description,
      pretty: readonlySetPretty,
      arbitrary: setArbitrary,
      equivalence: readonlySetEquivalence
    }
  )

/**
 * @category ReadonlySet
 * @since 3.10.0
 */
export const ReadonlySetFromSelf = <Value extends Schema.Any>(value: Value): ReadonlySetFromSelf<Value> =>
  setFromSelf_(value, `ReadonlySet<${format(value)}>`)

/**
 * @category api interface
 * @since 3.10.0
 */
export interface SetFromSelf<Value extends Schema.Any> extends
  AnnotableDeclare<
    SetFromSelf<Value>,
    Set<Schema.Type<Value>>,
    ReadonlySet<Schema.Encoded<Value>>,
    [Value]
  >
{}

/**
 * @category Set
 * @since 3.10.0
 */
export const SetFromSelf = <Value extends Schema.Any>(value: Value): SetFromSelf<Value> =>
  setFromSelf_(value, `Set<${format(value)}>`) as any

/**
 * @category api interface
 * @since 3.10.0
 */
export interface ReadonlySet$<Value extends Schema.Any>
  extends transform<Array$<Value>, ReadonlySetFromSelf<SchemaClass<Schema.Type<Value>>>>
{}

/**
 * @category ReadonlySet transformations
 * @since 3.10.0
 */
export function ReadonlySet<Value extends Schema.Any>(value: Value): ReadonlySet$<Value> {
  return transform(
    Array$(value),
    ReadonlySetFromSelf(typeSchema(asSchema(value))),
    {
      strict: true,
      decode: (i) => new Set(i),
      encode: (a) => Array.from(a)
    }
  )
}

/**
 * @category api interface
 * @since 3.10.0
 */
export interface Set$<Value extends Schema.Any>
  extends transform<Array$<Value>, SetFromSelf<SchemaClass<Schema.Type<Value>>>>
{}

/** @ignore */
function set<Value extends Schema.Any>(value: Value): Set$<Value> {
  return transform(
    Array$(value),
    SetFromSelf(typeSchema(asSchema(value))),
    {
      strict: true,
      decode: (i) => new Set(i),
      encode: (a) => Array.from(a)
    }
  )
}

export {
  /**
   * @category Set transformations
   * @since 3.10.0
   */
  set as Set
}

const bigDecimalPretty = (): pretty_.Pretty<bigDecimal_.BigDecimal> => (val) =>
  `BigDecimal(${bigDecimal_.format(bigDecimal_.normalize(val))})`

const bigDecimalArbitrary = (): LazyArbitrary<bigDecimal_.BigDecimal> => (fc) =>
  fc.tuple(fc.bigInt(), fc.integer({ min: 0, max: 18 }))
    .map(([value, scale]) => bigDecimal_.make(value, scale))

/**
 * @category BigDecimal constructors
 * @since 3.10.0
 */
export class BigDecimalFromSelf extends declare(
  bigDecimal_.isBigDecimal,
  {
    identifier: "BigDecimalFromSelf",
    pretty: bigDecimalPretty,
    arbitrary: bigDecimalArbitrary,
    equivalence: () => bigDecimal_.Equivalence
  }
) {}

/**
 * @category BigDecimal transformations
 * @since 3.10.0
 */
export class BigDecimal extends transformOrFail(
  String$.annotations({ description: "a string to be decoded into a BigDecimal" }),
  BigDecimalFromSelf,
  {
    strict: true,
    decode: (i, _, ast) =>
      bigDecimal_.fromString(i).pipe(option_.match({
        onNone: () =>
          ParseResult.fail(new ParseResult.Type(ast, i, `Unable to decode ${JSON.stringify(i)} into a BigDecimal`)),
        onSome: (val) => ParseResult.succeed(bigDecimal_.normalize(val))
      })),
    encode: (a) => ParseResult.succeed(bigDecimal_.format(bigDecimal_.normalize(a)))
  }
).annotations({ identifier: "BigDecimal" }) {}

/**
 * A schema that transforms a `number` into a `BigDecimal`.
 * When encoding, this Schema will produce incorrect results if the BigDecimal exceeds the 64-bit range of a number.
 *
 * @category BigDecimal transformations
 * @since 3.10.0
 */
export class BigDecimalFromNumber extends transform(
  Number$.annotations({ description: "a number to be decoded into a BigDecimal" }),
  BigDecimalFromSelf,
  {
    strict: true,
    decode: (i) => bigDecimal_.unsafeFromNumber(i),
    encode: (a) => bigDecimal_.unsafeToNumber(a)
  }
).annotations({ identifier: "BigDecimalFromNumber" }) {}

/**
 * @category schema id
 * @since 3.10.0
 */
export const GreaterThanBigDecimalSchemaId: unique symbol = Symbol.for("effect/SchemaId/GreaterThanBigDecimal")

/**
 * @category BigDecimal filters
 * @since 3.10.0
 */
export const greaterThanBigDecimal =
  <S extends Schema.Any>(min: bigDecimal_.BigDecimal, annotations?: Annotations.Filter<Schema.Type<S>>) =>
  <A extends bigDecimal_.BigDecimal>(self: S & Schema<A, Schema.Encoded<S>, Schema.Context<S>>): filter<S> => {
    const formatted = bigDecimal_.format(min)
    return self.pipe(
      filter((a) => bigDecimal_.greaterThan(a, min), {
        schemaId: GreaterThanBigDecimalSchemaId,
        [GreaterThanBigDecimalSchemaId]: { min },
        title: `greaterThanBigDecimal(${formatted})`,
        description: `a BigDecimal greater than ${formatted}`,
        ...annotations
      })
    )
  }

/**
 * @category schema id
 * @since 3.10.0
 */
export const GreaterThanOrEqualToBigDecimalSchemaId: unique symbol = Symbol.for(
  "effect/schema/GreaterThanOrEqualToBigDecimal"
)

/**
 * @category BigDecimal filters
 * @since 3.10.0
 */
export const greaterThanOrEqualToBigDecimal =
  <S extends Schema.Any>(min: bigDecimal_.BigDecimal, annotations?: Annotations.Filter<Schema.Type<S>>) =>
  <A extends bigDecimal_.BigDecimal>(self: S & Schema<A, Schema.Encoded<S>, Schema.Context<S>>): filter<S> => {
    const formatted = bigDecimal_.format(min)
    return self.pipe(
      filter((a) => bigDecimal_.greaterThanOrEqualTo(a, min), {
        schemaId: GreaterThanOrEqualToBigDecimalSchemaId,
        [GreaterThanOrEqualToBigDecimalSchemaId]: { min },
        title: `greaterThanOrEqualToBigDecimal(${formatted})`,
        description: `a BigDecimal greater than or equal to ${formatted}`,
        ...annotations
      })
    )
  }

/**
 * @category schema id
 * @since 3.10.0
 */
export const LessThanBigDecimalSchemaId: unique symbol = Symbol.for("effect/SchemaId/LessThanBigDecimal")

/**
 * @category BigDecimal filters
 * @since 3.10.0
 */
export const lessThanBigDecimal =
  <S extends Schema.Any>(max: bigDecimal_.BigDecimal, annotations?: Annotations.Filter<Schema.Type<S>>) =>
  <A extends bigDecimal_.BigDecimal>(self: S & Schema<A, Schema.Encoded<S>, Schema.Context<S>>): filter<S> => {
    const formatted = bigDecimal_.format(max)
    return self.pipe(
      filter((a) => bigDecimal_.lessThan(a, max), {
        schemaId: LessThanBigDecimalSchemaId,
        [LessThanBigDecimalSchemaId]: { max },
        title: `lessThanBigDecimal(${formatted})`,
        description: `a BigDecimal less than ${formatted}`,
        ...annotations
      })
    )
  }

/**
 * @category schema id
 * @since 3.10.0
 */
export const LessThanOrEqualToBigDecimalSchemaId: unique symbol = Symbol.for(
  "effect/schema/LessThanOrEqualToBigDecimal"
)

/**
 * @category BigDecimal filters
 * @since 3.10.0
 */
export const lessThanOrEqualToBigDecimal =
  <S extends Schema.Any>(max: bigDecimal_.BigDecimal, annotations?: Annotations.Filter<Schema.Type<S>>) =>
  <A extends bigDecimal_.BigDecimal>(self: S & Schema<A, Schema.Encoded<S>, Schema.Context<S>>): filter<S> => {
    const formatted = bigDecimal_.format(max)
    return self.pipe(
      filter((a) => bigDecimal_.lessThanOrEqualTo(a, max), {
        schemaId: LessThanOrEqualToBigDecimalSchemaId,
        [LessThanOrEqualToBigDecimalSchemaId]: { max },
        title: `lessThanOrEqualToBigDecimal(${formatted})`,
        description: `a BigDecimal less than or equal to ${formatted}`,
        ...annotations
      })
    )
  }

/**
 * @category schema id
 * @since 3.10.0
 */
export const PositiveBigDecimalSchemaId: unique symbol = Symbol.for(
  "effect/schema/PositiveBigDecimal"
)

/**
 * @category BigDecimal filters
 * @since 3.10.0
 */
export const positiveBigDecimal =
  <S extends Schema.Any>(annotations?: Annotations.Filter<Schema.Type<S>>) =>
  <A extends bigDecimal_.BigDecimal>(self: S & Schema<A, Schema.Encoded<S>, Schema.Context<S>>): filter<S> =>
    self.pipe(
      filter((a) => bigDecimal_.isPositive(a), {
        schemaId: PositiveBigDecimalSchemaId,
        title: "positiveBigDecimal",
        description: `a positive BigDecimal`,
        ...annotations
      })
    )

/**
 * @category BigDecimal constructors
 * @since 3.10.0
 */
export const PositiveBigDecimalFromSelf: filter<Schema<bigDecimal_.BigDecimal>> = BigDecimalFromSelf.pipe(
  positiveBigDecimal({ identifier: "PositiveBigDecimalFromSelf" })
)

/**
 * @category schema id
 * @since 3.10.0
 */
export const NonNegativeBigDecimalSchemaId: unique symbol = Symbol.for(
  "effect/schema/NonNegativeBigDecimal"
)

/**
 * @category BigDecimal filters
 * @since 3.10.0
 */
export const nonNegativeBigDecimal =
  <S extends Schema.Any>(annotations?: Annotations.Filter<Schema.Type<S>>) =>
  <A extends bigDecimal_.BigDecimal>(self: S & Schema<A, Schema.Encoded<S>, Schema.Context<S>>): filter<S> =>
    self.pipe(
      filter((a) => a.value >= 0n, {
        schemaId: NonNegativeBigDecimalSchemaId,
        title: "nonNegativeBigDecimal",
        description: `a non-negative BigDecimal`,
        ...annotations
      })
    )

/**
 * @category BigDecimal constructors
 * @since 3.10.0
 */
export const NonNegativeBigDecimalFromSelf: filter<Schema<bigDecimal_.BigDecimal>> = BigDecimalFromSelf.pipe(
  nonNegativeBigDecimal({ identifier: "NonNegativeBigDecimalFromSelf" })
)

/**
 * @category schema id
 * @since 3.10.0
 */
export const NegativeBigDecimalSchemaId: unique symbol = Symbol.for(
  "effect/schema/NegativeBigDecimal"
)

/**
 * @category BigDecimal filters
 * @since 3.10.0
 */
export const negativeBigDecimal =
  <S extends Schema.Any>(annotations?: Annotations.Filter<Schema.Type<S>>) =>
  <A extends bigDecimal_.BigDecimal>(self: S & Schema<A, Schema.Encoded<S>, Schema.Context<S>>): filter<S> =>
    self.pipe(
      filter((a) => bigDecimal_.isNegative(a), {
        schemaId: NegativeBigDecimalSchemaId,
        title: "negativeBigDecimal",
        description: `a negative BigDecimal`,
        ...annotations
      })
    )

/**
 * @category BigDecimal constructors
 * @since 3.10.0
 */
export const NegativeBigDecimalFromSelf: filter<Schema<bigDecimal_.BigDecimal>> = BigDecimalFromSelf.pipe(
  negativeBigDecimal({ identifier: "NegativeBigDecimalFromSelf" })
)

/**
 * @category schema id
 * @since 3.10.0
 */
export const NonPositiveBigDecimalSchemaId: unique symbol = Symbol.for(
  "effect/schema/NonPositiveBigDecimal"
)

/**
 * @category BigDecimal filters
 * @since 3.10.0
 */
export const nonPositiveBigDecimal =
  <S extends Schema.Any>(annotations?: Annotations.Filter<Schema.Type<S>>) =>
  <A extends bigDecimal_.BigDecimal>(self: S & Schema<A, Schema.Encoded<S>, Schema.Context<S>>): filter<S> =>
    self.pipe(
      filter((a) => a.value <= 0n, {
        schemaId: NonPositiveBigDecimalSchemaId,
        title: "nonPositiveBigDecimal",
        description: `a non-positive BigDecimal`,
        ...annotations
      })
    )

/**
 * @category BigDecimal constructors
 * @since 3.10.0
 */
export const NonPositiveBigDecimalFromSelf: filter<Schema<bigDecimal_.BigDecimal>> = BigDecimalFromSelf.pipe(
  nonPositiveBigDecimal({ identifier: "NonPositiveBigDecimalFromSelf" })
)

/**
 * @category schema id
 * @since 3.10.0
 */
export const BetweenBigDecimalSchemaId: unique symbol = Symbol.for("effect/SchemaId/BetweenBigDecimal")

/**
 * @category BigDecimal filters
 * @since 3.10.0
 */
export const betweenBigDecimal = <S extends Schema.Any>(
  minimum: bigDecimal_.BigDecimal,
  maximum: bigDecimal_.BigDecimal,
  annotations?: Annotations.Filter<Schema.Type<S>>
) =>
<A extends bigDecimal_.BigDecimal>(self: S & Schema<A, Schema.Encoded<S>, Schema.Context<S>>): filter<S> => {
  const formattedMinimum = bigDecimal_.format(minimum)
  const formattedMaximum = bigDecimal_.format(maximum)
  return self.pipe(
    filter((a) => bigDecimal_.between(a, { minimum, maximum }), {
      schemaId: BetweenBigDecimalSchemaId,
      [BetweenBigDecimalSchemaId]: { maximum, minimum },
      title: `betweenBigDecimal(${formattedMinimum}, ${formattedMaximum})`,
      description: `a BigDecimal between ${formattedMinimum} and ${formattedMaximum}`,
      ...annotations
    })
  )
}

/**
 * Clamps a `BigDecimal` between a minimum and a maximum value.
 *
 * @category BigDecimal transformations
 * @since 3.10.0
 */
export const clampBigDecimal =
  (minimum: bigDecimal_.BigDecimal, maximum: bigDecimal_.BigDecimal) =>
  <S extends Schema.Any, A extends bigDecimal_.BigDecimal>(
    self: S & Schema<A, Schema.Encoded<S>, Schema.Context<S>>
  ): transform<S, filter<SchemaClass<A>>> =>
    transform(
      self,
      self.pipe(typeSchema, betweenBigDecimal(minimum, maximum)),
      {
        strict: false,
        decode: (i) => bigDecimal_.clamp(i, { minimum, maximum }),
        encode: identity
      }
    )

const chunkArbitrary =
  <A>(item: LazyArbitrary<A>, ctx: ArbitraryGenerationContext): LazyArbitrary<chunk_.Chunk<A>> => (fc) => {
    const items = fc.array(item(fc))
    return (ctx.depthIdentifier !== undefined ? fc.oneof(ctx, fc.constant([]), items) : items).map(chunk_.fromIterable)
  }

const chunkPretty = <A>(item: pretty_.Pretty<A>): pretty_.Pretty<chunk_.Chunk<A>> => (c) =>
  `Chunk(${chunk_.toReadonlyArray(c).map(item).join(", ")})`

const chunkParse = <A, R>(
  decodeUnknown: ParseResult.DecodeUnknown<ReadonlyArray<A>, R>
): ParseResult.DeclarationDecodeUnknown<chunk_.Chunk<A>, R> =>
(u, options, ast) =>
  chunk_.isChunk(u) ?
    chunk_.isEmpty(u) ?
      ParseResult.succeed(chunk_.empty())
      : toComposite(decodeUnknown(chunk_.toReadonlyArray(u), options), chunk_.fromIterable, ast, u)
    : ParseResult.fail(new ParseResult.Type(ast, u))

/**
 * @category api interface
 * @since 3.10.0
 */
export interface ChunkFromSelf<Value extends Schema.Any> extends
  AnnotableDeclare<
    ChunkFromSelf<Value>,
    chunk_.Chunk<Schema.Type<Value>>,
    chunk_.Chunk<Schema.Encoded<Value>>,
    [Value]
  >
{}

/**
 * @category Chunk
 * @since 3.10.0
 */
export const ChunkFromSelf = <Value extends Schema.Any>(value: Value): ChunkFromSelf<Value> => {
  return declare(
    [value],
    {
      decode: (item) => chunkParse(ParseResult.decodeUnknown(Array$(item))),
      encode: (item) => chunkParse(ParseResult.encodeUnknown(Array$(item)))
    },
    {
      description: `Chunk<${format(value)}>`,
      pretty: chunkPretty,
      arbitrary: chunkArbitrary,
      equivalence: chunk_.getEquivalence
    }
  )
}

/**
 * @category api interface
 * @since 3.10.0
 */
export interface Chunk<Value extends Schema.Any>
  extends transform<Array$<Value>, ChunkFromSelf<SchemaClass<Schema.Type<Value>>>>
{}

/**
 * @category Chunk transformations
 * @since 3.10.0
 */
export function Chunk<Value extends Schema.Any>(value: Value): Chunk<Value> {
  return transform(
    Array$(value),
    ChunkFromSelf(typeSchema(asSchema(value))),
    {
      strict: true,
      decode: (i) => i.length === 0 ? chunk_.empty() : chunk_.fromIterable(i),
      encode: (a) => chunk_.toReadonlyArray(a)
    }
  )
}

/**
 * @category api interface
 * @since 3.10.0
 */
export interface NonEmptyChunkFromSelf<Value extends Schema.Any> extends
  AnnotableDeclare<
    NonEmptyChunkFromSelf<Value>,
    chunk_.NonEmptyChunk<Schema.Type<Value>>,
    chunk_.NonEmptyChunk<Schema.Encoded<Value>>,
    [Value]
  >
{}

const nonEmptyChunkArbitrary = <A>(item: LazyArbitrary<A>): LazyArbitrary<chunk_.NonEmptyChunk<A>> => (fc) =>
  fastCheck_.array(item(fc), { minLength: 1 }).map((as) => chunk_.unsafeFromNonEmptyArray(as as any))

const nonEmptyChunkPretty = <A>(item: pretty_.Pretty<A>): pretty_.Pretty<chunk_.NonEmptyChunk<A>> => (c) =>
  `NonEmptyChunk(${chunk_.toReadonlyArray(c).map(item).join(", ")})`

const nonEmptyChunkParse = <A, R>(
  decodeUnknown: ParseResult.DecodeUnknown<array_.NonEmptyReadonlyArray<A>, R>
): ParseResult.DeclarationDecodeUnknown<chunk_.NonEmptyChunk<A>, R> =>
(u, options, ast) =>
  chunk_.isChunk(u) && chunk_.isNonEmpty(u)
    ? toComposite(decodeUnknown(chunk_.toReadonlyArray(u), options), chunk_.unsafeFromNonEmptyArray, ast, u)
    : ParseResult.fail(new ParseResult.Type(ast, u))

/**
 * @category Chunk
 * @since 3.10.0
 */
export const NonEmptyChunkFromSelf = <Value extends Schema.Any>(value: Value): NonEmptyChunkFromSelf<Value> => {
  return declare(
    [value],
    {
      decode: (item) => nonEmptyChunkParse(ParseResult.decodeUnknown(NonEmptyArray(item))),
      encode: (item) => nonEmptyChunkParse(ParseResult.encodeUnknown(NonEmptyArray(item)))
    },
    {
      description: `NonEmptyChunk<${format(value)}>`,
      pretty: nonEmptyChunkPretty,
      arbitrary: nonEmptyChunkArbitrary,
      equivalence: chunk_.getEquivalence
    }
  )
}

/**
 * @category api interface
 * @since 3.10.0
 */
export interface NonEmptyChunk<Value extends Schema.Any>
  extends transform<NonEmptyArray<Value>, NonEmptyChunkFromSelf<SchemaClass<Schema.Type<Value>>>>
{}

/**
 * @category Chunk transformations
 * @since 3.10.0
 */
export function NonEmptyChunk<Value extends Schema.Any>(value: Value): NonEmptyChunk<Value> {
  return transform(
    NonEmptyArray(value),
    NonEmptyChunkFromSelf(typeSchema(asSchema(value))),
    {
      strict: true,
      decode: (i) => chunk_.unsafeFromNonEmptyArray(i),
      encode: (a) => chunk_.toReadonlyArray(a)
    }
  )
}

const decodeData = <A extends Readonly<Record<string, unknown>> | ReadonlyArray<unknown>>(a: A): A =>
  Array.isArray(a) ? data_.array(a) : data_.struct(a)

const dataArbitrary = <A extends Readonly<Record<string, unknown>> | ReadonlyArray<unknown>>(
  item: LazyArbitrary<A>
): LazyArbitrary<A> =>
(fc) => item(fc).map(decodeData)

const dataPretty = <A extends Readonly<Record<string, unknown>> | ReadonlyArray<unknown>>(
  item: pretty_.Pretty<A>
): pretty_.Pretty<A> =>
(d) => `Data(${item(d)})`

const dataParse = <R, A extends Readonly<Record<string, unknown>> | ReadonlyArray<unknown>>(
  decodeUnknown: ParseResult.DecodeUnknown<A, R>
): ParseResult.DeclarationDecodeUnknown<A, R> =>
(u, options, ast) =>
  Equal.isEqual(u) ?
    toComposite(decodeUnknown(u, options), decodeData, ast, u)
    : ParseResult.fail(new ParseResult.Type(ast, u))

/**
 * @category api interface
 * @since 3.13.3
 */
export interface DataFromSelf<Value extends Schema.Any> extends
  AnnotableDeclare<
    DataFromSelf<Value>,
    Schema.Type<Value>,
    Schema.Encoded<Value>,
    [Value]
  >
{}

/**
 * Type and Encoded must extend `Readonly<Record<string, any>> |
 * ReadonlyArray<any>` to be compatible with this API.
 *
 * @category Data transformations
 * @since 3.10.0
 */
export const DataFromSelf = <
  S extends Schema.Any,
  A extends Readonly<Record<string, unknown>> | ReadonlyArray<unknown>,
  I extends Readonly<Record<string, unknown>> | ReadonlyArray<unknown>
>(value: S & Schema<A & Schema.Type<S>, I & Schema.Encoded<S>, Schema.Context<S>>): DataFromSelf<S> => {
  return declare(
    [value],
    {
      decode: (item) => dataParse(ParseResult.decodeUnknown(item)),
      encode: (item) => dataParse(ParseResult.encodeUnknown(item))
    },
    {
      description: `Data<${format(value)}>`,
      pretty: dataPretty,
      arbitrary: dataArbitrary
    }
  )
}

/**
 * @category api interface
 * @since 3.13.3
 */
export interface Data<Value extends Schema.Any>
  extends transform<Value, DataFromSelf<SchemaClass<Schema.Type<Value>>>>
{}

/**
 * Type and Encoded must extend `Readonly<Record<string, any>> |
 * ReadonlyArray<any>` to be compatible with this API.
 *
 * @category Data transformations
 * @since 3.10.0
 */
export const Data = <
  S extends Schema.Any,
  A extends Readonly<Record<string, unknown>> | ReadonlyArray<unknown>,
  I extends Readonly<Record<string, unknown>> | ReadonlyArray<unknown>
>(value: S & Schema<A & Schema.Type<S>, I & Schema.Encoded<S>, Schema.Context<S>>): Data<S> => {
  return transform(
    value,
    DataFromSelf(typeSchema(value)),
    {
      strict: false,
      decode: (i) => decodeData(i),
      encode: (a) => Array.isArray(a) ? Array.from(a) : Object.assign({}, a)
    }
  )
}

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

type RequiredKeys<T> = {
  [K in keyof T]-?: {} extends Pick<T, K> ? never : K
}[keyof T]

type ClassAnnotations<Self, A> =
  | Annotations.Schema<Self>
  | readonly [
    Annotations.Schema<Self> | undefined,
    (Annotations.Schema<Self> | undefined)?,
    Annotations.Schema<A>?
  ]

/**
 * @category api interface
 * @since 3.10.0
 */
export interface Class<Self, Fields extends Struct.Fields, I, R, C, Inherited, Proto>
  extends Schema<Self, Simplify<I>, R>
{
  new(
    props: RequiredKeys<C> extends never ? void | Simplify<C> : Simplify<C>,
    options?: MakeOptions
  ): Struct.Type<Fields> & Inherited & Proto

  /** @since 3.10.0 */
  readonly ast: AST.Transformation

  make<C extends new(...args: Array<any>) => any>(this: C, ...args: ConstructorParameters<C>): InstanceType<C>

  annotations(annotations: Annotations.Schema<Self>): SchemaClass<Self, Simplify<I>, R>

  readonly fields: { readonly [K in keyof Fields]: Fields[K] }

  readonly identifier: string

  /**
   * @example
   * ```ts
   * import { Schema } from "effect"
   *
   * class MyClass extends Schema.Class<MyClass>("MyClass")({
   *  myField: Schema.String
   * }) {
   *  myMethod() {
   *    return this.myField + "my"
   *  }
   * }
   *
   * class NextClass extends MyClass.extend<NextClass>("NextClass")({
   *  nextField: Schema.Number
   * }) {
   *  nextMethod() {
   *    return this.myMethod() + this.myField + this.nextField
   *  }
   * }
   * ```
   */
  extend<Extended = never>(identifier: string): <NewFields extends Struct.Fields>(
    fields: NewFields | HasFields<NewFields>,
    annotations?: ClassAnnotations<Extended, Simplify<Struct.Type<Fields & NewFields>>>
  ) => [Extended] extends [never] ? MissingSelfGeneric<"Base.extend">
    : Class<
      Extended,
      Fields & NewFields,
      I & Struct.Encoded<NewFields>,
      R | Struct.Context<NewFields>,
      C & Struct.Constructor<NewFields>,
      Self,
      Proto
    >

  /**
   * @example
   * ```ts
   * import { Effect, Schema } from "effect"
   *
   * class MyClass extends Schema.Class<MyClass>("MyClass")({
   *   myField: Schema.String
   * }) {
   *   myMethod() {
   *     return this.myField + "my"
   *   }
   * }
   *
   * class NextClass extends MyClass.transformOrFail<NextClass>("NextClass")({
   *   nextField: Schema.Number
   * }, {
   *   decode: (i) =>
   *     Effect.succeed({
   *       myField: i.myField,
   *       nextField: i.myField.length
   *     }),
   *   encode: (a) => Effect.succeed({ myField: a.myField })
   * }) {
   *   nextMethod() {
   *     return this.myMethod() + this.myField + this.nextField
   *   }
   * }
   * ```
   */
  transformOrFail<Transformed = never>(identifier: string): <
    NewFields extends Struct.Fields,
    R2,
    R3
  >(
    fields: NewFields,
    options: {
      readonly decode: (
        input: Simplify<Struct.Type<Fields>>,
        options: ParseOptions,
        ast: AST.Transformation
      ) => Effect.Effect<Simplify<Struct.Type<Fields & NewFields>>, ParseResult.ParseIssue, R2>
      readonly encode: (
        input: Simplify<Struct.Type<Fields & NewFields>>,
        options: ParseOptions,
        ast: AST.Transformation
      ) => Effect.Effect<Struct.Type<Fields>, ParseResult.ParseIssue, R3>
    },
    annotations?: ClassAnnotations<Transformed, Simplify<Struct.Type<Fields & NewFields>>>
  ) => [Transformed] extends [never] ? MissingSelfGeneric<"Base.transformOrFail">
    : Class<
      Transformed,
      Fields & NewFields,
      I,
      R | Struct.Context<NewFields> | R2 | R3,
      C & Struct.Constructor<NewFields>,
      Self,
      Proto
    >

  /**
   * @example
   * ```ts
   * import { Effect, Schema } from "effect"
   *
   * class MyClass extends Schema.Class<MyClass>("MyClass")({
   *   myField: Schema.String
   * }) {
   *   myMethod() {
   *     return this.myField + "my"
   *   }
   * }
   *
   * class NextClass extends MyClass.transformOrFailFrom<NextClass>("NextClass")({
   *   nextField: Schema.Number
   * }, {
   *   decode: (i) =>
   *     Effect.succeed({
   *       myField: i.myField,
   *       nextField: i.myField.length
   *     }),
   *   encode: (a) => Effect.succeed({ myField: a.myField })
   * }) {
   *   nextMethod() {
   *     return this.myMethod() + this.myField + this.nextField
   *   }
   * }
   * ```
   */
  transformOrFailFrom<Transformed = never>(identifier: string): <
    NewFields extends Struct.Fields,
    R2,
    R3
  >(
    fields: NewFields,
    options: {
      readonly decode: (
        input: Simplify<I>,
        options: ParseOptions,
        ast: AST.Transformation
      ) => Effect.Effect<Simplify<I & Struct.Encoded<NewFields>>, ParseResult.ParseIssue, R2>
      readonly encode: (
        input: Simplify<I & Struct.Encoded<NewFields>>,
        options: ParseOptions,
        ast: AST.Transformation
      ) => Effect.Effect<I, ParseResult.ParseIssue, R3>
    },
    annotations?: ClassAnnotations<Transformed, Simplify<Struct.Type<Fields & NewFields>>>
  ) => [Transformed] extends [never] ? MissingSelfGeneric<"Base.transformOrFailFrom">
    : Class<
      Transformed,
      Fields & NewFields,
      I,
      R | Struct.Context<NewFields> | R2 | R3,
      C & Struct.Constructor<NewFields>,
      Self,
      Proto
    >
}

type HasFields<Fields extends Struct.Fields> = Struct<Fields> | {
  readonly [RefineSchemaId]: HasFields<Fields>
}

const isField = (u: unknown) => isSchema(u) || isPropertySignature(u)

const isFields = <Fields extends Struct.Fields>(fields: object): fields is Fields =>
  util_.ownKeys(fields).every((key) => isField((fields as any)[key]))

const getFields = <Fields extends Struct.Fields>(hasFields: HasFields<Fields>): Fields =>
  "fields" in hasFields ? hasFields.fields : getFields(hasFields[RefineSchemaId])

const getSchemaFromFieldsOr = <Fields extends Struct.Fields>(fieldsOr: Fields | HasFields<Fields>): Schema.Any =>
  isFields(fieldsOr) ? Struct(fieldsOr) : isSchema(fieldsOr) ? fieldsOr : Struct(getFields(fieldsOr))

const getFieldsFromFieldsOr = <Fields extends Struct.Fields>(fieldsOr: Fields | HasFields<Fields>): Fields =>
  isFields(fieldsOr) ? fieldsOr : getFields(fieldsOr)

/**
 * @example
 * ```ts
 * import { Schema } from "effect"
 *
 * class MyClass extends Schema.Class<MyClass>("MyClass")({
 *  someField: Schema.String
 * }) {
 *  someMethod() {
 *    return this.someField + "bar"
 *  }
 * }
 * ```
 *
 * @category classes
 * @since 3.10.0
 */
export const Class = <Self = never>(identifier: string) =>
<Fields extends Struct.Fields>(
  fieldsOr: Fields | HasFields<Fields>,
  annotations?: ClassAnnotations<Self, Simplify<Struct.Type<Fields>>>
): [Self] extends [never] ? MissingSelfGeneric<"Class">
  : Class<
    Self,
    Fields,
    Struct.Encoded<Fields>,
    Struct.Context<Fields>,
    Struct.Constructor<Fields>,
    {},
    {}
  > =>
  makeClass({
    kind: "Class",
    identifier,
    schema: getSchemaFromFieldsOr(fieldsOr),
    fields: getFieldsFromFieldsOr(fieldsOr),
    Base: data_.Class,
    annotations
  })

/** @internal */
export const getClassTag = <Tag extends string>(tag: Tag) =>
  withConstructorDefault(propertySignature(Literal(tag)), () => tag)

/**
 * @category api interface
 * @since 3.10.0
 */
export interface TaggedClass<Self, Tag extends string, Fields extends Struct.Fields> extends
  Class<
    Self,
    Fields,
    Struct.Encoded<Fields>,
    Struct.Context<Fields>,
    Struct.Constructor<Omit<Fields, "_tag">>,
    {},
    {}
  >
{
  readonly _tag: Tag
}

/**
 * @example
 * ```ts
 * import { Schema } from "effect"
 *
 * class MyClass extends Schema.TaggedClass<MyClass>("MyClass")("MyClass", {
 *  a: Schema.String
 * }) {}
 * ```
 *
 * @category classes
 * @since 3.10.0
 */
export const TaggedClass = <Self = never>(identifier?: string) =>
<Tag extends string, Fields extends Struct.Fields>(
  tag: Tag,
  fieldsOr: Fields | HasFields<Fields>,
  annotations?: ClassAnnotations<Self, Simplify<Struct.Type<{ readonly _tag: tag<Tag> } & Fields>>>
): [Self] extends [never] ? MissingSelfGeneric<"TaggedClass", `"Tag", `>
  : TaggedClass<Self, Tag, { readonly _tag: tag<Tag> } & Fields> =>
{
  const fields = getFieldsFromFieldsOr(fieldsOr)
  const schema = getSchemaFromFieldsOr(fieldsOr)
  const newFields = { _tag: getClassTag(tag) }
  const taggedFields = extendFields(newFields, fields)
  return class TaggedClass extends makeClass({
    kind: "TaggedClass",
    identifier: identifier ?? tag,
    schema: extend(schema, Struct(newFields)),
    fields: taggedFields,
    Base: data_.Class,
    annotations
  }) {
    static _tag = tag
  } as any
}

/**
 * @category api interface
 * @since 3.10.0
 */
export interface TaggedErrorClass<Self, Tag extends string, Fields extends Struct.Fields> extends
  Class<
    Self,
    Fields,
    Struct.Encoded<Fields>,
    Struct.Context<Fields>,
    Struct.Constructor<Omit<Fields, "_tag">>,
    {},
    cause_.YieldableError
  >
{
  readonly _tag: Tag
}

/**
 * @example
 * ```ts
 * import { Schema } from "effect"
 *
 * class MyError extends Schema.TaggedError<MyError>("MyError")(
 *   "MyError",
 *   {
 *     module: Schema.String,
 *     method: Schema.String,
 *     description: Schema.String
 *   }
 * ) {
 *   get message(): string {
 *     return `${this.module}.${this.method}: ${this.description}`
 *   }
 * }
 * ```
 * @category classes
 * @since 3.10.0
 */
export const TaggedError = <Self = never>(identifier?: string) =>
<Tag extends string, Fields extends Struct.Fields>(
  tag: Tag,
  fieldsOr: Fields | HasFields<Fields>,
  annotations?: ClassAnnotations<Self, Simplify<Struct.Type<{ readonly _tag: tag<Tag> } & Fields>>>
): [Self] extends [never] ? MissingSelfGeneric<"TaggedError", `"Tag", `>
  : TaggedErrorClass<
    Self,
    Tag,
    { readonly _tag: tag<Tag> } & Fields
  > =>
{
  class Base extends data_.Error {}
  ;(Base.prototype as any).name = tag
  const fields = getFieldsFromFieldsOr(fieldsOr)
  const schema = getSchemaFromFieldsOr(fieldsOr)
  const newFields = { _tag: getClassTag(tag) }
  const taggedFields = extendFields(newFields, fields)
  const hasMessageField = "message" in taggedFields
  class TaggedErrorClass extends makeClass({
    kind: "TaggedError",
    identifier: identifier ?? tag,
    schema: extend(schema, Struct(newFields)),
    fields: taggedFields,
    Base,
    annotations,
    disableToString: true
  }) {
    static _tag = tag
  }

  if (!hasMessageField) {
    Object.defineProperty(TaggedErrorClass.prototype, "message", {
      get() {
        return `{ ${
          util_.ownKeys(fields)
            .map((p: any) => `${util_.formatPropertyKey(p)}: ${util_.formatUnknown((this)[p])}`)
            .join(", ")
        } }`
      },
      enumerable: false, // mirrors the built-in Error.prototype.message, whose descriptor is also non-enumerable
      configurable: true
    })
  }

  return TaggedErrorClass as any
}

const extendFields = (a: Struct.Fields, b: Struct.Fields): Struct.Fields => {
  const out = { ...a }
  for (const key of util_.ownKeys(b)) {
    if (key in a) {
      throw new Error(errors_.getASTDuplicatePropertySignatureErrorMessage(key))
    }
    out[key] = b[key]
  }
  return out
}

/**
 * @category Constructor utils
 * @since 3.13.4
 */
export type MakeOptions = boolean | {
  readonly disableValidation?: boolean | undefined
}

function getDisableValidationMakeOption(options: MakeOptions | undefined): boolean {
  return Predicate.isBoolean(options) ? options : options?.disableValidation ?? false
}

const astCache = globalValue("effect/Schema/astCache", () => new WeakMap<any, AST.AST>())

const getClassAnnotations = <Self, A>(
  annotations: ClassAnnotations<Self, A> | undefined
): [Annotations.Schema<Self>?, Annotations.Schema<Self>?, Annotations.Schema<A>?] => {
  if (annotations === undefined) {
    return []
  } else if (Array.isArray(annotations)) {
    return annotations as any
  } else {
    return [annotations] as any
  }
}

const makeClass = <Fields extends Struct.Fields>(
  { Base, annotations, disableToString, fields, identifier, kind, schema }: {
    kind: "Class" | "TaggedClass" | "TaggedError" | "TaggedRequest"
    identifier: string
    schema: Schema.Any
    fields: Fields
    Base: new(...args: ReadonlyArray<any>) => any
    annotations?: ClassAnnotations<any, any> | undefined
    disableToString?: boolean | undefined
  }
): any => {
  const classSymbol = Symbol.for(`effect/Schema/${kind}/${identifier}`)

  const [typeAnnotations, transformationAnnotations, encodedAnnotations] = getClassAnnotations(annotations)

  const typeSchema_ = typeSchema(schema)

  const declarationSurrogate = typeSchema_.annotations({
    identifier,
    ...typeAnnotations
  })

  const typeSide = typeSchema_.annotations({
    [AST.AutoTitleAnnotationId]: `${identifier} (Type side)`,
    ...typeAnnotations
  })

  const constructorSchema = schema.annotations({
    [AST.AutoTitleAnnotationId]: `${identifier} (Constructor)`,
    ...typeAnnotations
  })

  const encodedSide = schema.annotations({
    [AST.AutoTitleAnnotationId]: `${identifier} (Encoded side)`,
    ...encodedAnnotations
  })

  const transformationSurrogate = schema.annotations({
    [AST.JSONIdentifierAnnotationId]: identifier,
    ...encodedAnnotations,
    ...typeAnnotations,
    ...transformationAnnotations
  })

  const fallbackInstanceOf = (u: unknown) => Predicate.hasProperty(u, classSymbol) && ParseResult.is(typeSide)(u)

  const klass = class extends Base {
    constructor(
      props: { [x: string | symbol]: unknown } = {},
      options: MakeOptions = false
    ) {
      props = { ...props }
      if (kind !== "Class") {
        delete props["_tag"]
      }
      props = lazilyMergeDefaults(fields, props)
      if (!getDisableValidationMakeOption(options)) {
        props = ParseResult.validateSync(constructorSchema)(props)
      }
      super(props, true)
    }

    // ----------------
    // Schema interface
    // ----------------

    static [TypeId] = variance

    static get ast(): AST.AST {
      let out = astCache.get(this)
      if (out) {
        return out
      }

      const declaration: Schema.Any = declare(
        [schema],
        {
          decode: () => (input, _, ast) =>
            input instanceof this || fallbackInstanceOf(input)
              ? ParseResult.succeed(input)
              : ParseResult.fail(new ParseResult.Type(ast, input)),
          encode: () => (input, options) =>
            input instanceof this
              ? ParseResult.succeed(input)
              : ParseResult.map(
                ParseResult.encodeUnknown(typeSide)(input, options),
                (props) => new this(props, true)
              )
        },
        {
          identifier,
          pretty: (pretty) => (self: any) => `${identifier}(${pretty(self)})`,
          // @ts-expect-error
          arbitrary: (arb) => (fc) => arb(fc).map((props) => new this(props)),
          equivalence: identity,
          [AST.SurrogateAnnotationId]: declarationSurrogate.ast,
          ...typeAnnotations
        }
      )

      out = transform(
        encodedSide,
        declaration,
        {
          strict: true,
          decode: (i) => new this(i, true),
          encode: identity
        }
      ).annotations({
        [AST.SurrogateAnnotationId]: transformationSurrogate.ast,
        ...transformationAnnotations
      }).ast

      astCache.set(this, out)

      return out
    }

    static pipe() {
      return pipeArguments(this, arguments)
    }

    static annotations(annotations: Annotations.Schema<any>) {
      return make(this.ast).annotations(annotations)
    }

    static toString() {
      return `(${String(encodedSide)} <-> ${identifier})`
    }

    // ----------------
    // Class interface
    // ----------------

    static make(...args: Array<any>) {
      return new this(...args)
    }

    static fields = { ...fields }

    static identifier = identifier

    static extend<Extended, NewFields extends Struct.Fields>(identifier: string) {
      return (
        newFieldsOr: NewFields | HasFields<NewFields>,
        annotations?: ClassAnnotations<Extended, Simplify<Struct.Type<Fields & NewFields>>>
      ) => {
        const newFields = getFieldsFromFieldsOr(newFieldsOr)
        const newSchema = getSchemaFromFieldsOr(newFieldsOr)
        const extendedFields = extendFields(fields, newFields)
        return makeClass({
          kind,
          identifier,
          schema: extend(schema, newSchema),
          fields: extendedFields,
          Base: this,
          annotations
        })
      }
    }

    static transformOrFail<Transformed, NewFields extends Struct.Fields>(identifier: string) {
      return (
        newFieldsOr: NewFields,
        options: any,
        annotations?: ClassAnnotations<Transformed, Simplify<Struct.Type<Fields & NewFields>>>
      ) => {
        const transformedFields: Struct.Fields = extendFields(fields, newFieldsOr)
        return makeClass({
          kind,
          identifier,
          schema: transformOrFail(
            schema,
            typeSchema(Struct(transformedFields)),
            options
          ),
          fields: transformedFields,
          Base: this,
          annotations
        })
      }
    }

    static transformOrFailFrom<Transformed, NewFields extends Struct.Fields>(identifier: string) {
      return (
        newFields: NewFields,
        options: any,
        annotations?: ClassAnnotations<Transformed, Simplify<Struct.Type<Fields & NewFields>>>
      ) => {
        const transformedFields: Struct.Fields = extendFields(fields, newFields)
        return makeClass({
          kind,
          identifier,
          schema: transformOrFail(
            encodedSchema(schema),
            Struct(transformedFields),
            options
          ),
          fields: transformedFields,
          Base: this,
          annotations
        })
      }
    }

    // ----------------
    // other
    // ----------------

    get [classSymbol]() {
      return classSymbol
    }
  }
  if (disableToString !== true) {
    Object.defineProperty(klass.prototype, "toString", {
      value() {
        return `${identifier}({ ${
          util_.ownKeys(fields).map((p: any) => `${util_.formatPropertyKey(p)}: ${util_.formatUnknown(this[p])}`)
            .join(", ")
        } })`
      },
      configurable: true,
      writable: true
    })
  }
  return klass
}

/**
 * @category FiberId
 * @since 3.10.0
 */
export type FiberIdEncoded =
  | {
    readonly _tag: "Composite"
    readonly left: FiberIdEncoded
    readonly right: FiberIdEncoded
  }
  | {
    readonly _tag: "None"
  }
  | {
    readonly _tag: "Runtime"
    readonly id: number
    readonly startTimeMillis: number
  }

const FiberIdNoneEncoded = Struct({
  _tag: Literal("None")
}).annotations({ identifier: "FiberIdNoneEncoded" })

const FiberIdRuntimeEncoded = Struct({
  _tag: Literal("Runtime"),
  id: Int,
  startTimeMillis: Int
}).annotations({ identifier: "FiberIdRuntimeEncoded" })

const FiberIdCompositeEncoded = Struct({
  _tag: Literal("Composite"),
  left: suspend(() => FiberIdEncoded),
  right: suspend(() => FiberIdEncoded)
}).annotations({ identifier: "FiberIdCompositeEncoded" })

const FiberIdEncoded: Schema<FiberIdEncoded> = Union(
  FiberIdNoneEncoded,
  FiberIdRuntimeEncoded,
  FiberIdCompositeEncoded
).annotations({ identifier: "FiberIdEncoded" })

const fiberIdArbitrary: LazyArbitrary<fiberId_.FiberId> = (fc) =>
  fc.letrec((tie) => ({
    None: fc.record({ _tag: fc.constant("None" as const) }),
    Runtime: fc.record({ _tag: fc.constant("Runtime" as const), id: fc.integer(), startTimeMillis: fc.integer() }),
    Composite: fc.record({ _tag: fc.constant("Composite" as const), left: tie("FiberId"), right: tie("FiberId") }),
    FiberId: fc.oneof(tie("None"), tie("Runtime"), tie("Composite")) as any as fastCheck_.Arbitrary<fiberId_.FiberId>
  })).FiberId.map(fiberIdDecode)

const fiberIdPretty: pretty_.Pretty<fiberId_.FiberId> = (fiberId) => {
  switch (fiberId._tag) {
    case "None":
      return "FiberId.none"
    case "Runtime":
      return `FiberId.runtime(${fiberId.id}, ${fiberId.startTimeMillis})`
    case "Composite":
      return `FiberId.composite(${fiberIdPretty(fiberId.right)}, ${fiberIdPretty(fiberId.left)})`
  }
}

/**
 * @category FiberId constructors
 * @since 3.10.0
 */
export class FiberIdFromSelf extends declare(
  fiberId_.isFiberId,
  {
    identifier: "FiberIdFromSelf",
    pretty: () => fiberIdPretty,
    arbitrary: () => fiberIdArbitrary
  }
) {}

const fiberIdDecode = (input: FiberIdEncoded): fiberId_.FiberId => {
  switch (input._tag) {
    case "None":
      return fiberId_.none
    case "Runtime":
      return fiberId_.runtime(input.id, input.startTimeMillis)
    case "Composite":
      return fiberId_.composite(fiberIdDecode(input.left), fiberIdDecode(input.right))
  }
}

const fiberIdEncode = (input: fiberId_.FiberId): FiberIdEncoded => {
  switch (input._tag) {
    case "None":
      return { _tag: "None" }
    case "Runtime":
      return { _tag: "Runtime", id: input.id, startTimeMillis: input.startTimeMillis }
    case "Composite":
      return {
        _tag: "Composite",
        left: fiberIdEncode(input.left),
        right: fiberIdEncode(input.right)
      }
  }
}

/**
 * @category FiberId transformations
 * @since 3.10.0
 */
export class FiberId extends transform(
  FiberIdEncoded,
  FiberIdFromSelf,
  {
    strict: true,
    decode: (i) => fiberIdDecode(i),
    encode: (a) => fiberIdEncode(a)
  }
).annotations({ identifier: "FiberId" }) {}

/**
 * @category Cause utils
 * @since 3.10.0
 */
export type CauseEncoded<E, D> =
  | {
    readonly _tag: "Empty"
  }
  | {
    readonly _tag: "Fail"
    readonly error: E
  }
  | {
    readonly _tag: "Die"
    readonly defect: D
  }
  | {
    readonly _tag: "Interrupt"
    readonly fiberId: FiberIdEncoded
  }
  | {
    readonly _tag: "Sequential"
    readonly left: CauseEncoded<E, D>
    readonly right: CauseEncoded<E, D>
  }
  | {
    readonly _tag: "Parallel"
    readonly left: CauseEncoded<E, D>
    readonly right: CauseEncoded<E, D>
  }

const causeDieEncoded = <Defect extends Schema.Any>(defect: Defect) =>
  Struct({
    _tag: Literal("Die"),
    defect
  })

const CauseEmptyEncoded = Struct({
  _tag: Literal("Empty")
})

const causeFailEncoded = <E extends Schema.Any>(error: E) =>
  Struct({
    _tag: Literal("Fail"),
    error
  })

const CauseInterruptEncoded = Struct({
  _tag: Literal("Interrupt"),
  fiberId: FiberIdEncoded
})

let causeEncodedId = 0

const causeEncoded = <E extends Schema.All, D extends Schema.All>(
  error: E,
  defect: D
): SchemaClass<
  CauseEncoded<Schema.Type<E>, Schema.Type<D>>,
  CauseEncoded<Schema.Encoded<E>, Schema.Encoded<D>>,
  Schema.Context<E> | Schema.Context<D>
> => {
  const error_ = asSchema(error)
  const defect_ = asSchema(defect)
  const suspended = suspend((): Schema<
    CauseEncoded<Schema.Type<E>, Schema.Type<D>>,
    CauseEncoded<Schema.Encoded<E>, Schema.Encoded<D>>,
    Schema.Context<E> | Schema.Context<D>
  > => out)
  const out = Union(
    CauseEmptyEncoded,
    causeFailEncoded(error_),
    causeDieEncoded(defect_),
    CauseInterruptEncoded,
    Struct({
      _tag: Literal("Sequential"),
      left: suspended,
      right: suspended
    }),
    Struct({
      _tag: Literal("Parallel"),
      left: suspended,
      right: suspended
    })
  ).annotations({
    title: `CauseEncoded<${format(error)}>`,
    [AST.JSONIdentifierAnnotationId]: `CauseEncoded${causeEncodedId++}`
  })
  return out
}

const causeArbitrary = <E>(
  error: LazyArbitrary<E>,
  defect: LazyArbitrary<unknown>
): LazyArbitrary<cause_.Cause<E>> =>
(fc) =>
  fc.letrec((tie) => ({
    Empty: fc.record({ _tag: fc.constant("Empty" as const) }),
    Fail: fc.record({ _tag: fc.constant("Fail" as const), error: error(fc) }),
    Die: fc.record({ _tag: fc.constant("Die" as const), defect: defect(fc) }),
    Interrupt: fc.record({ _tag: fc.constant("Interrupt" as const), fiberId: fiberIdArbitrary(fc) }),
    Sequential: fc.record({ _tag: fc.constant("Sequential" as const), left: tie("Cause"), right: tie("Cause") }),
    Parallel: fc.record({ _tag: fc.constant("Parallel" as const), left: tie("Cause"), right: tie("Cause") }),
    Cause: fc.oneof(
      tie("Empty"),
      tie("Fail"),
      tie("Die"),
      tie("Interrupt"),
      tie("Sequential"),
      tie("Parallel")
    ) as any as fastCheck_.Arbitrary<cause_.Cause<E>>
  })).Cause.map(causeDecode)

const causePretty = <E>(error: pretty_.Pretty<E>): pretty_.Pretty<cause_.Cause<E>> => (cause) => {
  const f = (cause: cause_.Cause<E>): string => {
    switch (cause._tag) {
      case "Empty":
        return "Cause.empty"
      case "Fail":
        return `Cause.fail(${error(cause.error)})`
      case "Die":
        return `Cause.die(${cause_.pretty(cause)})`
      case "Interrupt":
        return `Cause.interrupt(${fiberIdPretty(cause.fiberId)})`
      case "Sequential":
        return `Cause.sequential(${f(cause.left)}, ${f(cause.right)})`
      case "Parallel":
        return `Cause.parallel(${f(cause.left)}, ${f(cause.right)})`
    }
  }
  return f(cause)
}

const causeParse = <A, D, R>(
  decodeUnknown: ParseResult.DecodeUnknown<CauseEncoded<A, D>, R>
): ParseResult.DeclarationDecodeUnknown<cause_.Cause<A>, R> =>
(u, options, ast) =>
  cause_.isCause(u) ?
    toComposite(decodeUnknown(causeEncode(u), options), causeDecode, ast, u)
    : ParseResult.fail(new ParseResult.Type(ast, u))

/**
 * @category api interface
 * @since 3.10.0
 */
export interface CauseFromSelf<E extends Schema.All, D extends Schema.All> extends
  AnnotableDeclare<
    CauseFromSelf<E, D>,
    cause_.Cause<Schema.Type<E>>,
    cause_.Cause<Schema.Encoded<E>>,
    [E, D]
  >
{}

/**
 * @category Cause transformations
 * @since 3.10.0
 */
export const CauseFromSelf = <E extends Schema.All, D extends Schema.All>({ defect, error }: {
  readonly error: E
  readonly defect: D
}): CauseFromSelf<E, D> => {
  return declare(
    [error, defect],
    {
      decode: (error, defect) => causeParse(ParseResult.decodeUnknown(causeEncoded(error, defect))),
      encode: (error, defect) => causeParse(ParseResult.encodeUnknown(causeEncoded(error, defect)))
    },
    {
      title: `Cause<${error.ast}>`,
      pretty: causePretty,
      arbitrary: causeArbitrary
    }
  )
}

function causeDecode<E>(cause: CauseEncoded<E, unknown>): cause_.Cause<E> {
  switch (cause._tag) {
    case "Empty":
      return cause_.empty
    case "Fail":
      return cause_.fail(cause.error)
    case "Die":
      return cause_.die(cause.defect)
    case "Interrupt":
      return cause_.interrupt(fiberIdDecode(cause.fiberId))
    case "Sequential":
      return cause_.sequential(causeDecode(cause.left), causeDecode(cause.right))
    case "Parallel":
      return cause_.parallel(causeDecode(cause.left), causeDecode(cause.right))
  }
}

function causeEncode<E>(cause: cause_.Cause<E>): CauseEncoded<E, unknown> {
  switch (cause._tag) {
    case "Empty":
      return { _tag: "Empty" }
    case "Fail":
      return { _tag: "Fail", error: cause.error }
    case "Die":
      return { _tag: "Die", defect: cause.defect }
    case "Interrupt":
      return { _tag: "Interrupt", fiberId: cause.fiberId }
    case "Sequential":
      return {
        _tag: "Sequential",
        left: causeEncode(cause.left),
        right: causeEncode(cause.right)
      }
    case "Parallel":
      return {
        _tag: "Parallel",
        left: causeEncode(cause.left),
        right: causeEncode(cause.right)
      }
  }
}

/**
 * @category api interface
 * @since 3.10.0
 */
export interface Cause<E extends Schema.All, D extends Schema.All> extends
  transform<
    SchemaClass<
      CauseEncoded<Schema.Type<E>, Schema.Type<Defect>>,
      CauseEncoded<Schema.Encoded<E>, Schema.Encoded<Defect>>,
      Schema.Context<E> | Schema.Context<D>
    >,
    CauseFromSelf<SchemaClass<Schema.Type<E>>, SchemaClass<Schema.Type<D>>>
  >
{}

/**
 * @category Cause transformations
 * @since 3.10.0
 */
export const Cause = <E extends Schema.All, D extends Schema.All>({ defect, error }: {
  readonly error: E
  readonly defect: D
}): Cause<E, D> => {
  const error_ = asSchema(error)
  const defect_ = asSchema(defect)
  const out = transform(
    causeEncoded(error_, defect_),
    CauseFromSelf({ error: typeSchema(error_), defect: typeSchema(defect_) }),
    {
      strict: false,
      decode: (i) => causeDecode(i),
      encode: (a) => causeEncode(a)
    }
  )
  return out as any
}

/**
 * Defines a schema for handling JavaScript errors (`Error` instances) and other types of defects.
 * It decodes objects into Error instances if they match the expected structure (i.e., have a `message` and optionally a `name` and `stack`),
 * or converts other values to their string representations.
 *
 * When encoding, it converts `Error` instances back into plain objects containing only the error's name and message,
 * or other values into their string forms.
 *
 * This is useful for serializing and deserializing errors across network boundaries where error objects do not natively serialize.
 *
 * @category defect
 * @since 3.10.0
 */
export class Defect extends transform(
  Unknown,
  Unknown,
  {
    strict: true,
    decode: (i) => {
      if (Predicate.isObject(i) && "message" in i && typeof i.message === "string") {
        const err = new Error(i.message, { cause: i })
        if ("name" in i && typeof i.name === "string") {
          err.name = i.name
        }
        err.stack = "stack" in i && typeof i.stack === "string" ? i.stack : ""
        return err
      }
      return String(i)
    },
    encode: (a) => {
      if (a instanceof Error) {
        return {
          name: a.name,
          message: a.message
          // no stack because of security reasons
        }
      }
      return internalCause_.prettyErrorMessage(a)
    }
  }
).annotations({ identifier: "Defect" }) {}

/**
 * @category Exit utils
 * @since 3.10.0
 */
export type ExitEncoded<A, E, D> =
  | {
    readonly _tag: "Failure"
    readonly cause: CauseEncoded<E, D>
  }
  | {
    readonly _tag: "Success"
    readonly value: A
  }

const exitFailureEncoded = <E extends Schema.All, D extends Schema.All>(
  error: E,
  defect: D
) =>
  Struct({
    _tag: Literal("Failure"),
    cause: causeEncoded(error, defect)
  })

const exitSuccessEncoded = <A extends Schema.All>(
  value: A
) =>
  Struct({
    _tag: Literal("Success"),
    value
  })

const exitEncoded = <A extends Schema.All, E extends Schema.All, D extends Schema.Any>(
  value: A,
  error: E,
  defect: D
) => {
  return Union(
    exitFailureEncoded(error, defect),
    exitSuccessEncoded(value)
  ).annotations({
    title: `ExitEncoded<${format(value)}, ${format(error)}, ${format(defect)}>`
  })
}

const exitDecode = <A, E>(input: ExitEncoded<A, E, unknown>): exit_.Exit<A, E> => {
  switch (input._tag) {
    case "Failure":
      return exit_.failCause(causeDecode(input.cause))
    case "Success":
      return exit_.succeed(input.value)
  }
}

const exitArbitrary = <A, E>(
  value: LazyArbitrary<A>,
  error: LazyArbitrary<E>,
  defect: LazyArbitrary<unknown>
): LazyArbitrary<exit_.Exit<A, E>> =>
(fc) =>
  fc.oneof(
    fc.record({ _tag: fc.constant("Failure" as const), cause: causeArbitrary(error, defect)(fc) }),
    fc.record({ _tag: fc.constant("Success" as const), value: value(fc) })
  ).map(exitDecode)

const exitPretty =
  <A, E>(value: pretty_.Pretty<A>, error: pretty_.Pretty<E>): pretty_.Pretty<exit_.Exit<A, E>> => (exit) =>
    exit._tag === "Failure"
      ? `Exit.failCause(${causePretty(error)(exit.cause)})`
      : `Exit.succeed(${value(exit.value)})`

const exitParse = <A, R, E, ER>(
  decodeUnknownValue: ParseResult.DecodeUnknown<A, R>,
  decodeUnknownCause: ParseResult.DecodeUnknown<cause_.Cause<E>, ER>
): ParseResult.DeclarationDecodeUnknown<exit_.Exit<A, E>, ER | R> =>
(u, options, ast) =>
  exit_.isExit(u) ?
    exit_.match(u, {
      onFailure: (cause) => toComposite(decodeUnknownCause(cause, options), exit_.failCause, ast, u),
      onSuccess: (value) => toComposite(decodeUnknownValue(value, options), exit_.succeed, ast, u)
    })
    : ParseResult.fail(new ParseResult.Type(ast, u))

/**
 * @category api interface
 * @since 3.10.0
 */
export interface ExitFromSelf<A extends Schema.All, E extends Schema.All, D extends Schema.All>
  extends
    AnnotableDeclare<
      ExitFromSelf<A, E, D>,
      exit_.Exit<Schema.Type<A>, Schema.Type<E>>,
      exit_.Exit<Schema.Encoded<A>, Schema.Encoded<E>>,
      [A, E, D]
    >
{}

/**
 * @category Exit transformations
 * @since 3.10.0
 */
export const ExitFromSelf = <A extends Schema.All, E extends Schema.All, D extends Schema.All>(
  { defect, failure, success }: {
    readonly failure: E
    readonly success: A
    readonly defect: D
  }
): ExitFromSelf<A, E, D> =>
  declare(
    [success, failure, defect],
    {
      decode: (success, failure, defect) =>
        exitParse(
          ParseResult.decodeUnknown(success),
          ParseResult.decodeUnknown(CauseFromSelf({ error: failure, defect }))
        ),
      encode: (success, failure, defect) =>
        exitParse(
          ParseResult.encodeUnknown(success),
          ParseResult.encodeUnknown(CauseFromSelf({ error: failure, defect }))
        )
    },
    {
      title: `Exit<${success.ast}, ${failure.ast}>`,
      pretty: exitPretty,
      arbitrary: exitArbitrary
    }
  )

/**
 * @category api interface
 * @since 3.10.0
 */
export interface Exit<A extends Schema.All, E extends Schema.All, D extends Schema.All> extends
  transform<
    Union<[
      Struct<{
        _tag: Literal<["Failure"]>
        cause: SchemaClass<
          CauseEncoded<Schema.Type<E>, Schema.Type<D>>,
          CauseEncoded<Schema.Encoded<E>, Schema.Encoded<D>>,
          Schema.Context<E> | Schema.Context<D>
        >
      }>,
      Struct<{
        _tag: Literal<["Success"]>
        value: A
      }>
    ]>,
    ExitFromSelf<SchemaClass<Schema.Type<A>>, SchemaClass<Schema.Type<E>>, SchemaClass<Schema.Type<D>>>
  >
{}

/**
 * @category Exit transformations
 * @since 3.10.0
 */
export const Exit = <A extends Schema.All, E extends Schema.All, D extends Schema.All>(
  { defect, failure, success }: {
    readonly failure: E
    readonly success: A
    readonly defect: D
  }
): Exit<A, E, D> => {
  const success_ = asSchema(success)
  const failure_ = asSchema(failure)
  const defect_ = asSchema(defect)
  const out = transform(
    exitEncoded(success_, failure_, defect_),
    ExitFromSelf({ failure: typeSchema(failure_), success: typeSchema(success_), defect: typeSchema(defect_) }),
    {
      strict: false,
      decode: (i) => exitDecode(i),
      encode: (a) =>
        a._tag === "Failure"
          ? { _tag: "Failure", cause: a.cause } as const
          : { _tag: "Success", value: a.value } as const
    }
  )
  return out as any
}

const hashSetArbitrary =
  <A>(item: LazyArbitrary<A>, ctx: ArbitraryGenerationContext): LazyArbitrary<hashSet_.HashSet<A>> => (fc) => {
    const items = fc.array(item(fc))
    return (ctx.depthIdentifier !== undefined ? fc.oneof(ctx, fc.constant([]), items) : items).map(
      hashSet_.fromIterable
    )
  }

const hashSetPretty = <A>(item: pretty_.Pretty<A>): pretty_.Pretty<hashSet_.HashSet<A>> => (set) =>
  `HashSet(${Array.from(set).map((a) => item(a)).join(", ")})`

const hashSetEquivalence = <A>(
  item: Equivalence.Equivalence<A>
): Equivalence.Equivalence<hashSet_.HashSet<A>> => {
  const arrayEquivalence = array_.getEquivalence(item)
  return Equivalence.make((a, b) => arrayEquivalence(Array.from(a), Array.from(b)))
}

const hashSetParse = <A, R>(
  decodeUnknown: ParseResult.DecodeUnknown<ReadonlyArray<A>, R>
): ParseResult.DeclarationDecodeUnknown<hashSet_.HashSet<A>, R> =>
(u, options, ast) =>
  hashSet_.isHashSet(u) ?
    toComposite(decodeUnknown(Array.from(u), options), hashSet_.fromIterable, ast, u)
    : ParseResult.fail(new ParseResult.Type(ast, u))

/**
 * @category api interface
 * @since 3.10.0
 */
export interface HashSetFromSelf<Value extends Schema.Any> extends
  AnnotableDeclare<
    HashSetFromSelf<Value>,
    hashSet_.HashSet<Schema.Type<Value>>,
    hashSet_.HashSet<Schema.Encoded<Value>>,
    [Value]
  >
{}

/**
 * @category HashSet transformations
 * @since 3.10.0
 */
export const HashSetFromSelf = <Value extends Schema.Any>(
  value: Value
): HashSetFromSelf<Value> => {
  return declare(
    [value],
    {
      decode: (item) => hashSetParse(ParseResult.decodeUnknown(Array$(item))),
      encode: (item) => hashSetParse(ParseResult.encodeUnknown(Array$(item)))
    },
    {
      description: `HashSet<${format(value)}>`,
      pretty: hashSetPretty,
      arbitrary: hashSetArbitrary,
      equivalence: hashSetEquivalence
    }
  )
}

/**
 * @category api interface
 * @since 3.10.0
 */
export interface HashSet<Value extends Schema.Any>
  extends transform<Array$<Value>, HashSetFromSelf<SchemaClass<Schema.Type<Value>>>>
{}

/**
 * @category HashSet transformations
 * @since 3.10.0
 */
export function HashSet<Value extends Schema.Any>(value: Value): HashSet<Value> {
  return transform(
    Array$(value),
    HashSetFromSelf(typeSchema(asSchema(value))),
    {
      strict: true,
      decode: (i) => hashSet_.fromIterable(i),
      encode: (a) => Array.from(a)
    }
  )
}

const hashMapArbitrary = <K, V>(
  key: LazyArbitrary<K>,
  value: LazyArbitrary<V>,
  ctx: ArbitraryGenerationContext
): LazyArbitrary<hashMap_.HashMap<K, V>> =>
(fc) => {
  const items = fc.array(fc.tuple(key(fc), value(fc)))
  return (ctx.depthIdentifier !== undefined ? fc.oneof(ctx, fc.constant([]), items) : items).map(hashMap_.fromIterable)
}

const hashMapPretty = <K, V>(
  key: pretty_.Pretty<K>,
  value: pretty_.Pretty<V>
): pretty_.Pretty<hashMap_.HashMap<K, V>> =>
(map) =>
  `HashMap([${
    Array.from(map)
      .map(([k, v]) => `[${key(k)}, ${value(v)}]`)
      .join(", ")
  }])`

const hashMapEquivalence = <K, V>(
  key: Equivalence.Equivalence<K>,
  value: Equivalence.Equivalence<V>
): Equivalence.Equivalence<hashMap_.HashMap<K, V>> => {
  const arrayEquivalence = array_.getEquivalence(
    Equivalence.make<[K, V]>(([ka, va], [kb, vb]) => key(ka, kb) && value(va, vb))
  )
  return Equivalence.make((a, b) => arrayEquivalence(Array.from(a), Array.from(b)))
}

const hashMapParse = <R, K, V>(
  decodeUnknown: ParseResult.DecodeUnknown<ReadonlyArray<readonly [K, V]>, R>
): ParseResult.DeclarationDecodeUnknown<hashMap_.HashMap<K, V>, R> =>
(u, options, ast) =>
  hashMap_.isHashMap(u) ?
    toComposite(decodeUnknown(Array.from(u), options), hashMap_.fromIterable, ast, u)
    : ParseResult.fail(new ParseResult.Type(ast, u))

/**
 * @category api interface
 * @since 3.10.0
 */
export interface HashMapFromSelf<K extends Schema.Any, V extends Schema.Any> extends
  AnnotableDeclare<
    HashMapFromSelf<K, V>,
    hashMap_.HashMap<Schema.Type<K>, Schema.Type<V>>,
    hashMap_.HashMap<Schema.Encoded<K>, Schema.Encoded<V>>,
    [K, V]
  >
{}

/**
 * @category HashMap transformations
 * @since 3.10.0
 */
export const HashMapFromSelf = <K extends Schema.Any, V extends Schema.Any>({ key, value }: {
  readonly key: K
  readonly value: V
}): HashMapFromSelf<K, V> => {
  return declare(
    [key, value],
    {
      decode: (key, value) => hashMapParse(ParseResult.decodeUnknown(Array$(Tuple(key, value)))),
      encode: (key, value) => hashMapParse(ParseResult.encodeUnknown(Array$(Tuple(key, value))))
    },
    {
      description: `HashMap<${format(key)}, ${format(value)}>`,
      pretty: hashMapPretty,
      arbitrary: hashMapArbitrary,
      equivalence: hashMapEquivalence
    }
  )
}

/**
 * @category api interface
 * @since 3.10.0
 */
export interface HashMap<K extends Schema.Any, V extends Schema.Any>
  extends transform<Array$<Tuple2<K, V>>, HashMapFromSelf<SchemaClass<Schema.Type<K>>, SchemaClass<Schema.Type<V>>>>
{}

/**
 * @category HashMap transformations
 * @since 3.10.0
 */
export const HashMap = <K extends Schema.Any, V extends Schema.Any>({ key, value }: {
  readonly key: K
  readonly value: V
}): HashMap<K, V> => {
  return transform(
    Array$(Tuple(key, value)),
    HashMapFromSelf({ key: typeSchema(asSchema(key)), value: typeSchema(asSchema(value)) }),
    {
      strict: true,
      decode: (i) => hashMap_.fromIterable(i),
      encode: (a) => Array.from(a)
    }
  )
}

const listArbitrary =
  <A>(item: LazyArbitrary<A>, ctx: ArbitraryGenerationContext): LazyArbitrary<list_.List<A>> => (fc) => {
    const items = fc.array(item(fc))
    return (ctx.depthIdentifier !== undefined ? fc.oneof(ctx, fc.constant([]), items) : items).map(list_.fromIterable)
  }

const listPretty = <A>(item: pretty_.Pretty<A>): pretty_.Pretty<list_.List<A>> => (set) =>
  `List(${Array.from(set).map((a) => item(a)).join(", ")})`

const listEquivalence = <A>(
  item: Equivalence.Equivalence<A>
): Equivalence.Equivalence<list_.List<A>> => {
  const arrayEquivalence = array_.getEquivalence(item)
  return Equivalence.make((a, b) => arrayEquivalence(Array.from(a), Array.from(b)))
}

const listParse = <A, R>(
  decodeUnknown: ParseResult.DecodeUnknown<ReadonlyArray<A>, R>
): ParseResult.DeclarationDecodeUnknown<list_.List<A>, R> =>
(u, options, ast) =>
  list_.isList(u) ?
    toComposite(decodeUnknown(Array.from(u), options), list_.fromIterable, ast, u)
    : ParseResult.fail(new ParseResult.Type(ast, u))

/**
 * @category api interface
 * @since 3.10.0
 */
export interface ListFromSelf<Value extends Schema.Any> extends
  AnnotableDeclare<
    ListFromSelf<Value>,
    list_.List<Schema.Type<Value>>,
    list_.List<Schema.Encoded<Value>>,
    [Value]
  >
{}

/**
 * @category List transformations
 * @since 3.10.0
 */
export const ListFromSelf = <Value extends Schema.Any>(
  value: Value
): ListFromSelf<Value> => {
  return declare(
    [value],
    {
      decode: (item) => listParse(ParseResult.decodeUnknown(Array$(item))),
      encode: (item) => listParse(ParseResult.encodeUnknown(Array$(item)))
    },
    {
      description: `List<${format(value)}>`,
      pretty: listPretty,
      arbitrary: listArbitrary,
      equivalence: listEquivalence
    }
  )
}

/**
 * @category api interface
 * @since 3.10.0
 */
export interface List<Value extends Schema.Any>
  extends transform<Array$<Value>, ListFromSelf<SchemaClass<Schema.Type<Value>>>>
{}

/**
 * @category List transformations
 * @since 3.10.0
 */
export function List<Value extends Schema.Any>(value: Value): List<Value> {
  return transform(
    Array$(value),
    ListFromSelf(typeSchema(asSchema(value))),
    {
      strict: true,
      decode: (i) => list_.fromIterable(i),
      encode: (a) => Array.from(a)
    }
  )
}

const sortedSetArbitrary = <A>(
  item: LazyArbitrary<A>,
  ord: Order.Order<A>,
  ctx: ArbitraryGenerationContext
): LazyArbitrary<sortedSet_.SortedSet<A>> =>
(fc) => {
  const items = fc.array(item(fc))
  return (ctx.depthIdentifier !== undefined ? fc.oneof(ctx, fc.constant([]), items) : items).map((as) =>
    sortedSet_.fromIterable(as, ord)
  )
}

const sortedSetPretty = <A>(item: pretty_.Pretty<A>): pretty_.Pretty<sortedSet_.SortedSet<A>> => (set) =>
  `new SortedSet([${Array.from(sortedSet_.values(set)).map((a) => item(a)).join(", ")}])`

const sortedSetParse = <A, R>(
  decodeUnknown: ParseResult.DecodeUnknown<ReadonlyArray<A>, R>,
  ord: Order.Order<A>
): ParseResult.DeclarationDecodeUnknown<sortedSet_.SortedSet<A>, R> =>
(u, options, ast) =>
  sortedSet_.isSortedSet(u) ?
    toComposite(
      decodeUnknown(Array.from(sortedSet_.values(u)), options),
      (as): sortedSet_.SortedSet<A> => sortedSet_.fromIterable(as, ord),
      ast,
      u
    )
    : ParseResult.fail(new ParseResult.Type(ast, u))

/**
 * @category api interface
 * @since 3.10.0
 */
export interface SortedSetFromSelf<Value extends Schema.Any> extends
  AnnotableDeclare<
    SortedSetFromSelf<Value>,
    sortedSet_.SortedSet<Schema.Type<Value>>,
    sortedSet_.SortedSet<Schema.Encoded<Value>>,
    [Value]
  >
{}

/**
 * @category SortedSet transformations
 * @since 3.10.0
 */
export const SortedSetFromSelf = <Value extends Schema.Any>(
  value: Value,
  ordA: Order.Order<Schema.Type<Value>>,
  ordI: Order.Order<Schema.Encoded<Value>>
): SortedSetFromSelf<Value> => {
  return declare(
    [value],
    {
      decode: (item) => sortedSetParse(ParseResult.decodeUnknown(Array$(item)), ordA),
      encode: (item) => sortedSetParse(ParseResult.encodeUnknown(Array$(item)), ordI)
    },
    {
      description: `SortedSet<${format(value)}>`,
      pretty: sortedSetPretty,
      arbitrary: (arb, ctx) => sortedSetArbitrary(arb, ordA, ctx),
      equivalence: () => sortedSet_.getEquivalence<Schema.Type<Value>>()
    }
  )
}

/**
 * @category api interface
 * @since 3.10.0
 */
export interface SortedSet<Value extends Schema.Any>
  extends transform<Array$<Value>, SortedSetFromSelf<SchemaClass<Schema.Type<Value>>>>
{}

/**
 * @category SortedSet transformations
 * @since 3.10.0
 */
export function SortedSet<Value extends Schema.Any>(
  value: Value,
  ordA: Order.Order<Schema.Type<Value>>
): SortedSet<Value> {
  const to = typeSchema(asSchema(value))
  return transform(
    Array$(value),
    SortedSetFromSelf<typeof to>(to, ordA, ordA),
    {
      strict: true,
      decode: (i) => sortedSet_.fromIterable(i, ordA),
      encode: (a) => Array.from(sortedSet_.values(a))
    }
  )
}

/**
 * Converts an arbitrary value to a `boolean` by testing whether it is truthy.
 * Uses `!!val` to coerce the value to a `boolean`.
 *
 * @see https://developer.mozilla.org/docs/Glossary/Truthy
 *
 * @category boolean constructors
 * @since 3.10.0
 */
export class BooleanFromUnknown extends transform(
  Unknown,
  Boolean$,
  {
    strict: true,
    decode: (i) => Predicate.isTruthy(i),
    encode: identity
  }
).annotations({ identifier: "BooleanFromUnknown" }) {}

/**
 * Converts an `string` value into its corresponding `boolean`
 * ("true" as `true` and "false" as `false`).
 *
 * @category boolean transformations
 * @since 3.11.0
 */
export class BooleanFromString extends transform(
  Literal("true", "false").annotations({ description: "a string to be decoded into a boolean" }),
  Boolean$,
  {
    strict: true,
    decode: (i) => i === "true",
    encode: (a) => a ? "true" : "false"
  }
).annotations({ identifier: "BooleanFromString" }) {}

/**
 * @category Config validations
 * @since 3.10.0
 */
export const Config = <A, I extends string>(name: string, schema: Schema<A, I>): config_.Config<A> => {
  const decodeUnknownEither = ParseResult.decodeUnknownEither(schema)
  return config_.string(name).pipe(
    config_.mapOrFail((s) =>
      decodeUnknownEither(s).pipe(
        either_.mapLeft((error) => configError_.InvalidData([], ParseResult.TreeFormatter.formatIssueSync(error)))
      )
    )
  )
}

// ---------------------------------------------
// Serializable
// ---------------------------------------------

/**
 * @since 3.10.0
 * @category symbol
 */
export const symbolSerializable: unique symbol = Symbol.for(
  "effect/Schema/Serializable/symbol"
)

/**
 * The `Serializable` trait allows objects to define their own schema for
 * serialization.
 *
 * @since 3.10.0
 * @category model
 */
export interface Serializable<A, I, R> {
  readonly [symbolSerializable]: Schema<A, I, R>
}

/**
 * @since 3.10.0
 * @category model
 */
export declare namespace Serializable {
  /**
   * @since 3.10.0
   */
  export type Type<T> = T extends Serializable<infer A, infer _I, infer _R> ? A : never
  /**
   * @since 3.10.0
   */
  export type Encoded<T> = T extends Serializable<infer _A, infer I, infer _R> ? I : never
  /**
   * @since 3.10.0
   */
  export type Context<T> = T extends Serializable<infer _A, infer _I, infer R> ? R : never
  /**
   * @since 3.10.0
   */
  export type Any = Serializable<any, any, unknown>
  /**
   * @since 3.10.0
   */
  export type All =
    | Any
    | Serializable<any, never, unknown>
    | Serializable<never, any, unknown>
    | Serializable<never, never, unknown>
}

/**
 * @since 3.10.0
 */
export const asSerializable = <S extends Serializable.All>(
  serializable: S
): Serializable<Serializable.Type<S>, Serializable.Encoded<S>, Serializable.Context<S>> => serializable as any

/**
 * @since 3.10.0
 * @category accessor
 */
export const serializableSchema = <A, I, R>(self: Serializable<A, I, R>): Schema<A, I, R> => self[symbolSerializable]

/**
 * @since 3.10.0
 * @category encoding
 */
export const serialize = <A, I, R>(self: Serializable<A, I, R>): Effect.Effect<I, ParseResult.ParseError, R> =>
  encodeUnknown(self[symbolSerializable])(self)

/**
 * @since 3.10.0
 * @category decoding
 */
export const deserialize: {
  /**
   * @since 3.10.0
   * @category decoding
   */
  (value: unknown): <A, I, R>(self: Serializable<A, I, R>) => Effect.Effect<A, ParseResult.ParseError, R>
  /**
   * @since 3.10.0
   * @category decoding
   */
  <A, I, R>(self: Serializable<A, I, R>, value: unknown): Effect.Effect<A, ParseResult.ParseError, R>
} = dual(
  2,
  <A, I, R>(self: Serializable<A, I, R>, value: unknown): Effect.Effect<A, ParseResult.ParseError, R> =>
    decodeUnknown(self[symbolSerializable])(value)
)

/**
 * @since 3.10.0
 * @category symbol
 */
export const symbolWithResult: unique symbol = Symbol.for(
  "effect/Schema/Serializable/symbolResult"
)

/**
 * The `WithResult` trait is designed to encapsulate the outcome of an
 * operation, distinguishing between success and failure cases. Each case is
 * associated with a schema that defines the structure and types of the success
 * or failure data.
 *
 * @since 3.10.0
 * @category model
 */
export interface WithResult<Success, SuccessEncoded, Failure, FailureEncoded, ResultR> {
  readonly [symbolWithResult]: {
    readonly success: Schema<Success, SuccessEncoded, ResultR>
    readonly failure: Schema<Failure, FailureEncoded, ResultR>
  }
}

/**
 * @since 3.10.0
 * @category model
 */
export declare namespace WithResult {
  /**
   * @since 3.10.0
   */
  export type Success<T> = T extends WithResult<infer _A, infer _I, infer _E, infer _EI, infer _R> ? _A : never
  /**
   * @since 3.10.0
   */
  export type SuccessEncoded<T> = T extends WithResult<infer _A, infer _I, infer _E, infer _EI, infer _R> ? _I : never
  /**
   * @since 3.10.0
   */
  export type Failure<T> = T extends WithResult<infer _A, infer _I, infer _E, infer _EI, infer _R> ? _E : never
  /**
   * @since 3.10.0
   */
  export type FailureEncoded<T> = T extends WithResult<infer _A, infer _I, infer _E, infer _EI, infer _R> ? _EI : never

  /**
   * @since 3.10.0
   */
  export type Context<T> = T extends WithResult<infer _SA, infer _SI, infer _FA, infer _FI, infer R> ? R : never
  /**
   * @since 3.10.0
   */
  export type Any = WithResult<any, any, any, any, unknown>
  /**
   * @since 3.10.0
   */
  export type All =
    | Any
    | WithResult<any, any, never, never, unknown>
}

/**
 * @since 3.10.0
 */
export const asWithResult = <WR extends WithResult.All>(
  withExit: WR
): WithResult<
  WithResult.Success<WR>,
  WithResult.SuccessEncoded<WR>,
  WithResult.Failure<WR>,
  WithResult.FailureEncoded<WR>,
  WithResult.Context<WR>
> => withExit as any

/**
 * @since 3.10.0
 * @category accessor
 */
export const failureSchema = <SA, SI, FA, FI, R>(self: WithResult<SA, SI, FA, FI, R>): Schema<FA, FI, R> =>
  self[symbolWithResult].failure

/**
 * @since 3.10.0
 * @category accessor
 */
export const successSchema = <SA, SI, FA, FI, R>(self: WithResult<SA, SI, FA, FI, R>): Schema<SA, SI, R> =>
  self[symbolWithResult].success

const exitSchemaCache = globalValue(
  "effect/Schema/Serializable/exitSchemaCache",
  () => new WeakMap<object, Schema<any, any, any>>()
)

/**
 * @since 3.10.0
 * @category accessor
 */
export const exitSchema = <SA, SI, FA, FI, R>(self: WithResult<SA, SI, FA, FI, R>): Schema<
  exit_.Exit<SA, FA>,
  ExitEncoded<SI, FI, unknown>,
  R
> => {
  const proto = Object.getPrototypeOf(self)
  if (!(symbolWithResult in proto)) {
    return Exit({
      failure: failureSchema(self),
      success: successSchema(self),
      defect: Defect
    })
  }
  let schema = exitSchemaCache.get(proto)
  if (schema === undefined) {
    schema = Exit({
      failure: failureSchema(self),
      success: successSchema(self),
      defect: Defect
    })
    exitSchemaCache.set(proto, schema)
  }
  return schema
}

/**
 * @since 3.10.0
 * @category encoding
 */
export const serializeFailure: {
  /**
   * @since 3.10.0
   * @category encoding
   */
  <FA>(value: FA): <SA, SI, FI, R>(
    self: WithResult<SA, SI, FA, FI, R>
  ) => Effect.Effect<FI, ParseResult.ParseError, R>
  /**
   * @since 3.10.0
   * @category encoding
   */
  <SA, SI, FA, FI, R>(self: WithResult<SA, SI, FA, FI, R>, value: FA): Effect.Effect<FI, ParseResult.ParseError, R>
} = dual(
  2,
  <SA, SI, FA, FI, R>(self: WithResult<SA, SI, FA, FI, R>, value: FA): Effect.Effect<FI, ParseResult.ParseError, R> =>
    encode(self[symbolWithResult].failure)(value)
)

/**
 * @since 3.10.0
 * @category decoding
 */
export const deserializeFailure: {
  /**
   * @since 3.10.0
   * @category decoding
   */
  (value: unknown): <SA, SI, FA, FI, R>(self: WithResult<SA, SI, FA, FI, R>) => Effect.Effect<FA, ParseResult.ParseError, R>
  /**
   * @since 3.10.0
   * @category decoding
   */
  <SA, SI, FA, FI, R>(self: WithResult<SA, SI, FA, FI, R>, value: unknown): Effect.Effect<FA, ParseResult.ParseError, R>
} = dual(
  2,
  <SA, SI, FA, FI, R>(
    self: WithResult<SA, SI, FA, FI, R>,
    value: unknown
  ): Effect.Effect<FA, ParseResult.ParseError, R> => decodeUnknown(self[symbolWithResult].failure)(value)
)

/**
 * @since 3.10.0
 * @category encoding
 */
export const serializeSuccess: {
  /**
   * @since 3.10.0
   * @category encoding
   */
  <SA>(value: SA): <SI, FA, FI, R>(
    self: WithResult<SA, SI, FA, FI, R>
  ) => Effect.Effect<SI, ParseResult.ParseError, R>
  /**
   * @since 3.10.0
   * @category encoding
   */
  <SA, SI, FA, FI, R>(self: WithResult<SA, SI, FA, FI, R>, value: SA): Effect.Effect<SI, ParseResult.ParseError, R>
} = dual(
  2,
  <SA, SI, FA, FI, R>(self: WithResult<SA, SI, FA, FI, R>, value: SA): Effect.Effect<SI, ParseResult.ParseError, R> =>
    encode(self[symbolWithResult].success)(value)
)

/**
 * @since 3.10.0
 * @category decoding
 */
export const deserializeSuccess: {
  /**
   * @since 3.10.0
   * @category decoding
   */
  (value: unknown): <SA, SI, FA, FI, R>(
    self: WithResult<SA, SI, FA, FI, R>
  ) => Effect.Effect<SA, ParseResult.ParseError, R>
  /**
   * @since 3.10.0
   * @category decoding
   */
  <SA, SI, FA, FI, R>(self: WithResult<SA, SI, FA, FI, R>, value: unknown): Effect.Effect<SA, ParseResult.ParseError, R>
} = dual(
  2,
  <SA, SI, FA, FI, R>(
    self: WithResult<SA, SI, FA, FI, R>,
    value: unknown
  ): Effect.Effect<SA, ParseResult.ParseError, R> => decodeUnknown(self[symbolWithResult].success)(value)
)

/**
 * @since 3.10.0
 * @category encoding
 */
export const serializeExit: {
  /**
   * @since 3.10.0
   * @category encoding
   */
  <SA, FA>(value: exit_.Exit<SA, FA>): <SI, FI, R>(
    self: WithResult<SA, SI, FA, FI, R>
  ) => Effect.Effect<ExitEncoded<SI, FI, unknown>, ParseResult.ParseError, R>
  /**
   * @since 3.10.0
   * @category encoding
   */
  <SA, SI, FA, FI, R>(self: WithResult<SA, SI, FA, FI, R>, value: exit_.Exit<SA, FA>): Effect.Effect<ExitEncoded<SI, FI, unknown>, ParseResult.ParseError, R>
} = dual(2, <SA, SI, FA, FI, R>(
  self: WithResult<SA, SI, FA, FI, R>,
  value: exit_.Exit<SA, FA>
): Effect.Effect<ExitEncoded<SI, FI, unknown>, ParseResult.ParseError, R> => encode(exitSchema(self))(value))

/**
 * @since 3.10.0
 * @category decoding
 */
export const deserializeExit: {
  /**
   * @since 3.10.0
   * @category decoding
   */
  (value: unknown): <SA, SI, FA, FI, R>(
    self: WithResult<SA, SI, FA, FI, R>
  ) => Effect.Effect<exit_.Exit<SA, FA>, ParseResult.ParseError, R>
  /**
   * @since 3.10.0
   * @category decoding
   */
  <SA, SI, FA, FI, R>(self: WithResult<SA, SI, FA, FI, R>, value: unknown): Effect.Effect<exit_.Exit<SA, FA>, ParseResult.ParseError, R>
} = dual(2, <SA, SI, FA, FI, R>(
  self: WithResult<SA, SI, FA, FI, R>,
  value: unknown
): Effect.Effect<exit_.Exit<SA, FA>, ParseResult.ParseError, R> => decodeUnknown(exitSchema(self))(value))

// ---------------------------------------------
// SerializableWithResult
// ---------------------------------------------

/**
 * The `SerializableWithResult` trait is specifically designed to model remote
 * procedures that require serialization of their input and output, managing
 * both successful and failed outcomes.
 *
 * This trait combines functionality from both the `Serializable` and `WithResult`
 * traits to handle data serialization and the bifurcation of operation results
 * into success or failure categories.
 *
 * @since 3.10.0
 * @category model
 */
export interface SerializableWithResult<
  A,
  I,
  R,
  Success,
  SuccessEncoded,
  Failure,
  FailureEncoded,
  ResultR
> extends Serializable<A, I, R>, WithResult<Success, SuccessEncoded, Failure, FailureEncoded, ResultR> {}

/**
 * @since 3.10.0
 * @category model
 */
export declare namespace SerializableWithResult {
  /**
   * @since 3.10.0
   */
  export type Context<P> = P extends
    SerializableWithResult<infer _S, infer _SI, infer SR, infer _A, infer _AI, infer _E, infer _EI, infer RR> ? SR | RR
    : never
  /**
   * @since 3.10.0
   */
  export type Any = SerializableWithResult<any, any, any, any, any, any, any, unknown>
  /**
   * @since 3.10.0
   */
  export type All =
    | Any
    | SerializableWithResult<any, any, any, any, any, never, never, unknown>
}

/**
 * @since 3.10.0
 */
export const asSerializableWithResult = <SWR extends SerializableWithResult.All>(
  procedure: SWR
): SerializableWithResult<
  Serializable.Type<SWR>,
  Serializable.Encoded<SWR>,
  Serializable.Context<SWR>,
  WithResult.Success<SWR>,
  WithResult.SuccessEncoded<SWR>,
  WithResult.Failure<SWR>,
  WithResult.FailureEncoded<SWR>,
  WithResult.Context<SWR>
> => procedure as any

/**
 * @since 3.10.0
 */
export interface TaggedRequest<
  Tag extends string,
  A,
  I,
  R,
  SuccessType,
  SuccessEncoded,
  FailureType,
  FailureEncoded,
  ResultR
> extends
  Request.Request<SuccessType, FailureType>,
  SerializableWithResult<
    A,
    I,
    R,
    SuccessType,
    SuccessEncoded,
    FailureType,
    FailureEncoded,
    ResultR
  >
{
  readonly _tag: Tag
}

/**
 * @since 3.10.0
 */
export declare namespace TaggedRequest {
  /**
   * @since 3.10.0
   */
  export type Any = TaggedRequest<string, any, any, any, any, any, any, any, unknown>
  /**
   * @since 3.10.0
   */
  export type All =
    | Any
    | TaggedRequest<string, any, any, any, any, any, never, never, unknown>
}

/**
 * @category api interface
 * @since 3.10.0
 */
export interface TaggedRequestClass<
  Self,
  Tag extends string,
  Payload extends Struct.Fields,
  Success extends Schema.All,
  Failure extends Schema.All
> extends
  Class<
    Self,
    Payload,
    Struct.Encoded<Payload>,
    Struct.Context<Payload>,
    Struct.Constructor<Omit<Payload, "_tag">>,
    TaggedRequest<
      Tag,
      Self,
      Struct.Encoded<Payload>,
      Struct.Context<Payload>,
      Schema.Type<Success>,
      Schema.Encoded<Success>,
      Schema.Type<Failure>,
      Schema.Encoded<Failure>,
      Schema.Context<Success> | Schema.Context<Failure>
    >,
    {}
  >
{
  readonly _tag: Tag
  readonly success: Success
  readonly failure: Failure
}

/**
 * @example
 * ```ts
 * import { Schema } from "effect"
 *
 * class MyRequest extends Schema.TaggedRequest<MyRequest>("MyRequest")("MyRequest", {
 *  failure: Schema.String,
 *  success: Schema.Number,
 *  payload: { id: Schema.String }
 * }) {}
 * ```
 *
 * @category classes
 * @since 3.10.0
 */
export const TaggedRequest =
  <Self = never>(identifier?: string) =>
  <Tag extends string, Payload extends Struct.Fields, Success extends Schema.All, Failure extends Schema.All>(
    tag: Tag,
    options: {
      failure: Failure
      success: Success
      payload: Payload
    },
    annotations?: ClassAnnotations<Self, Simplify<Struct.Type<{ readonly _tag: tag<Tag> } & Payload>>>
  ): [Self] extends [never] ? MissingSelfGeneric<"TaggedRequest", `"Tag", SuccessSchema, FailureSchema, `>
    : TaggedRequestClass<
      Self,
      Tag,
      { readonly _tag: tag<Tag> } & Payload,
      Success,
      Failure
    > =>
  {
    const taggedFields = extendFields({ _tag: getClassTag(tag) }, options.payload)
    return class TaggedRequestClass extends makeClass({
      kind: "TaggedRequest",
      identifier: identifier ?? tag,
      schema: Struct(taggedFields),
      fields: taggedFields,
      Base: Request.Class<any, any, { readonly _tag: string }>,
      annotations
    }) {
      static _tag = tag
      static success = options.success
      static failure = options.failure
      get [symbolSerializable]() {
        return this.constructor
      }
      get [symbolWithResult]() {
        return {
          failure: options.failure,
          success: options.success
        }
      }
    } as any
  }

// -------------------------------------------------------------------------------------------------
// Equivalence compiler
// -------------------------------------------------------------------------------------------------

/**
 * Given a schema `Schema<A, I, R>`, returns an `Equivalence` instance for `A`.
 *
 * @category Equivalence
 * @since 3.10.0
 */
export const equivalence = <A, I, R>(schema: Schema<A, I, R>): Equivalence.Equivalence<A> => go(schema.ast, [])

const getEquivalenceAnnotation = AST.getAnnotation<AST.EquivalenceAnnotation<any, any>>(AST.EquivalenceAnnotationId)

const go = (ast: AST.AST, path: ReadonlyArray<PropertyKey>): Equivalence.Equivalence<any> => {
  const hook = getEquivalenceAnnotation(ast)
  if (option_.isSome(hook)) {
    switch (ast._tag) {
      case "Declaration":
        return hook.value(...ast.typeParameters.map((tp) => go(tp, path)))
      case "Refinement":
        return hook.value(go(ast.from, path))
      default:
        return hook.value()
    }
  }
  switch (ast._tag) {
    case "NeverKeyword":
      throw new Error(errors_.getEquivalenceUnsupportedErrorMessage(ast, path))
    case "Transformation":
      return go(ast.to, path)
    case "Declaration":
    case "Literal":
    case "StringKeyword":
    case "TemplateLiteral":
    case "UniqueSymbol":
    case "SymbolKeyword":
    case "UnknownKeyword":
    case "AnyKeyword":
    case "NumberKeyword":
    case "BooleanKeyword":
    case "BigIntKeyword":
    case "UndefinedKeyword":
    case "VoidKeyword":
    case "Enums":
    case "ObjectKeyword":
      return Equal.equals
    case "Refinement":
      return go(ast.from, path)
    case "Suspend": {
      const get = util_.memoizeThunk(() => go(ast.f(), path))
      return (a, b) => get()(a, b)
    }
    case "TupleType": {
      const elements = ast.elements.map((element, i) => go(element.type, path.concat(i)))
      const rest = ast.rest.map((annotatedAST) => go(annotatedAST.type, path))
      return Equivalence.make((a, b) => {
        if (!Array.isArray(a) || !Array.isArray(b)) {
          return false
        }
        const len = a.length
        if (len !== b.length) {
          return false
        }
        // ---------------------------------------------
        // handle elements
        // ---------------------------------------------
        let i = 0
        for (; i < Math.min(len, ast.elements.length); i++) {
          if (!elements[i](a[i], b[i])) {
            return false
          }
        }
        // ---------------------------------------------
        // handle rest element
        // ---------------------------------------------
        if (array_.isNonEmptyReadonlyArray(rest)) {
          const [head, ...tail] = rest
          for (; i < len - tail.length; i++) {
            if (!head(a[i], b[i])) {
              return false
            }
          }
          // ---------------------------------------------
          // handle post rest elements
          // ---------------------------------------------
          for (let j = 0; j < tail.length; j++) {
            i += j
            if (!tail[j](a[i], b[i])) {
              return false
            }
          }
        }
        return true
      })
    }
    case "TypeLiteral": {
      if (ast.propertySignatures.length === 0 && ast.indexSignatures.length === 0) {
        return Equal.equals
      }
      const propertySignatures = ast.propertySignatures.map((ps) => go(ps.type, path.concat(ps.name)))
      const indexSignatures = ast.indexSignatures.map((is) => go(is.type, path))
      return Equivalence.make((a, b) => {
        if (!Predicate.isRecord(a) || !Predicate.isRecord(b)) {
          return false
        }
        const aStringKeys = Object.keys(a)
        const aSymbolKeys = Object.getOwnPropertySymbols(a)
        // ---------------------------------------------
        // handle property signatures
        // ---------------------------------------------
        for (let i = 0; i < propertySignatures.length; i++) {
          const ps = ast.propertySignatures[i]
          const name = ps.name
          const aHas = Object.prototype.hasOwnProperty.call(a, name)
          const bHas = Object.prototype.hasOwnProperty.call(b, name)
          if (ps.isOptional) {
            if (aHas !== bHas) {
              return false
            }
          }
          if (aHas && bHas && !propertySignatures[i](a[name], b[name])) {
            return false
          }
        }
        // ---------------------------------------------
        // handle index signatures
        // ---------------------------------------------
        let bSymbolKeys: Array<symbol> | undefined
        let bStringKeys: Array<string> | undefined
        for (let i = 0; i < indexSignatures.length; i++) {
          const is = ast.indexSignatures[i]
          const encodedParameter = AST.getEncodedParameter(is.parameter)
          const isSymbol = AST.isSymbolKeyword(encodedParameter)
          if (isSymbol) {
            bSymbolKeys = bSymbolKeys || Object.getOwnPropertySymbols(b)
            if (aSymbolKeys.length !== bSymbolKeys.length) {
              return false
            }
          } else {
            bStringKeys = bStringKeys || Object.keys(b)
            if (aStringKeys.length !== bStringKeys.length) {
              return false
            }
          }
          const aKeys = isSymbol ? aSymbolKeys : aStringKeys
          for (let j = 0; j < aKeys.length; j++) {
            const key = aKeys[j]
            if (
              !Object.prototype.hasOwnProperty.call(b, key) || !indexSignatures[i](a[key], b[key])
            ) {
              return false
            }
          }
        }
        return true
      })
    }
    case "Union": {
      const searchTree = ParseResult.getSearchTree(ast.types, true)
      const ownKeys = util_.ownKeys(searchTree.keys)
      const len = ownKeys.length
      return Equivalence.make((a, b) => {
        let candidates: Array<AST.AST> = []
        if (len > 0 && Predicate.isRecordOrArray(a)) {
          for (let i = 0; i < len; i++) {
            const name = ownKeys[i]
            const buckets = searchTree.keys[name].buckets
            if (Object.prototype.hasOwnProperty.call(a, name)) {
              const literal = String(a[name])
              if (Object.prototype.hasOwnProperty.call(buckets, literal)) {
                candidates = candidates.concat(buckets[literal])
              }
            }
          }
        }
        if (searchTree.otherwise.length > 0) {
          candidates = candidates.concat(searchTree.otherwise)
        }
        const tuples = candidates.map((ast) => [go(ast, path), ParseResult.is({ ast } as any)] as const)
        for (let i = 0; i < tuples.length; i++) {
          const [equivalence, is] = tuples[i]
          if (is(a) && is(b)) {
            if (equivalence(a, b)) {
              return true
            }
          }
        }
        return false
      })
    }
  }
}

const SymbolStruct = TaggedStruct("symbol", {
  key: String$
}).annotations({ description: "an object to be decoded into a globally shared symbol" })

const SymbolFromStruct = transformOrFail(
  SymbolStruct,
  SymbolFromSelf,
  {
    strict: true,
    decode: (i) => decodeSymbol(i.key),
    encode: (a, _, ast) => ParseResult.map(encodeSymbol(a, ast), (key) => SymbolStruct.make({ key }))
  }
)

/** @ignore */
class PropertyKey$ extends Union(String$, Number$, SymbolFromStruct).annotations({ identifier: "PropertyKey" }) {}

export {
  /**
   * @since 3.12.5
   */
  PropertyKey$ as PropertyKey
}

/**
 * @category ArrayFormatter
 * @since 3.12.5
 */
export class ArrayFormatterIssue extends Struct({
  _tag: propertySignature(Literal(
    "Pointer",
    "Unexpected",
    "Missing",
    "Composite",
    "Refinement",
    "Transformation",
    "Type",
    "Forbidden"
  )).annotations({ description: "The tag identifying the type of parse issue" }),
  path: propertySignature(Array$(PropertyKey$)).annotations({
    description: "The path to the property where the issue occurred"
  }),
  message: propertySignature(String$).annotations({ description: "A descriptive message explaining the issue" })
}).annotations({
  identifier: "ArrayFormatterIssue",
  description: "Represents an issue returned by the ArrayFormatter formatter"
}) {}

CasperSecurity Mini