/* eslint-disable ts/no-this-alias */
import type { VNode } from 'vue';
import type { DropdownButtonItemArgs } from '../dropdown-button/dropdown-button-item';
import type { FormItemWrapperArgs, MarginType } from '../form/form-item-wrapper';
import { Prop, toNative } from 'vue-facing-decorator';
import { globalState } from '../../app/global-state';
import PowerduckState from '../../app/powerduck-state';
import TsxComponent, { Component } from '../../app/vuetsx';
import { AppHttpProvider } from '../../common/api-http';
import { CountryISOMapping } from '../../common/country-iso-mapping';
import { isNullOrEmpty } from '../../common/utils/is-null-or-empty';
import FormItemWrapper from '../form/form-item-wrapper';
import GoogleMapsApiLoader from './ts/google-maps-api';

interface GooglePlacesAutocompleteValue {
	latitude: string;
	longitude: string;
	name: string;
	googlePlace: any;
}

interface TimezoneData {
	dstOffset: number;
	gmtOffset: number;
	timezoneId: string;
}

interface GooglePlacesAutocompleteArgs extends FormItemWrapperArgs {
	value?: string;
	placeholder?: string;
	changed: (newValue: GooglePlacesChangedArgs) => void;
	bound: () => void;
	resultType?: GooglePlacesResultType;
	timezoneRequired?: boolean;
}

interface GooglePlacesAutocompleteSingletonArgs {
	element: HTMLElement;
	resultType: GooglePlacesResultType;
	changed: (args: GooglePlacesChangedArgs) => void;
}

export interface GooglePlacesChangedArgs {
	name: string;
	street: string;
	zip: string;
	city: string;
	country: string;
	gpsLatitude: string;
	gpsLongitude: string;
	fullName: string;
	inputValue: string;
	dstOffset?: number;
	gmtOffset?: number;
	timezoneName?: string;
}

export enum GooglePlacesResultType {
	All = 0,
	Address = 1,
	City = 2,
}

@Component
class GooglePlacesAutocompleteComponent extends TsxComponent<GooglePlacesAutocompleteArgs> implements GooglePlacesAutocompleteArgs {
	@Prop() label!: string | VNode;
	@Prop() labelButtons!: DropdownButtonItemArgs[];
	@Prop() subtitle!: string;
	@Prop() placeholder!: string;
	@Prop() mandatory!: boolean;
	@Prop() wrap!: boolean;
	@Prop() showClearValueButton!: boolean;
	@Prop() hint: string;
	@Prop() appendIcon: string;
	@Prop() prependIcon: string;
	@Prop() maxWidth?: number;
	@Prop() marginType?: MarginType;
	@Prop() resultType: GooglePlacesResultType;
	@Prop() appendClicked: () => void;
	@Prop() prependClicked: () => void;
	@Prop() bound: () => void;
	@Prop() changed: (newValue: GooglePlacesChangedArgs) => void;
	@Prop() value: string;
	@Prop() timezoneRequired: boolean;
	currentValue: string = null;

	raiseChangeEvent(e: GooglePlacesChangedArgs) {
		this.populateValidationDeclaration();

		if (this.changed != null) {
			this.changed(e);
		}
	}

	trySetInputValue(val: string): void {
		try {
			(this.$refs.googleInput as HTMLInputElement).value = val;
		} catch (e) { }
	}

	mounted() {
		const mySelf = this;
		this.currentValue = this.value;

		GoogleMapsApiLoader.load().then((result) => {
			if (result) {
				GooglePlacesAutocompleteWrapper.bind({
					element: this.$el.querySelector('input'),
					resultType: this.resultType || GooglePlacesResultType.All,
					changed(changeArgs) {
						if (mySelf.timezoneRequired != true) {
							mySelf.raiseChangeEvent(changeArgs);
						} else {
							GooglePlacesUtils.getTimezone(changeArgs).then((data) => {
								changeArgs.dstOffset = data.dstOffset;
								changeArgs.gmtOffset = data.gmtOffset;
								changeArgs.timezoneName = data.timezoneId;

								if (changeArgs.timezoneName != null && changeArgs.timezoneName.toLowerCase().includes('bratislava')) {
									changeArgs.timezoneName = 'Europe/Vienna';
								} else if (changeArgs.dstOffset == 120 && changeArgs.gmtOffset == 60) {
									changeArgs.timezoneName = 'Europe/Vienna';
								}

								mySelf.raiseChangeEvent(changeArgs);
							});
						}
					},
				});
			}
		});

		this.$nextTick(() => {
			(this.$refs.googleInput as HTMLInputElement).value = this.currentValue;
		});
	}

	render(h) {
		return (
			<FormItemWrapper
				label={this.label}
				mandatory={this.mandatory}
				wrap={this.wrap}
				appendIcon={this.appendIcon}
				prependIcon={this.prependIcon}
				hint={this.hint}
				appendClicked={this.appendClicked}
				marginType={this.marginType}
				maxWidth={this.maxWidth}
				prependClicked={this.prependClicked}
				validationState={this.validationState}
				labelButtons={this.labelButtons}
				subtitle={this.subtitle}
				showClearValueButton={this.showClearValueButton}
			>
				<input ref="googleInput" type="text" class={PowerduckState.getFormControlCssClass()} placeholder={this.placeholder} />
			</FormItemWrapper>
		);
	}
}

class GooglePlacesUtils {
	static getName(place: any, type: GooglePlacesResultType): string {
		if (type != GooglePlacesResultType.Address) {
			if (place.types && place.types.includes('establishment')) {
				return place.name;
			}

			if (!isNullOrEmpty(place.adr_address)) {
				try {
					return place.adr_address.split('</span>')[0].split('>')[1];
				} catch (e) { }
			}

			return place.name;
		} else {
			return GooglePlacesUtils.getStreet(place);
		}
	}

	static getStreet(place: any): string {
		const ac: any[] = place.address_components;
		let houseNo: string = '';
		let street: string = '';

		if (!isNullOrEmpty(ac)) {
			ac.forEach((acItem) => {
				if (acItem.types) {
					if (acItem.types.contains('street_number')) {
						houseNo = acItem.long_name;
					} else if (acItem.types.contains('route')) {
						street = acItem.long_name;
					}
				}
			});
		}

		return (`${street} ${houseNo}`).trim();
	}

	static getCity(place: any, resultType: GooglePlacesResultType): string {
		if (resultType == GooglePlacesResultType.City) {
			return GooglePlacesUtils.getName(place, resultType);
		}

		let retVal: string;
		if (!isNullOrEmpty(place.address_components)) {
			let adminArea: string;
			for (let i = 0; i < place.address_components.length; i++) {
				const ac = place.address_components[i];
				if (ac.types) {
					if (ac.types.contains('locality')) {
						retVal = ac.short_name;
					} else {
						for (let j = 0; j < ac.types.length; j++) {
							const acType = ac.types[j];
							if (acType.contains('administrative_area') && isNullOrEmpty(adminArea)) {
								adminArea = ac.short_name;
							}
						}
					}
				}
			}

			if (isNullOrEmpty(retVal)) {
				retVal = adminArea;
			}
		}

		if (!isNullOrEmpty(retVal)) {
			const rlc = retVal.toLowerCase();
			if (rlc.includes('bratislava')) {
				return 'Bratislava';
			} else if (rlc.includes('praha')) {
				return 'Praha';
			}

			return retVal;
		}

		return GooglePlacesUtils.getName(place, resultType);
	}

	static getZIP(place: any): string {
		const ac: any[] = place.address_components;
		if (!isNullOrEmpty(ac)) {
			for (let i = 0, len = ac.length; i < len; i++) {
				const acItem = ac[i];
				if (acItem.types.contains('postal_code')) {
					return acItem.short_name.replaceAll(' ', '');
				}
			}
		}

		return null;
	}

	static getCountry(place: any): string {
		const ac: any[] = place.address_components;
		if (!isNullOrEmpty(ac)) {
			for (let i = 0, len = ac.length; i < len; i++) {
				const acItem = ac[i];
				if (acItem.types.contains('country')) {
					return CountryISOMapping[acItem.short_name] || acItem.short_name;
				}
			}
		}

		return null;
	}

	static getPlacesRestrictionCode(resultType: GooglePlacesResultType): string {
		if (resultType == GooglePlacesResultType.Address) {
			return 'address';
		} else if (resultType == GooglePlacesResultType.City) {
			return '(cities)';
		}

		return null;
	}

	static getTimezone(args: GooglePlacesChangedArgs): Promise<TimezoneData> {
		return new Promise((resolve, reject) => {
			AppHttpProvider
				.callAjax({
					type: 'GET',
					url: `https://secure.geonames.org/timezoneJSON?lat=${args.gpsLatitude}&lng=${args.gpsLongitude}&username=${PowerduckState.getGeonamesUsername()}`,
				})
				.then((data: any) => {
					resolve({
						dstOffset: data.dstOffset * 60,
						gmtOffset: data.gmtOffset * 60,
						timezoneId: data.timezoneId,
					});
				});
		});
	}
}

class GooglePlacesAutocompleteWrapper {
	static bind(args: GooglePlacesAutocompleteSingletonArgs): void {
		let acArgs: any;
		if (args.resultType != GooglePlacesResultType.All) {
			acArgs = {
				types: [GooglePlacesUtils.getPlacesRestrictionCode(args.resultType)],
			};
		}

		if (!globalState.windowExists) {
			return;
		}

		const autocomplete = new globalState.google.maps.places.Autocomplete(args.element as any, acArgs);
		autocomplete.addListener('place_changed', () => {
			const place = autocomplete.getPlace();
			const name = GooglePlacesUtils.getName(place, args.resultType);
			(args.element as HTMLInputElement).value = name;

			args.changed({
				name,
				fullName: place.formatted_address,
				inputValue: (args.element as HTMLInputElement).value,
				street: GooglePlacesUtils.getStreet(place),
				city: GooglePlacesUtils.getCity(place, args.resultType),
				zip: GooglePlacesUtils.getZIP(place),
				country: GooglePlacesUtils.getCountry(place),
				gpsLatitude: place.geometry.location.lat() as any,
				gpsLongitude: place.geometry.location.lng() as any,
			});
		});
	}
}

const GooglePlacesAutocomplete = toNative(GooglePlacesAutocompleteComponent);
export default GooglePlacesAutocomplete;
