text: /* @flow */
  
  import { install } from './install'
  import { START } from './util/route'
  import { assert } from './util/warn'
  import { inBrowser } from './util/dom'
  import { cleanPath } from './util/path'
  import { createMatcher } from './create-matcher'
  import { normalizeLocation } from './util/location'
  import { supportsPushState } from './util/push-state'
  import { handleScroll } from './util/scroll'
  
  import { HashHistory } from './history/hash'
  import { HTML5History } from './history/html5'
  import { AbstractHistory } from './history/abstract'
  
  import type { Matcher } from './create-matcher'
  
  import { isNavigationFailure, NavigationFailureType } from './util/errors'
  
  export default class VueRouter {
    static install: () => void
    static version: string
    static isNavigationFailure: Function
    static NavigationFailureType: any
  
    app: any
    apps: Array<any>
    ready: boolean
    readyCbs: Array<Function>
    options: RouterOptions
    mode: string
    history: HashHistory | HTML5History | AbstractHistory
    matcher: Matcher
    fallback: boolean
    beforeHooks: Array<?NavigationGuard>
    resolveHooks: Array<?NavigationGuard>
    afterHooks: Array<?AfterNavigationHook>
  
    constructor (options: RouterOptions = {}) {
      this.app = null
      this.apps = []
      this.options = options
      this.beforeHooks = []
      this.resolveHooks = []
      this.afterHooks = []
      this.matcher = createMatcher(options.routes || [], this)
  
      let mode = options.mode || 'hash'
      this.fallback =
        mode === 'history' && !supportsPushState && options.fallback !== false
      if (this.fallback) {
        mode = 'hash'
      }
      if (!inBrowser) {
        mode = 'abstract'
      }
      this.mode = mode
  
      switch (mode) {
        case 'history':
          this.history = new HTML5History(this, options.base)
          break
        case 'hash':
          this.history = new HashHistory(this, options.base, this.fallback)
          break
        case 'abstract':
          this.history = new AbstractHistory(this, options.base)
          break
        default:
          if (process.env.NODE_ENV !== 'production') {
            assert(false, `invalid mode: ${mode}`)
          }
      }
    }
  
    match (raw: RawLocation, current?: Route, redirectedFrom?: Location): Route {
      return this.matcher.match(raw, current, redirectedFrom)
    }
  
    get currentRoute (): ?Route {
      return this.history && this.history.current
    }
  
    init (app: any /* Vue component instance */) {
      process.env.NODE_ENV !== 'production' &&
        assert(
          install.installed,
          `not installed. Make sure to call \`Vue.use(VueRouter)\` ` +
            `before creating root instance.`
        )
  
      this.apps.push(app)
  
      // set up app destroyed handler
      // https://github.com/vuejs/vue-router/issues/2639
      app.$once('hook:destroyed', () => {
        // clean out app from this.apps array once destroyed
        const index = this.apps.indexOf(app)
        if (index > -1) this.apps.splice(index, 1)
        // ensure we still have a main app or null if no apps
        // we do not release the router so it can be reused
        if (this.app === app) this.app = this.apps[0] || null
  
        if (!this.app) {
          // clean up event listeners
          // https://github.com/vuejs/vue-router/issues/2341
          this.history.teardownListeners()
        }
      })
  
      // main app previously initialized
      // return as we don't need to set up new history listener
      if (this.app) {
        return
      }
  
      this.app = app
  
      const history = this.history
  
      if (history instanceof HTML5History || history instanceof HashHistory) {
        const handleInitialScroll = routeOrError => {
          const from = history.current
          const expectScroll = this.options.scrollBehavior
          const supportsScroll = supportsPushState && expectScroll
  
          if (supportsScroll && 'fullPath' in routeOrError) {
            handleScroll(this, routeOrError, from, false)
          }
        }
        const setupListeners = routeOrError => {
          history.setupListeners()
          handleInitialScroll(routeOrError)
        }
        history.transitionTo(
          history.getCurrentLocation(),
          setupListeners,
          setupListeners
        )
      }
  
      history.listen(route => {
        this.apps.forEach(app => {
          app._route = route
        })
      })
    }
  
    beforeEach (fn: Function): Function {
      return registerHook(this.beforeHooks, fn)
    }
  
    beforeResolve (fn: Function): Function {
      return registerHook(this.resolveHooks, fn)
    }
  
    afterEach (fn: Function): Function {
      return registerHook(this.afterHooks, fn)
    }
  
    onReady (cb: Function, errorCb?: Function) {
      this.history.onReady(cb, errorCb)
    }
  
    onError (errorCb: Function) {
      this.history.onError(errorCb)
    }
  
    push (location: RawLocation, onComplete?: Function, onAbort?: Function) {
      // $flow-disable-line
      if (!onComplete && !onAbort && typeof Promise !== 'undefined') {
        return new Promise((resolve, reject) => {
          this.history.push(location, resolve, reject)
        })
      } else {
        this.history.push(location, onComplete, onAbort)
      }
    }
  
    replace (location: RawLocation, onComplete?: Function, onAbort?: Function) {
      // $flow-disable-line
      if (!onComplete && !onAbort && typeof Promise !== 'undefined') {
        return new Promise((resolve, reject) => {
          this.history.replace(location, resolve, reject)
        })
      } else {
        this.history.replace(location, onComplete, onAbort)
      }
    }
  
    go (n: number) {
      this.history.go(n)
    }
  
    back () {
      this.go(-1)
    }
  
    forward () {
      this.go(1)
    }
  
    getMatchedComponents (to?: RawLocation | Route): Array<any> {
      const route: any = to
        ? to.matched
          ? to
          : this.resolve(to).route
        : this.currentRoute
      if (!route) {
        return []
      }
      return [].concat.apply(
        [],
        route.matched.map(m => {
          return Object.keys(m.components).map(key => {
            return m.components[key]
          })
        })
      )
    }
  
    resolve (
      to: RawLocation,
      current?: Route,
      append?: boolean
    ): {
      location: Location,
      route: Route,
      href: string,
      // for backwards compat
      normalizedTo: Location,
      resolved: Route
    } {
      current = current || this.history.current
      const location = normalizeLocation(to, current, append, this)
      const route = this.match(location, current)
      const fullPath = route.redirectedFrom || route.fullPath
      const base = this.history.base
      const href = createHref(base, fullPath, this.mode)
      return {
        location,
        route,
        href,
        // for backwards compat
        normalizedTo: location,
        resolved: route
      }
    }
  
    addRoutes (routes: Array<RouteConfig>) {
      this.matcher.addRoutes(routes)
      if (this.history.current !== START) {
        this.history.transitionTo(this.history.getCurrentLocation())
      }
    }
  }
  
  function registerHook (list: Array<any>, fn: Function): Function {
    list.push(fn)
    return () => {
      const i = list.indexOf(fn)
      if (i > -1) list.splice(i, 1)
    }
  }
  
  function createHref (base: string, fullPath: string, mode) {
    var path = mode === 'hash' ? '#' + fullPath : fullPath
    return base ? cleanPath(base + '/' + path) : path
  }
  
  VueRouter.install = install
  VueRouter.version = '__VERSION__'
  VueRouter.isNavigationFailure = isNavigationFailure
  VueRouter.NavigationFailureType = NavigationFailureType
  
  // if (inBrowser && window.Vue) {
  //   window.Vue.use(VueRouter)
  // }
  