Source: map.js

/**
 * @file map.js
 * @description Module de gestion de la carte interactive pour la création et l'édition de RoadTrips.
 * Architecture : constantes → état → utilitaires → fonctionnalités → init
 * @requires L (Leaflet)
 * @requires Swal (SweetAlert2)
 */

// ============================================================
// 0. CONSTANTES & CONFIGURATION
// ============================================================
/**
 * Configuration des régions (Europe et Amérique) pour centrer la carte.
 * @constant {Object}
 */
const REGIONS_CONFIG = {
    'europe': {
        center: [46.5, 2.5],
        zoom: 5,
        codes: ["fr", "be", "ch", "lu", "de", "at", "li", "it", "sm", "va", "es", "pt", "ad", "gb", "ie", "nl", "dk", "no", "se", "fi", "is", "pl", "cz", "sk", "hu", "ee", "lv", "lt", "ro", "bg", "gr", "cy", "mt", "si", "hr", "ba", "rs", "me", "al", "mk", "xk", "ua", "md", "by", "ge", "am", "az"],
        bbox: [-25, 34, 45, 72]
    },
    'america': {
        center: [39.8283, -98.5795],
        zoom: 4,
        codes: ["us", "ca", "mx"],
        bbox: [-169, 15, -52, 72]
    }
};
/**
 * Correspondance entre les modes de transport de l'UI et ceux de l'API OSRM.
 * @constant {Object<string, string>}
 */
const TRANSPORT_STRATEGIES = {
    'Voiture': 'driving',
    'Velo':    'cycling',
    'Marche':  'walking'
};
/**
 * URLs de base pour l'API de routage OSRM selon le mode de transport.
 * @constant {Object<string, string>}
 */
const ROUTING_SERVERS = {
    'Voiture': 'https://routing.openstreetmap.de/routed-car',
    'Velo':    'https://routing.openstreetmap.de/routed-bike',
    'Marche':  'https://routing.openstreetmap.de/routed-foot'
};

const SEGMENT_COLORS = [
    '#0B667D', '#2E8B57', '#FF7F50', '#BF092F', '#8e44ad', '#d35400', '#2980b9',
    '#16A085', '#E67E22', '#8E44AD', '#2C3E50', '#C0392B', '#27AE60', '#F1C40F',
    '#E74C3C', '#34495E', '#9B59B6', '#1ABC9C', '#7F8C8D', '#D35400'
];

const FAVORITE_ICON = L.divIcon({
    html: '<div style="font-size: 24px; color: #f1c40f; text-shadow: 0 0 3px black;">⭐</div>',
    className: 'fav-marker-icon',
    iconSize: [30, 30],
    iconAnchor: [15, 15],
    popupAnchor: [0, -15]
});

// ============================================================
// 1. ÉTAT GLOBAL DU MODULE
// ============================================================
/**
 * État global de l'application gérant les données de la carte et du trajet.
 * @type {Object}
 * @property {L.Map|null} map - Instance de la carte Leaflet.
 * @property {string} currentStartCity - Ville de départ.
 * @property {Array<number>|null} currentStartCoords - Coordonnées [lat, lon] du départ.
 * @property {Array<Object>} segments - Tableau contenant les étapes du trajet.
 * @property {Array<L.Polyline>} polylineLayers - Tableau des tracés affichés sur la carte.
 * @property {Object<string, L.Marker>} markers - Dictionnaire des marqueurs (clé = nom de la ville).
 * @property {number|null} currentSegmentIndex - Index du segment actuellement édité.
 * @property {boolean} isCalculating - Indicateur d'état pendant les calculs d'itinéraires.
 * @property {Array<Object>} userFavorites - Liste des lieux favoris de l'utilisateur.
 * @property {L.FeatureGroup|null} favoritesLayer - Groupe Leaflet contenant les marqueurs de favoris.
 */
const state = {
    map:                 null,
    currentRegion:       'europe',
    segments:            [],
    markers:             {},
    userFavorites:       [],
    subStepEditors:      {},
    currentSegmentIndex: null,
    currentStartCity:    '',
    currentStartCoords:  null,
};

// ============================================================
// 2. UTILITAIRES PURS (pas de dépendances vers state)
// ============================================================

function compresserImageJS(file, quality = 0.7, maxWidth = 1920) {
    return new Promise((resolve, reject) => {
        const fileName = file.name;
        const reader = new FileReader();
        reader.readAsDataURL(file);
        reader.onload = event => {
            const img = new Image();
            img.src = event.target.result;
            img.onload = () => {
                let width = img.width;
                let height = img.height;
                if (width > maxWidth) {
                    height = Math.round(height * (maxWidth / width));
                    width = maxWidth;
                }
                const canvas = document.createElement('canvas');
                canvas.width = width;
                canvas.height = height;
                const ctx = canvas.getContext('2d');
                ctx.drawImage(img, 0, 0, width, height);
                ctx.canvas.toBlob((blob) => {
                    if (!blob) {
                        reject(new Error("Erreur lors de la création du blob via Canvas"));
                        return;
                    }
                    resolve(new File([blob], fileName, { type: 'image/jpeg', lastModified: Date.now() }));
                }, 'image/jpeg', quality);
            };
            img.onerror = error => reject(error);
        };
        reader.onerror = error => reject(error);
    });
}

function formatDuration(seconds) {
    const h = Math.floor(seconds / 3600);
    const m = Math.floor((seconds % 3600) / 60);
    return h > 0 ? `${h}h${m.toString().padStart(2, '0')}` : `${m} min`;
}

function addTimeToString(startTime, secondsToAdd) {
    const [h, m] = startTime.split(':').map(Number);
    const date = new Date();
    date.setHours(h, m, 0);
    date.setSeconds(date.getSeconds() + secondsToAdd);
    return date.getHours().toString().padStart(2, '0') + ':' +
        date.getMinutes().toString().padStart(2, '0');
}

function durationStringToSeconds(timeStr) {
    if (!timeStr) return 0;
    const [h, m] = timeStr.split(':').map(Number);
    return (h * 3600) + (m * 60);
}

function getNomSimple(nom) {
    return nom ? nom.split(',')[0].trim() : '';
}

// ============================================================
// 3. UTILITAIRES UI
// ============================================================

function showToast(message) {
    const existing = document.querySelector('.toast-notification');
    if (existing) existing.remove();

    const toast = document.createElement('div');
    toast.className = 'toast-notification';
    toast.innerHTML = '⚠️ ' + message;
    document.body.appendChild(toast);

    setTimeout(() => {
        toast.classList.add('fade-out');
        setTimeout(() => toast.remove(), 500);
    }, 3500);
}

// ============================================================
// 4. CARTE : INITIALISATION
// ============================================================

function initRoadTripMap() {
    const regionSelectEl = document.getElementById('regionSelect');
    state.currentRegion = regionSelectEl ? regionSelectEl.value : 'europe';

    state.map = L.map('map').setView(
        REGIONS_CONFIG[state.currentRegion].center,
        REGIONS_CONFIG[state.currentRegion].zoom
    );

    L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
        maxZoom: 19,
        attribution: '@ OpenStreetMap'
    }).addTo(state.map);
}

// ============================================================
// 5. GÉOCODAGE & MARQUEURS
// ============================================================

async function getCoordinate(ville) {
    const codes = REGIONS_CONFIG[state.currentRegion].codes.join(',');
    const url = `https://nominatim.openstreetmap.org/search?format=json&q=${encodeURIComponent(ville)}&limit=1&accept-language=fr&countrycodes=${codes}`;
    try {
        const resp = await fetch(url, { headers: { 'Accept-Language': 'fr' } });
        if (!resp.ok) return null;
        const data = await resp.json();
        if (!data.length) return null;
        return [parseFloat(data[0].lat), parseFloat(data[0].lon)];
    } catch (e) {
        console.error(e);
        return null;
    }
}

function addMarker(lieu, coords, type, popupContent) {
    const marker = L.marker(coords).addTo(state.map).bindPopup(popupContent);
    if (!state.markers[lieu]) state.markers[lieu] = [];
    state.markers[lieu].push({ marker, type });
    return marker;
}

function initAutocomplete(element) {
    if (!element) return;

    element.addEventListener('input', function () {
        this.removeAttribute('data-lat');
        this.removeAttribute('data-lon');
        this.removeAttribute('data-full-name');
        this.style.backgroundColor = '';
    });

    $(element).autocomplete({
        source: async function (request, response) {
            const codes = REGIONS_CONFIG[state.currentRegion].codes.join(',');
            const url = `https://nominatim.openstreetmap.org/search?format=json&q=${encodeURIComponent(request.term)}&limit=8&accept-language=fr&countrycodes=${codes}&addressdetails=1`;

            try {
                const res = await fetch(url, { headers: { 'Accept-Language': 'fr' } });
                const data = await res.json();

                const seen = new Set();
                const suggestions = [];

                data.forEach(item => {
                    const addr    = item.address || {};
                    const name    = addr.city || addr.town || addr.village || addr.municipality || item.display_name.split(',')[0] || "";
                    const state   = addr.state || "";
                    const country = addr.country || "";
                    const postcode = addr.postcode || "";

                    const uniqueKey = `${name}-${state}-${country}`.toLowerCase();

                    if (!seen.has(uniqueKey)) {
                        seen.add(uniqueKey);
                        const fullLabel = [name, state, postcode, country].filter(Boolean).join(", ");
                        suggestions.push({
                            label: `<div class="ui-menu-item-content">
                                        <span class="ui-menu-item-name" style="font-weight: bold;">${name}</span>
                                        <span class="ui-menu-item-details" style="font-size: 0.85em; color: #666; margin-left: 5px;">${fullLabel}</span>
                                    </div>`,
                            value: fullLabel,
                            full_name: fullLabel,
                            lat: parseFloat(item.lat),
                            lon: parseFloat(item.lon)
                        });
                    }
                });

                response(suggestions);
            } catch (error) {
                console.error(error);
                response([]);
            }
        },
        minLength: 2,
        select: function (event, ui) {
            $(this).val(ui.item.full_name);
            $(this).attr('data-full-name', ui.item.full_name);
            $(this).attr('data-lat', ui.item.lat);
            $(this).attr('data-lon', ui.item.lon);
            $(this).css('backgroundColor', '#e8f8f5');
            return false;
        }
    }).data("ui-autocomplete")._renderItem = function (ul, item) {
        return $("<li>").append($("<div>").html(item.label)).appendTo(ul);
    };
}

// ============================================================
// 6. FAVORIS
// ============================================================

async function fetchUserFavorites() {
    try {
        const url = typeof URL_GET_FAVORIS !== 'undefined' ? URL_GET_FAVORIS : '/roadtrips/get-lieux-favoris';
        const resp = await fetch(url, { headers: { 'X-Requested-With': 'XMLHttpRequest' } });
        if (resp.ok) {
            state.userFavorites = await resp.json();
        }
    } catch (e) {
        console.log("Erreur chargement favoris", e);
    }
}

async function loadMapFavorites() {
    const favoris = state.userFavorites;
    if (!favoris || favoris.length === 0) return;

    favoris.forEach(fav => {
        const lat = parseFloat(fav.latitude);
        const lon = parseFloat(fav.longitude);
        const marker = L.marker([lat, lon], { icon: FAVORITE_ICON }).addTo(state.map);

        const container = document.createElement('div');
        container.style.textAlign = 'center';
        container.innerHTML = `
            <strong style="color:#d35400">${fav.nom_lieu}</strong><br>
            <small>${fav.categorie}</small><br>
            <div style="margin-top:10px; display:flex; flex-direction:column; gap:5px;">
                <button class="btn-fav-action btn-start" style="background:#27ae60; color:white; border:none; padding:5px; cursor:pointer; border-radius:3px;">🚩 Définir comme Départ</button>
                <button class="btn-fav-action btn-end" style="background:#c0392b; color:white; border:none; padding:5px; cursor:pointer; border-radius:3px;">🏁 Définir comme Arrivée</button>
                <button class="btn-fav-action btn-step" style="background:#2980b9; color:white; border:none; padding:5px; cursor:pointer; border-radius:3px;">📍 Ajouter comme Étape</button>
            </div>`;

        container.querySelector('.btn-start').addEventListener('click', () => {
            const btnAdd = document.getElementById('btnAddSegment');
            if (btnAdd && btnAdd.style.display !== 'none') btnAdd.click();
            setTimeout(() => {
                const inputStart = document.getElementById('inputStartBlock');
                if (inputStart) {
                    inputStart.value = fav.nom_lieu;
                    state.currentStartCity = fav.nom_lieu;
                    state.currentStartCoords = [lat, lon];
                    inputStart.style.backgroundColor = '#d5f5e3';
                }
            }, 100);
            marker.closePopup();
        });

        container.querySelector('.btn-end').addEventListener('click', () => {
            const btnAdd = document.getElementById('btnAddSegment');
            if (btnAdd && btnAdd.style.display !== 'none') btnAdd.click();
            setTimeout(() => {
                const inputEnd = document.getElementById('inputEndBlock');
                if (inputEnd) {
                    inputEnd.value = fav.nom_lieu;
                    inputEnd.style.backgroundColor = '#d5f5e3';
                }
            }, 100);
            marker.closePopup();
        });

        container.querySelector('.btn-step').addEventListener('click', () => {
            const formContainer = document.getElementById('segmentFormContainer');
            if (formContainer.style.display === 'block') {
                document.getElementById('addSubEtape').click();
                setTimeout(() => {
                    const allInputs = document.querySelectorAll('.subEtapeNom');
                    const lastInput = allInputs[allInputs.length - 1];
                    if (lastInput) {
                        lastInput.value = fav.nom_lieu;
                        lastInput.style.backgroundColor = '#d6eaf8';
                    }
                }, 50);
                marker.closePopup();
            } else {
                alert("Veuillez d'abord ouvrir le mode modification d'un trajet.");
            }
        });

        marker.bindPopup(container);
    });
}

function createFavSelectForInput(targetInputId) {
    if (!state.userFavorites || state.userFavorites.length === 0) return null;

    const select = document.createElement('select');
    select.style.cssText = "width: 100%; margin-bottom: 10px; padding: 10px 14px; border: 1px solid #ddd; border-radius: 15px; background-color: #fff; color: #555; font-size: 1rem; cursor: pointer;";

    let optionsHtml = '<option value="">⭐ Choisir un favori...</option>';
    state.userFavorites.forEach(fav => {
        const icon = fav.categorie === 'restaurant' ? '🍽️' : fav.categorie === 'hotel' ? '🏨' : '📍';
        optionsHtml += `<option value="${fav.nom_lieu}">${icon} ${fav.nom_lieu}</option>`;
    });
    select.innerHTML = optionsHtml;

    select.addEventListener('change', function () {
        const input = document.getElementById(targetInputId);
        if (input && this.value) {
            input.value = this.value;
            input.style.backgroundColor = '#e8f8f5';
            setTimeout(() => { input.style.backgroundColor = ''; }, 500);
        }
    });

    return select;
}

// ============================================================
// 7. GESTION DES SEGMENTS
// ============================================================

function removeSegmentFromMap(index) {
    const seg = state.segments[index];
    if (!seg) return;

    if (seg.line) state.map.removeLayer(seg.line);

    if (state.markers[seg.endName]) {
        state.markers[seg.endName].forEach(m => state.map.removeLayer(m.marker));
        delete state.markers[seg.endName];
    }

    if (seg.sousEtapes) {
        seg.sousEtapes.forEach(se => {
            if (state.markers[se.nom]) {
                state.markers[se.nom].forEach(m => state.map.removeLayer(m.marker));
                delete state.markers[se.nom];
            }
        });
    }

    if (index === 0) {
        if (state.markers[seg.startName]) {
            state.markers[seg.startName].forEach(m => state.map.removeLayer(m.marker));
            delete state.markers[seg.startName];
        }
        state.currentStartCity   = (typeof USER_DEFAULT_CITY !== 'undefined') ? USER_DEFAULT_CITY : "";
        state.currentStartCoords = null;
    } else {
        state.currentStartCity   = state.segments[index - 1].endName;
        state.currentStartCoords = state.segments[index - 1].endCoord;
    }

    state.segments.splice(index, 1);

    if (state.segments.length > 0) {
        const group = new L.featureGroup(state.segments.map(s => s.line));
        state.map.fitBounds(group.getBounds(), { padding: [50, 50] });
    }
}

async function _ajouterSegmentEntre(startName, startCoords, endName, endCoords, index, strategy, existingData = null) {
    const modeTransport  = existingData ? existingData.mode : 'Voiture';
    const currentProfile = TRANSPORT_STRATEGIES[modeTransport] || 'driving';

    let coordsList = [startCoords];
    if (existingData && existingData.sousEtapes) {
        existingData.sousEtapes.forEach(se => { if (se.coords) coordsList.push(se.coords); });
    }
    coordsList.push(endCoords);

    const coordString = coordsList.map(c => `${c[1]},${c[0]}`).join(';');
    const url = `https://router.project-osrm.org/route/v1/${currentProfile}/${coordString}?overview=full&geometries=geojson`;

    try {
        const resp = await fetch(url);
        const data = await resp.json();

        const color = SEGMENT_COLORS[index % SEGMENT_COLORS.length];
        let line, routeDistance = 0, routeDuration = 0, routeLegs = null;

        if (data.code === 'Ok' && data.routes && data.routes.length > 0) {
            const route = data.routes[0];
            routeDistance = route.distance;
            routeDuration = route.duration;
            routeLegs     = route.legs;

            const lineStyle = {
                color,
                weight: 6,
                opacity: 0.8,
                dashArray: modeTransport !== 'Voiture' ? '10, 10' : null
            };
            line = L.geoJSON(route.geometry, lineStyle).addTo(state.map);
        } else {
            console.warn("⚠️ Impossible de tracer la route pour : " + startName + " -> " + endName, data);
            line = L.polyline(coordsList, { color: 'red', weight: 4, dashArray: '5, 10' }).addTo(state.map);
        }

        state.map.fitBounds(line.getBounds(), { padding: [50, 50] });

        const segData = {
            line,
            startName,    startCoord:      startCoords,
            endName,      endCoord:        endCoords,
            couleurSegment: color,
            sousEtapes:   existingData && existingData.sousEtapes ? existingData.sousEtapes : [],
            startNameSimple: getNomSimple(startName),
            endNameSimple:   getNomSimple(endName),
            modeTransport,
            options:   {},
            date:      existingData ? existingData.date_trajet : '',
            distance:  routeDistance,
            duration:  routeDuration,
            legs:      routeLegs
        };
        state.segments.push(segData);

        const template = document.getElementById('template-legend-item');
        const clone    = template.content.cloneNode(true);
        const li       = clone.querySelector('li');
        li.dataset.index = index;
        clone.querySelector('.legend-color-indicator').style.background = color;
        clone.querySelector('.toggleSousEtapes').innerHTML = `${getNomSimple(startName)} → ${getNomSimple(endName)}`;

        const dateInput = clone.querySelector('.legend-date-input');
        if (segData.date) dateInput.value = segData.date;

        const timeInput = clone.querySelector('.legend-time-input');
        if (existingData && existingData.heure_depart) {
            timeInput.value       = existingData.heure_depart;
            segData.heure_depart  = existingData.heure_depart;
        } else {
            segData.heure_depart = timeInput.value;
        }
        timeInput.addEventListener('change', (e) => {
            state.segments[index].heure_depart = e.target.value;
            updateLegendHtml(index);
        });

        const transportBtns = clone.querySelectorAll('.transport-btn');
        transportBtns.forEach(btn => {
            btn.classList.toggle('active', btn.dataset.mode === modeTransport);
            btn.addEventListener('click', async () => {
                const nouveauMode = btn.dataset.mode;
                transportBtns.forEach(b => b.classList.remove('active'));
                btn.classList.add('active');
                await updateRouteSegment(index, nouveauMode, state.segments[index].options);
            });
        });

        clone.querySelector('.modifierSousEtapes').dataset.index = index;
        document.getElementById('legendList').appendChild(clone);
        updateDateConstraints();
        updateLegendHtml(index);

    } catch (e) {
        console.error("Erreur fatale lors de l'ajout du segment :", e);
    }
}
/**
 * Met à jour un segment de route spécifique sur la carte.
 * @async
 * @function updateRouteSegment
 * @param {number} index - L'index du segment à mettre à jour.
 * @param {string} mode - Le mode de transport (Voiture, Velo, Marche).
 * @returns {Promise<void>}
 */
async function updateRouteSegment(index, mode, options = {}) {
    const seg = state.segments[index];
    if (!seg) return;

    const baseUrl = ROUTING_SERVERS[mode] || ROUTING_SERVERS['Voiture'];

    let points = [seg.startCoord];
    if (seg.sousEtapes) {
        seg.sousEtapes.forEach(se => { if (se.coords) points.push(se.coords); });
    }
    points.push(seg.endCoord);

    const coordString = points.map(p => `${p[1]},${p[0]}`).join(';');
    const url = `${baseUrl}/route/v1/driving/${coordString}?overview=full&geometries=geojson&continue_straight=true`;

    try {
        const resp = await fetch(url);
        const data = await resp.json();

        if (data.code === 'Ok') {
            const route = data.routes[0];
            if (seg.line) state.map.removeLayer(seg.line);

            seg.distance      = route.distance;
            seg.duration      = route.duration;
            seg.modeTransport = mode;

            const lineStyle = {
                color:     seg.couleurSegment,
                weight:    6,
                opacity:   0.8,
                dashArray: mode !== 'Voiture' ? '10, 10' : null
            };
            seg.line = L.geoJSON(route.geometry, lineStyle).addTo(state.map);
            state.map.fitBounds(seg.line.getBounds(), { padding: [50, 50] });

            updateLegendHtml(index);
            console.log(`Succès ! Mode: ${mode}, Route mise à jour.`);
        }
    } catch (e) {
        console.error("Erreur de mise à jour de la route :", e);
    }
}

// ============================================================
// 8. LÉGENDE & CONTRAINTES DE DATE
// ============================================================

function updateDateConstraints() {
    const dateInputs = document.querySelectorAll('.legend-date-input');
    for (let i = 0; i < dateInputs.length; i++) {
        const currentInput = dateInputs[i];
        if (i > 0) {
            const prevInput = dateInputs[i - 1];
            if (prevInput.value) {
                currentInput.min = prevInput.value;
                if (currentInput.value && currentInput.value < prevInput.value) {
                    currentInput.value = prevInput.value;
                    const idx = currentInput.closest('li').dataset.index;
                    if (state.segments[idx]) state.segments[idx].date = prevInput.value;
                }
            }
        }
        currentInput.onchange = (e) => {
            const idx = e.target.closest('li').dataset.index;
            if (state.segments[idx]) state.segments[idx].date = e.target.value;
            updateDateConstraints();
        };
    }
}

function updateLegendHtml(index) {
    const seg = state.segments[index];
    const li  = document.querySelector(`li[data-index="${index}"]`);
    if (!li || !seg.heure_depart) return;

    const ul = li.querySelector('.sousEtapesList');
    let html = '';
    let currentClock = seg.heure_depart;

    html += `<div class="summary-container">`;
    html += `<div class="summary-step start">📍 Départ à <strong>${currentClock}</strong></div>`;

    if (seg.legs) {
        seg.legs.forEach((leg, i) => {
            currentClock = addTimeToString(currentClock, leg.duration);

            if (i < seg.sousEtapes.length) {
                const se = seg.sousEtapes[i];
                html += `<div class="summary-step sub">
                            <div class="sub-arrival">Arrivée à ${getNomSimple(se.nom)} : <strong>${currentClock}</strong></div>
                            <span class="sub-pause">☕ Pause : ${se.heure}</span>
                         </div>`;
                currentClock = addTimeToString(currentClock, durationStringToSeconds(se.heure));
            }
        });
    }

    html += `<div class="summary-step end">🏁 Arrivée estimée : <strong>${currentClock}</strong></div>`;

    const distKm     = (seg.distance / 1000).toFixed(1);
    const totalTime  = formatDuration(seg.duration);
    const iconMode   = seg.modeTransport === 'Voiture' ? '🚗' : (seg.modeTransport === 'Velo' ? '🚲' : '🚶');

    html += `<div class="summary-stats">${iconMode} <strong>${distKm} km</strong> parcourus en <strong>${totalTime}</strong></div>`;
    html += `</div>`;

    ul.innerHTML = html;
}

// ============================================================
// 9. ÉDITEUR DE SOUS-ÉTAPES
// ============================================================

function openSegmentEditor(index) {
    state.currentSegmentIndex = index;
    const seg = state.segments[index];

    document.getElementById('segmentFormContainer').style.display = 'block';
    document.getElementById('segmentTitle').textContent = `${seg.startNameSimple} → ${seg.endNameSimple}`;

    const container = document.getElementById('subEtapesContainer');
    container.innerHTML = '';
    state.subStepEditors = {};

    if (seg.sousEtapes && seg.sousEtapes.length > 0) {
        seg.sousEtapes.forEach(se => addSousEtapeForm(container, se));
    } else {
        addSousEtapeForm(container);
    }
}

function addSousEtapeForm(targetContainer, data = {}) {
    const template = document.getElementById('template-sub-etape');
    if (!template) {
        console.error("ERREUR FATALE : Le template 'template-sub-etape' est introuvable");
        return;
    }

    const clone    = template.content.cloneNode(true);
    const div      = clone.querySelector('.subEtape');
    const inputNom = div.querySelector('.subEtapeNom');

    inputNom.value = data.nom || '';
    div.querySelector('.subEtapeHeure').value = data.heure || '';

    const editorContainer = div.querySelector('.subEtapeEditorContainer');
    const uniqueId = 'editor-' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);

    if (editorContainer) {
        editorContainer.id = uniqueId;
    } else {
        console.error("Erreur: .subEtapeEditorContainer introuvable dans le template HTML");
    }

    targetContainer.appendChild(div);
    initAutocomplete(inputNom);

    setTimeout(() => {
        if (!document.getElementById(uniqueId)) return;

        state.subStepEditors[uniqueId] = new toastui.Editor({
            el: document.querySelector('#' + uniqueId),
            height: '350px',
            initialEditType: 'wysiwyg',
            previewStyle: 'vertical',
            initialValue: data.remarque || '',
            placeholder: 'Décrivez cette étape et glissez-y vos photos !',
            language: 'fr-FR',
            usageStatistics: false,
            hideModeSwitch: true,
            toolbarItems: [
                ['heading', 'bold', 'italic', 'strike'],
                ['hr', 'quote'],
                ['ul', 'ol', 'task'],
                ['table', 'image', 'link']
            ],
            hooks: {
                addImageBlobHook: async (blob, callback) => {
                    try {
                        const compressedFile = await compresserImageJS(blob, 0.7, 1200);
                        const formData = new FormData();
                        formData.append('image', compressedFile);

                        const response = await fetch(UPLOAD_IMAGE_URL, {
                            method: 'POST',
                            body: formData,
                            headers: {
                                'X-Requested-With': 'XMLHttpRequest',
                                'X-CSRF-Token': CSRF_TOKEN
                            }
                        });

                        if (response.ok) {
                            const result = await response.json();
                            if (result.success && result.url) {
                                callback(result.url, "Photo de l'étape");
                            } else {
                                alert("Erreur serveur : " + result.message);
                            }
                        } else {
                            alert("Échec de la connexion lors de l'envoi de l'image.");
                        }
                    } catch (error) {
                        console.error("Erreur hook image:", error);
                        alert("Impossible de traiter cette image.");
                    }
                }
            }
        });
    }, 100);

    div.querySelector('.removeSubEtapeBtn').addEventListener('click', () => {
        if (state.subStepEditors[uniqueId]) {
            state.subStepEditors[uniqueId].destroy();
            delete state.subStepEditors[uniqueId];
        }
        div.remove();
    });
}

// ============================================================
// 10. FORMULAIRE D'AJOUT DE SEGMENT
// ============================================================

function openNewSegmentForm() {
    const btnAddSegment       = document.getElementById('btnAddSegment');
    const newBlockFormContainer = document.getElementById('newBlockForm');

    btnAddSegment.style.display = 'none';
    let html = '';

    if (!state.currentStartCoords) {
        html += `<div class="new-block-field"><label class="new-block-label">Départ :</label><input type="text" id="inputStartBlock" class="new-block-input"></div>`;
    } else {
        html += `<div class="new-block-static"><strong>Départ :</strong> ${state.currentStartCity}</div>`;
    }

    html += `<div class="new-block-field"><label class="new-block-label">Arrivée :</label><input type="text" id="inputEndBlock" class="new-block-input"></div>
             <div class="new-block-actions">
                <button id="btnCancelBlock" class="btn-block-action btn-block-cancel">Annuler</button>
                <button id="btnValidateBlock" class="btn-block-action btn-block-validate">Valider</button>
             </div>`;

    newBlockFormContainer.innerHTML = html;

    newBlockFormContainer.classList.remove('hidden');
    newBlockFormContainer.style.display = 'block';

    const inputStart = document.getElementById('inputStartBlock');
    if (inputStart) {
        const selectStart = createFavSelectForInput('inputStartBlock');
        if (selectStart) inputStart.parentNode.insertBefore(selectStart, inputStart);
        initAutocomplete(inputStart);
    }

    const inputEnd = document.getElementById('inputEndBlock');
    if (inputEnd) {
        const selectEnd = createFavSelectForInput('inputEndBlock');
        if (selectEnd) inputEnd.parentNode.insertBefore(selectEnd, inputEnd);
        initAutocomplete(inputEnd);
    }

    document.getElementById('btnCancelBlock').addEventListener('click', () => {
        newBlockFormContainer.innerHTML = '';
        newBlockFormContainer.style.display = 'none';
        btnAddSegment.style.display = 'block';
    });

    document.getElementById('btnValidateBlock').addEventListener('click', () => validateNewSegment());
}

async function validateNewSegment() {
    const btn = document.getElementById('btnValidateBlock');
    btn.disabled = true;
    btn.textContent = "Calcul...";

    let startName, startCoords;
    const inputStartEl = document.getElementById('inputStartBlock');

    if (inputStartEl) {
        const lat  = inputStartEl.getAttribute('data-lat');
        const lon  = inputStartEl.getAttribute('data-lon');
        startName  = inputStartEl.value.trim();
        startCoords = (lat && lon) ? [parseFloat(lat), parseFloat(lon)] : await getCoordinate(startName);

        if (!startCoords) {
            alert('Départ introuvable');
            btn.disabled = false;
            return;
        }
        addMarker(startName, startCoords, "ville", startName);
    } else {
        startName   = state.currentStartCity;
        startCoords = state.currentStartCoords;
    }

    const inputEndEl = document.getElementById('inputEndBlock');
    const eLat = inputEndEl.getAttribute('data-lat');
    const eLon = inputEndEl.getAttribute('data-lon');
    const endName   = inputEndEl.value.trim();
    const endCoords = (eLat && eLon) ? [parseFloat(eLat), parseFloat(eLon)] : await getCoordinate(endName);

    if (!endCoords) {
        alert('Arrivée introuvable');
        btn.disabled = false;
        return;
    }

    await _ajouterSegmentEntre(startName, startCoords, endName, endCoords, state.segments.length, TRANSPORT_STRATEGIES['Voiture']);
    addMarker(endName, endCoords, "ville", endName);

    state.currentStartCity   = endName;
    state.currentStartCoords = endCoords;

    document.getElementById('newBlockForm').innerHTML = '';
    document.getElementById('newBlockForm').style.display = 'none';
    document.getElementById('btnAddSegment').style.display = 'block';

}

// ============================================================
// 11. SAUVEGARDE DU ROADTRIP
// ============================================================
/**
 * Enregistre le roadtrip actuel via une requête POST.
 * @async
 * @function handleSaveRoadtrip
 * @returns {Promise<void>}
 */
async function handleSaveRoadtrip(e) {
    if (state.segments.length === 0) {
        alert('Votre RoadTrip est vide ! Ajoutez au moins un trajet.');
        return;
    }

    const btn    = document.getElementById('saveRoadtrip');
    const oldTxt = btn.textContent;
    btn.textContent = "Sauvegarde en cours...";
    btn.disabled    = true;

    try {
        const formData = new FormData();

        if (e.target.dataset.id) formData.append('id', e.target.dataset.id);

        formData.append('title',       document.getElementById('roadtripTitle').value);
        formData.append('description', document.getElementById('roadtripDescription').value);
        formData.append('visibility',  document.getElementById('roadtripVisibilite').value);
        formData.append('status',      document.getElementById('roadtripStatut').value);
        formData.append('place',       document.getElementById('regionSelect').value);

        const fileInput = document.getElementById('roadtripPhoto');
        if (fileInput.files.length > 0) {
            const originalFile = fileInput.files[0];
            if (originalFile.type.startsWith('image/')) {
                try {
                    const compressed = await compresserImageJS(originalFile, 0.7, 1200);
                    formData.append('photo_cover', compressed);
                } catch (err) {
                    console.warn("Erreur compression, envoi fichier original", err);
                    formData.append('photo_cover', originalFile);
                }
            } else {
                formData.append('photo_cover', originalFile);
            }
        }

        const cleanTrajets = state.segments.map((s, index) => {
            const timeInput = document.querySelector(`li[data-index="${index}"] .legend-time-input`);
            const cleanSousEtapes = s.sousEtapes.map(se => {
                let desc = (se.remarque || "").replace(/!\[.*?\]\(data:image\/[^)]+\)/g, '[Image lourde retirée]');
                return {
                    nom:     se.nom,
                    heure:   se.heure,
                    remarque: desc,
                    lat:     se.lat || (se.coords ? se.coords[0] : null),
                    lon:     se.lon || (se.coords ? se.coords[1] : null)
                };
            });
            return {
                depart:       s.startName,
                departLat:    s.startCoord[0], departLon:   s.startCoord[1],
                arrivee:      s.endName,
                arriveeLat:   s.endCoord[0],   arriveeLon:  s.endCoord[1],
                mode:         s.modeTransport,
                date_trajet:  s.date,
                heure_depart: timeInput ? timeInput.value : '08:00',
                sousEtapes:   cleanSousEtapes
            };
        });

        const blob = new Blob([JSON.stringify(cleanTrajets)], { type: 'application/json' });
        formData.append('trajets_file', blob, 'trajets.json');

        const saveUrl   = typeof SAVE_URL    !== 'undefined' ? SAVE_URL    : '/roadtrips/add';
        const csrfToken = typeof CSRF_TOKEN  !== 'undefined' ? CSRF_TOKEN  : '';

        const resp = await fetch(saveUrl, {
            method: 'POST',
            body: formData,
            headers: {
                'X-Requested-With': 'XMLHttpRequest',
                'X-CSRF-Token': csrfToken,
                'Accept': 'application/json'
            }
        });

        if (resp.ok) {
            const json = await resp.json();
            if (json.success) {
                alert("🎉 Sauvegardé avec succès !");
                window.location.href = "/roadtrips/my-roadtrips";
            } else {
                alert("Erreur : " + (json.message || "Erreur inconnue"));
            }
        } else {
            const errorJson = await resp.json().catch(() => ({}));
            console.error("Erreur Serveur:", errorJson);
            let errorMsg = "Une erreur est survenue lors de la sauvegarde.";
            if (errorJson.details) {
                errorMsg += "\n\nDétails :";
                for (const [field, errors] of Object.entries(errorJson.details)) {
                    errorMsg += `\n- ${field} : ${Object.values(errors).join(', ')}`;
                }
            } else if (errorJson.message) {
                errorMsg += "\n" + errorJson.message;
            }
            alert(errorMsg);
        }

    } catch (err) {
        console.error("Erreur JS Critique :", err);
        alert("Une erreur technique inattendue est survenue. Vérifiez la console.");
    } finally {
        btn.textContent = oldTxt;
        btn.disabled    = false;
    }
}

// ============================================================
// 12. ASSISTANT IA
// ============================================================
/**
 * Gère la génération de trajet via l'IA.
 * @async
 * @function handleGenerateAI
 * @returns {Promise<void>}
 */
async function handleGenerateAI() {
    const depart      = document.getElementById('aiDepart').value.trim();
    const destination = document.getElementById('aiDestination').value.trim();
    const duree       = document.getElementById('aiDuree').value.trim();
    const theme       = document.getElementById('aiTheme').value.trim();

    if (!depart || !destination) {
        alert("Veuillez indiquer au moins une ville de départ et une destination.");
        return;
    }

    const btn       = document.getElementById('btnGenerateAI');
    const loader    = document.getElementById('aiLoading');
    const resultBox = document.getElementById('aiResultBox');

    loader.style.display = 'block';
    resultBox.style.display = 'none';
    btn.disabled    = true;
    btn.textContent = "⏳ Génération en cours...";

    const formData = new FormData();
    formData.append('depart',      depart);
    formData.append('destination', destination);
    formData.append('duree',       duree);
    formData.append('theme',       theme);

    const urlAI = typeof AI_GENERATE_URL !== 'undefined' ? AI_GENERATE_URL : '/roadtrips/genererRoadtripGratuit';
    const token = typeof CSRF_TOKEN      !== 'undefined' ? CSRF_TOKEN      : '';

    try {
        const response = await fetch(urlAI, {
            method: 'POST',
            headers: { 'X-CSRF-Token': token, 'X-Requested-With': 'XMLHttpRequest' },
            body: formData
        });
        const result = await response.json();

        if (result.success && result.data) {
            const data       = result.data;
            const titleInput = document.getElementById('roadtripTitle');
            const descInput  = document.getElementById('roadtripDescription');

            let shouldFill = true;
            if (titleInput.value !== '' || descInput.value !== '') {
                shouldFill = confirm("L'IA a généré un titre et une description. Voulez-vous remplacer votre texte actuel ?");
            }

            if (shouldFill) {
                if (data.titre)       titleInput.value = data.titre;
                if (data.description) descInput.value  = data.description;
                titleInput.style.backgroundColor = "#d5f5e3";
                setTimeout(() => { titleInput.style.backgroundColor = ""; }, 1500);
            }

            let htmlEtapes = '<ul>';
            if (data.etapes && Array.isArray(data.etapes)) {
                data.etapes.forEach(etape => {
                    htmlEtapes += `<li>
                        <strong>${etape.ville}</strong>
                        <br><small style="color:var(--gris_fonce)">👀 À voir : ${etape.lieux || etape.activites || 'Centre ville'}</small>
                    </li>`;
                });
            } else {
                htmlEtapes += '<li>Aucune étape spécifique suggérée, trajet direct.</li>';
            }
            htmlEtapes += '</ul>';

            document.getElementById('aiResultContent').innerHTML = htmlEtapes;
            resultBox.style.display = 'block';
        } else {
            alert("L'IA n'a pas pu générer de résultat : " + (result.message || 'Erreur inconnue'));
        }
    } catch (error) {
        console.error("Erreur Fetch IA:", error);
        alert("Une erreur de connexion est survenue avec l'assistant IA.");
    } finally {
        loader.style.display = 'none';
        btn.disabled    = false;
        btn.textContent = "🚀 Générer des idées";
    }
}

// ============================================================
// 13. CHARGEMENT DU MODE ÉDITION
// ============================================================

async function loadExistingRoadTrip() {
    for (let i = 0; i < EXISTING_TRAJETS.length; i++) {
        const t = EXISTING_TRAJETS[i];

        const startCoords = (t.departLat && t.departLon)
            ? [parseFloat(t.departLat), parseFloat(t.departLon)]
            : await getCoordinate(t.depart);

        const endCoords = (t.arriveeLat && t.arriveeLon)
            ? [parseFloat(t.arriveeLat), parseFloat(t.arriveeLon)]
            : await getCoordinate(t.arrivee);

        let sousEtapesWithCoords = [];
        if (t.sousEtapes && t.sousEtapes.length > 0) {
            for (const se of t.sousEtapes) {
                const c = (se.lat && se.lon)
                    ? [parseFloat(se.lat), parseFloat(se.lon)]
                    : await getCoordinate(se.nom);
                if (c) {
                    sousEtapesWithCoords.push({ ...se, coords: c });
                    addMarker(se.nom, c, "sous_etape", `<b>${se.nom}</b><br>${se.heure}`);
                }
            }
        }

        if (startCoords && endCoords) {
            addMarker(t.depart,  startCoords, "ville", t.depart);
            addMarker(t.arrivee, endCoords,   "ville", t.arrivee);

            await _ajouterSegmentEntre(
                t.depart, startCoords,
                t.arrivee, endCoords,
                state.segments.length,
                TRANSPORT_STRATEGIES[t.mode],
                { mode: t.mode, date_trajet: t.date_trajet || t.date, heure_depart: t.heure_depart, sousEtapes: sousEtapesWithCoords }
            );

            state.currentStartCity   = t.arrivee;
            state.currentStartCoords = endCoords;
        }
    }

    if (state.segments.length > 0) {
        const group = new L.featureGroup(state.segments.map(s => s.line));
        state.map.fitBounds(group.getBounds(), { padding: [50, 50] });
    }
}

// ============================================================
// 14. BINDING DES ÉVÉNEMENTS
// ============================================================

function bindEvents() {
    const regionSelect = document.getElementById('regionSelect');
    if (regionSelect) {
        regionSelect.addEventListener('change', (e) => {
            state.currentRegion = e.target.value;
            const config = REGIONS_CONFIG[state.currentRegion];
            state.map.flyTo(config.center, config.zoom, { duration: 1.5 });
        });
    }

    const statusSelect     = document.getElementById('roadtripStatut');
    const visibilitySelect = document.getElementById('roadtripVisibilite');
    if (visibilitySelect) visibilitySelect.disabled = false;
    if (statusSelect) {
        statusSelect.addEventListener('change', () => {
            if (visibilitySelect) visibilitySelect.disabled = false;
        });
    }

    const btnAddSegment = document.getElementById('btnAddSegment');
    if (btnAddSegment) {
        btnAddSegment.addEventListener('click', openNewSegmentForm);
    }

    document.getElementById('legendList').addEventListener('click', (e) => {
        if (e.target.classList.contains('modifierSousEtapes')) {
            openSegmentEditor(e.target.closest('li').dataset.index);
        }
        if (e.target.classList.contains('toggleSousEtapes')) {
            const ul = e.target.closest('li').querySelector('.sousEtapesList');
            ul.style.display = (ul.style.display === 'none') ? 'block' : 'none';
        }

        const removeBtn = e.target.closest('.remove-segment-btn');
        if (removeBtn) {
            const segmentLi       = removeBtn.closest('li');
            const currentIndex    = parseInt(segmentLi.dataset.index);
            const allSegments     = Array.from(document.querySelectorAll('#legendList > li:not(.fade-out-item)'));
            const currentDomIndex = allSegments.indexOf(segmentLi);

            if (currentDomIndex < allSegments.length - 1) {
                showToast("Veuillez d'abord supprimer les trajets suivants pour ne pas briser l'itinéraire.");
                return;
            }

            segmentLi.classList.add('fade-out-item');
            removeSegmentFromMap(currentIndex);
            setTimeout(() => segmentLi.remove(), 300);
        }
    });

    document.getElementById('addSubEtape').onclick  = () => addSousEtapeForm(document.getElementById('subEtapesContainer'));
    document.getElementById('closeSegmentForm').onclick = () => {
        document.getElementById('segmentFormContainer').style.display = 'none';
    };
    document.getElementById('saveSegment').onclick = async () => {
        const seg     = state.segments[state.currentSegmentIndex];
        const newSubs = [];

        for (const div of document.querySelectorAll('.subEtape')) {
            const inputNom = div.querySelector('.subEtapeNom');
            const nom      = inputNom.value.trim();
            const heure    = div.querySelector('.subEtapeHeure').value;
            if (!nom || !heure) continue;

            const containerEl = div.querySelector('.subEtapeEditorContainer');
            let remarque = "";
            if (containerEl && state.subStepEditors[containerEl.id]) {
                remarque = state.subStepEditors[containerEl.id].getMarkdown();
            }

            const latAttr = inputNom.getAttribute('data-lat');
            const lonAttr = inputNom.getAttribute('data-lon');
            const coords  = (latAttr && lonAttr) ? [parseFloat(latAttr), parseFloat(lonAttr)] : await getCoordinate(nom);

            if (coords) {
                newSubs.push({ nom, heure, remarque, coords, lat: coords[0], lon: coords[1] });
                addMarker(nom, coords, "sous_etape", `<b>${nom}</b><br>${heure}`);
            } else {
                alert(`Impossible de localiser : ${nom}. Veuillez sélectionner une suggestion.`);
            }
        }

        seg.sousEtapes = newSubs;
        await updateRouteSegment(state.currentSegmentIndex, seg.modeTransport || 'Voiture');
        document.getElementById('segmentFormContainer').style.display = 'none';
    };

    const saveBtn = document.getElementById('saveRoadtrip');
    if (saveBtn) saveBtn.onclick = handleSaveRoadtrip;

    const btnGenerateAI = document.getElementById('btnGenerateAI');
    if (btnGenerateAI) btnGenerateAI.addEventListener('click', handleGenerateAI);
}

// ============================================================
// 15. POINT D'ENTRÉE
// ============================================================
/**
 * Point d'entrée principal initialisant la carte, les favoris et les événements.
 * @async
 * @function init
 * @returns {Promise<void>}
 */
async function init() {
    initRoadTripMap();

    await fetchUserFavorites();
    loadMapFavorites();

    state.currentStartCity = (typeof USER_DEFAULT_CITY !== 'undefined') ? USER_DEFAULT_CITY : "";

    if (typeof MODE_EDITION !== 'undefined' && MODE_EDITION === true && typeof EXISTING_TRAJETS !== 'undefined') {
        await loadExistingRoadTrip();
    } else if (state.currentStartCity) {
        const coords = await getCoordinate(state.currentStartCity);
        if (coords) {
            state.currentStartCoords = coords;
            addMarker(state.currentStartCity, coords, "ville", `Départ : ${state.currentStartCity}`);
            state.map.setView(coords, 10);
        }
    }

    bindEvents();
}

document.addEventListener('DOMContentLoaded', () => {
    if (document.getElementById('map')) {
        init();
    }
});