import { get as getDistance } from 'fast-levenshtein' ; import { State , Token , Article , Store , GeneratorConfig , getDefaults , ThemeStyle , } from '../useStore' ; import { fetchArticles } from './fetchArticles' ; import { buildResult } from './buildResult' ; import produce from 'immer' ; import { PartialState } from 'zustand' ; /** How many articles to keep in the queue */ const MAX_QUEUE_DEPTH = 1 ; class Actions { set : ( state : PartialState < Store > ) => void ; get : ( ) => Store ; constructor ( set , get ) { this . set = set ; this . get = get ; } example update = ( fn ) : void => { return this . set ( produce ( fn ) ) ; } ; /**
     * Start a new session. Cancels the current one, if there is one.
     */ startNewSession = async ( options ? ) : Promise < void > => { let { state } = this . get ( ) ; if ( ! options || ! options . force ) { if ( state . session . status === 'ready' ) return ; } this . update ( ( { state } ) => { resetSession ( state ) ; popArticleQueue ( state ) ; } ) ; state = this . get ( ) . state ; const needed = MAX_QUEUE_DEPTH + 1 - state . articleQueue . length ; return fetchArticles ( needed , state . generator ) . then ( ( articles ) => { this . receiveArticles ( articles ) ; } ) ; } ; receiveArticles = ( articles ) => { this . update ( ( { state } ) => { if ( hasArticle ( state ) ) { state . articleQueue = articles ; } else { const [ article , ... tail ] = articles ; state . articleQueue = tail ; loadArticle ( state , article ) ; } } ) ; } ; setInputValue = ( value ) : void => { this . update ( ( { state } ) => setInputValue ( state , value ) ) ; } ; inputWhitespace = async ( ) : Promise < void > => { let action ; this . update ( ( { state } ) => { action = inputWhitespace ( state ) ; } ) ; if ( action ?. done ) { this . update ( ( { state } ) => generateResults ( state ) ) ; return this . startNewSession ( ) ; } } ; setGeneratorConfig = ( generator ? ) => { this . update ( ( { state } ) => { state . generator = generator ?? getDefaults ( ) . generator ; state . articleQueue = [ ] ; } ) ; } ; updatePreferences = ( fn ) => { this . update ( ( { state } ) => { fn ( state . preferences ) ; } ) ; } ; setThemeStyle = ( value ) => { this . update ( ( { state } ) => { state . preferences . themeStyle = value ; } ) ; } ; } /**
 * Inputs a whitespace; check if done afterwards
 */ export function inputWhitespace ( state ) { if ( state . session . status !== 'ongoing' ) return ; const tokens = state . article . tokens ; const index = state . currentInput . tokenIndex ; const token = tokens [ index ] ; const currentValue = state . currentInput . value ; if ( currentValue === '' ) { state . currentInput . charIndex = 0 ; return ; } const mistakes = getInaccuracyCount ( token . value , currentValue ) ; state . currentInput . finishedTokens [ index ] = { value : currentValue , mistakes , } ; let nextIndex = index + 1 , nextToken = tokens [ nextIndex ] ; while ( nextToken ?. type === 'whitespace' ) { state . currentInput . finishedTokens [ nextIndex ] = { value : ' ' , mistakes : 0 , } ; nextIndex += 1 ; nextToken = tokens [ nextIndex ] ; } if ( nextIndex >= tokens . length ) { return { done : true } ; } else { state . currentInput . tokenIndex = nextIndex ; state . currentInput . charIndex = 0 ; state . currentInput . value = '' ; state . currentInput . isAccurate = true ; buildInterimResult ( state ) ; } } function buildInterimResult ( state ) { if ( state . session . status !== 'ongoing' ) return ; const tokens = state . article . tokens ; const durationMs = + new Date ( ) - + state . session . startedAt ; const nextIndex = state . currentInput . tokenIndex + 1 ; state . interimResult = buildResult ( { isInterim : true , durationMs , tokens : tokens . slice ( 0 , nextIndex ) , finishedTokens : state . currentInput . finishedTokens . slice ( 0 , nextIndex ) , } ) ; } /**
 * Generates results.
 */ export function generateResults ( state ) { if ( state . session . status !== 'ongoing' || ! state . session . startedAt ) { throw new Error ( 'Invariant violation' ) ; } const now = + new Date ( ) ; const durationMs = now - + state . session . startedAt ; const result = buildResult ( { durationMs , tokens : state . article . tokens , finishedTokens : state . currentInput . finishedTokens , } ) ; state . results . push ( result ) ; } /**
 * Resets the current session to the 'pending' state.
 */ export function resetSession ( state ) { state . session = { status : 'pending' } ; state . interimResult = null ; state . currentInput = { tokenIndex : 0 , charIndex : 0 , value : '' , isAccurate : true , finishedTokens : [ ] , } ; state . article . tokens = [ ] ; } /**
 * Returns the current token.
 */ function getCurrentToken ( state ) { return state . article . tokens [ state . currentInput . tokenIndex ] ; } /**
 * Updates an input value.
 */ export function setInputValue ( state , value ) { const token = getCurrentToken ( state ) ; if ( state . session . status === 'ready' ) { state . session = { status : 'ongoing' , startedAt : new Date ( ) } ; } if ( token ?. value ?. substr ( 0 , value . length ) !== value ) { state . currentInput . isAccurate = false ; } else { state . currentInput . isAccurate = true ; } state . currentInput . value = value ; state . currentInput . charIndex = value . length ; } /**
 * Pick an article from the queue, if there are any queued.
 */ export function popArticleQueue ( state ) { if ( ! state . articleQueue . length ) return ; if ( state . session . status !== 'pending' ) return ; const article = state . articleQueue . shift ( ) ; if ( ! article ) return ; loadArticle ( state , article ) ; } /**
 * Load an article as the current one
 */ function loadArticle ( state , article ) { resetSession ( state ) ; state . session = { status : 'ready' } ; state . article = article ; } function hasArticle ( state ) { return Boolean ( state . article ?. tokens ?. length ) ; } function getInaccuracyCount ( expected , actual ) { if ( expected === actual ) return 0 ; return Math . min ( getDistance ( expected , actual ) , expected . length ) ; } export default Actions ;