export function createLaunchBackgroundWarmupTaskHandler(deps: { now: () => number; logDebug: (message: string) => void; logWarn: (message: string) => void; }) { return (label: string, task: () => Promise): void => { const startedAtMs = deps.now(); void task() .then(() => { deps.logDebug(`[startup-warmup] ${label} completed in ${deps.now() - startedAtMs}ms`); }) .catch((error) => { const message = error instanceof Error ? error.message : String(error); deps.logWarn(`[startup-warmup] ${label} failed: ${message}`); }); }; } export function createStartBackgroundWarmupsHandler(deps: { getStarted: () => boolean; setStarted: (started: boolean) => void; isTexthookerOnlyMode: () => boolean; launchTask: (label: string, task: () => Promise) => void; createMecabTokenizerAndCheck: () => Promise; ensureYomitanExtensionLoaded: () => Promise; prewarmSubtitleDictionaries: () => Promise; shouldWarmupMecab: () => boolean; shouldWarmupYomitanExtension: () => boolean; shouldWarmupSubtitleDictionaries: () => boolean; shouldWarmupJellyfinRemoteSession: () => boolean; shouldAutoConnectJellyfinRemote: () => boolean; startJellyfinRemoteSession: () => Promise; onYomitanExtensionWarmupScheduled?: (promise: Promise) => void; onTokenizationWarmupScheduled?: (promise: Promise) => void; logDebug?: (message: string) => void; }) { return (): void => { if (deps.getStarted()) return; if (deps.isTexthookerOnlyMode()) return; const warmupMecab = deps.shouldWarmupMecab(); const warmupYomitanExtension = deps.shouldWarmupYomitanExtension(); const warmupSubtitleDictionaries = deps.shouldWarmupSubtitleDictionaries(); const warmupJellyfinRemoteSession = deps.shouldWarmupJellyfinRemoteSession(); const autoConnectJellyfinRemote = deps.shouldAutoConnectJellyfinRemote(); deps.setStarted(true); const shouldWarmupTokenization = warmupMecab || warmupYomitanExtension || warmupSubtitleDictionaries; if (shouldWarmupTokenization) { const yomitanWarmupPromise = warmupYomitanExtension ? (async () => { deps.logDebug?.('[startup-warmup] stage start: yomitan-extension'); await deps.ensureYomitanExtensionLoaded(); deps.logDebug?.('[startup-warmup] stage ready: yomitan-extension'); })() : Promise.resolve().then(() => { deps.logDebug?.('[startup-warmup] stage skipped: yomitan-extension'); }); if (warmupYomitanExtension) { deps.onYomitanExtensionWarmupScheduled?.(yomitanWarmupPromise); } const tokenizationWarmupPromise = Promise.all([ yomitanWarmupPromise, warmupMecab ? (async () => { deps.logDebug?.('[startup-warmup] stage start: mecab'); await deps.createMecabTokenizerAndCheck(); deps.logDebug?.('[startup-warmup] stage ready: mecab'); })() : Promise.resolve().then(() => { deps.logDebug?.('[startup-warmup] stage skipped: mecab'); }), warmupSubtitleDictionaries ? (async () => { deps.logDebug?.('[startup-warmup] stage start: subtitle-dictionaries'); await deps.prewarmSubtitleDictionaries(); deps.logDebug?.('[startup-warmup] stage ready: subtitle-dictionaries'); })() : Promise.resolve().then(() => { deps.logDebug?.('[startup-warmup] stage skipped: subtitle-dictionaries'); }), ]).then(() => {}); deps.onTokenizationWarmupScheduled?.(tokenizationWarmupPromise); deps.launchTask('subtitle-tokenization', () => tokenizationWarmupPromise); } if (warmupJellyfinRemoteSession && autoConnectJellyfinRemote) { deps.launchTask('jellyfin-remote-session', async () => { deps.logDebug?.('[startup-warmup] stage start: jellyfin-remote-session'); await deps.startJellyfinRemoteSession(); deps.logDebug?.('[startup-warmup] stage ready: jellyfin-remote-session'); }); } else if (!warmupJellyfinRemoteSession) { deps.logDebug?.('[startup-warmup] stage skipped: jellyfin-remote-session (disabled)'); } else { deps.logDebug?.('[startup-warmup] stage skipped: jellyfin-remote-session (auto-connect off)'); } }; }