<template>
	<div
		ref="muuriel"
		class="muuri"
		>

		<div
			class="muuri-item"
			v-for="field in value"
			:muurikey="field[muurikey]"
			:key="field[muurikey]"
			>
			<slot
				:field="field"
				/>
		</div>

	</div>
</template>

<script lang="ts">
	import {
		Component,
		Vue,
		Prop,
		PropSync,
		Ref,
		Watch,
	} from "vue-property-decorator";
	import Muuri from "muuri";
	import Options from "@/types/modules/muuri/options";

	@Component({
	})
	export default class MuuriGridVue extends Vue {
		// ┌─────────────┐
		// │    Props    │
		// └─────────────┘

		@Prop() readonly muurikey!: string;
		@Prop({ default: () => ({}) }) readonly options!: Options;


		// ┌────────────┐
		// │    Refs    │
		// └────────────┘

		@Ref() readonly muuriel!: HTMLDivElement;


		// ┌────────────────────────────┐
		// │    Value Sync (v-model)    │
		// └────────────────────────────┘

		@Prop() readonly value!: any[];
		get fields() { return this.value; }
		set fields(newVal: any[]) { this.$emit("input", newVal); }


		// ┌─────────────┐
		// │    State    │
		// └─────────────┘

		grid: Muuri | null = null;


		// ┌───────────────────────┐
		// │    Lifecycle Hooks    │
		// └───────────────────────┘

		beforeDestroy() {
			if(this.grid) {
				this.grid.destroy(false);
			}
		}


		// ┌────────────────────┐
		// │    Grid Helpers    │
		// └────────────────────┘

		@Watch("options", { immediate: true })
		async init() {
			if(this.options !== null) {
				while(!this.muuriel) await this.$nextTick();
				if(this.grid) this.grid.destroy();
				this.grid = new Muuri(this.muuriel, this.options);
				this.grid.on("move", () => this.updateData());
			}
		}

		layout() {
			if(this.grid) {
				this.grid.refreshItems();
				this.grid.layout();
			}
		}

		private getOrder() {
			if(this.grid) {
				return this.grid
					.getItems()
					.map((item) => item.getElement().getAttribute("muurikey"));
			} else return [];
		}


		// ┌─────────────────┐
		// │    Data Sync    │
		// └─────────────────┘

		updateData() {
			// If there's no grid, there's nothing to model the data after
			if(!this.grid) return;

			// Get data order by keys
			const order = this.getOrder();

			// Use keys to sort data
			this.fields
				.sort((a, b) => order.indexOf(a[this.muurikey]) - order.indexOf(b[this.muurikey]));
		}

		@Watch("fields", { deep: true })
		async updateGrid(newData: any[], oldData: any[]) {
			// If there's no grid, we don't need to update it
			if(!this.grid) return;

			// Map to what vue uses as keys (used to determine what to update/replace/reorder)
			const oldKeys = oldData.map((e) => e[this.muurikey]);
			const newKeys = newData.map((e) => e[this.muurikey]);

			// Get which keys (individual elements) were added/removed
			const added = newKeys.filter((key) => oldKeys.indexOf(key) === -1);
			const removed = oldKeys.filter((key) => newKeys.indexOf(key) === -1);

			// Deal with the removed tiles first
			removed.forEach((po) => {
				this.grid!.remove(this.getOrder().indexOf(po), {
					// Vue will take care of this
					removeElements: false,
					// We will be adding items too, so not quite yet
					layout: false,
				});
			});

			await this.$nextTick();

			// Now add in the new ones
			this.grid.add(
				([ // Wrap NodeList in array for extra methods
					...this.muuriel.childNodes,
				] as HTMLElement[])
					// Filter out anything not in list of added orders
					.filter((el) => added.indexOf(el.getAttribute("muurikey")!) !== -1),
			{
				// We will be re-ordering, so wait a bit longer
				layout: false,
			});

			// Re-order if needed
			const items = this.grid.getItems();
			const newOrder = this.fields.map((field) => field[this.muurikey]);
			this.grid.sort((a, b) => {
				return newOrder.indexOf(a.getElement().getAttribute("muurikey")!)
					- newOrder.indexOf(b.getElement().getAttribute("muurikey")!);
			});

			// Now we can start the layout
			this.layout();
		}
	}
</script>

<style lang="scss" scoped>
	.muuri {
		position: relative;

		& > .muuri-item {
			display: block;
			position: absolute;

			&.muuri-item-dragging {
				z-index: 3;
			}
			&.muuri-item-releasing {
				z-index: 2;
			}
			&.muuri-item-hidden {
				z-index: 0;
			}
		}
	}
</style>
