// User-facing script
// ignore_for_file: avoid_print

import "package:a_star/a_star.dart";
import "package:shared/generator.dart";
import "package:shared/graph.dart";

import "package:shelf_router/shelf_router.dart";
import "package:shelf/shelf.dart";
import "package:shelf/shelf_io.dart" as io;

// const startLat = 42.0924949645996;
// const startLong = -75.9538421630859;
// const endLat = 42.0869369506836;
// const endLong = -75.965934753418;

const startLat = 42.092083;
const startLong = -75.952271;
const endLat = 42.080822;
const endLong = -75.912529;

const numStops = 5;

void log(String message) {
  // print(message);
}

Iterable<Stop> findStopsNear(Coordinates location) {
  log("Finding stops near $location...");
  final stopDistances = [
    for (final stop in StopState.stops.values)
      (stop, stop.coordinates.distanceTo(location)),
  ];
  stopDistances.sort((a, b) => a.$2.compareTo(b.$2));
  return stopDistances.take(numStops).map((pair) => pair.$1);
}

void printStops(Iterable<Stop> stops) {
  log("Found stops:");
  for (final stop in stops) {
    log("- ${stop.routes}, ${stop.name}");
  }
}

Set<RouteID> findRoutes(Iterable<Stop> stops) => {
  for (final stop in stops)
    ...stop.routeIDs,
};

Stop findStopWithRoute(Iterable<Stop> stops, RouteID route) =>
  stops.firstWhere((stop) => stop.routeIDs.contains(route));

Future<void> init() async {
  print("Initializing...");
  final stops = await Generator.stops.parse();
  final routes = await Generator.routes.parse();
  StopState.init(routes, stops);
}

final headers = {
  "Access-Control-Allow-Origin": "*",
};

Future<Response> handleRequest(Request request) async {
  final startLat = double.tryParse(request.url.queryParameters["start_lat"] ?? "");
  final startLong = double.tryParse(request.url.queryParameters["start_lon"] ?? "");
  final endLat = double.tryParse(request.url.queryParameters["end_lat"] ?? "");
  final endLong = double.tryParse(request.url.queryParameters["end_lon"] ?? "");
  if (startLat == null || startLong == null || endLat == null || endLong == null) {
    return Response.badRequest(body: "Could not parse coordinates", headers: headers);
  }

  final start = (lat: startLat, long: startLong);
  final end = (lat: endLat, long: endLong);

  // 1) Find [numStops] nearest stops by the given locations
  log("Finding nearest stops...");
  final startStops = findStopsNear(start);
  printStops(startStops);
  final endStops = findStopsNear(end);
  printStops(endStops);

  // 2) Find all routes by those stops
  log("Finding intersecting routes");
  final startRoutes = findRoutes(startStops);
  final endRoutes = findRoutes(endStops);
  log("Start routes: $startRoutes");
  log("End routes: $endRoutes");

  // 3) Check for a direct route without transfers
  final routesInCommon = startRoutes.intersection(endRoutes);
  if (routesInCommon.isNotEmpty) {
    final route = routesInCommon.first;
    final startStop = findStopWithRoute(startStops, route);
    final endStop = findStopWithRoute(endStops, route);
    return Response.ok("\nBoard the $route at ${startStop.name} and get off at ${endStop.name}", headers: headers);
  }

  // 4) Use A* to find a route with transfers
  log("Using A*");
  final paths = <Iterable<StopState>>[];
  for (final startStop in startStops) {
    for (final startRoute in startStop.routeIDs) {
      for (final endStop in endStops) {
        final state = StopState(stopID: startStop.id, goalID: endStop.id, routeID: startRoute, depth: 0);
        log("Finding a route using ${state.hash()}");
        final result = aStar(state);
        if (result == null) continue;
        paths.add(result.reconstructPath());
      }
    }
  }

  if (paths.isEmpty) {
    return Response.ok("No routes found", headers: headers);
  }

  final minPath = paths.min((path) => path.length);
  final buffer = StringBuffer();
  buffer.writeln("Found ${paths.length} paths, here's the best one with only ${minPath.length} steps:");
  StopState? prevStep;
  var index = 0;
  for (final step in minPath) {
    index++;
    final stop = StopState.stops[step.stopID]!;
    final route = StopState.routes[step.routeID]!;
    if (prevStep == null) {
      buffer.writeln("$index. Board the ${route.provider.humanName} ${route.fullName} bus\n  at ${stop.name}");
    } else if (index == minPath.length) {
      buffer.writeln("$index. Get off at ${stop.name}");
    } else if (prevStep.routeID != step.routeID) {
      buffer.writeln("$index. Transfer to the ${route.provider.humanName} ${route.fullName}");
    } else {
      buffer.writeln("$index. Go to ${stop.name}");
    }
    prevStep = step;
  }
  return Response.ok(buffer.toString(), headers: headers);
}

void main() async {
  await init();
  final router = Router();
  router.get("/path", handleRequest);
  await io.serve(router.call, "0.0.0.0", 8001);
  print("Listening on 0.0.0.0:8001");
}
