import "package:client/data.dart";
import "package:client/services.dart";
import "package:flutter/foundation.dart";
import "package:google_maps_flutter/google_maps_flutter.dart";

enum MarkerState {
  start,
  end,
  override;
}

mixin HomeMarkers on ChangeNotifier {
  MarkerState? markerState;
  bool get shouldShowMarkers => markerState != null;

  // List<Stop>? stops;
  Map<StopID, Stop> stops = {};
  Map<RouteID, Route> routes = {};
  Marker? _startMarker;
  Marker? _endMarker;

  Iterable<Stop> getStopsForRoute(RouteID routeID) => routes[routeID]!.stops
    .map((stopID) => stops[stopID]!);

  Set<Marker> get _filteredMarkers => {
    // for (final stop in stops ?? <Stop>[])
    for (final routeID in routesToShow)
      for (final stop in getStopsForRoute(routeID))
        Marker(
          markerId: MarkerId(stop.name),
          position: stop.coordinates.toLatLng(),
          onTap: () => onMarkerTapped(stop),
          consumeTapEvents: true,
          infoWindow: InfoWindow(
            title: stop.name,
            snippet: stop.summary,
          ),
        ),
  };

  Set<Marker> get markers => shouldShowMarkers ? _filteredMarkers : {
    if (_startMarker != null) _startMarker!,
    if (_endMarker != null) _endMarker!,
  };

  List<Route> bcRouteNames = [];
  List<Route> occtRouteNames = [];
  Set<RouteID> routesToShow = {};
  Iterable<(String, List<Route>)> get providers => [
    ("OCCT", occtRouteNames),
    ("BC Transit", bcRouteNames),
  ];

  int compareBcRoutes(Route a, Route b) =>
    int.parse(a.shortName).compareTo(int.parse(b.shortName));

  int compareOcctRoutes(Route a, Route b) =>
    a.shortName.compareTo(b.shortName);

  MarkerId? get markerId => switch (markerState!) {
    MarkerState.start => const MarkerId("start"),
    MarkerState.end => const MarkerId("end"),
    _ => null,
  };

  BitmapDescriptor? get markerIcon => switch (markerState!) {
    MarkerState.start => BitmapDescriptor.defaultMarkerWithHue(BitmapDescriptor.hueCyan),
    MarkerState.end => BitmapDescriptor.defaultMarkerWithHue(BitmapDescriptor.hueGreen),
    _ => null,
  };

  String? get startOrEnd => switch (markerState!) {
    MarkerState.start => "Start",
    MarkerState.end => "End",
    _ => null,
  };

  Future<void> updateMarkers() async {
    final stopsResponse = await services.api.getStops();
    if (stopsResponse == null) return;
    stops = {
      for (final stop in stopsResponse)
        stop.id: stop,
    };
    final routesResponse = await services.api.getRoutes();
    if (routesResponse == null) return;
    routes = {
      for (final route in routesResponse)
        route.id: route,
    };
    for (final route in routesResponse) {
      final namesList = switch (route.provider) {
        Provider.occt => occtRouteNames,
        Provider.bc => bcRouteNames,
      };
      namesList.add(route);
    }
    occtRouteNames.sort(compareOcctRoutes);
    bcRouteNames.sort(compareBcRoutes);
  }

  void showMarkers(MarkerState newState) {
    markerState = newState;
    notifyListeners();
  }

  void hideMarkers() {
    markerState = null;
    notifyListeners();
  }

  void onMarkerTapped(Stop stop) {
    final coordinates = stop.coordinates.toLatLng();
    final marker = Marker(
      markerId: markerId ?? MarkerId(stop.name),
      position: coordinates,
      infoWindow: InfoWindow(
        title: "$startOrEnd at ${stop.name}",
        snippet: stop.description,
      ),
      icon: markerIcon ?? BitmapDescriptor.defaultMarker,
    );
    switch (markerState!) {
      case MarkerState.start: updateStart(coordinates, marker);
      case MarkerState.end: updateEnd(coordinates, marker);
      case MarkerState.override: return;
    }
  }

  // Flutter widget
  // ignore: avoid_positional_boolean_parameters
  void overrideMarkers(bool value) {
    markerState = value ? MarkerState.override : null;
    notifyListeners();
  }

  @mustCallSuper
  void updateStart(LatLng coordinates, Marker marker) {
    _startMarker = marker;
    markerState = null;
    notifyListeners();
  }

  @mustCallSuper
  void updateEnd(LatLng coordinates, Marker marker) {
    _endMarker = marker;
    markerState = null;
    notifyListeners();
  }

  void showRoute(Route route, {required bool shouldShow}) {
    if (shouldShow) {
      routesToShow.add(route.id);
    } else {
      routesToShow.remove(route.id);
    }
    notifyListeners();
  }
}
