import 'dart:convert';

import 'package:aves_map/aves_map.dart';
import 'package:flutter/material.dart';
import 'package:latlong2/latlong.dart';
import 'package:mapbox_maps_flutter/mapbox_maps_flutter.dart' as mbx;

class MapboxTracksLayer {
  static const sourceId = 'aves-mapbox-tracks-source';
  static const layerId = 'aves-mapbox-tracks-layer';

  static Future<void> remove({
    required mbx.MapboxMap mapboxMap,
    required void Function(String message) log,
    required String reason,
  }) async {
    final style = mapboxMap.style as dynamic;

    log('remove tracks layer reason=$reason');

    try {
      await style.removeStyleLayer(layerId);
    } catch (_) {
      // Layer may not exist.
    }

    try {
      await style.removeStyleSource(sourceId);
    } catch (_) {
      // Source may not exist.
    }
  }

  static Future<void> update({
    required mbx.MapboxMap mapboxMap,
    required Set<List<LatLng>>? tracks,
    required Color color,
    required double width,
    required void Function(String message) log,
    required String reason,
  }) async {
    await remove(
      mapboxMap: mapboxMap,
      log: log,
      reason: reason,
    );

    if (tracks == null || tracks.isEmpty) {
      log('tracks skipped empty reason=$reason');
      return;
    }

    final lineStrings = tracks
        .map(
          (track) => track
              .where(
                (point) =>
                    point.latitude.isFinite && point.longitude.isFinite,
              )
              .map(
                (point) => [
                  point.longitude,
                  point.latitude,
                ],
              )
              .toList(),
        )
        .where((coordinates) => coordinates.length >= 2)
        .toList();

    if (lineStrings.isEmpty) {
      log('tracks skipped no valid line strings reason=$reason');
      return;
    }

    final geoJson = jsonEncode({
      'type': 'FeatureCollection',
      'features': [
        {
          'type': 'Feature',
          'properties': {},
          'geometry': {
            'type': 'MultiLineString',
            'coordinates': lineStrings,
          },
        },
      ],
    });

    log(
      'add tracks layer '
      'reason=$reason '
      'tracks=${tracks.length} '
      'segments=${lineStrings.length} '
      'points=${lineStrings.fold<int>(0, (sum, line) => sum + line.length)}',
    );

    try {
      await mapboxMap.style.addSource(
        mbx.GeoJsonSource(
          id: sourceId,
          data: geoJson,
        ),
      );

      await mapboxMap.style.addLayer(
        mbx.LineLayer(
          id: layerId,
          sourceId: sourceId,
          lineColor: color.value,
          lineWidth: width,
          lineOpacity: 1.0,
        ),
      );
    } catch (e) {
      log('tracks layer add failed reason=$reason error=$e');
    }
  }
}
