import {ChangeEventHandler, FocusEventHandler, useEffect, useRef, useState} from "react";
import {useTracker} from "meteor/react-meteor-data";
import {Meteor} from "meteor/meteor";
import {LocationDocument, LocationsDB} from "/imports/api/Listings/both/collections/locations";
import {Tracker} from "meteor/tracker";
import Google from "/imports/api/Listings/client/init/google_api";
import _ from "lodash";

export type AutocompleteLocation =
    | { type: "popular", location: LocationDocument }
    | { type: "google", place: google.maps.places.AutocompletePrediction }

type LocationsAutocomplete = {
    inputHasFocus: boolean
    input: { onChange: ChangeEventHandler<HTMLInputElement>, value: string, onFocus: FocusEventHandler<HTMLInputElement>, onBlur: FocusEventHandler<HTMLInputElement> }
    selectedLocation?: AutocompleteLocation
    locations: AutocompleteLocation[]
    popularLocations: LocationDocument[]
    predictions: google.maps.places.AutocompletePrediction[]
    getCoordinates: (location: AutocompleteLocation) => Promise<[number, number]>
    setSelectedLocation: (location?: AutocompleteLocation) => void
    clearInput: () => void
}

type LocationAutocompleteConfig = {
    value?: string
}

export function useLocationsAutocomplete(config?: LocationAutocompleteConfig): LocationsAutocomplete {

    const [selectedLocation, setSelectedLocation] = useState<AutocompleteLocation>();
    const [inputHasFocus, setInputHasFocus] = useState(false);
    const [inputValue, setInputValue] = useState(config?.value || "");
    const [predictions, setPredictions] = useState<Array<google.maps.places.AutocompletePrediction>>([]);

    useEffect(() => setInputValue(config?.value || ""), [ config?.value ]);

    const autoCompleteServiceRef = useRef<google.maps.places.AutocompleteService | null>(null);

    const [_loaded, popularLocations] = useTracker(() => {
        const ready = Meteor.subscribe("SearchLocations").ready();
        return [ready, LocationsDB.find({ name: new RegExp(inputValue, "i") }, { limit: 5 }).fetch()];
    }, [ inputValue ])

    useEffect(() => {
        const computation = Tracker.autorun(() => {
            if(Google.ready()) {
                autoCompleteServiceRef.current = new window.google.maps.places.AutocompleteService();
            }
        });
        return () => computation.stop();
    }, [])

    const onSearchValueChange: ChangeEventHandler<HTMLInputElement> = (evt) => {
        setSelectedLocation(undefined);
        const input = evt.target.value;
        setInputValue(input);

        if(input && autoCompleteServiceRef.current) {
            autoCompleteServiceRef.current.getPlacePredictions({
                input,
                types: [ '(regions)' ],
                componentRestrictions: { country: 'za' }
            }, (predictions) => {
                predictions = predictions || [];
                setPredictions(predictions)
            });
        } else {
            setPredictions([]);
        }
    }

    let results = [
        ..._.map(popularLocations, location => ({ location, type: 'popular' }) as AutocompleteLocation),
        ..._.map(predictions, place => ({ place, type: 'google' }) as AutocompleteLocation),
    ]

    results = results.slice(0, 5);

    // Using this hook: when hiding an element using focus state of an input, the blur event triggers and hides the
    // ui before the click in that UI is registered. This prevents that all test cases so far.
    let _timeoutHandle: number | null = null;

    return {
        inputHasFocus,
        input: {
            onChange: onSearchValueChange,
            onFocus: () => {
                if(_timeoutHandle) {
                    window.clearTimeout(_timeoutHandle);
                    _timeoutHandle = null;
                }
                setInputHasFocus(true)
            },
            onBlur: () => {
                _timeoutHandle = window.setTimeout(function () {
                    setInputHasFocus(false)
                }, 100)
            },
            value: selectedLocation
                ? selectedLocation.type === "google"
                    ? selectedLocation.place.description
                    : selectedLocation.location.name
                : inputValue
        },
        selectedLocation,
        popularLocations,
        predictions,
        locations: results,
        getCoordinates: (location) => {
            return getCoordinatesFromAutocompleteLocation(location);
        },
        clearInput: () => {
            setInputValue("");
            setSelectedLocation(undefined);
        },
        setSelectedLocation: (location) => setSelectedLocation(location)
    }
}

export function getCoordinatesFromAutocompleteLocation(location: AutocompleteLocation) {
    return new Promise<[number, number]>((resolve) => {
        if(location.type === "google") {
            new google.maps.Geocoder().geocode({ address: location.place.description }, (results, status) => {
                if(status === google.maps.GeocoderStatus.OK && results) {
                    const coordinates = [results[0].geometry.location.lng(), results[0].geometry.location.lat()];
                    resolve(coordinates as [number, number]);
                }
            });
        } else {
            resolve(location.location.center);
        }
    });
}

export function getAutocompleteLocationFromLocationString(location: string) {

    return new Promise<[number, number]>((resolve) => {
        // @ts-ignore
        new google.maps.places.AutocompleteService().getPlacePredictions({
            input: location,
            types: [ '(regions)' ],
            componentRestrictions: { country: 'za' }
        }, (predictions: any[]) => {
            predictions = predictions || [];
            resolve(predictions[0]);
        });
    });
}
