import { onBeforeUnmount, ref } from "vue";
import { combine } from "@atlaskit/pragmatic-drag-and-drop/combine";
import {
  draggable,
  dropTargetForElements
} from "@atlaskit/pragmatic-drag-and-drop/element/adapter";
import {
  attachInstruction,
  extractInstruction
} from "@atlaskit/pragmatic-drag-and-drop-hitbox/tree-item";
const CUSTOM_INSTRUCTION_KEY = "__shwfedCustomInstruction__";
function toHTMLElement(el) {
  return el instanceof HTMLElement ? el : null;
}
function readInstructionType(data) {
  if (CUSTOM_INSTRUCTION_KEY in data) {
    const v = data[CUSTOM_INSTRUCTION_KEY];
    return typeof v === "string" ? v : null;
  }
  const inst = extractInstruction(data);
  return inst && inst.type !== "instruction-blocked" ? inst.type : null;
}
function toSource(data) {
  const kind = data.kind;
  const id = data.id;
  if (typeof kind !== "string" || typeof id !== "string") return null;
  return { kind, id, data };
}
export function useTreeDnd(options) {
  const rowInstruction = ref(null);
  const activeZoneId = ref(null);
  const rowRegs = /* @__PURE__ */ new Map();
  const zoneRegs = /* @__PURE__ */ new Map();
  const rowConfigs = /* @__PURE__ */ new Map();
  const zoneConfigs = /* @__PURE__ */ new Map();
  const rowRefCache = /* @__PURE__ */ new Map();
  const zoneRefCache = /* @__PURE__ */ new Map();
  function wireRow(key, id, el) {
    return combine(
      draggable({
        element: el,
        dragHandle: (() => {
          const cfg = rowConfigs.get(key);
          return cfg?.dragHandle?.(el) ?? void 0;
        })(),
        getInitialData: () => {
          const cfg = rowConfigs.get(key);
          return { kind: cfg?.kind ?? "row", id, ...cfg?.data ?? {} };
        }
      }),
      dropTargetForElements({
        element: el,
        canDrop: ({ source }) => {
          const src = toSource(source.data);
          if (!src) return false;
          const cfg = rowConfigs.get(key);
          return cfg?.canDrop ? cfg.canDrop(src) : true;
        },
        getIsSticky: () => true,
        getData: ({ input, element, source }) => {
          const cfg = rowConfigs.get(key);
          const src = toSource(source.data);
          const block = src && cfg?.blockInstructions ? cfg.blockInstructions(src) : [];
          const base = { kind: `${cfg?.kind ?? "row"}-target`, id };
          if (cfg?.resolveInstruction && src) {
            let type = cfg.resolveInstruction({ input, element, source: src });
            if (type && block.includes(type)) type = null;
            return { ...base, [CUSTOM_INSTRUCTION_KEY]: type };
          }
          return attachInstruction(
            base,
            {
              input,
              element,
              currentLevel: 0,
              indentPerLevel: 0,
              mode: "standard",
              block
            }
          );
        },
        onDrag: ({ self }) => {
          const type = readInstructionType(self.data);
          rowInstruction.value = type ? { id, type } : null;
        },
        onDragLeave: () => {
          if (rowInstruction.value?.id === id) rowInstruction.value = null;
        },
        onDrop: ({ source, self }) => {
          rowInstruction.value = null;
          const type = readInstructionType(self.data);
          if (!type) return;
          const src = toSource(source.data);
          if (!src) return;
          const cfg = rowConfigs.get(key);
          options.onRowDrop({
            source: src,
            target: { kind: cfg?.kind ?? "row", id },
            instruction: type
          });
        }
      })
    );
  }
  function wireZone(id, el) {
    return dropTargetForElements({
      element: el,
      canDrop: ({ source }) => {
        const src = toSource(source.data);
        if (!src) return false;
        const cfg = zoneConfigs.get(id);
        return cfg?.canDrop ? cfg.canDrop(src) : true;
      },
      getIsSticky: () => true,
      getData: () => {
        const cfg = zoneConfigs.get(id);
        return { kind: "zone", id, ...cfg?.data ?? {} };
      },
      onDragEnter: () => {
        activeZoneId.value = id;
      },
      onDragLeave: () => {
        if (activeZoneId.value === id) activeZoneId.value = null;
      },
      onDrop: ({ source }) => {
        activeZoneId.value = null;
        const src = toSource(source.data);
        if (!src) return;
        const cfg = zoneConfigs.get(id);
        options.onZoneDrop?.({
          source: src,
          zoneId: id,
          data: cfg?.data ?? {}
        });
      }
    });
  }
  function setRow(key, id, el) {
    const existing = rowRegs.get(key);
    if (!el) {
      if (existing) {
        existing.cleanup();
        rowRegs.delete(key);
        if (rowInstruction.value?.id === id) rowInstruction.value = null;
      }
      return;
    }
    if (existing && existing.el === el) return;
    existing?.cleanup();
    const cfg = rowConfigs.get(key);
    if (!cfg) return;
    rowRegs.set(key, { el, config: cfg, cleanup: wireRow(key, id, el) });
  }
  function setZone(id, el) {
    const existing = zoneRegs.get(id);
    if (!el) {
      if (existing) {
        existing.cleanup();
        zoneRegs.delete(id);
        if (activeZoneId.value === id) activeZoneId.value = null;
      }
      return;
    }
    if (existing && existing.el === el) return;
    existing?.cleanup();
    const cfg = zoneConfigs.get(id);
    if (!cfg) return;
    zoneRegs.set(id, { el, config: cfg, cleanup: wireZone(id, el) });
  }
  function rowRef(id, config) {
    const key = `${config.kind}:${id}`;
    rowConfigs.set(key, config);
    let cached = rowRefCache.get(key);
    if (!cached) {
      cached = (el) => setRow(key, id, toHTMLElement(el));
      rowRefCache.set(key, cached);
    }
    return cached;
  }
  function zoneRef(id, config) {
    zoneConfigs.set(id, config);
    let cached = zoneRefCache.get(id);
    if (!cached) {
      cached = (el) => setZone(id, toHTMLElement(el));
      zoneRefCache.set(id, cached);
    }
    return cached;
  }
  function instructionFor(id) {
    return rowInstruction.value?.id === id ? rowInstruction.value.type : null;
  }
  function isZoneActive(id) {
    return activeZoneId.value === id;
  }
  onBeforeUnmount(() => {
    for (const reg of rowRegs.values()) reg.cleanup();
    for (const reg of zoneRegs.values()) reg.cleanup();
    rowRegs.clear();
    zoneRegs.clear();
    rowConfigs.clear();
    zoneConfigs.clear();
    rowRefCache.clear();
    zoneRefCache.clear();
  });
  return {
    rowRef,
    zoneRef,
    instructionFor,
    isZoneActive
  };
}
