import {Controller} from '@hotwired/stimulus';
import mapboxgl from 'mapbox-gl';
import 'mapbox-gl/dist/mapbox-gl.css';
import '@mapbox/search-js-web';
import {GeocodingCore} from '@mapbox/search-js-core';

export default class extends Controller {
    static targets = ['map', 'geocoder', 'address', 'meetingLat', 'meetingLng', 'meetingLatDisplay', 'meetingLngDisplay'];
    static values = {
        token: String,
        meetingLat: Number,
        meetingLng: Number,
    };

    connect() {
        const token = this.tokenValue;
        if (!token) {
            console.error('Mapbox token is missing!');
            return;
        }
        mapboxgl.accessToken = token;
        try {
            const startLng = this.meetingLngValue || 9.9355;
            const startLat = this.meetingLatValue || 51.5413;

            this.map = new mapboxgl.Map({
                container: this.mapTarget,
                style: 'mapbox://styles/mapbox/streets-v12',
                center: [startLng, startLat],
                zoom: 13,
            });

            this.map.on('load', () => this.map.resize());

            this.marker = new mapboxgl.Marker({ draggable: true })
                .setLngLat([startLng, startLat])
                .addTo(this.map);

            this.geocoderTarget.accessToken = token;
            this.geocoderTarget.mapboxgl = mapboxgl;
            this.geocoderTarget.bindMap(this.map);

            this.onRetrieve = this.onRetrieve.bind(this);
            this.geocoderTarget.addEventListener('retrieve', this.onRetrieve);

            // Suppress the geocoder's internal marker so we don't end up with two.
            this.geocoderTarget.marker = false;

            if (this.addressTarget.value) {
                this.geocoderTarget.value = this.addressTarget.value;
            }

            this.addressTarget.addEventListener('input', () => this.dispatch('dirty'));

            this.reverse = new GeocodingCore({ accessToken: token });

            this.marker.on('dragend', async () => {
                const lngLat = this.marker.getLngLat();
                this.setCoords(lngLat.lat, lngLat.lng);
                try {
                    const res = await this.reverse.reverse(lngLat, {
                        limit: 1,
                        language: 'de',
                    });
                    const place = res.features[0];
                    if (place) {
                        const address = place.properties.full_address ?? place.properties.name;
                        this.addressTarget.value = address;
                        this.addressTarget.dispatchEvent(new Event('input', { bubbles: true }));
                        this.geocoderTarget.value = address;
                    }
                } catch (err) {
                    console.warn('Reverse geocoding failed', err);
                }
            });

            this.map.on('click', (e) => {
                this.marker.setLngLat(e.lngLat);
                this.marker.fire('dragend');
            });

            // Force a resize after a short delay to ensure the container is visible and has size
            setTimeout(() => this.map.resize(), 200);
        } catch (error) {
            console.error('Error initializing Mapbox:', error);
        }
    }

    onRetrieve(e) {
        const feature = e.detail;
        if (!feature?.geometry?.coordinates) return;

        const [lng, lat] = feature.geometry.coordinates;
        this.marker.setLngLat([lng, lat]);
        this.setCoords(lat, lng, feature.properties.full_address ?? feature.properties.name);
    }

    setCoords(lat, lng, address = null) {
        this.meetingLatTarget.value = lat.toFixed(7);
        this.meetingLngTarget.value = lng.toFixed(7);
        this.meetingLatDisplayTarget.textContent = this.meetingLatTarget.value;
        this.meetingLngDisplayTarget.textContent = this.meetingLngTarget.value;

        this.meetingLatTarget.dispatchEvent(new Event('input', { bubbles: true }));
        this.meetingLngTarget.dispatchEvent(new Event('input', { bubbles: true }));

        if (address !== null) {
            this.addressTarget.value = address;
            this.addressTarget.dispatchEvent(new Event('input', { bubbles: true }));
        }

        this.dispatch('dirty');
    }

    disconnect() {
        if (this.geocoderTarget && this.onRetrieve) {
            this.geocoderTarget.removeEventListener('retrieve', this.onRetrieve);
        }
    }
}