/* eslint-disable class-methods-use-this */
import { CoordsCannotBeParsedError, GeocoderFailedError, LocationNotFoundError, QUERY_FIELDS, QUERY_TYPES } from './Location.types';
import { getNavigatorPosition, isValueDegreeFormatCoordinate, isValueDMCoordinate, isValueDMSCoordinate, parseDMS, toGoogleLatLng } from './utils';
export default class GoogleMapsAPI {
    version = 'v2';
    /** Returns coordinations for given input: either coords, address or current position. */
    getCoords = async (location) => {
        if (location === '' || location === undefined)
            throw new Error();
        if (location === 'current')
            return getNavigatorPosition();
        let coords;
        if (typeof location === 'string') {
            let latLng = [];
            if (isValueDMSCoordinate(location) ||
                isValueDMCoordinate(location) ||
                isValueDegreeFormatCoordinate(location)) {
                latLng = parseDMS(location);
            }
            else {
                let splitter;
                if (location.includes(', ')) {
                    splitter = ', ';
                }
                else if (location.includes(',')) {
                    splitter = ',';
                }
                else if (location.includes(' ')) {
                    splitter = ' ';
                }
                else {
                    splitter = ', ';
                }
                latLng = location.split(splitter).map(Number);
            }
            const [lat, lng] = latLng;
            if (Number.isNaN(lat) || Number.isNaN(lng) || lat === undefined || lng === undefined) {
                const geocoder = new google.maps.Geocoder();
                return new Promise((resolve, reject) => {
                    geocoder.geocode({ address: location }, (results, status) => {
                        if (status === 'OK') {
                            const [result] = results ?? [];
                            if (result?.geometry?.location) {
                                resolve({
                                    latitude: result.geometry.location.lat(),
                                    longitude: result.geometry.location.lng()
                                });
                            }
                            else {
                                resolve({ latitude: NaN, longitude: NaN });
                            }
                        }
                        else {
                            reject(new Error(`${GeocoderFailedError}: ${status}`));
                        }
                    });
                });
            }
            coords = { latitude: lat, longitude: lng };
        }
        else {
            coords = location;
        }
        if (Number.isFinite(coords.latitude) && Number.isFinite(coords.longitude))
            return coords;
        throw new Error(CoordsCannotBeParsedError);
    };
    getPlaceById = async (placeId, sessionToken = new google.maps.places.AutocompleteSessionToken(), mapElement = document.createElement('div')) => {
        const placesService = new google.maps.places.PlacesService(mapElement);
        return new Promise((resolve, reject) => {
            placesService.getDetails({ placeId, fields: QUERY_FIELDS, sessionToken }, (place, status) => {
                if (status === google.maps.places.PlacesServiceStatus.OK) {
                    resolve({
                        name: place?.name,
                        address: place?.formatted_address,
                        latitude: place?.geometry?.location?.lat(),
                        longitude: place?.geometry?.location?.lng()
                    });
                }
                else {
                    reject(new Error(`${LocationNotFoundError}: ${status}`));
                }
            });
        });
    };
    getAddress = async (coords) => {
        const location = {
            lat: coords.latitude,
            lng: coords.longitude
        };
        const geocoder = new google.maps.Geocoder();
        return new Promise((resolve, reject) => {
            geocoder.geocode({ location }, (results, status) => {
                if (status === 'OK') {
                    const [result] = results ?? [];
                    if (result) {
                        resolve({
                            name: result.formatted_address,
                            address: result.formatted_address,
                            ...(result.geometry && {
                                latitude: result.geometry.location.lat(),
                                longitude: result.geometry.location.lng()
                            })
                        });
                    }
                    else {
                        resolve({
                            name: `${coords.latitude}, ${coords.longitude}`,
                            latitude: coords.latitude,
                            longitude: coords.longitude
                        });
                    }
                }
                else {
                    reject(new Error(`${GeocoderFailedError}: ${status}`));
                }
            });
        });
    };
    getPlacePredictionsUtil = async (location, bias = {}, existingToken) => {
        const autocompleteService = new google.maps.places.AutocompleteService();
        const optionalParams = {};
        if (bias?.location) {
            if (bias?.location.center === 'current') {
                await getNavigatorPosition()
                    .then(coords => {
                    optionalParams.location = toGoogleLatLng(coords);
                    if (bias.location)
                        optionalParams.radius = bias.location.radius;
                })
                    // no-op user didn't allow location
                    .catch(() => { });
            }
            else {
                optionalParams.location = toGoogleLatLng(bias.location.center);
                optionalParams.radius = bias.location.radius;
            }
        }
        if (bias?.bounds) {
            const [sw, ne] = bias.bounds;
            optionalParams.bounds = new google.maps.LatLngBounds(toGoogleLatLng(sw), toGoogleLatLng(ne));
        }
        return new Promise((resolve, reject) => {
            const token = existingToken ?? new google.maps.places.AutocompleteSessionToken();
            autocompleteService.getPlacePredictions({
                input: location,
                types: QUERY_TYPES,
                sessionToken: token,
                ...optionalParams
            }, (placePredictions, status) => {
                if (status === google.maps.places.PlacesServiceStatus.OK ||
                    status === google.maps.places.PlacesServiceStatus.ZERO_RESULTS) {
                    const res = placePredictions?.map(p => ({
                        placeId: p.place_id,
                        mainText: p.structured_formatting.main_text,
                        secondaryText: p.structured_formatting.secondary_text
                    }));
                    resolve({ placeSuggestions: res?.length ? res : [], token });
                }
                else {
                    reject(new Error(`${LocationNotFoundError}: ${status}`));
                }
            });
        });
    };
    getPlaceSuggestionsUtil = async (location, bias = {}, existingToken) => {
        const { AutocompleteSuggestion } = google.maps.places;
        const optionalParams = {};
        if (bias?.location) {
            if (bias?.location.center === 'current') {
                await getNavigatorPosition()
                    .then(coords => {
                    const b = new google.maps.Circle({
                        center: toGoogleLatLng(coords)
                    });
                    if (bias.location) {
                        b.setRadius(bias.location.radius);
                    }
                    optionalParams.locationBias = b;
                })
                    // no-op user didn't allow location
                    .catch(() => { });
            }
            else {
                optionalParams.locationBias = new google.maps.Circle({
                    center: toGoogleLatLng(bias.location.center),
                    radius: bias.location.radius
                });
            }
        }
        if (bias?.bounds) {
            const [sw, ne] = bias.bounds;
            optionalParams.locationRestriction = new google.maps.LatLngBounds(toGoogleLatLng(sw), toGoogleLatLng(ne));
        }
        const token = existingToken ?? new google.maps.places.AutocompleteSessionToken();
        const rp = {
            input: location,
            sessionToken: token,
            ...optionalParams
        };
        return new Promise((resolve, reject) => {
            AutocompleteSuggestion.fetchAutocompleteSuggestions(rp)
                .then(({ suggestions }) => {
                const res = suggestions
                    ?.map(s => s.placePrediction)
                    .filter(p => p !== null)
                    .map(p => ({
                    placeId: p.placeId,
                    mainText: p.mainText?.text ?? '',
                    secondaryText: p.secondaryText?.text ?? ''
                }));
                resolve({ placeSuggestions: res ?? [], token });
            })
                .catch(e => {
                reject(e);
            });
        });
    };
    /** Returns an array of place prediction objects ('place' can be an establishment, geographic location, or prominent point of interest)
     * and  session token (valid for multiple queries, followed by one place selection).
     */
    getPlacePredictions = async (location, bias, sessionToken) => {
        try {
            if (this.version === 'v2') {
                return await this.getPlaceSuggestionsUtil(location, bias, sessionToken);
            }
            return await this.getPlacePredictionsUtil(location, bias, sessionToken);
        }
        catch (error) {
            if (this.version === 'v2' &&
                error instanceof Error &&
                'name' in Error &&
                error.name === 'RpcError') {
                this.version = 'v1';
                return await this.getPlacePredictions(location, bias, sessionToken);
            }
            throw new Error(LocationNotFoundError);
        }
    };
}
//# sourceMappingURL=GoogleMapsAPI.js.map