Source: Router.js

import EventHandler from './EventHandler.js'

/**
Router maps [`window.history`](https://developer.mozilla.org/en-US/docs/Web/API/Window/history) events and URL path fragments (the part after the `#` in https://example.com/path#one-two-three) to events.

This is usually used by an {@link App} to show and hide {@link Component}s based on the URL.

@example <caption>routing `/^blog\/([0-9]+)\/app\/([0-9a-z]+)$/` to an event with parameters for blog and app IDs</caption>
let router = new Router()

// Set up a couple of routes, each with a URL matching regexs and a route name

// matches http://<domain>/<path> and http://<domain>/<path>#
router.addRoute(/^$/, 'default')

// matches http://<domain>/<path>#blog/1123/app/abc-123
router.addRoute(/^blog\/([0-9]+)\/app\/([0-9a-z\-]+)$/, 'blog-app')

// Listen for the route
router.addListener('blog-app', (routeName, hash, ...regexMatches, ...parameters) => {
	// If this was an event triggered by routing to #blog/1123/app/abc-123 then:
	// `routeName` would be 'blog-app'
	// `hash` would be 'blog/1123/app/abc-123'
	// `regexMatches` would be ['1123', 'abc-123']
	// `parameters` is empty in this example but could be any number of extra items in the route (see `addRoute`)
})

// Start must be called in order to begin routing
router.start()
*/
const Router = class extends EventHandler {
	constructor() {
		super()
		/** @type {boolean} */
		this.cleanedUp = false
		/** @type {Array<Router>} */
		this.routes = []
		this._checkHash = this._checkHash.bind(this)
		window.addEventListener('hashchange', this._checkHash, false)
	}
	cleanup() {
		if (this.cleanedUp) return
		this.cleanedUp = true
		window.removeEventListener('hashchange', this._checkHash)
		super.cleanup()
	}
	/**
	@param {RegExp} regex - The regular expression that matches the incoming hash changes
	@param {string} eventName - The event name used when triggering
	@param {...*} parameters - Parameters passed without modification to listeners after the event name and matches
	*/
	addRoute(regex, eventName, ...parameters) {
		const route = new Route(regex, eventName, ...parameters)
		this.routes.push(route)
		this.trigger(Router.RouteAddedEvent, this, route)
	}
	/**
	This must be called in order to start routing.
	*/
	start() {
		this._checkHash()
		// This event is triggered after the hash check (and its subsequent event) so that listeners are notified when in a fully started state
		this.trigger(Router.StartedRoutingEvent, this)
	}
	_checkHash() {
		this._handleNewPath(document.location.hash.slice(1))
	}
	_handleNewPath(path) {
		for (const route of this.routes) {
			const matches = route.matches(path)
			if (matches == null) {
				continue
			}
			this.trigger(route.eventName, ...matches, ...route.parameters)
			return
		}
		this.trigger(Router.UnknownRouteEvent, path)
	}
}

Router.RouteAddedEvent = Symbol('route-added')
Router.StartedRoutingEvent = Symbol('started-routing')
Router.UnknownRouteEvent = Symbol('unknown-route')

/*
	Route tracks URL routes for {@link Router}
*/
const Route = class {
	constructor(regex, eventName, ...parameters) {
		this.regex = regex
		this.eventName = eventName
		this.parameters = parameters
	}
	/**
	@return {boolean} true if this route matches a given path
	*/
	matches(path) {
		return path.match(this.regex)
	}
}

export default Router
export { Router, Route }