npm install --save @playframe/routerimport {h, app, route, mount, Component} from '@playframe/playframe'
route({
  counter: 1,
  _: {
    inc: (e, state)=> state.counter++,
    dec: (e, state)=> state.counter--
  },
  routes: {
    '/': ({state})=> <a href="/hello/world"><h1>Link</h1></a>,
    '/counter': ({state})=> CounterView(state),
    '/hello/:name': ({state, param})=> <h1>Hello {param.name}!</h1>,
    '/*': ()=>  <h1>404</h1>
  }
})(
  mount(document.body)
)doc = @document
{isArray} = Array
NAMED = Symbol 'NAMED'
WILDCARD = Symbol 'WILD'
LEAF = Symbol 'LEAF'
forest = new WeakMap
_sync = null
_state = null
_href = null
_props = null
_Page = null
_subscribed = false
_404 = => 404
_fallback = => _Page = _404
module.exports = view = (sync)=> _sync = sync; (state, l)=>
  _state = state
  {href} = l or= location
  if not _subscribed and doc
    do subscribe
    _subscribed = true
  unless href is _href
    set_Page l
  do add_methods unless state._.push
  _props.state = state
  _Page _props
add_methods = =>
  hop = => _sync.next =>
    set_Page location
    _state._ {}
    _sync.frame => scrollTo 0, 0
    return
  _state._.push = (href)=>
    history.replaceState {height: document.body.clientHeight}, ''
    history.pushState {}, '', href
    do hop
    return
  _state._.replace = (href)=>
    history.replaceState {}, '', href
    do hop
    return
  
  return
subscribe = =>
  doc.addEventListener(
    if doc.ontouchstart then 'touchstart' else 'click'
    (event)=>
      el = event.composedPath?()[0] or event.target
      el = el.parentNode while el and el.nodeName isnt 'A'
      return if not el or event.button isnt 0 or
        event.metaKey or event.altKey or event.ctrlKey or event.shiftKey or
        el.target is '_blank' or el.origin isnt location.origin or
        el.getAttribute('href').startsWith('#')
      event.preventDefault()
      _state._.push el.href
  )
  addEventListener 'popstate', ({state})=>
    {style} = doc.body
    style.minHeight = "#{state?.height}px"
    set_Page location
    _state._ {}
    setTimeout (=> _sync.render => style.minHeight = ''), 1000
  
  return
set_Page = (l)=>
  {href, pathname, search, hash, query} = l
  _href = href
  unless query
    query = {}
    for pair, i in search.slice(1).split '&'
      [k, v] = pair.split '='
      query[k] = v
  match get_trie(_state.routes), pathname
  _props = {_props..., pathname, search, query, hash}
  return
get_trie = (routes)=>
  unless trie = forest.get routes
    forest.set routes, trie = make_trie routes
  trie
match = (trie, pathname)=>
  path = pathname.split '/'
  param = {}
  walk_trie param, trie, path, 1, _fallback
walk_trie = (param, trie, path, position, fallback)=>
  fallback = trie[WILDCARD] or fallback
  if step = path[position]
    if sub_trie = trie[step]
      walk_trie param, sub_trie, path, position + 1, fallback
    else if named = trie[NAMED]
      named param, trie, path, position,
        => fallback param, trie, path, position
    else
      fallback param, trie, path, position
  else if Page = trie[LEAF]
    _props = {param}
    _Page = Page
  else
    fallback param, trie, path, position
  return
make_trie = (routes)=>
  trie = {}
  for k, v of routes
    route = k.split '/'
    pos = 0
    if route[1]? and not route[0]
      pos = 1
    if typeof v is 'object'
      # Nested router
      Object.assign grow_trie(trie, route, pos), get_trie v
    else # function Page
      grow_trie trie, route, pos, v
  trie
grow_trie = (trie, route, position, Page)=>
  if step = route[position]
    first_char = step.charCodeAt 0
    if first_char is 58 # starts with `:`
      name = step.slice 1
      named_trie = trie[NAMED] or= (param, trie, path, position, fallback)=>
        fallback = trie[WILDCARD] or fallback
        param = {param...}
        param[name] = path[position]
        walk_trie param, named_trie, path, position + 1, fallback
        return
      grow_trie named_trie, route, position + 1, Page
    else if first_char is 42 # starts with `*`
      trie[WILDCARD] = (param, trie, path, position)=>
        wild = path.slice position
        _props = {param, wild}
        _Page = Page
        return
      trie
    else
      grow_trie (trie[step] or= {}), route, position + 1, Page
  else
    trie[LEAF] = Page if Page
    trie