import { Directive } from "vue";

type Value = { max?: number[]; min?: number[] };

//const observers = new Map<Element, ResizeObserver>();
const mountings = new Map<
	Element,
	{
		value: Value;
		resize: ResizeObserver;
		intersection?: IntersectionObserver;
		previousWidth: number;
	}
>();

type ClassOrder = {
	add: string[];
	remove: string[];
};

const cache = new Map<string, ClassOrder>();

function getClassOrder(width: number, queue: Value): ClassOrder {
	const getMaxClass = (v: number) => `max-width_${v}px`;
	const getMinClass = (v: number) => `min-width_${v}px`;

	return {
		add: [
			...(queue.max
				? queue.max.filter((v) => width <= v).map(getMaxClass)
				: []),
			...(queue.min
				? queue.min.filter((v) => width >= v).map(getMinClass)
				: []),
		],
		remove: [
			...(queue.max ? queue.max.filter((v) => width > v).map(getMaxClass) : []),
			...(queue.min ? queue.min.filter((v) => width < v).map(getMinClass) : []),
		],
	};
}

function applyClassOrder(el: Element, order: ClassOrder) {
	el.classList.add(...order.add);
	el.classList.remove(...order.remove);
}

function getOrderName(width: number, queue: Value): string {
	return `${width}|${queue.max ? queue.max.join(",") : ""}|${
		queue.min ? queue.min.join(",") : ""
	}`;
}

function calc(el: Element) {
	const info = mountings.get(el);
	const width = el.clientWidth;

	if (!info || info.previousWidth === width) return;

	// アクティベート前などでsrcが描画されていない場合
	if (!width) {
		// IntersectionObserverで表示検出する
		if (!info.intersection) {
			info.intersection = new IntersectionObserver((entries) => {
				if (entries.some((entry) => entry.isIntersecting)) calc(el);
			});
		}
		info.intersection.observe(el);
		return;
	}
	if (info.intersection) {
		info.intersection.disconnect();
		info.intersection = undefined;
	}

	mountings.set(el, Object.assign(info, { previousWidth: width }));

	const cached = cache.get(getOrderName(width, info.value));
	if (cached) {
		applyClassOrder(el, cached);
	} else {
		const order = getClassOrder(width, info.value);
		cache.set(getOrderName(width, info.value), order);
		applyClassOrder(el, order);
	}
}

export default {
	mounted(src, binding, vn) {
		const resize = new ResizeObserver((entries, observer) => {
			calc(src);
		});

		mountings.set(src, {
			value: binding.value,
			resize,
			previousWidth: 0,
		});

		calc(src);
		resize.observe(src);
	},

	updated(src, binding, vn) {
		mountings.set(
			src,
			Object.assign({}, mountings.get(src), { value: binding.value }),
		);
		calc(src);
	},

	unmounted(src, binding, vn) {
		const info = mountings.get(src);
		if (!info) return;
		info.resize.disconnect();
		if (info.intersection) info.intersection.disconnect();
		mountings.delete(src);
	},
} as Directive<Element, Value>;
