<template>
	<div v-frag>
		<slot />
		<div ref="resizeListener" class="resize-listener" />
		<svg>
			<branch-line v-for="(branch, index) in branches" :key="index"
				:startNode="nodes[branch.start]" :endNode="nodes[branch.end]" :options="branch.options" />
		</svg>
	</div>
</template>

<script>
import Vue from 'vue';

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

export default {
	name: 'branches',
	data() {
		return {
			observer: new MutationObserver(this.updateNodes),
			resizeObserver: new ResizeObserver(this.updateNodes),
			nodes: {},
			links: []
		};
	},
	mounted() {
		// ResizeListener exists because ResizeObserver's don't work when told to observe an SVG element
		this.resizeObserver.observe(this.$refs.resizeListener);
		this.updateNodes();
	},
	provide() {
		return {
			registerNode: this.registerNode,
			unregisterNode: this.unregisterNode,
			registerBranch: this.registerBranch,
			unregisterBranch: this.unregisterBranch
		};
	},
	computed: {
		branches() {
			return this.links.filter(link => link.start in this.nodes && link.end in this.nodes &&
				this.nodes[link.start].x != undefined && this.nodes[link.start].y != undefined &&
				this.nodes[link.end].x != undefined && this.nodes[link.end].y != undefined);
		}
	},
	methods: {
		updateNodes() {
			if (this.$refs.resizeListener != undefined) {
				const containerRect = this.$refs.resizeListener.getBoundingClientRect();
				Object.keys(this.nodes).forEach(id => this.updateNode(id, containerRect));
			}
		},
		updateNode(id, containerRect) {
			const linkStartRect = this.nodes[id].element.getBoundingClientRect();
			Vue.set(this.nodes[id], 'x', linkStartRect.x + linkStartRect.width / 2 - containerRect.x);
			Vue.set(this.nodes[id], 'y', linkStartRect.y + linkStartRect.height / 2 - containerRect.y);
		},
		registerNode(id, component) {
			const element = component.$el.parentElement;
			Vue.set(this.nodes, id, { component, element });
			this.observer.observe(element, observerOptions);
			this.$nextTick(() => {
				if (this.$refs.resizeListener != undefined) {
					this.updateNode(id, this.$refs.resizeListener.getBoundingClientRect());
				}
			});
		},
		unregisterNode(id) {
			Vue.delete(this.nodes, id);
		},
		registerBranch(start, options) {
			const end = typeof options === 'string' ? options : options.target;
			this.links.push({ start, end, options });
			Vue.set(this, 'links', this.links);
		},
		unregisterBranch(start, options) {
			const index = this.links.findIndex(l => l.start === start && l.options === options);
			this.links.splice(index, 1);
			Vue.set(this, 'links', this.links);
		}
	}
};
</script>

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