import { Contactable , ContactableConfig , ContactableSpecifyInit , Statement , PairwiseQualified , PairwiseChoice , PairwiseQuantified , PairwiseVote } from 'rsf-types' ; import * as moment from 'moment' ; const DEFAULT_ALL_COMPLETED_TEXT = `Everyone has completed. Thanks for participating.` ; const DEFAULT_TIMEOUT_TEXT = `The max time has been reached. Stopping now. Thanks for participating.` ; const DEFAULT_INVALID_RESPONSE_TEXT = `That's not a valid response, please try again.` ; const DEFAULT_MAX_RESPONSES_TEXT = `You've responded to everything. Thanks for participating. You will be notified when everyone has completed.` ; const rulesText = ( maxTime ) => `The process will stop automatically after ${ moment . duration ( maxTime , 'seconds' ) . humanize ( ) }.` ; const whichToInit = ( contactableConfigs ) : ContactableSpecifyInit => { const specifyDefault = { doTelegram : false , doMattermost : false , doSms : false } ; return contactableConfigs . reduce ( ( memo , value ) => { const uppercased = value . type . charAt ( 0 ) . toUpperCase ( ) + value . type . slice ( 1 ) ; memo [ `do${ uppercased }` ] = true ; return memo ; } , specifyDefault ) ; } ; const timer = ( ms ) : Promise < void > => new Promise ( resolve => { setTimeout ( resolve , ms ) ; } ) ; interface CollectResults < T > { timeoutComplete : boolean ; results : T [ ] ; } type ValidateFn = ( msg : string ) => boolean ; type FormatPairwiseFn = ( numPerPerson : number , numSoFar : number , pairwiseChoice : PairwiseChoice ) => string ; type PairwiseResultFn < T > = ( msg : string , personalResultsSoFar : T [ ] , contactable : Contactable , pairsTexts : PairwiseChoice [ ] ) => T ; type ConvertToResultFn < T > = ( msg : string , personalResultsSoFar : T [ ] , contactable : Contactable ) => T ; type OnInvalidFn = ( msg : string , contactable : Contactable ) => void ; type IsPersonalCompleteFn < T > = ( personalResultsSoFar : T [ ] ) => boolean ; type OnPersonalCompleteFn < T > = ( personalResultsSoFar : T [ ] , contactable : Contactable ) => void ; type OnResultFn < T > = ( result : T , personalResultsSoFar : T [ ] , contactable : Contactable ) => void ; type IsTotalCompleteFn < T > = ( allResultsSoFar : T [ ] ) => boolean ; const collectFromContactables = async < T > ( contactables , maxTime , validate , onInvalid , isPersonalComplete , onPersonalComplete , convertToResult , onResult , isTotalComplete ) : Promise < CollectResults < T > > => { return new Promise ( resolve => { const results = [ ] ; const timeoutId = setTimeout ( ( ) => { contactables . forEach ( contactable => contactable . stopListening ( ) ) ; resolve ( { timeoutComplete : true , results } ) ; } , maxTime * 1000 ) ; contactables . forEach ( contactable => { const personalResults = [ ] ; contactable . listen ( ( text ) => { const personalComplete = isPersonalComplete ( personalResults ) ; if ( ! personalComplete ) { if ( ! validate ( text ) ) { onInvalid ( text , contactable ) ; return ; } const newResult = convertToResult ( text , personalResults , contactable ) ; personalResults . push ( newResult ) ; results . push ( newResult ) ; onResult ( { ... newResult } , personalResults , contactable ) ; } if ( isPersonalComplete ( personalResults ) ) { onPersonalComplete ( personalResults , contactable ) ; contactable . stopListening ( ) ; } if ( isTotalComplete ( results ) ) { clearTimeout ( timeoutId ) ; contactables . forEach ( contactable => contactable . stopListening ( ) ) ; resolve ( { timeoutComplete : false , results } ) ; } } ) ; } ) ; } ) ; } ; const formatPairwiseChoice = ( numPerPerson , numSoFar , pairwiseChoice ) : string => { return `(${ numPerPerson - 1 - numSoFar } more remaining)
0) ${ pairwiseChoice [ 0 ] . text }
1) ${ pairwiseChoice [ 1 ] . text }` ; } ; const formatPairwiseList = ( description , key , pairwiseList , anonymize ) : string => { return pairwiseList . reduce ( ( memo , el ) => { return ( `${ memo }
0) ${ el . choices [ 0 ] . text }
1) ${ el . choices [ 1 ] . text }
${ description }: ${ el [ key ] }` + ( anonymize || ! el . contact ? '' : ` : ${ el . contact . id }@${ el . contact . type }` ) ) ; } , '' ) ; } ; const genericPairwise = async < T > ( contactables , statements , contextMsg , maxTime , eachCb , validate , convertToPairwiseResult , maxResponsesText = DEFAULT_MAX_RESPONSES_TEXT , allCompletedText = DEFAULT_ALL_COMPLETED_TEXT , timeoutText = DEFAULT_TIMEOUT_TEXT , invalidResponseText = DEFAULT_INVALID_RESPONSE_TEXT , speechDelay = 500 ) : Promise < T [ ] > => { const pairsTexts = [ ] ; statements . forEach ( ( statement , index ) => { for ( let i = index + 1 ; i < statements . length ; i ++ ) { let pairedStatement = statements [ i ] ; pairsTexts . push ( { 0 : statement , 1 : pairedStatement } ) ; } } ) ; contactables . forEach ( async ( contactable ) : Promise < void > => { await contactable . speak ( rulesText ( maxTime ) ) ; await timer ( speechDelay ) ; await contactable . speak ( contextMsg ) ; if ( statements . length ) { await timer ( speechDelay ) ; const first = formatPairwiseChoice ( pairsTexts . length , 0 , pairsTexts [ 0 ] ) ; await contactable . speak ( first ) ; } } ) ; const onInvalid = ( msg , contactable ) : void => { contactable . speak ( invalidResponseText ) ; } ; const isPersonalComplete = ( personalResultsSoFar ) : boolean => { return personalResultsSoFar . length === pairsTexts . length ; } ; const onPersonalComplete = ( personalResultsSoFar , contactable ) : void => { contactable . speak ( maxResponsesText ) ; } ; const onResult = ( el , personalResultsSoFar , contactable ) : void => { const responsesSoFar = personalResultsSoFar . length ; if ( pairsTexts [ responsesSoFar ] ) { const nextPair = pairsTexts [ responsesSoFar ] ; const nextPairFormatted = formatPairwiseChoice ( pairsTexts . length , responsesSoFar , nextPair ) ; contactable . speak ( nextPairFormatted ) ; } eachCb ( el ) ; } ; const isTotalComplete = ( allResultsSoFar ) : boolean => { return allResultsSoFar . length === contactables . length * pairsTexts . length ; } ; const convertToResult = ( msg , personalResultsSoFar , contactable ) : T => { return convertToPairwiseResult ( msg , personalResultsSoFar , contactable , pairsTexts ) ; } ; const collectResults = await collectFromContactables < T > ( contactables , maxTime , validate , onInvalid , isPersonalComplete , onPersonalComplete , convertToResult , onResult , isTotalComplete ) ; const { timeoutComplete , results } = collectResults ; const closeFlowText = timeoutComplete ? timeoutText : allCompletedText ; await Promise . all ( contactables . map ( ( contactable ) : Promise < void > => contactable . speak ( closeFlowText ) ) ) ; return results ; } ; export { DEFAULT_ALL_COMPLETED_TEXT , DEFAULT_INVALID_RESPONSE_TEXT , DEFAULT_MAX_RESPONSES_TEXT , DEFAULT_TIMEOUT_TEXT , rulesText , whichToInit , timer , formatPairwiseList , collectFromContactables , genericPairwise , CollectResults , ValidateFn , FormatPairwiseFn , PairwiseResultFn } ;