import * as google_helpers from "./modules/google-api-module-helpers.js";
jQuery(window).on("elementor/frontend/init dynamicooo/google-maps-init", () => {
	const waitForGoogleMapsMap = (tries = 50, delay = 100) =>
		new Promise((resolve, reject) => {
			const tick = () => {
				if (
					window.google &&
					google.maps &&
					typeof google.maps.Map === "function"
				) {
					resolve();
					return;
				}
				if (tries-- <= 0) {
					reject(new Error("Google Maps API not ready"));
					return;
				}
				setTimeout(tick, delay);
			};
			tick();
		});

	class DynamicMapDistance extends elementorModules.frontend.handlers.Base {
		getDefaultSettings() {
			return {};
		}

		getDefaultElements() {
			return {
				google_utility: google_helpers,
				travel_mode:
					this.getElementSettings().travel_mode.toUpperCase(),
				customMarkers: {
					start: {
						url:
							this.getElementSettings().departure_marker_image !=
							undefined
								? this.getElementSettings()
										.departure_marker_image.url
								: "",
						title: this.getElementSettings().departure_marker_title,
						label: this.getElementSettings().departure_marker_label,
						size:
							this.getElementSettings().departure_marker_size !=
							undefined
								? this.getElementSettings()
										.departure_marker_size.size
								: 30,
					},
					end: {
						url:
							this.getElementSettings()
								.destination_marker_image != undefined
								? this.getElementSettings()
										.destination_marker_image.url
								: "",
						title: this.getElementSettings()
							.destination_marker_title,
						label: this.getElementSettings()
							.destination_marker_label,
						size:
							this.getElementSettings().destination_marker_size !=
							undefined
								? this.getElementSettings()
										.destination_marker_size.size
								: 30,
					},
				},
				map: this.$element.find(".map")[0],
				map_name: this.getElementSettings().map_name,
				wrapper: this.$element.find(".map")[0],
				mapParams: {},
				zoom: this.getElementSettings().zoom.size,
				mapType: this.getElementSettings().map_type,
				use_geolocation_as:
					this.getElementSettings().use_geolocation_as,
				distance_geolocation_zoom:
					this.getElementSettings().distance_geolocation_zoom,
				distance_geolocation_change_zoom:
					this.getElementSettings()
						.distance_geolocation_change_zoom == "" ||
					this.getElementSettings()
						.distance_geolocation_change_zoom == undefined
						? false
						: true,
				geolocation: Boolean(this.getElementSettings().geolocation),
				geolocation_button_text:
					this.getElementSettings().geolocation_button_text,
				markers: Boolean(this.getElementSettings().markers),
				infoWindow: Boolean(this.getElementSettings().infoWindow),
				scrollwheel: Boolean(this.getElementSettings().scrollwheel),
				maptypecontrol: Boolean(
					this.getElementSettings().maptypecontrol,
				),
				pancontrol: Boolean(this.getElementSettings().pancontrol),
				rotatecontrol: Boolean(this.getElementSettings().rotatecontrol),
				scalecontrol: Boolean(this.getElementSettings().scalecontrol),
				streetviewcontrol: Boolean(
					this.getElementSettings().streetviewcontrol,
				),
				zoomcontrol: Boolean(this.getElementSettings().zoomcontrol),
				fullscreenControl: Boolean(
					this.getElementSettings().fullscreenControl,
				),
				locations: {
					start: {
						address: "",
						lat: 45.4408474,
						long: 12.3155151,
					},
					end: {
						address: "",
						lat: 45.4600113,
						long: 9.1797373,
					},
				},
			};
		}

		refreshInfo() {
			setTimeout(() => {
				// recovery all dynamic tag info
				let $all_ext_info = jQuery("body div#dce-directions-info");
				jQuery($all_ext_info).map((index, e) => {
					// check if map name is equal to data tag id
					if (
						this.elements.map_name ==
						e.getAttribute("data-tag-name")
					) {
						// if dynamic tag found take data
						let dataraw = jQuery(e)
							.find("span")
							.attr("data-directions");
						let data = JSON.parse(dataraw);

						this.map_name = data.map_name;
						this.loading_text = data.loading_text;
						this.option = data.option;
						// dynamic ext element to fire
						let $element_tag = jQuery(
							'body div#dce-directions-info[data-tag-name="' +
								this.map_name +
								'"]',
						);

						// get data from specific map
						var alldivs = document.querySelectorAll("div.map");
						jQuery(alldivs).map((index, el) => {
							if (
								this.map_name ==
								el.getAttribute("data-map-name")
							) {
								this.data_info_elevation_distance_converted =
									el.getAttribute("distance_info");
								this.data_info_elevation_distance_converted =
									JSON.parse(
										this
											.data_info_elevation_distance_converted,
									);
							}
						});

						setTimeout(() => {
							$element_tag.html("");

							switch (this.option) {
								case "miles":
									$element_tag.html(
										this.data_info_elevation_distance_converted.distance_in_miles.toFixed(
											2,
										),
									);
									break;
								case "km":
									$element_tag.html(
										this.data_info_elevation_distance_converted.distance_in_kilometers.toFixed(
											2,
										),
									);
									break;
								case "text":
									$element_tag.html(
										this
											.data_info_elevation_distance_converted
											.duration_text,
									);
									break;
								case "minutes":
									$element_tag.html(
										this
											.data_info_elevation_distance_converted
											.duration_value,
									);
									break;
								case "mode":
									$element_tag.html(
										this
											.data_info_elevation_distance_converted
											.travel,
									);
									break;
							}
							$element_tag.append(
								"<span class='distance dce-directions-info' data-directions='" +
									dataraw +
									"'></span>",
							);
						}, 4000);
					}
				});
			}, 1000);
		}

		refreshInstruction() {
			setTimeout(() => {
				// retrieve all dynamic tag info
				let $all_ext_info = jQuery(
					"body div#dce-gm-directions-instructions",
				);
				jQuery($all_ext_info).map((index, e) => {
					if (
						this.elements.map_name ==
						e.getAttribute("data-tag-name")
					) {
						let map_name = e.getAttribute("data-tag-name");

						// If a dynamic tag is found, take data
						var alldivs = document.querySelectorAll("div.map");
						let distance_instructions = [];
						jQuery(alldivs).map((index, el) => {
							if (map_name == el.getAttribute("data-map-name")) {
								distance_instructions = el.getAttribute(
									"distance_instructions",
								);
								distance_instructions = JSON.parse(
									distance_instructions,
								);
							}
						});
						let $element_tag = jQuery(
							'body div#dce-gm-directions-instructions[data-tag-name="' +
								map_name +
								'"]',
						);
						if (distance_instructions.length > 0) {
							$element_tag.html("");
							distance_instructions.map((val, index) => {
								$element_tag.append(
									`<p class="single_instruction">${val.instructions}</p>`,
								);
							});
						}
					}
				});
			}, 4000);
		}

		/**
		 * Set global configuration - position for init map
		 * @returns void
		 */
		setConfigurations() {
			// check if map is undefined
			if (this.elements.map === undefined) {
				return console.error("Map not defined");
			}

			// get position from backend object
			let positions =
				this.elements.map.getAttribute(
					"data-positions" + this.getID(),
				) || [];
			positions = JSON.parse(positions);

			// Exit if the maps doesn't have positions or have less of 2
			if (positions.length < 2) {
				console.error("Default addresses activated");
			}

			// build data start
			if (positions[0]) {
				this.elements.locations.start.address = positions[0].address;
				this.elements.locations.start.lat =
					parseFloat(positions[0].lat) || 0;
				this.elements.locations.start.long =
					parseFloat(positions[0].lng) || 0;
			}
			// build data end
			if (positions[1]) {
				this.elements.locations.end.address = positions[1].address;
				this.elements.locations.end.lat =
					parseFloat(positions[1].lat) || 0;
				this.elements.locations.end.long =
					parseFloat(positions[1].lng) || 0;
			}
		}

		/**
		 * Set global parameters - position for init map
		 * @returns void
		 */
		setMapParams() {
			this.elements.mapParams = {
				mapTypeId: this.elements.mapType,
				zoom: this.elements.zoom,
				scrollwheel: this.elements.scrollwheel,
				mapTypeControl: this.maptypecontrol,
				panControl: this.elements.pancontrol,
				rotateControl: this.elements.rotatecontrol,
				scaleControl: this.elements.scalecontrol,
				streetViewControl: this.elements.streetviewcontrol,
				zoomControl: this.elements.zoomcontrol,
				fullscreenControl: this.elements.fullscreenControl,
				center: {
					lat: this.elements.locations.start.lat,
					lng: this.elements.locations.start.long,
				},
			};
		}

		/**
		 * Set global parameters - position for init map
		 * @returns void
		 */
		async buildDirection() {
			try {
				await waitForGoogleMapsMap();
			} catch (error) {
				return;
			}

			if ("address" == this.getElementSettings().map_data_type) {
				if (
					this.elements.locations.start.address.length === 0 ||
					this.elements.locations.end.address.length === 0
				) {
					console.error("Address not found");
					return;
				}
			}
			// init direction check and get data latlong or Address return data
			try {
				const data = await this.setOriginalFinalPosition();

				// set the center map to origin position if get address string
				if ("address" == this.getElementSettings().map_data_type) {
					const origin_results_obj =
						await this.elements.google_utility.findLatLongCb(
							data.results.origin_address,
						);
					if ("OK" == origin_results_obj.status) {
						this.elements.mapParams.center = {
							lat: origin_results_obj.results.geometry.location.lat(),
							lng: origin_results_obj.results.geometry.location.lng(),
						};
					}
				}

				// check data status
				if ("OK" == data.status) {
					// Init the MAP with the start object (latLng or formatted address )
					try {
						var mapRequest =
							await this.elements.google_utility.makeGoogleMap(
								this.elements.map,
								this.elements.mapParams,
							);

						var map = mapRequest.map;
						// build standard data to send
						var data_to_send = {
							map: map,
							results: {
								data_obj: {
									start: data.results.origin_address,
									end: data.results.final_address,
								},
							},
						};

						// check if geoLocalization is active and change start or end position
						// return {lat lng} Obj
						this.geoLocalization(map, async (data) => {
							// check data status
							if (data.status == "OK") {
								// apply geoLocalization to start point or end point
								if (
									"departure" ==
									this.elements.use_geolocation_as
								) {
									data_to_send.results.data_obj.start =
										data.geo_position;
								} else if (
									"destination" ==
									this.elements.use_geolocation_as
								) {
									data_to_send.results.data_obj.end =
										data.geo_position;
								}

								// get new map with new param with geoLocalization
								var mapRequest =
									await this.elements.google_utility.makeGoogleMap(
										this.elements.map,
										this.elements.mapParams,
									);

								var map = mapRequest.map;
								data_to_send.map = map;

								this.fireDirectionServices(data_to_send);
							}
						});

						//without localization
						this.fireDirectionServices(data_to_send);
					} catch (error) {
						// map error
						console.error(error.message);
					}
				} // status ok
			} catch (error) {
				console.error(error.message);
			}
		}

		/**
		 * Set origin and final address Type Address || lat/lng from widget
		 * @param {status, {}} callback
		 * @returns latLong Obj  || formattedAddress
		 */
		async setOriginalFinalPosition() {
			return new Promise(async (resolve, reject) => {
				var origin_address;
				var final_address;

				if ("address" == this.getElementSettings().map_data_type) {
					if (
						typeof this.elements.locations.start.address !==
						"string"
					) {
						return;
					}

					// get first async address
					try {
						const data_start =
							await this.elements.google_utility.findLatLongCb(
								this.elements.locations.start.address,
							);
						if (data_start.status == "OK") {
							// data_start.results => results api object
							this.elements.locations.start.address =
								data_start.results;

							// fire address 2
							if (
								typeof this.elements.locations.end.address !==
								"string"
							) {
								return;
							}
							// get second async address
							const data_end =
								await this.elements.google_utility.findLatLongCb(
									this.elements.locations.end.address,
								);
							if (data_end.status == "OK") {
								// data_end.results => results api object
								this.elements.locations.end.address =
									data_end.results;

								origin_address =
									this.elements.locations.start.address
										.formatted_address;
								final_address =
									this.elements.locations.end.address
										.formatted_address;

								resolve({
									status: "OK",
									results: { origin_address, final_address },
								});
							}
						}
					} catch (error) {
						console.error(error.message);
					}
				} else if (
					this.getElementSettings().map_data_type == "latlng"
				) {
					var origin_address = {
						lat: this.elements.locations.start.lat,
						lng: this.elements.locations.start.long,
					};
					var final_address = {
						lat: this.elements.locations.end.lat,
						lng: this.elements.locations.end.long,
					};
					resolve({
						status: "OK",
						results: { origin_address, final_address },
					});
				}
			});
		}

		/**
		 * Check if Geolocation is active ad fire address
		 * @param {*} map
		 * @param {*} callback
		 */
		geoLocalization(map, callback) {
			if (this.elements.geolocation) {
				var data = {
					map: map,
					buttons: {
						geo_button_text: this.elements.geolocation_button_text,
						id: this.getID(),
					},
				};
				this.elements.google_utility.geoLocalization(
					data,
					(data_geo_result) => {
						if ("OK" == data_geo_result.status) {
							// reset map
							data.map = this.elements.google_utility.resetMap(
								this.elements.map,
								this.elements.mapParams,
							);
							// change global data start latlng
							callback({
								status: "OK",
								geo_position: data_geo_result.position,
							});
						}
						if ("NOCLICK" == data_geo_result.status) {
							callback({ status: "NOCLICK", geo_position: null });
						}
					},
				);
			}
		}

		/**
		 * Fire direction services and if enabled add custom markers
		 * Show instructions if enabled
		 * Show info repeaters if enabled
		 * @param {*} map
		 * @param {*} origin_address
		 * @param {*} final_address
		 */
		fireDirectionServices(data_to_send) {
			data_to_send.suppressMarkers = this.elements.markers;
			data_to_send.suppressInfoWindows = this.elements.infoWindow;

			this.elements.google_utility.fireDirectionServices(
				this.elements.travel_mode,
				data_to_send,
				(directions) => {
					if ("OK" == directions.status) {
						var result = directions.results.result;
						var map = data_to_send.map;
						jQuery("#result span#loading").hide();

						var distance = result.routes[0].legs[0].distance;
						var duration = result.routes[0].legs[0].duration;
						var distance_in_kilometers = distance.value / 1000;
						var distance_in_miles = distance.value / 1609.34;
						var duration_text = duration.text;
						var duration_value = duration.value;
						let distance_info = {
							distance_in_miles,
							distance_in_kilometers,
							duration_text,
							duration_value,
							travel: this.elements.travel_mode,
						};
						// insert distance info in attr distance_info for widget map distance info
						jQuery(this.elements.wrapper).attr(
							"distance_info",
							JSON.stringify(distance_info),
						);
						// insert the instruction in arr distance instructions for widget map distance instructions
						jQuery(this.elements.wrapper).attr(
							"distance_instructions",
							JSON.stringify(result.routes[0].legs[0].steps),
						);

						// change markers if enabled
						if (this.elements.markers) {
							var leg = result.routes[0].legs[0];

							this.makeMarker(
								"start",
								map,
								leg.start_location,
								{
									url: this.elements.customMarkers.start.url,
									size: [30, 30],
								},
								this.elements.customMarkers.start.title,
								this.elements.customMarkers.start.label,
								this.elements.customMarkers.start.size,
							);

							this.makeMarker(
								"end",
								map,
								leg.end_location,
								{
									url: this.elements.customMarkers.end.url,
									size: [30, 30],
								},
								this.elements.customMarkers.end.title,
								this.elements.customMarkers.end.label,
								this.elements.customMarkers.end.size,
							);
						}

						this.refreshInstruction();
						this.refreshInfo();
					}
				},
			);

			return;
		}

		makeMarker(point, map, position, urlSizeObj, title, label, size) {
			var icon = {
				url: urlSizeObj.url,
				anchor: new google.maps.Point(0, 0),
				scaledSize: new google.maps.Size(size, size),
			};
			var marker = new google.maps.Marker({
				position: position,
				draggable: false,
				map: map,
				icon: icon,
				title: title,
				label: label,
				animation: google.maps.Animation.DROP,
			});

			this.addInfoWindow(point, marker, position, map);
		}

		async addInfoWindow(point, marker, location, map) {
			var infoWindowContent = "";
			// check if infoWindow is enabled
			if (this.elements.infoWindow) {
				if ("start" == point) {
					infoWindowContent =
						this.getElementSettings().departure_info_window;
				} else if ("end" == point) {
					infoWindowContent =
						this.getElementSettings().destination_info_window;
				}
			} else {
				// not enabled. Retrieve default address
				try {
					var latlng = new google.maps.LatLng(
						location.lat(),
						location.lng(),
					);
					const address =
						await this.elements.google_utility.findAddressCb(
							latlng,
						);
					if ("OK" == address.status) {
						infoWindowContent = address.results.formatted_address;
					}
				} catch (error) {
					console.error(error.message);
				}
			}
			// infoWindows fire
			setTimeout(() => {
				var infowindow = new google.maps.InfoWindow();
				infowindow.setContent(infoWindowContent);
				infowindow.setPosition(location);

				// add marker custom event listener
				marker.addListener("click", (e) => {
					infowindow.open({
						anchor: marker,
						map,
						shouldFocus: false,
					});
				});
			}, 2000);
		}

		onInit() {
			super.onInit();

			// setConfigurations
			this.setConfigurations();
			// setParams
			this.setMapParams();
			// Fire Map
			if (this.elements.map !== undefined) {
				this.buildDirection();
			}
		}
	}

	const addHandler = ($element) => {
		elementorFrontend.elementsHandler.addHandler(DynamicMapDistance, {
			$element,
		});
	};
	elementorFrontend.hooks.addAction(
		"frontend/element_ready/dce-dynamic-google-maps-directions.default",
		addHandler,
	);
});
