import repl from 'node:repl';

import fs from 'fs';

import { Query, Router, StopsIndex, Time, Timetable } from '../router.js';
import { plotGraphToDotFile, prettyPrintRoute } from './utils.js';

export const startRepl = (stopsPath: string, timetablePath: string) => {
  const stopsIndex = StopsIndex.fromData(fs.readFileSync(stopsPath));
  const timetable = Timetable.fromData(fs.readFileSync(timetablePath));
  console.log(`Minotor Transit Router CLI`);
  console.log(
    'Enter your stop (.find) or routing (.route) queries. Type ".exit" to quit.',
  );
  const replServer = repl.start({
    prompt: 'minotor> ',
    ignoreUndefined: true,
  });
  replServer.context.stopFinder = stopsIndex;
  replServer.defineCommand('find', {
    help: 'Find stops by name using .find <query>',
    action(query: string) {
      this.clearBufferedCommand();
      const stops = stopsIndex.findStopsByName(query);
      stops.forEach((stop) => {
        console.log(`${stop.name} (${stop.id})`);
      });
      this.displayPrompt();
    },
  });
  replServer.defineCommand('route', {
    help: 'Find a route using .route from <stationIdOrName> to <stationIdOrName> at <HH:mm> [with <N> transfers]',
    action(routeQuery: string) {
      this.clearBufferedCommand();
      const parts = routeQuery.split(' ').filter(Boolean);
      const withTransfersIndex = parts.indexOf('with');
      const maxTransfers =
        withTransfersIndex !== -1 && parts[withTransfersIndex + 1] !== undefined
          ? parseInt(parts[withTransfersIndex + 1] as string)
          : 4;
      const atTime = parts
        .slice(
          withTransfersIndex === -1
            ? parts.indexOf('at') + 1
            : parts.indexOf('at') + 1,
          withTransfersIndex === -1 ? parts.length : withTransfersIndex,
        )
        .join(' ');
      const fromIndex = parts.indexOf('from');
      const toIndex = parts.indexOf('to');
      const fromId = parts.slice(fromIndex + 1, toIndex).join(' ');
      const toId = parts
        .slice(
          toIndex + 1,
          withTransfersIndex === -1 ? parts.indexOf('at') : parts.indexOf('at'),
        )
        .join(' ');

      if (!fromId || !toId || !atTime) {
        console.log(
          'Usage: .route from <stationIdOrName> to <stationIdOrName> at <HH:mm> [with <N> transfers]',
        );
        this.displayPrompt();
        return;
      }

      const fromStop =
        stopsIndex.findStopBySourceStopId(fromId) ||
        stopsIndex.findStopsByName(fromId)[0];
      const toStop =
        stopsIndex.findStopBySourceStopId(toId) ||
        stopsIndex.findStopsByName(toId)[0];

      if (!fromStop) {
        console.log(`No stop found for 'from' ID or name: ${fromId}`);
        this.displayPrompt();
        return;
      }

      if (!toStop) {
        console.log(`No stop found for 'to' ID or name: ${toId}`);
        this.displayPrompt();
        return;
      }

      const departureTime = Time.fromString(atTime);

      try {
        const query = new Query.Builder()
          .from(fromStop.sourceStopId)
          .to([toStop.sourceStopId])
          .departureTime(departureTime)
          .maxTransfers(maxTransfers)
          .build();

        const router = new Router(timetable, stopsIndex);

        const result = router.route(query);
        const arrivalTime = result.arrivalAt(toStop.sourceStopId);
        if (arrivalTime === undefined) {
          console.log(`Destination not reachable`);
        } else {
          console.log(
            `Arriving to ${toStop.name} at ${arrivalTime.time.toString()} with ${arrivalTime.legNumber - 1} transfers from ${stopsIndex.findStopById(arrivalTime.origin)?.name}.`,
          );
        }
        const bestRoute = result.bestRoute(toStop.sourceStopId);

        if (bestRoute) {
          console.log(`Found route from ${fromStop.name} to ${toStop.name}:`);
          prettyPrintRoute(bestRoute);
        } else {
          console.log('No route found');
        }
      } catch (error) {
        console.log('Error querying route:', error);
      }

      this.displayPrompt();
    },
  });
  replServer.defineCommand('plot', {
    help: 'Plot a network graph using .plot from <stationId> to <stationId> at <HH:mm> [with <N> transfers] [to <graph.dot>]',
    action(routeQuery: string) {
      this.clearBufferedCommand();
      const parts = routeQuery.split(' ').filter(Boolean);
      const withTransfersIndex = parts.indexOf('with');
      const maxTransfers =
        withTransfersIndex !== -1 && parts[withTransfersIndex + 1] !== undefined
          ? parseInt(parts[withTransfersIndex + 1] as string)
          : 1;
      const atTimeIndex = parts.indexOf('at');
      const atTime = parts
        .slice(
          atTimeIndex + 1,
          withTransfersIndex === -1
            ? parts.indexOf('to', atTimeIndex) >= 0
              ? parts.indexOf('to', atTimeIndex)
              : parts.length
            : withTransfersIndex,
        )
        .join(' ');
      const fromIndex = parts.indexOf('from');
      const toIndex = parts.indexOf('to');
      const toFileIndex =
        toIndex !== -1 && parts.indexOf('to', toIndex + 1) !== -1
          ? parts.indexOf('to', toIndex + 1)
          : -1;
      const fromId = parts.slice(fromIndex + 1, toIndex).join(' ');
      const toId = parts.slice(toIndex + 1, atTimeIndex).join(' ');
      const outputFile =
        toFileIndex !== -1
          ? parts.slice(toFileIndex + 1).join(' ')
          : `${fromId.replace(/ /g, '')}-${toId.replace(/ /g, '')}-${atTime.replace(/:/g, '')}.dot`;

      if (!fromId || !toId || !atTime || isNaN(maxTransfers)) {
        console.log(
          'Usage: .plot from <stationId> to <stationId> at <HH:mm> [with <N> transfers] [to <graph.dot>]',
        );
        this.displayPrompt();
        return;
      }

      const fromStop =
        stopsIndex.findStopBySourceStopId(fromId) ||
        stopsIndex.findStopsByName(fromId)[0];
      const toStop =
        stopsIndex.findStopBySourceStopId(toId) ||
        stopsIndex.findStopsByName(toId)[0];

      if (!fromStop) {
        console.log(`No stop found for 'from' ID or name: ${fromId}`);
        this.displayPrompt();
        return;
      }

      if (!toStop) {
        console.log(`No stop found for 'to' ID or name: ${toId}`);
        this.displayPrompt();
        return;
      }

      const departureTime = Time.fromString(atTime);
      try {
        const query = new Query.Builder()
          .from(fromStop.sourceStopId)
          .to([toStop.sourceStopId])
          .departureTime(departureTime)
          .maxTransfers(maxTransfers)
          .build();

        const router = new Router(timetable, stopsIndex);

        const result = router.route(query);
        plotGraphToDotFile(result, outputFile);
      } catch (error) {
        console.log('Error plotting route:', error);
      }

      this.displayPrompt();
    },
  });
};
