/**
 * SchedulerTaskAdapter — bridges TaskScheduler scheduled job executions into
 * the unified RuntimeTask registry.
 *
 * Each time a scheduled task fires (i.e. TaskRunRecord is created), a
 * corresponding RuntimeTask of kind 'scheduler' is registered. The adapter
 * tracks runRecord agentId → task ID and handles completion via state sync.
 *
 * NOTE: This adapter writes directly to the Zustand store for performance,
 * bypassing TaskManager. Lifecycle validation is the caller's responsibility.
 * This is intentional — adapters are authoritative sources for their subsystem.
 */
import { randomUUID } from 'node:crypto';
import { createDomainDispatch } from '../../store/index.js';
function mapSchedulerRunStatus(status) {
    switch (status) {
        case 'running':
            return 'running';
        case 'completed':
            return 'completed';
        case 'failed':
            return 'failed';
    }
    return assertNeverSchedulerRunStatus(status);
}
function assertNeverSchedulerRunStatus(status) {
    throw new Error(`Unhandled scheduler run status: ${String(status)}`);
}
/**
 * Bridges TaskScheduler job executions into the RuntimeTask registry.
 *
 * The scheduler runs jobs that spawn agent sessions. Each run is tracked as a
 * RuntimeTask of kind 'scheduler'. The run's agentId is used as the correlation
 * key between the scheduler's TaskRunRecord and the RuntimeTask.
 *
 * @example
 * ```ts
 * const adapter = new SchedulerTaskAdapter(store);
 * // When a scheduled task fires:
 * const taskId = adapter.wrapScheduledRun(record, scheduledTask);
 * // When the run completes:
 * adapter.handleRunComplete(record.agentId, 'completed');
 * ```
 */
export class SchedulerTaskAdapter {
    _store;
    /** Maps run agentId → task ID. */
    _runToTask = new Map();
    /** Maps task ID → run agentId. */
    _taskToRun = new Map();
    _dispatch;
    constructor(_store) {
        this._store = _store;
        this._dispatch = createDomainDispatch(_store);
    }
    // ── Core API ────────────────────────────────────────────────────────────────
    /**
     * Wrap a scheduled task run as a RuntimeTask.
     *
     * @param record - The TaskRunRecord from the scheduler.
     * @param scheduledTask - The parent ScheduledTask definition.
     * @returns The new task ID.
     */
    wrapScheduledRun(record, scheduledTask) {
        // Idempotent: return existing if already wrapped
        const existing = this._runToTask.get(record.agentId);
        if (existing !== undefined)
            return existing;
        const taskId = randomUUID();
        const now = record.startedAt;
        const status = mapSchedulerRunStatus(record.status);
        const task = {
            id: taskId,
            kind: 'scheduler',
            title: `Scheduled: ${scheduledTask.name}`,
            description: scheduledTask.prompt.length > 200
                ? `${scheduledTask.prompt.slice(0, 197)}...`
                : scheduledTask.prompt,
            status,
            owner: `scheduler:${scheduledTask.id}`,
            cancellable: status === 'running',
            childTaskIds: [],
            queuedAt: now,
            startedAt: now,
            endedAt: status !== 'running' ? Date.now() : undefined,
            correlationId: record.agentId,
            turnId: record.taskId,
            error: record.error,
        };
        this._runToTask.set(record.agentId, taskId);
        this._taskToRun.set(taskId, record.agentId);
        this._upsertTask(task);
        return taskId;
    }
    /**
     * Handle a scheduled run completion or failure.
     *
     * @param runAgentId - The agentId from the TaskRunRecord.
     * @param status - Terminal status: 'completed' or 'failed'.
     * @param error - Optional error message if status is 'failed'.
     */
    handleRunComplete(runAgentId, status, error) {
        const taskId = this._runToTask.get(runAgentId);
        if (taskId === undefined)
            return;
        this._transitionTask(taskId, status, { error });
        this._runToTask.delete(runAgentId);
        this._taskToRun.delete(taskId);
    }
    /**
     * Cancel a scheduled run task by task ID.
     * Marks the task as cancelled. The caller is responsible for disabling the
     * scheduled task via TaskScheduler.setEnabled().
     *
     * @param taskId - The RuntimeTask ID to cancel.
     */
    cancelScheduled(taskId) {
        const runAgentId = this._taskToRun.get(taskId);
        if (runAgentId === undefined)
            return;
        this._transitionTask(taskId, 'cancelled', {});
        this._runToTask.delete(runAgentId);
        this._taskToRun.delete(taskId);
    }
    /**
     * Reconcile adapter state with the current TaskScheduler snapshot.
     *
     * Scans all run history records from the scheduler:
     * - Wraps any un-tracked running jobs as new tasks.
     * - Marks completed/failed jobs that are still showing as running.
     *
     * @param scheduler - The TaskScheduler instance to sync from.
     */
    sync(scheduler) {
        const scheduledTasks = scheduler.list();
        const scheduledTaskMap = new Map(scheduledTasks.map((t) => [t.id, t]));
        const allHistory = scheduler.getAllHistory();
        const runningRuns = allHistory.filter((r) => r.status === 'running');
        for (const record of runningRuns) {
            const scheduledTask = scheduledTaskMap.get(record.taskId);
            if (!scheduledTask)
                continue;
            if (!this._runToTask.has(record.agentId)) {
                this.wrapScheduledRun(record, scheduledTask);
            }
        }
        // Reconcile tracked runs that are no longer running.
        // Build a Map for O(1) history lookups — acceptable O(n) build at current scheduler scale.
        const historyByAgentId = new Map(allHistory.map((r) => [r.agentId, r]));
        const runningAgentIds = new Set(runningRuns.map((r) => r.agentId));
        const staleRunIds = [];
        for (const [runAgentId] of this._runToTask.entries()) {
            if (!runningAgentIds.has(runAgentId))
                staleRunIds.push(runAgentId);
        }
        for (const runAgentId of staleRunIds) {
            const taskId = this._runToTask.get(runAgentId);
            const record = historyByAgentId.get(runAgentId);
            if (!record) {
                this._transitionTask(taskId, 'cancelled', {
                    error: `Scheduler run ${runAgentId} disappeared before terminal status was recorded`,
                });
            }
            else {
                const terminalStatus = mapSchedulerRunStatus(record.status);
                if (terminalStatus === 'running')
                    continue;
                this._transitionTask(taskId, terminalStatus, { error: record.error });
            }
            this._runToTask.delete(runAgentId);
            this._taskToRun.delete(taskId);
        }
    }
    // ── Private helpers ─────────────────────────────────────────────────────────
    _upsertTask(task) {
        this._dispatch.syncRuntimeTask(task, 'scheduler-adapter');
    }
    _transitionTask(taskId, status, opts) {
        this._dispatch.transitionRuntimeTask(taskId, status, {
            endedAt: Date.now(),
            error: opts.error,
        }, 'scheduler-adapter');
    }
}
