import { defineStore } from 'pinia';
import { parseSpawns } from './utils';

import {
  ActiveScenery,
  CheckpointTrain,
  Station,
  StationRoutes,
  Status,
  Train
} from '../typings/common';
import { useApiStore } from './apiStore';
import { MainStoreState } from './typings';
import i18n from '../i18n';
import StorageManager from '../managers/storageManager';

const checkpointsTrains: Map<string, CheckpointTrain[]> = new Map();
const unknownSceneryCheckpoints: Map<string, Set<string>> = new Map();
const sceneriesTrains: Map<string, Train[]> = new Map();

export const useMainStore = defineStore('mainStore', {
  state: () =>
    ({
      region: { id: 'eu', value: 'PL1', name: 'PL1' },

      isOffline: false,
      appUpdate: null,

      chosenModalTrainId: undefined,

      modalLastClickedTarget: null,
      currentLocale: 'pl'
    }) as MainStoreState,

  actions: {
    changeLocale(localeName: string) {
      (i18n.global.locale.value as any) = localeName;
      this.currentLocale = localeName;

      StorageManager.setStringValue('lang', localeName);
    }
  },

  getters: {
    trainList(): Train[] {
      const apiStore = useApiStore();

      checkpointsTrains.clear();
      sceneriesTrains.clear();
      unknownSceneryCheckpoints.clear();

      const dateNow = new Date();

      const x = (apiStore.activeData?.trains ?? [])
        .filter(
          (train) =>
            train.timetable || train.lastSeen >= dateNow.getTime() - 60000 || train.online == 1
        )
        .map((train) => {
          const stock = train.stockString.split(';');
          const locoType = stock ? stock[0] : train.stockString;

          const timetable = train.timetable;

          const trainObj = {
            id: train.id,
            modalId: `${train.driverName}${train.trainNo}`, // simplified id for train modal

            trainNo: train.trainNo,
            mass: train.mass,
            length: train.length,
            speed: train.speed,
            region: train.region,

            distance: train.distance,
            signal: train.signal,
            online: Boolean(train.online),
            driverId: train.driverId,
            driverName: train.driverName,
            driverLanguageId: train.driverLanguageId,
            currentStationName: train.currentStationName,
            currentStationHash: train.currentStationHash,
            connectedTrack: train.connectedTrack,
            stockList: stock,
            locoType,

            lastSeen: train.lastSeen,
            isTimeout: train.isTimeout,

            isSupporter: train.driverIsSupporter,
            driverLevel: train.driverLevel,

            driverRouteLocation: {
              name: 'DriverView',
              query: {
                trainId: train.id
              }
            },

            timetableData: timetable
              ? {
                  timetableId: timetable.timetableId,
                  route: timetable.route,
                  category: timetable.category,
                  followingStops: timetable.stopList,
                  routeDistance: timetable.stopList[timetable.stopList.length - 1].stopDistance,
                  sceneries: timetable.sceneries,
                  twr: timetable.twr,
                  warningNotes: timetable.warningNotes,
                  hasDangerousCargo: timetable.hasDangerousCargo,
                  hasExtraDeliveries: timetable.hasExtraDeliveries,
                  trainMaxSpeed: timetable.trainMaxSpeed,

                  timetablePath: timetable.path.split(';').map((pathElementString) => {
                    const [arrival, station, departure] = pathElementString.split(',');

                    return {
                      arrivalRouteExt: arrival,
                      departureRouteExt: departure,
                      stationName: station.split(' ').slice(0, -1).join(' '),
                      stationHash: station.split(' ').slice(-1).join(' ').replace('.sc', '')
                    };
                  })
                }
              : undefined
          } as Train;

          const stationNameKey =
            train.currentStationName.indexOf('.sc') != -1
              ? train.currentStationName.split(' ').slice(0, -1).join(' ')
              : train.currentStationName;

          // Sceneries trains map
          if (sceneriesTrains.has(stationNameKey)) {
            sceneriesTrains.set(stationNameKey, [
              ...sceneriesTrains.get(stationNameKey)!,
              trainObj
            ]);
          } else sceneriesTrains.set(stationNameKey, [trainObj]);

          // Checkpoints trains map
          if (trainObj.timetableData) {
            const timetablePath = trainObj.timetableData.timetablePath;
            let currentSceneryIndex = 0;

            let currentSceneryData: Station | null =
              this.stationList.find(
                (s) => s.name == timetablePath[currentSceneryIndex].stationName
              ) ?? null;

            trainObj.timetableData.followingStops.forEach((stop, i) => {
              if (/strong|podg|pe/.test(stop.stopName)) {
                const checkpointTrain: CheckpointTrain = {
                  train: trainObj,
                  checkpointStop: stop,

                  previousSceneryElement:
                    currentSceneryIndex > 0 ? timetablePath[currentSceneryIndex - 1] : null,

                  nextSceneryElement:
                    currentSceneryIndex < timetablePath.length - 1
                      ? timetablePath[currentSceneryIndex + 1]
                      : null,

                  timetablePathElement: timetablePath[currentSceneryIndex]
                };

                // Adding missing sceneries checkpoints as a fallback when scenery data is missing (and "generalInfo" is unavailable)
                if (!currentSceneryData) {
                  const sceneryCheckpointsSet = unknownSceneryCheckpoints.get(
                    checkpointTrain.timetablePathElement.stationName
                  );

                  if (!sceneryCheckpointsSet) {
                    unknownSceneryCheckpoints.set(
                      checkpointTrain.timetablePathElement.stationName,
                      new Set([stop.stopNameRAW])
                    );
                  } else {
                    sceneryCheckpointsSet.add(stop.stopNameRAW);
                  }
                }

                // Adding trains to their corresponding checkpoints
                if (checkpointsTrains.has(stop.stopNameRAW.toLowerCase())) {
                  checkpointsTrains.set(stop.stopNameRAW.toLowerCase(), [
                    ...checkpointsTrains.get(stop.stopNameRAW.toLowerCase())!,
                    checkpointTrain
                  ]);
                } else {
                  checkpointsTrains.set(stop.stopNameRAW.toLowerCase(), [checkpointTrain]);
                }
              }

              if (timetablePath[currentSceneryIndex].departureRouteExt == stop.departureLine) {
                currentSceneryIndex++;

                currentSceneryData =
                  this.stationList.find(
                    (s) => s.name == timetablePath[currentSceneryIndex].stationName
                  ) ?? null;
              }
            });
          }

          return trainObj;
        });

      return x;
    },

    // computed active sceneries
    activeSceneryList(state): ActiveScenery[] {
      const apiStore = useApiStore();

      if (state.isOffline) return [];

      if (!apiStore.activeData?.activeSceneries) return [];

      const offlineActiveSceneries = this.trainList.reduce((acc, train) => {
        if (!train.timetableData) return acc;

        train.timetableData.timetablePath.forEach((p) => {
          if (
            acc.findIndex(
              (v) =>
                (v.name == p.stationName || v.hash == p.stationHash) && v.region == train.region
            ) != -1 ||
            apiStore.activeData?.activeSceneries?.findIndex(
              (sc) =>
                (sc.stationName == p.stationName || sc.stationHash == p.stationHash) &&
                sc.region == train.region &&
                Date.now() - sc.lastSeen < 1000 * 60 * 2
            ) != -1
          )
            return acc;

          acc.push({
            name: p.stationName,
            hash: '',
            region: train.region,
            maxUsers: 0,
            currentUsers: 0,
            spawns: [],
            dispatcherName: '',
            dispatcherRate: 0,
            dispatcherId: -1,
            dispatcherExp: -1,
            dispatcherIsSupporter: false,
            dispatcherStatus: Status.ActiveDispatcher.FREE,
            dispatcherTimestamp: -1,
            dispatcherLanguageId: -1,

            isOnline: false,

            stationTrains: [],
            scheduledTrains: [],

            scheduledTrainCount: {
              all: 0,
              confirmed: 0,
              unconfirmed: 0
            },

            missingCheckpoints: []
          });
        });

        return acc;
      }, [] as ActiveScenery[]);

      const referenceTimestamp = Date.now();

      const onlineActiveSceneries = apiStore.activeData?.activeSceneries.reduce((list, scenery) => {
        if (scenery.isOnline !== 1 && Date.now() - scenery.lastSeen > 1000 * 60 * 2) return list;
        if (scenery.dispatcherStatus == Status.ActiveDispatcher.UNKNOWN) return list;

        const dispatcherTimestamp =
          scenery.dispatcherStatus == Status.ActiveDispatcher.NO_LIMIT
            ? referenceTimestamp + 25500000
            : scenery.dispatcherStatus > 5
              ? scenery.dispatcherStatus
              : null;

        list.push({
          name: scenery.stationName,
          hash: scenery.stationHash,
          region: scenery.region,
          maxUsers: scenery.maxUsers,
          currentUsers: scenery.currentUsers,
          spawns: parseSpawns(scenery.spawnString),
          dispatcherName: scenery.dispatcherName,
          dispatcherRate: scenery.dispatcherRate,
          dispatcherId: scenery.dispatcherId,
          dispatcherExp: scenery.dispatcherExp,
          dispatcherIsSupporter: scenery.dispatcherIsSupporter,
          dispatcherStatus: scenery.dispatcherStatus,
          dispatcherTimestamp: dispatcherTimestamp,
          dispatcherLanguageId: scenery.dispatcherLanguageId,

          isOnline: scenery.isOnline == 1,

          stationTrains: [],
          scheduledTrains: [],

          scheduledTrainCount: {
            all: 0,
            confirmed: 0,
            unconfirmed: 0
          },

          missingCheckpoints: []
        });

        return list;
      }, [] as ActiveScenery[]);

      const allActiveSceneries = [...onlineActiveSceneries, ...offlineActiveSceneries];

      for (let i = 0, n = allActiveSceneries.length; i < n; i++) {
        const scenery = allActiveSceneries[i];

        let station = this.stationList.find((s) => s.name === scenery.name);

        let checkpointsSet: Set<string> = new Set();

        // Add checkpoints to active scenery data
        checkpointsSet.add(scenery.name.toLowerCase());

        station?.generalInfo?.checkpoints.forEach((cpName) => {
          checkpointsSet.add(cpName.toLowerCase());
        });

        const checkpoints = Array.from(checkpointsSet);

        scenery.stationTrains =
          sceneriesTrains.get(scenery.name)?.filter((sc) => sc.region == this.region.id) ?? [];

        // Missing checkpoints as a fallback for sceneries without generalInfo & checkpoints property
        const missingCheckpointsToAdd = unknownSceneryCheckpoints.get(scenery.name);

        if (missingCheckpointsToAdd) {
          [...missingCheckpointsToAdd].forEach((cp) => {
            if (cp.toLowerCase() == scenery.name.toLowerCase()) return;

            checkpoints.push(cp);
            scenery.missingCheckpoints.push(cp);
          });
        }

        const uniqueTrainIds: string[] = [];
        checkpoints.forEach((cp) => {
          const scheduledTrains = checkpointsTrains.get(cp.toLowerCase());

          if (!scheduledTrains) return;

          scheduledTrains.forEach(({ train, checkpointStop, timetablePathElement, ...v }) => {
            if (
              scenery.name != timetablePathElement.stationName &&
              scenery.hash != timetablePathElement.stationHash
            )
              return;

            scenery.scheduledTrains.push({ train, checkpointStop, timetablePathElement, ...v });

            if (uniqueTrainIds.includes(train.id) || train.region != this.region.id) return;

            scenery.scheduledTrainCount.all += 1;

            if (checkpointStop.confirmed) scenery.scheduledTrainCount.confirmed++;
            else scenery.scheduledTrainCount.unconfirmed++;

            uniqueTrainIds.push(train.id);
          });
        });
      }

      return allActiveSceneries;
    },

    // computed station data
    stationList(): Station[] {
      const apiStore = useApiStore();

      return apiStore.sceneryData.map((scenery) => {
        const routes = scenery.routesInfo.reduce(
          (acc, route) => {
            acc['all'].push(route);

            if (route.hidden) return acc;

            const tracksKey = route.routeTracks == 2 ? 'double' : 'single';
            const isElectric = route.isElectric;

            const routesKey: keyof StationRoutes = `${tracksKey}${
              !isElectric ? 'Other' : 'Electrified'
            }${route.isInternal ? 'Internal' : ''}Names`;
            
            acc[routesKey].push(route.routeName);

            if (route.isRouteSBL) acc['sblNames'].push(route.routeName);

            acc.minRouteSpeed =
              acc.minRouteSpeed == 0
                ? route.routeSpeed
                : Math.min(route.routeSpeed, acc.minRouteSpeed);

            acc.maxRouteSpeed = Math.max(route.routeSpeed, acc.maxRouteSpeed);

            acc[tracksKey].push(route);

            return acc;
          },
          {
            all: [],
            single: [],
            double: [],

            singleElectrifiedNames: [],
            singleOtherNames: [],
            doubleElectrifiedNames: [],
            doubleOtherNames: [],

            singleElectrifiedInternalNames: [],
            singleOtherInternalNames: [],
            doubleElectrifiedInternalNames: [],
            doubleOtherInternalNames: [],

            sblNames: [],
            minRouteSpeed: 0,
            maxRouteSpeed: 0
          } as StationRoutes
        );

        return {
          name: scenery.name,
          generalInfo: {
            ...scenery,
            authors: scenery.authors?.split(',').map((a) => a.trim()),
            routes: routes,
            checkpoints:
              scenery.checkpoints && scenery.checkpoints.trim().length > 0
                ? scenery.checkpoints.split(';')
                : []
          }
        };
      });
    },

    allStationInfo(): Station[] {
      const onlineUnsavedStations: Station[] = this.activeSceneryList
        .filter(
          (scenery) =>
            this.stationList.findIndex((st) => st.name == scenery.name) == -1 &&
            scenery.region == this.region.id
        )
        .map((os) => ({
          name: os.name,
          generalInfo: undefined,
          onlineInfo: os
        }));

      return [
        ...onlineUnsavedStations,
        ...this.stationList.map((st) => ({
          ...st,
          onlineInfo: this.activeSceneryList.find(
            (os) => os.name == st.name && os.region == this.region.id
          )
        }))
      ];
    }
  }
});
