import "dart:async";

import "package:client/data.dart";
import "package:client/services.dart";
import "package:flutter/widgets.dart" hide Path, Route;

import "package:google_maps_flutter/google_maps_flutter.dart";
import "package:shared/graph.dart";

import "home_markers.dart";
import "view_model.dart";

typedef PathStep = (Stop, String);

/// The view model for the home page.
class HomeModel extends ViewModel with HomeMarkers {
  final startLatitudeController = TextEditingController();
  final startLongitudeController = TextEditingController();
  final endLatitudeController = TextEditingController();
  final endLongitudeController = TextEditingController();

  GoogleMapController? mapController;

  double? get startLatitude => double.tryParse(startLatitudeController.text);
  double? get startLongitude => double.tryParse(startLongitudeController.text);
  double? get endLatitude => double.tryParse(endLatitudeController.text);
  double? get endLongitude => double.tryParse(endLongitudeController.text);

  bool isSearching = false;
  bool isGoogleReady = false;
  List<List<LatLng>> paths = [];

  @override
  Future<void> init() async {
    await Future<void>.delayed(const Duration(seconds: 2));
    isGoogleReady = true;
    notifyListeners();
    await updateMarkers();
  }

  void onMapTapped(LatLng coordinates) {
    if (!shouldShowMarkers) return;
    final marker = Marker(
      markerId: markerId ?? const MarkerId("Tapped"),
      position: coordinates,
      icon: markerIcon ?? BitmapDescriptor.defaultMarker,
      infoWindow: InfoWindow(title: "$startOrEnd here"),
    );
    switch (markerState!) {
      case MarkerState.start: updateStart(coordinates, marker);
      case MarkerState.end: updateEnd(coordinates, marker);
      case MarkerState.override: return;
    }
  }

  @override
  void updateStart(LatLng coordinates, Marker marker) {
    startLatitudeController.text = coordinates.latitude.toString();
    startLongitudeController.text = coordinates.longitude.toString();
    super.updateStart(coordinates, marker);
  }

  @override
  void updateEnd(LatLng coordinates, Marker marker) {
    endLatitudeController.text = coordinates.latitude.toString();
    endLongitudeController.text = coordinates.longitude.toString();
    super.updateEnd(coordinates, marker);
  }

  List<String>? pathText;
  List<PathStep> pathWaypoint = [];
  List<List<Coordinates>> pathWalking = [];
  List<List<Coordinates>> pathStops = [];

  void explainPath(Iterable<StopState> path, void Function(String) printer) {
    var index = 0;
    for (final step in path) {
      index++;
      final stop = stops[step.stopID]!;
      if (index == path.length) {
        printer("$index. Get off at ${stop.name}");
      } else {
        printer("$index. ${explainStep(step)}");
      }
    }
  }

  String explainStep(StopState step) {
    final route = routes[step.routeID]!;
    final stop = stops[step.stopID]!;
    if (step.isGoal()) return "Get off at $stop";
    return switch (step.method) {
      SearchMethod.bus => "Ride the bus to $stop",
      SearchMethod.start => "Walk to $stop\n  and board the $route line",
      SearchMethod.transfer => "Transfer at $stop\n  to the $route line",
      SearchMethod.walk => "Walk to $stop\n  and board the $route line"
    };
  }

  Future<void> search() async {
    final start = (lat: startLatitude, long: startLongitude);
    final end = (lat: endLatitude, long: endLongitude);
    pathText = null;
    errorText = null;
    if (start.lat == null || start.long == null) return;
    if (end.lat == null || end.long == null) return;
    isSearching = true;
    isLoading = true;
    final path = await services.api.getPath(start: start as Coordinates, end: end as Coordinates);
    isLoading = false;
    if (path == null) {
      errorText = "An error occcurred";
      return;
    } else if (path.isEmpty) {
      errorText = "No path found";
      return;
    }
    pathWalking.clear();
    pathWaypoint.clear();
    pathStops.clear();
    for (final (index, step) in path.enumerate) {
      final stop = stops[step.stopID]!;
      final position = stop.coordinates;
      switch (step.method) {
        case SearchMethod.start:
          pathWalking.add([start, position]);
          pathWaypoint.add( (stop, explainStep(step)) );
          pathStops.add([position]);
        case SearchMethod.bus:
          pathStops.last.add(position);
        case SearchMethod.transfer:
          pathWaypoint.add( (stop, explainStep(step)) );
          pathStops.last.add(position);
          pathStops.add([position]);
        case SearchMethod.walk:
          final prevStep = path[index - 1];
          final prevStopID = prevStep.stopID;
          final prevStop = stops[prevStopID]!;
          pathWalking.add([prevStop.coordinates, position]);
          pathWaypoint.add( (prevStop, explainStep(prevStep)) );
          pathWaypoint.add( (stop, explainStep(step)) );
          pathStops.add([position]);
      }
      if (index == (path.length - 1)) {
        if (step.method == SearchMethod.bus) {
          pathWaypoint.add( (stop, explainStep(step)) );
        }
        pathWalking.add([stop.coordinates, end]);
      }
    }
    pathText = [];
    explainPath(path, pathText!.add);
    isSearching = false;
    notifyListeners();
  }
}
