// Shared accordion controller.
//
// The program / workshop editors are built from many independent section
// components, each rendering its own single-panel <VExpansionPanels>. They used
// to coordinate through a single shared string ref ("basic-0", "details-3", …),
// which by design only ever kept ONE section open at a time.
//
// This controller replaces that string with a reactive *set* of open keys plus a
// registry of every known key, so sections can be opened independently and an
// "Expand all / Collapse all" toggle can operate on the whole group.
import { computed, inject, onUnmounted, reactive } from "vue";

export function createAccordionGroup(initialKeys = []) {
  const seed = Array.isArray(initialKeys)
    ? initialKeys
    : initialKeys != null
      ? [initialKeys]
      : [];
  const open = reactive(new Set(seed));
  const known = reactive(new Set());

  return {
    open,
    known,
    isOpen: (key) => open.has(key),
    // Raw setter — does not touch other keys. Used for bulk syncing.
    setOpen(key, value) {
      if (value) open.add(key);
      else open.delete(key);
    },
    // Open `key` and close everything else. This is what the panel bindings use,
    // so opening a section collapses whichever one was already open.
    openExclusive(key) {
      open.clear();
      open.add(key);
    },
    register(key) {
      known.add(key);
    },
    unregister(key) {
      known.delete(key);
    },
    expandAll() {
      known.forEach((key) => open.add(key));
    },
    collapseAll() {
      open.clear();
    },
    // True once every registered (currently rendered) section is open.
    allExpanded: computed(
      () => known.size > 0 && [...known].every((key) => open.has(key)),
    ),
    anyOpen: computed(() => open.size > 0),
  };
}

// Bind ONE panel to a group. Returns a computed suitable for a single-panel
// <VExpansionPanels v-model> (value 0 = open, null = closed) and auto
// registers/unregisters the key for "expand all".
export function usePanelIn(group, key) {
  group.register(key);
  onUnmounted(() => group.unregister(key));
  return computed({
    get: () => (group.isOpen(key) ? 0 : null),
    set: (value) => {
      if (value !== undefined && value !== null) group.openExclusive(key);
      else group.setOpen(key, false);
    },
  });
}

// Same as usePanelIn but resolves the group from an injection key, falling back
// to a standalone group when the component is used outside a provider.
export function usePanel(injectionKey, key) {
  const group = inject(injectionKey, null) || createAccordionGroup();
  return usePanelIn(group, key);
}

// Bind a MULTI-panel <VExpansionPanels multiple> to a group. `keys` are the
// per-index panel keys; the returned computed is an array of open indices.
export function useMultiPanel(injectionKey, keys) {
  const group = inject(injectionKey, null) || createAccordionGroup();
  keys.forEach((key) => group.register(key));
  onUnmounted(() => keys.forEach((key) => group.unregister(key)));
  return computed({
    get: () =>
      keys.reduce((indices, key, index) => {
        if (group.isOpen(key)) indices.push(index);
        return indices;
      }, []),
    set: (value) => {
      const openIndices = new Set(
        Array.isArray(value) ? value : value == null ? [] : [value],
      );

      // A newly opened panel wins outright: close everything else in the group.
      // Pure close events (and the post-"expand all" state) fall through to a
      // plain sync so we don't fight the bulk toggle.
      const opened = keys.filter(
        (key, index) => openIndices.has(index) && !group.isOpen(key),
      );
      if (opened.length) {
        group.openExclusive(opened[opened.length - 1]);

        return;
      }

      keys.forEach((key, index) => group.setOpen(key, openIndices.has(index)));
    },
  });
}
