import { combine } from "./combine" ; import { getCues } from "./getCues" ; import { requireExactMatch } from "./isElementMatch" ; import { Cue , CueSet } from "./types" ; type GenerateRelativeCueSets = { exactMatchOnly : boolean ; level : number ; relative : HTMLElement ; targetCues : Cue [ ] ; twoTargetCueSets : Cue [ ] [ ] ; } ; export function buildCueSet ( cues ) { const distance = Math . max ( ... cues . map ( ( a ) => Math . abs ( a . level ) ) ) ; const penalty = cues . reduce ( ( a , b ) => a + b . penalty , 0 ) ; const valueLength = cues . reduce ( ( a , b ) => a + b . value . length , 0 ) ; const distanceFactor = 1 + Math . log ( Math . max ( distance - 3 , 1 ) ) ; return { cues , penalty : penalty * distanceFactor , valueLength , } ; } export function compareCueSet ( a , b ) { const value = a . penalty - b . penalty ; if ( value !== 0 ) return value ; return a . valueLength - b . valueLength ; } export function * generateCueSets ( target ) { let targetCues = getCues ( target , 0 ) ; yield * targetCues . map ( ( cue ) => buildCueSet ( [ cue ] ) ) as any ; targetCues = targetCues . filter ( ( c ) => c . type !== "text" ) ; const twoTargetCueSets = combine ( targetCues , 2 ) ; yield * twoTargetCueSets . map ( buildCueSet ) as any ; const threeTargetCues = combine ( targetCues , 3 ) ; yield * threeTargetCues . map ( buildCueSet ) as any ; const exactMatchOnly = requireExactMatch ( target ) ; const descendants = target . querySelectorAll ( "*" ) ; let level = 1 ; let ancestor = target . parentElement ; while ( level - 1 < descendants . length || ancestor ) { if ( ancestor ) { yield * generateRelativeCueSets ( { exactMatchOnly , level : level * - 1 , relative : ancestor , targetCues , twoTargetCueSets , } ) ; } const descendant = descendants [ level - 1 ] as HTMLElement ; if ( descendant ) { yield * generateRelativeCueSets ( { exactMatchOnly , level , relative : descendant , targetCues , twoTargetCueSets , } ) ; } if ( ancestor ) ancestor = ancestor . parentElement ; level += 1 ; } } export function * generateRelativeCueSets ( { exactMatchOnly , level , relative , targetCues , twoTargetCueSets , } ) { const relativeCues = getCues ( relative , level ) ; if ( ! relativeCues . length ) return ; for ( let i = 0 ; i < relativeCues . length ; i ++ ) { const relativeCue = relativeCues [ i ] ; yield buildCueSet ( [ relativeCue ] ) ; for ( let j = 0 ; j < targetCues . length ; j ++ ) { yield buildCueSet ( [ relativeCue , targetCues [ j ] ] ) ; } for ( let j = 0 ; j < twoTargetCueSets . length ; j ++ ) { yield buildCueSet ( [ relativeCue , ... twoTargetCueSets [ j ] ] ) ; } } const twoRelativeCues = combine ( relativeCues , 2 ) ; for ( let i = 0 ; i < twoRelativeCues . length ; i ++ ) { if ( ! exactMatchOnly ) { yield buildCueSet ( twoRelativeCues [ i ] ) ; } for ( let j = 0 ; j < targetCues . length ; j ++ ) { yield buildCueSet ( [ ... twoRelativeCues [ i ] , targetCues [ j ] ] ) ; } } if ( ! exactMatchOnly ) { const threeRelativeCues = combine ( relativeCues , 3 ) ; for ( let i = 0 ; i < threeRelativeCues . length ; i ++ ) { yield buildCueSet ( threeRelativeCues [ i ] ) ; } } } export function * generateSortedCueSets ( target , timePerBatch = 30 ) { let start = Date . now ( ) ; const generator = generateCueSets ( target ) ; const yieldedHashes = new Set < string > ( ) ; let testAttributeCue = null ; function prepareBatch ( ) { const prepared = [ ] ; batch . sort ( compareCueSet ) . forEach ( ( cueSet ) => { if ( testAttributeCue && ! cueSet . cues . some ( ( c ) => c . penalty === 0 ) ) { cueSet . cues . unshift ( testAttributeCue ) ; } const hash = cueSet . cues . map ( ( c ) => c . value ) . sort ( ) . join ( "," ) ; if ( yieldedHashes . has ( hash ) ) return ; yieldedHashes . add ( hash ) ; prepared . push ( cueSet ) ; } ) ; batch = [ ] ; return prepared ; } let batch = [ ] ; for ( const cueSet of generator ) { if ( ! cueSet ) continue ; batch . push ( cueSet ) ; cueSet . cues . filter ( ( c ) => c . penalty === 0 && c . level <= 0 ) . forEach ( ( cue ) => { if ( ! testAttributeCue || cue . level > testAttributeCue . level ) { testAttributeCue = cue ; } } ) ; if ( timePerBatch > 0 && Date . now ( ) - start >= timePerBatch ) { const batchToYield = prepareBatch ( ) ; yield * batchToYield as any ; start = Date . now ( ) ; } } if ( batch . length ) { const batchToYield = prepareBatch ( ) ; yield * batchToYield as any ; } }