/**
 * BackgroundManager - Fire-and-forget 패턴 기반 백그라운드 에이전트 매니저
 */
import { CONCURRENCY } from '../lib/constants.js';
import { warnLog } from '../lib/utils.js';
import { launchBackgroundAgent } from './AgentExecutor.js';
import { getModelOverride } from '../lib/config/GlobalConfigManager.js';
import { agentAnnouncer } from './AgentAnnouncer.js';
import { AgentRegistry } from './AgentRegistry.js';
// ============================================
// Error Types (SPEC Error Taxonomy)
// ============================================
/** 큐 용량 초과 에러 */
export class QueueOverflowError extends Error {
    constructor(queueSize, maxSize) {
        super(`Queue overflow: ${queueSize}/${maxSize}. Retry later.`);
        this.name = 'QueueOverflowError';
    }
}
/** 개별 태스크 타임아웃 에러 */
export class TaskTimeoutError extends Error {
    constructor(taskId, timeout) {
        super(`Task "${taskId}" exceeded timeout: ${timeout}ms`);
        this.name = 'TaskTimeoutError';
    }
}
/** 전체 파이프라인 타임아웃 에러 */
export class PipelineTimeoutError extends Error {
    constructor(timeout) {
        super(`Pipeline exceeded timeout: ${timeout}ms`);
        this.name = 'PipelineTimeoutError';
    }
}
/** Agent 실행 실패 에러 */
export class AgentExecutionError extends Error {
    constructor(agentName, reason) {
        super(`Agent "${agentName}" execution failed: ${reason}`);
        this.name = 'AgentExecutionError';
    }
}
// ============================================
// ConcurrencyManager - 동시 실행 제한 관리
// ============================================
class ConcurrencyManager {
    runningByModel = new Map();
    runningByProvider = new Map();
    /** 실행 가능 여부 확인 */
    canRun(model, provider) {
        const modelLimit = this.getModelLimit(model);
        const providerLimit = this.getProviderLimit(provider);
        const currentModel = this.runningByModel.get(model) || 0;
        const currentProvider = this.runningByProvider.get(provider) || 0;
        return currentModel < modelLimit && currentProvider < providerLimit;
    }
    /** 실행 시작 (슬롯 획득) */
    acquire(model, provider) {
        const currentModel = this.runningByModel.get(model) || 0;
        const currentProvider = this.runningByProvider.get(provider) || 0;
        this.runningByModel.set(model, currentModel + 1);
        this.runningByProvider.set(provider, currentProvider + 1);
    }
    /** 실행 종료 (슬롯 반환) */
    release(model, provider) {
        const currentModel = this.runningByModel.get(model) || 0;
        const currentProvider = this.runningByProvider.get(provider) || 0;
        if (currentModel > 0)
            this.runningByModel.set(model, currentModel - 1);
        if (currentProvider > 0)
            this.runningByProvider.set(provider, currentProvider - 1);
    }
    /** 모델별 제한 조회 */
    getModelLimit(model) {
        return CONCURRENCY.MODEL_LIMITS[model] ?? CONCURRENCY.MODEL_LIMITS['default'];
    }
    /** 프로바이더별 제한 조회 */
    getProviderLimit(provider) {
        return CONCURRENCY.PROVIDER_LIMITS[provider] ?? CONCURRENCY.PROVIDER_LIMITS['default'];
    }
    /** 현재 상태 조회 */
    getStatus() {
        return {
            byModel: Object.fromEntries(this.runningByModel),
            byProvider: Object.fromEntries(this.runningByProvider)
        };
    }
}
// ============================================
// TaskQueue - Bounded Queue with Backpressure
// ============================================
class TaskQueue {
    queue = [];
    maxSize;
    constructor(maxSize = CONCURRENCY.QUEUE_MAX_SIZE) {
        this.maxSize = maxSize;
    }
    /** 큐에 추가 (backpressure 적용) */
    enqueue(task) {
        if (this.queue.length >= this.maxSize) {
            throw new QueueOverflowError(this.queue.length, this.maxSize);
        }
        this.queue.push(task);
    }
    /** 다음 대기 중인 태스크 조회 */
    peek() {
        return this.queue.find(t => t.status === 'pending');
    }
    /** 다음 대기 중인 태스크 제거 및 반환 */
    dequeue() {
        const index = this.queue.findIndex(t => t.status === 'pending');
        if (index === -1)
            return undefined;
        return this.queue[index];
    }
    /** 큐 사이즈 */
    get size() {
        return this.queue.filter(t => t.status === 'pending').length;
    }
    /** 전체 태스크 수 */
    get totalSize() {
        return this.queue.length;
    }
    /** 큐가 비었는지 확인 */
    isEmpty() {
        return this.size === 0;
    }
    /** 큐가 가득 찼는지 확인 */
    isFull() {
        return this.queue.length >= this.maxSize;
    }
    /** 태스크 조회 */
    get(taskId) {
        return this.queue.find(t => t.id === taskId);
    }
    /** 완료된 태스크 정리 (24시간 후) */
    cleanup() {
        const cutoff = Date.now() - 24 * 60 * 60 * 1000;
        const before = this.queue.length;
        this.queue = this.queue.filter(t => {
            if (t.status === 'pending' || t.status === 'running')
                return true;
            return (t.completedAt || t.createdAt) > cutoff;
        });
        return before - this.queue.length;
    }
    /** 통계 */
    getStats() {
        const stats = {
            total: this.queue.length,
            pending: 0,
            running: 0,
            stale_canceling: 0,
            completed: 0,
            failed: 0,
            cancelled: 0,
            queueSize: this.size,
            maxQueueSize: this.maxSize
        };
        for (const task of this.queue) {
            stats[task.status]++;
        }
        return stats;
    }
    /** 모든 태스크 */
    getAll() {
        return [...this.queue];
    }
}
// ============================================
// BackgroundManager - 메인 클래스
// ============================================
class BackgroundManagerImpl {
    concurrency = new ConcurrencyManager();
    taskQueue = new TaskQueue();
    tasks = new Map();
    processing = false;
    cleanupTimer = null;
    registry = null;
    staleCheckTimer = null;
    constructor() {
        this.startCleanupTimer();
        this.startStaleDetection();
    }
    /**
     * 레지스트리 초기화 (프로젝트 경로가 있을 때만)
     */
    initRegistry(projectPath) {
        try {
            this.registry = new AgentRegistry(projectPath);
            this.registry.markOrphaned(3600000); // 1시간 이상 running = crashed
        }
        catch (e) {
            warnLog('[BackgroundManager] Registry init failed', e);
        }
    }
    startCleanupTimer() {
        if (this.cleanupTimer)
            return;
        this.cleanupTimer = setInterval(() => {
            const cleaned = this.taskQueue.cleanup();
            if (cleaned > 0) {
                warnLog(`[BackgroundManager] Cleaned up ${cleaned} old tasks`);
            }
        }, 10 * 60 * 1000); // 10분마다
        if (this.cleanupTimer.unref) {
            this.cleanupTimer.unref();
        }
    }
    /**
     * Stale 감지 타이머 — running 태스크의 무활동 감지
     */
    startStaleDetection() {
        if (this.staleCheckTimer)
            return;
        this.staleCheckTimer = setInterval(() => {
            this.checkStaleRunningTasks();
        }, CONCURRENCY.STALE_CHECK_INTERVAL);
        if (this.staleCheckTimer.unref) {
            this.staleCheckTimer.unref();
        }
    }
    /**
     * Running 태스크의 stale 감지 — CAS 패턴 (running → stale_canceling)
     */
    checkStaleRunningTasks() {
        for (const task of this.taskQueue.getAll()) {
            if (task.status === 'running' && !task.stale) {
                const inactiveMs = Date.now() - task.lastActivity;
                if (inactiveMs > CONCURRENCY.ACTIVITY_TIMEOUT) {
                    task.stale = true;
                    task.status = 'stale_canceling';
                    if (task.handle) {
                        task.handle.cancel();
                    }
                    warnLog(`[BackgroundManager] Task ${task.id} stale — no activity for ${inactiveMs}ms`);
                }
            }
        }
    }
    /**
     * Fire-and-forget 방식으로 백그라운드 에이전트 시작
     * 즉시 handle 반환 (< 100ms)
     */
    launch(args) {
        const taskId = `task-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
        const model = args.model || getModelOverride('claudeBackground') || process.env.CLAUDE_BACKGROUND_MODEL;
        const provider = this.extractProvider(model);
        const task = {
            id: taskId,
            args,
            status: 'pending',
            createdAt: Date.now(),
            model,
            provider,
            retryCount: 0,
            lastActivity: Date.now(),
            stale: false,
        };
        try {
            this.taskQueue.enqueue(task);
            this.tasks.set(taskId, task);
        }
        catch (error) {
            if (error instanceof QueueOverflowError) {
                return {
                    content: [{
                            type: 'text',
                            text: `❌ ${error.message}`
                        }],
                    taskId,
                    isError: true
                };
            }
            throw error;
        }
        // 비동기로 큐 처리 시작 (fire-and-forget)
        this.processQueue().catch(err => {
            warnLog(`[BackgroundManager] Queue processing error: ${err.message}`);
        });
        return {
            content: [{
                    type: 'text',
                    text: `🚀 Task queued: ${taskId}\nAgent: ${args.agentName || 'unnamed'}\nModel: ${model}\nQueue position: ${this.taskQueue.size}\n\nUse poll("${taskId}") to check status.`
                }],
            taskId
        };
    }
    /**
     * 결과 폴링 (비동기)
     */
    async poll(taskId) {
        const task = this.tasks.get(taskId);
        if (!task) {
            return {
                content: [{ type: 'text', text: `Task "${taskId}" not found` }]
            };
        }
        switch (task.status) {
            case 'pending':
                return {
                    content: [{
                            type: 'text',
                            text: `⏳ Task "${taskId}" is pending\nQueue position: ${this.getQueuePosition(taskId)}`
                        }],
                    task
                };
            case 'running':
                return {
                    content: [{
                            type: 'text',
                            text: `🔄 Task "${taskId}" is running\nStarted: ${new Date(task.startedAt).toISOString()}\nLast activity: ${new Date(task.lastActivity).toISOString()}\nStale: ${task.stale}`
                        }],
                    task
                };
            case 'stale_canceling':
                return {
                    content: [{
                            type: 'text',
                            text: `⚠️ Task "${taskId}" detected stale, canceling for retry\nLast activity: ${new Date(task.lastActivity).toISOString()}`
                        }],
                    task
                };
            case 'completed':
                return {
                    content: [{
                            type: 'text',
                            text: `✅ Task "${taskId}" completed\nDuration: ${this.formatDuration(task)}\n\nResult:\n${task.result?.result || 'No result'}`
                        }],
                    task,
                    result: task.result
                };
            case 'failed':
                return {
                    content: [{
                            type: 'text',
                            text: `❌ Task "${taskId}" failed\nError: ${task.error || 'Unknown error'}`
                        }],
                    task
                };
            case 'cancelled':
                return {
                    content: [{
                            type: 'text',
                            text: `🚫 Task "${taskId}" was cancelled`
                        }],
                    task
                };
        }
    }
    /**
     * 태스크 취소
     */
    cancel(taskId) {
        const task = this.tasks.get(taskId);
        if (!task) {
            return {
                content: [{ type: 'text', text: `Task "${taskId}" not found` }]
            };
        }
        if (task.status !== 'pending' && task.status !== 'running') {
            return {
                content: [{ type: 'text', text: `Task "${taskId}" is already ${task.status}` }]
            };
        }
        if (task.handle) {
            task.handle.cancel();
        }
        task.status = 'cancelled';
        task.completedAt = Date.now();
        return {
            content: [{ type: 'text', text: `Task "${taskId}" cancelled` }]
        };
    }
    /**
     * 통계 조회
     */
    getStats() {
        const stats = this.taskQueue.getStats();
        const concurrencyStatus = this.concurrency.getStatus();
        const announcerStats = agentAnnouncer.getStats();
        let text = `## BackgroundManager Stats\n\n`;
        text += `**Queue**: ${stats.queueSize}/${stats.maxQueueSize}\n`;
        text += `**Total tasks**: ${stats.total}\n`;
        text += `- Pending: ${stats.pending}\n`;
        text += `- Running: ${stats.running}\n`;
        text += `- Completed: ${stats.completed}\n`;
        text += `- Failed: ${stats.failed}\n`;
        text += `- Cancelled: ${stats.cancelled}\n\n`;
        text += `**Agent Stats**:\n`;
        text += `- Launched: ${announcerStats.totalLaunched}\n`;
        text += `- Completed: ${announcerStats.totalCompleted}\n`;
        text += `- Failed: ${announcerStats.totalFailed}\n`;
        text += `- Avg Duration: ${Math.round(announcerStats.avgDuration)}ms\n\n`;
        text += `**Concurrency**:\n`;
        text += `- By Model: ${JSON.stringify(concurrencyStatus.byModel)}\n`;
        text += `- By Provider: ${JSON.stringify(concurrencyStatus.byProvider)}\n`;
        return {
            content: [{ type: 'text', text }],
            stats
        };
    }
    /**
     * 큐 처리 (내부)
     */
    async processQueue() {
        if (this.processing)
            return;
        this.processing = true;
        try {
            // 대기 제한: PIPELINE_TIMEOUT 이내에서만 큐 처리 시도
            const deadline = Date.now() + CONCURRENCY.PIPELINE_TIMEOUT;
            while (!this.taskQueue.isEmpty()) {
                // Stale 감지: running 태스크 순회 (SPEC Phase 1-4)
                this.checkStaleRunningTasks();
                const task = this.taskQueue.peek();
                if (!task)
                    break;
                if (!this.concurrency.canRun(task.model, task.provider)) {
                    if (Date.now() > deadline) {
                        warnLog(`[BackgroundManager] Queue processing deadline exceeded — ${this.taskQueue.size} tasks still pending`);
                        break;
                    }
                    await this.sleep(100);
                    continue;
                }
                task.status = 'running';
                task.startedAt = Date.now();
                this.concurrency.acquire(task.model, task.provider);
                // Fire-and-forget 실행
                this.executeTask(task).catch(err => {
                    warnLog(`[BackgroundManager] Task ${task.id} error: ${err.message}`);
                });
            }
        }
        finally {
            this.processing = false;
        }
    }
    /**
     * 태스크 실행 (내부) — 실패 시 자동 재시도 (MAX_RETRIES)
     */
    async executeTask(task) {
        const timeout = CONCURRENCY.TASK_TIMEOUT;
        const maxRetries = CONCURRENCY.MAX_RETRIES;
        const agentName = task.args.agentName || 'unnamed';
        // Announce start
        agentAnnouncer.announceStart({
            taskId: task.id, agentName, model: task.model,
            prompt: task.args.prompt || '', timestamp: Date.now(),
        });
        // Registry record start
        try {
            this.registry?.recordStart({
                id: task.id, taskId: task.id, agentName,
                prompt: task.args.prompt, model: task.model,
            });
        }
        catch { /* registry is optional */ }
        // onProgress를 래핑하여 activity 갱신 (Phase 1-3)
        const originalOnProgress = task.args.onProgress;
        const activityTrackingArgs = {
            ...task.args,
            onProgress: (message) => {
                // 의미 있는 활동만 갱신 (system.progress 이벤트 = tool/model 활동)
                task.lastActivity = Date.now();
                if (originalOnProgress)
                    originalOnProgress(message);
            },
        };
        while (task.retryCount <= maxRetries) {
            // 재시도 시 상태 리셋
            if (task.retryCount > 0) {
                const delay = CONCURRENCY.RETRY_BASE_DELAY * Math.pow(2, task.retryCount - 1);
                warnLog(`[BackgroundManager] Task ${task.id} retry ${task.retryCount}/${maxRetries} after ${delay}ms`);
                await this.sleep(delay);
                task.status = 'running';
                task.handle = undefined;
                task.error = undefined;
                task.startedAt = Date.now();
                task.lastActivity = Date.now();
                task.stale = false;
            }
            let timedOut = false;
            const timeoutId = setTimeout(() => {
                if (task.status === 'running') {
                    timedOut = true;
                    // handle 정리하여 Agent SDK 스트림 종료
                    if (task.handle) {
                        task.handle.cancel();
                    }
                    warnLog(`[BackgroundManager] Task ${task.id} timed out after ${timeout}ms (attempt ${task.retryCount + 1})`);
                }
            }, timeout);
            try {
                const result = await launchBackgroundAgent(activityTrackingArgs);
                const handle = result.handle;
                if (handle) {
                    task.handle = handle;
                    // getResult()에 타임아웃 — 스트림이 hang되면 재시도 가능하도록
                    const agentResult = await Promise.race([
                        handle.getResult(),
                        new Promise((_, reject) => setTimeout(() => reject(new TaskTimeoutError(task.id, timeout)), timeout))
                    ]);
                    task.result = agentResult;
                    task.status = agentResult.success ? 'completed' : 'failed';
                    if (!agentResult.success) {
                        task.error = agentResult.error;
                    }
                }
                else {
                    task.status = 'failed';
                    task.error = 'No handle returned';
                }
            }
            catch (error) {
                // handle 정리
                if (task.handle) {
                    task.handle.cancel();
                }
                task.status = 'failed';
                task.error = error instanceof Error ? error.message : String(error);
            }
            finally {
                clearTimeout(timeoutId);
            }
            // stale 감지 후 cancel된 경우 에러 메시지 보강 (status는 try-catch에서 이미 'failed' 처리)
            if (task.stale && task.status === 'failed') {
                task.error = task.error || `Stale: no activity for ${CONCURRENCY.ACTIVITY_TIMEOUT}ms`;
            }
            // 실패가 아닌 경우 (성공/취소) → 재시도 안 함
            // SPEC: 태스크가 cancel 도중 자체 완료하면 완료 우선
            if (task.status !== 'failed') {
                break;
            }
            // 실패 → 재시도 여부 판단
            task.retryCount++;
            if (task.retryCount > maxRetries) {
                warnLog(`[BackgroundManager] Task ${task.id} exhausted ${maxRetries} retries — giving up`);
                break;
            }
        }
        // 최종 정리
        task.completedAt = Date.now();
        const duration = (task.startedAt && task.completedAt)
            ? task.completedAt - task.startedAt : 0;
        this.concurrency.release(task.model, task.provider);
        // Announce + Registry record
        if (task.status === 'completed') {
            agentAnnouncer.announceComplete({
                taskId: task.id, agentName, success: true, duration, model: task.model,
                resultSummary: task.result?.result || '', timestamp: Date.now(),
            });
            try {
                this.registry?.recordComplete(task.id, task.result?.result || '', duration);
            }
            catch { /* optional */ }
        }
        else if (task.status === 'failed') {
            agentAnnouncer.announceError({
                taskId: task.id, agentName, error: task.error || 'Unknown',
                retryable: false, duration, timestamp: Date.now(),
            });
            try {
                this.registry?.recordFailure(task.id, task.error || 'Unknown', duration);
            }
            catch { /* optional */ }
        }
        // 큐에 대기 중인 태스크가 있으면 계속 처리
        if (!this.taskQueue.isEmpty()) {
            this.processQueue().catch(() => { });
        }
    }
    extractProvider(model) {
        if (!model)
            return 'default';
        if (model.startsWith('claude'))
            return 'claude';
        if (model.startsWith('gpt'))
            return 'gpt';
        if (model.startsWith('antigravity'))
            return 'antigravity';
        return 'default';
    }
    getQueuePosition(taskId) {
        const all = this.taskQueue.getAll();
        let position = 0;
        for (const task of all) {
            if (task.status === 'pending') {
                position++;
                if (task.id === taskId)
                    return position;
            }
        }
        return -1;
    }
    formatDuration(task) {
        if (!task.startedAt || !task.completedAt)
            return 'N/A';
        const ms = task.completedAt - task.startedAt;
        return `${(ms / 1000).toFixed(1)}s`;
    }
    sleep(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }
}
// 싱글톤 인스턴스
export const backgroundManager = new BackgroundManagerImpl();
// 편의 함수
export function launch(args) {
    return backgroundManager.launch(args);
}
export function poll(taskId) {
    return backgroundManager.poll(taskId);
}
export function cancel(taskId) {
    return backgroundManager.cancel(taskId);
}
export function getStats() {
    return backgroundManager.getStats();
}
//# sourceMappingURL=BackgroundManager.js.map