import type { TokenizerDepsRuntimeOptions } from '../../core/services/tokenizer'; type TokenizerMainDeps = TokenizerDepsRuntimeOptions & { getJlptEnabled: NonNullable; getFrequencyDictionaryEnabled: NonNullable< TokenizerDepsRuntimeOptions['getFrequencyDictionaryEnabled'] >; getFrequencyDictionaryMatchMode: NonNullable< TokenizerDepsRuntimeOptions['getFrequencyDictionaryMatchMode'] >; getFrequencyRank: NonNullable; getMinSentenceWordsForNPlusOne: NonNullable< TokenizerDepsRuntimeOptions['getMinSentenceWordsForNPlusOne'] >; getYomitanGroupDebugEnabled: NonNullable< TokenizerDepsRuntimeOptions['getYomitanGroupDebugEnabled'] >; recordLookup: (hit: boolean) => void; }; export function createBuildTokenizerDepsMainHandler(deps: TokenizerMainDeps) { return (): TokenizerDepsRuntimeOptions => ({ getYomitanExt: () => deps.getYomitanExt(), getYomitanParserWindow: () => deps.getYomitanParserWindow(), setYomitanParserWindow: (window) => deps.setYomitanParserWindow(window), getYomitanParserReadyPromise: () => deps.getYomitanParserReadyPromise(), setYomitanParserReadyPromise: (promise: Promise | null) => deps.setYomitanParserReadyPromise(promise), getYomitanParserInitPromise: () => deps.getYomitanParserInitPromise(), setYomitanParserInitPromise: (promise: Promise | null) => deps.setYomitanParserInitPromise(promise), isKnownWord: (text: string) => { const hit = deps.isKnownWord(text); deps.recordLookup(hit); return hit; }, getKnownWordMatchMode: () => deps.getKnownWordMatchMode(), ...(deps.getNPlusOneEnabled ? { getNPlusOneEnabled: () => deps.getNPlusOneEnabled!(), } : {}), getMinSentenceWordsForNPlusOne: () => deps.getMinSentenceWordsForNPlusOne(), getJlptLevel: (text: string) => deps.getJlptLevel(text), getJlptEnabled: () => deps.getJlptEnabled(), getFrequencyDictionaryEnabled: () => deps.getFrequencyDictionaryEnabled(), getFrequencyDictionaryMatchMode: () => deps.getFrequencyDictionaryMatchMode(), getFrequencyRank: (text: string) => deps.getFrequencyRank(text), getYomitanGroupDebugEnabled: () => deps.getYomitanGroupDebugEnabled(), getMecabTokenizer: () => deps.getMecabTokenizer(), }); } export function createCreateMecabTokenizerAndCheckMainHandler(deps: { getMecabTokenizer: () => TMecab | null; setMecabTokenizer: (tokenizer: TMecab) => void; createMecabTokenizer: () => TMecab; checkAvailability: (tokenizer: TMecab) => Promise; }) { return async (): Promise => { let tokenizer = deps.getMecabTokenizer(); if (!tokenizer) { tokenizer = deps.createMecabTokenizer(); deps.setMecabTokenizer(tokenizer); } await deps.checkAvailability(tokenizer); }; } export function createPrewarmSubtitleDictionariesMainHandler(deps: { ensureJlptDictionaryLookup: () => Promise; ensureFrequencyDictionaryLookup: () => Promise; showMpvOsd?: (message: string) => void; shouldShowOsdNotification?: () => boolean; setInterval?: (callback: () => void, delayMs: number) => unknown; clearInterval?: (timer: unknown) => void; }) { let prewarmed = false; let prewarmPromise: Promise | null = null; let loadingOsdDepth = 0; let loadingOsdFrame = 0; let loadingOsdTimer: unknown = null; const showMpvOsd = deps.showMpvOsd; const shouldShowOsdNotification = deps.shouldShowOsdNotification ?? (() => false); const setIntervalHandler = deps.setInterval ?? ((callback: () => void, delayMs: number): unknown => setInterval(callback, delayMs)); const clearIntervalHandler = deps.clearInterval ?? ((timer: unknown): void => clearInterval(timer as ReturnType)); const spinnerFrames = ['|', '/', '-', '\\']; const beginLoadingOsd = (): boolean => { if (!showMpvOsd || !shouldShowOsdNotification()) { return false; } loadingOsdDepth += 1; if (loadingOsdDepth > 1) { return true; } loadingOsdFrame = 0; showMpvOsd(`Loading subtitle annotations ${spinnerFrames[loadingOsdFrame]}`); loadingOsdFrame += 1; loadingOsdTimer = setIntervalHandler(() => { if (!showMpvOsd) { return; } showMpvOsd(`Loading subtitle annotations ${spinnerFrames[loadingOsdFrame % spinnerFrames.length]}`); loadingOsdFrame += 1; }, 180); return true; }; const endLoadingOsd = (): void => { if (!showMpvOsd) { return; } loadingOsdDepth = Math.max(0, loadingOsdDepth - 1); if (loadingOsdDepth > 0) { return; } if (loadingOsdTimer) { clearIntervalHandler(loadingOsdTimer); loadingOsdTimer = null; } showMpvOsd('Subtitle annotations loaded'); }; return async (options?: { showLoadingOsd?: boolean }): Promise => { if (prewarmed) { return; } const shouldTrackLoadingOsd = options?.showLoadingOsd === true; const loadingOsdStarted = shouldTrackLoadingOsd ? beginLoadingOsd() : false; if (!prewarmPromise) { prewarmPromise = (async () => { try { await Promise.all([deps.ensureJlptDictionaryLookup(), deps.ensureFrequencyDictionaryLookup()]); prewarmed = true; } finally { prewarmPromise = null; } })(); } try { await prewarmPromise; } finally { if (loadingOsdStarted) { endLoadingOsd(); } } }; }