import { watchDebounced } from "@vueuse/core";
import { nanoid } from "nanoid";
import { computed, ref, reactive, onBeforeUnmount, nextTick } from "vue";
import { getSchemaParamsFromId } from "./id-routing.js";
const priorityQueue = reactive(/* @__PURE__ */ new Set());
const pendingQueue = reactive(/* @__PURE__ */ new Set());
const readyQueue = reactive(/* @__PURE__ */ new Set());
const isRunning = ref(false);
const firstLazyLoadComplete = ref(false);
const intersectionBlockers = reactive(/* @__PURE__ */ new Set());
const onRenderComplete = /* @__PURE__ */ new Set();
const addLazyCompleteCallback = (callback) => {
  if (callback) {
    onRenderComplete.add(callback);
  }
};
const blockIntersection = () => {
  const blockId = nanoid();
  intersectionBlockers.add(blockId);
  return () => setTimeout(() => intersectionBlockers.delete(blockId), 100);
};
const intersectionEnabled = computed(() => intersectionBlockers.size === 0);
const runLazyBus = () => {
  if (typeof window === "undefined") {
    return;
  }
  const unblock = blockIntersection();
  const processQueue = async () => {
    if (pendingQueue.size > 0 || priorityQueue.size > 0) {
      isRunning.value = true;
      for (const id of [...pendingQueue, ...priorityQueue]) {
        readyQueue.add(id);
        pendingQueue.delete(id);
        priorityQueue.delete(id);
      }
    }
    await nextTick();
    onRenderComplete.forEach((fn) => fn());
    onRenderComplete.clear();
    unblock();
    isRunning.value = false;
    firstLazyLoadComplete.value = true;
  };
  if (window.requestIdleCallback) {
    window.requestIdleCallback(processQueue, { timeout: 1500 });
  } else {
    nextTick(processQueue);
  }
};
watchDebounced(
  [() => pendingQueue.size, () => priorityQueue.size, () => isRunning.value],
  () => {
    if ((pendingQueue.size > 0 || priorityQueue.size > 0) && !isRunning.value) {
      runLazyBus();
    }
  },
  { debounce: 300, maxWait: 1500 }
);
const addToPendingQueue = (id) => {
  if (!!id && !priorityQueue.has(id)) {
    pendingQueue.add(id);
  }
};
const addToPriorityQueue = (id) => {
  if (id) {
    priorityQueue.add(id);
  }
};
const resetLazyElement = (id) => {
  priorityQueue.delete(id);
  pendingQueue.delete(id);
  readyQueue.delete(id);
};
function useLazyBus(id) {
  addToPendingQueue(id);
  onBeforeUnmount(() => {
    resetLazyElement(id);
  });
  return {
    isReady: computed(() => typeof window === "undefined" || priorityQueue.has(id) || readyQueue.has(id))
  };
}
const scrollToLazy = (id, setExpanded, getEntryById) => {
  const item = getEntryById(id);
  const isLazy = !readyQueue.has(id) || item?.children?.some((child) => !readyQueue.has(child.id));
  const unfreeze = isLazy ? freeze(id) : void 0;
  addLazyCompleteCallback(unfreeze);
  const unblock = blockIntersection();
  const { rawId } = getSchemaParamsFromId(id);
  addToPriorityQueue(id);
  addToPriorityQueue(rawId);
  if (item?.children) {
    item.children.slice(0, 2).forEach((child) => {
      addToPriorityQueue(child.id);
    });
  }
  if (item?.parent) {
    const parent = getEntryById(item.parent.id);
    const elementIdx = parent?.children?.findIndex((child) => child.id === id);
    if (elementIdx !== void 0 && elementIdx >= 0) {
      parent?.children?.slice(elementIdx, elementIdx + 2).forEach((child) => {
        addToPriorityQueue(child.id);
      });
    }
  }
  tryScroll(id, Date.now() + 1e3, unblock, unfreeze);
  setExpanded(rawId, true);
  const addParents = (currentId) => {
    const parent = getEntryById(currentId)?.parent;
    if (parent) {
      addToPriorityQueue(parent.id);
      setExpanded(parent.id, true);
      addParents(parent.id);
    }
  };
  addParents(rawId);
};
const tryScroll = (id, stopTime, onComplete, onFailure) => {
  const element = document.getElementById(id);
  if (element) {
    element.scrollIntoView({
      block: "start"
    });
    onComplete();
  } else if (Date.now() < stopTime) {
    requestAnimationFrame(() => tryScroll(id, stopTime, onComplete));
  } else {
    onComplete();
    onFailure?.();
  }
};
const freeze = (id) => {
  let stop = false;
  const runFrame = (stopAfterFrame) => {
    const element = document.getElementById(id);
    if (element) {
      element.scrollIntoView({
        block: "start"
      });
    }
    if (!stopAfterFrame) {
      requestAnimationFrame(() => runFrame(stop));
    }
  };
  runFrame(false);
  return () => {
    stop = true;
  };
};
export {
  blockIntersection,
  firstLazyLoadComplete,
  intersectionEnabled,
  scrollToLazy,
  useLazyBus
};
