package com.grigowashere.aismap.maps;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.util.Log;

import com.grigowashere.aismap.models.AISVessel;
import com.grigowashere.aismap.models.Vessel;
import com.grigowashere.aismap.utils.SettingsManager;
import com.grigowashere.aismap.controllers.VesselPathController;
import com.grigowashere.aismap.controllers.AppController;
import com.grigowashere.aismap.view.CursorOverlay;
import com.grigowashere.aismap.R;
import android.view.ViewGroup;

import org.json.JSONArray;
import org.json.JSONObject;
import org.maplibre.android.maps.MapView;
import org.maplibre.android.maps.MapLibreMap;
import org.maplibre.android.maps.Style;
import org.maplibre.android.storage.FileSource;
import org.maplibre.android.geometry.LatLng;
import org.maplibre.android.style.expressions.Expression;
import org.maplibre.android.style.layers.PropertyFactory;
import org.maplibre.android.style.layers.SymbolLayer;
import org.maplibre.android.style.sources.GeoJsonSource;

import java.util.HashMap;
import java.util.Map;

/**
 * Реализация MapInterface на базе MapLibre GL
 */
public class MapLibreMapImpl implements MapInterface {

    private static final String TAG = "MapLibreMapImpl";

    private static final String SOURCE_VESSELS = "vessels_source";
    private static final String LAYER_VESSELS = "vessels_layer";
    private static final String LAYER_VESSELS_STALE = "vessels_layer_stale";
    private static final String LAYER_STATUS_OVERLAY = "vessels_layer_status";
    private static final String SOURCE_OWN_PATH = "own_path_source";
    private static final String LAYER_OWN_PATH = "own_path_layer";
    private static final String SOURCE_OWN_PRED = "own_prediction_source";
    private static final String LAYER_OWN_PRED = "own_prediction_layer";
    private static final String SOURCE_AIS_PATHS = "ais_paths_source";
    private static final String LAYER_AIS_PATHS = "ais_paths_layer";
    private static final String SOURCE_AIS_PREDICTIONS = "ais_predictions_source";
    private static final String LAYER_AIS_PREDICTIONS = "ais_predictions_layer";
    private static final String IMAGE_VESSEL_OWN = "vessel_icon_own";
    private static final String IMAGE_VESSEL_A = "vessel_icon_a";
    private static final String IMAGE_VESSEL_B = "vessel_icon_b";
    // Имиджи, сопоставленные с ресурсами drawable (target_*.xml/png)
    // Базовые имена без класса; для выбора добавляем префикс target_a_ / target_b_
    private static final String TYPE_CARGO = "cargo";
    private static final String TYPE_TANKER = "tanker";
    private static final String TYPE_FISHING = "fishing";
    private static final String TYPE_PASSENGER = "passenger";
    private static final String TYPE_PLEASURE = "pleasure";
    private static final String TYPE_TUG = "tug";
    private static final String TYPE_NAVY = "navy";
    private static final String TYPE_OTHER = "other";
    private static final String IMAGE_VESSEL_LOSING = "vessel_icon_losing";
    // Status overlay drawable names present in res/drawable
    private static final String STATUS_UNDER_WAY_ENGINE = "engine";
    private static final String STATUS_AT_ANCHOR = "achor"; // anchor icon filename
    private static final String STATUS_MOORED = "moored"; // no dedicated icon yet
    private static final String STATUS_FISHING = "fishing";
    private static final String STATUS_SAILING = "sail";
    private static final boolean PATH_FEATURES_ENABLED = true;

    private final Context context;
    private final MapView mapView;
    private MapLibreMap maplibreMap;
    private Style style;
    private final SettingsManager settingsManager;
    private VesselPathController pathController;
    private AppController appController; // Для доступа к AIS VesselPathController
    private CursorOverlay cursorOverlay;
    private Vessel ownVessel;
    private final android.os.Handler uiHandler = new android.os.Handler(android.os.Looper.getMainLooper());
    private final android.os.Handler staleHandler = new android.os.Handler(android.os.Looper.getMainLooper());
    
    // Throttling для обновлений карты
    private final android.os.Handler mapUpdateHandler = new android.os.Handler(android.os.Looper.getMainLooper());
    private Runnable mapUpdateRunnable;
    private boolean mapUpdatePending = false;
    private Vessel pendingVesselUpdate = null;
    private static final long MAP_UPDATE_DELAY = 500; // 500ms throttling
    
    // Throttling для refreshGeoJson
    private final android.os.Handler geoJsonUpdateHandler = new android.os.Handler(android.os.Looper.getMainLooper());
    private Runnable geoJsonUpdateRunnable;
    private boolean geoJsonUpdatePending = false;
    private static final long GEOJSON_UPDATE_DELAY = 1000; // 1сек throttling для тяжелой операции
    private final Runnable staleRunnable = new Runnable() {
        @Override
        public void run() {
            try {
                boolean changed = false;
                java.util.List<String> toRemove = new java.util.ArrayList<>();
                int warnMin = settingsManager.getDataStaleWarningMinutes();
                int removeMin = settingsManager.getDataStaleRemoveMinutes();

                for (Map.Entry<String, AISVessel> e : idToAisVessel.entrySet()) {
                    String mmsi = e.getKey();
                    AISVessel v = e.getValue();
                    if (v == null) continue;
                    if (v.shouldBeRemoved(removeMin)) {
                        toRemove.add(mmsi);
                        continue;
                    }
                    boolean stale = v.isDataStale(warnMin);
                    JSONObject f = idToFeature.get(mmsi);
                    if (f != null) {
                        try {
                            JSONObject props = f.getJSONObject("properties");
                            boolean prev = props.optBoolean("stale", false);
                            if (prev != stale) {
                                props.put("stale", stale);
                                changed = true;
                            }
                        } catch (Exception ignore) {}
                    }
                }

                if (!toRemove.isEmpty()) {
                    // Удаляем суда без обновления UI
                    for (String m : toRemove) {
                        idToFeature.remove(m);
                        idToAisVessel.remove(m);
                        
                        // Очищаем путь AIS судна через AppController
                        if (appController != null) {
                            appController.clearAISVesselPath(m);
                        }
                        
                        // Очищаем путь и прогноз AIS судна
                        aisPathFeatures.remove(m);
                        aisPredictionFeatures.remove(m);
                    }
                    changed = true;
                }

                if (changed) {
                    // Переносим обновление GeoJSON на UI поток
                    uiHandler.post(() -> {
                        refreshGeoJson();
                        refreshAISPathsSource();
                        refreshAISPredictionsSource();
                    });
                }
                
                // Логируем память каждые 10 секунд
                logMemoryUsage();
            } finally {
                staleHandler.postDelayed(this, 10_000L);
            }
        }
    };

    // Хранилище текущих GeoJSON фич (ключ: mmsi или "own_vessel")
    private final Map<String, JSONObject> idToFeature = new HashMap<>();
    // Хранилище последних модельных объектов для кликов
    private final Map<String, AISVessel> idToAisVessel = new HashMap<>();
    private Vessel lastOwnVessel;
    // Буфер координат пути собственного судна
    private final JSONArray ownPathCoords = new JSONArray();
    // Буферы координат путей AIS судов больше не нужны - используем VesselPathController из AppController
    
    // Хранилище FeatureCollection для путей и прогнозов AIS судов
    private final Map<String, JSONObject> aisPathFeatures = new HashMap<>();
    private final Map<String, JSONObject> aisPredictionFeatures = new HashMap<>();

    private MarkerClickListener markerClickListener;

    public MapLibreMapImpl(Context context, MapView mapView) {
        this.context = context;
        this.mapView = mapView;
        this.settingsManager = new SettingsManager(context);
        // VesselPathController будет установлен через setVesselPathController()
        this.pathController = null;
        this.cursorOverlay = new CursorOverlay(context);
        
        // Добавляем overlay курсора в MapView
        if (mapView instanceof ViewGroup) {
            ViewGroup parent = (ViewGroup) mapView;
            // Проверяем, не добавлен ли уже курсор
            if (parent.findViewById(R.id.cursor_cross) == null) {
                parent.addView(cursorOverlay.getView());
            }
        }
        
        // Инициализируем throttling для карты
        setupMapThrottling();
    }
    
    /**
     * Устанавливает VesselPathController для использования
     */
    public void setVesselPathController(VesselPathController pathController) {
        this.pathController = pathController;
        Log.i(TAG, "VesselPathController установлен в MapLibreMapImpl");
    }
    
    /**
     * Устанавливает AppController для доступа к AIS VesselPathController
     */
    public void setAppController(AppController appController) {
        this.appController = appController;
        Log.i(TAG, "AppController установлен в MapLibreMapImpl");
    }
    
    /**
     * Настраивает throttling для обновлений карты
     */
    private void setupMapThrottling() {
        mapUpdateRunnable = () -> {
            mapUpdatePending = false;
            updateMapBatched(pendingVesselUpdate);
            pendingVesselUpdate = null;
        };
        
        // Инициализируем throttling для refreshGeoJson
        geoJsonUpdateRunnable = () -> {
            geoJsonUpdatePending = false;
            refreshGeoJsonInternal();
        };
    }

    @Override
    public void initialize() {
        try {
            mapView.getMapAsync(map -> {
                maplibreMap = map;
                maplibreMap.setStyle("https://basemaps.cartocdn.com/gl/positron-gl-style/style.json", loadedStyle -> {
                    style = loadedStyle;
                    ensureSourcesAndLayers();
                    // УДАЛЯЕМ ИНИЦИАЛИЗАЦИЮ СТАРЫХ ОБЩИХ ИСТОЧНИКОВ - теперь используем индивидуальные
                    // if (PATH_FEATURES_ENABLED) {
                    //     // Инициализируем пустые слои пути/предсказания
                    //     updateOwnPathSource();
                    //     updateOwnPredictionSource();
                    // }
                    // Обновим источник текущими фичами, чтобы метки появились сразу после загрузки стиля
                    refreshGeoJson();
                    setupClickListener();
                    setupMapMovementListener();
                    staleHandler.removeCallbacks(staleRunnable);
                    staleHandler.postDelayed(staleRunnable, 5_000L);
                });
            });
        } catch (Exception e) {
            Log.e(TAG, "Ошибка инициализации MapLibre: " + e.getMessage(), e);
        }
    }

    @Override
    public void cleanup() {
        try {
            if (maplibreMap != null) {
                maplibreMap.removeOnMapClickListener(onMapClickListener);
            }
            if (mapView != null) {
                mapView.onStop();
            }
            staleHandler.removeCallbacks(staleRunnable);
            
            // Останавливаем throttling handlers
            if (mapUpdateHandler != null && mapUpdateRunnable != null) {
                mapUpdateHandler.removeCallbacks(mapUpdateRunnable);
            }
            if (geoJsonUpdateHandler != null && geoJsonUpdateRunnable != null) {
                geoJsonUpdateHandler.removeCallbacks(geoJsonUpdateRunnable);
            }
        } catch (Exception ignored) {}
    }

    @Override
    public void addOwnVesselMarker(Vessel vessel) {
        this.ownVessel = vessel;
        if (cursorOverlay != null) {
            cursorOverlay.setOwnVessel(vessel);
        }
        updateOwnVesselPosition(vessel);
    }

    @Override
    public void updateOwnVesselPosition(Vessel vessel) {
        if (vessel == null) return;
        
        this.ownVessel = vessel;
        if (cursorOverlay != null) {
            cursorOverlay.setOwnVessel(vessel);
        }
        
        // Сохраняем последнее судно для отображения
        lastOwnVessel = vessel;
        
        // Обновляем данные судна сразу (не блокирующее)
        try {
            JSONObject feature = buildFeature(
                "own_vessel",
                vessel.getLongitude(),
                vessel.getLatitude(),
                vessel.getCourse(),
                true
            );
            idToFeature.put("own_vessel", feature);
        } catch (Exception e) {
            Log.e(TAG, "Ошибка создания feature для собственного судна: " + e.getMessage(), e);
        }
        
        // Throttled обновление карты
        updateMapThrottled(vessel);
    }
    
    /**
     * Безопасное throttled обновление карты
     */
    private void updateMapThrottled(Vessel vessel) {
        if (!mapUpdatePending) {
            mapUpdatePending = true;
            pendingVesselUpdate = vessel;
            mapUpdateHandler.removeCallbacks(mapUpdateRunnable);
            mapUpdateHandler.postDelayed(mapUpdateRunnable, MAP_UPDATE_DELAY);
        }
    }
    
    /**
     * Батчевое обновление карты (все операции в одном UI вызове)
     */
    private void updateMapBatched(Vessel vessel) {
        if (vessel == null) return;
        
        try {
            // Убираем лишние логи
            // Log.d(TAG, "updateMapBatched: vessel=" + vessel.getLatitude() + "," + vessel.getLongitude() + 
            //       ", speed=" + vessel.getSpeed() + ", course=" + vessel.getCourse());
            
            uiHandler.post(() -> {
                try {
                    if (PATH_FEATURES_ENABLED) {
                        // 1. Обновляем путь судна (если есть точки)
                        if (pathController != null && pathController.getPathPointsCount() >= 2) {
                            JSONArray pathCoords = pathController.getPathCoordinatesWithSpeed();
                            updateOwnVesselPathSource("own_vessel", pathCoords);
                        }
                        
                        // 2. Обновляем прогноз
                        updateOwnVesselPredictionSource("own_vessel", vessel);
                    }
                    
                    // 3. Обновляем GeoJSON (маркеры судов)
                    refreshGeoJson();
                    
                    Log.d(TAG, "Карта обновлена батчом");
                } catch (Exception e) {
                    Log.e(TAG, "Ошибка в updateMapBatched: " + e.getMessage(), e);
                }
            });
            
        } catch (Exception e) {
            Log.e(TAG, "Ошибка при подготовке updateMapBatched: " + e.getMessage(), e);
        }
    }

    @Override
    public void addAISVesselMarker(AISVessel vessel) {
        updateAISVesselPosition(vessel);
    }

    @Override
    public void updateAISVesselPosition(AISVessel vessel) {
        if (vessel == null || vessel.getMmsi() == null) return;
        
        // Проверяем валидность координат
        if (!isValidCoordinates(vessel.getLatitude(), vessel.getLongitude())) {
            Log.d(TAG, "updateAISVesselPosition: AIS vessel " + vessel.getMmsi() + 
                  " has invalid coordinates " + vessel.getLatitude() + "," + vessel.getLongitude() + 
                  " - skipping marker and path update");
            return;
        }
        
        try {
            Log.d(TAG, "updateAISVesselPosition: AIS vessel " + vessel.getMmsi() + 
                  " at " + vessel.getLatitude() + "," + vessel.getLongitude() + 
                  ", speed=" + vessel.getSpeed() + ", course=" + vessel.getCourse());
            
            // Удаление по таймауту
            if (vessel.shouldBeRemoved(settingsManager.getDataStaleRemoveMinutes())) {
                removeAISVesselMarker(vessel.getMmsi());
                return;
            }
            idToAisVessel.put(vessel.getMmsi(), vessel);
            
            // Добавляем трассировку пути и предсказание для AIS судна
            if (PATH_FEATURES_ENABLED) {
                Log.d(TAG, "PATH_FEATURES_ENABLED=true, updating AIS path and prediction for " + vessel.getMmsi());
                // Переносим обновление AIS пути на UI поток (теперь используем VesselPathController из AppController)
                uiHandler.post(() -> updateAISPathSource(vessel.getMmsi()));
                // Переносим обновление AIS прогноза на UI поток
                uiHandler.post(() -> updateAISVesselPredictionSource(vessel.getMmsi(), vessel));
            } else {
                Log.d(TAG, "PATH_FEATURES_ENABLED=false, skipping AIS path and prediction update for " + vessel.getMmsi());
            }
            
            JSONObject feature = buildFeature(
                vessel.getMmsi(),
                vessel.getLongitude(),
                vessel.getLatitude(),
                getDisplayCourse(vessel),
                false
            );
            try {
                boolean stale = vessel.isDataStale(settingsManager.getDataStaleWarningMinutes());
                JSONObject props = feature.getJSONObject("properties");
                // Выберем корректное имя ресурса target_{a|b}_{type}
                String iconName = pickIconNameFor(vessel);
                props.put("icon", iconName);
                props.put("stale", stale);
                // Проставим статусную иконку, если статус поддержан
                String status = vessel.getNavigationalStatus();
                String statusIcon = mapStatusToIcon(status);
                if (statusIcon != null) {
                    props.put("status_icon", statusIcon);
                } else {
                    props.remove("status_icon");
                }
            } catch (Exception ignore) {}
            idToFeature.put(vessel.getMmsi(), feature);
            // Переносим обновление GeoJSON на UI поток
            uiHandler.post(() -> refreshGeoJson());
        } catch (Exception e) {
            Log.e(TAG, "updateAISVesselPosition: " + e.getMessage(), e);
        }
    }

    @Override
    public void removeAISVesselMarker(String mmsi) {
        if (mmsi == null) return;
        idToFeature.remove(mmsi);
        idToAisVessel.remove(mmsi);
        
        // Очищаем путь AIS судна через AppController
        if (appController != null) {
            appController.clearAISVesselPath(mmsi);
        }
        
        // Очищаем путь и прогноз AIS судна на UI потоке
        uiHandler.post(() -> {
            removeAISVesselPath(mmsi);
            removeAISVesselPrediction(mmsi);
        });
        
        // Переносим обновление GeoJSON на UI поток
        uiHandler.post(() -> refreshGeoJson());
    }

    @Override
    public void clearAISVesselMarkers() {
        // Удаляем все, кроме собственного судна
        idToFeature.entrySet().removeIf(e -> !"own_vessel".equals(e.getKey()));
        idToAisVessel.clear();
        
        // Очищаем все пути AIS судов через AppController
        if (appController != null) {
            appController.clearAllAISVesselPaths();
        }
        
        // Очищаем все пути и прогнозы AIS судов на UI потоке
        uiHandler.post(() -> {
            clearAllAISVesselPaths();
            clearAllAISVesselPredictions();
        });
        
        // Переносим обновление GeoJSON на UI поток
        uiHandler.post(() -> refreshGeoJson());
    }

    @Override
    public void centerOnPosition(double latitude, double longitude) {
        if (maplibreMap == null) return;
        maplibreMap.setCameraPosition(new org.maplibre.android.camera.CameraPosition.Builder()
            .target(new LatLng(latitude, longitude))
            .zoom(13.0)
            .build());
    }

    @Override
    public void setZoom(float zoom) {
        if (maplibreMap == null) return;
        org.maplibre.android.camera.CameraPosition current = maplibreMap.getCameraPosition();
        maplibreMap.setCameraPosition(new org.maplibre.android.camera.CameraPosition.Builder()
            .target(current.target)
            .zoom(zoom)
            .tilt(current.tilt)
            .bearing(current.bearing)
            .build());
    }

    @Override
    public float getZoom() {
        if (maplibreMap == null) return 0f;
        return (float) maplibreMap.getCameraPosition().zoom;
    }

    @Override
    public void addLayer(String layerId, Object layerData) {
        // Не используется в первой итерации
    }

    @Override
    public void removeLayer(String layerId) {
        // Не используется в первой итерации
    }

    @Override
    public void setMarkerClickListener(MarkerClickListener listener) {
        this.markerClickListener = listener;
    }

    private void ensureSourcesAndLayers() {
        if (style == null) {
            Log.d(TAG, "ensureSourcesAndLayers: style is null");
            return;
        }
        Log.d(TAG, "ensureSourcesAndLayers: starting...");

        // Иконки судов: own, class A, class B
        try {
            if (style.getImage(IMAGE_VESSEL_OWN) == null) {
                Bitmap bmpOwn = getBitmapByName("target");
                if (bmpOwn == null) bmpOwn = BitmapFactory.decodeResource(context.getResources(), android.R.drawable.ic_menu_compass);
                // Добавляем как SDF для последующего окрашивания iconColor
                style.addImage(IMAGE_VESSEL_OWN, bmpOwn, true);
            }
            // Предзагрузка цветных иконок из ресурсов target_a_* и target_b_*
            preloadClassTypeIcons("a");
            preloadClassTypeIcons("b");
            if (style.getImage(IMAGE_VESSEL_LOSING) == null) {
                Bitmap bmpLosing = getBitmapByName("losingtarget");
                if (bmpLosing != null) {
                    style.addImage(IMAGE_VESSEL_LOSING, bmpLosing);
                }
            }
            // Предзагрузка статусных иконок
            preloadImageIfMissing(STATUS_UNDER_WAY_ENGINE);
            preloadImageIfMissing(STATUS_AT_ANCHOR);
            if (STATUS_MOORED != null) preloadImageIfMissing(STATUS_MOORED);
            preloadImageIfMissing(STATUS_FISHING);
            preloadImageIfMissing(STATUS_SAILING);
            // Иконка для Aid-to-Navigation
            preloadImageIfMissing("green_buey");
            // Иконка для Base Station
            preloadImageIfMissing("base_station");
        } catch (Exception e) {
            Log.w(TAG, "Не удалось добавить иконки: " + e.getMessage());
        }

        // Источник GeoJSON
        if (style.getSource(SOURCE_VESSELS) == null) {
            style.addSource(new GeoJsonSource(SOURCE_VESSELS, emptyFeatureCollection()));
        }

        // Отладочные линии удалены

        // Слой символов (основные иконки)
        if (style.getLayer(LAYER_VESSELS) == null) {
            SymbolLayer layer = new SymbolLayer(LAYER_VESSELS, SOURCE_VESSELS)
                .withProperties(
                    // Берём имя иконки из свойства feature.properties.icon (target_a_*/target_b_*), c дефолтом
                    PropertyFactory.iconImage(
                        Expression.coalesce(
                            Expression.get("icon"),
                            Expression.literal("target_b_other")
                        )
                    ),
                    PropertyFactory.iconAnchor(org.maplibre.android.style.layers.Property.ICON_ANCHOR_CENTER),
                    PropertyFactory.iconRotationAlignment(org.maplibre.android.style.layers.Property.ICON_ROTATION_ALIGNMENT_MAP),
                    PropertyFactory.iconRotate(Expression.get("course")),
                    // Цвет задаём через предгенерированные картинки, iconColor не используем
                    PropertyFactory.iconAllowOverlap(true),
                    PropertyFactory.iconIgnorePlacement(true),
                    PropertyFactory.iconSize(
                        Expression.interpolate(
                            Expression.linear(),
                            Expression.zoom(),
                            Expression.stop(5, 0.085f),
                            Expression.stop(8, 0.115f),
                            Expression.stop(12, 0.165f),
                            Expression.stop(15, 0.235f),
                            Expression.stop(17, 0.300f)
                        )
                    )
                );
            
            
            style.addLayer(layer);
        }

        // Слой предупреждения (losing) поверх — рисуется поверх, если feature.properties.stale == true
        if (style.getLayer(LAYER_VESSELS_STALE) == null && style.getImage(IMAGE_VESSEL_LOSING) != null) {
            SymbolLayer losingLayer = new SymbolLayer(LAYER_VESSELS_STALE, SOURCE_VESSELS)
                .withFilter(Expression.eq(Expression.get("stale"), true))
                .withProperties(
                    PropertyFactory.iconImage(IMAGE_VESSEL_LOSING),
                    PropertyFactory.iconAnchor(org.maplibre.android.style.layers.Property.ICON_ANCHOR_CENTER),
                    // фиксированно вверх для читаемости
                    PropertyFactory.iconRotationAlignment(org.maplibre.android.style.layers.Property.ICON_ROTATION_ALIGNMENT_VIEWPORT),
                    PropertyFactory.iconRotate(0.0f),
                    PropertyFactory.iconAllowOverlap(true),
                    PropertyFactory.iconIgnorePlacement(true),
                    PropertyFactory.iconSize(
                        Expression.interpolate(
                            Expression.linear(),
                            Expression.zoom(),
                            Expression.stop(5, 0.085f),
                            Expression.stop(8, 0.115f),
                            Expression.stop(12, 0.165f),
                            Expression.stop(15, 0.235f),
                            Expression.stop(17, 0.300f)
                        )
                    )
                );
            if (style.getLayer(LAYER_VESSELS) != null) {
                style.addLayerAbove(losingLayer, LAYER_VESSELS);
            } else {
                style.addLayer(losingLayer);
            }
        }

        // Слой статусных иконок поверх маркеров
        if (style.getLayer(LAYER_STATUS_OVERLAY) == null) {
            SymbolLayer statusLayer = new SymbolLayer(LAYER_STATUS_OVERLAY, SOURCE_VESSELS)
                .withFilter(Expression.has("status_icon"))
                .withProperties(
                    PropertyFactory.iconImage(Expression.get("status_icon")),
                    PropertyFactory.iconAnchor(org.maplibre.android.style.layers.Property.ICON_ANCHOR_CENTER),
                    // фиксированно вверх для читаемости
                    PropertyFactory.iconRotationAlignment(org.maplibre.android.style.layers.Property.ICON_ROTATION_ALIGNMENT_VIEWPORT),
                    PropertyFactory.iconRotate(0.0f),
                    PropertyFactory.iconAllowOverlap(true),
                    PropertyFactory.iconIgnorePlacement(true),
                    PropertyFactory.iconSize(
                        Expression.interpolate(
                            Expression.linear(),
                            Expression.zoom(),
                            Expression.stop(5, 0.085f),
                            Expression.stop(8, 0.115f),
                            Expression.stop(12, 0.165f),
                            Expression.stop(15, 0.235f),
                            Expression.stop(17, 0.300f)
                        )
                    )
                );
            // Над основными маркерами и losing
            if (style.getLayer(LAYER_VESSELS_STALE) != null) {
                style.addLayerAbove(statusLayer, LAYER_VESSELS_STALE);
            } else if (style.getLayer(LAYER_VESSELS) != null) {
                style.addLayerAbove(statusLayer, LAYER_VESSELS);
            } else {
                style.addLayer(statusLayer);
            }
        }
        
        // Создаем источники и слои для трассировки корабля ПОСЛЕ создания маркеров
        createVesselTracingSources();
        
        // Восстанавливаем путь судна после создания слоев
        restoreVesselPath();
        
        Log.d(TAG, "ensureSourcesAndLayers: completed");
    }
    
    
    /**
     * Создает источники и слои для трассировки корабля - НАСТРАИВАЕМАЯ ВЕРСИЯ
     */
    private void createVesselTracingSources() {
        Log.d(TAG, "=== СОЗДАНИЕ СЛОЕВ ТРАССИРОВКИ ===");
        
        try {
            // Получаем настройки из SettingsManager
            int pathColor = settingsManager.getPathColor();
            float pathWidth = settingsManager.getPathWidth();
            int predictionColor = settingsManager.getPredictionColor();
            float predictionWidth = settingsManager.getPredictionWidth();
            
            Log.d(TAG, "Настройки линий: path=" + String.format("#%08X", pathColor) + ":" + pathWidth + 
                      ", prediction=" + String.format("#%08X", predictionColor) + ":" + predictionWidth);
            
            // Отладочная линия удалена
            
            // 1. ЛИНИЯ ПУТИ (путь корабля)
            Log.d(TAG, "Создаем линию пути...");
            // Создаем с двумя одинаковыми точками (корабль)
            String pathGeoJson = "{\"type\":\"Feature\",\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-1.9931215,-0.16609],[-1.9931215,-0.16609]]}}";
            GeoJsonSource pathSource = new GeoJsonSource("path_line_source", pathGeoJson);
            style.addSource(pathSource);
            
            // Извлекаем компоненты цвета пути (ARGB)
            int pathAlpha = (pathColor >> 24) & 0xFF;
            int pathRed = (pathColor >> 16) & 0xFF;
            int pathGreen = (pathColor >> 8) & 0xFF;
            int pathBlue = pathColor & 0xFF;
            
            org.maplibre.android.style.layers.LineLayer pathLayer = new org.maplibre.android.style.layers.LineLayer("path_line_layer", "path_line_source")
                .withProperties(
                    org.maplibre.android.style.layers.PropertyFactory.lineColor(org.maplibre.android.style.expressions.Expression.rgba(pathRed, pathGreen, pathBlue, pathAlpha / 255.0)),
                    org.maplibre.android.style.layers.PropertyFactory.lineWidth(pathWidth),
                    org.maplibre.android.style.layers.PropertyFactory.lineDasharray(new Float[]{5f, 5f}) // СТАТИЧЕСКИЙ ПУНКТИР
                );
            style.addLayerBelow(pathLayer, LAYER_VESSELS);
            Log.d(TAG, "✓ Линия пути создана: цвет=" + String.format("#%08X", pathColor) + ", ширина=" + pathWidth);
            
            // 2. ЛИНИЯ ПРОГНОЗА (прогноз движения)
            Log.d(TAG, "Создаем линию прогноза...");
            // Создаем с двумя точками (корабль и прогноз)
            String predictionGeoJson = "{\"type\":\"Feature\",\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-1.9931215,-0.16609],[-1.9931215,-0.16609]]}}";
            GeoJsonSource predictionSource = new GeoJsonSource("prediction_line_source", predictionGeoJson);
            style.addSource(predictionSource);
            
            // Извлекаем компоненты цвета прогноза (ARGB)
            int predictionAlpha = (predictionColor >> 24) & 0xFF;
            int predictionRed = (predictionColor >> 16) & 0xFF;
            int predictionGreen = (predictionColor >> 8) & 0xFF;
            int predictionBlue = predictionColor & 0xFF;
            
            org.maplibre.android.style.layers.LineLayer predictionLayer = new org.maplibre.android.style.layers.LineLayer("prediction_line_layer", "prediction_line_source")
                .withProperties(
                    org.maplibre.android.style.layers.PropertyFactory.lineColor(org.maplibre.android.style.expressions.Expression.rgba(predictionRed, predictionGreen, predictionBlue, predictionAlpha / 255.0)),
                    org.maplibre.android.style.layers.PropertyFactory.lineWidth(predictionWidth)
                    // Убираем пунктир для линии прогноза
                );
            style.addLayerBelow(predictionLayer, LAYER_VESSELS);
            Log.d(TAG, "✓ Линия прогноза создана: цвет=" + String.format("#%08X", predictionColor) + ", ширина=" + predictionWidth);
            
            // 3. ОБЩИЕ СЛОИ ДЛЯ AIS СУДОВ
            Log.d(TAG, "Создаем общие слои для AIS судов...");
            
            // Источник для путей AIS судов
            if (style.getSource(SOURCE_AIS_PATHS) == null) {
                GeoJsonSource aisPathsSource = new GeoJsonSource(SOURCE_AIS_PATHS, emptyFeatureCollection());
                style.addSource(aisPathsSource);
                Log.d(TAG, "Created AIS paths source: " + SOURCE_AIS_PATHS);
            }
            
            // Слой для путей AIS судов
            if (style.getLayer(LAYER_AIS_PATHS) == null) {
                org.maplibre.android.style.layers.LineLayer aisPathsLayer = new org.maplibre.android.style.layers.LineLayer(LAYER_AIS_PATHS, SOURCE_AIS_PATHS)
                    .withProperties(
                        org.maplibre.android.style.layers.PropertyFactory.lineColor(org.maplibre.android.style.expressions.Expression.rgba(pathRed, pathGreen, pathBlue, pathAlpha / 255.0)),
                        org.maplibre.android.style.layers.PropertyFactory.lineWidth(pathWidth),
                        org.maplibre.android.style.layers.PropertyFactory.lineCap(org.maplibre.android.style.layers.Property.LINE_CAP_ROUND),
                        org.maplibre.android.style.layers.PropertyFactory.lineJoin(org.maplibre.android.style.layers.Property.LINE_JOIN_ROUND),
                        org.maplibre.android.style.layers.PropertyFactory.lineDasharray(new Float[]{5f, 5f}) // Пунктир для пройденного пути
                    );
                style.addLayerBelow(aisPathsLayer, LAYER_VESSELS);
                Log.d(TAG, "✓ Общий слой путей AIS создан: " + LAYER_AIS_PATHS);
            }
            
            // Источник для прогнозов AIS судов
            if (style.getSource(SOURCE_AIS_PREDICTIONS) == null) {
                GeoJsonSource aisPredictionsSource = new GeoJsonSource(SOURCE_AIS_PREDICTIONS, emptyFeatureCollection());
                style.addSource(aisPredictionsSource);
                Log.d(TAG, "Created AIS predictions source: " + SOURCE_AIS_PREDICTIONS);
            }
            
            // Слой для прогнозов AIS судов
            if (style.getLayer(LAYER_AIS_PREDICTIONS) == null) {
                org.maplibre.android.style.layers.LineLayer aisPredictionsLayer = new org.maplibre.android.style.layers.LineLayer(LAYER_AIS_PREDICTIONS, SOURCE_AIS_PREDICTIONS)
                    .withProperties(
                        org.maplibre.android.style.layers.PropertyFactory.lineColor(org.maplibre.android.style.expressions.Expression.rgba(predictionRed, predictionGreen, predictionBlue, predictionAlpha / 255.0)),
                        org.maplibre.android.style.layers.PropertyFactory.lineWidth(predictionWidth)
                    );
                style.addLayerBelow(aisPredictionsLayer, LAYER_VESSELS);
                Log.d(TAG, "✓ Общий слой прогнозов AIS создан: " + LAYER_AIS_PREDICTIONS);
            }
            
            Log.d(TAG, "=== ВСЕ СЛОИ СОЗДАНЫ УСПЕШНО ===");
            
        } catch (Exception e) {
            Log.e(TAG, "ОШИБКА при создании слоев: " + e.getMessage(), e);
        }
    }

    /**
     * Запрос обновления GeoJSON с throttling
     */
    private void refreshGeoJson() {
        if (!geoJsonUpdatePending) {
            geoJsonUpdatePending = true;
            geoJsonUpdateHandler.removeCallbacks(geoJsonUpdateRunnable);
            geoJsonUpdateHandler.postDelayed(geoJsonUpdateRunnable, GEOJSON_UPDATE_DELAY);
            Log.d(TAG, "GeoJSON update запланирован на " + GEOJSON_UPDATE_DELAY + "мс");
        }
    }
    
    /**
     * Внутренняя реализация обновления GeoJSON
     */
    private void refreshGeoJsonInternal() {
        if (style == null) return;
        
        try {
            // Проверка валидности стиля перед обращением к источникам
            if (!isStyleValid()) {
                Log.w(TAG, "refreshGeoJson: стиль не валиден, пропускаем обновление");
                return;
            }
            
            GeoJsonSource source = (GeoJsonSource) style.getSource(SOURCE_VESSELS);
            if (source == null) {
                Log.w(TAG, "refreshGeoJson: источник VESSELS не найден, пропускаем обновление");
                return;
            }
            
            // Убираем лишние логи
            // Log.d(TAG, "🔄 refreshGeoJson: обновляем " + idToFeature.size() + " судов");
            long startTime = System.currentTimeMillis();
            
            JSONObject fc = new JSONObject();
            fc.put("type", "FeatureCollection");
            JSONArray features = new JSONArray();
            for (JSONObject f : idToFeature.values()) {
                features.put(f);
            }
            fc.put("features", features);
            source.setGeoJson(fc.toString());
            
            long duration = System.currentTimeMillis() - startTime;
            // Убираем лишние логи
            // Log.d(TAG, "✅ refreshGeoJson: выполнено за " + duration + "мс");
            
            // Логируем использование памяти
            logMemoryUsage();
        } catch (IllegalStateException e) {
            if (e.getMessage() != null && e.getMessage().contains("Calling getSource when a newer style is loading")) {
                Log.w(TAG, "refreshGeoJson: стиль изменяется, пропускаем обновление");
            } else {
                Log.e(TAG, "refreshGeoJson: IllegalStateException " + e.getMessage(), e);
            }
        } catch (Exception e) {
            Log.e(TAG, "refreshGeoJson: " + e.getMessage(), e);
        }
    }
    
    private void logMemoryUsage() {
        Runtime runtime = Runtime.getRuntime();
        long usedMemory = runtime.totalMemory() - runtime.freeMemory();
        long maxMemory = runtime.maxMemory();
        long freeMemory = runtime.freeMemory();
        
        // Убираем лишние логи
        // Log.d(TAG, String.format("Memory: used=%dMB, free=%dMB, max=%dMB (%.1f%%), vessels=%d, paths=%d, predictions=%d", 
        //     usedMemory / 1024 / 1024, 
        //     freeMemory / 1024 / 1024,
        //     maxMemory / 1024 / 1024,
        //     (float) usedMemory / maxMemory * 100,
        //     idToFeature.size(),
        //     aisPathFeatures.size(),
        //     aisPredictionFeatures.size()));
    }
    
    private void refreshAISPathsSource() {
        if (style == null) return;
        
        try {
            // Проверка валидности стиля перед обращением к источникам
            if (!isStyleValid()) {
                Log.w(TAG, "refreshAISPathsSource: стиль не валиден, пропускаем обновление");
                return;
            }
            
            GeoJsonSource source = (GeoJsonSource) style.getSource(SOURCE_AIS_PATHS);
            if (source == null) {
                Log.w(TAG, "refreshAISPathsSource: источник AIS_PATHS не найден, пропускаем обновление");
                return;
            }
            
            JSONObject fc = new JSONObject();
            fc.put("type", "FeatureCollection");
            JSONArray features = new JSONArray();
            for (JSONObject f : aisPathFeatures.values()) {
                features.put(f);
            }
            fc.put("features", features);
            source.setGeoJson(fc.toString());
            Log.d(TAG, "refreshAISPathsSource: updated with " + aisPathFeatures.size() + " paths");
        } catch (IllegalStateException e) {
            if (e.getMessage() != null && e.getMessage().contains("Calling getSource when a newer style is loading")) {
                Log.w(TAG, "refreshAISPathsSource: стиль изменяется, пропускаем обновление");
            } else {
                Log.e(TAG, "refreshAISPathsSource: IllegalStateException " + e.getMessage(), e);
            }
        } catch (Exception e) {
            Log.e(TAG, "refreshAISPathsSource: " + e.getMessage(), e);
        }
    }
    
    private void refreshAISPredictionsSource() {
        if (style == null) return;
        
        try {
            // Проверка валидности стиля перед обращением к источникам
            if (!isStyleValid()) {
                Log.w(TAG, "refreshAISPredictionsSource: стиль не валиден, пропускаем обновление");
                return;
            }
            
            GeoJsonSource source = (GeoJsonSource) style.getSource(SOURCE_AIS_PREDICTIONS);
            if (source == null) {
                Log.w(TAG, "refreshAISPredictionsSource: источник AIS_PREDICTIONS не найден, пропускаем обновление");
                return;
            }
            
            JSONObject fc = new JSONObject();
            fc.put("type", "FeatureCollection");
            JSONArray features = new JSONArray();
            for (JSONObject f : aisPredictionFeatures.values()) {
                features.put(f);
            }
            fc.put("features", features);
            source.setGeoJson(fc.toString());
            Log.d(TAG, "refreshAISPredictionsSource: updated with " + aisPredictionFeatures.size() + " predictions");
        } catch (IllegalStateException e) {
            if (e.getMessage() != null && e.getMessage().contains("Calling getSource when a newer style is loading")) {
                Log.w(TAG, "refreshAISPredictionsSource: стиль изменяется, пропускаем обновление");
            } else {
                Log.e(TAG, "refreshAISPredictionsSource: IllegalStateException " + e.getMessage(), e);
            }
        } catch (Exception e) {
            Log.e(TAG, "refreshAISPredictionsSource: " + e.getMessage(), e);
        }
        
    }

    private JSONObject buildFeature(String id, double lon, double lat, double course, boolean own) throws Exception {
        JSONObject feature = new JSONObject();
        feature.put("type", "Feature");
        feature.put("id", id);

        JSONObject geom = new JSONObject();
        geom.put("type", "Point");
        JSONArray coords = new JSONArray();
        coords.put(lon);
        coords.put(lat);
        geom.put("coordinates", coords);
        feature.put("geometry", geom);

        JSONObject props = new JSONObject();
        props.put("course", normalizeCourse(course));
        props.put("own", own);
        // Для собственного судна используем дефолтную иконку типа (серый), чтобы точно существовала
        props.put("icon", own ? "target_a_other" : "target_b_other");
        feature.put("properties", props);

        return feature;
    }

    private double getDisplayCourse(AISVessel v) {
        double hdg = v.getHeading();
        if (!Double.isNaN(hdg) && !Double.isInfinite(hdg)) {
            int h = (int) Math.round(hdg);
            if (h != 511) return normalizeCourse(hdg);
        }
        return normalizeCourse(v.getCourse());
    }

    private double normalizeCourse(double c) {
        if (Double.isNaN(c) || Double.isInfinite(c)) return 0.0;
        double v = c % 360.0;
        if (v < 0) v += 360.0;
        return v;
    }

    private String emptyFeatureCollection() {
        return "{\"type\":\"FeatureCollection\",\"features\":[]}";
    }

    private String buildEmptyLineString() {
        return "{\"type\":\"Feature\",\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[0,0],[0,0]]}}";
    }
    
    /**
     * Проверяет валидность координат
     * Игнорирует координаты 0,0 и 181,91 (невалидные значения AIS)
     */
    private boolean isValidCoordinates(double latitude, double longitude) {
        // Проверяем на нулевые координаты
        if (latitude == 0.0 && longitude == 0.0) {
            return false;
        }
        
        // Проверяем на невалидные координаты AIS (181, 91)
        if (latitude == 91.0 && longitude == 181.0) {
            return false;
        }
        
        // Проверяем на стандартные границы координат
        if (latitude < -90.0 || latitude > 90.0) {
            return false;
        }
        
        if (longitude < -180.0 || longitude > 180.0) {
            return false;
        }
        
        return true;
    }

    private void appendOwnPathPoint(double lon, double lat) {
        try {
            // Не добавляем нулевые координаты
            if (lon == 0.0 && lat == 0.0) {
                Log.d(TAG, "appendOwnPathPoint: skipping zero coordinates");
                return;
            }
            
                // ВРЕМЕННО ОТКЛЮЧАЕМ ПРОВЕРКУ РАССТОЯНИЯ ДЛЯ ТЕСТИРОВАНИЯ
                // Проверяем, изменились ли координаты (строгая проверка на дублирование)
                // if (ownPathCoords.length() > 0) {
                //     JSONArray lastPoint = ownPathCoords.getJSONArray(ownPathCoords.length() - 1);
                //     double lastLon = lastPoint.getDouble(0);
                //     double lastLat = lastPoint.getDouble(1);
                //     
                //     // Строгая проверка на точное совпадение координат
                //     if (lon == lastLon && lat == lastLat) {
                //         Log.d(TAG, "appendOwnPathPoint: exact duplicate coordinates, skipping");
                //         return;
                //     }
                //     
                //     double distance = Math.sqrt(Math.pow(lon - lastLon, 2) + Math.pow(lat - lastLat, 2));
                //     Log.d(TAG, "appendOwnPathPoint: distance=" + distance + " (threshold=0.00001)");
                //     if (distance < 0.00001) { // примерно 1 метр
                //         Log.d(TAG, "appendOwnPathPoint: vessel not moving, distance=" + distance);
                //         return;
                //     }
                // }
            
                // ЗАХАРДКОДИМ МАКСИМАЛЬНОЕ КОЛИЧЕСТВО ТОЧЕК ДЛЯ ТЕСТИРОВАНИЯ
                int max = 5000; // settingsManager.getPathMaxPoints();
            if (ownPathCoords.length() >= max) {
                // удаляем из начала
                ownPathCoords.remove(0);
            }
            JSONArray pt = new JSONArray();
            pt.put(lon);
            pt.put(lat);
            ownPathCoords.put(pt);
            Log.d(TAG, "appendOwnPathPoint: lon=" + lon + ", lat=" + lat + ", size=" + ownPathCoords.length());
        } catch (Exception e) {
            Log.e(TAG, "Error in appendOwnPathPoint", e);
        }
    }

    private void updateOwnPathSource() {
        if (style == null) {
            Log.d(TAG, "updateOwnPathSource: style is null");
            return;
        }
        GeoJsonSource src = (GeoJsonSource) style.getSource(SOURCE_OWN_PATH);
        if (src == null) {
            Log.d(TAG, "updateOwnPathSource: source is null");
            return;
        }
        
        // Проверяем, что слой существует
        if (style.getLayer(LAYER_OWN_PATH) == null) {
            Log.e(TAG, "updateOwnPathSource: LAYER_OWN_PATH layer is null!");
            return;
        }
        
        Log.d(TAG, "updateOwnPathSource: source and layer exist, proceeding...");
        
        try {
            if (ownPathCoords.length() < 2) {
                Log.d(TAG, "updateOwnPathSource: not enough points (" + ownPathCoords.length() + ")");
                return; // нужна минимум пара точек
            }
            
            // Создаем очищенный массив координат без нулевых точек и дубликатов
            JSONArray cleanCoords = new JSONArray();
            double lastLon = Double.NaN;
            double lastLat = Double.NaN;
            
            for (int i = 0; i < ownPathCoords.length(); i++) {
                JSONArray point = ownPathCoords.getJSONArray(i);
                double lon = point.getDouble(0);
                double lat = point.getDouble(1);
                
                // Пропускаем нулевые координаты
                if (lon == 0.0 && lat == 0.0) {
                    continue;
                }
                
                // Пропускаем дубликаты
                if (!Double.isNaN(lastLon) && !Double.isNaN(lastLat) && 
                    lon == lastLon && lat == lastLat) {
                    continue;
                }
                
                cleanCoords.put(point);
                lastLon = lon;
                lastLat = lat;
            }
            
            if (cleanCoords.length() < 2) {
                Log.d(TAG, "updateOwnPathSource: not enough valid points after cleanup (" + cleanCoords.length() + ")");
                return;
            }
            
            Log.d(TAG, "updateOwnPathSource: cleaned " + ownPathCoords.length() + " -> " + cleanCoords.length() + " points");
            
            // Проверяем координаты первых и последних точек
            JSONArray firstPoint = cleanCoords.getJSONArray(0);
            JSONArray lastPoint = cleanCoords.getJSONArray(cleanCoords.length() - 1);
            Log.d(TAG, "updateOwnPathSource: first point=[" + firstPoint.getDouble(0) + "," + firstPoint.getDouble(1) + 
                  "], last point=[" + lastPoint.getDouble(0) + "," + lastPoint.getDouble(1) + "]");
            
            JSONObject line = new JSONObject();
            line.put("type", "Feature");
            JSONObject geom = new JSONObject();
            geom.put("type", "LineString");
            geom.put("coordinates", cleanCoords);
            line.put("geometry", geom);
            
            String geoJsonString = line.toString();
            Log.d(TAG, "updateOwnPathSource: setGeoJson points=" + cleanCoords.length() + ", geoJson length=" + geoJsonString.length());
            Log.d(TAG, "updateOwnPathSource: geoJson preview=" + geoJsonString.substring(0, Math.min(200, geoJsonString.length())) + "...");
            
            // Проверяем, что источник обновился
            try {
                src.setGeoJson(geoJsonString);
                Log.d(TAG, "updateOwnPathSource: GeoJSON successfully set to source");
            } catch (Exception e) {
                Log.e(TAG, "updateOwnPathSource: Error setting GeoJSON to source", e);
            }
        } catch (Exception e) {
            Log.e(TAG, "Error in updateOwnPathSource", e);
        }
    }

    private void updateOwnPredictionSource() {
        if (style == null) {
            Log.d(TAG, "updateOwnPredictionSource: style is null");
            return;
        }
        if (lastOwnVessel == null) {
            Log.d(TAG, "updateOwnPredictionSource: lastOwnVessel is null");
            return;
        }
        GeoJsonSource src = (GeoJsonSource) style.getSource(SOURCE_OWN_PRED);
        if (src == null) {
            Log.d(TAG, "updateOwnPredictionSource: source is null");
            return;
        }
        try {
            // Простейшая экстраполяция: точка сейчас -> точка через horizon секунд по курсу/скорости (узлы)
            double lat = lastOwnVessel.getLatitude();
            double lon = lastOwnVessel.getLongitude();
            double courseDeg = lastOwnVessel.getCourse();
            double speedKn = lastOwnVessel.getSpeed();
            int horizonSec = settingsManager.getPredictionHorizonSec();

            Log.d(TAG, "updateOwnPredictionSource: lat=" + lat + ", lon=" + lon + 
                  ", course=" + courseDeg + ", speed=" + speedKn + ", horizon=" + horizonSec);

            if (horizonSec <= 0 || speedKn <= 0) {
                Log.d(TAG, "updateOwnPredictionSource: clearing prediction (horizon=" + horizonSec + ", speed=" + speedKn + ")");
                src.setGeoJson("{\"type\":\"Feature\",\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[0,0],[0,0]]}}");
                return;
            }

            // пересчёт: 1 узел = 1852 м/ч; за t сек = speedKn * 1852 * t / 3600 метров
            double meters = speedKn * 1852.0 * ((double) horizonSec) / 3600.0;
            // приблизительно: 1 градус широты ~ 111320 м; долготы ~ 111320 * cos(lat)
            double dLat = meters / 111320.0;
            double rad = Math.toRadians(courseDeg);
            double dLonMeters = meters * Math.sin(rad);
            double dLatMeters = meters * Math.cos(rad);
            double dLon = dLonMeters / (111320.0 * Math.cos(Math.toRadians(lat)) + 1e-9);
            double lat2 = lat + (dLatMeters / 111320.0);
            double lon2 = lon + dLon;

            JSONArray coords = new JSONArray();
            JSONArray p0 = new JSONArray(); p0.put(lon); p0.put(lat); coords.put(p0);
            JSONArray p1 = new JSONArray(); p1.put(lon2); p1.put(lat2); coords.put(p1);

            JSONObject line = new JSONObject();
            line.put("type", "Feature");
            JSONObject geom = new JSONObject();
            geom.put("type", "LineString");
            geom.put("coordinates", coords);
            line.put("geometry", geom);
            src.setGeoJson(line.toString());
            Log.d(TAG, "updateOwnPredictionSource: prediction updated, distance=" + meters + "m");
            Log.d(TAG, "updateOwnPredictionSource: GeoJSON successfully set to source");
        } catch (Exception e) {
            Log.e(TAG, "Error in updateOwnPredictionSource", e);
        }
    }
    
    
    private void updateAISPathSource(String mmsi) {
        if (style == null) {
            Log.d(TAG, "updateAISPathSource: style is null for " + mmsi);
            return;
        }
        
        // Получаем VesselPathController для этого AIS судна из AppController
        VesselPathController aisPathController = null;
        if (appController != null) {
            aisPathController = appController.getAISVesselPathController(mmsi);
        }
        
        if (aisPathController == null) {
            Log.d(TAG, "updateAISPathSource: no VesselPathController for " + mmsi);
            return;
        }
        
        // Получаем координаты из VesselPathController
        JSONArray coords = aisPathController.getPathCoordinates();
        if (coords == null || coords.length() < 2) {
            Log.d(TAG, "updateAISPathSource: not enough points (" + (coords != null ? coords.length() : 0) + ") for " + mmsi);
            // Удаляем путь из общего источника
            aisPathFeatures.remove(mmsi);
            refreshAISPathsSource();
            return;
        }
        
        try {
            // Создаем Feature для пути этого судна
            JSONObject line = new JSONObject();
            line.put("type", "Feature");
            line.put("id", mmsi); // Уникальный ID для этого судна
            JSONObject geom = new JSONObject();
            geom.put("type", "LineString");
            geom.put("coordinates", coords);
            line.put("geometry", geom);
            
            // Сохраняем в общем хранилище
            aisPathFeatures.put(mmsi, line);
            
            // Обновляем общий источник
            refreshAISPathsSource();
            
            Log.d(TAG, "updateAISPathSource: updated path for " + mmsi + " with " + coords.length() + " points");
        } catch (Exception e) {
            Log.e(TAG, "Error in updateAISPathSource for " + mmsi, e);
        }
    }

    private org.maplibre.android.style.expressions.Expression colorIntToRgba(int argb) {
        int a = (argb >> 24) & 0xFF;
        int r = (argb >> 16) & 0xFF;
        int g = (argb >> 8) & 0xFF;
        int b = (argb) & 0xFF;
        float af = a / 255f;
        Log.d(TAG, "colorIntToRgba: argb=0x" + Integer.toHexString(argb) + 
              " -> rgba(" + r + "," + g + "," + b + "," + af + ")");
        return org.maplibre.android.style.expressions.Expression.rgba(r, g, b, af);
    }

    private Bitmap getBitmapByName(String name) {
        try {
            int resId = context.getResources().getIdentifier(name, "drawable", context.getPackageName());
            if (resId == 0) return null;
            Drawable d = context.getResources().getDrawable(resId, null);
            if (d == null) return null;
            int w = Math.max(1, d.getIntrinsicWidth());
            int h = Math.max(1, d.getIntrinsicHeight());
            Bitmap bmp = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
            Canvas c = new Canvas(bmp);
            d.setBounds(0, 0, w, h);
            d.draw(c);
            return bmp;
        } catch (Throwable t) {
            return null;
        }
    }

    private void addImageIfAbsent(String id, Bitmap bmp) {
        if (bmp == null) return;
        if (style.getImage(id) == null) {
            style.addImage(id, bmp);
        }
    }

    private String normalizeVesselType(String raw) {
        if (raw == null) return null;
        String r = raw.toLowerCase();
        if (r.contains("cargo")) return TYPE_CARGO;
        if (r.contains("tanker")) return TYPE_TANKER;
        if (r.contains("fishing")) return TYPE_FISHING;
        if (r.contains("passenger")) return TYPE_PASSENGER;
        if (r.contains("pleasure") || r.contains("sailing")) return TYPE_PLEASURE;
        if (r.contains("tug") || r.contains("towing")) return TYPE_TUG;
        if (r.contains("military") || r.contains("navy")) return TYPE_NAVY;
        if (r.contains("pilot")) return TYPE_TUG;
        if (r.contains("law enforcement")) return TYPE_NAVY;
        if (r.contains("high speed craft") || r.contains("hsc")) return TYPE_PASSENGER;
        if (r.contains("other")) return TYPE_OTHER;
        return TYPE_OTHER;
    }

    private String mapStatusToIcon(String raw) {
        if (raw == null) return null;
        String r = raw.toLowerCase();
        if (r.contains("under way using engine") || r.equals("under way")) return STATUS_UNDER_WAY_ENGINE;
        if (r.contains("at anchor")) return STATUS_AT_ANCHOR;
        if (r.contains("moored")) return STATUS_MOORED;
        if (r.contains("engaged in fishing") || r.contains("fishing")) return STATUS_FISHING;
        if (r.contains("under way sailing") || r.contains("sailing")) return STATUS_SAILING;
        return null;
    }

    private final MapLibreMap.OnMapClickListener onMapClickListener = point -> {
        if (maplibreMap == null || style == null) return false;
        try {
            // Кликаем по слою
            java.util.List<org.maplibre.geojson.Feature> features = maplibreMap.queryRenderedFeatures(
                maplibreMap.getProjection().toScreenLocation(point), LAYER_VESSELS);
            if (features != null && !features.isEmpty()) {
                String id = features.get(0).id();
                if ("own_vessel".equals(id)) {
                    if (markerClickListener != null) {
                        markerClickListener.onOwnVesselClick(lastOwnVessel);
                    }
                } else {
                    if (markerClickListener != null) {
                        markerClickListener.onAISVesselClick(idToAisVessel.get(id));
                    }
                }
                return true;
            }
        } catch (Exception ignored) {}
        return false;
    };

    private void setupClickListener() {
        if (maplibreMap != null) {
            maplibreMap.addOnMapClickListener(onMapClickListener);
        }
    }

    private void preloadImageIfMissing(String drawableName) {
        if (style == null) return;
        if (style.getImage(drawableName) != null) return;
        Bitmap bmp = getBitmapByName(drawableName);
        if (bmp != null) {
            style.addImage(drawableName, bmp);
            Log.d(TAG, "Added style image: " + drawableName + " (" + bmp.getWidth() + "x" + bmp.getHeight() + ")");
        } else {
            Log.w(TAG, "Drawable not found: " + drawableName);
        }
    }

    private void preloadClassTypeIcons(String klass) {
        String[] types = new String[]{TYPE_CARGO, TYPE_TANKER, TYPE_FISHING, TYPE_PASSENGER, TYPE_PLEASURE, TYPE_TUG, TYPE_NAVY, TYPE_OTHER};
        for (String t : types) {
            String name = "target_" + klass + "_" + t;
            preloadImageIfMissing(name);
        }
    }

    private String pickIconNameFor(AISVessel vessel) {
        // Специальные случаи по классу судна
        try {
            String vesselClass = vessel.getVesselClass();
            if (vesselClass != null) {
                String cls = vesselClass.trim().toLowerCase();
                if (cls.contains("base station")) {
                    return "base_station";
                }
                if (cls.contains("navigation aid") || cls.contains("aid-to-navigation") || cls.contains("aton")) {
                    return "green_buey";
                }
            }
            // Обратная совместимость: по типу, если он явно указан как Aid-to-Navigation
            String vesselType = vessel.getVesselType();
            if (vesselType != null && vesselType.trim().toLowerCase().contains("aid-to-navigation")) {
                return "green_buey";
            }
        } catch (Exception ignore) {}
        boolean isClassA = vessel.getVesselClass() != null && vessel.getVesselClass().trim().equalsIgnoreCase("class a");
        String klass = isClassA ? "a" : "b";
        String norm = normalizeTypeToken(vessel.getVesselType());
        String name = "target_" + klass + "_" + norm;
        Log.d(TAG, "pickIconNameFor mmsi=" + vessel.getMmsi() + ", class=" + (isClassA?"A":"B") + ", type=" + norm + " -> " + name);
        return name;
    }

    private String normalizeTypeToken(String raw) {
        if (raw == null) return TYPE_OTHER;
        String r = raw.toLowerCase();
        if (r.contains("cargo")) return TYPE_CARGO;
        if (r.contains("tanker")) return TYPE_TANKER;
        if (r.contains("fishing")) return TYPE_FISHING;
        if (r.contains("passenger")) return TYPE_PASSENGER;
        if (r.contains("pleasure") || r.contains("sailing")) return TYPE_PLEASURE;
        if (r.contains("tug") || r.contains("towing")) return TYPE_TUG;
        if (r.contains("military") || r.contains("navy")) return TYPE_NAVY;
        if (r.contains("pilot")) return TYPE_TUG;
        if (r.contains("law enforcement")) return TYPE_NAVY;
        if (r.contains("high speed craft") || r.contains("hsc")) return TYPE_PASSENGER;
        return TYPE_OTHER;
    }
    
    
    /**
     * Обновляет источник пути для конкретного судна - ПРОСТАЯ ВЕРСИЯ
     */
    private void updateOwnVesselPathSource(String vesselId, JSONArray pathCoords) {
        try {
            // Убираем лишние логи
            // Log.d(TAG, "updateOwnVesselPathSource вызван для vesselId=" + vesselId + ", координат=" + pathCoords.length());
            
            if (maplibreMap == null || maplibreMap.getStyle() == null) {
                Log.w(TAG, "MapLibre map или style is null, cannot update path source");
                return;
            }
            
            Style style = maplibreMap.getStyle();
            String sourceId = "path_line_source";
            
            GeoJsonSource src = (GeoJsonSource) style.getSource(sourceId);
            if (src == null) {
                Log.w(TAG, "Источник пути не найден!");
                return;
            }
            
            // Убираем лишние логи
            // Log.d(TAG, "updateOwnVesselPathSource: получено " + pathCoords.length() + " координат");
            if (pathCoords.length() < 2) {
                Log.d(TAG, "Недостаточно точек пути для отображения: " + pathCoords.length() + " (нужно минимум 2)");
                return;
            }
            
            JSONObject line = new JSONObject();
            line.put("type", "Feature");
            JSONObject geom = new JSONObject();
            geom.put("type", "LineString");
            geom.put("coordinates", pathCoords);
            line.put("geometry", geom);
            
            src.setGeoJson(line.toString());
            
            Log.d(TAG, "✓ Обновлена линия пути: " + pathCoords.length() + " точек");
            
        } catch (Exception e) {
            Log.e(TAG, "Ошибка обновления линии пути", e);
        }
    }
    
    /**
     * Сохраняет текущий путь судна
     */
    public void saveVesselPath() {
        if (pathController != null) {
            Log.d(TAG, "Сохранение пути судна: " + pathController.getPathInfo());
        }
    }
    
    /**
     * Восстанавливает путь судна после перезапуска
     */
    public void restoreVesselPath() {
        if (pathController != null && pathController.getPathPointsCount() >= 2) {
            JSONArray pathCoords = pathController.getPathCoordinatesWithSpeed();
            // Переносим восстановление пути на UI поток
            uiHandler.post(() -> updateOwnVesselPathSource("own_vessel", pathCoords));
            Log.d(TAG, "Восстановлен путь судна: " + pathController.getPathInfo());
        } else if (pathController != null) {
            Log.d(TAG, "Недостаточно точек для восстановления пути: " + pathController.getPathPointsCount() + " (нужно минимум 2)");
        }
    }
    
    /**
     * Очищает путь судна
     */
    @Override
    public void clearVesselPath() {
        Log.i(TAG, "MapLibreMapImpl.clearVesselPath() вызван");
        
        if (pathController != null) {
            Log.i(TAG, "Очищаем VesselPathController");
            pathController.clearPath();
            
            // Очищаем буфер координат пути в памяти
            synchronized (ownPathCoords) {
                int coordsCount = ownPathCoords.length();
                while (ownPathCoords.length() > 0) {
                    ownPathCoords.remove(0);
                }
                Log.i(TAG, "Очищено " + coordsCount + " координат из буфера ownPathCoords");
            }
            
            // Обновляем источник с пустыми координатами на UI потоке
            JSONArray emptyCoords = new JSONArray();
            uiHandler.post(() -> updateOwnVesselPathSource("own_vessel", emptyCoords));
            Log.i(TAG, "Путь судна очищен из памяти, SharedPreferences и карты");
        } else {
            Log.w(TAG, "pathController is null, не можем очистить путь");
        }
    }
    
    /**
     * Получает информацию о пути судна
     */
    public String getVesselPathInfo() {
        if (pathController != null) {
            return pathController.getPathInfo();
        }
        return "Контроллер пути не инициализирован";
    }
    
    /**
     * Обновляет источник прогноза для конкретного судна - ПРОСТАЯ ВЕРСИЯ
     */
    private void updateOwnVesselPredictionSource(String vesselId, Vessel vessel) {
        try {
            if (maplibreMap == null || maplibreMap.getStyle() == null || vessel == null) {
                return;
            }
            
            Style style = maplibreMap.getStyle();
            String sourceId = "prediction_line_source";
            
            GeoJsonSource src = (GeoJsonSource) style.getSource(sourceId);
            if (src == null) {
                Log.w(TAG, "Источник прогноза не найден!");
                return;
            }
            
            double lat = vessel.getLatitude();
            double lon = vessel.getLongitude();
            double courseDeg = vessel.getCourse();
            double speedKn = vessel.getSpeed();
            int horizonSec = settingsManager.getPredictionHorizonSec();

            if (horizonSec <= 0 || speedKn <= 0) {
                Log.d(TAG, "Очищаем прогноз (horizon=" + horizonSec + ", speed=" + speedKn + ")");
                src.setGeoJson("{\"type\":\"Feature\",\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[0,0],[0,0]]}}");
                return;
            }

            // Расчет прогноза
            double meters = speedKn * 1852.0 * ((double) horizonSec) / 3600.0;
            double rad = Math.toRadians(courseDeg);
            double dLonMeters = meters * Math.sin(rad);
            double dLatMeters = meters * Math.cos(rad);
            double dLon = dLonMeters / (111320.0 * Math.cos(Math.toRadians(lat)) + 1e-9);
            double lat2 = lat + (dLatMeters / 111320.0);
            double lon2 = lon + dLon;

            JSONArray coords = new JSONArray();
            JSONArray p0 = new JSONArray(); p0.put(lon); p0.put(lat); coords.put(p0);
            JSONArray p1 = new JSONArray(); p1.put(lon2); p1.put(lat2); coords.put(p1);

            JSONObject line = new JSONObject();
            line.put("type", "Feature");
            JSONObject geom = new JSONObject();
            geom.put("type", "LineString");
            geom.put("coordinates", coords);
            line.put("geometry", geom);
            
            src.setGeoJson(line.toString());
            Log.d(TAG, "✓ Обновлена линия прогноза: расстояние=" + meters + "м");
            
        } catch (Exception e) {
            Log.e(TAG, "Ошибка обновления линии прогноза", e);
        }
    }
    
    /**
     * Обновляет источник прогноза для AIS судна
     */
    private void updateAISVesselPredictionSource(String mmsi, AISVessel vessel) {
        try {
            if (maplibreMap == null || maplibreMap.getStyle() == null || vessel == null) {
                return;
            }
            
            double lat = vessel.getLatitude();
            double lon = vessel.getLongitude();
            double courseDeg = getDisplayCourse(vessel);
            double speedKn = vessel.getSpeed();
            int horizonSec = settingsManager.getPredictionHorizonSec();

            if (horizonSec <= 0 || speedKn <= 0) {
                Log.d(TAG, "Очищаем прогноз для AIS " + mmsi + " (horizon=" + horizonSec + ", speed=" + speedKn + ")");
                aisPredictionFeatures.remove(mmsi);
                refreshAISPredictionsSource();
                return;
            }

            // Расчет прогноза
            double meters = speedKn * 1852.0 * ((double) horizonSec) / 3600.0;
            double rad = Math.toRadians(courseDeg);
            double dLonMeters = meters * Math.sin(rad);
            double dLatMeters = meters * Math.cos(rad);
            double dLon = dLonMeters / (111320.0 * Math.cos(Math.toRadians(lat)) + 1e-9);
            double lat2 = lat + (dLatMeters / 111320.0);
            double lon2 = lon + dLon;

            JSONArray coords = new JSONArray();
            JSONArray p0 = new JSONArray(); p0.put(lon); p0.put(lat); coords.put(p0);
            JSONArray p1 = new JSONArray(); p1.put(lon2); p1.put(lat2); coords.put(p1);

            // Создаем Feature для прогноза этого судна
            JSONObject line = new JSONObject();
            line.put("type", "Feature");
            line.put("id", mmsi); // Уникальный ID для этого судна
            JSONObject geom = new JSONObject();
            geom.put("type", "LineString");
            geom.put("coordinates", coords);
            line.put("geometry", geom);
            
            // Сохраняем в общем хранилище
            aisPredictionFeatures.put(mmsi, line);
            
            // Обновляем общий источник
            refreshAISPredictionsSource();
            
            Log.d(TAG, "✓ Обновлена линия прогноза для AIS " + mmsi + ": расстояние=" + meters + "м");
            
        } catch (Exception e) {
            Log.e(TAG, "Ошибка обновления линии прогноза для AIS " + mmsi, e);
        }
    }
    
    /**
     * Удаляет путь для AIS судна
     */
    private void removeAISVesselPath(String mmsi) {
        try {
            // Удаляем из общего хранилища
            aisPathFeatures.remove(mmsi);
            // Обновляем общий источник
            refreshAISPathsSource();
            Log.d(TAG, "Removed AIS path for " + mmsi);
        } catch (Exception e) {
            Log.e(TAG, "Ошибка удаления пути для AIS " + mmsi, e);
        }
    }
    
    /**
     * Удаляет прогноз для AIS судна
     */
    private void removeAISVesselPrediction(String mmsi) {
        try {
            // Удаляем из общего хранилища
            aisPredictionFeatures.remove(mmsi);
            // Обновляем общий источник
            refreshAISPredictionsSource();
            Log.d(TAG, "Removed AIS prediction for " + mmsi);
        } catch (Exception e) {
            Log.e(TAG, "Ошибка удаления прогноза для AIS " + mmsi, e);
        }
    }
    
    /**
     * Очищает все пути AIS судов
     */
    private void clearAllAISVesselPaths() {
        try {
            // Очищаем общее хранилище
            aisPathFeatures.clear();
            // Обновляем общий источник
            refreshAISPathsSource();
            Log.d(TAG, "All AIS vessel paths cleared");
        } catch (Exception e) {
            Log.e(TAG, "Ошибка очистки всех путей AIS судов", e);
        }
    }
    
    /**
     * Очищает все прогнозы AIS судов
     */
    private void clearAllAISVesselPredictions() {
        try {
            // Очищаем общее хранилище
            aisPredictionFeatures.clear();
            // Обновляем общий источник
            refreshAISPredictionsSource();
            Log.d(TAG, "All AIS vessel predictions cleared");
        } catch (Exception e) {
            Log.e(TAG, "Ошибка очистки всех прогнозов AIS судов", e);
        }
    }

    /**
     * Проверка валидности текущего стиля карты
     * @return true если стиль валиден и готов к работе
     */
    private boolean isStyleValid() {
        try {
            // Проверяем состояние стиля
            if (style == null) {
                return false;
            }
            
            // Попытка обращения к состоянию стиля для проверки валидности
            style.isFullyLoaded();
            
            // Если мы дошли до этого места, стиль валиден
            return true;
        } catch (Exception e) {
            // Если произошло исключение, стиль не валиден
            return false;
        }
    }
    
    @Override
    public void showCursor() {
        if (cursorOverlay != null) {
            cursorOverlay.showCursor();
        }
    }
    
    @Override
    public void hideCursor() {
        if (cursorOverlay != null) {
            cursorOverlay.hideCursor();
        }
    }
    
    @Override
    public void updateCursorCoordinates(double latitude, double longitude) {
        if (cursorOverlay != null) {
            cursorOverlay.updateCursorCoordinates(latitude, longitude);
        }
    }
    
    @Override
    public void updateCursorFromMapCenter() {
        if (cursorOverlay != null && maplibreMap != null) {
            // Получаем координаты центра карты
            org.maplibre.android.geometry.LatLng center = maplibreMap.getCameraPosition().target;
            cursorOverlay.updateCursorCoordinates(center.getLatitude(), center.getLongitude());
            
            // Проверяем, есть ли AIS судно под курсором
            checkAisVesselUnderCursor(center);
        }
    }
    
    /**
     * Проверяет, есть ли AIS судно под курсором (в центре экрана)
     */
    private void checkAisVesselUnderCursor(org.maplibre.android.geometry.LatLng center) {
        if (maplibreMap == null || style == null) return;
        
        try {
            // Получаем экранные координаты центра
            android.graphics.PointF screenPoint = maplibreMap.getProjection().toScreenLocation(center);
            
            // Ищем AIS суда в радиусе 50 пикселей от центра
            java.util.List<org.maplibre.geojson.Feature> features = maplibreMap.queryRenderedFeatures(
                new android.graphics.RectF(
                    screenPoint.x - 50, screenPoint.y - 50,
                    screenPoint.x + 50, screenPoint.y + 50
                ), LAYER_VESSELS);
            
            if (features != null && !features.isEmpty()) {
                // Находим ближайшее AIS судно
                AISVessel closestVessel = null;
                double minDistance = Double.MAX_VALUE;
                
                for (org.maplibre.geojson.Feature feature : features) {
                    String id = feature.id();
                    if (id != null && !"own_vessel".equals(id)) {
                        AISVessel vessel = idToAisVessel.get(id);
                        if (vessel != null) {
                            // Вычисляем расстояние от центра до судна
                            double distance = calculateDistance(
                                center.getLatitude(), center.getLongitude(),
                                vessel.getLatitude(), vessel.getLongitude()
                            );
                            
                            if (distance < minDistance) {
                                minDistance = distance;
                                closestVessel = vessel;
                            }
                        }
                    }
                }
                
                // Если нашли судно в радиусе 100 метров, показываем информацию
                if (closestVessel != null && minDistance < 100) {
                    setAisVesselInfo(closestVessel);
                } else {
                    clearAisVesselInfo();
                }
            } else {
                clearAisVesselInfo();
            }
        } catch (Exception e) {
            // В случае ошибки очищаем информацию
            clearAisVesselInfo();
        }
    }
    
    /**
     * Вычисляет расстояние между двумя точками в метрах
     */
    private double calculateDistance(double lat1, double lon1, double lat2, double lon2) {
        final int R = 6371000; // Радиус Земли в метрах
        
        double lat1Rad = Math.toRadians(lat1);
        double lat2Rad = Math.toRadians(lat2);
        double deltaLatRad = Math.toRadians(lat2 - lat1);
        double deltaLonRad = Math.toRadians(lon2 - lon1);
        
        double a = Math.sin(deltaLatRad / 2) * Math.sin(deltaLatRad / 2) +
                Math.cos(lat1Rad) * Math.cos(lat2Rad) *
                Math.sin(deltaLonRad / 2) * Math.sin(deltaLonRad / 2);
        double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
        
        return R * c;
    }

    @Override
    public void setAisVesselInfo(com.grigowashere.aismap.models.AISVessel vessel) {
        if (cursorOverlay != null) {
            cursorOverlay.setAisVesselInfo(vessel);
        }
    }

    @Override
    public void clearAisVesselInfo() {
        if (cursorOverlay != null) {
            cursorOverlay.clearAisVesselInfo();
        }
    }
    
    /**
     * Настраивает слушатель движения карты для обновления курсора
     */
    private void setupMapMovementListener() {
        if (maplibreMap != null) {
            maplibreMap.addOnCameraMoveListener(() -> {
                // Обновляем координаты курсора при движении карты
                updateCursorFromMapCenter();
            });
        }
    }
}


