/**
 * Algorithmes de calcul de plus court chemin pour les voyages
 */

/**
 * Classe pour le calcul de plus court chemin (algorithme de Dijkstra)
 */
export class PathFinder {
    /**
     * Construit un graphe à partir des données de voyage
     * @param {Array} travelData - Données de voyage
     * @param {String|Array} modes - Mode(s) de transport ('road', 'river', 'sea', ['road', 'river', 'sea'], 'water')
     * @returns {Object} Graphe avec adjacence et poids
     */
    static buildGraph(travelData, modes = 'road') {
        const graph = {};

        // Normaliser modes en tableau
        let modeList = [];
        if (modes === 'water') {
            modeList = ['river', 'sea'];
        } else if (modes === 'mixed') {
            modeList = ['road', 'river', 'sea'];
        } else if (Array.isArray(modes)) {
            modeList = modes;
        } else {
            modeList = [modes];
        }

        for (const route of travelData) {
            const from = route.from;
            const to = route.to;

            // Initialiser les nœuds
            if (!graph[from]) {
                graph[from] = [];
            }
            if (!graph[to]) {
                graph[to] = [];
            }

            // Pour chaque mode de transport disponible
            for (const mode of modeList) {
                const distanceKey = `${mode}_distance`;
                const daysKey = `${mode}_days`;
                const distance = route[distanceKey];
                const days = route[daysKey];

                // Ignorer les routes sans ce mode de transport
                if (!distance || distance === "" || !days || days === "") {
                    continue;
                }

                // Ajouter les arêtes (bidirectionnelles)
                graph[from].push({
                    destination: to,
                    distance: parseFloat(distance),
                    days: parseFloat(days),
                    danger: route[`${mode}_danger`] || "",
                    mode: mode
                });

                graph[to].push({
                    destination: from,
                    distance: parseFloat(distance),
                    days: parseFloat(days),
                    danger: route[`${mode}_danger`] || "",
                    mode: mode
                });
            }
        }

        return graph;
    }

    /**
     * Algorithme de Dijkstra pour trouver le plus court chemin
     * @param {Object} graph - Graphe d'adjacence
     * @param {String} start - Ville de départ
     * @param {String} end - Ville d'arrivée
     * @param {String} metric - Métrique à minimiser ('distance' ou 'days')
     * @returns {Object|null} Chemin trouvé avec détails ou null si pas de chemin
     */
    static dijkstra(graph, start, end, metric = 'days') {
        // Normaliser les noms de villes
        const normalizedStart = this.findCityInGraph(graph, start);
        const normalizedEnd = this.findCityInGraph(graph, end);

        if (!normalizedStart || !normalizedEnd) {
            console.warn(`PathFinder: Ville non trouvée - start: ${start}, end: ${end}`);
            return null;
        }

        // Initialisation
        const distances = {};
        const previous = {};
        const visited = new Set();
        const queue = [];

        // Initialiser toutes les distances à l'infini
        for (const node in graph) {
            distances[node] = Infinity;
            previous[node] = null;
        }

        distances[normalizedStart] = 0;
        queue.push({ node: normalizedStart, distance: 0 });

        while (queue.length > 0) {
            // Trouver le nœud avec la plus petite distance
            queue.sort((a, b) => a.distance - b.distance);
            const { node: current } = queue.shift();

            if (visited.has(current)) continue;
            visited.add(current);

            // Si on a atteint la destination
            if (current === normalizedEnd) {
                return this.reconstructPath(previous, normalizedStart, normalizedEnd, graph, metric);
            }

            // Explorer les voisins
            const neighbors = graph[current] || [];
            for (const neighbor of neighbors) {
                if (visited.has(neighbor.destination)) continue;

                const weight = neighbor[metric]; // 'distance' ou 'days'
                const newDistance = distances[current] + weight;

                if (newDistance < distances[neighbor.destination]) {
                    distances[neighbor.destination] = newDistance;
                    previous[neighbor.destination] = {
                        from: current,
                        edge: neighbor
                    };
                    queue.push({ node: neighbor.destination, distance: newDistance });
                }
            }
        }

        // Pas de chemin trouvé
        console.warn(`PathFinder: Aucun chemin trouvé entre ${start} et ${end}`);
        return null;
    }

    /**
     * Trouve une ville dans le graphe (insensible à la casse)
     * @param {Object} graph - Graphe
     * @param {String} cityName - Nom de la ville
     * @returns {String|null} Nom normalisé de la ville ou null
     */
    static findCityInGraph(graph, cityName) {
        const lowerName = cityName.toLowerCase();
        for (const city in graph) {
            if (city.toLowerCase() === lowerName) {
                return city;
            }
        }
        return null;
    }

    /**
     * Reconstruit le chemin à partir des prédécesseurs
     * @param {Object} previous - Map des prédécesseurs
     * @param {String} start - Ville de départ
     * @param {String} end - Ville d'arrivée
     * @param {Object} graph - Graphe d'adjacence
     * @param {String} metric - Métrique utilisée
     * @returns {Object} Détails du chemin
     */
    static reconstructPath(previous, start, end, graph, metric) {
        const path = [];
        let current = end;
        let totalDistance = 0;
        let totalDays = 0;
        let maxDanger = "";
        const modesUsed = new Set();

        // Reconstruire le chemin en remontant
        while (current !== start) {
            const prev = previous[current];
            if (!prev) break;

            const mode = prev.edge.mode || 'road';
            modesUsed.add(mode);

            path.unshift({
                from: prev.from,
                to: current,
                distance: prev.edge.distance,
                days: prev.edge.days,
                danger: prev.edge.danger,
                mode: mode
            });

            totalDistance += prev.edge.distance;
            totalDays += prev.edge.days;

            // Calculer le danger maximum
            const dangerLevel = (prev.edge.danger || "").length;
            const currentMaxLevel = maxDanger.length;
            if (dangerLevel > currentMaxLevel) {
                maxDanger = prev.edge.danger;
            }

            current = prev.from;
        }

        return {
            path: path,
            totalDistance: totalDistance,
            totalDays: totalDays,
            maxDanger: maxDanger,
            steps: path.length,
            modesUsed: Array.from(modesUsed)
        };
    }
}
