<template>
    <slot />
    <div ref="resizeListener" class="resize-listener" />
    <svg v-if="validLinks" v-bind="$attrs">
        <LinkVue
            v-for="(link, index) in validLinks"
            :key="index"
            :link="link"
            :startNode="nodes[link.startNode.id]!"
            :endNode="nodes[link.endNode.id]!"
        />
    </svg>
</template>

<script setup lang="ts">
import {
    Link,
    LinkNode,
    NodesInjectionKey,
    RegisterLinkNodeInjectionKey,
    UnregisterLinkNodeInjectionKey
} from "features/links";
import { computed, nextTick, onMounted, provide, ref, toRef } from "vue";
import LinkVue from "./Link.vue";

const _props = defineProps<{ links?: Link[] }>();
const links = toRef(_props, "links");

const observer = new MutationObserver(updateNodes);
const resizeObserver = new ResizeObserver(updateNodes);

const nodes = ref<Record<string, LinkNode | undefined>>({});

defineExpose({ nodes });

const resizeListener = ref<Element | null>(null);

onMounted(() => {
    // ResizeListener exists because ResizeObserver's don't work when told to observe an SVG element
    const resListener = resizeListener.value;
    if (resListener != null) {
        resizeObserver.observe(resListener);
    }
});

const validLinks = computed(
    () =>
        links.value?.filter(link => {
            const n = nodes.value;
            return (
                n[link.startNode.id]?.x != undefined &&
                n[link.startNode.id]?.y != undefined &&
                n[link.endNode.id]?.x != undefined &&
                n[link.endNode.id]?.y != undefined
            );
        }) ?? []
);

const observerOptions = {
    attributes: true,
    childList: true,
    subtree: false
};

provide(RegisterLinkNodeInjectionKey, (id, element) => {
    nodes.value[id] = { element };
    observer.observe(element, observerOptions);
    nextTick(() => {
        if (resizeListener.value != null) {
            updateNode(id);
        }
    });
});
provide(UnregisterLinkNodeInjectionKey, id => {
    nodes.value[id] = undefined;
});
provide(NodesInjectionKey, nodes);

let isDirty = true;
let boundingRect = resizeListener.value?.getBoundingClientRect();
function updateNodes() {
    if (resizeListener.value != null && isDirty) {
        isDirty = false;
        nextTick(() => {
            boundingRect = resizeListener.value?.getBoundingClientRect();
            Object.keys(nodes.value).forEach(id => updateNode(id));
            isDirty = true
        });
    }
}

function updateNode(id: string) {
    const node = nodes.value[id];
    if (!node || boundingRect == null) {
        return;
    }
    const nodeRect = node.element.getBoundingClientRect();

    node.y = nodeRect.x + nodeRect.width / 2 - boundingRect.x;
    node.x = nodeRect.y + nodeRect.height / 2 - boundingRect.y;
    node.rect = nodeRect;
}
</script>

<style scoped>
svg,
.resize-listener {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    z-index: -10;
    pointer-events: none;
}
</style>