whole_func_string: function Router(declarativeStates) {
    const router = {}
    const states = stateTrees(declarativeStates)
    const eventCallbacks = {}
  
    let options = util.copyObject(defaultOptions)
    let firstTransition = true
    let ignoreNextURLChange = false
    let currentPathQuery
    let currentParamsDiff = {}
    let currentState
    let previousState
    let transition
    let leafStates
    let urlChanged
    let initialized
    let hashSlashString
  
    /*
    * Setting a new state will start a transition from the current state to the target state.
    * A successful transition will result in the URL being changed.
    * A failed transition will leave the router in its current state.
    */
    function setState(state, params, acc) {
      const fromState = transition
        ? StateWithParams(transition.currentState, transition.toParams)
        : currentState
  
      const diff = util.objectDiff(fromState && fromState.params, params)
  
      const toState = StateWithParams(state, params, currentPathQuery, diff)
  
      if (preventTransition(fromState, toState, diff)) {
        if (transition && transition.exiting) cancelTransition()
        return
      }
  
      if (transition) cancelTransition()
  
      // While the transition is running, any code asking the router about the previous/current state should
      // get the end result state.
      previousState = currentState
      currentState = toState
      currentParamsDiff = diff
  
      transition = Transition(
        fromState,
        toState,
        diff,
        acc,
        router,
        logger
      )
  
      startingTransition(fromState, toState)
  
      // In case of a redirect() called from 'startingTransition', the transition already ended.
      if (transition) transition.run()
  
      // In case of a redirect() called from the transition itself, the transition already ended
      if (transition) {
        if (transition.cancelled) currentState = fromState
        else endingTransition(fromState, toState)
      }
  
      transition = null
    }
  
    function cancelTransition() {
      logger.log('Cancelling existing transition from {0} to {1}',
        transition.from, transition.to)
  
      transition.cancel()
  
      firstTransition = false
    }
  
    function startingTransition(fromState, toState) {
      logger.log('Starting transition from {0} to {1}', fromState, toState)
  
      const from = fromState ? fromState.asPublic : null
      const to = toState.asPublic
  
      eventCallbacks.started && eventCallbacks.started(to, from)
    }
  
    function endingTransition(fromState, toState) {
      if (!urlChanged && !firstTransition) {
        logger.log('Updating URL: {0}', currentPathQuery)
        updateURLFromState(currentPathQuery, document.title, currentPathQuery)
      }
  
      firstTransition = false
  
      logger.log('Transition from {0} to {1} ended', fromState, toState)
  
      toState.state.lastParams = toState.params
  
      const from = fromState ? fromState.asPublic : null
      const to = toState.asPublic
      eventCallbacks.ended && eventCallbacks.ended(to, from)
    }
  
    function updateURLFromState(state, title, url) {
      if (isHashMode()) {
        ignoreNextURLChange = true
        location.hash = options.hashPrefix + url
      } 
      else if(!initialized) {
        history.replaceState(state, title, url)
      }
      else
        history.pushState(state, title, url)
    }
  
    /*
    * Return whether the passed state is the same as the current one
    * in which case the router can ignore the change.
    */
    function preventTransition(current, newState, diff) {
      if (!current) return false
  
      return (newState.state == current.state) && (Object.keys(diff.all).length == 0)
    }
  
    /*
    * The state wasn't found
    * Transition to the 'notFound' state if the developer specified it or else throw an error.
    */
    function notFound(state) {
      logger.log('State not found: {0}', state)
  
      if (options.notFound)
        return setState(leafStates[options.notFound], {})
      else throw new Error ('State "' + state + '" could not be found')
    }
  
    /*
    * Configure the router before its initialization.
    * The available options are:
    *   enableLogs: Whether (debug and error) console logs should be enabled. Defaults to false.
    *   interceptAnchors: Whether anchor mousedown/clicks should be intercepted and trigger a state change. Defaults to true.
    *   notFound: The State to enter when no state matching the current path query or name could be found. Defaults to null.
    *   urlSync: How should the router maintain the current state and the url in sync. Defaults to true (history API).
    *   hashPrefix: Customize the hash separator. Set to '!' in order to have a hashbang like '/#!/'. Defaults to empty string.
    */
    function configure(withOptions) {
      util.mergeObjects(options, withOptions)
      return router
    }
  
    /*
    * Initialize the router.
    * The router will immediately initiate a transition to, in order of priority:
    * 1) The init state passed as an argument
    * 2) The state captured by the current URL
    */
    function init(initState, initParams) {
      if (options.enableLogs || Router.log)
        Router.enableLogs()
  
      if (options.interceptAnchors)
        interceptAnchors(router)
  
      hashSlashString = '#' + options.hashPrefix + '/'
  
      logger.log('Router init')
  
      initStates()
      logStateTree()
  
      initState = (initState !== undefined) ? initState : urlPathQuery()
  
      logger.log('Initializing to state {0}', initState || '""')
      transitionTo(initState, initParams)
  
      listenToURLChanges()
  
      initialized = true
      return router
    }
  
    /*
    * Remove any possibility of side effect this router instance might cause.
    * Used for testing purposes where we keep reusing the same router instance.
    */
    function terminate() {
      window.onhashchange = null
      window.onpopstate = null
      options = util.copyObject(defaultOptions)
      logger.enabled = false
      logger.log = logger.error = util.noop
    }
  
    function listenToURLChanges() {
  
      function onURLChange(evt) {
        if (ignoreNextURLChange) {
          ignoreNextURLChange = false
          return
        }
  
        const newState = evt.state || urlPathQuery()
  
        logger.log('URL changed: {0}', newState)
        urlChanged = true
        setStateForPathQuery(newState)
      }
  
      window[isHashMode() ? 'onhashchange' : 'onpopstate'] = onURLChange
    }
  
    function initStates() {
      const stateArray = util.objectToArray(states)
  
      addDefaultStates(stateArray)
  
      eachRootState((name, state) => {
        state.init(router, name)
      })
  
      assertPathUniqueness(stateArray)
  
      leafStates = registerLeafStates(stateArray, {})
  
      assertNoAmbiguousPaths()
    }
  
    function assertPathUniqueness(states) {
      const paths = {}
  
      states.forEach(state => {
        if (paths[state.path]) {
          const fullPaths = states.map(s => s.fullPath() || 'empty')
          throw new Error('Two sibling states have the same path (' + fullPaths + ')')
        }
  
        paths[state.path] = 1
        assertPathUniqueness(state.children)
      })
    }
  
    function assertNoAmbiguousPaths() {
      const paths = {}
  
      for (var name in leafStates) {
        const path = util.normalizePathQuery(leafStates[name].fullPath())
        if (paths[path]) throw new Error('Ambiguous state paths: ' + path)
        paths[path] = 1
      }
    }
  
    function addDefaultStates(states) {
      states.forEach(state => {
        var children = util.objectToArray(state.states)
  
        // This is a parent state: Add a default state to it if there isn't already one
        if (children.length) {
          addDefaultStates(children)
  
          var hasDefaultState = children.reduce((result, state) => {
            return state.path == '' || result
          }, false)
  
          if (hasDefaultState) return
  
          var defaultState = State({ uri: '' })
          state.states._default_ = defaultState
        }
      })
    }
  
    function eachRootState(callback) {
      for (let name in states) callback(name, states[name])
    }
  
    function registerLeafStates(states, leafStates) {
      return states.reduce((leafStates, state) => {
        if (state.children.length)
          return registerLeafStates(state.children, leafStates)
        else {
          leafStates[state.fullName] = state
          state.paths = util.parsePaths(state.fullPath())
          return leafStates
        }
      }, leafStates)
    }
  
    /*
    * Request a programmatic state change.
    *
    * Two notations are supported:
    * transitionTo('my.target.state', {id: 33, filter: 'desc'})
    * transitionTo('target/33?filter=desc')
    */
    function transitionTo(pathQueryOrName) {
      const name = leafStates[pathQueryOrName]
      const params = (name ? arguments[1] : null) || {}
      const acc = name ? arguments[2] : arguments[1]
  
      logger.log('Changing state to {0}', pathQueryOrName || '""')
  
      urlChanged = false
  
      if (name)
        setStateByName(name, params, acc)
      else
        setStateForPathQuery(pathQueryOrName, acc)
    }
  
    /*
     * Replaces the current state's params in the history with new params.
     * The state is NOT exited/re-entered.
     */
    function replaceParams(newParams) {
      if (!currentState) return
  
      const newUri = router.link(currentState.state.fullName, newParams)
  
      currentState = StateWithParams(currentState.state, newParams, newUri)
  
      history.replaceState(newUri, document.title, newUri)
    }
  
    /*
    * Attempt to navigate to 'stateName' with its previous params or
    * fallback to the defaultParams parameter if the state was never entered.
    */
    function backTo(stateName, defaultParams, acc) {
      const params = leafStates[stateName].lastParams || defaultParams
      transitionTo(stateName, params, acc)
    }
  
    function setStateForPathQuery(pathQuery, acc) {
      let state, params, _state, _params
  
      currentPathQuery = util.normalizePathQuery(pathQuery)
  
      const pq = currentPathQuery.split('?')
      const path = pq[0]
      const query = pq[1]
      const paths = util.parsePaths(path)
      const queryParams = util.parseQueryParams(query)
  
      for (var name in leafStates) {
        _state = leafStates[name]
        _params = _state.matches(paths)
  
        if (_params) {
          state = _state
          params = util.mergeObjects(_params, queryParams)
          break
        }
      }
  
      if (state) setState(state, params, acc)
      else notFound(currentPathQuery)
    }
  
    function setStateByName(name, params, acc) {
      const state = leafStates[name]
  
      if (!state) return notFound(name)
  
      const pathQuery = interpolate(state, params)
      setStateForPathQuery(pathQuery, acc)
    }
  
    /*
    * Add a new root state to the router.
    * The name must be unique among root states.
    */
    function addState(name, state) {
      if (states[name])
        throw new Error('A state already exist in the router with the name ' + name)
  
      state = stateTree(state)
  
      states[name] = state
  
      // The router is already initialized: Hot patch this state in.
      if (initialized) {
        state.init(router, name)
        registerLeafStates([state], leafStates)
      }
  
      return router
    }
  
    /*
    * Read the path/query from the URL.
    */
    function urlPathQuery() {
      const hashSlash = location.href.indexOf(hashSlashString)
      let pathQuery
  
      if (hashSlash > -1)
        pathQuery = location.href.slice(hashSlash + hashSlashString.length)
      else if (isHashMode())
        pathQuery = '/'
      else
        pathQuery = (location.pathname + location.search).slice(1)
  
      return util.normalizePathQuery(pathQuery)
    }
  
    function isHashMode() {
      return options.urlSync == 'hash'
    }
  
    /*
    * Compute a link that can be used in anchors' href attributes
    * from a state name and a list of params, a.k.a reverse routing.
    */
    function link(stateName, params) {
      const state = leafStates[stateName]
      if (!state) throw new Error('Cannot find state ' + stateName)
  
      const interpolated = interpolate(state, params)
      const uri = util.normalizePathQuery(interpolated)
  
      return isHashMode()
        ? '#' + options.hashPrefix + uri
        : uri
    }
  
    function interpolate(state, params) {
      const encodedParams = {}
  
      for (let key in params) {
        if (params[key] !== undefined)
          encodedParams[key] = encodeURIComponent(params[key])
      }
  
      return state.interpolate(encodedParams)
    }
  
    /*
    * Returns an object representing the current state of the router.
    */
    function getCurrent() {
      return currentState && currentState.asPublic
    }
  
    /*
    * Returns an object representing the previous state of the router
    * or null if the router is still in its initial state.
    */
    function getPrevious() {
      return previousState && previousState.asPublic
    }
  
    /*
    * Returns the diff between the current params and the previous ones.
    */
    function getParamsDiff() {
      return currentParamsDiff
    }
  
    function allStatesRec(states, acc) {
      acc.push.apply(acc, states)
      states.forEach(state => allStatesRec(state.children, acc))
      return acc
    }
  
    function allStates() {
      return allStatesRec(util.objectToArray(states), [])
    }
  
    /*
    * Returns the state object that was built with the given options object or that has the given fullName.
    * Returns undefined if the state doesn't exist.
    */
    function findState(by) {
      const filterFn = (typeof by === 'object')
        ? state => by === state.options
        : state => by === state.fullName
  
      const state = allStates().filter(filterFn)[0]
      return state && state.asPublic
    }
  
    /*
    * Returns whether the router is executing its first transition.
    */
    function isFirstTransition() {
      return previousState == null
    }
  
    function on(eventName, cb) {
      eventCallbacks[eventName] = cb
      return router
    }
  
    function stateTrees(states) {
      return util.mapValues(states, stateTree)
    }
  
    /*
    * Creates an internal State object from a specification POJO.
    */
    function stateTree(state) {
      if (state.children) state.children = stateTrees(state.children)
      return State(state)
    }
  
    function logStateTree() {
      if (!logger.enabled) return
  
      function indent(level) {
        if (level == 0) return ''
        return new Array(2 + (level - 1) * 4).join(' ') + '── '
      }
  
      const stateTree = function(state) {
        const path = util.normalizePathQuery(state.fullPath())
        const pathStr = (state.children.length == 0)
          ? ' (@ path)'.replace('path', path)
          : ''
        const str = indent(state.parents.length) + state.name + pathStr + '\n'
        return str + state.children.map(stateTree).join('')
      }
  
      let msg = '\nState tree\n\n'
      msg += util.objectToArray(states).map(stateTree).join('')
      msg += '\n'
  
      logger.log(msg)
    }
  
  
    // Public methods
  
    router.configure = configure
    router.init = init
    router.transitionTo = transitionTo
    router.replaceParams = replaceParams
    router.backTo = backTo
    router.addState = addState
    router.link = link
    router.current = getCurrent
    router.previous = getPrevious
    router.findState = findState
    router.isFirstTransition = isFirstTransition
    router.paramsDiff = getParamsDiff
    router.options = options
    router.on = on
  
    // Used for testing purposes only
    router.urlPathQuery = urlPathQuery
    router.terminate = terminate
  
    util.mergeObjects(api, router)
  
    return router
  }
func_code_string: function Router(declarativeStates) {
    const router = {}
    const states = stateTrees(declarativeStates)
    const eventCallbacks = {}
  
    let options = util.copyObject(defaultOptions)
    let firstTransition = true
    let ignoreNextURLChange = false
    let currentPathQuery
    let currentParamsDiff = {}
    let currentState
    let previousState
    let transition
    let leafStates
    let urlChanged
    let initialized
    let hashSlashString
  
    /*
    * Setting a new state will start a transition from the current state to the target state.
    * A successful transition will result in the URL being changed.
    * A failed transition will leave the router in its current state.
    */
    function setState(state, params, acc) {
      const fromState = transition
        ? StateWithParams(transition.currentState, transition.toParams)
        : currentState
  
      const diff = util.objectDiff(fromState && fromState.params, params)
  
      const toState = StateWithParams(state, params, currentPathQuery, diff)
  
      if (preventTransition(fromState, toState, diff)) {
        if (transition && transition.exiting) cancelTransition()
        return
      }
  
      if (transition) cancelTransition()
  
      // While the transition is running, any code asking the router about the previous/current state should
      // get the end result state.
      previousState = currentState
      currentState = toState
      currentParamsDiff = diff
  
      transition = Transition(
        fromState,
        toState,
        diff,
        acc,
        router,
        logger
      )
  
      startingTransition(fromState, toState)
  
      // In case of a redirect() called from 'startingTransition', the transition already ended.
      if (transition) transition.run()
  
      // In case of a redirect() called from the transition itself, the transition already ended
      if (transition) {
        if (transition.cancelled) currentState = fromState
        else endingTransition(fromState, toState)
      }
  
      transition = null
    }
  
    function cancelTransition() {
      logger.log('Cancelling existing transition from {0} to {1}',
        transition.from, transition.to)
  
      transition.cancel()
  
      firstTransition = false
    }
  
    function startingTransition(fromState, toState) {
      logger.log('Starting transition from {0} to {1}', fromState, toState)
  
      const from = fromState ? fromState.asPublic : null
      const to = toState.asPublic
  
      eventCallbacks.started && eventCallbacks.started(to, from)
    }
  
    function endingTransition(fromState, toState) {
      if (!urlChanged && !firstTransition) {
        logger.log('Updating URL: {0}', currentPathQuery)
        updateURLFromState(currentPathQuery, document.title, currentPathQuery)
      }
  
      firstTransition = false
  
      logger.log('Transition from {0} to {1} ended', fromState, toState)
  
      toState.state.lastParams = toState.params
  
      const from = fromState ? fromState.asPublic : null
      const to = toState.asPublic
      eventCallbacks.ended && eventCallbacks.ended(to, from)
    }
  
    function updateURLFromState(state, title, url) {
      if (isHashMode()) {
        ignoreNextURLChange = true
        location.hash = options.hashPrefix + url
      } 
      else if(!initialized) {
        history.replaceState(state, title, url)
      }
      else
        history.pushState(state, title, url)
    }
  
    /*
    * Return whether the passed state is the same as the current one
    * in which case the router can ignore the change.
    */
    function preventTransition(current, newState, diff) {
      if (!current) return false
  
      return (newState.state == current.state) && (Object.keys(diff.all).length == 0)
    }
  
    /*
    * The state wasn't found
    * Transition to the 'notFound' state if the developer specified it or else throw an error.
    */
    function notFound(state) {
      logger.log('State not found: {0}', state)
  
      if (options.notFound)
        return setState(leafStates[options.notFound], {})
      else throw new Error ('State "' + state + '" could not be found')
    }
  
    /*
    * Configure the router before its initialization.
    * The available options are:
    *   enableLogs: Whether (debug and error) console logs should be enabled. Defaults to false.
    *   interceptAnchors: Whether anchor mousedown/clicks should be intercepted and trigger a state change. Defaults to true.
    *   notFound: The State to enter when no state matching the current path query or name could be found. Defaults to null.
    *   urlSync: How should the router maintain the current state and the url in sync. Defaults to true (history API).
    *   hashPrefix: Customize the hash separator. Set to '!' in order to have a hashbang like '/#!/'. Defaults to empty string.
    */
    function configure(withOptions) {
      util.mergeObjects(options, withOptions)
      return router
    }
  
    /*
    * Initialize the router.
    * The router will immediately initiate a transition to, in order of priority:
    * 1) The init state passed as an argument
    * 2) The state captured by the current URL
    */
    function init(initState, initParams) {
      if (options.enableLogs || Router.log)
        Router.enableLogs()
  
      if (options.interceptAnchors)
        interceptAnchors(router)
  
      hashSlashString = '#' + options.hashPrefix + '/'
  
      logger.log('Router init')
  
      initStates()
      logStateTree()
  
      initState = (initState !== undefined) ? initState : urlPathQuery()
  
      logger.log('Initializing to state {0}', initState || '""')
      transitionTo(initState, initParams)
  
      listenToURLChanges()
  
      initialized = true
      return router
    }
  
    /*
    * Remove any possibility of side effect this router instance might cause.
    * Used for testing purposes where we keep reusing the same router instance.
    */
    function terminate() {
      window.onhashchange = null
      window.onpopstate = null
      options = util.copyObject(defaultOptions)
      logger.enabled = false
      logger.log = logger.error = util.noop
    }
  
    function listenToURLChanges() {
  
      function onURLChange(evt) {
        if (ignoreNextURLChange) {
          ignoreNextURLChange = false
          return
        }
  
        const newState = evt.state || urlPathQuery()
  
        logger.log('URL changed: {0}', newState)
        urlChanged = true
        setStateForPathQuery(newState)
      }
  
      window[isHashMode() ? 'onhashchange' : 'onpopstate'] = onURLChange
    }
  
    function initStates() {
      const stateArray = util.objectToArray(states)
  
      addDefaultStates(stateArray)
  
      eachRootState((name, state) => {
        state.init(router, name)
      })
  
      assertPathUniqueness(stateArray)
  
      leafStates = registerLeafStates(stateArray, {})
  
      assertNoAmbiguousPaths()
    }
  
    function assertPathUniqueness(states) {
      const paths = {}
  
      states.forEach(state => {
        if (paths[state.path]) {
          const fullPaths = states.map(s => s.fullPath() || 'empty')
          throw new Error('Two sibling states have the same path (' + fullPaths + ')')
        }
  
        paths[state.path] = 1
        assertPathUniqueness(state.children)
      })
    }
  
    function assertNoAmbiguousPaths() {
      const paths = {}
  
      for (var name in leafStates) {
        const path = util.normalizePathQuery(leafStates[name].fullPath())
        if (paths[path]) throw new Error('Ambiguous state paths: ' + path)
        paths[path] = 1
      }
    }
  
    function addDefaultStates(states) {
      states.forEach(state => {
        var children = util.objectToArray(state.states)
  
        // This is a parent state: Add a default state to it if there isn't already one
        if (children.length) {
          addDefaultStates(children)
  
          var hasDefaultState = children.reduce((result, state) => {
            return state.path == '' || result
          }, false)
  
          if (hasDefaultState) return
  
          var defaultState = State({ uri: '' })
          state.states._default_ = defaultState
        }
      })
    }
  
    function eachRootState(callback) {
      for (let name in states) callback(name, states[name])
    }
  
    function registerLeafStates(states, leafStates) {
      return states.reduce((leafStates, state) => {
        if (state.children.length)
          return registerLeafStates(state.children, leafStates)
        else {
          leafStates[state.fullName] = state
          state.paths = util.parsePaths(state.fullPath())
          return leafStates
        }
      }, leafStates)
    }
  
    /*
    * Request a programmatic state change.
    *
    * Two notations are supported:
    * transitionTo('my.target.state', {id: 33, filter: 'desc'})
    * transitionTo('target/33?filter=desc')
    */
    function transitionTo(pathQueryOrName) {
      const name = leafStates[pathQueryOrName]
      const params = (name ? arguments[1] : null) || {}
      const acc = name ? arguments[2] : arguments[1]
  
      logger.log('Changing state to {0}', pathQueryOrName || '""')
  
      urlChanged = false
  
      if (name)
        setStateByName(name, params, acc)
      else
        setStateForPathQuery(pathQueryOrName, acc)
    }
  
    /*
     * Replaces the current state's params in the history with new params.
     * The state is NOT exited/re-entered.
     */
    function replaceParams(newParams) {
      if (!currentState) return
  
      const newUri = router.link(currentState.state.fullName, newParams)
  
      currentState = StateWithParams(currentState.state, newParams, newUri)
  
      history.replaceState(newUri, document.title, newUri)
    }
  
    /*
    * Attempt to navigate to 'stateName' with its previous params or
    * fallback to the defaultParams parameter if the state was never entered.
    */
    function backTo(stateName, defaultParams, acc) {
      const params = leafStates[stateName].lastParams || defaultParams
      transitionTo(stateName, params, acc)
    }
  
    function setStateForPathQuery(pathQuery, acc) {
      let state, params, _state, _params
  
      currentPathQuery = util.normalizePathQuery(pathQuery)
  
      const pq = currentPathQuery.split('?')
      const path = pq[0]
      const query = pq[1]
      const paths = util.parsePaths(path)
      const queryParams = util.parseQueryParams(query)
  
      for (var name in leafStates) {
        _state = leafStates[name]
        _params = _state.matches(paths)
  
        if (_params) {
          state = _state
          params = util.mergeObjects(_params, queryParams)
          break
        }
      }
  
      if (state) setState(state, params, acc)
      else notFound(currentPathQuery)
    }
  
    function setStateByName(name, params, acc) {
      const state = leafStates[name]
  
      if (!state) return notFound(name)
  
      const pathQuery = interpolate(state, params)
      setStateForPathQuery(pathQuery, acc)
    }
  
    /*
    * Add a new root state to the router.
    * The name must be unique among root states.
    */
    function addState(name, state) {
      if (states[name])
        throw new Error('A state already exist in the router with the name ' + name)
  
      state = stateTree(state)
  
      states[name] = state
  
      // The router is already initialized: Hot patch this state in.
      if (initialized) {
        state.init(router, name)
        registerLeafStates([state], leafStates)
      }
  
      return router
    }
  
    /*
    * Read the path/query from the URL.
    */
    function urlPathQuery() {
      const hashSlash = location.href.indexOf(hashSlashString)
      let pathQuery
  
      if (hashSlash > -1)
        pathQuery = location.href.slice(hashSlash + hashSlashString.length)
      else if (isHashMode())
        pathQuery = '/'
      else
        pathQuery = (location.pathname + location.search).slice(1)
  
      return util.normalizePathQuery(pathQuery)
    }
  
    function isHashMode() {
      return options.urlSync == 'hash'
    }
  
    /*
    * Compute a link that can be used in anchors' href attributes
    * from a state name and a list of params, a.k.a reverse routing.
    */
    function link(stateName, params) {
      const state = leafStates[stateName]
      if (!state) throw new Error('Cannot find state ' + stateName)
  
      const interpolated = interpolate(state, params)
      const uri = util.normalizePathQuery(interpolated)
  
      return isHashMode()
        ? '#' + options.hashPrefix + uri
        : uri
    }
  
    function interpolate(state, params) {
      const encodedParams = {}
  
      for (let key in params) {
        if (params[key] !== undefined)
          encodedParams[key] = encodeURIComponent(params[key])
      }
  
      return state.interpolate(encodedParams)
    }
  
    /*
    * Returns an object representing the current state of the router.
    */
    function getCurrent() {
      return currentState && currentState.asPublic
    }
  
    /*
    * Returns an object representing the previous state of the router
    * or null if the router is still in its initial state.
    */
    function getPrevious() {
      return previousState && previousState.asPublic
    }
  
    /*
    * Returns the diff between the current params and the previous ones.
    */
    function getParamsDiff() {
      return currentParamsDiff
    }
  
    function allStatesRec(states, acc) {
      acc.push.apply(acc, states)
      states.forEach(state => allStatesRec(state.children, acc))
      return acc
    }
  
    function allStates() {
      return allStatesRec(util.objectToArray(states), [])
    }
  
    /*
    * Returns the state object that was built with the given options object or that has the given fullName.
    * Returns undefined if the state doesn't exist.
    */
    function findState(by) {
      const filterFn = (typeof by === 'object')
        ? state => by === state.options
        : state => by === state.fullName
  
      const state = allStates().filter(filterFn)[0]
      return state && state.asPublic
    }
  
    /*
    * Returns whether the router is executing its first transition.
    */
    function isFirstTransition() {
      return previousState == null
    }
  
    function on(eventName, cb) {
      eventCallbacks[eventName] = cb
      return router
    }
  
    function stateTrees(states) {
      return util.mapValues(states, stateTree)
    }
  
    /*
    * Creates an internal State object from a specification POJO.
    */
    function stateTree(state) {
      if (state.children) state.children = stateTrees(state.children)
      return State(state)
    }
  
    function logStateTree() {
      if (!logger.enabled) return
  
      function indent(level) {
        if (level == 0) return ''
        return new Array(2 + (level - 1) * 4).join(' ') + '── '
      }
  
      const stateTree = function(state) {
        const path = util.normalizePathQuery(state.fullPath())
        const pathStr = (state.children.length == 0)
          ? ' (@ path)'.replace('path', path)
          : ''
        const str = indent(state.parents.length) + state.name + pathStr + '\n'
        return str + state.children.map(stateTree).join('')
      }
  
      let msg = '\nState tree\n\n'
      msg += util.objectToArray(states).map(stateTree).join('')
      msg += '\n'
  
      logger.log(msg)
    }
  
  
    // Public methods
  
    router.configure = configure
    router.init = init
    router.transitionTo = transitionTo
    router.replaceParams = replaceParams
    router.backTo = backTo
    router.addState = addState
    router.link = link
    router.current = getCurrent
    router.previous = getPrevious
    router.findState = findState
    router.isFirstTransition = isFirstTransition
    router.paramsDiff = getParamsDiff
    router.options = options
    router.on = on
  
    // Used for testing purposes only
    router.urlPathQuery = urlPathQuery
    router.terminate = terminate
  
    util.mergeObjects(api, router)
  
    return router
  }
func_documentation_string: /*
  Create a new Router instance, passing any state defined declaratively.
  More states can be added using addState().
  
  Because a router manages global state (the URL), only one instance of Router
  should be used inside an application.