import * as Chunk from "@effect/data/Chunk" import { Structural } from "@effect/data/Data" import type { Differ } from "@effect/data/Differ" import * as Dual from "@effect/data/Function" import * as HashSet from "@effect/data/HashSet" /** @internal */ export const HashSetPatchTypeId: Differ.HashSet.TypeId = Symbol.for( "@effect/data/DifferHashSetPatch" ) as Differ.HashSet.TypeId function variance(a: A): B { return a as unknown as B } /** @internal */ const PatchProto = Object.setPrototypeOf({ [HashSetPatchTypeId]: { _Value: variance, _Key: variance, _Patch: variance } }, Structural.prototype) interface Empty extends Differ.HashSet.Patch { readonly _tag: "Empty" } const EmptyProto = Object.setPrototypeOf({ _tag: "Empty" }, PatchProto) const _empty = Object.create(EmptyProto) /** @internal */ export const empty = (): Differ.HashSet.Patch => _empty interface AndThen extends Differ.HashSet.Patch { readonly _tag: "AndThen" readonly first: Differ.HashSet.Patch readonly second: Differ.HashSet.Patch } const AndThenProto = Object.setPrototypeOf({ _tag: "AndThen" }, PatchProto) /** @internal */ export const makeAndThen = ( first: Differ.HashSet.Patch, second: Differ.HashSet.Patch ): Differ.HashSet.Patch => { const o = Object.create(AndThenProto) o.first = first o.second = second return o } interface Add extends Differ.HashSet.Patch { readonly _tag: "Add" readonly value: Value } const AddProto = Object.setPrototypeOf({ _tag: "Add" }, PatchProto) /** @internal */ export const makeAdd = ( value: Value ): Differ.HashSet.Patch => { const o = Object.create(AddProto) o.value = value return o } interface Remove extends Differ.HashSet.Patch { readonly _tag: "Remove" readonly value: Value } const RemoveProto = Object.setPrototypeOf({ _tag: "Remove" }, PatchProto) /** @internal */ export const makeRemove = ( value: Value ): Differ.HashSet.Patch => { const o = Object.create(RemoveProto) o.value = value return o } type Instruction = | Add | AndThen | Empty | Remove /** @internal */ export const diff = ( oldValue: HashSet.HashSet, newValue: HashSet.HashSet ): Differ.HashSet.Patch => { const [removed, patch] = HashSet.reduce( [oldValue, empty()] as const, ([set, patch], value: Value) => { if (HashSet.has(value)(set)) { return [HashSet.remove(value)(set), patch] as const } return [set, combine(makeAdd(value))(patch)] as const } )(newValue) return HashSet.reduce(patch, (patch, value: Value) => combine(makeRemove(value))(patch))(removed) } /** @internal */ export const combine = Dual.dual< ( that: Differ.HashSet.Patch ) => ( self: Differ.HashSet.Patch ) => Differ.HashSet.Patch, ( self: Differ.HashSet.Patch, that: Differ.HashSet.Patch ) => Differ.HashSet.Patch >(2, (self, that) => makeAndThen(self, that)) /** @internal */ export const patch = Dual.dual< ( oldValue: HashSet.HashSet ) => ( self: Differ.HashSet.Patch ) => HashSet.HashSet, ( self: Differ.HashSet.Patch, oldValue: HashSet.HashSet ) => HashSet.HashSet >(2, ( self: Differ.HashSet.Patch, oldValue: HashSet.HashSet ) => { let set = oldValue let patches: Chunk.Chunk> = Chunk.of(self) 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 "Add": { set = HashSet.add(head.value)(set) patches = tail break } case "Remove": { set = HashSet.remove(head.value)(set) patches = tail } } } return set })