import * as Chunk from "@effect/data/Chunk" import { Structural } from "@effect/data/Data" import type { Differ } from "@effect/data/Differ" import type { Either } from "@effect/data/Either" import * as E from "@effect/data/Either" import * as Equal from "@effect/data/Equal" import * as Dual from "@effect/data/Function" /** @internal */ export const OrPatchTypeId: Differ.Or.TypeId = Symbol.for("@effect/data/DifferOrPatch") as Differ.Or.TypeId function variance(a: A): B { return a as unknown as B } /** @internal */ const PatchProto = Object.setPrototypeOf({ [OrPatchTypeId]: { _Value: variance, _Key: variance, _Patch: variance } }, Structural.prototype) /** @internal */ export interface Empty extends Differ.Or.Patch { readonly _tag: "Empty" } const EmptyProto = Object.setPrototypeOf({ _tag: "Empty" }, PatchProto) const _empty = Object.create(EmptyProto) /** @internal */ export const empty = (): Differ.Or.Patch< Value, Value2, Patch, Patch2 > => _empty /** @internal */ export interface AndThen extends Differ.Or.Patch { readonly _tag: "AndThen" readonly first: Differ.Or.Patch readonly second: Differ.Or.Patch } const AndThenProto = Object.setPrototypeOf({ _tag: "AndThen" }, PatchProto) /** @internal */ export const makeAndThen = ( first: Differ.Or.Patch, second: Differ.Or.Patch ): Differ.Or.Patch< Value, Value2, Patch, Patch2 > => { const o = Object.create(AndThenProto) o.first = first o.second = second return o } /** @internal */ export interface SetLeft extends Differ.Or.Patch { readonly _tag: "SetLeft" readonly value: Value } const SetLeftProto = Object.setPrototypeOf({ _tag: "SetLeft" }, PatchProto) /** @internal */ export const makeSetLeft = ( value: Value ): Differ.Or.Patch< Value, Value2, Patch, Patch2 > => { const o = Object.create(SetLeftProto) o.value = value return o } /** @internal */ export interface SetRight extends Differ.Or.Patch { readonly _tag: "SetRight" readonly value: Value2 } const SetRightProto = Object.setPrototypeOf({ _tag: "SetRight" }, PatchProto) /** @internal */ export const makeSetRight = ( value: Value2 ): Differ.Or.Patch< Value, Value2, Patch, Patch2 > => { const o = Object.create(SetRightProto) o.value = value return o } /** @internal */ export interface UpdateLeft extends Differ.Or.Patch { readonly _tag: "UpdateLeft" readonly patch: Patch } const UpdateLeftProto = Object.setPrototypeOf({ _tag: "UpdateLeft" }, PatchProto) /** @internal */ export const makeUpdateLeft = ( patch: Patch ): Differ.Or.Patch< Value, Value2, Patch, Patch2 > => { const o = Object.create(UpdateLeftProto) o.patch = patch return o } /** @internal */ export interface UpdateRight extends Differ.Or.Patch { readonly _tag: "UpdateRight" readonly patch: Patch2 } const UpdateRightProto = Object.setPrototypeOf({ _tag: "UpdateRight" }, PatchProto) /** @internal */ export const makeUpdateRight = ( patch: Patch2 ): Differ.Or.Patch< Value, Value2, Patch, Patch2 > => { const o = Object.create(UpdateRightProto) o.patch = patch return o } type Instruction = | AndThen | Empty | SetLeft | SetRight | UpdateLeft | UpdateRight /** @internal */ export const diff = ( options: { readonly oldValue: Either readonly newValue: Either readonly left: Differ readonly right: Differ } ): Differ.Or.Patch => { switch (options.oldValue._tag) { case "Left": { switch (options.newValue._tag) { case "Left": { const valuePatch = options.left.diff(options.oldValue.left, options.newValue.left) if (Equal.equals(valuePatch, options.left.empty)) { return empty() } return makeUpdateLeft(valuePatch) } case "Right": { return makeSetRight(options.newValue.right) } } } case "Right": { switch (options.newValue._tag) { case "Left": { return makeSetLeft(options.newValue.left) } case "Right": { const valuePatch = options.right.diff(options.oldValue.right, options.newValue.right) if (Equal.equals(valuePatch, options.right.empty)) { return empty() } return makeUpdateRight(valuePatch) } } } } } /** @internal */ export const combine = Dual.dual< ( that: Differ.Or.Patch ) => ( self: Differ.Or.Patch ) => Differ.Or.Patch, ( self: Differ.Or.Patch, that: Differ.Or.Patch ) => Differ.Or.Patch >(2, (self, that) => makeAndThen(self, that)) /** @internal */ export const patch = Dual.dual< ( options: { readonly oldValue: Either readonly left: Differ readonly right: Differ } ) => (self: Differ.Or.Patch) => Either, ( self: Differ.Or.Patch, options: { readonly oldValue: Either readonly left: Differ readonly right: Differ } ) => Either >(2, ( self: Differ.Or.Patch, { left, oldValue, right }: { oldValue: Either left: Differ right: Differ } ) => { let patches: Chunk.Chunk> = Chunk.of(self) let result = oldValue while (Chunk.isNonEmpty(patches)) { const head: Instruction = Chunk.headNonEmpty(patches) as Instruction const tail = Chunk.tailNonEmpty(patches) switch (head._tag) { case "Empty": { patches = tail break } case "AndThen": { patches = Chunk.prepend(head.first)(Chunk.prepend(head.second)(tail)) break } case "UpdateLeft": { if (result._tag === "Left") { result = E.left(left.patch(head.patch, result.left)) } patches = tail break } case "UpdateRight": { if (result._tag === "Right") { result = E.right(right.patch(head.patch, result.right)) } patches = tail break } case "SetLeft": { result = E.left(head.value) patches = tail break } case "SetRight": { result = E.right(head.value) patches = tail break } } } return result })