import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:math';
import 'dart:math' as math;

import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:flutter_map_polygon_editor/polygon_editor/polygon_editor_controller.dart';
// import 'package:flutter_map_geojson/flutter_map_geojson.dart';
import 'package:flutter_map_polywidget/flutter_map_polywidget.dart';
import 'package:geolocator/geolocator.dart';
import 'package:get/get.dart';
import 'package:intl/intl.dart';
// import 'package:location/location.dart';
import 'package:latlong2/latlong.dart';
import 'package:path_provider/path_provider.dart';
import 'package:share_plus/share_plus.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
import 'package:terepi_seged/controls/geoid_grid.dart';
import 'package:terepi_seged/controls/wgs84_coordinate_formatter.dart';
import 'package:terepi_seged/enums/map_edit_tool.dart';
import 'package:terepi_seged/enums/map_survey_mode.dart';
import 'package:terepi_seged/enums/note_type.dart';
import 'package:terepi_seged/eov/convert_coordinate.dart';
import 'package:terepi_seged/eov/eov.dart';
import 'package:terepi_seged/models/note_item.dart';
import 'package:terepi_seged/models/point_to_measure.dart';
import 'package:terepi_seged/models/point_with_description_model.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:terepi_seged/pages/map_survey/presentations/views/measured_points_table_dialog.dart';
import 'package:terepi_seged/pages/ntrip_settings/presentation/controllers/ntrip_settings_controller.dart';
import 'package:terepi_seged/pages/ntrip_settings/presentation/views/ntrip_settings_sheet.dart';
import 'package:terepi_seged/pages/tracking/presentation/controllers/tracking_controller.dart';
import 'package:terepi_seged/services/app_database.dart';
import 'package:terepi_seged/services/coord_converter_service.dart';
import 'package:terepi_seged/services/gnss/gnss_connection.dart';
import 'package:terepi_seged/services/gnss/gnss_device_service.dart';
import 'package:terepi_seged/services/gnss/gnss_service.dart';
import 'package:terepi_seged/services/ntrip_service.dart';
import 'package:terepi_seged/services/project_service.dart';
import 'package:terepi_seged/widgets/map_edit_tools/map_feature_save_sheet.dart';

class MapSurveyController extends GetxController {
  static MapSurveyController get to => Get.find();

  // ── Függőségek (service-ek) ───────────────────────────────────────
  GnssService get _gnss => GnssService.to;
  NtripService get _ntrip => NtripService.to;

  final mode = MapSurveyMode.measure.obs;
  final targetName = ''.obs;
  final targetEovX = 0.0.obs;
  final targetEovY = 0.0.obs;

  double get deltaY => eov.value.Y - targetEovY.value;
  double get deltaX => eov.value.X - targetEovX.value;
  double get distanceToTarget => sqrt(deltaX * deltaX + deltaY * deltaY);
  bool get isOnTarget => distanceToTarget < 0.05;

  // ── GPS állapot — rövidítések a GnssService-hez ──────────────────
  // Ezeket a view-ban közvetlenül a service-ből is lehetne olvasni,
  // de a controller-en keresztül is hozzáférhetők a meglévő view kódhoz.
  RxDouble get gpsLatitude => _gnss.latitude;
  RxDouble get gpsLongitude => _gnss.longitude;
  RxDouble get gpsAltitude => _gnss.altitude;
  RxDouble get gpsGeoidSeparation => _gnss.geoidSeparation;
  RxInt get gpsQuality => _gnss.gpsQuality;
  RxDouble get gpsLatitudeError => _gnss.latitudeError;
  RxDouble get gpsLongitudeError => _gnss.longitudeError;
  RxDouble get gpsAltitudeError => _gnss.altitudeError;
  Rx<DateTime> get gpsDateTime => _gnss.gpsDateTime;
  bool get gpsIsConnected =>
      _gnss.connectionState.value == GnssConnectionState.connected;
  double? get horizontalAccuracy => _gnss.horizontalAccuracy;

  RxDouble get pdop => _gnss.pdop;
  RxDouble get hdop => _gnss.hdop;
  RxDouble get vdop => _gnss.vdop;

  final wgs84CoordinateFormat = Wgs84CoordinateFormat.decimalDegrees.obs;
  final showWgs84Card = true.obs;
  final showEovCard = true.obs;
  final showGnssQualityCard = true.obs;

  // NTRIP állapot
  RxBool get ntripIsConnected => _ntrip.isConnected;
  RxInt get ntripDataPacketNumbers => _ntrip.packetCount;
  RxInt get ggaSenDataPacketNumber => _ntrip.ggaSentCount;
  RxString get ggaSendLastTimeStr => _ntrip.ggaLastSentTime;
  RxInt get ntripReceivedData => _ntrip.receivedBytes;

  // ── Számformátumok ────────────────────────────────────────────────
  final formatEov = NumberFormat('##0,000.0', 'hu-HU');
  final formatEovZ = NumberFormat('###0.0', 'hu-HU');
  final formatAltitudeError = NumberFormat('####0.000', 'hu-HU');
  final formatEovForFile = NumberFormat('#####0.0', 'hu-HU');
  final formatWgs84Sec = NumberFormat('00.000', 'hu-HU');

  // ── EOV koordináták (számítottak) ─────────────────────────────────
  Rx<Eov> eov = Eov(0, 0).obs;
  Rx<double?> eovHeight = (0.0 as double?).obs;

  final eovY = 0.0.obs;
  final eovX = 0.0.obs;

  // DMS formátum
  RxInt latDegree = 0.obs;
  RxInt latMin = 0.obs;
  RxDouble latSec = 0.0.obs;
  RxInt longDegree = 0.obs;
  RxInt longMin = 0.obs;
  RxDouble longSec = 0.0.obs;

  // ── Térkép állapot ────────────────────────────────────────────────
  static const double maxZoomValue = 25.0;
  RxDouble currentLongitude = 0.0.obs;
  RxDouble currentLatitude = 0.0.obs;
  RxDouble currentZoom = 12.0.obs;
  RxBool isMapMoveToCenter = true.obs;
  RxBool mapIsInitialized = false.obs;

  final MapController mapController = MapController();

  final polylineHitNotifier = ValueNotifier<LayerHitResult<int>?>(null);
  final polygonHitNotifier = ValueNotifier<LayerHitResult<int>?>(null);

  final currentLocationMarker = <Marker>[].obs;
  final pointNotesMarker = <Marker>[].obs;
  final pointsToMeasureMarker = <Marker>[].obs;
  final pointsToMeasureLabel = <PolyWidget>[].obs;
  final pointsToMeasureDropDownMenuItem = <DropdownMenuItem<int>>[].obs;

  // ── Pont adatok ───────────────────────────────────────────────────
  final RxList<PointToMeasure> pointsToMeasure = <PointToMeasure>[].obs;
  final RxList<PointWithDescription> pointWithDescriptionList =
      <PointWithDescription>[].obs;
  RxInt pointsToMeasureSelectedValue = (-1).obs;
  RxDouble distance = 0.0.obs;

  int pointId = 1;
  String pointIdPrefix = '';
  String pointIdPostfix = '';
  Rx<bool> pointMeasuringDirectionForward = true.obs;

  // ── Geoid grid ────────────────────────────────────────────────────
  late GeoidGrid geoidGrid;

  // ── Fájlok ────────────────────────────────────────────────────────
  late Directory? directory;
  late File dataFile;

  // ── Telefon GPS fallback ──────────────────────────────────────────
  StreamSubscription<Position>? _phoneLocationSub;
  StreamSubscription<void>? _gnssUpdateSub;

  // ── UI controllerek ───────────────────────────────────────────────
  final pointIdController = TextEditingController();
  final pointDescriptionController = TextEditingController();
  final gpsHeightController = TextEditingController();
  final pointPrefixController = TextEditingController();
  final pointPostfixController = TextEditingController();

  late SharedPreferences prefs;
  Rx<bool> isShowPassword = false.obs;

  // ── Supabase ──────────────────────────────────────────────────────
  RealtimeChannel? _supaChannel;

  // -------  Map edit ----------------
  final activeEditTool = MapEditTool.none.obs;
  final editorPointCount = 0.obs;
  final pointNotes = <Marker>[].obs;
  final polylineNotes = <Polyline<int>>[].obs;
  final polygonNotes = <Polygon<int>>[].obs;

  late final PolygonEditorController polygonEditorController;

  final activeEditColor = const Color(0xFF185FA5).obs;
  final activeEditOpacity = 0.5.obs;
  final activeEditStrokeWidth = 3.0.obs;
  final activeEditStrokeColor = const Color(0xFFFFD700).obs;
  final activeEditLabel = ''.obs;

  final PolygonLabelPlacementCalculator _labelPlacementCalculator =
      const PolygonLabelPlacementCalculator.centroid();

  bool get isMapEditing => activeEditTool.value != MapEditTool.none;
  final selectedNoteItemId = Rx<int?>(null);
  final selectedNoteItemType = NoteType.line.obs;
  int? _editingNoteItemId;
  bool get isGeometryEditing => _editingNoteItemId != null;

  // NoteItem? get selectedPoint =>
  //     pointNotes.firstWhereOrNull((n) => n.id == selectedNoteItemId.value);

  // ─────────────────────────────────────────────────────────────────
  // Lifecycle
  // ─────────────────────────────────────────────────────────────────

  @override
  void onInit() {
    super.onInit();
    _initAsync();
  }

  Future<void> _initAsync() async {
    prefs = await SharedPreferences.getInstance();
    geoidGrid = await GeoidGrid.load('assets/Grids/geoid_eht2014.gtx');

    NtripService.to.onRtcmData = (data) => GnssService.to.sendToReceiver(data);
    _gnssUpdateSub = _gnss.onDataUpdated.listen((_) => _onGnssUpdate());

    // ── Supabase realtime ─────────────────────────────────────────
    _supaChannel = Supabase.instance.client
        .channel('public:TerepiSeged_Receiver')
        .onPostgresChanges(
          event: PostgresChangeEvent.update,
          schema: 'public',
          table: 'TerepiSeged_Receiver',
          callback: (payload) {
            final id = payload.newRecord['pointNumber'] as int?;
            if (id != null) updatePointStatus(id);
          },
        )
        .subscribe();

    polygonEditorController =
        PolygonEditorController(mode: PolygonEditorMode.polygon);
    polygonEditorController.addListener(() {
      editorPointCount.value = polygonEditorController.points.length;
    });

    mapIsInitialized.value = true;
  }

  @override
  void onReady() async {
    super.onReady();

    await _initStorage();

    gpsHeightController.text = '1.8';

    ever(ProjectService.to.activeProject, (_) => _loadNoteItems());

    await _loadNoteItems();
  }

  @override
  void onClose() {
    _phoneLocationSub?.cancel();
    _gnssUpdateSub?.cancel();
    final f = _supaChannel?.unsubscribe();
    if (f != null) unawaited(f);

    pointIdController.dispose();
    pointDescriptionController.dispose();
    gpsHeightController.dispose();
    pointPrefixController.dispose();
    pointPostfixController.dispose();
    polygonEditorController.dispose();

    super.onClose();
  }

  // ─────────────────────────────────────────────────────────────────
  // GnssService frissítés kezelése
  // ─────────────────────────────────────────────────────────────────

  void _onGnssUpdate() {
    if (!_gnss.hasValidData) return;

    final lat = _gnss.latitude.value;
    final lon = _gnss.longitude.value;
    final alt = _gnss.altitude.value;
    final sep = _gnss.geoidSeparation.value;

    // EOV konverzió
    final converted = ConvertCoordinate.ConvertWgsToEov(lat, lon);
    eov.value = converted;
    eovY.value = converted.Y.toDouble();
    eovX.value = converted.X.toDouble();

    eovHeight.value = geoidGrid.toEovHeight(lat, lon, alt, sep);

    // DMS
    latDegree.value = ConvertCoordinate.toDegree(lat);
    latMin.value = ConvertCoordinate.toMinute(lat);
    latSec.value = ConvertCoordinate.toSecond(lat);
    longDegree.value = ConvertCoordinate.toDegree(lon);
    longMin.value = ConvertCoordinate.toMinute(lon);
    longSec.value = ConvertCoordinate.toSecond(lon);

    // Távolság a kiválasztott ponthoz
    if (pointsToMeasureSelectedValue.value >= 0 &&
        pointsToMeasureSelectedValue.value < pointsToMeasure.length) {
      final pt = pointsToMeasure[pointsToMeasureSelectedValue.value];
      final wgs = CoordConverterService.to.eovToWgsPoint(pt.coordX, pt.coordY);
      distance.value = calculateDistance(
        LatLng(lat, lon),
        LatLng(wgs.y, wgs.x),
      );
    }

    // Térkép pozíció
    currentLatitude.value = lat;
    currentLongitude.value = lon;
    _updateCurrentLocationMarker();

    // NTRIP GGA küldés
    NtripService.to.onGgaReceived(
      _gnss.lastGgaLine.value,
      _gnss.utcFix.value,
    );
  }

  String _formatMeter(double? value, [int decimalSpace = 3]) {
    if (value == null || value.isNaN || value.isInfinite) {
      return '-';
    }

    return '${value.toStringAsFixed(decimalSpace)} m';
  }

  String get verticalAccuracyText {
    return _formatMeter(gpsAltitudeError.value);
  }

  String get horizontalAccuracyText {
    return _formatMeter(max(gpsLatitudeError.value, gpsLongitudeError.value));
  }

  void setMode(MapSurveyMode newMode) {
    if (mode.value == MapSurveyMode.track &&
        newMode != MapSurveyMode.track &&
        TrackingController.to.isRecording.value) {
      Get.snackbar(
        'Rögzítés folytatódik',
        'A track rögzítés a háttérben aktív marad.',
        icon: const Icon(Icons.fiber_manual_record, color: Colors.red),
        duration: const Duration(seconds: 3),
        snackPosition: SnackPosition.TOP,
      );
    }

    mode.value = newMode;

    // Itt lehet módhoz kötött állapotokat állítani:
    // - alsó panel tartalma
    // - térképi tap viselkedés
    // - aktív kártyák
    // - track indítás/leállítás figyelmeztetés stb.
  }

  String get currentModeLabel => switch (mode.value) {
        MapSurveyMode.browse => 'Térkép',
        MapSurveyMode.measure => 'Bemérés',
        MapSurveyMode.stakeout => 'Kitűzés',
        MapSurveyMode.fieldWalk => 'Bejárás',
        MapSurveyMode.track => 'Útvonal'
      };

  IconData get currentModeIcon => switch (mode.value) {
        MapSurveyMode.browse => Icons.map,
        MapSurveyMode.measure => Icons.add_location_alt,
        MapSurveyMode.stakeout => Icons.gps_fixed,
        MapSurveyMode.fieldWalk => Icons.hiking,
        MapSurveyMode.track => Icons.route
      };

  void openNtripsettings() {
    if (!Get.isRegistered<NtripSettingsController>()) {
      Get.put(NtripSettingsController());
    }

    Get.bottomSheet(const NtripSettingsSheet(),
        isScrollControlled: true,
        backgroundColor: Colors.transparent,
        ignoreSafeArea: false);
  }

  void showNtripStatus() {
    // Get.bottomSheet(
    //   const NtripStatusSheet(),
    //   isScrollControlled: true,
    //   backgroundColor: Colors.transparent,
    // );
  }

  // ─────────────────────────────────────────────────────────────────
  // Térkép vezérlők
  // ─────────────────────────────────────────────────────────────────

  void mapZoomIn() {
    if (currentZoom.value >= maxZoomValue) return;
    currentZoom.value++;
    _moveMap();
  }

  void mapZoomOut() {
    if (currentZoom.value <= 0) return;
    currentZoom.value--;
    _moveMap();
  }

  void setIsMapMoveToCenter() =>
      isMapMoveToCenter.value = !isMapMoveToCenter.value;

  void _moveMap() {
    final lat = isMapMoveToCenter.value
        ? currentLatitude.value
        : mapController.camera.center.latitude;
    final lon = isMapMoveToCenter.value
        ? currentLongitude.value
        : mapController.camera.center.longitude;
    mapController.move(LatLng(lat, lon), currentZoom.value);
  }

  void _updateCurrentLocationMarker() {
    currentLocationMarker.assignAll([
      Marker(
        point: LatLng(currentLatitude.value, currentLongitude.value),
        width: 15.0,
        height: 15.0,
        child: Container(
          width: 15.0,
          height: 15.0,
          decoration: BoxDecoration(
            color: getCurrentLocationMarkerColor(_gnss.gpsQuality.value),
            shape: BoxShape.circle,
            border: Border.all(width: 1.5, color: Colors.white),
          ),
        ),
      )
    ]);

    if (isMapMoveToCenter.value) {
      mapController.move(
        LatLng(currentLatitude.value, currentLongitude.value),
        currentZoom.value,
      );
    }
  }

  Color getCurrentLocationMarkerColor(int quality) => switch (quality) {
        0 => Colors.black,
        1 => Colors.red,
        2 => Colors.blue,
        4 => Colors.green,
        5 => Colors.orange,
        6 => Colors.yellow,
        _ => Colors.white,
      };

  String getGpsQualityIndicator({required int quality}) => switch (quality) {
        0 => 'Invalid',
        1 => 'Standard GPS',
        2 => 'Differential GPS',
        4 => 'RTK Fix',
        5 => 'RTK Float',
        6 => 'Estimated (DR)',
        _ => 'Invalid',
      };

  // ─────────────────────────────────────────────────────────────────
  // Pont mentés dialóg
  // ─────────────────────────────────────────────────────────────────

  void showAddPointDialog() => onBottomNavigationBarTap(0);

  void onBottomNavigationBarTap(int index) async {
    if (index != 0) return;

    if (pointsToMeasureSelectedValue.value >= 0 &&
        pointsToMeasureSelectedValue.value < pointsToMeasure.length) {
      pointIdController.text =
          '${pointsToMeasure[pointsToMeasureSelectedValue.value].id}';
    } else {
      pointIdController.text = pointId.toString();
    }
    pointDescriptionController.text = '';

    Get.dialog(
      AlertDialog(
        title: const Text('Pont rögzítése'),
        content: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            TextField(
              controller: pointIdController,
              autofocus: true,
              keyboardType: TextInputType.number,
              decoration: const InputDecoration(
                  border: OutlineInputBorder(), labelText: 'Azonosító'),
            ),
            const SizedBox(height: 20),
            TextField(
              controller: pointDescriptionController,
              decoration: const InputDecoration(
                  border: OutlineInputBorder(), labelText: 'Leírás'),
            ),
            const SizedBox(height: 20),
            TextField(
              controller: gpsHeightController,
              keyboardType: TextInputType.number,
              decoration: const InputDecoration(
                  border: OutlineInputBorder(), labelText: 'GPS magasság'),
            ),
          ],
        ),
        actions: [
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceEvenly,
            children: [
              OutlinedButton(
                style:
                    OutlinedButton.styleFrom(minimumSize: const Size(120, 40)),
                onPressed: () => Get.back(),
                child:
                    const Text('Mégsem', style: TextStyle(color: Colors.red)),
              ),
              OutlinedButton(
                style:
                    OutlinedButton.styleFrom(minimumSize: const Size(120, 40)),
                onPressed: _saveCurrentPoint,
                child: const Text('Ment',
                    style: TextStyle(
                        color: Colors.green, fontWeight: FontWeight.bold)),
              ),
            ],
          ),
        ],
      ),
      barrierDismissible: false,
    );
  }

  Future<void> _saveCurrentPoint() async {
    pointId = int.tryParse(pointIdController.text) ?? pointId;

    // Helyi lista
    pointWithDescriptionList.add(PointWithDescription(
      pointId,
      _gnss.gpsDateTime.value,
      pointDescriptionController.text,
      eov.value.Y,
      eov.value.X,
      _gnss.latitude.value,
      _gnss.longitude.value,
      max(_gnss.latitudeError.value, _gnss.longitudeError.value),
      _gnss.altitudeError.value,
    ));

    // Marker hozzáadása
    pointNotesMarker.add(Marker(
      point: LatLng(_gnss.latitude.value, _gnss.longitude.value),
      width: 15.0,
      height: 15.0,
      child: Container(
        width: 15.0,
        height: 15.0,
        decoration: BoxDecoration(
          color: Colors.amber[700],
          shape: BoxShape.circle,
          border: Border.all(width: 1.0, color: Colors.black),
        ),
      ),
    ));

    // Fájl mentés
    await dataFile.writeAsString(
      '$pointId;${_gnss.gpsDateTime.value};'
      '${pointDescriptionController.text};'
      '${formatEovForFile.format(eov.value.Y)};'
      '${formatEovForFile.format(eov.value.X)};'
      '${_gnss.latitude.value};${_gnss.longitude.value};'
      '${_gnss.altitude.value};'
      '${max(_gnss.latitudeError.value, _gnss.longitudeError.value)};'
      '${_gnss.altitudeError.value};'
      '${gpsHeightController.text}\r\n',
      mode: FileMode.append,
    );

    // Supabase mentés
    await Supabase.instance.client.from('TerepiSeged_MeasuredPoints').insert({
      'pointNumber': pointId,
      'gnssNumber': GnssDeviceService.to.selectedDevice.value?.name ?? '',
      'latitude': _gnss.latitude.value,
      'longitude': _gnss.longitude.value,
      'altitude': _gnss.altitude.value,
      'heightOfGeoid': _gnss.geoidSeparation.value,
      'eovX': eov.value.X,
      'eovY': eov.value.Y,
      'poleHeight': double.tryParse(gpsHeightController.text),
      'horizontalError':
          max(_gnss.latitudeError.value, _gnss.longitudeError.value),
      'verticalError': _gnss.altitudeError.value,
      'description': pointDescriptionController.text,
      'isDeleted': false,
      'projectId': 2,
    });

    await Supabase.instance.client
        .from('TerepiSeged_Receiver')
        .update({'isMeasured': true}).eq('pointNumber', pointId);

    // Következő pont léptetése
    _advancePointSelection();

    Get.back();
  }

  void _advancePointSelection() {
    final len = pointsToMeasure.length;
    if (len == 0) return;

    if (pointMeasuringDirectionForward.isTrue) {
      if (pointsToMeasureSelectedValue.value < len - 1) {
        pointId++;
        pointsToMeasureSelectedValue.value++;
      } else {
        _showNoMorePoints();
      }
    } else {
      if (pointsToMeasureSelectedValue.value > 0) {
        pointId--;
        pointsToMeasureSelectedValue.value--;
      } else {
        _showNoMorePoints();
      }
    }
  }

  void _showNoMorePoints() {
    ScaffoldMessenger.of(Get.context!).showSnackBar(
      const SnackBar(content: Text('Nincs több bemérendő pont.')),
    );
  }

  // ─────────────────────────────────────────────────────────────────
  // Pont betöltés
  // ─────────────────────────────────────────────────────────────────

  void ReadPointsFromFile() async {
    final result = await FilePicker.platform.pickFiles();
    if (result == null) return;

    final file = File(result.files.single.path!);
    if (!await file.exists()) return;

    _clearPoints();
    final content = await file.readAsLines();

    for (final (index, item) in content.indexed) {
      if (index == 0) continue;
      _addPointFromCsv(item, index - 1);
    }
    if (pointsToMeasure.isNotEmpty) {
      pointsToMeasureSelectedValue.value = 0;
    }
  }

  void readPointsFromSupa() async {
    final data =
        await Supabase.instance.client.from('TerepiSeged_Receiver').select();

    _clearPoints();
    for (int i = 0; i < data.length; i++) {
      final item = data[i];
      final id = item['pointNumber'];
      final coordX = (item['eovX'] as num?)?.toDouble();
      final coordY = (item['eovY'] as num?)?.toDouble();
      if (id == null || coordX == null || coordY == null) continue;
      _addPoint(id: id, coordX: coordX, coordY: coordY, listIndex: i);
    }
    if (pointsToMeasure.isNotEmpty) {
      pointsToMeasureSelectedValue.value = 0;
    }
  }

  void _clearPoints() {
    pointsToMeasure.clear();
    pointsToMeasureMarker.clear();
    pointsToMeasureLabel.clear();
    pointsToMeasureDropDownMenuItem.clear();
  }

  void _addPointFromCsv(String line, int listIndex) {
    final parts = line.split(';');
    if (parts.length < 3) return;
    final id = int.tryParse(parts[0]);
    final coordX = double.tryParse(parts[1].replaceAll(',', '.'));
    final coordY = double.tryParse(parts[2].replaceAll(',', '.'));
    if (id == null || coordX == null || coordY == null) return;
    _addPoint(id: id, coordX: coordX, coordY: coordY, listIndex: listIndex);
  }

  void _addPoint({
    required int id,
    required double coordX,
    required double coordY,
    required int listIndex,
  }) {
    pointsToMeasure.add(PointToMeasure(id: id, coordX: coordX, coordY: coordY));

    final wgs = CoordConverterService.to.eovToWgsPoint(coordX, coordY);

    pointsToMeasureMarker.add(Marker(
      point: LatLng(wgs.y, wgs.x),
      width: 15.0,
      height: 15.0,
      child: Container(
        width: 15.0,
        height: 15.0,
        decoration: BoxDecoration(
          color: Colors.purple,
          shape: BoxShape.circle,
          border: Border.all(width: 1.0, color: Colors.black),
        ),
      ),
    ));

    pointsToMeasureLabel.add(PolyWidget(
      center: LatLng(wgs.y + 0.0000075, wgs.x + 0.0000075),
      widthInMeters: 3,
      heightInMeters: 3,
      child: FittedBox(
        child: Padding(
          padding: const EdgeInsets.all(4),
          child: Text(
            ' $id ',
            style: const TextStyle(
              fontWeight: FontWeight.bold,
              color: Colors.yellow,
              fontSize: 12,
            ),
          ),
        ),
      ),
    ));

    pointsToMeasureDropDownMenuItem.add(DropdownMenuItem<int>(
      value: listIndex,
      child: Text('$id'),
    ));
  }

  void pointsToMeasureSelectedValueChanged(int value) =>
      pointsToMeasureSelectedValue.value = value;

  // ─────────────────────────────────────────────────────────────────
  // Segédmetódusok
  // ─────────────────────────────────────────────────────────────────

  double calculateDistance(LatLng start, LatLng end) {
    const r = 6371000.0;
    final lat1 = start.latitude * (pi / 180);
    final lon1 = start.longitude * (pi / 180);
    final lat2 = end.latitude * (pi / 180);
    final lon2 = end.longitude * (pi / 180);
    final dLat = lat2 - lat1;
    final dLon = lon2 - lon1;
    final a = sin(dLat / 2) * sin(dLat / 2) +
        cos(lat1) * cos(lat2) * sin(dLon / 2) * sin(dLon / 2);
    return r * 2 * atan2(sqrt(a), sqrt(1 - a));
  }

  void updatePointStatus(int pointId) {}

  void showMeasuredPointsTableDialog() =>
      Get.to(() => MeasuredPointsTableDialog(), transition: Transition.fadeIn);

  // ─────────────────────────────────────────────────────────────────
  // Tárhely inicializálás
  // ─────────────────────────────────────────────────────────────────

  Future<void> _initStorage() async {
    directory = await getExternalStorageDirectory();
    if (directory != null && !await directory!.exists()) {
      await directory!.create(recursive: true);
    }
    dataFile = File('${directory!.path}/data.txt');
    if (!await dataFile.exists()) {
      await dataFile.writeAsString(
        'Id;DateTime;Description;EovX;EovY;Latitude;Longitude;Altitude;Hor.Err;Vert.Err\r\n',
      );
    }
  }

  // ─────────────────────────────────────────────────────────────────
  // Export
  // ─────────────────────────────────────────────────────────────────

  Future<List> readMeasuredPoints() async => Supabase.instance.client
      .from('TerepiSeged_MeasuredPoints')
      .select()
      .eq('projectId', 2)
      .order('created_at');

  void SaveMeasuredPointsToFile() async {
    final dir = await getApplicationDocumentsDirectory();
    final file = File('${dir.path}/measuredsPoints.csv');

    if (await file.exists()) await file.delete();
    await file.create();
    await file.writeAsString(
        'Id;DateTime;Description;EovX;EovY;Altitude;Hor.Err;Vert.Err\r\n');

    final data = await readMeasuredPoints();
    for (final d in data) {
      file.writeAsStringSync(
        '${d['id']};${d['created_at']};${d['description']};'
        '${formatEov.format(d['eovY'])};${formatEov.format(d['eovX'])};'
        '${formatEovZ.format((d['altitude'] as num) - (d['poleHeight'] as num))};'
        '${formatAltitudeError.format(d['horizontalError'])};'
        '${formatAltitudeError.format(d['verticalError'])}\r\n',
        mode: FileMode.append,
        encoding: utf8,
      );
    }

    await SharePlus.instance.share(ShareParams(
      text: 'Mérési eredmények',
      files: [XFile('${dir.path}/measuredsPoints.csv')],
      subject: 'Mérési eredmények',
    ));
  }

  void switchMode(MapSurveyMode newMode) {
    mode.value = newMode;
    if (newMode == MapSurveyMode.measure) {
      targetName.value = '';
      targetEovY.value = 0;
      targetEovX.value = 0;
    }
  }

  void setTarget(String name, double y, double x) {
    targetName.value = name;
    targetEovY.value = y;
    targetEovX.value = x;
    mode.value = MapSurveyMode.stakeout;
  }

  void setWgs84CoordinateFormat(Wgs84CoordinateFormat format) {
    wgs84CoordinateFormat.value = format;
  }

  void toggleShowWgs84Card() {
    showWgs84Card.value = !showWgs84Card.value;
  }

  void toggleShowEovCard() {
    showEovCard.value = !showEovCard.value;
  }

  void toggleShowGnssQualityCard() {
    showGnssQualityCard.value = !showGnssQualityCard.value;
  }

  String get latitudeText {
    return Wgs84CoordinateFormatter.formatLatitude(
        gpsLatitude.value, wgs84CoordinateFormat.value);
  }

  String get longitudeText {
    return Wgs84CoordinateFormatter.formatLongitude(
        gpsLongitude.value, wgs84CoordinateFormat.value);
  }

  String formatDop(double? value) {
    if (value == null || value.isNaN || value.isInfinite) {
      return '-';
    }

    return value.toStringAsFixed(2);
  }

  String formatMeterCompact(double? value) {
    if (value == null || value.isNaN || value.isInfinite) {
      return '-';
    }

    if (value < 1.0) {
      return '${value.toStringAsFixed(3)}m';
    }

    if (value < 10.0) {
      return '${value.toStringAsFixed(2)}m';
    }

    return '${value.toStringAsFixed(1)}m';
  }

  // ---------  Térkép szerkesztési műveletek
  IconData get activeEditToolIcon {
    switch (activeEditTool.value) {
      case MapEditTool.point:
        return Icons.add_location_alt_outlined;
      case MapEditTool.line:
        return Icons.polyline_outlined;
      case MapEditTool.polygon:
        return Icons.border_outer_outlined;
      case MapEditTool.none:
        return Icons.edit_location_alt_outlined;
    }
  }

  String get activeEditToolTitle {
    switch (activeEditTool.value) {
      case MapEditTool.point:
        return 'Pont hozzáadása';
      case MapEditTool.line:
        return 'Vonal rögzítése';
      case MapEditTool.polygon:
        return 'Terület rögzítése';
      case MapEditTool.none:
        return '';
    }
  }

  String get activeEditToolHint {
    switch (activeEditTool.value) {
      case MapEditTool.point:
        return 'Koppints a térképre a pont helyéhez.';
      case MapEditTool.line:
        return 'Hosszan nyomj a térképre a töréspontokhoz';
      case MapEditTool.polygon:
        return 'Hosszan nyomj a térképre a sarokpontokhoz.';
      case MapEditTool.none:
        return '';
    }
  }

  bool get canFinishGeometry {
    switch (activeEditTool.value) {
      case MapEditTool.point:
        return editorPointCount == 1;
      case MapEditTool.line:
        return editorPointCount >= 2;
      case MapEditTool.polygon:
        return editorPointCount >= 3;
      case MapEditTool.none:
        return false;
    }
  }

  String get finishButtonText {
    switch (activeEditTool.value) {
      case MapEditTool.point:
        return 'Kész';
      case MapEditTool.line:
        return 'Kész';
      case MapEditTool.polygon:
        return 'Lezárás';
      case MapEditTool.none:
        return 'Kész';
    }
  }

  void startPointTool() {
    activeEditTool.value = MapEditTool.point;
  }

  void startLineTool() {
    polygonEditorController.clear();
    polygonEditorController.setMode(PolygonEditorMode.line);
    activeEditTool.value = MapEditTool.line;
  }

  void startPolygonTool() {
    polygonEditorController.clear();
    polygonEditorController.setMode(PolygonEditorMode.polygon);
    activeEditTool.value = MapEditTool.polygon;
  }

  void cancelEditing() {
    polygonEditorController.clear();
    activeEditTool.value = MapEditTool.none;
    _editingNoteItemId = null;
    selectedNoteItemId.value = null;
    editorPointCount.value = 0;
  }

  void openFeatureList() {
    // TODO: DraggableScrollableSheet / bottom sheet lista
  }

  void openLayerPanel() {
    // TODO: rétegek sheet
  }

  void finishGeometry() {
    if (!canFinishGeometry) return;

    // Itt nyisd majd meg a szerkesztő sheetet:
    // openFeatureEditorSheet();

    // Példa:
    // Get.bottomSheet(
    //   FeatureEditorSheet(
    //     tool: activeTool.value,
    //     points: List<LatLng>.from(draftPoints),
    //   ),
    //   isScrollControlled: true,
    // );

    Get.bottomSheet(
        DraggableScrollableSheet(
            initialChildSize: 0.52,
            minChildSize: 0.35,
            maxChildSize: 0.85,
            snap: true,
            snapSizes: const [0.35, 0.52, 0.85],
            expand: false,
            builder: (_, scrollCtrl) =>
                MapFeatureSaveSheet(ctrl: this, scrollCtrl: scrollCtrl)),
        isScrollControlled: true,
        backgroundColor: Colors.transparent,
        ignoreSafeArea: false);

    //activeEditTool.value = MapEditTool.none;
    //draftPoints.clear();
  }

  Marker _markerFromNoteItem(NoteItem item) {
    return Marker(
      key: ValueKey('note_point_${item.id}'),
      point: item.points.first,
      width: 32.0,
      height: 32.0,
      child: GestureDetector(
        onTap: () => selectedNoteItem(item.id!),
        child: Obx(() {
          final isSelected = selectedNoteItemId.value == item.id;
          return Center(
              child: AnimatedContainer(
            duration: const Duration(milliseconds: 200),
            width: isSelected ? 26.0 : 20.0,
            height: isSelected ? 26.0 : 20.0,
            decoration: BoxDecoration(
                color: item.color,
                shape: BoxShape.circle,
                border: Border.all(
                    width: isSelected ? 3.0 : 1.5,
                    color: isSelected ? Colors.white : item.strokeColor),
                boxShadow: [
                  BoxShadow(
                      color: Colors.black.withValues(alpha: 0.3),
                      blurRadius: isSelected ? 8 : 3)
                ]),
          ));
        }),
      ),
    );
  }
  // ── SQLite mentés ─────────────────────────────────────────────────

  Future<NoteItem?> _saveItem({
    required NoteType type,
    required List<LatLng> points,
  }) async {
    final projectId = ProjectService.to.activeProject.value?.id;

    final item = NoteItem(
      projectId: projectId, // projekt nélkül is mentődik
      type: type,
      points: points,
      color: activeEditColor.value,
      opacity: activeEditOpacity.value,
      strokeWidth: activeEditStrokeWidth.value,
      strokeColor: activeEditStrokeColor.value,
      label: activeEditLabel.value,
      createdAt: DateTime.now(),
    );

    try {
      final id = await AppDatabase.instance.insertNoteItem(item);
      // id-vel visszaadott NoteItem — a hitValue ehhez az id-hez kötődik
      return NoteItem(
        id: id,
        projectId: item.projectId,
        type: item.type,
        points: item.points,
        color: item.color,
        opacity: item.opacity,
        strokeWidth: item.strokeWidth,
        strokeColor: item.strokeColor,
        label: item.label,
        createdAt: item.createdAt,
      );
    } catch (e) {
      print('_saveItem hiba: $e');
      return null;
    }
  }

// ── SQLite betöltés (onReady-ben hívandó) ─────────────────────────

  Future<void> _loadNoteItems() async {
    final projectId = ProjectService.to.activeProject.value?.id;
    final items = await AppDatabase.instance.listNoteItems(projectId);

    // Listák resetelése
    pointNotes.clear();
    polylineNotes.clear();
    polygonNotes.clear();

    for (final item in items) {
      switch (item.type) {
        case NoteType.point:
          pointNotes.add(_markerFromNoteItem(item));
        case NoteType.line:
          polylineNotes.add(item.toPolyline());
        case NoteType.polygon:
          polygonNotes.add(item.toPolygon());
      }
    }
  }

  Future<void> finishDraft() async {
    if (_editingNoteItemId != null) {
      if (polygonEditorController.points.isEmpty) {
        await _finishStyleUpdate();
      } else {
        // ── FRISSÍTÉSI MÓD ──────────────────────────────────────────
        await _finishGeometryUpdate();
      }
    } else {
      // ── LÉTREHOZÁSI MÓD (eredeti logika) ────────────────────────
      await _finishCreate();
    }
  }

  Future<void> _finishStyleUpdate() async {
    final id = _editingNoteItemId!;
    final existing = await AppDatabase.instance.getNoteItem(id);
    if (existing == null) return;

    final updated = existing.copyWith(
      color: activeEditColor.value,
      opacity: activeEditOpacity.value,
      strokeWidth: activeEditStrokeWidth.value,
      strokeColor: activeEditStrokeColor.value,
      label: activeEditLabel.value,
    );

    await updateNoteItem(updated);
    _editingNoteItemId = null;
  }

  Future<void> deleteEditingItem() async {
    final id = _editingNoteItemId;
    if (id == null) return;
    final item = await AppDatabase.instance.getNoteItem(id);
    if (item != null) await deleteNoteItem(item);
    _editingNoteItemId = null;
    selectedNoteItemId.value = null;
    activeEditTool.value = MapEditTool.none;
  }

  // Future<void> finishDraft() async {
  //   if (polygonEditorController.mode == PolygonEditorMode.line) {
  //     print("Points number in line: ${polygonEditorController.points.length}");
  //     print(
  //         "1. point coords: ${polygonEditorController.points[0].latitude} - ${polygonEditorController.points[0].longitude}");
  //     if (polygonEditorController.points.length < 2) return;

  //     final saved = await _saveItem(
  //         type: NoteType.line,
  //         points: List.from(polygonEditorController.points));

  //     if (saved != null) {
  //       polylineNotes.add(saved.toPolyline());
  //     }

  //     polygonEditorController.clear();
  //     activeEditTool.value = MapEditTool.none;
  //   }
  //   if (polygonEditorController.mode == PolygonEditorMode.polygon) {
  //     if (polygonEditorController.points.length < 3) return;

  //     final saved = await _saveItem(
  //         type: NoteType.polygon,
  //         points: List.from(polygonEditorController.points));

  //     if (saved != null) {
  //       polygonNotes.add(saved.toPolygon());
  //     }

  //     polygonEditorController.clear();
  //     activeEditTool.value = MapEditTool.none;
  //   }
  // }

  void saveEditedPoint({required LatLng point}) {
    if (_editingNoteItemId != null) {
      // ── PONT FRISSÍTÉSI MÓD ──────────────────────────────────────
      final id = _editingNoteItemId!;
      _editingNoteItemId = null;
      activeEditTool.value = MapEditTool.none;

      AppDatabase.instance.getNoteItem(id).then((existing) async {
        if (existing == null) return;
        final updated = existing.copyWith(points: [point]);
        await updateNoteItem(updated);
      });
    } else {
      // ── ÚJ PONT LÉTREHOZÁS (eredeti logika) ─────────────────────
      _saveItem(
        type: NoteType.point,
        points: [point],
      ).then((saved) {
        if (saved == null) return;
        pointNotes.add(_markerFromNoteItem(saved));
      });
      activeEditTool.value = MapEditTool.none;
    }
  }

  // void saveEditedPoint({required LatLng point}) {
  //   _saveItem(type: NoteType.point, points: [point]).then((saved) {
  //     if (saved == null) return;
  //     pointNotes.add(_markerFromNoteItem(saved));
  //   });

  //   // Marker marker = Marker(
  //   //     point: point,
  //   //     width: 15.0,
  //   //     height: 15.0,
  //   //     child: Container(
  //   //       width: 15.0,
  //   //       height: 15.0,
  //   //       decoration: BoxDecoration(
  //   //           color: Colors.amber[700],
  //   //           shape: BoxShape.circle,
  //   //           border: Border.all(width: 1.0, color: Colors.black)),
  //   //     ));
  //   //pointNotes.add(marker);
  //   activeEditTool.value = MapEditTool.none;
  // }

  Future<void> selectedNoteItem(int id) async {
    selectedNoteItemId.value = id;

    final item = await AppDatabase.instance.getNoteItem(id);
    if (item == null) return;

    _editingNoteItemId = item.id;
    activeEditColor.value = item.color;
    activeEditOpacity.value = item.opacity;
    activeEditStrokeWidth.value = item.strokeWidth;
    activeEditStrokeColor.value = item.strokeColor;
    activeEditLabel.value = item.label;
    activeEditTool.value = switch (item.type) {
      NoteType.point => MapEditTool.point,
      NoteType.line => MapEditTool.line,
      NoteType.polygon => MapEditTool.polygon
    };

    Get.bottomSheet(
            DraggableScrollableSheet(
                initialChildSize: 0.52,
                minChildSize: 0.35,
                maxChildSize: 0.85,
                snap: true,
                snapSizes: const [0.35, 0.52, 0.85],
                expand: false,
                builder: (_, scrollCtrl) =>
                    MapFeatureSaveSheet(ctrl: this, scrollCtrl: scrollCtrl)),
            isScrollControlled: true,
            backgroundColor: Colors.transparent,
            ignoreSafeArea: false)
        .whenComplete(() {
      selectedNoteItemId.value = null;
      //_editingNoteItemId = null;
      if (!isGeometryEditing) {
        activeEditTool.value = MapEditTool.none;
      }
    });
  }

  void clearNoteItemSelection() {
    selectedNoteItemId.value = null;
  }

  Future<void> updateNoteItem(NoteItem updated) async {
    await AppDatabase.instance.updateNoteItem(updated);

    switch (updated.type) {
      case NoteType.line:
        final idx = polylineNotes.indexWhere((p) => p.hitValue == updated.id);
        if (idx >= 0) {
          polylineNotes[idx] = updated.toPolyline();
          polylineNotes.refresh();
        }
      case NoteType.polygon:
        final idx = polygonNotes.indexWhere((p) => p.hitValue == updated.id);
        if (idx >= 0) {
          polygonNotes[idx] = updated.toPolygon();
          polygonNotes.refresh();
        }
      case NoteType.point:
        final idx = _findPointMarkerIndex(updated.id!);
        if (idx >= 0) {
          pointNotes[idx] = _markerFromNoteItem(updated);
          pointNotes.refresh();
        }
    }
  }

  Future<void> deleteNoteItem(NoteItem item) async {
    await AppDatabase.instance.deleteNoteItem(item.id!);

    switch (item.type) {
      case NoteType.line:
        polylineNotes.removeWhere((p) => p.hitValue == item.id);
      case NoteType.polygon:
        polygonNotes.removeWhere((p) => p.hitValue == item.id);
      case NoteType.point:
        // Pontot tag-elt Key alapján keresünk
        final idx = _findPointMarkerIndex(item.id!);
        if (idx >= 0) pointNotes.removeAt(idx);
    }
    selectedNoteItemId.value = null;
  }

  /// Pont marker indexének megkeresése.
  /// A marker Key-je hordozza a NoteItem id-t.
  int _findPointMarkerIndex(int noteId) {
    return pointNotes.indexWhere(
      (m) => m.key == ValueKey('note_point_$noteId'),
    );
  }
  // ── Geometria szerkesztés indítása ────────────────────────────────────

  Future<void> startGeometryEdit() async {
    final id = _editingNoteItemId;
    if (id == null) return;

    final item = await AppDatabase.instance.getNoteItem(id);
    if (item == null) return;

    if (item.type == NoteType.point) {
      // Pontnál: régi törlése, új elhelyezés vár
      activeEditTool.value = MapEditTool.point;
      return;
    }

    // Editor mód beállítása a típus szerint
    final editorMode = item.type == NoteType.line
        ? PolygonEditorMode.line
        : PolygonEditorMode.polygon;

    // PolygonEditorController újraindítása a meglévő pontokkal
    // Dispose → újra létrehozás szükséges ha a controller már él
    polygonEditorController.clear();
    polygonEditorController.setMode(editorMode);

    // Meglévő pontok betöltése az editorba
    for (final point in item.points) {
      polygonEditorController.addPoint(point);
    }

    editorPointCount.value = item.points.length;

    // Szerkesztési mód aktiválása
    activeEditTool.value =
        item.type == NoteType.line ? MapEditTool.line : MapEditTool.polygon;
  }

// ── Geometria szerkesztés megszakítása ────────────────────────────────

  void cancelGeometryEdit() {
    _editingNoteItemId = null;
    polygonEditorController.clear();
    activeEditTool.value = MapEditTool.none;
    editorPointCount.value = 0;
  }

  Future<void> _finishGeometryUpdate() async {
    final id = _editingNoteItemId!;

    if (polygonEditorController.points.length < _minPoints) {
      Get.snackbar(
        'Figyelem',
        'Nincs elég pont a geometriához.',
        snackPosition: SnackPosition.BOTTOM,
      );
      return;
    }

    // Meglévő elem lekérése az adatbázisból
    final existing = await AppDatabase.instance.getNoteItem(id);
    if (existing == null) return;

    // Frissítés: csak a pontok változnak, a stílus marad
    final updated = existing.copyWith(
      points: List<LatLng>.from(polygonEditorController.points),
      // Ha a stílus is változott a szerkesztés közben:
      color: activeEditColor.value,
      opacity: activeEditOpacity.value,
      strokeWidth: activeEditStrokeWidth.value,
      strokeColor: activeEditStrokeColor.value,
      label: activeEditLabel.value,
    );

    // SQLite + display lista frissítése
    await updateNoteItem(updated);

    // Reset
    _editingNoteItemId = null;
    polygonEditorController.clear();
    activeEditTool.value = MapEditTool.none;
    editorPointCount.value = 0;

    Get.snackbar(
      'Geometria frissítve',
      updated.label.isNotEmpty ? updated.label : '',
      snackPosition: SnackPosition.BOTTOM,
      duration: const Duration(seconds: 2),
    );
  }

  Future<void> _finishCreate() async {
    if (polygonEditorController.mode == PolygonEditorMode.line) {
      if (polygonEditorController.points.length < 2) return;

      final saved = await _saveItem(
        type: NoteType.line,
        points: List.from(polygonEditorController.points),
      );
      if (saved != null) {
        polylineNotes.add(saved.toPolyline());
      }
      polygonEditorController.clear();
      activeEditTool.value = MapEditTool.none;
    }

    if (polygonEditorController.mode == PolygonEditorMode.polygon) {
      if (polygonEditorController.points.length < 3) return;

      final saved = await _saveItem(
        type: NoteType.polygon,
        points: List.from(polygonEditorController.points),
      );
      if (saved != null) {
        polygonNotes.add(saved.toPolygon());
      }
      polygonEditorController.clear();
      activeEditTool.value = MapEditTool.none;
    }
  }

  int get _minPoints {
    if (polygonEditorController.mode == PolygonEditorMode.line) return 2;
    return 3; // polygon
  }
}
