// Import Deno-compatible modules
import { parse as parseCSV } from "https://deno.land/std/csv/mod.ts";
import { Neo4j } from "https://deno.land/x/neo4j_lite_client/mod.ts";

// Define types
interface LocationNode {
  id: number;
  lat: number;
  lng: number;
}

interface GTFSStop {
  stop_id: string;
  stop_name: string;
  stop_lat: string;
  stop_lon: string;
  location_type?: string;
  parent_station?: string;
  zone_id?: string;
  stop_url?: string;
  [key: string]: string | undefined; // Allow for additional fields
}

/**
 * Sets up a Neo4j graph database with location nodes from JSON and GTFS stop data
 * @param driver Neo4j driver instance to use for database operations
 * @param jsonFilePath Optional path to the JSON file containing location nodes (defaults to './data/locations.json')
 * @param gtfsStopsPath Optional path to the GTFS stops.txt file (defaults to './data/gtfs/stops.txt')
 */
export async function graph_setup(
  driver: Neo4j.Driver,
  OCCT_stops_json: string = "./data/OCCT/stops.json",
  BCT_GTFS_stops_txt: string = "./data/BCT/stops.txt",
): Promise<void> {
  const session = driver.session();

  const jsonData = await Deno.readTextFile(OCCT_stops_json);
  const locationNodes: LocationNode[] = JSON.parse(jsonData);

  await stops_json_node_import(session, locationNodes);

  const BCTStopsData = await Deno.readTextFile(BCT_GTFS_stops_txt);

  const BCT_stops = await parse_gtfs_stops(BCTStopsData);

  await stops_gtfs_node_import(session, BCT_stops);

  await session.close();
}

async function stops_json_node_import(
  session: Neo4j.Session,
  stops: LocationNode[],
) {
  for (const node of stops) {
    await session.run(
      `
        MERGE (n:TransportNode {id: $id})
        ON CREATE SET
          n.originalId = $originalId,
          n.latitude = $lat,
          n.longitude = $lng,
          n.source = 'OCCT'
        ON MATCH SET
          n.originalId = $originalId,
          n.latitude = $lat,
          n.longitude = $lng,
          n.source = 'OCCT'
        `,
      {
        id: `OCCT_${node.id}`,
        originalId: node.id,
        lat: node.lat,
        lng: node.lng,
      },
    );
  }
}

async function stops_gtfs_node_import(
  session: Neo4j.Session,
  stops: GTFSStop[],
) {
  // Add GTFS stops to Neo4j
  for (const stop of stops) {
    // Make sure required fields exist
    if (
      !stop.stop_id || !stop.stop_name || !stop.stop_lat || !stop.stop_lon
    ) {
      continue;
    }

    // Use MERGE to update existing nodes or create new ones
    await session.run(
      `
        MERGE (s:TransportNode {id: $id})
        ON CREATE SET
          s.name = $name,
          s.latitude = $lat,
          s.longitude = $lng,
          s.locationType = $locationType,
          s.parentStation = $parentStation,
          s.zoneId = $zoneId,
          s.url = $url,
          s.source = 'BCT'
        ON MATCH SET
          s.name = $name,
          s.latitude = $lat,
          s.longitude = $lng,
          s.locationType = $locationType,
          s.parentStation = $parentStation,
          s.zoneId = $zoneId,
          s.url = $url,
          s.source = 'BCT'
        `,
      {
        id: "BCT_" + stop.stop_id,
        name: stop.stop_name,
        lat: parseFloat(stop.stop_lat),
        lng: parseFloat(stop.stop_lon),
        locationType: stop.location_type || null,
        parentStation: stop.parent_station || null,
        zoneId: stop.zone_id || null,
        url: stop.stop_url || null,
      },
    );
  }
}

async function parse_gtfs_stops(txt: string): Promise<GTFSStop[]> {
  // First, read the file content to manually determine headers
  const lines = txt.split("\n");
  const headers = lines[0].split(",").map((header) => header.trim());

  // Parse CSV data using Deno's CSV parser with flexible parsing options
  const gtfsStops = await parseCSV(txt, {
    skipFirstRow: false, // We'll handle headers manually
    columns: headers,
    parse: (input: unknown[]) => {
      // This function gets called for each row
      // The first row contains headers, so we'll skip it
      if (input[0] === headers[0]) {
        return null; // Skip the header row
      }

      // Create a stop object with the right properties
      const stop: GTFSStop = {
        stop_id: "",
        stop_name: "",
        stop_lat: "",
        stop_lon: "",
      };

      // Map the values to their corresponding headers
      headers.forEach((header, index) => {
        if (input[index] !== undefined) {
          // @ts-ignore - We're dynamically setting properties
          stop[header] = input[index];
        }
      });

      return stop;
    },
  }) as GTFSStop[];

  // Filter out any null values (like the header row we skipped)
  const validStops = gtfsStops.filter((stop) => stop !== null);

  return validStops;
}
