/* global google */
/* eslint-disable filenames/match-regex */
import type { Cluster } from './Cluster';

import type { ClusterIconStyle, ClusterIconInfo } from './types';

export class ClusterIcon {
	cluster: Cluster;
	className: string;
	clusterClassName: string;
	styles: ClusterIconStyle[];
	center: google.maps.LatLng | undefined;
	div: HTMLDivElement | null;
	sums: ClusterIconInfo | null;
	visible: boolean;
	url: string;
	height: number;
	width: number;
	anchorText: [number, number];
	anchorIcon: [number, number];
	textColor: string;
	textSize: number;
	textDecoration: string;
	fontWeight: string;
	fontStyle: string;
	fontFamily: string;
	backgroundPosition: string;
	cMouseDownInCluster: boolean | null;
	cDraggingMapByCluster: boolean | null;
	timeOut: number | null;

	boundsChangedListener: google.maps.MapsEventListener | null;

	constructor(cluster: Cluster, styles: ClusterIconStyle[]) {
		cluster.getClusterer().extend(ClusterIcon, google.maps.OverlayView);

		this.cluster = cluster;

		this.clusterClassName = this.cluster.getClusterer().getClusterClass();

		this.className = this.clusterClassName;

		this.styles = styles;

		this.center = undefined;

		this.div = null;

		this.sums = null;

		this.visible = false;

		this.boundsChangedListener = null;

		this.url = '';

		this.height = 0;
		this.width = 0;

		this.anchorText = [0, 0];
		this.anchorIcon = [0, 0];

		this.textColor = 'black';
		this.textSize = 11;
		this.textDecoration = 'none';
		this.fontWeight = 'bold';
		this.fontStyle = 'normal';
		this.fontFamily = 'Arial,sans-serif';

		this.backgroundPosition = '0 0';

		this.cMouseDownInCluster = null;
		this.cDraggingMapByCluster = null;
		this.timeOut = null;

		(this as unknown as google.maps.OverlayView).setMap(cluster.getMap()); // Note: this causes onAdd to be called

		this.onBoundsChanged = this.onBoundsChanged.bind(this);
		this.onMouseDown = this.onMouseDown.bind(this);
		this.onClick = this.onClick.bind(this);
		this.onMouseOver = this.onMouseOver.bind(this);
		this.onMouseOut = this.onMouseOut.bind(this);
		this.onAdd = this.onAdd.bind(this);
		this.onRemove = this.onRemove.bind(this);
		this.draw = this.draw.bind(this);
		this.hide = this.hide.bind(this);
		this.show = this.show.bind(this);
		this.useStyle = this.useStyle.bind(this);
		this.setCenter = this.setCenter.bind(this);
		this.getPosFromLatLng = this.getPosFromLatLng.bind(this);
	}

	onBoundsChanged() {
		this.cDraggingMapByCluster = this.cMouseDownInCluster;
	}

	onMouseDown() {
		this.cMouseDownInCluster = true;

		this.cDraggingMapByCluster = false;
	}

	onClick(event: Event) {
		this.cMouseDownInCluster = false;

		if (!this.cDraggingMapByCluster) {
			const markerClusterer = this.cluster.getClusterer();

			/**
			 * This event is fired when a cluster marker is clicked.
			 * @name MarkerClusterer#click
			 * @param {Cluster} c The cluster that was clicked.
			 * @event
			 */
			google.maps.event.trigger(markerClusterer, 'click', this.cluster);
			google.maps.event.trigger(markerClusterer, 'clusterclick', this.cluster); // deprecated name

			// The default click handler follows. Disable it by setting
			// the zoomOnClick property to false.
			if (markerClusterer.getZoomOnClick()) {
				// Zoom into the cluster.
				const maxZoom = markerClusterer.getMaxZoom();

				const bounds = this.cluster.getBounds();

				const map = (markerClusterer as unknown as google.maps.OverlayView).getMap();

				if (map !== null && 'fitBounds' in map) {
					map.fitBounds(bounds);
				}

				// There is a fix for Issue 170 here:
				this.timeOut = window.setTimeout(() => {
					const map = (markerClusterer as unknown as google.maps.OverlayView).getMap();

					if (map !== null) {
						if ('fitBounds' in map) {
							map.fitBounds(bounds);
						}

						const zoom = map.getZoom() || 0;

						// Don't zoom beyond the max zoom level
						if (maxZoom !== null && zoom > maxZoom) {
							map.setZoom(maxZoom + 1);
						}
					}
				}, 100);
			}

			// Prevent event propagation to the map:
			event.cancelBubble = true;

			if (event.stopPropagation) {
				event.stopPropagation();
			}
		}
	}

	onMouseOver() {
		/**
		 * This event is fired when the mouse moves over a cluster marker.
		 * @name MarkerClusterer#mouseover
		 * @param {Cluster} c The cluster that the mouse moved over.
		 * @event
		 */
		google.maps.event.trigger(this.cluster.getClusterer(), 'mouseover', this.cluster);
	}

	onMouseOut() {
		/**
		 * This event is fired when the mouse moves out of a cluster marker.
		 * @name MarkerClusterer#mouseout
		 * @param {Cluster} c The cluster that the mouse moved out of.
		 * @event
		 */
		google.maps.event.trigger(this.cluster.getClusterer(), 'mouseout', this.cluster);
	}

	onAdd() {
		this.div = document.createElement('div');

		this.div.className = this.className;

		if (this.visible) {
			this.show();
		}

		(this as unknown as google.maps.OverlayView).getPanes()?.overlayMouseTarget.appendChild(this.div);

		const map = (this as unknown as google.maps.OverlayView).getMap();

		if (map !== null) {
			// Fix for Issue 157
			this.boundsChangedListener = google.maps.event.addListener(map, 'bounds_changed', this.onBoundsChanged);

			this.div.addEventListener('mousedown', this.onMouseDown);

			this.div.addEventListener('click', this.onClick);

			this.div.addEventListener('mouseover', this.onMouseOver);

			this.div.addEventListener('mouseout', this.onMouseOut);
		}
	}

	onRemove() {
		if (this.div && this.div.parentNode) {
			this.hide();

			if (this.boundsChangedListener !== null) {
				google.maps.event.removeListener(this.boundsChangedListener);
			}

			this.div.removeEventListener('mousedown', this.onMouseDown);

			this.div.removeEventListener('click', this.onClick);

			this.div.removeEventListener('mouseover', this.onMouseOver);

			this.div.removeEventListener('mouseout', this.onMouseOut);

			this.div.parentNode.removeChild(this.div);

			if (this.timeOut !== null) {
				window.clearTimeout(this.timeOut);

				this.timeOut = null;
			}

			this.div = null;
		}
	}

	draw() {
		if (this.visible && this.div !== null && this.center) {
			const pos = this.getPosFromLatLng(this.center);

			this.div.style.top = pos !== null ? `${pos.y}px` : '0';
			this.div.style.left = pos !== null ? `${pos.x}px` : '0';
		}
	}

	hide() {
		if (this.div) {
			this.div.style.display = 'none';
		}

		this.visible = false;
	}

	show() {
		if (this.div && this.center) {
			const divTitle =
				this.sums === null || typeof this.sums.title === 'undefined' || this.sums.title === ''
					? this.cluster.getClusterer().getTitle()
					: this.sums.title;

			// NOTE: values must be specified in px units
			const bp = this.backgroundPosition.split(' ');

			const spriteH = parseInt(bp[0]?.replace(/^\s+|\s+$/g, '') || '0', 10);
			const spriteV = parseInt(bp[1]?.replace(/^\s+|\s+$/g, '') || '0', 10);

			const pos = this.getPosFromLatLng(this.center);

			this.div.className = this.className;
			this.div.setAttribute(
				'style',
				`cursor: pointer; position: absolute; top: ${pos !== null ? `${pos.y}px` : '0'}; left: ${pos !== null ? `${pos.x}px` : '0'}; width: ${
					this.width
				}px; height: ${this.height}px; `
			);

			const img = document.createElement('img');

			img.alt = divTitle;
			img.src = this.url;
			img.width = this.width;
			img.height = this.height;
			img.setAttribute('style', `position: absolute; top: ${spriteV}px; left: ${spriteH}px`);

			if (!this.cluster.getClusterer().enableRetinaIcons) {
				img.style.clip = `rect(-${spriteV}px, -${spriteH + this.width}px, -${spriteV + this.height}, -${spriteH})`;
			}

			const textElm = document.createElement('div');

			textElm.setAttribute(
				'style',
				`position: absolute; top: ${this.anchorText[0]}px; left: ${this.anchorText[1]}px; color: ${this.textColor}; font-size: ${this.textSize}px; font-family: ${this.fontFamily}; font-weight: ${this.fontWeight}; fontStyle: ${this.fontStyle}; text-decoration: ${this.textDecoration}; text-align: center; width: ${this.width}px; line-height: ${this.height}px`
			);

			if (this.sums?.text) textElm.innerText = `${this.sums?.text}`;
			if (this.sums?.html) textElm.innerHTML = `${this.sums?.html}`;

			this.div.innerHTML = '';

			this.div.appendChild(img);
			this.div.appendChild(textElm);

			this.div.title = divTitle;

			this.div.style.display = '';
		}

		this.visible = true;
	}

	useStyle(sums: ClusterIconInfo) {
		this.sums = sums;

		const styles = this.cluster.getClusterer().getStyles();

		const style = styles[Math.min(styles.length - 1, Math.max(0, sums.index - 1))];

		if (style) {
			this.url = style.url;
			this.height = style.height;
			this.width = style.width;

			if (style.className) {
				this.className = `${this.clusterClassName} ${style.className}`;
			}

			this.anchorText = style.anchorText || [0, 0];
			this.anchorIcon = style.anchorIcon || [this.height / 2, this.width / 2];

			this.textColor = style.textColor || 'black';

			this.textSize = style.textSize || 11;

			this.textDecoration = style.textDecoration || 'none';

			this.fontWeight = style.fontWeight || 'bold';

			this.fontStyle = style.fontStyle || 'normal';

			this.fontFamily = style.fontFamily || 'Arial,sans-serif';

			this.backgroundPosition = style.backgroundPosition || '0 0';
		}
	}

	setCenter(center: google.maps.LatLng) {
		this.center = center;
	}

	getPosFromLatLng(latlng: google.maps.LatLng): google.maps.Point | null {
		const pos = (this as unknown as google.maps.OverlayView).getProjection().fromLatLngToDivPixel(latlng);

		if (pos !== null) {
			pos.x -= this.anchorIcon[1];

			pos.y -= this.anchorIcon[0];
		}

		return pos;
	}
}
