import cron from 'node-cron';
import fs from 'node:fs';
import path from 'node:path';
import YAML from 'yaml';
export class SchedulerService {
    db;
    jobRunner;
    squadsDir;
    broadcast;
    jobs = new Map();
    constructor(config) {
        this.db = config.db;
        this.jobRunner = config.jobRunner;
        this.squadsDir = config.squadsDir;
        this.broadcast = config.broadcast;
    }
    start() {
        // 1. Register squads from YAML files
        if (fs.existsSync(this.squadsDir)) {
            const entries = fs.readdirSync(this.squadsDir, { withFileTypes: true });
            for (const entry of entries) {
                if (!entry.isDirectory() || entry.name.startsWith('.'))
                    continue;
                this.register(entry.name);
            }
        }
        // 2. Restore API-created jobs from DB that weren't loaded from YAML
        this.restoreFromDb();
    }
    restoreFromDb() {
        const rows = this.db.prepare("SELECT squad_name, cron_expression, prompt, timezone, timeout_minutes, retry_on_failure, status FROM scheduled_jobs").all();
        for (const row of rows) {
            // Skip if already registered from YAML
            if (this.jobs.has(row.squad_name))
                continue;
            if (!cron.validate(row.cron_expression)) {
                console.error(`[scheduler] Invalid cron in DB for ${row.squad_name}: ${row.cron_expression}`);
                continue;
            }
            const isPaused = row.status === 'paused';
            const task = cron.schedule(row.cron_expression, () => {
                this.jobRunner.run({
                    squadName: row.squad_name,
                    prompt: row.prompt,
                    timeoutMinutes: row.timeout_minutes ?? 30,
                    retryOnFailure: row.retry_on_failure ?? 0,
                    attempt: 1,
                });
            }, {
                timezone: row.timezone ?? undefined,
            });
            if (isPaused) {
                task.stop();
            }
            this.jobs.set(row.squad_name, {
                squadName: row.squad_name,
                cronExpression: row.cron_expression,
                timezone: row.timezone ?? undefined,
                prompt: row.prompt,
                timeoutMinutes: row.timeout_minutes ?? 30,
                retryOnFailure: row.retry_on_failure ?? 0,
                task,
                status: isPaused ? 'paused' : 'active',
            });
        }
    }
    stop() {
        for (const job of this.jobs.values()) {
            job.task.stop();
        }
        this.jobs.clear();
        this.jobRunner.stopAll();
    }
    register(squadName) {
        const schedule = this.readScheduleConfig(squadName);
        if (!schedule || !schedule.enabled)
            return;
        // Skip if no cron configured yet (schedule enabled but not fully configured)
        if (!schedule.cron)
            return;
        if (!cron.validate(schedule.cron)) {
            console.error(`[scheduler] Invalid cron expression for ${squadName}: ${schedule.cron}`);
            return;
        }
        // Unregister if already exists
        if (this.jobs.has(squadName)) {
            this.unregister(squadName);
        }
        // Restore paused state from DB
        const dbRow = this.db.prepare('SELECT status FROM scheduled_jobs WHERE squad_name = ?')
            .get(squadName);
        const isPaused = dbRow?.status === 'paused';
        const task = cron.schedule(schedule.cron, () => {
            this.jobRunner.run({
                squadName,
                prompt: schedule.prompt,
                timeoutMinutes: schedule.timeout_minutes ?? 30,
                retryOnFailure: schedule.retry_on_failure ?? 0,
                attempt: 1,
            });
        }, {
            timezone: schedule.timezone,
        });
        if (isPaused) {
            task.stop();
        }
        const job = {
            squadName,
            cronExpression: schedule.cron,
            timezone: schedule.timezone,
            prompt: schedule.prompt,
            timeoutMinutes: schedule.timeout_minutes ?? 30,
            retryOnFailure: schedule.retry_on_failure ?? 0,
            task,
            status: isPaused ? 'paused' : 'active',
        };
        this.jobs.set(squadName, job);
        // Upsert DB row
        this.db.prepare(`
      INSERT INTO scheduled_jobs (squad_name, cron_expression, prompt, mode, timeout_minutes, retry_on_failure, timezone, status)
      VALUES (?, ?, ?, 'autonomous', ?, ?, ?, ?)
      ON CONFLICT(squad_name) DO UPDATE SET
        cron_expression = excluded.cron_expression,
        prompt = excluded.prompt,
        timeout_minutes = excluded.timeout_minutes,
        retry_on_failure = excluded.retry_on_failure,
        timezone = excluded.timezone,
        updated_at = datetime('now')
    `).run(squadName, schedule.cron, schedule.prompt, schedule.timeout_minutes ?? 30, schedule.retry_on_failure ?? 0, schedule.timezone ?? null, isPaused ? 'paused' : 'active');
    }
    unregister(squadName) {
        const job = this.jobs.get(squadName);
        if (job) {
            job.task.stop();
            this.jobs.delete(squadName);
        }
        this.db.prepare('DELETE FROM scheduled_jobs WHERE squad_name = ?').run(squadName);
    }
    pause(squadName) {
        const job = this.jobs.get(squadName);
        if (!job)
            return;
        job.task.stop();
        job.status = 'paused';
        this.db.prepare("UPDATE scheduled_jobs SET status = 'paused', updated_at = datetime('now') WHERE squad_name = ?")
            .run(squadName);
        this.broadcast?.(squadName, { type: 'schedule:status_changed', squadName, status: 'paused' });
    }
    resume(squadName) {
        const job = this.jobs.get(squadName);
        if (!job)
            return;
        job.task.start();
        job.status = 'active';
        this.db.prepare("UPDATE scheduled_jobs SET status = 'active', updated_at = datetime('now') WHERE squad_name = ?")
            .run(squadName);
        this.broadcast?.(squadName, { type: 'schedule:status_changed', squadName, status: 'active' });
    }
    triggerNow(squadName) {
        const job = this.jobs.get(squadName);
        if (!job)
            return;
        this.jobRunner.run({
            squadName,
            prompt: job.prompt,
            timeoutMinutes: job.timeoutMinutes,
            retryOnFailure: job.retryOnFailure,
            attempt: 1,
        });
    }
    reload(squadName) {
        this.unregister(squadName);
        this.register(squadName);
    }
    /** Register a schedule from API data (not from YAML) */
    registerDynamic(squadName, config) {
        if (!cron.validate(config.cron))
            return false;
        // Unregister if already exists
        if (this.jobs.has(squadName)) {
            this.unregister(squadName);
        }
        const timeoutMin = config.timeout_minutes ?? 30;
        const retryCount = config.retry_on_failure ?? 0;
        const task = cron.schedule(config.cron, () => {
            this.jobRunner.run({
                squadName,
                prompt: config.prompt,
                timeoutMinutes: timeoutMin,
                retryOnFailure: retryCount,
                attempt: 1,
            });
        }, {
            timezone: config.timezone,
        });
        const job = {
            squadName,
            cronExpression: config.cron,
            timezone: config.timezone,
            prompt: config.prompt,
            timeoutMinutes: timeoutMin,
            retryOnFailure: retryCount,
            task,
            status: 'active',
        };
        this.jobs.set(squadName, job);
        this.db.prepare(`
      INSERT INTO scheduled_jobs (squad_name, cron_expression, prompt, mode, timeout_minutes, retry_on_failure, timezone, status)
      VALUES (?, ?, ?, 'autonomous', ?, ?, ?, 'active')
      ON CONFLICT(squad_name) DO UPDATE SET
        cron_expression = excluded.cron_expression,
        prompt = excluded.prompt,
        timeout_minutes = excluded.timeout_minutes,
        retry_on_failure = excluded.retry_on_failure,
        timezone = excluded.timezone,
        status = 'active',
        updated_at = datetime('now')
    `).run(squadName, config.cron, config.prompt, timeoutMin, retryCount, config.timezone ?? null);
        return true;
    }
    getStatus() {
        const result = [];
        for (const job of this.jobs.values()) {
            const dbRow = this.db.prepare('SELECT last_run_at, last_result, next_run_at FROM scheduled_jobs WHERE squad_name = ?')
                .get(job.squadName);
            result.push({
                squadName: job.squadName,
                cronExpression: job.cronExpression,
                timezone: job.timezone,
                status: job.status,
                lastRunAt: dbRow?.last_run_at,
                lastResult: dbRow?.last_result,
                nextRunAt: dbRow?.next_run_at,
            });
        }
        return result;
    }
    getJob(squadName) {
        const job = this.jobs.get(squadName);
        if (!job)
            return null;
        const dbRow = this.db.prepare('SELECT last_run_at, last_result, next_run_at FROM scheduled_jobs WHERE squad_name = ?')
            .get(squadName);
        return {
            squadName: job.squadName,
            cronExpression: job.cronExpression,
            timezone: job.timezone,
            status: job.status,
            lastRunAt: dbRow?.last_run_at,
            lastResult: dbRow?.last_result,
            nextRunAt: dbRow?.next_run_at,
        };
    }
    readScheduleConfig(squadName) {
        try {
            const yamlPath = path.join(this.squadsDir, squadName, 'squad.yaml');
            if (!fs.existsSync(yamlPath))
                return null;
            const raw = fs.readFileSync(yamlPath, 'utf-8');
            const parsed = YAML.parse(raw);
            const schedule = parsed?.squad?.schedule;
            if (!schedule || !schedule.enabled)
                return null;
            return schedule;
        }
        catch {
            return null;
        }
    }
}
//# sourceMappingURL=scheduler-service.js.map