package com.grigowashere.aismap.controllers;

import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import com.grigowashere.aismap.models.Vessel;
import com.grigowashere.aismap.models.AISVessel;
import com.grigowashere.aismap.models.AISNavigationAid;
import com.grigowashere.aismap.maps.MapInterface;
import com.grigowashere.aismap.maps.MapInterfaceChangeListener;
import com.grigowashere.aismap.ui.UIDataChangeNotifier;
import com.grigowashere.aismap.utils.SettingsManager;
import com.grigowashere.aismap.ble.hub.AisHubConstants;
import com.grigowashere.aismap.ble.hub.AisHubGattClient;
import com.grigowashere.aismap.ble.hub.AisHubJsonMapper;

import org.json.JSONArray;
import org.json.JSONObject;

import java.util.List;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * Главный координатор приложения
 * Координирует работу всех контроллеров и управляет общим состоянием
 */
public class AppCoordinator implements 
    NMEAController.NMEAControllerListener,
    NetworkController.NetworkControllerListener,
    DataController.DataControllerListener,
    NotificationController.NotificationControllerListener,
    CompassController.CompassControllerListener,
    MapInterface.MarkerClickListener,
    MapInterfaceChangeListener {
    
    private static final String TAG = "AppCoordinator";
    
    private Context context;
    
    // Контроллеры
    private NMEAController nmeaController;
    private NetworkController networkController;
    private DataController dataController;
    private NotificationController notificationController;
    private CompassController compassController;
    private MapController mapController;
    private AisHubGattClient aisHubGattClient;
    
    // Состояние приложения
    private Vessel ownVessel;
    private Map<String, AISVessel> aisVessels;
    private Map<String, AISNavigationAid> navigationAids;
    private Map<String, VesselPathController> aisPathControllers;
    private SettingsManager settingsManager;
    private VesselPathController pathController;
    
    // UI координация
    private UIDataChangeNotifier uiDataNotifier;
    private Handler uiHandler;
    
    // Callbacks для уведомления UI
    private AppCoordinatorListener listener;
    
    // Диагностика
    private long lastServiceLogTime = 0;
    
    // Время последнего получения сообщений ($ GPS) и (! AIS) в elapsedRealtime
    private long lastGPSMessageRealtimeMs;
    private long lastAISMessageRealtimeMs;
    private Integer lastBleRssi = null;
    private Integer lastBleBattery = null;

    // Диагностика: live ownship от AIS Hub
    private long lastHubOwnshipLogUptimeMs = 0;
    private double lastHubOwnshipLat = Double.NaN;
    private double lastHubOwnshipLon = Double.NaN;
    private long lastHubOwnshipRealtimeMs = 0;
    private long lastHubOwnshipHeavyWorkUptimeMs = 0;
    private static final long HUB_OWNSHIP_HEAVY_THROTTLE_MS = 400;
    private boolean hubSnapshotInProgress = false;
    private List<String> hubSnapshotPreviousAisKeys = new ArrayList<>();
    private List<String> hubSnapshotPreviousNavigationAidKeys = new ArrayList<>();
    private int hubSnapshotVesselsApplied = 0;
    private int hubSnapshotBaseStationsApplied = 0;
    private int hubSnapshotNavigationAidsApplied = 0;
    private long lastAisHeavyWorkUptimeMs = 0;
    private static final long AIS_LIVE_HEAVY_THROTTLE_MS = 1000;
    private static final int AIS_UI_BATCH_SIZE = 40;
    private static final int AIS_UI_REMOVAL_BATCH_SIZE = 80;
    
    public interface AppCoordinatorListener {
        void onVesselPositionUpdated(Vessel vessel);
        void onGPSQualityUpdated(Vessel vessel);
        void onShowOwnVesselBottomSheet();
        void onShowAISVesselInfo(AISVessel vessel);
        void onUpdateCompass(float azimuth, List<AISVessel> nearbyVessels);
    }
    
    public AppCoordinator(Context context) {
        this.context = context;
        this.ownVessel = new Vessel();
        this.aisVessels = new LinkedHashMap<>();
        this.navigationAids = new LinkedHashMap<>();
        this.aisPathControllers = new HashMap<>();
        this.settingsManager = new SettingsManager(context);
        this.pathController = new VesselPathController(context, settingsManager);
        this.uiHandler = new Handler(Looper.getMainLooper());
        
        initializeControllers();
        Log.i(TAG, "App Coordinator инициализирован");
    }
    
    /**
     * Инициализирует все контроллеры
     */
    private void initializeControllers() {
        // Создаем контроллеры
        nmeaController = new NMEAController(context);
        networkController = new NetworkController(context);
        dataController = new DataController(context);
        notificationController = new NotificationController(context);
        compassController = new CompassController(context);
        aisHubGattClient = new AisHubGattClient(context);

        // КРИТИЧНО: без setListener колбэки onGPSLocationUpdated/onVesselUpdated
        // и прочие события контроллеров не доходят до координатора — именно из-за
        // этого Android GPS не обновлял ownVessel между снапшотами, а UDP NMEA
        // молчал полностью. Здесь one-shot привязываем AppCoordinator ко всем.
        nmeaController.setListener(this);
        networkController.setListener(this);
        dataController.setListener(this);
        notificationController.setListener(this);
        compassController.setListener(this);
        aisHubGattClient.setCallback(new AisHubGattClient.SessionCallback() {
            @Override
            public void onState(String state) {
                Log.i(TAG, "BLE AIS Hub: " + state);
                if ("disconnected".equals(state) || "stopped".equals(state)) {
                    lastBleRssi = null;
                }
            }

            @Override
            public void onError(String message) {
                Log.e(TAG, "BLE AIS Hub: " + message);
                if (notificationController != null) {
                    notificationController.notifyConnectionError("BLE: " + message);
                }
            }

            @Override
            public void onRssi(int rssi) {
                lastBleRssi = rssi;
            }

            @Override
            public void onBatteryPercent(int percent) {
                lastBleBattery = percent;
            }

            @Override
            public void onDataJson(int msgType, JSONObject json) {
                handleAisHubDataJson(msgType, json);
            }
        });
        
        Log.i(TAG, "Все контроллеры инициализированы и подключены");
    }
    
    /**
     * Устанавливает слушателя для уведомлений
     */
    public void setListener(AppCoordinatorListener listener) {
        this.listener = listener;
    }
    
    /**
     * Устанавливает MapController
     */
    public void setMapController(MapController mapController) {
        // Отписываемся от старого MapController
        if (this.mapController != null) {
            this.mapController.removeMapInterfaceChangeListener(this);
        }
        
        this.mapController = mapController;
        
        // Подписываемся на новый MapController
        if (mapController != null) {
            mapController.addMapInterfaceChangeListener(this);
            Log.i(TAG, "AppCoordinator подключен к MapController");
        }
    }
    
    /**
     * Устанавливает UI Data Change Notifier
     */
    public void setUIDataChangeNotifier(UIDataChangeNotifier notifier) {
        this.uiDataNotifier = notifier;
        Log.i(TAG, "UIDataChangeNotifier установлен: " + (notifier != null ? "success" : "null"));
    }
    
    /**
     * Запускает все контроллеры
     */
    public void startAllControllers() {
        Log.i(TAG, "🚀 Запускаем все контроллеры...");
        
        // Запускаем контроллеры на основе настроек
        startControllersBasedOnSettings();
        
        networkController.startUDPListener();
        compassController.startCompass();
        dataController.startDatabaseCleanup();
        // BLE старт по настройкам
        tryStartBleIfEnabled();
        
        // Восстанавливаем данные из БД
        dataController.restoreDataAsync();
        
        Log.i(TAG, "✅ Все контроллеры запущены");
    }
    
    /**
     * Запускает контроллеры на основе настроек данных
     */
    private void startControllersBasedOnSettings() {
        String gpsSource = settingsManager.getGpsSource();
        Log.i(TAG, "📍 Запускаем контроллеры: gpsSource=" + gpsSource);

        if (settingsManager.isGpsFromHub()) {
            // BLE Hub — единственный источник ownship, Android GPS/NMEA не нужны.
            Log.i(TAG, "📍 Источник: BLE Hub (Android GPS/NMEA не запускаем)");
            return;
        }

        // Внешний источник: только Android LocationManager (координаты, курс,
        // скорость, точность) — raw NMEA-парсер намеренно не стартуем, так как
        // он дублирует данные и шумит в логах.
        Log.i(TAG, "📍 Источник: Android GPS (Android Location API)");
        nmeaController.startGPSLocationListener();
    }

    /**
     * Переключает источник координат и применяет изменение на лету:
     * останавливает/запускает Android-слушатели в соответствии с новым выбором.
     * Если переключились на BLE Hub — гарантируем, что BLE клиент включён,
     * иначе позицию получать будет неоткуда.
     */
    public void applyGpsSourceChange() {
        Log.i(TAG, "🔄 Смена источника GPS → " + settingsManager.getGpsSource());
        nmeaController.stopAndroidNMEAListener();
        nmeaController.stopGPSLocationListener();
        if (settingsManager.isGpsFromHub() && !settingsManager.isBLEEnabled()) {
            settingsManager.setBLEEnabled(true);
        }
        startControllersBasedOnSettings();
        tryStartBleIfEnabled();
    }

    private void configureBleFromSettings() {
        if (aisHubGattClient == null) return;
        String mac = settingsManager.getBLEDeviceMac();
        aisHubGattClient.setDeviceMac(mac);
        Log.i(TAG, "BLE AIS Hub: mac=" + mac);
    }

    private void tryStartBleIfEnabled() {
        if (aisHubGattClient == null) return;
        boolean bleEnabled = settingsManager.isBLEEnabled();
        if (bleEnabled && !aisHubGattClient.isRunning()) {
            aisHubGattClient.start();
            Log.i(TAG, "BLE AIS Hub запущен");
        } else if (!bleEnabled && aisHubGattClient.isRunning()) {
            aisHubGattClient.stop();
            Log.i(TAG, "BLE AIS Hub остановлен по настройкам");
        }
    }

    private void markRecentGpsActivity() {
        lastGPSMessageRealtimeMs = android.os.SystemClock.elapsedRealtime();
    }

    private void markRecentAisActivity() {
        lastAISMessageRealtimeMs = android.os.SystemClock.elapsedRealtime();
    }

    private void handleAisHubDataJson(int msgType, JSONObject root) {
        if (root == null) return;
        try {
            switch (msgType) {
                case AisHubConstants.MSG_HELLO_ACK:
                    // НЕ вызываем enqueueGetSnapshot() здесь.
                    // Запрос snapshot уже гейтится внутри AisHubGattClient.processDataRaw()
                    // через snapshotRequested.compareAndSet — двойной вызов отсюда приводил
                    // к гонке: второй get_snapshot прилетал на сервер до того, как первый
                    // успевал поставить snapshot_in_progress=true, и сервер обрабатывал
                    // ОБА (или второй ловил snapshot_busy и терялся), что приводило
                    // к недополучению судов на клиенте.
                    break;
                case AisHubConstants.MSG_SNAPSHOT_BEGIN:
                    onHubSnapshotBegin();
                    break;
                case AisHubConstants.MSG_SNAPSHOT_CHUNK:
                    onHubSnapshotChunk(root);
                    break;
                case AisHubConstants.MSG_SNAPSHOT_END:
                    // GATT client owns subscribe gating; duplicating it here can
                    // race with long snapshot transfers and queue extra CONTROL writes.
                    onHubSnapshotEnd(root);
                    break;
                case AisHubConstants.MSG_EVENT:
                    onHubLiveEvent(root);
                    break;
                case AisHubConstants.MSG_ERROR:
                    Log.e(TAG, "AIS Hub ERROR payload: " + root);
                    break;
                default:
                    break;
            }
        } catch (Exception e) {
            Log.e(TAG, "handleAisHubDataJson: " + e.getMessage(), e);
        }
    }

    private void onHubSnapshotBegin() {
        synchronized (aisVessels) {
            hubSnapshotPreviousAisKeys = new ArrayList<>(aisVessels.keySet());
            aisVessels.clear();
        }
        synchronized (navigationAids) {
            hubSnapshotPreviousNavigationAidKeys = new ArrayList<>(navigationAids.keySet());
            navigationAids.clear();
        }
        synchronized (aisPathControllers) {
            aisPathControllers.clear();
        }
        hubSnapshotInProgress = true;
        hubSnapshotVesselsApplied = 0;
        hubSnapshotBaseStationsApplied = 0;
        hubSnapshotNavigationAidsApplied = 0;
        Log.i(TAG, "AIS Hub snapshot: кэш целей очищен, старых целей=" + hubSnapshotPreviousAisKeys.size() +
                ", старых знаков=" + hubSnapshotPreviousNavigationAidKeys.size());
    }

    private void onHubSnapshotChunk(JSONObject root) {
        String section = root.optString("section", "");
        if ("ownship".equals(section)) {
            JSONObject item = root.optJSONObject("item");
            if (item != null) {
                applyHubOwnshipJson(item);
            }
        } else if ("vessels".equals(section)) {
            JSONArray items = root.optJSONArray("items");
            if (items != null) {
                List<AISVessel> vessels = new ArrayList<>(items.length());
                for (int i = 0; i < items.length(); i++) {
                    JSONObject it = items.optJSONObject(i);
                    if (it == null) continue;
                    AISVessel v = AisHubJsonMapper.aisVesselFromJson(it);
                    if (v != null) {
                        applySnapshotAisVessel(v);
                        hubSnapshotVesselsApplied++;
                        vessels.add(v);
                    }
                }
                if (!vessels.isEmpty()) {
                    markRecentAisActivity();
                    dataController.saveAISVessels(vessels);
                    publishAisVesselsToUiBatched(vessels);
                }
            }
        } else if ("base_stations".equals(section)) {
            JSONArray items = root.optJSONArray("items");
            if (items != null) {
                List<AISVessel> stations = new ArrayList<>(items.length());
                for (int i = 0; i < items.length(); i++) {
                    JSONObject it = items.optJSONObject(i);
                    if (it == null) continue;
                    AISVessel v = AisHubJsonMapper.baseStationFromJson(it);
                    if (v != null) {
                        applySnapshotAisVessel(v);
                        hubSnapshotBaseStationsApplied++;
                        stations.add(v);
                    }
                }
                if (!stations.isEmpty()) {
                    markRecentAisActivity();
                    dataController.saveAISVessels(stations);
                    publishAisVesselsToUiBatched(stations);
                }
            }
        } else if ("atons".equals(section)) {
            JSONArray items = root.optJSONArray("items");
            if (items != null) {
                List<AISVessel> aidVessels = new ArrayList<>(items.length());
                for (int i = 0; i < items.length(); i++) {
                    JSONObject it = items.optJSONObject(i);
                    if (it == null) continue;
                    AISNavigationAid aid = AisHubJsonMapper.navigationAidFromJson(it);
                    if (aid != null) {
                        applySnapshotNavigationAid(aid);
                        aidVessels.add(createTempVesselFromNavigationAid(aid));
                    }
                }
                if (!aidVessels.isEmpty()) {
                    markRecentAisActivity();
                    dataController.saveAISVessels(aidVessels);
                    publishAisVesselsToUiBatched(aidVessels);
                }
            }
        }
    }

    private void onHubSnapshotEnd(JSONObject root) {
        hubSnapshotInProgress = false;

        List<String> staleKeys = new ArrayList<>();
        synchronized (aisVessels) {
            for (String mmsi : hubSnapshotPreviousAisKeys) {
                if (!aisVessels.containsKey(mmsi)) {
                    staleKeys.add(mmsi);
                }
            }
        }
        for (String mmsi : hubSnapshotPreviousNavigationAidKeys) {
            boolean presentAsAid;
            boolean presentAsVessel;
            synchronized (navigationAids) {
                presentAsAid = navigationAids.containsKey(mmsi);
            }
            synchronized (aisVessels) {
                presentAsVessel = aisVessels.containsKey(mmsi);
            }
            if (!presentAsAid && !presentAsVessel) {
                staleKeys.add(mmsi);
            }
        }
        hubSnapshotPreviousAisKeys.clear();
        hubSnapshotPreviousNavigationAidKeys.clear();

        publishAisRemovalsToUiBatched(staleKeys);
        updateCompass();

        Log.i(TAG, "AIS Hub snapshot завершен: vessels=" + hubSnapshotVesselsApplied +
                ", base_stations=" + hubSnapshotBaseStationsApplied +
                ", atons=" + hubSnapshotNavigationAidsApplied +
                ", удалено с карты=" + staleKeys.size());
    }

    private void applySnapshotAisVessel(AISVessel vessel) {
        if (vessel == null || vessel.getMmsi() == null) {
            return;
        }
        synchronized (aisVessels) {
            aisVessels.put(vessel.getMmsi(), vessel);
        }
    }

    private void applySnapshotNavigationAid(AISNavigationAid aid) {
        if (aid == null || aid.getMmsi() == null) {
            return;
        }
        synchronized (navigationAids) {
            navigationAids.put(aid.getMmsi(), aid);
        }
        hubSnapshotNavigationAidsApplied++;
    }

    private void publishAisVesselsToUiBatched(List<AISVessel> vessels) {
        if (uiDataNotifier == null || vessels == null || vessels.isEmpty()) {
            return;
        }
        final List<AISVessel> copy = new ArrayList<>(vessels);
        for (int start = 0; start < copy.size(); start += AIS_UI_BATCH_SIZE) {
            final int from = start;
            final int to = Math.min(start + AIS_UI_BATCH_SIZE, copy.size());
            uiHandler.post(() -> {
                if (uiDataNotifier == null) return;
                for (int i = from; i < to; i++) {
                    uiDataNotifier.onAISVesselChanged(copy.get(i));
                }
            });
        }
    }

    private void publishAisRemovalsToUiBatched(List<String> mmsis) {
        if (uiDataNotifier == null || mmsis == null || mmsis.isEmpty()) {
            return;
        }
        final List<String> copy = new ArrayList<>(mmsis);
        for (int start = 0; start < copy.size(); start += AIS_UI_REMOVAL_BATCH_SIZE) {
            final int from = start;
            final int to = Math.min(start + AIS_UI_REMOVAL_BATCH_SIZE, copy.size());
            uiHandler.post(() -> {
                if (uiDataNotifier == null) return;
                for (int i = from; i < to; i++) {
                    uiDataNotifier.onAISVesselRemoved(copy.get(i));
                }
            });
        }
    }

    private void onHubLiveEvent(JSONObject root) {
        String type = root.optString("type", "");
        JSONObject data = root.optJSONObject("data");
        if ("ownship.update".equals(type) && data != null) {
            long now = android.os.SystemClock.uptimeMillis();
            if (now - lastHubOwnshipLogUptimeMs > 1000) {
                lastHubOwnshipLogUptimeMs = now;
                double lat = data.optDouble("lat", Double.NaN);
                double lon = data.optDouble("lon", Double.NaN);
                Log.d(TAG, "AIS Hub EVENT ownship.update: lat=" + lat + " lon=" + lon);
            }
        }
        if ("ownship.update".equals(type) && data != null) {
            applyHubOwnshipJson(data);
        } else if ("target.update".equals(type) && data != null) {
            AISVessel v = AisHubJsonMapper.aisVesselFromJson(data);
            if (v != null) {
                mergeAisVesselFromSource(v);
            }
        } else if ("base_station.update".equals(type) && data != null) {
            AISVessel v = AisHubJsonMapper.baseStationFromJson(data);
            if (v != null) {
                mergeAisVesselFromSource(v);
            }
        } else if ("aton.update".equals(type) && data != null) {
            AISNavigationAid aid = AisHubJsonMapper.navigationAidFromJson(data);
            if (aid != null) {
                onNavigationAidUpdated(aid);
            }
        } else if ("stats.update".equals(type)) {
            Log.d(TAG, "AIS Hub stats.update: " + root);
        }
    }

    private void applyHubOwnshipJson(JSONObject o) {
        // Если пользователь выбрал источник "Android GPS" — BLE Hub по-прежнему
        // передаёт ownship.update (мы подписаны на него для диагностики), но в
        // модель эти данные не пишем, чтобы они не боролись с Android GPS.
        if (settingsManager != null && settingsManager.isGpsFromAndroid()) {
            return;
        }
        AisHubJsonMapper.mergeVesselOwnship(o, ownVessel);
        markRecentGpsActivity();
        lastHubOwnshipRealtimeMs = android.os.SystemClock.elapsedRealtime();
        double lat = ownVessel.getLatitude();
        double lon = ownVessel.getLongitude();
        if (Double.isNaN(lastHubOwnshipLat) || Double.isNaN(lastHubOwnshipLon) ||
                Math.abs(lat - lastHubOwnshipLat) > 1e-6 || Math.abs(lon - lastHubOwnshipLon) > 1e-6) {
            lastHubOwnshipLat = lat;
            lastHubOwnshipLon = lon;
            Log.d(TAG, "AIS Hub ownVessel applied: lat=" + lat + " lon=" + lon +
                    " mode=" + settingsManager.getDataMode());
        }

        // Важно: ownship.update может приходить очень часто (десятки раз в секунду).
        // Модель обновляем всегда, а тяжёлые операции (путь/БД/UI) — с throttling,
        // чтобы не забивать главный поток и не провоцировать нестабильность BLE.
        long nowUptime = android.os.SystemClock.uptimeMillis();
        if (nowUptime - lastHubOwnshipHeavyWorkUptimeMs < HUB_OWNSHIP_HEAVY_THROTTLE_MS) {
            return;
        }
        lastHubOwnshipHeavyWorkUptimeMs = nowUptime;

        if (pathController != null && isValidCoordinates(lat, lon)) {
            pathController.addPathPoint(
                    lon,
                    lat,
                    (float) ownVessel.getSpeed()
            );
        }
        dataController.saveVesselPosition(ownVessel);
        updateCompass();
        if (listener != null) {
            listener.onVesselPositionUpdated(ownVessel);
        }
        if (uiDataNotifier != null) {
            uiDataNotifier.onVesselPositionChanged(ownVessel);
        }
    }
    
    /**
     * Останавливает все контроллеры
     */
    public void stopAllControllers() {
        Log.i(TAG, "⏹️ Останавливаем все контроллеры...");
        
        nmeaController.stopAndroidNMEAListener();
        nmeaController.stopGPSLocationListener();
        networkController.stopUDPListener();
        compassController.stopCompass();
        dataController.stopDatabaseCleanup();
        if (aisHubGattClient != null) {
            aisHubGattClient.stop();
        }
        
        Log.i(TAG, "✅ Все контроллеры остановлены");
    }
    
    /**
     * Применяет настройки ко всем контроллерам
     */
    public void applySettings() {
        // Применяем настройки к контроллерам
        String dataMode = settingsManager.getDataMode();
        nmeaController.setDataMode(dataMode);
        
        boolean udpEnabled = settingsManager.isUDPEnabled();
        networkController.setUDPEnabled(udpEnabled);
        
        int udpPort = settingsManager.getUDPPort();
        networkController.setUDPPort(udpPort);
        // Применяем BLE настройки
        configureBleFromSettings();
        // Гарантируем запуск BLE при включённой настройке (без лишних остановок)
        tryStartBleIfEnabled();
        
        boolean notificationsEnabled = settingsManager.areNotificationsEnabled();
        notificationController.setNotificationsEnabled(notificationsEnabled);
        
        // Перезапускаем контроллеры данных на основе новых настроек
        restartDataControllers();
        
        Log.i(TAG, "Настройки применены ко всем контроллерам");
    }
    
    /**
     * Перезапускает контроллеры данных на основе текущих настроек
     */
    private void restartDataControllers() {
        Log.i(TAG, "🔄 Перезапускаем контроллеры данных...");
        
        // Останавливаем текущие контроллеры данных
        nmeaController.stopAndroidNMEAListener();
        nmeaController.stopGPSLocationListener();
        
        // Запускаем с новыми настройками
        startControllersBasedOnSettings();
        // BLE не трогаем здесь — управляется отдельно через tryStartBleIfEnabled/applySettings
        
        Log.i(TAG, "✅ Контроллеры данных перезапущены");
    }
    
    // Реализация NMEAControllerListener
    
    @Override
    public void onGPSLocationUpdated(Vessel vessel) {
        // Если активен источник "BLE Hub" — Android GPS игнорируется: BLE уже
        // даёт ownship и мы не хотим, чтобы два трека конкурировали.
        if (settingsManager != null && settingsManager.isGpsFromHub()) {
            return;
        }
        if (vessel == null || ownVessel == null) return;

        // НЕ подменяем ownVessel целиком — иначе теряется магнитный компас
        // и прочие поля, которые обновляются независимо. Копируем в старую
        // модель только то, что фактически пришло от Android LocationManager.
        if (vessel.getLatitude() != 0 || vessel.getLongitude() != 0) {
            ownVessel.setLatitude(vessel.getLatitude());
            ownVessel.setLongitude(vessel.getLongitude());
        }
        if (vessel.getSpeed() >= 0) ownVessel.setSpeed(vessel.getSpeed());
        if (!Double.isNaN(vessel.getCourse())) ownVessel.setCourse(vessel.getCourse());
        if (vessel.getAccuracy() > 0) ownVessel.setAccuracy(vessel.getAccuracy());
        if (vessel.getFixTime() != 0) ownVessel.setFixTime(vessel.getFixTime());
        if (vessel.getFixQuality() != null) ownVessel.setFixQuality(vessel.getFixQuality());
        ownVessel.setSatellites(vessel.getSatellites());
        ownVessel.setActiveSatellites(vessel.getActiveSatellites());

        markRecentGpsActivity();
        if (pathController != null && isValidCoordinates(ownVessel.getLatitude(), ownVessel.getLongitude())) {
            pathController.addPathPoint(
                    ownVessel.getLongitude(),
                    ownVessel.getLatitude(),
                    (float) ownVessel.getSpeed());
        }
        dataController.saveVesselPosition(ownVessel);
        updateCompass();

        if (listener != null) {
            listener.onVesselPositionUpdated(ownVessel);
            listener.onGPSQualityUpdated(ownVessel);
        }
        if (uiDataNotifier != null) {
            uiDataNotifier.onVesselPositionChanged(ownVessel);
        }
    }

    @Override
    public void onVesselUpdated(Vessel vessel) {
        // При источнике "BLE Hub" NMEA-обновлений координат мы не применяем,
        // чтобы не перетирать ownship, полученный по BLE.
        if (settingsManager != null && settingsManager.isGpsFromHub()) {
            return;
        }

        // Обновляем координаты, если они есть
        if (vessel.getLatitude() != 0 && vessel.getLongitude() != 0) {
            ownVessel.setLatitude(vessel.getLatitude());
            ownVessel.setLongitude(vessel.getLongitude());
            
            // Добавляем точку в путь судна
            if (pathController != null) {
                pathController.addPathPoint(
                    vessel.getLongitude(), 
                    vessel.getLatitude(), 
                    (float) vessel.getSpeed()
                );
            }
        }
        
        // Обновляем дополнительные данные (без фильтров по нулю, чтобы корректно обрабатывать сбросы)
        ownVessel.setCourse(vessel.getCourse());
        ownVessel.setSpeed(vessel.getSpeed());
        ownVessel.setSatellites(vessel.getSatellites());
        ownVessel.setActiveSatellites(vessel.getActiveSatellites());
        ownVessel.setAltitude(vessel.getAltitude());
        ownVessel.setFixQuality(vessel.getFixQuality());
        // Пробрасываем точность и время фикса из парсера (RMC/ZDA)
        ownVessel.setAccuracy(vessel.getAccuracy());
        ownVessel.setFixTime(vessel.getFixTime());
        ownVessel.setFixTimeNmea(vessel.getFixTimeNmea());

        // Обновляем компас после изменения курса
        updateCompass();
        
        // Сохраняем в БД
        dataController.saveVesselPosition(ownVessel);
        
        // Уведомляем UI
        if (listener != null) {
            listener.onVesselPositionUpdated(ownVessel);
        }
        
        if (uiDataNotifier != null) {
            uiDataNotifier.onVesselPositionChanged(ownVessel);
        }
    }
    
    @Override
    public void onDOPUpdated(double pdop, double hdop, double vdop) {
        ownVessel.setPdop(pdop);
        ownVessel.setHdop(hdop);
        ownVessel.setVdop(vdop);
        // Обновляем оценку точности в метрах (из HDOP)
        if (hdop > 0) {
            ownVessel.setAccuracy((float)(hdop * 5.0));
        }
        
        if (listener != null) {
            listener.onGPSQualityUpdated(ownVessel);
        }
    }
    
    @Override
    public void onAISVesselUpdated(AISVessel vessel) {
        mergeAisVesselFromSource(vessel);
    }

    /**
     * Объединяет обновление AIS-цели из NMEA или AIS Hub в общее хранилище.
     */
    public void mergeAisVesselFromSource(AISVessel vessel) {
        if (vessel == null || vessel.getMmsi() == null) {
            return;
        }
        markRecentAisActivity();
        AISVessel existingVessel = findAISVesselByMMSI(vessel.getMmsi());
        AISVessel appliedVessel;
        
        if (existingVessel != null) {
            if (vessel.getLastSafetyMessage() != null && !vessel.getLastSafetyMessage().isEmpty()) {
                String prev = existingVessel.getLastSafetyMessage();
                String curr = vessel.getLastSafetyMessage();
                if (prev == null || !prev.equals(curr)) {
                    notificationController.notifySafetyMessage(vessel.getMmsi(), curr);
                }
                existingVessel.setLastSafetyMessage(curr);
            }
            if (isValidCoordinates(vessel.getLatitude(), vessel.getLongitude())) {
                existingVessel.updatePosition(
                    vessel.getLatitude(),
                    vessel.getLongitude(),
                    vessel.getCourse(),
                    vessel.getSpeed()
                );
            }
            mergeAisVesselDetails(existingVessel, vessel);
            dataController.saveAISVessel(existingVessel);
            if (uiDataNotifier != null) {
                uiDataNotifier.onAISVesselChanged(existingVessel);
            }
            appliedVessel = existingVessel;
        } else {
            synchronized (aisVessels) {
                aisVessels.put(vessel.getMmsi(), vessel);
            }
            if (vessel.getLastSafetyMessage() != null && !vessel.getLastSafetyMessage().isEmpty()) {
                notificationController.notifySafetyMessage(vessel.getMmsi(), vessel.getLastSafetyMessage());
            }
            notificationController.notifyNewAISTarget();
            dataController.saveAISVessel(vessel);
            if (uiDataNotifier != null) {
                uiDataNotifier.onAISVesselChanged(vessel);
            }
            appliedVessel = vessel;
        }
        maybeDoAisHeavyWork(appliedVessel);
    }

    private void mergeAisVesselDetails(AISVessel target, AISVessel source) {
        if (target == null || source == null) {
            return;
        }
        if (source.getVesselName() != null && !source.getVesselName().isEmpty()) {
            target.setVesselName(source.getVesselName());
        }
        if (source.getCallSign() != null && !source.getCallSign().isEmpty()) {
            target.setCallSign(source.getCallSign());
        }
        if (source.getImo() > 0) {
            target.setImo(source.getImo());
        }
        if (source.getVesselType() != null && !source.getVesselType().isEmpty()) {
            target.setVesselType(source.getVesselType());
        }
        if (source.getVesselClass() != null && !source.getVesselClass().isEmpty()) {
            target.setVesselClass(source.getVesselClass());
        }
        if (!Double.isNaN(source.getHeading())) {
            target.setHeading(source.getHeading());
        }
        if (!Double.isNaN(source.getRateOfTurn())) {
            target.setRateOfTurn(source.getRateOfTurn());
        }
        if (source.getLength() > 0.0) {
            target.setLength(source.getLength());
        }
        if (source.getWidth() > 0.0) {
            target.setWidth(source.getWidth());
        }
        if (source.getDraft() > 0.0) {
            target.setDraft(source.getDraft());
        }
        if (source.getDestination() != null && !source.getDestination().isEmpty()) {
            target.setDestination(source.getDestination());
        }
        if (source.getEta() != null) {
            target.setEta(source.getEta());
        }
        if (source.getSignalStrength() != 0) {
            target.setSignalStrength(source.getSignalStrength());
        }
        if (source.getNavigationalStatus() != null && !source.getNavigationalStatus().isEmpty()) {
            target.setNavigationalStatus(source.getNavigationalStatus());
        }
        if (source.getVendorId() != null && !source.getVendorId().isEmpty()) {
            target.setVendorId(source.getVendorId());
        }
        target.setPositionAccuracy(source.isPositionAccuracy());
        if (source.getLastUpdate() != null) {
            target.setLastUpdate(source.getLastUpdate());
        }
    }

    private void maybeDoAisHeavyWork(AISVessel vessel) {
        long now = android.os.SystemClock.uptimeMillis();
        if (now - lastAisHeavyWorkUptimeMs < AIS_LIVE_HEAVY_THROTTLE_MS) {
            return;
        }
        lastAisHeavyWorkUptimeMs = now;
        addAISVesselPathPoint(vessel);
        updateCompass();
    }

    @Override
    public void onNavigationAidUpdated(AISNavigationAid navigationAid) {
        if (navigationAid == null || navigationAid.getMmsi() == null) {
            return;
        }
        markRecentAisActivity();

        // Проверяем, есть ли уже такой навигационный знак
        AISNavigationAid existingAid = findNavigationAidByMMSI(navigationAid.getMmsi());

        if (existingAid != null) {
            // Обновляем существующий навигационный знак
            existingAid.updatePosition(navigationAid.getLatitude(), navigationAid.getLongitude());
            existingAid.setAidName(navigationAid.getAidName());
            existingAid.setAidType(navigationAid.getAidType());
            existingAid.setLength(navigationAid.getLength());
            existingAid.setWidth(navigationAid.getWidth());
            existingAid.setDraft(navigationAid.getDraft());
            existingAid.setPositionAccuracy(navigationAid.isPositionAccuracy());
            existingAid.setOffPositionIndicator(navigationAid.isOffPositionIndicator());
            existingAid.setRaimFlag(navigationAid.isRaimFlag());
            existingAid.setLastUpdate(navigationAid.getLastUpdate());

            // Сохраняем в БД
            dataController.saveNavigationAid(existingAid);

            // Уведомляем UI Coordinator об обновлении навигационного знака
            if (uiDataNotifier != null) {
                uiDataNotifier.onAISVesselChanged(createTempVesselFromNavigationAid(existingAid));
            }
        } else {
            // Новый навигационный знак
            synchronized (navigationAids) {
                navigationAids.put(navigationAid.getMmsi(), navigationAid);
            }

            // Сохраняем в БД
            dataController.saveNavigationAid(navigationAid);

            // Уведомляем UI Coordinator о новом навигационном знаке
            if (uiDataNotifier != null) {
                uiDataNotifier.onAISVesselChanged(createTempVesselFromNavigationAid(navigationAid));
            }
        }

        Log.d(TAG, "Навигационный знак обновлен: " + navigationAid.getMmsi() + " (" + navigationAid.getAidName() + ")");
    }

    @Override
    public void onParseError(String error) {
        Log.e(TAG, "Ошибка парсинга NMEA: " + error);
    }
    
    // Реализация NetworkControllerListener
    
    @Override
    public void onDataReceived(String data, String sourceAddress, int sourcePort) {
        // Обновляем метки времени по префиксу в UI потоке (быстрая операция)
        updateLastMessageAgesFromRaw(data);
        
        // Передаем данные в NMEA контроллер для парсинга
        nmeaController.parseNMEAMessage(data);
    }
    
    @Override
    public void onUDPError(String error) {
        Log.e(TAG, "UDP ошибка: " + error);
        notificationController.notifyConnectionError(error);
    }
    
    // Реализация DataControllerListener
    
    @Override
    public void onDataRestored(Vessel vessel, List<AISVessel> aisVessels, List<AISNavigationAid> navigationAids) {
        // НЕ восстанавливаем координаты ownVessel из БД, чтобы не перезаписать координаты телефона
        // if (vessel != null) {
        //     this.ownVessel = vessel;
        // }

        if (aisVessels != null) {
            synchronized (this.aisVessels) {
                this.aisVessels.clear();
                for (AISVessel ais : aisVessels) {
                    if (ais.getMmsi() != null) {
                        this.aisVessels.put(ais.getMmsi(), ais);
                    }
                }
            }
        }

        if (navigationAids != null) {
            synchronized (this.navigationAids) {
                this.navigationAids.clear();
                for (AISNavigationAid aid : navigationAids) {
                    if (aid.getMmsi() != null) {
                        this.navigationAids.put(aid.getMmsi(), aid);
                    }
                }
            }
        }

        Log.i(TAG, "Данные восстановлены из БД: " + (aisVessels != null ? aisVessels.size() : 0) + " судов, " +
            (navigationAids != null ? navigationAids.size() : 0) + " навигационных знаков (координаты ownVessel пропущены)");
    }
    
    @Override
    public void onDataSaved(String dataType, boolean success) {
        Log.d(TAG, "Данные сохранены: " + dataType + " = " + success);
    }
    
    @Override
    public void onDataCleaned(int removedCount) {
        Log.i(TAG, "Очищено " + removedCount + " устаревших записей из БД");
    }
    
    // Реализация NotificationControllerListener
    
    @Override
    public void onNotificationShown(String type, String message) {
        Log.d(TAG, "Уведомление показано: " + type + " - " + message);
    }
    
    @Override
    public void onNotificationError(String error) {
        Log.e(TAG, "Ошибка уведомления: " + error);
    }
    
    // Реализация CompassControllerListener
    
    @Override
    public void onCompassChanged(float azimuth) {
        // Обновляем магнитный компас в модели нашего судна
        if (ownVessel != null) {
            ownVessel.setMagneticCompass(azimuth);
        }
        
        // Уведомляем UI
        if (listener != null) {
            List<AISVessel> nearbyVessels = getNearbyVessels();
            listener.onUpdateCompass(azimuth, nearbyVessels);
        }
    }
    
    @Override
    public void onCompassError(String error) {
        Log.e(TAG, "Ошибка компаса: " + error);
    }
    
    // Реализация MarkerClickListener
    
    @Override
    public void onOwnVesselClick(Vessel vessel) {
        Log.i(TAG, "Клик по нашему судну: " + vessel);
        if (listener != null) {
            listener.onShowOwnVesselBottomSheet();
        }
    }
    
    @Override
    public void onAISVesselClick(AISVessel vessel) {
        Log.i(TAG, "Клик по AIS судну: " + vessel);
        if (listener != null) {
            listener.onShowAISVesselInfo(vessel);
        }
    }
    
    // Реализация MapInterfaceChangeListener
    
    @Override
    public void onMapInterfaceChanged(MapInterface oldMapInterface, MapInterface newMapInterface) {
        Log.i(TAG, "🔄 MapInterface изменен в MapController");
        
        if (newMapInterface != null) {
            // Устанавливаем MarkerClickListener на новую карту
            newMapInterface.setMarkerClickListener(this);
            Log.i(TAG, "MarkerClickListener установлен на новую карту");
            
            // Восстанавливаем состояние на новой карте
            restoreMapStateOnNewInterface();
        }
    }
    
    /**
     * Восстанавливает состояние карты на новом MapInterface
     */
    private void restoreMapStateOnNewInterface() {
        if (mapController == null || uiDataNotifier == null) {
            Log.w(TAG, "⚠️ Нельзя восстановить состояние: mapController=" + (mapController != null) + 
                  ", uiDataNotifier=" + (uiDataNotifier != null));
            return;
        }
        
        Log.i(TAG, "🔄 Восстановление состояния карты на новом MapInterface");
        
        // Восстанавливаем позицию собственного судна
        if (ownVessel != null && ownVessel.getLatitude() != 0 && ownVessel.getLongitude() != 0) {
            Log.i(TAG, "📍 Восстанавливаем позицию судна: " + ownVessel.getLatitude() + "," + ownVessel.getLongitude());
            uiDataNotifier.onVesselPositionChanged(ownVessel);
        }
        
        // Восстанавливаем AIS суда
        if (aisVessels != null && !aisVessels.isEmpty()) {
            List<AISVessel> vesselsToRestore;
            synchronized (aisVessels) {
                vesselsToRestore = new ArrayList<>(aisVessels.values());
            }
            Log.i(TAG, "🚢 Восстанавливаем " + vesselsToRestore.size() + " AIS судов");
            publishAisVesselsToUiBatched(vesselsToRestore);
        }

        // Восстанавливаем навигационные знаки
        if (navigationAids != null && !navigationAids.isEmpty()) {
            List<AISVessel> aidsToRestore = new ArrayList<>();
            synchronized (navigationAids) {
                for (AISNavigationAid aid : navigationAids.values()) {
                    aidsToRestore.add(createTempVesselFromNavigationAid(aid));
                }
            }
            Log.i(TAG, "🔗 Восстанавливаем " + aidsToRestore.size() + " навигационных знаков");
            publishAisVesselsToUiBatched(aidsToRestore);
        }

        Log.i(TAG, "✅ Состояние карты восстановлено");
    }

    // Вспомогательные методы

    private AISVessel findAISVesselByMMSI(String mmsi) {
        if (mmsi == null) {
            return null;
        }
        synchronized (aisVessels) {
            return aisVessels.get(mmsi);
        }
    }

    private AISNavigationAid findNavigationAidByMMSI(String mmsi) {
        if (mmsi == null) {
            return null;
        }
        synchronized (navigationAids) {
            return navigationAids.get(mmsi);
        }
    }

    /**
     * Создает временный AISVessel из навигационного знака для совместимости с UI
     */
    private AISVessel createTempVesselFromNavigationAid(AISNavigationAid navigationAid) {
        AISVessel tempVessel = new AISVessel(navigationAid.getMmsi());
        tempVessel.setLatitude(navigationAid.getLatitude());
        tempVessel.setLongitude(navigationAid.getLongitude());
        tempVessel.setVesselName(navigationAid.getAidName());
        tempVessel.setVesselClass("Navigation Aid");
        tempVessel.setVesselType(navigationAid.getAidTypeDescription());
        tempVessel.setLength(navigationAid.getLength());
        tempVessel.setWidth(navigationAid.getWidth());
        tempVessel.setDraft(navigationAid.getDraft());
        tempVessel.setPositionAccuracy(navigationAid.isPositionAccuracy());
        tempVessel.setLastUpdate(navigationAid.getLastUpdate());

        // Добавляем специальные поля для навигационных знаков
        tempVessel.setDestination("Type: " + navigationAid.getAidType() + " (" + navigationAid.getAidTypeDescription() + ")");
        if (navigationAid.isOffPositionIndicator()) {
            tempVessel.setDestination(tempVessel.getDestination() + " - Off Position");
        }
        if (navigationAid.isRaimFlag()) {
            tempVessel.setDestination(tempVessel.getDestination() + " - RAIM Active");
        }

        return tempVessel;
    }

    private List<AISVessel> getNearbyVessels() {
        List<AISVessel> nearby = new ArrayList<>();
        double maxDistance = 10000; // 10 км в метрах

        synchronized (aisVessels) {
            for (AISVessel vessel : aisVessels.values()) {
                double distance = com.grigowashere.aismap.utils.GeoUtils.calculateDistance(ownVessel, vessel);
                if (distance <= maxDistance) {
                    nearby.add(vessel);
                }
            }
        }
        
        return nearby;
    }
    
    private void updateCompass() {
        if (listener != null) {
            float azimuth = (float) ownVessel.getCourse();
            List<AISVessel> nearbyVessels = getNearbyVessels();
            
            uiHandler.post(() -> {
                listener.onUpdateCompass(azimuth, nearbyVessels);
            });
        }
    }
    
    private void addAISVesselPathPoint(AISVessel vessel) {
        if (vessel == null || vessel.getMmsi() == null) {
            return;
        }
        
        // Проверяем валидность координат
        if (!isValidCoordinates(vessel.getLatitude(), vessel.getLongitude())) {
            return;
        }
        
        String mmsi = vessel.getMmsi();
        
        // Получаем или создаем VesselPathController для этого AIS судна
        VesselPathController aisPathController = aisPathControllers.get(mmsi);
        if (aisPathController == null) {
            // Ограничиваем количество трекеров для производительности
            if (aisPathControllers.size() >= 20) {
                Log.w(TAG, "Достигнуто максимальное количество AIS трекеров (20), пропускаем создание для " + mmsi);
                return;
            }
            
            aisPathController = new VesselPathController(context, settingsManager, mmsi);
            aisPathControllers.put(mmsi, aisPathController);
            Log.d(TAG, "Создан VesselPathController для AIS судна " + mmsi + " (всего трекеров: " + aisPathControllers.size() + ")");
        }
        
        // Добавляем точку в путь
        boolean pointAdded = aisPathController.addPathPoint(
            vessel.getLongitude(), 
            vessel.getLatitude(), 
            (float) vessel.getSpeed()
        );
        
        if (pointAdded) {
            Log.d(TAG, "Точка пути добавлена для AIS " + mmsi + ": " + aisPathController.getPathPointsCount() + " точек");
            
            // Уведомляем UI о изменении пути AIS судна
            if (uiDataNotifier != null) {
                uiDataNotifier.onVesselPathChanged(mmsi);
            }
        }
    }
    
    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;
    }
    
    // Геттеры для доступа к контроллерам и данным
    
    public Vessel getOwnVessel() {
        return ownVessel;
    }

    /**
     * Инициализирует позицию собственного судна координатами с устройства
     * и уведомляет UI, чтобы метка появилась сразу при старте.
     */
    public void seedOwnVesselFromDeviceLocation(double latitude, double longitude) {
        if (!isValidCoordinates(latitude, longitude)) {
            Log.w(TAG, "seedOwnVesselFromDeviceLocation: невалидные координаты: " + latitude + "," + longitude);
            return;
        }
        if (ownVessel == null) {
            ownVessel = new com.grigowashere.aismap.models.Vessel();
        }
        ownVessel.setLatitude(latitude);
        ownVessel.setLongitude(longitude);
        ownVessel.setFixTime(System.currentTimeMillis());

        // Добавим точку в путь (как и при обычном обновлении), чтобы сразу отрисовался путь
        if (pathController != null) {
            pathController.addPathPoint(
                longitude,
                latitude,
                (float) ownVessel.getSpeed()
            );
        }

        // Уведомим UI
        if (listener != null) {
            listener.onVesselPositionUpdated(ownVessel);
        }
        if (uiDataNotifier != null) {
            uiDataNotifier.onVesselPositionChanged(ownVessel);
        }
        Log.i(TAG, "ownVessel инициализирован координатами устройства: " + latitude + "," + longitude);
    }
    
    public List<AISVessel> getAISVessels() {
        synchronized (aisVessels) {
            return new ArrayList<>(aisVessels.values());
        }
    }

    public List<AISNavigationAid> getNavigationAids() {
        synchronized (navigationAids) {
            return new ArrayList<>(navigationAids.values());
        }
    }
    
    public VesselPathController getPathController() {
        return pathController;
    }
    
    public VesselPathController getAISVesselPathController(String mmsi) {
        return aisPathControllers.get(mmsi);
    }
    
    public String getSettingsStatus() {
        return String.format(
            "NMEA: Android=%s, GPS=%s\n" +
            "Network: %s\n" +
            "Compass: %s\n" +
            "Notifications: %s",
            nmeaController.isAndroidNMEAListenerActive() ? "включен" : "выключен",
            nmeaController.isGPSLocationListenerActive() ? "включен" : "выключен",
            networkController.getNetworkStatus(),
            compassController.getCompassStatus(),
            notificationController.getNotificationStatus()
        );
    }
    
    /**
     * Возвращает секунды с последнего GPS ($) сообщения; -1 если не было
     */
    public int getSecondsSinceLastGPSMessage() {
        if (lastGPSMessageRealtimeMs <= 0) return -1;
        long diff = android.os.SystemClock.elapsedRealtime() - lastGPSMessageRealtimeMs;
        if (diff < 0) return 0;
        return (int)(diff / 1000L);
    }

    /**
     * Возвращает секунды с последнего AIS (!) сообщения; -1 если не было
     */
    public int getSecondsSinceLastAISMessage() {
        if (lastAISMessageRealtimeMs <= 0) return -1;
        long diff = android.os.SystemClock.elapsedRealtime() - lastAISMessageRealtimeMs;
        if (diff < 0) return 0;
        return (int)(diff / 1000L);
    }

    public Integer getLastBleRssi() {
        return lastBleRssi;
    }

    public Integer getLastBleBattery() {
        return lastBleBattery;
    }
    
    /**
     * Центрирует карту на позиции нашего судна
     */
    public void centerOnOwnVessel() {
        if (ownVessel != null) {
            Log.d(TAG, "Запрос центрирования карты на судне: " + ownVessel.getLatitude() + "," + ownVessel.getLongitude());
            
            // Уведомляем UI Coordinator о необходимости центрирования карты
            if (uiDataNotifier != null) {
                uiDataNotifier.onRequestCenterMap(ownVessel.getLatitude(), ownVessel.getLongitude());
            } else {
                Log.w(TAG, "uiDataNotifier не установлен, центрирование карты пропущено");
            }
        }
    }
    
    /**
     * Очищает путь судна
     */
    public void clearVesselPath() {
        if (pathController != null) {
            pathController.clearPath();
            Log.i(TAG, "Путь судна очищен");
        }
    }
    
    /**
     * Очищает все AIS суда
     */
    public void clearAISVessels() {
        Log.i(TAG, "Очищаем AIS суда из координатора");
        
        // Очищаем локальные данные
        synchronized (aisVessels) {
            aisVessels.clear();
        }
        
        // Уведомляем UI Coordinator о необходимости очистки карты
        if (uiDataNotifier != null) {
            Log.d(TAG, "Уведомляем UI Coordinator об очистке AIS судов");
            // TODO: Добавить метод очистки всех AIS судов в UIDataChangeNotifier
            // Пока что очищаем через individual removals
            Log.i(TAG, "Individual AIS removal через uiDataNotifier еще не реализован");
        } else {
            Log.w(TAG, "uiDataNotifier не установлен, очистка AIS судов пропущена");
        }
        
        // Очищаем AIS path controllers
        aisPathControllers.clear();
    }
    
    /**
     * Проверяет, включен ли Android NMEA слушатель
     */
    public boolean isAndroidNMEAEnabled() {
        return nmeaController != null && nmeaController.isAndroidNMEAListenerActive();
    }
    
    /**
     * Проверяет, включен ли UDP слушатель
     */
    public boolean isUDPEnabled() {
        return networkController != null && networkController.isUDPEnabled();
    }
    
    /**
     * Получает текущий UDP порт
     */
    public int getUDPPort() {
        return networkController != null ? networkController.getUDPPort() : 10110;
    }
    
    /**
     * Перезапускает UDP слушатель с новым портом
     */
    public void restartUDPListener() {
        if (networkController != null) {
            networkController.restartUDPListener();
        }
    }
    
    /**
     * Обновляет метки времени последних сообщений
     */
    private void updateLastMessageAgesFromRaw(String raw) {
        if (raw == null) return;
        long now = android.os.SystemClock.elapsedRealtime();
        String[] lines = raw.split("\r?\n");
        for (String line : lines) {
            if (line == null) continue;
            String t = line.trim();
            if (t.isEmpty()) continue;
            char c = t.charAt(0);
            if (c == '$') {
                lastGPSMessageRealtimeMs = now;
                break;
            } else if (c == '!') {
                lastAISMessageRealtimeMs = now;
                break;
            }
        }
    }

    /**
     * Освобождает ресурсы
     */
    public void cleanup() {
        stopAllControllers();
        
        // Очищаем Handler для предотвращения утечек памяти
        if (uiHandler != null) {
            uiHandler.removeCallbacksAndMessages(null);
        }
        
        // Очищаем контроллеры
        nmeaController.cleanup();
        networkController.cleanup();
        dataController.cleanup();
        notificationController.cleanup();
        compassController.cleanup();
        
        // Отписываемся от MapController
        if (mapController != null) {
            mapController.removeMapInterfaceChangeListener(this);
        }
        
        Log.i(TAG, "App Coordinator очищен");
    }
}
