* dark overlay must be on all map except the boundries
* when click inside boundries nothing happen

/*
 * This file is part of  the extension: {{EXTENSION_NAME}}
 * Copyright (c) Eblasoft Bilişim Ltd.
 *
 * This Software is the property of Eblasoft Bilişim Ltd. and is protected
 * by copyright law - it is NOT Freeware and can be used only in one project
 * under a proprietary license, which is delivered along with this program.
 * If not, see <http://eblasoft.com.tr/eula>.
 *
 * This Software is distributed as is, with LIMITED WARRANTY AND LIABILITY.
 * Any unauthorised use of this Software without a valid license is
 * a violation of the License Agreement.
 *
 * According to the terms of the license you shall not resell, sublicense,
 * rent, lease, distribute or otherwise transfer rights or usage of this
 * Software or its derivatives. You may modify the code of this Software
 * for your own needs, if source code is provided.
 */

Espo.require('views/fields/address', function (Dep) {
    const _setupFn = Dep.prototype.setup;
    const _dataFn = Dep.prototype.data;
    const _fetchFn = Dep.prototype.fetch;
    const _afterRenderFn = Dep.prototype.afterRender;

    _.extend(Dep.prototype, {
        setup: function () {
            this.latitudeField = null;
            this.longitudeField = null;
            this.dataField = null;
            this.place = null;
            this.placesApiDisabled = false;

            _setupFn.call(this);

            this.apiKey = this.getConfig().get('googleMapsApiKey') || '';

            this.placesApiDisabled = this.placesApiDisabled || this.params.placesApiDisabled;

            if (this.placesApiDisabled) return;

            const googleMapsOptions = this.googleMapsOptions = this.getConfig().get('googleMapsOptions') || {};
            this.countryIsShort = googleMapsOptions['countryIsShort'];
            this.countryIsShort = googleMapsOptions['countryIsShort'];
            this.stateIsShort = googleMapsOptions['stateIsShort'];
            this.cityIsShort = googleMapsOptions['cityIsShort'];
            this.autocompleteRestrictedCountries = googleMapsOptions['autocompleteRestrictedCountries'] || [];

            this.wait(true);
            Espo.require('res!client/custom/modules/ebla-map-plus/res/templates/fields/address/coordinates.tpl', (coordinatesTmpl) => {
                this.coordinatesTmpl = coordinatesTmpl;
                this.wait(false);
            });

            this.events['click [data-action="locateAddress"]'] = this.actionLocateAddress;
        },

        data: function () {
            if (!this.placesApiDisabled) {
                this.params.viewMap = this.params.viewMap || (this.params.viewMap === null);
            }

            return _dataFn.call(this);
        },

        fetch: function () {
            const data = _fetchFn.call(this);

            if (this.placesApiDisabled) return data;

            if (this.$latitude.val().toString().trim()) {
                data[this.latitudeField] = parseFloat(this.$latitude.val().toString().trim());
            } else {
                data[this.latitudeField] = null;
            }

            if (this.$longitude.val().toString().trim()) {
                data[this.longitudeField] = parseFloat(this.$longitude.val().toString().trim());
            } else {
                data[this.longitudeField] = null;
            }

            if (this.fetchPlaceData()) {
                data[this.dataField] = this.fetchPlaceData();
            }

            return data;
        },

        fetchPlaceData: function () {
            if (!_.isObject(this.place)) return null;

            const keys = [
                "address_components",
                "adr_address",
                "business_status",
                "formatted_phone_number",
                "international_phone_number",
                "name",
                "place_id",
                "plus_code",
                "rating",
                "scope",
                "url",
                "website",
                "utc_offset_minutes"
            ];

            const mapData = {};
            keys.forEach(function (key) {
                mapData[key] = this.place[key];
            }.bind(this));

            return JSON.stringify(mapData);
        },

        canBeDisplayedOnMap: function () {
            return (
                (
                    !!this.model.get(this.name + 'Latitude') &&
                    !!this.model.get(this.name + 'Longitude')
                ) ||
                !!this.model.get(this.name + 'City') ||
                !!this.model.get(this.name + 'PostalCode')
            );
        },

        initGoogleAutocomplete: function () {
            // import google places api
            if (!google.maps.places) {
                google.maps.importLibrary('places').then(() => {
                    this.initGoogleAutocomplete();
                });
                return;
            }

            const defaultBounds = new google.maps.LatLngBounds(
                new google.maps.LatLng(-33.8902, 151.1759),
                new google.maps.LatLng(-33.8474, 151.2631));

            const options = {
                bounds: defaultBounds,
                language: this.googleMapsOptions.language || this.getConfig().get('language').substr(0, 2),
                types: ['geocode', 'establishment']
            };

            this.autocomplete = new google.maps.places.Autocomplete(this.$search.get(0), options);

            if (this.autocompleteRestrictedCountries.length > 0) {
                this.autocomplete.setComponentRestrictions({
                    country: this.autocompleteRestrictedCountries,
                });
            }

            this.autocomplete.addListener('place_changed', (e) => {
                const place = this.place = this.autocomplete.getPlace();
                console.log(this.place);
                this.setAddressFields(this.extractAddressFields(place));

                this.model.set(this.fetch());

                this.map.setCenter(place.geometry.location);
            })
        },

        setAddressFields: function (addressData) {
            this.$postalCode.val(addressData.postalCode);
            this.$country.val(addressData.country);
            this.$state.val(addressData.state);
            this.$city.val(addressData.city);
            this.$street.val(addressData.street);
            this.$latitude.val(addressData.lat);
            this.$longitude.val(addressData.long);
        },

        extractAddressFields: function (place) {
            let street = '';

            if (place.adr_address) {
                const streetAddress = $('<div>').append(place.adr_address).find('.street-address').text();
                street = place?.adr_address.substring(0, place.adr_address.indexOf('<span')) + (streetAddress || '')
            } else {
                street = place.formatted_address.split(',')[0] + ', ' + place.formatted_address.split(',')[1];
            }

            if (!street || street === '') {
                street = place.formatted_address;
            }

            let city = false;

            const addressFields = {
                postalCode: '',
                country: '',
                state: '',
                street: '',
                city: '',
                lat: '',
                long: ''
            };

            for (let i = 0; i < place.address_components.length; i++) {
                const addressComponent = place.address_components[i];
                const addressType = addressComponent.types[0];

                switch (addressType) {
                    case 'postal_code':
                        addressFields.postalCode = addressComponent.short_name;
                        break;
                    case 'country':
                        addressFields.country = addressComponent[this.countryIsShort ? 'short_name' : 'long_name'];
                        break;
                    case 'administrative_area_level_1':
                        addressFields.state = addressComponent[this.stateIsShort ? 'short_name' : 'long_name'];
                        break;
                    case 'street_number':
                    case 'route':
                        if (!street || street === '' || street === 'undefined') {
                            street = addressComponent.long_name;
                        }
                        break;
                    case 'locality':
                    case 'administrative_area_level_2':
                        if (!city) {
                            addressFields.city = addressComponent[this.cityIsShort ? 'short_name' : 'long_name'];
                            city = true;
                        }
                        break;
                    default:
                        break;
                }
            }

            addressFields.street = street;
            addressFields.lat = place.geometry.location.lat();
            addressFields.long = place.geometry.location.lng();

            return addressFields;
        },

        afterRender: function () {
            _afterRenderFn.call(this);

            if (this.mode === 'edit' && !this.placesApiDisabled) {
                const cacheKey = 'modules/ebla-map-plus/res/templates/fields/address/coordinates';
                let html = this._templator._getCachedTemplate(cacheKey);
                if (!html) {
                    html = this._templator.compileTemplate(this.coordinatesTmpl);
                    this._templator._templates[cacheKey] = html;
                }

                html = this._renderer.render(html, this.data());

                this.$el.prepend(html);

                const self = this;

                this.$latitude = this.$el.find('[data-name="' + this.latitudeField + '"]');
                this.$longitude = this.$el.find('[data-name="' + this.longitudeField + '"]');
                this.$search = this.$el.find('[data-name="' + this.name + 'Search"]');

                if (this.params.showCoordinates !== true) {
                    this.$latitude.hide();
                    this.$longitude.hide();
                }

                this.$latitude.on('change', function () {
                    self.trigger('change');
                });
                this.$longitude.on('change', function () {
                    self.trigger('change');
                });

                if (window.google && window.google.maps) {
                    this.initGoogleAutocomplete();
                    this.initMapGoogle();
                } else if (typeof window.mapapiloaded === 'function') {
                    const mapapiloaded = window.mapapiloaded;
                    window.mapapiloaded = function () {
                        this.initGoogleAutocomplete();
                        mapapiloaded();
                    }.bind(this);
                } else {
                    window.mapapiloaded = function () {
                        this.initGoogleAutocomplete();
                        this.initMapGoogle();
                    }.bind(this);
                    let src = 'https://maps.googleapis.com/maps/api/js?callback=mapapiloaded';
                    if (this.apiKey) {
                        src += '&key=' + this.apiKey;
                    }

                    const s = document.createElement('script');
                    s.setAttribute('async', 'async');
                    s.src = src;
                    document.head.appendChild(s);
                }
            } else {
                if (
                    this.params.showCoordinates === true &&
                    !!this.model.get(this.latitudeField) &&
                    !!this.model.get(this.longitudeField) &&
                    !this.placesApiDisabled
                ) {
                    this.$el.append(
                        '<span class="no-badge">LAT: ' + this.model.get(this.latitudeField) +
                        ', LONG: ' + this.model.get(this.longitudeField) + '</span>'
                    );
                }
            }
        },

        initMapGoogle: function () {
            if (!google.maps.marker) {
                google.maps.importLibrary('marker').then(() => {
                    this.initMapGoogle();
                });
                return;
            }

            this.map = new google.maps.Map(this.$el.find('.map').get(0), {
                center: {lat: 37.7749, lng: -122.4194}, // San Francisco
                zoom: 10,
                scrollwheel: this.scrollWheel,
                zoomControl: !this.scrollWheel,
                mapId: this.getConfig().get('googleMapsMapId') || '123'
            });

            this.marker = null;
            this.cityBoundary = null;

            if (this.model.get('addressCity')) {
                this.getCityBoundary(this.model.get('addressCity'));
            }

            this.map.addListener('click', (event) => {
                this.handleMapClick(event.latLng);
            });

            if (this.model.get(this.latitudeField) && this.model.get(this.longitudeField)) {
                const latLng = new google.maps.LatLng(
                    this.model.get(this.latitudeField),
                    this.model.get(this.longitudeField)
                );
                this.addMarker(latLng);
            }
        },

        handleMapClick: function (latLng) {
            if (this.isPointInBoundary(latLng)) {
                this.addMarker(latLng);
                this.map.setCenter(latLng);
                this.updateCoordinates(latLng);
            } else {
                console.log('Click outside boundary');
                // Optionally, you can show a message to the user
                // Espo.Ui.warning(this.translate('clickOutsideBoundary', 'messages'));
            }
        },


        isPointInBoundary: function (latLng) {
            let isInside = false;
            this.cityBoundary.forEach((feature) => {
                if (google.maps.geometry.poly.containsLocation(latLng, feature.getGeometry())) {
                    isInside = true;
                }
            });
            return isInside;
        },

        updateCoordinates: function (latLng) {
            this.$latitude.val(latLng.lat());
            this.$longitude.val(latLng.lng());
            this.trigger('change');
        },

        addMarker: function (latLng) {
            this.removeMarker(); // Remove existing marker if any

            this.marker = new google.maps.marker.AdvancedMarkerElement({
                map: this.map,
                position: latLng,
                title: 'Draggable Marker',
                draggable: true
            });

            // Add drag end event listener to update coordinates when marker is dragged
            this.marker.addListener('dragend', (event) => {
                const newPosition = this.marker.position;
                if (this.isPointInBoundary(newPosition)) {
                    this.updateCoordinates(newPosition);
                } else {
                    // If dragged outside boundary, move it back to its original position
                    this.marker.position = latLng;
                    // Optionally, show a message to the user
                    // Espo.Ui.warning(this.translate('markerOutsideBoundary', 'messages'));
                }
            });
        },

        removeMarker: function () {
            if (this.marker) {
                this.marker.setMap(null);
                this.marker = null;
            }
        },

        getCityBoundary: function (cityName) {
            const nominatimUrl = `https://nominatim.openstreetmap.org/search?q=${encodeURIComponent(cityName)}&format=json&polygon_geojson=1`;

            fetch(nominatimUrl)
                .then(response => response.json())
                .then(data => {
                    let cityIndex = -1;
                    for (let i = 0; i < data.length; i++) {
                        if (data[i].geojson && (data[i].geojson.type === 'Polygon' || data[i].geojson.type === 'MultiPolygon')) {
                            switch (data[i].type) {
                                case 'city':
                                case 'administrative':
                                case 'boundary':
                                    cityIndex = i;
                                    break;
                            }
                        }
                    }

                    console.log('City Index:', cityIndex);

                    if (cityIndex !== -1) {
                        const cityData = data[cityIndex];
                        console.log('City Data:', cityData);
                        if (cityData.geojson) {
                            this.drawCityBoundary(cityData.geojson);
                        } else {
                            console.error('No GeoJSON data found for the city');
                        }
                    } else {
                        console.error('City boundary not found');
                    }
                })
                .catch(error => console.error('Error fetching city data:', error));
        },

        drawCityBoundary: function (geojson) {
            if (this.cityBoundary) {
                this.cityBoundary.setMap(null);
            }

            this.cityBoundary = new google.maps.Data();

            try {
                const featureCollection = {
                    type: 'FeatureCollection',
                    features: [{
                        type: 'Feature',
                        geometry: geojson
                    }]
                };

                this.cityBoundary.addGeoJson(featureCollection);

                this.cityBoundary.setStyle({
                    fillColor: 'blue',
                    fillOpacity: 0.1,
                    strokeColor: 'blue',
                    strokeWeight: 2
                });

                this.cityBoundary.setMap(this.map);

                // Remove the click event listener from the Data layer

                // Fit the map to the boundary
                const bounds = new google.maps.LatLngBounds();
                this.cityBoundary.forEach(feature => {
                    const geometry = feature.getGeometry();
                    geometry.forEachLatLng(latLng => bounds.extend(latLng));
                });
                this.map.fitBounds(bounds);

                this.removeMarker();

                // Create a dark overlay outside the city boundary
                this.createDarkOverlay(bounds, geojson);

                // Add click event listener to the map
                google.maps.event.clearListeners(this.map, 'click');
                this.map.addListener('click', (event) => {
                    this.handleMapClick(event.latLng);
                });

            } catch (error) {
                console.error('Error drawing city boundary:', error);
            }
        },

        createDarkOverlay: function (bounds, geojson) {
            // Create a rectangle that covers the entire map
            const outerCoords = [
                bounds.getNorthEast(),
                new google.maps.LatLng(bounds.getNorthEast().lat(), bounds.getSouthWest().lng()),
                bounds.getSouthWest(),
                new google.maps.LatLng(bounds.getSouthWest().lat(), bounds.getNorthEast().lng()),
            ];

            // Get the coordinates of the city boundary
            let innerCoords;
            if (geojson.type === 'Polygon') {
                innerCoords = geojson.coordinates[0].map(coord => new google.maps.LatLng(coord[1], coord[0]));
            } else if (geojson.type === 'MultiPolygon') {
                // For simplicity, we'll use the first polygon of the MultiPolygon
                innerCoords = geojson.coordinates[0][0].map(coord => new google.maps.LatLng(coord[1], coord[0]));
            }

            // Create the polygon with a hole for the city boundary
            this.darkOverlay = new google.maps.Polygon({
                paths: [outerCoords, innerCoords],
                strokeColor: "#000000",
                strokeOpacity: 0.8,
                strokeWeight: 2,
                fillColor: "#000000",
                fillOpacity: 0.35
            });

            this.darkOverlay.setMap(this.map);
        },

        actionLocateAddress: function (e) {
            $(e.currentTarget).prop('disabled', true);

            Espo.Ui.notify(' ... ');

            if ('geolocation' in navigator) {
                navigator.geolocation.getCurrentPosition(position => {
                    const latitude = position.coords.latitude;
                    const longitude = position.coords.longitude;

                    this.model.set(this.latitudeField, latitude);
                    this.model.set(this.longitudeField, longitude);

                    this.getAddressInfo(latitude, longitude);

                    $(e.currentTarget).prop('disabled', false);
                }, error => {
                    Espo.Ui.error(this.translate('errorGettingLocation', 'messages'));
                });
            } else {
                Espo.Ui.error(this.translate('geoLocationNotSupported', 'messages'));
            }
        },

        getAddressInfo: function (latitude, longitude) {
            const geocoder = new google.maps.Geocoder();
            const latlng = new google.maps.LatLng(latitude, longitude);

            geocoder.geocode({'latLng': latlng}, (results, status) => {
                Espo.Ui.notify(false);

                if (status === google.maps.GeocoderStatus.OK) {
                    if (results[0]) {
                        const place = results[0];

                        this.setAddressFields(this.extractAddressFields(place));

                        this.model.set(this.fetch());
                    } else {
                        Espo.Ui.error(this.translate('noAddressFound', 'messages'));
                    }
                } else {
                    Espo.Ui.error(this.translate('noAddressFound', 'messages'));
                }
            });
        },
    });
});