"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.pipelineFromTargets = pipelineFromTargets;
exports.backfillRowToCheckpoint = backfillRowToCheckpoint;
exports.backfillJobToPublicProgress = backfillJobToPublicProgress;
exports.getBackfillJobById = getBackfillJobById;
exports.listChildBackfillJobs = listChildBackfillJobs;
exports.createBackfillJob = createBackfillJob;
exports.persistBackfillJobProgress = persistBackfillJobProgress;
exports.markBackfillJobFailed = markBackfillJobFailed;
exports.markBackfillJobCompleted = markBackfillJobCompleted;
exports.aggregateParentBackfillJob = aggregateParentBackfillJob;
exports.loadBackfillJobForResume = loadBackfillJobForResume;
const drizzle_orm_1 = require("drizzle-orm");
const uuid_1 = require("uuid");
const database_1 = require("../../../database");
const tables_1 = require("../../../database/tables");
function pipelineFromTargets(targets) {
    if (targets === 'ch') {
        return 'eval_denorm_ch';
    }
    if (targets === 'pg') {
        return 'eval_denorm_pg';
    }
    return 'eval_denorm_both';
}
function parseResumeCursor(raw) {
    if (!raw || typeof raw !== 'object') {
        return null;
    }
    const c = raw;
    if (typeof c.id !== 'string' || c.id === '') {
        return null;
    }
    return {
        id: c.id,
        createdAt: String(c.createdAt ?? c.created_at ?? ''),
        updatedAt: String(c.updatedAt ?? c.updated_at ?? ''),
        latencyMs: Number(c.latencyMs ?? c.latency_ms ?? 0),
        testIdx: Number(c.testIdx ?? c.test_idx ?? 0),
    };
}
function backfillRowToCheckpoint(row) {
    return {
        resumeCursor: parseResumeCursor(row.resumeCursor),
        nextOffset: row.rowsProcessed ?? 0,
        readSource: row.readSource === 'postgres' || row.readSource === 'clickhouse'
            ? row.readSource
            : undefined,
        pgAnalyticsInserted: Number(row.pgAnalyticsInserted ?? 0),
        pgComponentsInserted: Number(row.pgComponentsInserted ?? 0),
        chAnalyticsInserted: Number(row.chAnalyticsInserted ?? 0),
        chComponentsInserted: Number(row.chComponentsInserted ?? 0),
        skipped: Number(row.skipped ?? 0),
        errors: Array.isArray(row.errors) ? row.errors.map(String) : [],
        scopeCreatedAfter: row.scopeCreatedAfter?.toISOString() ?? null,
        scopeCreatedBefore: row.scopeCreatedBefore?.toISOString() ?? null,
        scopeReason: row.scopeReason ?? undefined,
        batchSize: row.batchSize ?? undefined,
        lastFailedAt: row.lastFailedAt?.toISOString() ?? null,
        lastError: row.lastError ?? null,
    };
}
function backfillJobToPublicProgress(row) {
    const total = Number(row.totalRows ?? 0);
    const processed = Number(row.rowsProcessed ?? 0);
    return {
        id: row.id,
        pipeline: row.pipeline,
        status: row.status,
        parentId: row.parentId,
        shardIndex: row.shardIndex,
        readSource: row.readSource,
        targets: row.targets,
        totalRows: total,
        rowsProcessed: processed,
        rowsRemaining: row.rowsRemaining ?? Math.max(0, total - processed),
        batchSize: row.batchSize,
        batchNumber: row.batchNumber,
        estimatedBatches: row.estimatedBatches,
        percentComplete: row.percentComplete,
        elapsedSeconds: row.elapsedSeconds,
        etaSeconds: row.etaSeconds,
        resumeCursor: row.resumeCursor,
        scopeCreatedAfter: row.scopeCreatedAfter?.toISOString() ?? null,
        scopeCreatedBefore: row.scopeCreatedBefore?.toISOString() ?? null,
        scopeReason: row.scopeReason,
        chAnalyticsInserted: row.chAnalyticsInserted,
        chComponentsInserted: row.chComponentsInserted,
        pgAnalyticsInserted: row.pgAnalyticsInserted,
        pgComponentsInserted: row.pgComponentsInserted,
        skipped: row.skipped,
        errors: row.errors,
        lastError: row.lastError,
        lastFailedAt: row.lastFailedAt?.toISOString() ?? null,
        projectId: row.projectId,
        applicationId: row.applicationId,
        dryRun: row.dryRun === 1,
        startedAt: row.startedAt?.toISOString(),
        updatedAt: row.updatedAt?.toISOString(),
        completedAt: row.completedAt?.toISOString() ?? null,
    };
}
async function getBackfillJobById(id) {
    const db = (0, database_1.getDb)();
    const rows = await db.select().from(tables_1.backfillsTable).where((0, drizzle_orm_1.eq)(tables_1.backfillsTable.id, id)).limit(1);
    return rows[0] ?? null;
}
async function listChildBackfillJobs(parentId) {
    const db = (0, database_1.getDb)();
    return db
        .select()
        .from(tables_1.backfillsTable)
        .where((0, drizzle_orm_1.eq)(tables_1.backfillsTable.parentId, parentId))
        .orderBy((0, drizzle_orm_1.asc)(tables_1.backfillsTable.shardIndex));
}
async function createBackfillJob(params) {
    const id = (0, uuid_1.v4)();
    const now = new Date();
    const db = (0, database_1.getDb)();
    await db.insert(tables_1.backfillsTable).values({
        id,
        pipeline: params.pipeline,
        status: 'running',
        parentId: params.parentId ?? null,
        shardIndex: params.shardIndex ?? null,
        shardStartOffset: params.shardStartOffset ?? null,
        shardEndOffset: params.shardEndOffset ?? null,
        readSource: params.readSource ?? null,
        targets: params.targets,
        batchSize: params.batchSize,
        scopeCreatedAfter: params.scopeCreatedAfter ? new Date(params.scopeCreatedAfter) : null,
        scopeCreatedBefore: params.scopeCreatedBefore ? new Date(params.scopeCreatedBefore) : null,
        scopeReason: params.scopeReason ?? null,
        projectId: params.options.projectId ?? null,
        applicationId: params.options.applicationId ?? null,
        dryRun: params.dryRun ? 1 : 0,
        options: params.options,
        startedAt: now,
        updatedAt: now,
    });
    return id;
}
async function persistBackfillJobProgress(jobId, update) {
    const { checkpoint } = update;
    const totalRows = update.totalRows;
    const rowsProcessed = checkpoint.nextOffset ?? 0;
    const rowsRemaining = totalRows != null && totalRows > 0 ? Math.max(0, totalRows - rowsProcessed) : null;
    const percentComplete = totalRows != null && totalRows > 0
        ? Math.min(100, (rowsProcessed / totalRows) * 100)
        : null;
    const db = (0, database_1.getDb)();
    await db
        .update(tables_1.backfillsTable)
        .set({
        status: update.status ?? 'running',
        totalRows: totalRows ?? undefined,
        rowsProcessed,
        rowsRemaining,
        batchNumber: update.batchNumber,
        estimatedBatches: update.estimatedBatches,
        percentComplete,
        elapsedSeconds: update.elapsedSeconds,
        etaSeconds: update.etaSeconds,
        resumeCursor: checkpoint.resumeCursor ?? null,
        readSource: update.readSource ?? checkpoint.readSource ?? undefined,
        chAnalyticsInserted: checkpoint.chAnalyticsInserted,
        chComponentsInserted: checkpoint.chComponentsInserted,
        pgAnalyticsInserted: checkpoint.pgAnalyticsInserted,
        pgComponentsInserted: checkpoint.pgComponentsInserted,
        skipped: checkpoint.skipped,
        errors: checkpoint.errors,
        lastError: checkpoint.lastError ?? null,
        lastFailedAt: checkpoint.lastFailedAt ? new Date(checkpoint.lastFailedAt) : null,
        scopeCreatedAfter: checkpoint.scopeCreatedAfter != null
            ? new Date(checkpoint.scopeCreatedAfter)
            : undefined,
        scopeCreatedBefore: checkpoint.scopeCreatedBefore != null
            ? new Date(checkpoint.scopeCreatedBefore)
            : undefined,
        scopeReason: checkpoint.scopeReason,
        batchSize: checkpoint.batchSize,
        shardStartOffset: update.shardStartOffset,
        shardEndOffset: update.shardEndOffset,
        updatedAt: new Date(),
        completedAt: update.status === 'completed' ? new Date() : undefined,
    })
        .where((0, drizzle_orm_1.eq)(tables_1.backfillsTable.id, jobId));
}
async function markBackfillJobFailed(jobId, checkpoint) {
    await persistBackfillJobProgress(jobId, {
        checkpoint,
        status: 'failed',
    });
}
async function markBackfillJobCompleted(jobId, checkpoint, progress) {
    await persistBackfillJobProgress(jobId, {
        checkpoint,
        status: 'completed',
        ...progress,
    });
}
async function aggregateParentBackfillJob(parentId) {
    const children = await listChildBackfillJobs(parentId);
    if (children.length === 0) {
        return;
    }
    const merged = {
        pgAnalyticsInserted: 0,
        pgComponentsInserted: 0,
        chAnalyticsInserted: 0,
        chComponentsInserted: 0,
        skipped: 0,
        errors: [],
    };
    let totalRows = 0;
    let rowsProcessed = 0;
    let allCompleted = true;
    let anyFailed = false;
    for (const child of children) {
        const cp = backfillRowToCheckpoint(child);
        merged.pgAnalyticsInserted += cp.pgAnalyticsInserted;
        merged.pgComponentsInserted += cp.pgComponentsInserted;
        merged.chAnalyticsInserted += cp.chAnalyticsInserted;
        merged.chComponentsInserted += cp.chComponentsInserted;
        merged.skipped += cp.skipped;
        merged.errors.push(...cp.errors);
        totalRows += Number(child.totalRows ?? 0);
        rowsProcessed += Number(child.rowsProcessed ?? 0);
        if (child.status !== 'completed') {
            allCompleted = false;
        }
        if (child.status === 'failed') {
            anyFailed = true;
        }
    }
    const status = anyFailed ? 'failed' : allCompleted ? 'completed' : 'running';
    await persistBackfillJobProgress(parentId, {
        checkpoint: merged,
        status,
        totalRows,
        batchNumber: children.reduce((n, c) => Math.max(n, c.batchNumber ?? 0), 0),
    });
}
async function loadBackfillJobForResume(backfillId) {
    const row = await getBackfillJobById(backfillId);
    if (!row) {
        return null;
    }
    return { row, checkpoint: backfillRowToCheckpoint(row) };
}
//# sourceMappingURL=backfillJobStore.js.map