import type { MarketApiFailureReason, MarketApiResult, MarketRowSnapshot } from "./types"; import type { AfterSearchRates } from "./types"; interface ResultStoreLike { setAuthorFailed(authorId: string, reason: MarketApiFailureReason): void; setAuthorLoading(authorId: string): void; setAuthorSuccess(authorId: string, rates: AfterSearchRates): void; upsertMarketRow(row: MarketRowSnapshot): void; } interface FullScanControllerOptions { goToNextPage(): Promise; hasNextPage(): boolean; loadAuthorMetrics(authorId: string): Promise; readCurrentPageRows(): MarketRowSnapshot[]; resultStore: ResultStoreLike; } export function createFullScanController(options: FullScanControllerOptions) { let completedScan = false; let scanPromise: Promise | null = null; return { ensureScanForExport() { return ensureScan(); }, ensureScanForFilter() { return ensureScan(); }, ensureScanForSort() { return ensureScan(); } }; function ensureScan(): Promise { if (completedScan) { return Promise.resolve(); } if (scanPromise) { return scanPromise; } scanPromise = runScan().finally(() => { scanPromise = null; }); return scanPromise; } async function runScan(): Promise { do { await scanCurrentPage(); if (!options.hasNextPage()) { completedScan = true; return; } } while (await options.goToNextPage()); completedScan = true; } async function scanCurrentPage(): Promise { const rows = options.readCurrentPageRows(); for (const row of rows) { options.resultStore.upsertMarketRow(row); if (row.hasDirectRatesSource) { const directRates = row.rates ?? {}; const hasAllRates = Boolean(directRates.singleVideoAfterSearchRate) && Boolean(directRates.personalVideoAfterSearchRate); options.resultStore.setAuthorSuccess(row.authorId, directRates); if (hasAllRates) { continue; } } options.resultStore.setAuthorLoading(row.authorId); const metricsResult = await options.loadAuthorMetrics(row.authorId); if (metricsResult.success) { options.resultStore.setAuthorSuccess(row.authorId, metricsResult.rates); continue; } options.resultStore.setAuthorFailed(row.authorId, metricsResult.reason); } } }