/* eslint-disable */
// Adapted from Alien Signals
// https://github.com/stackblitz/alien-signals/

export interface ReactiveNode {
  deps?: Link
  depsTail?: Link
  subs?: Link
  subsTail?: Link
  flags: ReactiveFlags
}

export interface Link {
  version: number
  dep: ReactiveNode
  sub: ReactiveNode
  prevSub: Link | undefined
  nextSub: Link | undefined
  prevDep: Link | undefined
  nextDep: Link | undefined
}

interface Stack<T> {
  value: T
  prev: Stack<T> | undefined
}

export const enum ReactiveFlags {
  None = 0,
  Mutable = 1,
  Watching = 2,
  RecursedCheck = 4,
  Recursed = 8,
  Dirty = 16,
  Pending = 32,
}
/*@__NO_SIDE_EFFECTS__*/
export function createReactiveSystem({
  update,
  notify,
  unwatched,
}: {
  update(sub: ReactiveNode): boolean
  notify(sub: ReactiveNode): void
  unwatched(sub: ReactiveNode): void
}) {
  return {
    link,
    unlink,
    propagate,
    checkDirty,
    shallowPropagate,
  }

  function link(dep: ReactiveNode, sub: ReactiveNode, version: number): void {
    const prevDep = sub.depsTail
    if (prevDep !== undefined && prevDep.dep === dep) {
      return
    }
    const nextDep = prevDep !== undefined ? prevDep.nextDep : sub.deps
    if (nextDep !== undefined && nextDep.dep === dep) {
      nextDep.version = version
      sub.depsTail = nextDep
      return
    }
    const prevSub = dep.subsTail
    if (
      prevSub !== undefined &&
      prevSub.version === version &&
      prevSub.sub === sub
    ) {
      return
    }
    const newLink =
      (sub.depsTail =
      dep.subsTail =
        {
          version,
          dep,
          sub,
          prevDep,
          nextDep,
          prevSub,
          nextSub: undefined,
        })
    if (nextDep !== undefined) {
      nextDep.prevDep = newLink
    }
    if (prevDep !== undefined) {
      prevDep.nextDep = newLink
    } else {
      sub.deps = newLink
    }
    if (prevSub !== undefined) {
      prevSub.nextSub = newLink
    } else {
      dep.subs = newLink
    }
  }

  function unlink(link: Link, sub = link.sub): Link | undefined {
    const dep = link.dep
    const prevDep = link.prevDep
    const nextDep = link.nextDep
    const nextSub = link.nextSub
    const prevSub = link.prevSub
    if (nextDep !== undefined) {
      nextDep.prevDep = prevDep
    } else {
      sub.depsTail = prevDep
    }
    if (prevDep !== undefined) {
      prevDep.nextDep = nextDep
    } else {
      sub.deps = nextDep
    }
    if (nextSub !== undefined) {
      nextSub.prevSub = prevSub
    } else {
      dep.subsTail = prevSub
    }
    if (prevSub !== undefined) {
      prevSub.nextSub = nextSub
    } else if ((dep.subs = nextSub) === undefined) {
      unwatched(dep)
    }
    return nextDep
  }

  function propagate(link: Link): void {
    let next = link.nextSub
    let stack: Stack<Link | undefined> | undefined

    top: do {
      const sub = link.sub
      let flags = sub.flags

      if (
        !(
          flags &
          (ReactiveFlags.RecursedCheck |
            ReactiveFlags.Recursed |
            ReactiveFlags.Dirty |
            ReactiveFlags.Pending)
        )
      ) {
        sub.flags = flags | ReactiveFlags.Pending
      } else if (
        !(flags & (ReactiveFlags.RecursedCheck | ReactiveFlags.Recursed))
      ) {
        flags = ReactiveFlags.None
      } else if (!(flags & ReactiveFlags.RecursedCheck)) {
        sub.flags = (flags & ~ReactiveFlags.Recursed) | ReactiveFlags.Pending
      } else if (
        !(flags & (ReactiveFlags.Dirty | ReactiveFlags.Pending)) &&
        isValidLink(link, sub)
      ) {
        sub.flags = flags | (ReactiveFlags.Recursed | ReactiveFlags.Pending)
        flags &= ReactiveFlags.Mutable
      } else {
        flags = ReactiveFlags.None
      }

      if (flags & ReactiveFlags.Watching) {
        notify(sub)
      }

      if (flags & ReactiveFlags.Mutable) {
        const subSubs = sub.subs
        if (subSubs !== undefined) {
          const nextSub = (link = subSubs).nextSub
          if (nextSub !== undefined) {
            stack = { value: next, prev: stack }
            next = nextSub
          }
          continue
        }
      }

      if ((link = next!) !== undefined) {
        next = link.nextSub
        continue
      }

      while (stack !== undefined) {
        link = stack.value!
        stack = stack.prev
        if (link !== undefined) {
          next = link.nextSub
          continue top
        }
      }

      break
    } while (true)
  }

  function checkDirty(link: Link, sub: ReactiveNode): boolean {
    let stack: Stack<Link> | undefined
    let checkDepth = 0
    let dirty = false

    top: do {
      const dep = link.dep
      const flags = dep.flags

      if (sub.flags & ReactiveFlags.Dirty) {
        dirty = true
      } else if (
        (flags & (ReactiveFlags.Mutable | ReactiveFlags.Dirty)) ===
        (ReactiveFlags.Mutable | ReactiveFlags.Dirty)
      ) {
        if (update(dep)) {
          const subs = dep.subs!
          if (subs.nextSub !== undefined) {
            shallowPropagate(subs)
          }
          dirty = true
        }
      } else if (
        (flags & (ReactiveFlags.Mutable | ReactiveFlags.Pending)) ===
        (ReactiveFlags.Mutable | ReactiveFlags.Pending)
      ) {
        if (link.nextSub !== undefined || link.prevSub !== undefined) {
          stack = { value: link, prev: stack }
        }
        link = dep.deps!
        sub = dep
        ++checkDepth
        continue
      }

      if (!dirty) {
        const nextDep = link.nextDep
        if (nextDep !== undefined) {
          link = nextDep
          continue
        }
      }

      while (checkDepth--) {
        const firstSub = sub.subs!
        const hasMultipleSubs = firstSub.nextSub !== undefined
        if (hasMultipleSubs) {
          link = stack!.value
          stack = stack!.prev
        } else {
          link = firstSub
        }
        if (dirty) {
          if (update(sub)) {
            if (hasMultipleSubs) {
              shallowPropagate(firstSub)
            }
            sub = link.sub
            continue
          }
          dirty = false
        } else {
          sub.flags &= ~ReactiveFlags.Pending
        }
        sub = link.sub
        const nextDep = link.nextDep
        if (nextDep !== undefined) {
          link = nextDep
          continue top
        }
      }

      return dirty
    } while (true)
  }

  function shallowPropagate(link: Link): void {
    do {
      const sub = link.sub
      const flags = sub.flags
      if (
        (flags & (ReactiveFlags.Pending | ReactiveFlags.Dirty)) ===
        ReactiveFlags.Pending
      ) {
        sub.flags = flags | ReactiveFlags.Dirty
        if (
          (flags & (ReactiveFlags.Watching | ReactiveFlags.RecursedCheck)) ===
          ReactiveFlags.Watching
        ) {
          notify(sub)
        }
      }
    } while ((link = link.nextSub!) !== undefined)
  }

  function isValidLink(checkLink: Link, sub: ReactiveNode): boolean {
    let link = sub.depsTail
    while (link !== undefined) {
      if (link === checkLink) {
        return true
      }
      link = link.prevDep
    }
    return false
  }
}
