// tracing: off

/* adapted from https://github.com/gcanti/fp-ts */
import * as Tp from "../Collections/Immutable/Tuple"
import { identity } from "../Function/core"
import * as St from "../Structural"

const _noneHash = /*#__PURE__*/ St.hashString("@effect-ts/system/Option/None")

const _someHash = /*#__PURE__*/ St.hashString("@effect-ts/system/Option/Some")
/**
 * Definitions
 */

export class None {
  constructor() {
    this._tag = "None"
  }

  [St.equalsSym](that) {
    return that instanceof None
  }

  get [St.hashSym]() {
    return _noneHash
  }
}
export class Some {
  constructor(value) {
    this.value = value
    this._tag = "Some"
  }

  [St.equalsSym](that) {
    return that instanceof Some && St.equals(this.value, that.value)
  }

  get [St.hashSym]() {
    return St.combineHash(_someHash, St.hash(this.value))
  }
}
/**
 * Constructs none
 */

export const none = /*#__PURE__*/ new None()
/**
 * Constructs none
 */

export const emptyOf = () => none
/**
 * Constructs Some(A)
 */

export function some(a) {
  return new Some(a)
}
/**
 * Classic applicative
 */

export const ap_ = (mab, ma) =>
  isNone(mab) ? none : isNone(ma) ? none : some(mab.value(ma.value))
/**
 * Classic applicative
 */

export const ap = (fa) => (fab) => ap_(fab, fa)
/**
 * Zips `Option[A]` and `Option[B]` into `Option[(A, B)]
 */

export const zip_ = (fa, fb) => chain_(fa, (a) => map_(fb, (b) => Tp.tuple(a, b)))
/**
 * Zips `Option[A]` and `Option[B]` into `Option[(A, B)]
 */

export const zip = (fb) => (fa) => zip_(fa, fb)
/**
 * Apply both and return first
 */

export const zipFirst = (fb) => (fa) =>
  ap_(
    map_(fa, (a) => () => a),
    fb
  )
/**
 * Apply both and return second
 */

export const zipSecond = (fb) => (fa) =>
  ap_(
    map_(fa, () => (b) => b),
    fb
  )
/**
 * Builds a new option constructed using the value of self
 */

export const chain_ = (ma, f) => (isNone(ma) ? none : f(ma.value))
/**
 * Builds a new option constructed using the value of self
 */

export const chain = (f) => (ma) => chain_(ma, f)
/**
 * Like chain but ignores the constructed outout
 */

export const tap = (f) => (ma) => chain_(ma, (a) => map_(f(a), () => a))
/**
 * Like chain but ignores the constructed outout
 */

export const tap_ = (ma, f) => chain_(ma, (a) => map_(f(a), () => a))
/**
 * Flattens nested options
 */

export const flatten = (fa) => chain_(fa, identity)
/**
 * Wraps this option into a second one
 */

export const duplicate = (ma) => (isNone(ma) ? none : some(ma))
/**
 * Returns `true` if the predicate is satisfied by the wrapped value
 *
 * @example
 * assert.strictEqual(
 *   pipe(
 *     some(1),
 *     exists(n => n > 0)
 *   ),
 *   true
 * )
 * assert.strictEqual(
 *   pipe(
 *     some(1),
 *     exists(n => n > 1)
 *   ),
 *   false
 * )
 * assert.strictEqual(
 *   pipe(
 *     none,
 *     exists(n => n > 0)
 *   ),
 *   false
 * )
 */

export function exists(predicate) {
  return (ma) => (isNone(ma) ? false : predicate(ma.value))
}
/**
 * Apply `Option[A] => B` in case self is some returning `Option[B]`
 */

export const extend = (f) => (self) => extend_(self, f)
/**
 * Apply `Option[A] => B` in case self is some returning `Option[B]`
 */

export const extend_ = (self, f) => (isNone(self) ? none : some(f(self)))
/**
 * Takes a default value, a function, and an `Option` value, if the `Option` value is `None` the default value is
 * returned, otherwise the function is applied to the value inside the `Some` and the result is returned.
 *
 * @example
 * assert.strictEqual(
 *   pipe(
 *     some(1),
 *     fold(() => 'a none', a => `a some containing ${a}`)
 *   ),
 *   'a some containing 1'
 * )
 *
 * assert.strictEqual(
 *   pipe(
 *     none,
 *     fold(() => 'a none', a => `a some containing ${a}`)
 *   ),
 *   'a none'
 * )
 */

export function fold(onNone, onSome) {
  return (ma) => fold_(ma, onNone, onSome)
}
/**
 * Takes a default value, a function, and an `Option` value, if the `Option` value is `None` the default value is
 * returned, otherwise the function is applied to the value inside the `Some` and the result is returned.
 *
 * @example
 * assert.strictEqual(
 *   pipe(
 *     some(1),
 *     fold(() => 'a none', a => `a some containing ${a}`)
 *   ),
 *   'a some containing 1'
 * )
 *
 * assert.strictEqual(
 *   pipe(
 *     none,
 *     fold(() => 'a none', a => `a some containing ${a}`)
 *   ),
 *   'a none'
 * )
 */

export function fold_(ma, onNone, onSome) {
  return isNone(ma) ? onNone() : onSome(ma.value)
}
/**
 * Constructs `Option[A]` from `Either[E, A]` discarding `E`
 */

export const fromEither = (ma) => (ma._tag === "Left" ? none : some(ma.right))
/**
 * Constructs a new `Option` from a nullable type. If the value is `null` or `undefined`, returns `None`, otherwise
 * returns the value wrapped in a `Some`
 *
 * @example
 * assert.deepStrictEqual(fromNullable(undefined), none)
 * assert.deepStrictEqual(fromNullable(null), none)
 * assert.deepStrictEqual(fromNullable(1), some(1))
 */

export function fromNullable(a) {
  return a == null ? none : some(a)
}
export function fromPredicate(predicate) {
  return (a) => (predicate(a) ? some(a) : none)
}
/**
 * Returns an `E` value if possible
 */

export function getLeft(ma) {
  return ma._tag === "Right" ? none : some(ma.left)
}
/**
 * Extracts the value out of the structure, if it exists. Otherwise returns the given default value
 *
 * @example
 * assert.strictEqual(
 *   pipe(
 *     some(1),
 *     getOrElse(() => 0)
 *   ),
 *   1
 * )
 * assert.strictEqual(
 *   pipe(
 *     none,
 *     getOrElse(() => 0)
 *   ),
 *   0
 * )
 */

export function getOrElse(onNone) {
  return (o) => getOrElse_(o, onNone)
}
/**
 * Extracts the value out of the structure, if it exists. Otherwise returns the given default value
 */

export function getOrElseInv(onNone) {
  return getOrElse(onNone)
}
/**
 * Extracts the value out of the structure, if it exists. Otherwise returns the given default value
 *
 * @example
 * assert.strictEqual(
 *   pipe(
 *     some(1),
 *     getOrElse(() => 0)
 *   ),
 *   1
 * )
 * assert.strictEqual(
 *   pipe(
 *     none,
 *     getOrElse(() => 0)
 *   ),
 *   0
 * )
 */

export function getOrElse_(ma, onNone) {
  return ma._tag === "None" ? onNone() : ma.value
}
/**
 * Extracts the value out of the structure, if it exists. Otherwise returns the given default value
 */

export function getOrElseInv_(ma, onNone) {
  return getOrElse_(ma, onNone)
}
/**
 * Returns a `Refinement` (i.e. a custom type guard) from a `Option` returning function.
 * This function ensures that a custom type guard definition is type-safe.
 *
 * ```ts
 * type A = { type: 'A' }
 * type B = { type: 'B' }
 * type C = A | B
 *
 * const isA = (c: C): c is A => c.type === 'B' // <= typo but typescript doesn't complain
 * const isA = getRefinement<C, A>(c => (c.type === 'B' ? some(c) : none)) // static error: Type '"B"' is not assignable to type '"A"'
 * ```
 */

export function getRefinement(getOption) {
  return (a) => isSome(getOption(a))
}
/**
 * Returns an `A` value if possible
 */

export function getRight(ma) {
  return ma._tag === "Left" ? none : some(ma.right)
}
/**
 * Returns `true` if the option is `None`, `false` otherwise
 *
 * @example
 * assert.strictEqual(isNone(some(1)), false)
 * assert.strictEqual(isNone(none), true)
 */

export function isNone(fa) {
  return fa._tag === "None"
}
/**
 * Returns `true` if the option is an instance of `Some`, `false` otherwise
 *
 * @example
 * assert.strictEqual(isSome(some(1)), true)
 * assert.strictEqual(isSome(none), false)
 */

export function isSome(fa) {
  return fa._tag === "Some"
}
/**
 * Use `A => B` to transform `Option[A]` to `Option[B]`
 */

export const map_ = (ma, f) => (isNone(ma) ? none : some(f(ma.value)))
/**
 * Use `A => B` to transform `Option[A]` to `Option[B]`
 */

export const map = (f) => (fa) => map_(fa, f)
/**
 * This is `chain` + `fromNullable`, useful when working with optional values
 *
 * @example
 * interface Employee {
 *   company?: {
 *     address?: {
 *       street?: {
 *         name?: string
 *       }
 *     }
 *   }
 * }
 *
 * const employee1: Employee = { company: { address: { street: { name: 'high street' } } } }
 *
 * assert.deepStrictEqual(
 *   pipe(
 *     fromNullable(employee1.company),
 *     mapNullable(company => company.address),
 *     mapNullable(address => address.street),
 *     mapNullable(street => street.name)
 *   ),
 *   some('high street')
 * )
 *
 * const employee2: Employee = { company: { address: { street: {} } } }
 *
 * assert.deepStrictEqual(
 *   pipe(
 *     fromNullable(employee2.company),
 *     mapNullable(company => company.address),
 *     mapNullable(address => address.street),
 *     mapNullable(street => street.name)
 *   ),
 *   none
 * )
 */

export function mapNullable(f) {
  return (ma) => (isNone(ma) ? none : fromNullable(f(ma.value)))
}
/**
 * Extracts the value out of the structure, if it exists. Otherwise returns `null`.
 *
 * @example
 * assert.strictEqual(
 *   pipe(
 *     some(1),
 *     toNullable
 *   ),
 *   1
 * )
 * assert.strictEqual(
 *   pipe(
 *     none,
 *     toNullable
 *   ),
 *   null
 * )
 */

export function toNullable(ma) {
  return isNone(ma) ? null : ma.value
}
/**
 * Extracts the value out of the structure, if it exists. Otherwise returns `undefined`.
 *
 * @example
 * assert.strictEqual(
 *   pipe(
 *     some(1),
 *     toUndefined
 *   ),
 *   1
 * )
 * assert.strictEqual(
 *   pipe(
 *     none,
 *     toUndefined
 *   ),
 *   undefined
 * )
 */

export function toUndefined(ma) {
  return isNone(ma) ? undefined : ma.value
}
/**
 * Transforms an exception into an `Option`. If `f` throws, returns `None`, otherwise returns the output wrapped in
 * `Some`
 *
 * @example
 * assert.deepStrictEqual(
 *   tryCatch(() => {
 *     throw new Error()
 *   }),
 *   none
 * )
 * assert.deepStrictEqual(tryCatch(() => 1), some(1))
 */

export function tryCatch(f) {
  try {
    return some(f())
  } catch (e) {
    return none
  }
}
export const PartialExceptionTypeId = /*#__PURE__*/ Symbol()
export class PartialException {
  constructor() {
    this._typeId = PartialExceptionTypeId
  }
}

function raisePartial() {
  throw new PartialException()
}
/**
 * Simulates a partial function
 */

export function partial(f) {
  return (...args) => {
    try {
      return some(f(raisePartial)(...args))
    } catch (e) {
      if (e instanceof PartialException) {
        return none
      }

      throw e
    }
  }
}
//# sourceMappingURL=core.js.map
