import { mapActions } from 'vuex'
import helpers from '@/helpers'
import _ from 'lodash'
import EscapeStringRegex from 'escape-string-regexp'

export default {
  data() {
    let vue = this
    return {
      selectedOrder: [],
      validateRoutes: true,
      validated: false,
      validatedBy: '',
      isValid: false,
      initValidated: false,
      invalidRoutingPath: '',
      lastRoutedPath: '',
      routeInitializeResolver: null,
      routeInitialize: new Promise(resolve => {
        vue.routeInitializeResolver = true
      }),
    }
  },
  async beforeRouteUpdate(to, from, nextRoute) {
    // allow routing if routing by validator
    if (this.lastRoutedPath !== to.path) {
      if (to.path === this.invalidRoutingPath) {
        this.lastRoutedPath = to.path
        nextRoute()
      } else {
        if (await this.validate('router', to)) {
          this.lastRoutedPath = to.path
          nextRoute()
        }
      }
    }
  },
  methods: {
    ...mapActions(['logError']),
    /**
     * Initializes routes
     *
     * This method checks all the routes and sees if the route addCallback exists. If the callback exists, it will execute
     * callback and only add the route to selected routes if the callback returns true. If the callback dose not exist
     * it will add the route immediately.
     *
     * @param {boolean} validate to validate on init
     * @returns {Promise<void>}
     */
    async initRouting(validate = true) {
      let selectedOrder = []
      let vue = this
      this.routeInitialize = new Promise(resolve => {
        vue.routeInitializeResolver = resolve
      })
      for (let i = 0; i < this.routingOrder.length; i++) {
        if (typeof this.routingOrder[i].addCallback === 'function') {
          if ((await this.routingOrder[i].addCallback.apply(this, [this.routingOrder[i]])) === true) {
            selectedOrder.push(this.routingOrder[i])
          }
        } else {
          selectedOrder.push(this.routingOrder[i])
        }
      }
      this.selectedOrder = _.cloneDeep(selectedOrder)
      await this.validate('init', this.$route, validate)
      this.initialValidated === true
      vue.routeInitializeResolver(true)
    },
    /**
     * next Route
     *
     * Returns the next route in the selected route based on current route
     *
     * @returns {*}
     */
    nextRoute() {
      let currentRoute = this.currentRouteIndex()
      if (currentRoute > -1) {
        if (currentRoute + 1 === this.selectedOrder.length) {
          this.selectedOrder[currentRoute].hideNext = true
          return this.selectedOrder[currentRoute]
        } else {
          return this.selectedOrder[currentRoute + 1]
        }
      }
      return { previous: '', next: '' }
    },
    /**
     * next Route
     *
     * Returns the next route in the selected route based on current route
     *
     * @returns {*}
     */
    currentRoute() {
      let currentRoute = this.currentRouteIndex()
      if (currentRoute > -1) {
        return this.selectedOrder[currentRoute]
      }
      return { previous: '', next: '' }
    },
    /**
     * Next Route
     *
     * Moves to the next route after validating
     *
     * @returns {Promise<void>}
     */
    async next() {
      this.initRouting(false).then(() => {
        let query = this.$route.query
        let nextRoute = this.nextRoute()
        this.logError({ cartError: '' })
        this.logError({ productError: '' })
        this.logError({ starterKitError: '' })

        let routesDestination = (nextRoute && nextRoute.path) || this.selectedOrder[0].path
        let routingPath = (typeof routesDestination === 'object' && routesDestination) || { path: routesDestination }

        this.$router.push({ ...routingPath, query })
      })
    },
    /**
     * Previous Route
     *
     * Returns the previous route
     *
     * @returns {*}
     */
    previousRoute() {
      let currentRoute = this.currentRouteIndex()
      if (currentRoute > -1) {
        if (currentRoute + 1 <= this.selectedOrder.length && currentRoute !== 0) {
          return this.selectedOrder[currentRoute - 1]
        }
      }
      return { previous: '', next: '' }
    },
    /**
     * Previous
     *
     * Moves to the previous route from the selected routes based on the current route path
     *
     */
    previous() {
      this.validated = false
      let query = this.$route.query
      let currentRoute = this.currentRouteIndex()
      let previousRoute = this.previousRoute()

      this.selectedOrder[currentRoute].hideNext = false

      let routesDestination = (previousRoute && previousRoute.path) || this.selectedOrder[0].path
      let routingPath = (typeof routesDestination === 'object' && routesDestination) || { path: routesDestination }

      this.$router.push({ ...routingPath, query })
    },
    /**
     * Returns the current routes index
     *
     * @returns int
     */
    currentRouteIndex() {
      return this.selectedOrder.findIndex(route => {
        let match = route.match || route.path
        return helpers.RouteHasPath(this.$route.path, match)
      })
    },

    /**
     * Find the matching validated routing configuration based on the given $route
     *
     * @param route
     * @returns {*}
     */
    findRoute(route) {
      return _.find(this.selectedOrder, orderedRoute => {
        let regEx = new RegExp(EscapeStringRegex(orderedRoute.match || orderedRoute.path))
        return regEx.test(route.path)
      })
    },

    /**
     * Validates the selected routes
     *
     * @param validator
     * @param movingtoRoute
     * @returns {Promise<boolean>}
     */
    async validate(validator, movingtoRoute, validate = true) {
      let nextRoute = this.findRoute(movingtoRoute) || this.nextRoute()
      let query = this.$route.query
      this.isValid = true
      this.validated = true
      this.validatedBy = validator
      this.invalidRoutingPath = ''
      if (this.validateRoutes && validate) {
        if (nextRoute.path) {
          for (let i = 0; i < this.selectedOrder.length; i++) {
            let regEx = new RegExp(EscapeStringRegex(this.selectedOrder[i].match || this.selectedOrder[i].path))

            if (!regEx.test(nextRoute.path)) {
              if (
                typeof this.selectedOrder[i].validate === 'function' &&
                (await this.selectedOrder[i].validate.apply(this, [this.selectedOrder[i], movingtoRoute])) === false
              ) {
                this.validated = false
                this.isValid = false
                this.invalidRoutingPath = this.selectedOrder[i].path
                if (this.$route.path !== this.selectedOrder[i].path) {
                  this.$router.replace({ path: this.selectedOrder[i].path, query })
                }
                this.logError(this.selectedOrder[i].error)
                break
              }
            } else {
              break
            }
          }
        } else if (
          nextRoute.path === undefined &&
          nextRoute.previous === '' &&
          nextRoute.next === '' &&
          this.selectedOrder.length > 0
        ) {
          // use is in a invalid route, send user to the first page
          this.validated = false
          this.isValid = false
          this.invalidRoutingPath = this.selectedOrder[0].path
          this.$router.replace({ path: this.selectedOrder[0].path, query })
          this.isValid = false
        }
      }
      return this.isValid
    },
  },
  computed: {
    initialValidated: {
      get() {
        return this.initValidated
      },
      set(value) {
        this.initValidated = value
      },
    },
  },
}
