import each from '@tinkoff/utils/array/each' ; import find from '@tinkoff/utils/array/find' ; import findIndex from '@tinkoff/utils/array/findIndex' ; import type { Route , NavigationRoute } from '../types' ; import { getParts , parse } from './utils' ; import { PartType } from './types' ; import { HISTORY_FALLBACK_REGEXP } from './constants' ; type Parameterized = { key : string ; paramName : string ; regexp ? : RegExp ; optional ? : boolean ; tree : Tree ; } ; interface Tree { route ? : Route ; children : { [ part ] : Tree ; } ; parameters : Array < Parameterized > ; wildcardRoute ? : Route ; historyFallbackRoute ? : Route ; } const createTree = ( route ? ) : Tree => { return { route , children : Object . create ( null ) , parameters : [ ] , } ; } ; const createNavigationRoute = ( route , pathname , params ? ) : NavigationRoute => { return { ... route , actualPath : pathname , params : params ?? { } , } ; } ; export class RouteTree { private tree : Tree ; constructor ( routes ? ) { this . tree = createTree ( ) ; each ( ( route ) => this . addRoute ( route ) , routes ) ; } addRoute ( route ) { const parts = getParts ( route . path ) ; let currentTree = this . tree ; for ( let i = 0 ; i < parts . length ; i ++ ) { const part = parts [ i ] ; const parsed = parse ( part ) ; if ( parsed . type === PartType . historyFallback ) { currentTree . historyFallbackRoute = route ; return ; } if ( parsed . type === PartType . wildcard ) { currentTree . wildcardRoute = route ; return ; } if ( parsed . type === PartType . parameter ) { const { paramName , regexp , optional } = parsed ; const found = find ( ( par ) => par . key === part , currentTree . parameters ) ; if ( found ) { currentTree = found . tree ; } else { const parameter = { key : part , paramName , regexp , optional , tree : createTree ( ) , } ; if ( regexp && ! optional ) { const index = findIndex ( ( par ) => ! par . regexp , currentTree . parameters ) ; currentTree . parameters . splice ( index , 0 , parameter ) ; } else { currentTree . parameters . push ( parameter ) ; } currentTree = parameter . tree ; } } else { if ( ! currentTree . children [ part ] ) { currentTree . children [ part ] = createTree ( ) ; } currentTree = currentTree . children [ part ] ; } } currentTree . route = route ; } getRoute ( pathname ) { return this . findRoute ( pathname , 'route' ) ; } getWildcard ( pathname ) { return this . findRoute ( pathname , 'wildcardRoute' ) ; } getHistoryFallback ( pathname ) { const route = this . findRoute ( pathname , 'historyFallbackRoute' ) ; return ( route && { ... route , actualPath : route . path . replace ( HISTORY_FALLBACK_REGEXP , '' ) || '/' , } ) ; } private findRoute ( pathname , propertyName ) { const exactMatch = propertyName === 'route' ; const parts = getParts ( pathname ) ; const queue = [ [ this . tree , 0 ] , ] ; while ( queue . length ) { const [ currentTree , index , params ] = queue . pop ( ) ; const { children , parameters } = currentTree ; let optionalOnly = false ; if ( index >= parts . length ) { if ( currentTree [ propertyName ] ) { return createNavigationRoute ( currentTree [ propertyName ] , pathname , params ) ; } optionalOnly = true ; } if ( ! exactMatch && currentTree [ propertyName ] ) { queue . push ( [ currentTree , parts . length , params ] ) ; } const part = parts [ index ] ; const child = children [ part ] ; for ( const param of parameters ) { const { optional , tree } = param ; if ( optional ) { queue . push ( [ tree , index , params ] ) ; } } if ( optionalOnly ) { continue ; } for ( let i = parameters . length - 1 ; i >= 0 ; i -- ) { const param = parameters [ i ] ; const { paramName , tree , regexp } = param ; const match = regexp ?. exec ( part ) ; const paramValue = regexp ? match ?. [ 1 ] : part ; if ( paramValue ) { queue . push ( [ tree , index + 1 , { ... params , [ paramName ] : paramValue } ] ) ; } } if ( child ) { queue . push ( [ child , index + 1 , params ] ) ; } } } }