/**
 * @fileoverview map-gen-mgr.js
 * Engine für passive Docking-Logik & Präzisions-Zentrierung auf Spiderfied-Marker.
 */

import { calculateClusterColor } from './marker-display-utils.js';
import { mylog, getColorCodeAndText } from './utilities.js';
import { updateClusterIcon, willClusterSpiderfy, isSquareType } from './map-cluster-mgr.js';
import { UpdateMarkerIcon } from './map-poi-mgr.js';
import { getPoiStore, getPoiData } from './poi-store.js';

import { demarcateRoute } from './navigation-co.js';
import { L,     mapState, getMap, setMap, getMarkers, setMarkers, loadLeafletResources } from './map-service.js';

// --- 1. ZENTRALER ZUSTAND ---
let appCoordinator = null;




// --- 2. BASIS-GETTER & EXPORTS ---
export { getMap, getMarkers }; // Nutzt nun korrekt die Service-Methoden
export function registerCoordinator(instance) {
    appCoordinator = instance;
}

// --- 3. INTERNE HELPER (NAVIGATION) ---
/*
function waitForMap(mapInstance) {
    return new Promise(resolve => {
        mapInstance.once('moveend', resolve);
    });*/
async function waitForMap(m) {
    return new Promise(resolve => {
        let resolved = false;
        const controller = new AbortController();

        const done = (msg) => {
            if (resolved) return;
            resolved = true;
            mylog(`[waitForMap] ${msg}`);
            resolve();
        };

        // 1. Der normale Weg
        m.once('moveend', () => done('Event moveend gefeuert'));

        // 2. Der harte Fallback (Sicherheitsnetz)
        setTimeout(() => done('Timeout Fallback (moveend kam nicht!)'), 500);
    });
}
/**
 * Berechnet die Zentrierung unter Berücksichtigung des Bottom-Sheet Offsets.
 */
/**
 * Zentriert den Marker exakt in deinem optischen Zentrum (vp.centerY).
 * Nutzt reine Container-Pixel, um den X-Drift bei Expanded Sheet zu verhindern.
 */
async function moveMapToCenter(m, latlng, zoom, vp) {
    const tZoom = zoom || m.getZoom();

    // 1. Karte erst einmal stumpf auf den Marker zoomen/setzen (X/Y Mitte)
    m.setView(latlng, tZoom, { animate: false });

    // 2. Jetzt den vertikalen Versatz berechnen
    // Geometrische Mitte minus dein Wunsch-Zentrum
    const pixelOffset = (m.getSize().y / 2) - vp.centerY;

    // 3. Das aktuelle Zentrum (das gerade auf dem Marker liegt) in Pixeln greifen
    const centerP = m.latLngToContainerPoint(m.getCenter());

    // 4. Nur den Y-Versatz anwenden. X bleibt absolut identisch (centerP.x).
    const targetP = L.point(centerP.x, centerP.y + pixelOffset);

    // 5. Zurück in Koordinaten
    const finalLatLng = m.containerPointToLatLng(targetP);

    mylog(`[CENTER-FIX] Offset: ${pixelOffset}px | X-Drift eliminiert.`);

    m.setView(finalLatLng, tZoom, { animate: true });
    return waitForMap(m);
}

/**
 * Berechnet Docking nur, wenn das Ziel den Viewport tatsächlich verlässt.
 */
function calculateDockingLatLng(mapInstance, targetLatLng, vp) {
    //   console.log("=== LOGGING: DOCKING BERECHNUNG ===");

    // 1. Ist-Zustand des Markers
    const p = mapInstance.latLngToContainerPoint(targetLatLng);

    // 3. Ziel-Berechnung (Wo soll der Marker hin?)
    const margin = 40;
    const targetX = Math.max(vp.left + margin, Math.min(p.x, vp.right - margin));
    const targetY = Math.max(vp.top + margin, Math.min(p.y, vp.bottom - margin));

    // 4. Differenz-Ermittlung
    const dx = p.x - targetX;
    const dy = p.y - targetY;

    // 5. Check: Muss überhaupt was getan werden?
    if (Math.abs(dx) < 1 && Math.abs(dy) < 1) {
        return null;
    }

    // 6. Karten-Zentrum verschieben
    const currentCenterPoint = mapInstance.latLngToContainerPoint(mapInstance.getCenter());

    const newCenterPoint = L.point(currentCenterPoint.x + dx, currentCenterPoint.y + dy);
    const finalLatLng = mapInstance.containerPointToLatLng(newCenterPoint);

    return finalLatLng;
}


// --- 4. INITIALISIERUNG & CLEANUP ---

export async function initMapMarkerService() {
    if (getMap()) return;

    // Ressourcen laden und warten, bis Leaflet (L) bereit ist
    await loadLeafletResources();

    // 1. CSS INJEKTION (Robust gegen "undefined" Fehler)
    if (!document.getElementById('cluster-ring-styles')) {
        const styleElement = document.createElement('style'); // Name geändert zur Sicherheit
        styleElement.id = 'cluster-ring-styles';
        styleElement.textContent = `
            .cluster-ring-outline {
                position: relative;
                display: flex;
                justify-content: center;
                align-items: center;
                border-radius: 50%;
box-shadow: 0 0 0 1px #d1d1d6, 0 2px 4px rgba(0,0,0, 0.5);
                border: none !important;
            }
            .cluster-ring-inner {
                position: absolute;
                width: 70%;
                height: 70%;
                background-color: white !important;
                border-radius: 50%;
                display: flex;
                justify-content: center;
                align-items: center;
                font-weight: 800;
                color: #1c1c1e;
                font-size: 13px;
                z-index: 2;
            }
            .cluster-ring-outline.is-square { border-radius: 4px; }
            .cluster-ring-outline.is-square .cluster-ring-inner { border-radius: 2px; }
            .leaflet-marker-cluster-empty { background: none !important; border: none !important; }
        `;
        document.head.appendChild(styleElement);
    }

    const mapInstance = L.map("map").setView([52.469, 13.4249], 10);
    L.tileLayer('https://{s}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}{r}.png', { maxZoom: 21 }).addTo(mapInstance);

    const CLUSTER_COLORS = {
        green: "#34c759", yellow: "yellow", orange: "#ff9500", red: "#ff3b30", gray: "#8e8e93"
    };

    const markersGroup = L.markerClusterGroup({
        showCoverageOnHover: false, maxZoom: 21, zoomToBoundsOnClick: true,spiderfyOnClick: false, spiderfyDistanceMultiplier: 3,
        polygonOptions: {
            fillColor: 'transparent',
            color: 'transparent',
            weight: 0,
            opacity: 0
        },
        iconCreateFunction: (cluster) => {
            const children = cluster.getAllChildMarkers();
            const total = children.length;
            const counts = { green: 0, yellow: 0, orange: 0, red: 0, gray: 0 };

            children.forEach(m => {
                const poi = getPoiData(m.options.gid);
                const { colorCode } = getColorCodeAndText(poi?.last_cleaned);
                const key = (colorCode && counts.hasOwnProperty(colorCode)) ? colorCode : 'gray';
                counts[key]++;
            });

            const pG = (counts.green / total) * 100;
            const pY = (counts.yellow / total) * 100;
            const pO = (counts.orange / total) * 100;
            const pR = (counts.red / total) * 100;

            const gradient = `conic-gradient(
                ${CLUSTER_COLORS.green} 0% ${pG}%, 
                ${CLUSTER_COLORS.yellow} ${pG}% ${pG + pY}%, 
                ${CLUSTER_COLORS.orange} ${pG + pY}% ${pG + pY + pO}%, 
                ${CLUSTER_COLORS.red} ${pG + pY + pO}% ${pG + pY + pO + pR}%, 
                ${CLUSTER_COLORS.gray} ${pG + pY + pO + pR}% 100%
            )`;

            const isSquare = isSquareType(cluster.getLatLng(), children);
            const size = isSquare ? 36 : 46;

            // Wir geben ein L.DivIcon zurück und bauen das HTML manuell zusammen
            return new L.DivIcon({
                html: `
                    <div class="cluster-ring-outline ${isSquare ? 'is-square' : ''}" 
                         style="background: ${gradient}; width: ${size}px; height: ${size}px;">
                        <div class="cluster-ring-inner">${total}</div>
                    </div>
                `,
                className: 'leaflet-marker-cluster-empty',
                iconSize: [size, size],
                iconAnchor: [size / 2, size / 2]
            });
        }
    });

    mapInstance.addLayer(markersGroup);
    
    // Instanzen im Service registrieren
    setMap(mapInstance);
    setMarkers(markersGroup);
}

export function removeAddressMarker() {
    const map = getMap();
    if (map && mapState.currentAddressMarker) {
        map.removeLayer(mapState.currentAddressMarker);
        mapState.currentAddressMarker = null;
    }
    mapState.poilessAddressData = null;
}

// --- 5. HAUPT-NAVIGATION ---

async function zoomUntilSpiderfy(cluster) {
    const m = getMap();
    const maxMapZoom = m.getMaxZoom();

    // Wir zoomen so lange rein, bis die Funktion "true" sagt 
    // oder wir nicht mehr weiter zoomen können.
    while (!willClusterSpiderfy(cluster) && m.getZoom() < maxMapZoom) {
        const nextZoom = m.getZoom() + 1;
        m.setZoom(nextZoom);
        // Warten, bis Leaflet das Cluster neu berechnet hat
        await new Promise(r => setTimeout(r, 100));
    }
}


/**
 * Zentrales Navigations-Hub der Karte.
 * Steuert Zoom, Zentrierung und Spiderfy basierend auf dem navigationMode (useCase).
 */
export async function fitMapToTarget(gid, coords, cluster = null, useCase = 'RESIZE', addressData = null) {
    const m = getMap();
    if (!m || !coords) return;

    // Aktuellen freien Bereich über dem Bottom Sheet berechnen
    const vp = getSafeViewport();

    // State-Sicherung für adressbasierte Suchen ohne POI-Eintrag
    mapState.poilessAddressData = (useCase === 'POILESS') ? { ...addressData } : null;

    // 1. Umkreis-Analyse für Zoom-Entscheidung
    let isSingleMarker = false;
    if (gid) {
        const poiData = getPoiData(gid);
        if (poiData) {
            const realcoords = { lat: poiData.lat, lng: poiData.lng };
            const nearbyGids = getNearbyGids(realcoords);
            // Wenn keine anderen Steine im 2m-Radius: Marker ist "alleinstehend"
            isSingleMarker = nearbyGids.length <= 1;
        }
    }

    // Zoom-Strategie: Einzelmarker brauchen Zoom 18, um Cluster-Strukturen aufzubrechen
    const minZoom = isSingleMarker ? 18 : 17;
    const targetZoom = Math.max(m.getZoom(), minZoom);

    // --- PHASE 1: Initialbewegung & Weiche ---

    if (useCase === 'CENTER' || useCase === 'POILESS') {
        // Suche/Neuanlage: Harte Zentrierung im Safe Viewport
        await moveMapToCenter(m, coords, targetZoom, vp);
    }
    else if (useCase === 'TOVIEWPORT') {
        // Header-Click/Swipe: Nur bewegen, wenn Marker aktuell verdeckt ist
        const point = m.latLngToContainerPoint(coords);
        const isVisible = (
            point.x >= vp.left &&
            point.x <= vp.right &&
            point.y >= vp.top &&
            point.y <= vp.bottom
        );

        if (!isVisible) {
            mylog("[fitMapToTarget] TOVIEWPORT: Marker verdeckt -> Sanftes Docking.");
            const dLL = calculateDockingLatLng(m, coords, vp);
            if (dLL) {
                m.panTo(dLL, { animate: true });
                await waitForMap(m);
            }
        } else {
            mylog("[fitMapToTarget] TOVIEWPORT: Marker bereits im Sichtfeld -> Keine Kartenbewegung.");
        }
    }
    else if (useCase === 'RESIZE') {
        // Standard-Klick auf Marker: Docking an die neue Sheet-Größe
        // HIER: Nutze immer die aktuelle Position des Markers/Clusters
        const currentPos = cluster ? cluster.getLatLng() : coords;
        const targetLatLng = calculateDockingLatLng(m, currentPos, vp);

        if (targetLatLng) {
            mylog(`[RESIZE] Minimal Move um den Marker sichtbar zu halten.`);
            m.panTo(targetLatLng, { animate: true });
            await waitForMap(m);
        }
    }

    // --- PHASE 2: Spiderfy & Präzisions-Korrektur ---
    return new Promise((resolve) => {
        setTimeout(async () => {
            let targetMarker = gid ? findMarkerByGid(gid) : cluster;
            if (!targetMarker) return resolve();

            // Native Koordinate aus dem Store/Marker-Ursprung
            const nativeLatLng = targetMarker.getLatLng();

            const markersGroup = getMarkers();
            if (!markersGroup) return resolve();

            // Wir nutzen die Logik aus deiner funktionierenden Version: 
            // Immer zoomToShowLayer aufrufen, außer wir wissen sicher, es ist ein Einzelmarker ohne GID
            markersGroup.zoomToShowLayer(targetMarker, async () => {
                let visibleParent = markersGroup.getVisibleParent(targetMarker);

                // SPIDERFY-LOGIK (Dein funktionierender Part)
                // Wir triggern Spiderfy, wenn der Parent ein Cluster ist
                if (visibleParent && (visibleParent._childCount || typeof visibleParent.spiderfy === 'function')) {

                    if (!visibleParent._spiderfied && typeof visibleParent.spiderfy === 'function') {
                        mylog("[fitMapToTarget] Triggere Spiderfy...");
                        visibleParent.spiderfy();
                    }

                    // Wichtig: Kurzer Timeout für die Plugin-Animation (wie in deinem alten Code 150ms)
                    await new Promise(r => setTimeout(r, 150));

                    // JETZT die spiderfied Koordinaten abgreifen
                    const spiderfiedLatLng = targetMarker.getLatLng();

                    if (useCase === 'CENTER') {
                        // Nach Suche: Den aufgefächerten Marker exakt zentrieren
                        await moveMapToCenter(m, spiderfiedLatLng, m.getZoom(), vp);
                    } else {
                        // Nach Klick/Resize: Minimal-Docking
                        const finalD = calculateDockingLatLng(m, spiderfiedLatLng, vp);
                        if (finalD) m.panTo(finalD, { animate: true });
                    }
                    resolve(visibleParent);
                }
                else {
                    // FALL: Einzelmarker (kein Spiderfy nötig)
                    if (useCase === 'CENTER') {
                        await moveMapToCenter(m, targetMarker.getLatLng(), m.getZoom(), vp);
                    } else {
                        const finalD = calculateDockingLatLng(m, targetMarker.getLatLng(), vp);
                        if (finalD) m.panTo(finalD, { animate: true });
                    }
                    resolve(targetMarker);
                }
            });
        }, 50);
    });
}

// --- 6. HILFSFUNKTIONEN ---

export function getSafeViewport() {
//    const mapSize = map.getSize();
    const map = getMap();
    const mapSize = map ? map.getSize() : { x: 0, y: 0 };
    const bs = document.getElementById('bottom-sheet');
    const bsH = (bs && bs.offsetHeight > 0) ? bs.offsetHeight : 0;
    const bsBuffer = 20; // Ein kleinerer, fester Wert reicht oft aus

    const topLimit = 60;
    const bottomLimit = mapSize.y - bsH - bsBuffer;

    return {
        top: topLimit,
        bottom: bottomLimit,
        left: 20,
        right: mapSize.x - 20,
        // Zentriert nun exakt zwischen Header und der "Puffer-Kante" über dem Sheet
        centerY: topLimit + (bottomLimit - topLimit) / 2
    };
}

export function findMarkerByGid(gid) {
    return getMarkers()?.getLayers().find(m => m.options?.gid == gid) || null;
}

export function demarcateElement(el) {
    if (!el) return;
    if (el.options?.gid) UpdateMarkerIcon(el, false);
    else if (el._icon) updateClusterIcon(el, false);
}


export function handlePoilessSelectionMap(d, origin) {
    const map = getMap();
    const pos = L.latLng(d.lat, d.lng);

    // Marker zuerst setzen
    if (map) mapState.currentAddressMarker = L.marker(pos).addTo(map);
    mapState.poilessAddressData = d; // gleich auch die Daten setzen

    // Dann die Map zentrieren / Docking
    fitMapToTarget(null, pos, null, 'POILESS', d, origin);
}
/*
export function getNearbyGids(c) {
    const sll = L.latLng(Number(c.lat), Number(c.lng));
    return Array.from(getPoiStore().values())
        .filter(p => L.latLng(Number(p.lat), Number(p.lng)).distanceTo(sll) <= 15)
        .map(p => parseInt(p.gid, 10))
        .sort((a, b) => a - b);
}*/
export function getNearbyGids(c) {
    const sll = L.latLng(Number(c.lat), Number(c.lng));
    const allPois = Array.from(getPoiStore().values());

    console.log("--- DEBUG START ---");
    console.log("Suche für Adresse:", c.street, c.houseNr);
    console.log("Anzahl POIs im Store:", allPois.length);

    const nearby = allPois.filter(p => {
        const pLat = Number(p.lat);
        const pLng = Number(p.lng);

        // Falls Koordinaten im Store fehlen oder 0 sind:
        if (!pLat || !pLng) return false;

        const poiLoc = L.latLng(pLat, pLng);
        const dist = poiLoc.distanceTo(sll);

        // Wir loggen ALLES, was unter 50m liegt, um zu sehen, ob wir knapp vorbei schießen
 /*
        if (dist < 50) {
            console.log(`Stein GID ${p.gid} gefunden! Distanz: ${dist.toFixed(2)}m. (Stimmt Adresse? ${p.street} ${p.houseNr})`);
        }
*/
        return dist <= 15;
    });

    console.log("Endergebnis GIDs:", nearby.map(p => p.gid));
    console.log("--- DEBUG ENDE ---");

    return nearby.map(p => parseInt(p.gid, 10)).sort((a, b) => a - b);
}

// zentralisierte Funktion um all obejekete zu löschen die vor einer "Map-Transation" mariert waren
export function demarcatePriorContext(clickedCluster, mapState) {

    const priorContext = determinePriorContext(clickedCluster, mapState);
    const activeElement = mapState.activeMarkerOrCluster;
    // von POIless adress
    removeAddressMarker();

 
// 1. Wenn eine Route aktiv ist, diese entfernen
// 1. Falls eine Route aktiv ist: Weg damit!
    if (mapState && mapState.isRouteActive) {
        const map = getMap();
        if (map) {
            demarcateRoute(map);
            mapState.isRouteActive = false;
            mylog("map-gen-mgr: Aktive Route beim Kontextwechsel entfernt.");
  //                  appCoordinator?.refreshBsTitle?.();  // NEU
        }
    }


    // marker oder cluster
    if (activeElement && ['OtherClusterChild', 'OtherClusterParent', 'SinglePOI', 'SameClusterChild', 'SameClusterParent'].includes(priorContext)) {
        demarcateElement(activeElement, mapState); // für marker UND cluster
    }
    // cluster 

    if (activeElement && ['OtherClusterChild', 'OtherClusterParent',  'SinglePOI'].includes(priorContext)) {
        if (activeElement instanceof L.MarkerCluster) {
            activeElement.unspiderfy();
        }
        mapState.currentlySpiderfiedCluster = null;  //mapstate update
    }

}

function determinePriorContext(targetElement, state) {
    if (!targetElement) return 'OtherClusterParent';

    const activeElement = state.activeMarkerOrCluster;
    if (!activeElement) return 'None';

    // 1. Identitätsprüfung
    if (activeElement === targetElement) {
        // Ist es ein Cluster?
        if (typeof targetElement.getAllChildMarkers === 'function') {
            return 'SameClusterParent';
        }

        // Es ist ein POI. Jetzt prüfen: Gehört er zu einem Cluster-Kontext?
        // Leaflet.markercluster fügt Kindern oft eine Referenz auf den Parent hinzu (__parent)
        // ODER wir prüfen deinen mapState
        if (targetElement.__parent || state.parentClusterOfActiveMarker) {
            return 'SameClusterChild'; // <--- Das ist die Korrektur für deine Logik
        }

        return 'SinglePOI';
    }

    const targetIsCluster = typeof targetElement.getAllChildMarkers === 'function';
    const activeIsCluster = typeof activeElement.getAllChildMarkers === 'function';

    // 2. Ziel ist ein Cluster
    if (targetIsCluster) {
        // Prüfung: Ist der aktuell aktive POI ein Kind des angeklickten Clusters?
        if (!activeIsCluster) {
            const isChildOfTarget = targetElement.getAllChildMarkers()
                .some(m => m.options.gid === activeElement.options.gid);

            if (isChildOfTarget) {
                return 'SameClusterParent';
            }
        }
        return 'OtherClusterParent';
    }

    // 3. Ziel ist ein POI

    // Fall A: Von einem Cluster zu einem seiner Kinder
    if (activeIsCluster) {
        const isChildOfActive = activeElement.getAllChildMarkers()
            .some(m => m.options.gid === targetElement.options.gid);

        return isChildOfActive ? 'SameClusterChild' : 'SinglePOI';
    }

    // Fall B: Von POI zu POI (Geschwister-Prüfung)
    const activeParent = activeElement.__parent;
    const targetParent = targetElement.__parent;

    if (activeParent && targetParent && activeParent === targetParent) {
        return 'SameClusterChild';
    }

    return 'SinglePOI';
}

/**
 * Prüft, ob ein LatLng-Punkt außerhalb des aktuell berechneten SafeViewports liegt.
 */
export function isOutsideSafeViewport(latlng) {
    const m = getMap();
    if (!m || !latlng) return false;

    const vp = getSafeViewport();
    const p = m.latLngToContainerPoint(latlng);

    return (
        p.x < vp.left ||
        p.x > vp.right ||
        p.y < vp.top ||
        p.y > vp.bottom
    );
}
