/**
 * Reads and updates focused parts of values without mutating the original
 * value.
 *
 * An optic describes where to look inside a value, such as a record field, a
 * union variant, an optional value, or several values in a collection. Different
 * optic types describe different kinds of focus: some always find a value,
 * some may not, and some can find many. This module includes the optic types,
 * constructors, focusing helpers, and operations for replacing, modifying, or
 * collecting focused values.
 *
 * @since 4.0.0
 */

import { format } from "./Formatter.ts"
import { identity, memoize } from "./Function.ts"
import * as Option from "./Option.ts"
import * as Predicate from "./Predicate.ts"
import * as Result from "./Result.ts"
import type * as Schema from "./Schema.ts"
import * as SchemaAST from "./SchemaAST.ts"
import type * as SchemaIssue from "./SchemaIssue.ts"
import * as Struct from "./Struct.ts"
import type { IsUnion } from "./Types.ts"

/**
 * A lossless, reversible conversion between types `S` and `A`.
 *
 * **When to use**
 *
 * Use when you have a pair of functions that convert back and forth without losing
 *   information (e.g. `Record ↔ entries`, `Celsius ↔ Fahrenheit`).
 * - You want the strongest optic that can be composed with any other.
 *
 * **Details**
 *
 * - `get(s)` always succeeds and returns an `A`.
 * - `set(a)` always succeeds and returns an `S`.
 * - `get(set(a)) === a` and `set(get(s))` equals `s` (round-trip laws).
 * - Extends both {@link Lens} and {@link Prism}.
 *
 * **Example** (Converting between Celsius and Fahrenheit)
 *
 * ```ts
 * import { Optic } from "effect"
 *
 * const fahrenheit = Optic.makeIso<number, number>(
 *   (c) => c * 9 / 5 + 32,
 *   (f) => (f - 32) * 5 / 9
 * )
 *
 * console.log(fahrenheit.get(100))
 * // Output: 212
 *
 * console.log(fahrenheit.set(32))
 * // Output: 0
 * ```
 *
 * @see {@link makeIso} — constructor
 * @see {@link Lens} — when you only need a one-directional focus into a whole
 * @see {@link Prism} — when the focus may not be present
 *
 * @category Iso
 * @since 4.0.0
 */
export interface Iso<in out S, in out A> extends Lens<S, A>, Prism<S, A> {}

/**
 * Creates an {@link Iso} from a pair of conversion functions.
 *
 * **When to use**
 *
 * Use when you have two pure conversion functions that preserve all information
 * between `S` and `A`.
 *
 * **Details**
 *
 * The returned optic can be composed with any other optic.
 *
 * **Example** (Wrapping and unwrapping a branded type)
 *
 * ```ts
 * import { Optic } from "effect"
 *
 * type Meters = { readonly value: number }
 * const meters = Optic.makeIso<Meters, number>(
 *   (m) => m.value,
 *   (n) => ({ value: n })
 * )
 *
 * console.log(meters.get({ value: 100 }))
 * // Output: 100
 *
 * console.log(meters.set(42))
 * // Output: { value: 42 }
 * ```
 *
 * @see {@link Iso} — the type this function returns
 * @see {@link id} — identity iso (no conversion)
 *
 * @category constructors
 * @since 4.0.0
 */
export function makeIso<S, A>(get: (s: S) => A, set: (a: A) => S): Iso<S, A> {
  return make(new IsoNode(get, set))
}

/**
 * Focuses on exactly one part `A` inside a whole `S`.
 *
 * **When to use**
 *
 * Use when you always have a value to read and need the original `S` to produce
 * the updated whole, unlike `Iso`.
 *
 * **Details**
 *
 * - `get(s)` always succeeds and returns `A`.
 * - `replace(a, s)` returns a new `S` with the focused part replaced.
 * - Extends {@link Optional}.
 * - Composing a Lens with a {@link Prism} or {@link Optional} produces an
 *   {@link Optional}.
 *
 * **Example** (Focusing on a struct field)
 *
 * ```ts
 * import { Optic } from "effect"
 *
 * type Person = { readonly name: string; readonly age: number }
 *
 * const _name = Optic.id<Person>().key("name")
 *
 * console.log(_name.get({ name: "Alice", age: 30 }))
 * // Output: "Alice"
 * ```
 *
 * @see {@link makeLens} — constructor
 * @see {@link Iso} — when conversion is lossless in both directions
 * @see {@link Optional} — when reading can also fail
 *
 * @category Lens
 * @since 4.0.0
 */
export interface Lens<in out S, in out A> extends Optional<S, A> {
  readonly get: (s: S) => A
}

/**
 * Creates a {@link Lens} from a getter and a replacer.
 *
 * **When to use**
 *
 * Use when you can always extract `A` from `S` and produce a new `S` by
 *   substituting a new `A`.
 *
 * **Details**
 *
 * - `replace(a, s)` should return a structurally new `S` with `a` in place
 *   of the old focus.
 *
 * **Example** (Focusing on the first element of a pair)
 *
 * ```ts
 * import { Optic } from "effect"
 *
 * const _first = Optic.makeLens<readonly [string, number], string>(
 *   (pair) => pair[0],
 *   (s, pair) => [s, pair[1]]
 * )
 *
 * console.log(_first.get(["hello", 42]))
 * // Output: "hello"
 *
 * console.log(_first.replace("world", ["hello", 42]))
 * // Output: ["world", 42]
 * ```
 *
 * @see {@link Lens} — the type this function returns
 * @see {@link makeIso} — when no original `S` is needed for `set`
 *
 * @category constructors
 * @since 4.0.0
 */
export function makeLens<S, A>(get: (s: S) => A, replace: (a: A, s: S) => S): Lens<S, A> {
  return make(new LensNode(get, replace))
}

/**
 * Focuses on a part `A` of `S` that may not be present (e.g. a union
 * variant or a validated subset).
 *
 * **When to use**
 *
 * Use when the focus is conditional — reading can fail (wrong variant, failed
 *   validation).
 * - Building a new `S` from `A` does **not** require the original `S`.
 *
 * **Details**
 *
 * - `getResult(s)` returns `Result.Success<A>` when the focus matches, or
 *   `Result.Failure<string>` with an error message.
 * - `set(a)` always succeeds and returns a new `S`.
 * - Extends {@link Optional}.
 * - Composing two Prisms produces a Prism; composing a Prism with a
 *   {@link Lens} produces an {@link Optional}.
 *
 * **Example** (Narrowing a tagged union)
 *
 * ```ts
 * import { Optic, Result } from "effect"
 *
 * type Shape =
 *   | { readonly _tag: "Circle"; readonly radius: number }
 *   | { readonly _tag: "Rect"; readonly width: number }
 *
 * const _circle = Optic.id<Shape>().tag("Circle")
 *
 * console.log(Result.isSuccess(_circle.getResult({ _tag: "Circle", radius: 5 })))
 * // Output: true
 *
 * console.log(Result.isFailure(_circle.getResult({ _tag: "Rect", width: 10 })))
 * // Output: true
 * ```
 *
 * @see {@link makePrism} — constructor
 * @see {@link fromChecks} — build a Prism from schema checks
 * @see {@link Lens} — when reading always succeeds
 *
 * @category Prism
 * @since 4.0.0
 */
export interface Prism<in out S, in out A> extends Optional<S, A> {
  readonly set: (a: A) => S
}

/**
 * Creates a {@link Prism} from a fallible getter and an infallible setter.
 *
 * **When to use**
 *
 * Use when reading can fail (the part may not exist in `S`), but building `S`
 *   from `A` always succeeds.
 *
 * **Details**
 *
 * - `getResult` should return `Result.fail(message)` on mismatch.
 *
 * **Example** (Parsing a string to a number)
 *
 * ```ts
 * import { Optic, Result } from "effect"
 *
 * const numeric = Optic.makePrism<string, number>(
 *   (s) => {
 *     const n = Number(s)
 *     return Number.isNaN(n) ? Result.fail("not a number") : Result.succeed(n)
 *   },
 *   String
 * )
 *
 * console.log(Result.isSuccess(numeric.getResult("42")))
 * // Output: true
 *
 * console.log(numeric.set(42))
 * // Output: "42"
 * ```
 *
 * @see {@link Prism} — the type this function returns
 * @see {@link fromChecks} — build from `Schema` checks instead
 *
 * @category constructors
 * @since 4.0.0
 */
export function makePrism<S, A>(getResult: (s: S) => Result.Result<A, string>, set: (a: A) => S): Prism<S, A> {
  return make(new PrismNode(getResult, set))
}

/**
 * Creates a {@link Prism} from one or more `Schema` validation checks.
 *
 * **When to use**
 *
 * Use when you want to narrow `T` to the subset that passes certain validation
 *   rules (e.g. positive integer).
 * - You already have `Schema.isGreaterThan`, `Schema.isInt`, etc.
 *
 * **Details**
 *
 * - `getResult` runs all checks; fails with a combined error message when
 *   any check fails.
 * - `set` is identity — the value passes through unchanged.
 *
 * **Example** (Creating a positive integer prism)
 *
 * ```ts
 * import { Optic, Result, Schema } from "effect"
 *
 * const posInt = Optic.fromChecks<number>(
 *   Schema.isGreaterThan(0),
 *   Schema.isInt()
 * )
 *
 * console.log(Result.isSuccess(posInt.getResult(3)))
 * // Output: true
 *
 * console.log(Result.isFailure(posInt.getResult(-1)))
 * // Output: true
 * ```
 *
 * @see {@link makePrism} — constructor with custom getter/setter
 * @see {@link Prism} — the type this function returns
 *
 * @category constructors
 * @since 4.0.0
 */
export function fromChecks<T>(...checks: readonly [SchemaAST.Check<T>, ...Array<SchemaAST.Check<T>>]): Prism<T, T> {
  return make(new CheckNode(checks))
}

type Node =
  | IdentityNode
  | IsoNode<any, any>
  | LensNode<any, any>
  | PrismNode<any, any>
  | OptionalNode<any, any>
  | PathNode
  | CheckNode<any>
  | CompositionNode

class IdentityNode {
  readonly _tag = "IdentityNode"
}

const identityNode = new IdentityNode()

class CompositionNode {
  readonly _tag = "CompositionNode"
  readonly nodes: readonly [Node, ...Array<Node>]

  constructor(nodes: readonly [Node, ...Array<Node>]) {
    this.nodes = nodes
  }
}

class IsoNode<S, A> {
  readonly _tag = "IsoNode"
  readonly get: (s: S) => A
  readonly set: (a: A) => S

  constructor(get: (s: S) => A, set: (a: A) => S) {
    this.get = get
    this.set = set
  }
}

class LensNode<S, A> {
  readonly _tag = "LensNode"
  readonly get: (s: S) => A
  readonly set: (a: A, s: S) => S

  constructor(get: (s: S) => A, set: (a: A, s: S) => S) {
    this.get = get
    this.set = set
  }
}

class PrismNode<S, A> {
  readonly _tag = "PrismNode"
  readonly get: (s: S) => Result.Result<A, string>
  readonly set: (a: A) => S

  constructor(get: (s: S) => Result.Result<A, string>, set: (a: A) => S) {
    this.get = get
    this.set = set
  }
}

class OptionalNode<S, A> {
  readonly _tag = "OptionalNode"
  readonly get: (s: S) => Result.Result<A, string>
  readonly set: (a: A, s: S) => Result.Result<S, string>

  constructor(get: (s: S) => Result.Result<A, string>, set: (a: A, s: S) => Result.Result<S, string>) {
    this.get = get
    this.set = set
  }
}

class PathNode {
  readonly _tag = "PathNode"
  readonly path: ReadonlyArray<PropertyKey>

  constructor(path: ReadonlyArray<PropertyKey>) {
    this.path = path
  }
}

class CheckNode<T> {
  readonly _tag = "CheckNode"
  readonly checks: readonly [SchemaAST.Check<T>, ...Array<SchemaAST.Check<T>>]

  constructor(checks: readonly [SchemaAST.Check<T>, ...Array<SchemaAST.Check<T>>]) {
    this.checks = checks
  }
}

// Nodes that can appear in a normalized chain (no Identity/Composition)
type NormalizedNode = Exclude<Node, IdentityNode | CompositionNode>

// Fuse with tail when possible, else push.
function pushNormalized(acc: Array<NormalizedNode>, node: NormalizedNode): void {
  const last = acc[acc.length - 1]
  if (last) {
    if (last._tag === "PathNode" && node._tag === "PathNode") {
      // fuse Path
      acc[acc.length - 1] = new PathNode([...last.path, ...node.path])
      return
    }
    if (last._tag === "CheckNode" && node._tag === "CheckNode") {
      // fuse Checks
      acc[acc.length - 1] = new CheckNode<any>([...last.checks, ...node.checks])
      return
    }
  }
  acc.push(node)
}

// Collect nodes from a node into `acc`, flattening & normalizing on the fly.
function collect(node: Node, acc: Array<NormalizedNode>): void {
  if (node._tag === "IdentityNode") return
  if (node._tag === "CompositionNode") {
    // flatten without extra arrays
    for (let i = 0; i < node.nodes.length; i++) collect(node.nodes[i], acc)
    return
  }
  // primitive node
  pushNormalized(acc, node)
}

function compose(a: Node, b: Node): Node {
  const nodes: Array<NormalizedNode> = []
  collect(a, nodes)
  collect(b, nodes)

  switch (nodes.length) {
    case 0:
      return identityNode
    case 1:
      return nodes[0]
    default:
      return new CompositionNode(nodes as [Node, ...Array<Node>])
  }
}

type ForbidUnion<A, Message extends string> = IsUnion<A> extends true ? [Message] : []

/**
 * The most general optic — both reading and writing can fail.
 *
 * **When to use**
 *
 * Use when the focus may not exist in `S` and writing a new `A` back may also
 * fail, for example when the source no longer matches the expected shape. This
 * is the base type extended by {@link Iso}, {@link Lens}, {@link Prism}, and
 * {@link Traversal}.
 *
 * **Details**
 *
 * - `getResult(s)` returns `Result.Success<A>` or `Result.Failure<string>`.
 * - `replaceResult(a, s)` returns `Result.Success<S>` or
 *   `Result.Failure<string>`.
 * - `replace(a, s)` returns the original `s` on failure (never throws).
 * - `modify(f)` returns the original `s` on failure (never throws).
 * - All operations are pure; inputs are never mutated.
 *
 * **Example** (Focusing on an optional record key)
 *
 * ```ts
 * import { Optic, Result } from "effect"
 *
 * type Env = { [key: string]: string }
 * const _home = Optic.id<Env>().at("HOME")
 *
 * console.log(Result.isSuccess(_home.getResult({ HOME: "/root" })))
 * // Output: true
 *
 * console.log(Result.isFailure(_home.getResult({ PATH: "/bin" })))
 * // Output: true
 *
 * // replace returns original on failure
 * console.log(_home.replace("/new", { PATH: "/bin" }))
 * // Output: { PATH: "/bin" }
 * ```
 *
 * @see {@link makeOptional} — constructor
 * @see {@link Lens} — when reading always succeeds
 * @see {@link Prism} — when writing always succeeds
 *
 * @category Optional
 * @since 4.0.0
 */
export interface Optional<in out S, in out A> {
  readonly node: Node
  /**
   * Attempts to read the focus `A` from the whole `S`. Returns
   * `Result.Success<A>` when the focus exists, or
   * `Result.Failure<string>` with a descriptive error otherwise.
   */
  readonly getResult: (s: S) => Result.Result<A, string>
  /**
   * Replaces the focus in `S` with a new `A`. Returns the original `s`
   * unchanged when the optic cannot focus (never throws).
   */
  readonly replace: (a: A, s: S) => S
  /**
   * Like {@link replace}, but returns an explicit `Result` so callers can
   * detect and handle failure.
   */
  readonly replaceResult: (a: A, s: S) => Result.Result<S, string>
  /**
   * Composes this optic with another. The result type is the weakest of
   * the two: Iso + Iso = Iso, Lens + Prism = Optional, etc.
   *
   * **Example** (Composing a lens with a prism)
   *
   * ```ts
   * import { Optic, Option } from "effect"
   *
   * type State = { value: Option.Option<number> }
   *
   * const _inner = Optic.id<State>().key("value").compose(Optic.some())
   * // _inner is Optional<State, number>
   * ```
   *
   * @see {@link id} — start a composition chain
   */
  compose<B>(this: Iso<S, A>, that: Iso<A, B>): Iso<S, B>
  compose<B>(this: Lens<S, A>, that: Lens<A, B>): Lens<S, B>
  compose<B>(this: Prism<S, A>, that: Prism<A, B>): Prism<S, B>
  compose<B>(this: Optional<S, A>, that: Optional<A, B>): Optional<S, B>

  /**
   * Returns a function `(s: S) => S` that applies `f` to the focused value.
   * If the optic cannot focus, the original `s` is returned unchanged.
   *
   * **Example** (Incrementing a nested field)
   *
   * ```ts
   * import { Optic } from "effect"
   *
   * type S = { readonly a: { readonly b: number } }
   * const _b = Optic.id<S>().key("a").key("b")
   *
   * const inc = _b.modify((n) => n + 1)
   * console.log(inc({ a: { b: 1 } }))
   * // Output: { a: { b: 2 } }
   * ```
   */
  modify(f: (a: A) => A): (s: S) => S

  /**
   * Focuses on a property of the current struct/tuple focus.
   *
   * **Details**
   *
   * - On a {@link Lens}, returns a Lens.
   * - On an {@link Optional}, returns an Optional.
   * - Does **not** work on union types (compile error).
   *
   * **Example** (Drilling into nested structs)
   *
   * ```ts
   * import { Optic } from "effect"
   *
   * type S = { readonly a: { readonly b: number } }
   * const _b = Optic.id<S>().key("a").key("b")
   *
   * console.log(_b.get({ a: { b: 42 } }))
   * // Output: 42
   * ```
   */
  key<S, A extends object, Key extends keyof A>(
    this: Lens<S, A>,
    key: Key,
    ..._err: ForbidUnion<A, "cannot use `key` on a union type">
  ): Lens<S, A[Key]>
  key<S, A extends object, Key extends keyof A>(
    this: Optional<S, A>,
    key: Key,
    ..._err: ForbidUnion<A, "cannot use `key` on a union type">
  ): Optional<S, A[Key]>

  /**
   * Focuses on a key where setting `undefined` **removes** the key from the
   * struct (or splices the element from an array/tuple).
   *
   * **Details**
   *
   * - The focus type becomes `A[Key] | undefined`.
   * - Does **not** work on union types (compile error).
   *
   * **Example** (Deleting an optional key)
   *
   * ```ts
   * import { Optic } from "effect"
   *
   * type S = { readonly a?: number }
   * const _a = Optic.id<S>().optionalKey("a")
   *
   * console.log(_a.replace(undefined, { a: 1 }))
   * // Output: {}
   *
   * console.log(_a.replace(2, {}))
   * // Output: { a: 2 }
   * ```
   */
  optionalKey<S, A extends object, Key extends keyof A>(
    this: Lens<S, A>,
    key: Key,
    ..._err: ForbidUnion<A, "cannot use `optionalKey` on a union type">
  ): Lens<S, A[Key] | undefined>
  optionalKey<S, A extends object, Key extends keyof A>(
    this: Optional<S, A>,
    key: Key,
    ..._err: ForbidUnion<A, "cannot use `optionalKey` on a union type">
  ): Optional<S, A[Key] | undefined>

  /**
   * Adds one or more `Schema` validation checks to the optic chain.
   * `getResult` fails when any check fails; `set` passes through unchanged.
   *
   * **Details**
   *
   * - On a {@link Prism}, returns a Prism.
   * - On an {@link Optional}, returns an Optional.
   *
   * **Example** (Focusing only on positive numbers)
   *
   * ```ts
   * import { Optic, Result, Schema } from "effect"
   *
   * const _pos = Optic.id<number>().check(Schema.isGreaterThan(0))
   *
   * console.log(Result.isSuccess(_pos.getResult(5)))
   * // Output: true
   *
   * console.log(Result.isFailure(_pos.getResult(-1)))
   * // Output: true
   * ```
   *
   * @see {@link fromChecks} — standalone prism from checks
   */
  check<S, A>(this: Prism<S, A>, ...checks: readonly [SchemaAST.Check<A>, ...Array<SchemaAST.Check<A>>]): Prism<S, A>
  check<S, A>(
    this: Optional<S, A>,
    ...checks: readonly [SchemaAST.Check<A>, ...Array<SchemaAST.Check<A>>]
  ): Optional<S, A>

  /**
   * Narrows the focus to a subtype `B` using a type guard.
   *
   * **Details**
   *
   * - On a {@link Prism}, returns a Prism.
   * - On an {@link Optional}, returns an Optional.
   * - Pass optional `annotations` to customize the error message.
   *
   * **Example** (Narrowing a union)
   *
   * ```ts
   * import { Optic, Result } from "effect"
   *
   * type B = { readonly _tag: "b"; readonly b: number }
   * type S = { readonly _tag: "a"; readonly a: string } | B
   *
   * const _b = Optic.id<S>().refine(
   *   (s: S): s is B => s._tag === "b",
   *   { expected: `"b" tag` }
   * )
   *
   * console.log(Result.isSuccess(_b.getResult({ _tag: "b", b: 1 })))
   * // Output: true
   * ```
   *
   * @see `.tag()` — shorthand for narrowing by `_tag`
   */
  refine<S, A, B extends A>(
    this: Prism<S, A>,
    refinement: (a: A) => a is B,
    annotations?: Schema.Annotations.Filter
  ): Prism<S, B>
  refine<S, A, B extends A>(
    this: Optional<S, A>,
    refinement: (a: A) => a is B,
    annotations?: Schema.Annotations.Filter
  ): Optional<S, B>

  /**
   * Narrows the focus to the variant of a tagged union with the given
   * `_tag` value.
   *
   * **Details**
   *
   * - On a {@link Prism}, returns a Prism.
   * - On an {@link Optional}, returns an Optional.
   * - Shorthand for `.refine(s => s._tag === tag)`.
   *
   * **Example** (Focusing a tagged variant)
   *
   * ```ts
   * import { Optic, Result } from "effect"
   *
   * type Shape =
   *   | { readonly _tag: "Circle"; readonly radius: number }
   *   | { readonly _tag: "Rect"; readonly width: number }
   *
   * const _radius = Optic.id<Shape>().tag("Circle").key("radius")
   *
   * console.log(Result.isSuccess(_radius.getResult({ _tag: "Circle", radius: 5 })))
   * // Output: true
   *
   * console.log(Result.isFailure(_radius.getResult({ _tag: "Rect", width: 10 })))
   * // Output: true
   * ```
   *
   * @see `.refine()` — for arbitrary type guards
   */
  tag<S, A extends { readonly _tag: SchemaAST.LiteralValue }, Tag extends A["_tag"]>(
    this: Prism<S, A>,
    tag: Tag
  ): Prism<S, Extract<A, { readonly _tag: Tag }>>
  tag<S, A extends { readonly _tag: SchemaAST.LiteralValue }, Tag extends A["_tag"]>(
    this: Optional<S, A>,
    tag: Tag
  ): Optional<S, Extract<A, { readonly _tag: Tag }>>

  /**
   * Focuses on a key only if it exists (`Object.hasOwn`). Both
   * `getResult` and `replaceResult` fail when the key is absent.
   *
   * **Details**
   *
   * Unlike `.key()`, which always succeeds on the read side, `.at()` is
   * useful for Records or arrays where the key/index may not be present.
   *
   * - Always returns an {@link Optional}.
   * - Does **not** work on union types (compile error).
   *
   * **Example** (Accessing records safely)
   *
   * ```ts
   * import { Optic, Result } from "effect"
   *
   * type Env = { [key: string]: number }
   * const _x = Optic.id<Env>().at("x")
   *
   * console.log(Result.isSuccess(_x.getResult({ x: 1 })))
   * // Output: true
   *
   * console.log(Result.isFailure(_x.getResult({ y: 2 })))
   * // Output: true
   * ```
   *
   * @see `.key()` — when the key is always present
   */
  at<S, A extends object, Key extends keyof A>(
    this: Optional<S, A>,
    key: Key,
    ..._err: ForbidUnion<A, "cannot use `at` on a union type">
  ): Optional<S, A[Key]>

  /**
   * Focuses on a subset of keys of the current struct focus.
   *
   * **Details**
   *
   * - On a {@link Lens}, returns a Lens.
   * - On an {@link Optional}, returns an Optional.
   * - Does **not** work on union types (compile error).
   *
   * **Example** (Picking keys)
   *
   * ```ts
   * import { Optic } from "effect"
   *
   * type S = { readonly a: string; readonly b: number; readonly c: boolean }
   *
   * const _ac = Optic.id<S>().pick(["a", "c"])
   *
   * console.log(_ac.get({ a: "hi", b: 1, c: true }))
   * // Output: { a: "hi", c: true }
   * ```
   *
   * @see `.omit()` — the inverse operation
   */
  pick<S, A, Keys extends ReadonlyArray<keyof A>>(
    this: Lens<S, A>,
    keys: Keys,
    ..._err: ForbidUnion<A, "cannot use `pick` on a union type">
  ): Lens<S, Pick<A, Keys[number]>>
  pick<S, A, Keys extends ReadonlyArray<keyof A>>(
    this: Optional<S, A>,
    keys: Keys,
    ..._err: ForbidUnion<A, "cannot use `pick` on a union type">
  ): Optional<S, Pick<A, Keys[number]>>

  /**
   * Focuses on all keys **except** the specified ones.
   *
   * **Details**
   *
   * - On a {@link Lens}, returns a Lens.
   * - On an {@link Optional}, returns an Optional.
   * - Does **not** work on union types (compile error).
   *
   * **Example** (Omitting keys)
   *
   * ```ts
   * import { Optic } from "effect"
   *
   * type S = { readonly a: string; readonly b: number; readonly c: boolean }
   *
   * const _ac = Optic.id<S>().omit(["b"])
   *
   * console.log(_ac.get({ a: "hi", b: 1, c: true }))
   * // Output: { a: "hi", c: true }
   * ```
   *
   * @see `.pick()` — the inverse operation
   *
   * @since 4.0.0
   */
  omit<S, A, Keys extends ReadonlyArray<keyof A>>(
    this: Lens<S, A>,
    keys: Keys,
    ..._err: ForbidUnion<A, "cannot use `omit` on a union type">
  ): Lens<S, Omit<A, Keys[number]>>
  omit<S, A, Keys extends ReadonlyArray<keyof A>>(
    this: Optional<S, A>,
    keys: Keys,
    ..._err: ForbidUnion<A, "cannot use `omit` on a union type">
  ): Optional<S, Omit<A, Keys[number]>>

  /**
   * Filters out `undefined` from the focus, producing a {@link Prism}.
   * `getResult` fails when the focus is `undefined`.
   *
   * **Example** (Filtering undefined values)
   *
   * ```ts
   * import { Optic, Result } from "effect"
   *
   * const _defined = Optic.id<number | undefined>().notUndefined()
   *
   * console.log(Result.isSuccess(_defined.getResult(42)))
   * // Output: true
   *
   * console.log(Result.isFailure(_defined.getResult(undefined)))
   * // Output: true
   * ```
   *
   * @since 4.0.0
   */
  notUndefined(): Prism<S, Exclude<A, undefined>>
  notUndefined(): Optional<S, Exclude<A, undefined>>

  /**
   * Focuses **all elements** of an array-like focus and optionally narrows
   * to a subset using an element-level optic.
   * Available only on {@link Traversal} (i.e. when `A` is
   * `ReadonlyArray<Element>`). Returns a new Traversal focused on the
   * selected elements.
   *
   * **Details**
   *
   * - **getResult** collects the values focused by `f(id<A>())` for each
   *   element. Non-focusable elements are skipped.
   * - **replaceResult** expects exactly as many values as were collected by
   *   `getResult` and writes them back in order. Fails with a
   *   length-mismatch error if counts differ.
   *
   * **Example** (Incrementing liked posts)
   *
   * ```ts
   * import { Optic, Schema } from "effect"
   *
   * type Post = { title: string; likes: number }
   * type S = { user: { posts: ReadonlyArray<Post> } }
   *
   * const _likes = Optic.id<S>()
   *   .key("user")
   *   .key("posts")
   *   .forEach((post) => post.key("likes").check(Schema.isGreaterThan(0)))
   *
   * const addLike = _likes.modifyAll((n) => n + 1)
   *
   * console.log(
   *   addLike({
   *     user: { posts: [{ title: "a", likes: 0 }, { title: "b", likes: 1 }] }
   *   })
   * )
   * // Output: { user: { posts: [{ title: "a", likes: 0 }, { title: "b", likes: 2 }] } }
   * ```
   *
   * @see {@link getAll} — extract all focused elements as an array
   * @see `.modifyAll()` — apply a function to every focused element
   */
  forEach<S, A, B>(this: Traversal<S, A>, f: (iso: Iso<A, A>) => Optional<A, B>): Traversal<S, B>

  /**
   * Applies a function to **every** element focused by the traversal.
   *
   * **Details**
   *
   * Available only on {@link Traversal}. Returns a function `(s: S) => S`.
   * If the traversal cannot focus, the original `s` is returned unchanged.
   *
   * Unlike `.modify()`, which operates on the whole array, `modifyAll`
   * maps `f` over each individual element.
   *
   * **Example** (Doubling all focused values)
   *
   * ```ts
   * import { Optic, Schema } from "effect"
   *
   * type S = { readonly items: ReadonlyArray<number> }
   *
   * const _positive = Optic.id<S>()
   *   .key("items")
   *   .forEach((n) => n.check(Schema.isGreaterThan(0)))
   *
   * const doubled = _positive.modifyAll((n) => n * 2)
   *
   * console.log(doubled({ items: [1, -2, 3] }))
   * // Output: { items: [2, -2, 6] }
   * ```
   *
   * @see `.forEach()` — create a sub-traversal
   * @see {@link getAll} — extract focused elements
   */
  modifyAll<S, A>(this: Traversal<S, A>, f: (a: A) => A): (s: S) => S
}

/**
 * Creates an {@link Optional} from a fallible getter and a fallible setter.
 *
 * **When to use**
 *
 * Use when you need an optic for a focus that may be missing on read and may
 * reject updates on write.
 *
 * **Details**
 *
 * - `getResult` should return `Result.fail(message)` on mismatch.
 * - `set` should return `Result.fail(message)` when the update cannot be
 *   applied.
 *
 * **Example** (Accessing record keys safely)
 *
 * ```ts
 * import { Optic, Result } from "effect"
 *
 * const atKey = (key: string) =>
 *   Optic.makeOptional<Record<string, number>, number>(
 *     (s) =>
 *       Object.hasOwn(s, key)
 *         ? Result.succeed(s[key])
 *         : Result.fail(`Key "${key}" not found`),
 *     (a, s) =>
 *       Object.hasOwn(s, key)
 *         ? Result.succeed({ ...s, [key]: a })
 *         : Result.fail(`Key "${key}" not found`)
 *   )
 *
 * console.log(Result.isSuccess(atKey("x").getResult({ x: 1 })))
 * // Output: true
 * ```
 *
 * @see {@link Optional} — the type this function returns
 * @see {@link makeLens} — when reading always succeeds
 * @see {@link makePrism} — when writing always succeeds
 *
 * @category constructors
 * @since 4.0.0
 */
export function makeOptional<S, A>(
  getResult: (s: S) => Result.Result<A, string>,
  set: (a: A, s: S) => Result.Result<S, string>
): Optional<S, A> {
  return make(new OptionalNode(getResult, set))
}

/**
 * An optic that focuses on **zero or more** elements of type `A` inside `S`.
 *
 * **When to use**
 *
 * Use when you want to read/update multiple elements at once (e.g. all items in
 *   an array, or a filtered subset).
 *
 * **Details**
 *
 * - Technically `Optional<S, ReadonlyArray<A>>` — the focused value is an
 *   array of all matched elements.
 * - Use `.forEach()` to add per-element sub-optics (filtering, drilling
 *   deeper).
 * - Use `.modifyAll(f)` to map a function over every focused element.
 * - Use {@link getAll} to extract all focused elements as a plain array.
 *
 * **Example** (Traversing array elements with a filter)
 *
 * ```ts
 * import { Optic, Schema } from "effect"
 *
 * type S = { readonly items: ReadonlyArray<number> }
 *
 * const _positive = Optic.id<S>()
 *   .key("items")
 *   .forEach((n) => n.check(Schema.isGreaterThan(0)))
 *
 * const getPositive = Optic.getAll(_positive)
 *
 * console.log(getPositive({ items: [1, -2, 3] }))
 * // Output: [1, 3]
 * ```
 *
 * @see {@link getAll} — extract focused elements
 * @see {@link Optional} — the base type
 *
 * @category Traversal
 * @since 4.0.0
 */
export interface Traversal<in out S, in out A> extends Optional<S, ReadonlyArray<A>> {}

class OptionalImpl<S, A> implements Optional<S, A> {
  readonly node: Node
  readonly getResult: (s: S) => Result.Result<A, string>
  readonly replaceResult: (a: A, s: S) => Result.Result<S, string>
  constructor(
    node: Node,
    getResult: (s: S) => Result.Result<A, string>,
    replaceResult: (a: A, s: S) => Result.Result<S, string>
  ) {
    this.node = node
    this.getResult = getResult
    this.replaceResult = replaceResult
  }
  replace(a: A, s: S): S {
    return Result.getOrElse(this.replaceResult(a, s), () => s)
  }
  modify(f: (a: A) => A): (s: S) => S {
    return (s) => Result.getOrElse(Result.flatMap(this.getResult(s), (a) => this.replaceResult(f(a), s)), () => s)
  }
  compose(that: any): any {
    return make(compose(this.node, that.node))
  }
  key(key: PropertyKey): any {
    return make(compose(this.node, new PathNode([key])))
  }
  optionalKey(key: PropertyKey): any {
    return make(
      compose(
        this.node,
        new LensNode(
          (s) => s[key],
          (a, s) => {
            const copy = cloneShallow(s)
            if (a === undefined) {
              if (Array.isArray(copy) && typeof key === "number") {
                copy.splice(key, 1)
              } else {
                delete copy[key]
              }
            } else {
              copy[key] = a
            }
            return copy
          }
        )
      )
    )
  }
  check(...checks: readonly [SchemaAST.Check<any>, ...Array<SchemaAST.Check<any>>]): any {
    return make(compose(this.node, new CheckNode(checks)))
  }
  refine<B extends A>(refinement: (a: A) => a is B, annotations?: Schema.Annotations.Filter): any {
    return make(compose(this.node, new CheckNode([SchemaAST.makeFilterByGuard(refinement, annotations)])))
  }
  tag(tag: string): any {
    return make(
      compose(
        this.node,
        new PrismNode(
          (s) =>
            s._tag === tag
              ? Result.succeed(s)
              : Result.fail(`Expected ${format(tag)} tag, got ${format(s._tag)}`),
          identity
        )
      )
    )
  }
  at(key: PropertyKey, ..._rest: Array<any>): any {
    const err = Result.fail(`Key ${format(key)} not found`)
    return make(
      compose(
        this.node,
        new OptionalNode(
          (s) => Object.hasOwn(s, key) ? Result.succeed(s[key]) : err,
          (a, s) => {
            if (Object.hasOwn(s, key)) {
              const copy = cloneShallow(s)
              copy[key] = a
              return Result.succeed(copy)
            } else {
              return err
            }
          }
        )
      )
    )
  }
  pick(keys: any) {
    return this.compose(makeLens(Struct.pick(keys), (p, a) => ({ ...a, ...p })))
  }
  omit(keys: any) {
    return this.compose(makeLens(Struct.omit(keys), (o, a) => ({ ...a, ...o })))
  }
  notUndefined(): Prism<S, Exclude<A, undefined>> {
    return this.refine(Predicate.isNotUndefined, { expected: "a value other than `undefined`" })
  }
  forEach<S, A, B>(this: Traversal<S, A>, f: (iso: Iso<A, A>) => Optional<A, B>): Traversal<S, B> {
    const inner = f(id<A>())
    return makeOptional<S, ReadonlyArray<B>>(
      // GET: collect focused Bs
      (s) =>
        Result.map(this.getResult(s), (as) => {
          const bs: Array<B> = []
          for (let i = 0; i < as.length; i++) {
            const r = inner.getResult(as[i])
            if (Result.isSuccess(r)) bs.push(r.success)
          }
          return bs
        }),
      // SET: bs must match the number of focusable elements
      (bs, s) =>
        Result.flatMap(this.getResult(s), (as) => {
          // 1) collect focusable indices
          const idxs: Array<number> = []
          for (let i = 0; i < as.length; i++) {
            if (Result.isSuccess(inner.getResult(as[i]))) idxs.push(i)
          }

          // 2) arity check
          if (bs.length !== idxs.length) {
            return Result.fail(
              `each: replacement length mismatch: ${bs.length} !== ${idxs.length}`
            )
          }

          // 3) update those indices
          const out: Array<A> = as.slice()
          for (let k = 0; k < idxs.length; k++) {
            const i = idxs[k]
            const r = inner.replaceResult(bs[k], as[i])
            if (Result.isFailure(r)) {
              return Result.fail(`each: could not set element ${i}`)
            }
            out[i] = r.success
          }
          return this.replaceResult(out, s)
        })
    )
  }
  modifyAll<S, A>(this: Traversal<S, A>, f: (a: A) => A): (s: S) => S {
    return (s) =>
      Result.getOrElse(
        Result.flatMap(this.getResult(s), (as) => this.replaceResult(as.map(f), s)),
        () => s
      )
  }
}

class IsoImpl<S, A> extends OptionalImpl<S, A> implements Iso<S, A> {
  readonly get: (s: S) => A
  readonly set: (a: A) => S
  constructor(node: Node, get: (s: S) => A, set: (a: A) => S) {
    super(node, (s) => Result.succeed(get(s)), (a) => Result.succeed(set(a)))
    this.get = get
    this.set = set
  }
  override replace(a: A, _: S): S {
    return this.set(a)
  }
  override modify(f: (a: A) => A): (s: S) => S {
    return (s) => this.set(f(this.get(s)))
  }
}

class LensImpl<S, A> extends OptionalImpl<S, A> implements Lens<S, A> {
  readonly get: (s: S) => A
  constructor(node: Node, get: (s: S) => A, replace: (a: A, s: S) => S) {
    super(node, (s) => Result.succeed(get(s)), (a, s) => Result.succeed(replace(a, s)))
    this.get = get
    this.replace = replace
  }
  override modify(f: (a: A) => A): (s: S) => S {
    return (s) => this.replace(f(this.get(s)), s)
  }
}

class PrismImpl<S, A> extends OptionalImpl<S, A> implements Prism<S, A> {
  readonly set: (a: A) => S
  constructor(node: Node, getResult: (s: S) => Result.Result<A, string>, set: (a: A) => S) {
    super(node, getResult, (a, _) => Result.succeed(set(a)))
    this.set = set
  }
  override replace(a: A, _: S): S {
    return this.set(a)
  }
  override modify(f: (a: A) => A): (s: S) => S {
    return (s) => Result.getOrElse(Result.map(this.getResult(s), (a) => this.set(f(a))), () => s)
  }
}

function make(node: Node): any {
  const op = recur(node)
  switch (op._tag) {
    case "IsoNode":
      return new IsoImpl(node, op.get, op.set)
    case "LensNode":
      return new LensImpl(node, op.get, op.set)
    case "PrismNode":
      return new PrismImpl(node, op.get, op.set)
    case "OptionalNode":
      return new OptionalImpl(node, op.get, op.set)
  }
}

function cloneShallow<T>(pojo: T): T {
  if (Array.isArray(pojo)) return pojo.slice() as T
  if (typeof pojo === "object" && pojo !== null) {
    const proto = Object.getPrototypeOf(pojo)
    if (proto !== Object.prototype && proto !== null) {
      throw new Error("Cannot clone object with non-Object constructor or null prototype")
    }
    return { ...pojo } as T
  }
  return pojo
}

type Op = {
  readonly _tag: "IsoNode" | "LensNode" | "PrismNode" | "OptionalNode"
  readonly get: (s: unknown) => any
  readonly set: (a: unknown, s?: unknown) => any
}

const recur = memoize((node: Node): Op => {
  switch (node._tag) {
    case "IdentityNode":
      return { _tag: "IsoNode", get: identity, set: identity }
    case "IsoNode":
    case "LensNode":
    case "PrismNode":
    case "OptionalNode":
      return { _tag: node._tag, get: node.get, set: node.set }
    case "PathNode": {
      return {
        _tag: "LensNode",
        get: (s: any) => {
          const path = node.path
          let out: any = s
          for (let i = 0, n = path.length; i < n; i++) {
            out = out[path[i]]
          }
          return out
        },
        set: (a: any, s: any) => {
          const path = node.path
          const out = cloneShallow(s)

          let current = out
          let i = 0
          for (; i < path.length - 1; i++) {
            const key = path[i]
            current[key] = cloneShallow(current[key])
            current = current[key]
          }

          const finalKey = path[i]
          current[finalKey] = a

          return out
        }
      }
    }
    case "CheckNode":
      return {
        _tag: "PrismNode",
        get: (s: any) => Result.mapError(SchemaAST.runChecks(node.checks, s), String),
        set: identity
      }
    case "CompositionNode": {
      const ops = node.nodes.map(recur)
      const _tag = ops.reduce<Op["_tag"]>((tag, op) => getCompositionTag(tag, op._tag), "IsoNode")
      return {
        _tag,
        get: (s: any) => {
          for (let i = 0; i < ops.length; i++) {
            const op = ops[i]
            const result = op.get(s)
            if (hasFailingGet(op._tag)) {
              if (Result.isFailure(result)) {
                return result
              }
              s = result.success
            } else {
              s = result
            }
          }
          return hasFailingGet(_tag) ? Result.succeed(s) : s
        },
        set: (a: any, s: any) => {
          const source = s
          const len = ops.length
          const ss = new Array(len + 1)
          ss[0] = s
          for (let i = 0; i < len; i++) {
            const op = ops[i]
            if (hasFailingGet(op._tag)) {
              const result = op.get(s)
              if (Result.isFailure(result)) {
                return _tag === "OptionalNode" ? result : source
              }
              s = result.success
            } else {
              s = op.get(s)
            }
            ss[i + 1] = s
          }
          for (let i = len - 1; i >= 0; i--) {
            const op = ops[i]
            if (hasSet(op._tag)) {
              a = op.set(a)
            } else if (op._tag === "LensNode") {
              a = op.set(a, ss[i])
            } else {
              const result = op.set(a, ss[i])
              if (Result.isFailure(result)) {
                return result
              }
              a = result.success
            }
          }
          return _tag === "OptionalNode" ? Result.succeed(a) : a
        }
      }
    }
  }
})

function hasFailingGet(tag: Op["_tag"]): boolean {
  return tag === "PrismNode" || tag === "OptionalNode"
}

function hasSet(tag: Op["_tag"]): boolean {
  return tag === "IsoNode" || tag === "PrismNode"
}

function getCompositionTag(a: Op["_tag"], b: Op["_tag"]): Op["_tag"] {
  switch (a) {
    case "IsoNode":
      return b
    case "LensNode":
      return hasFailingGet(b) ? "OptionalNode" : "LensNode"
    case "PrismNode":
      return hasSet(b) ? "PrismNode" : "OptionalNode"
    case "OptionalNode":
      return "OptionalNode"
  }
}
// ---------------------------------------------
// Derived APIs
// ---------------------------------------------

/**
 * Returns a function that extracts all elements focused by a
 * {@link Traversal} as a plain mutable array.
 *
 * **When to use**
 *
 * Use when you need the focused values as a simple `Array<A>` for further
 *   processing.
 *
 * **Details**
 *
 * - Returns an empty array when the traversal cannot focus.
 * - Always returns a fresh array (safe to mutate).
 *
 * **Example** (Collecting positive numbers)
 *
 * ```ts
 * import { Optic, Schema } from "effect"
 *
 * type S = { readonly values: ReadonlyArray<number> }
 *
 * const _pos = Optic.id<S>()
 *   .key("values")
 *   .forEach((n) => n.check(Schema.isGreaterThan(0)))
 *
 * const getPositive = Optic.getAll(_pos)
 *
 * console.log(getPositive({ values: [3, -1, 5] }))
 * // Output: [3, 5]
 *
 * console.log(getPositive({ values: [-1, -2] }))
 * // Output: []
 * ```
 *
 * @see {@link Traversal} — the optic type this operates on
 *
 * @category Traversal
 * @since 4.0.0
 */
export function getAll<S, A>(traversal: Traversal<S, A>): (s: S) => Array<A> {
  return (s) =>
    Result.match(traversal.getResult(s), {
      onFailure: () => [],
      onSuccess: (as) => [...as]
    })
}

// ---------------------------------------------
// Built-in Optics
// ---------------------------------------------

const identityIso = make(identityNode)

/**
 * Iso that focuses on the whole value unchanged.
 *
 * **When to use**
 *
 * Use when you need to start an optic chain with a focus on the whole value.
 *
 * **Details**
 *
 * - `get(s)` returns `s`.
 * - `set(a)` returns `a`.
 * - Singleton — every call returns the same instance.
 *
 * **Example** (Starting an optic chain)
 *
 * ```ts
 * import { Optic } from "effect"
 *
 * type S = { readonly x: number }
 *
 * const _x = Optic.id<S>().key("x")
 *
 * console.log(_x.get({ x: 42 }))
 * // Output: 42
 * ```
 *
 * @see {@link Iso} — the type this function returns
 *
 * @category Iso
 * @since 4.0.0
 */
export function id<S>(): Iso<S, S> {
  return identityIso
}

/**
 * Iso that converts a `Record<string, A>` to an array of
 * `[key, value]` entries and back.
 *
 * **When to use**
 *
 * Use when you want to traverse or manipulate record entries as an array (e.g.
 *   with `.forEach()`).
 *
 * **Details**
 *
 * - `get` uses `Object.entries`.
 * - `set` uses `Object.fromEntries`.
 * - Round-trip is lossless for `Record<string, A>`.
 *
 * **Example** (Traversing record values)
 *
 * ```ts
 * import { Optic, Schema } from "effect"
 *
 * const _positiveValues = Optic.entries<number>()
 *   .forEach((entry) => entry.key(1).check(Schema.isGreaterThan(0)))
 *
 * const inc = _positiveValues.modifyAll((n) => n + 1)
 *
 * console.log(inc({ a: 0, b: 3, c: -1 }))
 * // Output: { a: 0, b: 4, c: -1 }
 * ```
 *
 * @see {@link Iso} — the type this function returns
 * @see {@link id} — identity iso
 *
 * @category Iso
 * @since 4.0.0
 */
export function entries<A>(): Iso<Record<string, A>, ReadonlyArray<readonly [string, A]>> {
  return make(new IsoNode(Object.entries, Object.fromEntries))
}

/**
 * Prism that focuses on the value inside `Option.Some`.
 *
 * **When to use**
 *
 * Use when you have an `Option<A>` and want to read/update the inner value only
 *   when it is `Some`.
 *
 * **Details**
 *
 * - `getResult` fails with an error message when the option is `None`.
 * - `set(a)` wraps `a` in `Option.some(a)`.
 *
 * **Example** (Accessing Some value)
 *
 * ```ts
 * import { Optic, Option, Result } from "effect"
 *
 * const _some = Optic.id<Option.Option<number>>().compose(Optic.some())
 *
 * console.log(Result.isSuccess(_some.getResult(Option.some(42))))
 * // Output: true
 *
 * console.log(Result.isFailure(_some.getResult(Option.none())))
 * // Output: true
 *
 * console.log(_some.set(10))
 * // Output: { _tag: "Some", value: 10 }
 * ```
 *
 * @see {@link none} — focuses on `None` instead
 * @see {@link Prism} — the type this function returns
 *
 * @category Prism
 * @since 4.0.0
 */
export function some<A>(): Prism<Option.Option<A>, A> {
  const run = runRefinement(Option.isSome, { expected: "a Some value" })
  return makePrism(
    (s) =>
      Result.mapBoth(run(s), {
        onFailure: String,
        onSuccess: (s) => s.value
      }),
    Option.some
  )
}

/**
 * Prism that focuses on `Option.None`, exposing `undefined`.
 *
 * **When to use**
 *
 * Use when you want to match or construct `None` values within an optic chain.
 *
 * **Details**
 *
 * - `getResult` succeeds with `undefined` when the option is `None`.
 * - `getResult` fails when the option is `Some`.
 * - `set(undefined)` produces `Option.none()`.
 *
 * **Example** (Matching None)
 *
 * ```ts
 * import { Optic, Option, Result } from "effect"
 *
 * const _none = Optic.id<Option.Option<number>>().compose(Optic.none())
 *
 * console.log(Result.isSuccess(_none.getResult(Option.none())))
 * // Output: true
 *
 * console.log(Result.isFailure(_none.getResult(Option.some(1))))
 * // Output: true
 * ```
 *
 * @see {@link some} — focuses on `Some` instead
 * @see {@link Prism} — the type this function returns
 *
 * @category Prism
 * @since 4.0.0
 */
export function none<A>(): Prism<Option.Option<A>, undefined> {
  const run = runRefinement(Option.isNone, { expected: "a None value" })
  return makePrism(
    (s) =>
      Result.mapBoth(run(s), {
        onFailure: String,
        onSuccess: () => undefined
      }),
    () => Option.none()
  )
}

/**
 * Prism that focuses on the success value of a `Result`.
 *
 * **When to use**
 *
 * Use when you have a `Result<A, E>` and want to read/update `A` only when it
 *   is a `Success`.
 *
 * **Details**
 *
 * - `getResult` fails when the result is a `Failure`.
 * - `set(a)` produces `Result.succeed(a)`.
 *
 * **Example** (Accessing success)
 *
 * ```ts
 * import { Optic, Result } from "effect"
 *
 * const _ok = Optic.id<Result.Result<number, string>>().compose(Optic.success())
 *
 * console.log(Result.isSuccess(_ok.getResult(Result.succeed(42))))
 * // Output: true
 *
 * console.log(Result.isFailure(_ok.getResult(Result.fail("err"))))
 * // Output: true
 * ```
 *
 * @see {@link failure} — focuses on the failure side
 * @see {@link Prism} — the type this function returns
 *
 * @category Prism
 * @since 4.0.0
 */
export function success<A, E>(): Prism<Result.Result<A, E>, A> {
  const run = runRefinement(Result.isSuccess, { expected: "a Result.Success value" })
  return makePrism(
    (s) =>
      Result.mapBoth(run(s), {
        onFailure: String,
        onSuccess: (s) => s.success
      }),
    Result.succeed
  )
}

/**
 * Prism that focuses on the failure value of a `Result`.
 *
 * **When to use**
 *
 * Use when you have a `Result<A, E>` and want to read/update `E` only when it
 *   is a `Failure`.
 *
 * **Details**
 *
 * - `getResult` fails when the result is a `Success`.
 * - `set(e)` produces `Result.fail(e)`.
 *
 * **Example** (Accessing failure)
 *
 * ```ts
 * import { Optic, Result } from "effect"
 *
 * const _err = Optic.id<Result.Result<number, string>>().compose(Optic.failure())
 *
 * console.log(Result.isSuccess(_err.getResult(Result.fail("oops"))))
 * // Output: true
 *
 * console.log(Result.isFailure(_err.getResult(Result.succeed(42))))
 * // Output: true
 * ```
 *
 * @see {@link success} — focuses on the success side
 * @see {@link Prism} — the type this function returns
 *
 * @category Prism
 * @since 4.0.0
 */
export function failure<A, E>(): Prism<Result.Result<A, E>, E> {
  const run = runRefinement(Result.isFailure, { expected: "a Result.Failure value" })
  return makePrism(
    (s) =>
      Result.mapBoth(run(s), {
        onFailure: String,
        onSuccess: (s) => s.failure
      }),
    Result.fail
  )
}

function runRefinement<T extends E, E>(
  refinement: (e: E) => e is T,
  annotations?: Schema.Annotations.Filter
): (e: E) => Result.Result<T, SchemaIssue.Issue> {
  return (e) => SchemaAST.runChecks([SchemaAST.makeFilterByGuard(refinement, annotations)], e) as any
}
