import { computed, reactive, ref, shallowRef } from "vue";
import { Cause, Effect, Exit } from "effect";
export function useTreeState(opts) {
  const expanded = reactive(new Set(opts.initialExpanded()));
  const selected = reactive(new Set(opts.initialSelection()));
  const loadState = reactive(/* @__PURE__ */ new Map());
  const requested = reactive(/* @__PURE__ */ new Set());
  const focusedKey = ref(null);
  const rootsView = shallowRef(opts.rootsRef.value);
  /* @__PURE__ */ ((roots) => {
    void roots;
  })(opts.rootsRef);
  function syncRoots() {
    rootsView.value = opts.rootsRef.value;
  }
  function locate(key) {
    const stack = [
      { siblings: rootsView.value, parent: null, depth: 0 }
    ];
    while (stack.length) {
      const frame = stack.pop();
      for (let i = 0; i < frame.siblings.length; i++) {
        const n = frame.siblings[i];
        if (opts.getKey(n) === key) {
          return {
            node: n,
            parent: frame.parent,
            depth: frame.depth,
            index: i,
            siblings: frame.siblings
          };
        }
        const kids = resolvedChildren(n);
        if (kids && kids.length) {
          stack.push({ siblings: kids, parent: n, depth: frame.depth + 1 });
        }
      }
    }
    return null;
  }
  function resolvedChildren(node) {
    const k = opts.getKey(node);
    const cached = loadState.get(k);
    if (cached?.status === "loaded") return cached.children;
    const inline = opts.getChildren?.(node);
    if (inline === void 0) {
      if (opts.loadChildren) return null;
      return void 0;
    }
    if (inline.length > 0) return inline;
    if (opts.loadChildren) return null;
    return inline;
  }
  function isExpandable(node) {
    const cached = loadState.get(opts.getKey(node));
    if (cached?.status === "loaded" && cached.children.length > 0) return true;
    const inline = opts.getChildren?.(node);
    if (inline === void 0) return false;
    if (inline.length > 0) return true;
    return Boolean(opts.loadChildren);
  }
  function ensureLoaded(node) {
    const loader = opts.loadChildren;
    if (!loader) return;
    const inline = opts.getChildren?.(node);
    if (!(inline !== void 0 && inline.length === 0)) return;
    const key = opts.getKey(node);
    if (requested.has(key)) return;
    const prior = loadState.get(key);
    if (prior?.status === "loaded") return;
    requested.add(key);
    loadState.set(key, { status: "loading" });
    Effect.runPromiseExit(loader(node)).then((exit) => {
      Exit.match(exit, {
        onSuccess: (children) => {
          loadState.set(key, { status: "loaded", children });
        },
        onFailure: (cause) => {
          const failure = Cause.failureOption(cause);
          const error = failure._tag === "Some" ? failure.value : cause;
          loadState.set(key, { status: "error", error });
        }
      });
    });
  }
  function retry(key) {
    requested.delete(key);
    loadState.delete(key);
    const loc = locate(key);
    if (loc) ensureLoaded(loc.node);
  }
  function invalidate(key) {
    requested.delete(key);
    loadState.delete(key);
  }
  function expand(key) {
    const loc = locate(key);
    if (!loc) return;
    if (!isExpandable(loc.node)) return;
    expanded.add(key);
    ensureLoaded(loc.node);
  }
  function collapse(key) {
    expanded.delete(key);
  }
  function toggleExpand(key) {
    if (expanded.has(key)) collapse(key);
    else expand(key);
  }
  function emitSelection() {
    opts.onSelectionChange(Array.from(selected));
  }
  function selectSingle(key) {
    selected.clear();
    selected.add(key);
    emitSelection();
  }
  function clearSelection() {
    if (selected.size === 0) return;
    selected.clear();
    emitSelection();
  }
  function walkSubtree(node, visit, depth = 0) {
    visit(node, depth);
    const kids = resolvedChildren(node);
    if (kids && kids.length > 0) {
      for (const c of kids) walkSubtree(c, visit, depth + 1);
    }
  }
  function cascadeToggle(node, depth, on) {
    walkSubtree(node, (n, d) => {
      if (!opts.selectable(n, depth + d)) return;
      const k = opts.getKey(n);
      if (on) selected.add(k);
      else selected.delete(k);
    });
  }
  function toggleMulti(key) {
    const loc = locate(key);
    if (!loc) return;
    if (!opts.selectable(loc.node, loc.depth)) return;
    const mode = opts.cascade();
    const turningOn = !selected.has(key);
    if (mode === "independent") {
      if (turningOn) selected.add(key);
      else selected.delete(key);
    } else {
      cascadeToggle(loc.node, loc.depth, turningOn);
    }
    emitSelection();
  }
  function selectKey(key) {
    const mode = opts.selectionMode();
    if (mode === "none") return;
    if (mode === "single") {
      const loc = locate(key);
      if (!loc || !opts.selectable(loc.node, loc.depth)) return;
      selectSingle(key);
      return;
    }
    if (selected.has(key)) return;
    toggleMulti(key);
  }
  function deselectKey(key) {
    const mode = opts.selectionMode();
    if (mode === "none") return;
    if (mode === "single") {
      if (!selected.has(key)) return;
      selected.delete(key);
      emitSelection();
      return;
    }
    if (!selected.has(key)) return;
    toggleMulti(key);
  }
  function descendantSelectionStats(node, depth) {
    let total = 0;
    let selectedCount = 0;
    const kids = resolvedChildren(node);
    if (!kids || kids.length === 0) return { total, selectedCount };
    for (const c of kids) {
      walkSubtree(c, (n, d) => {
        if (!opts.selectable(n, depth + 1 + d)) return;
        total++;
        if (selected.has(opts.getKey(n))) selectedCount++;
      });
    }
    return { selectedCount, total };
  }
  function isIndeterminate(node, depth) {
    if (opts.selectionMode() !== "multi") return false;
    const { selectedCount, total } = descendantSelectionStats(node, depth);
    if (total === 0) return false;
    return selectedCount > 0 && selectedCount < total;
  }
  function isVisuallySelected(node, depth) {
    const k = opts.getKey(node);
    if (selected.has(k)) return true;
    if (opts.selectionMode() !== "multi") return false;
    const { selectedCount, total } = descendantSelectionStats(node, depth);
    return total > 0 && selectedCount === total;
  }
  function withMutation(next) {
    opts.setRoots(next);
    rootsView.value = next;
  }
  function mutateSiblings(parentKey, op) {
    if (parentKey === null) {
      const next = rootsView.value.slice();
      op(next);
      withMutation(next);
      return true;
    }
    const parentLoc = locate(parentKey);
    if (!parentLoc) return false;
    const inline = opts.getChildren?.(parentLoc.node);
    if (inline !== void 0) {
      op(inline);
    } else {
      const ls = loadState.get(parentKey);
      if (ls?.status === "loaded") {
        op(ls.children);
      } else {
        const arr = [];
        op(arr);
        loadState.set(parentKey, { status: "loaded", children: arr });
        requested.add(parentKey);
      }
    }
    withMutation(rootsView.value.slice());
    return true;
  }
  function insert(parentKey, node, index) {
    mutateSiblings(parentKey, (siblings) => {
      const at = index ?? siblings.length;
      siblings.splice(Math.max(0, Math.min(at, siblings.length)), 0, node);
    });
  }
  function remove(key) {
    const loc = locate(key);
    if (!loc) return;
    loc.siblings.splice(loc.index, 1);
    selected.delete(key);
    expanded.delete(key);
    loadState.delete(key);
    requested.delete(key);
    withMutation(rootsView.value.slice());
  }
  function move(key, newParentKey, index) {
    const loc = locate(key);
    if (!loc) return;
    const [node] = loc.siblings.splice(loc.index, 1);
    if (!node) return;
    if (newParentKey === null) {
      const next = rootsView.value.slice();
      const at = Math.max(0, Math.min(index, next.length));
      next.splice(at, 0, node);
      withMutation(next);
      return;
    }
    const parentLoc = locate(newParentKey);
    if (!parentLoc) {
      loc.siblings.splice(loc.index, 0, node);
      return;
    }
    const inline = opts.getChildren?.(parentLoc.node);
    if (inline !== void 0) {
      const at = Math.max(0, Math.min(index, inline.length));
      inline.splice(at, 0, node);
    } else {
      const ls = loadState.get(newParentKey);
      if (ls?.status === "loaded") {
        const at = Math.max(0, Math.min(index, ls.children.length));
        ls.children.splice(at, 0, node);
      } else {
        const arr = [node];
        loadState.set(newParentKey, { status: "loaded", children: arr });
        requested.add(newParentKey);
      }
    }
    withMutation(rootsView.value.slice());
  }
  function update(key, patch) {
    const loc = locate(key);
    if (!loc) return;
    Object.assign(loc.node, patch);
    withMutation(rootsView.value.slice());
  }
  function flattenVisible(filter) {
    const out = [];
    function walk(siblings, depth) {
      for (const n of siblings) {
        if (!filter || filter(n)) {
          out.push({ node: n, depth, key: opts.getKey(n) });
        }
        const kids = resolvedChildren(n);
        if (kids && kids.length > 0 && expanded.has(opts.getKey(n))) {
          walk(kids, depth + 1);
        }
      }
    }
    walk(rootsView.value, 0);
    return out;
  }
  function findPath(key) {
    const trail = [];
    function walk(siblings) {
      for (const n of siblings) {
        trail.push(n);
        if (opts.getKey(n) === key) return true;
        const kids = resolvedChildren(n);
        if (kids && kids.length && walk(kids)) return true;
        trail.pop();
      }
      return false;
    }
    return walk(rootsView.value) ? trail.slice() : null;
  }
  function snapshot() {
    return {
      roots: rootsView.value.slice(),
      selected: Array.from(selected),
      expanded: Array.from(expanded)
    };
  }
  const selectedNodes = computed(() => {
    const out = [];
    for (const k of selected) {
      const loc = locate(k);
      if (loc) out.push(loc.node);
    }
    return out;
  });
  return {
    // state (reactive)
    expanded,
    selected,
    loadState,
    focusedKey,
    rootsView,
    // queries
    locate,
    resolvedChildren,
    isExpandable,
    isIndeterminate,
    isVisuallySelected,
    flattenVisible,
    selectedNodes,
    // mutations / commands
    syncRoots,
    expand,
    collapse,
    toggleExpand,
    selectKey,
    deselectKey,
    clearSelection,
    toggleMulti,
    insert,
    remove,
    move,
    update,
    invalidate,
    retry,
    findPath,
    snapshot
  };
}
