import { createStore, produce } from "solid-js/store"

export type SessionScroll = {
  x: number
  y: number
}

type ScrollMap = Record<string, SessionScroll>

type Options = {
  debounceMs?: number
  getSnapshot: (sessionKey: string) => ScrollMap | undefined
  onFlush: (sessionKey: string, scroll: ScrollMap) => void
}

export function createScrollPersistence(opts: Options) {
  const wait = opts.debounceMs ?? 200
  const [cache, setCache] = createStore<Record<string, ScrollMap>>({})
  const dirty = new Set<string>()
  const timers = new Map<string, ReturnType<typeof setTimeout>>()

  function clone(input?: ScrollMap) {
    const out: ScrollMap = {}
    if (!input) return out

    for (const key of Object.keys(input)) {
      const pos = input[key]
      if (!pos) continue
      out[key] = { x: pos.x, y: pos.y }
    }

    return out
  }

  function seed(sessionKey: string) {
    const next = clone(opts.getSnapshot(sessionKey))
    const current = cache[sessionKey]
    if (!current) {
      setCache(sessionKey, next)
      return
    }

    if (Object.keys(current).length > 0) return
    if (Object.keys(next).length === 0) return
    setCache(sessionKey, next)
  }

  function scroll(sessionKey: string, tab: string) {
    seed(sessionKey)
    return cache[sessionKey]?.[tab] ?? opts.getSnapshot(sessionKey)?.[tab]
  }

  function schedule(sessionKey: string) {
    const prev = timers.get(sessionKey)
    if (prev) clearTimeout(prev)
    timers.set(
      sessionKey,
      setTimeout(() => flush(sessionKey), wait),
    )
  }

  function setScroll(sessionKey: string, tab: string, pos: SessionScroll) {
    seed(sessionKey)

    const prev = cache[sessionKey]?.[tab]
    if (prev?.x === pos.x && prev?.y === pos.y) return

    setCache(sessionKey, tab, { x: pos.x, y: pos.y })
    dirty.add(sessionKey)
    schedule(sessionKey)
  }

  function flush(sessionKey: string) {
    const timer = timers.get(sessionKey)
    if (timer) clearTimeout(timer)
    timers.delete(sessionKey)

    if (!dirty.has(sessionKey)) return
    dirty.delete(sessionKey)

    opts.onFlush(sessionKey, clone(cache[sessionKey]))
  }

  function flushAll() {
    const keys = Array.from(dirty)
    if (keys.length === 0) return

    for (const key of keys) {
      flush(key)
    }
  }

  function drop(keys: string[]) {
    if (keys.length === 0) return

    for (const key of keys) {
      const timer = timers.get(key)
      if (timer) clearTimeout(timer)
      timers.delete(key)
      dirty.delete(key)
    }

    setCache(
      produce((draft) => {
        for (const key of keys) {
          delete draft[key]
        }
      }),
    )
  }

  function dispose() {
    drop(Array.from(timers.keys()))
  }

  return {
    cache,
    drop,
    flush,
    flushAll,
    scroll,
    seed,
    setScroll,
    dispose,
  }
}
