import { resolve as resolvePath } from 'node:path';
import { pathToFileURL } from 'node:url';
import { collectSecrets, registerPipelineGlobals, runPlan } from '@kitsi/cli';
function formatError(error) {
    if (error instanceof Error) {
        return error.message;
    }
    if (typeof error === 'string') {
        return error;
    }
    if (error && typeof error === 'object') {
        if ('message' in error && typeof error.message === 'string') {
            return error.message;
        }
        try {
            return JSON.stringify(error);
        }
        catch {
            return String(error);
        }
    }
    return String(error);
}
function parseTimeout(timeout) {
    const match = timeout.match(/^(\d+)(s|m|h)?$/);
    if (!match) {
        throw new Error(`Invalid timeout format: ${timeout}. Use format like 30m, 1h, or 300s`);
    }
    const value = Number.parseInt(match[1], 10);
    const unit = match[2] ?? 's';
    switch (unit) {
        case 's':
            return value * 1000;
        case 'm':
            return value * 60 * 1000;
        case 'h':
            return value * 60 * 60 * 1000;
        default:
            return value * 1000;
    }
}
async function loadPipeline(file, cwd) {
    // Register globals (kit, plan, step, sh, etc.) before loading the pipeline
    registerPipelineGlobals();
    const resolved = resolvePath(cwd, file);
    const moduleUrl = pathToFileURL(resolved).href;
    const loaded = await import(moduleUrl);
    const registry = loaded.ci ?? loaded.default;
    if (!registry) {
        throw new Error(`Pipeline file must export "ci" (got: ${Object.keys(loaded).join(', ') || 'no exports'})`);
    }
    return registry;
}
function resolveSecrets(required, capabilities) {
    if (required.size === 0)
        return {};
    const missing = Array.from(required).filter((name) => process.env[name] === undefined);
    if (missing.length > 0) {
        const exampleLines = missing
            .map((name) => `    ${name}: \${{ secrets.${name} }}`)
            .join('\n');
        throw new Error(`Missing required secret${missing.length > 1 ? 's' : ''}: ${missing.join(', ')}.\n` +
            'Add them in repository secrets and pass via env in the workflow, for example:\n' +
            `env:\n${exampleLines}`);
    }
    const resolved = {};
    for (const name of required) {
        resolved[name] = capabilities.secrets.resolve({ kind: 'secret', name });
    }
    return resolved;
}
export async function runPipeline(options) {
    const { plan, file, task, timeout, failFast, workdir } = options;
    const reportPromises = [];
    // Load pipeline
    const registry = await loadPipeline(file, workdir);
    const planConfig = registry.plans.find((p) => p.config.name === plan);
    if (!planConfig) {
        const available = registry.plans.map((p) => p.config.name).join(', ');
        throw new Error(`Plan "${plan}" not found in ${file}\nAvailable plans: ${available}`);
    }
    const secrets = resolveSecrets(collectSecrets(planConfig.expand()), options.capabilities);
    // Create reporter for callbacks
    const reporter = {
        emit(event, data) {
            switch (event) {
                case 'task_start':
                    options.onTaskStart(data.task);
                    break;
                case 'task_end':
                    options.onTaskEnd(data.task, data.status, data.duration);
                    if (data.status === 'failed' && data.error) {
                        options.onLog(`Task failed: ${formatError(data.error)}`);
                    }
                    break;
                case 'step_start':
                    options.onStepStart(data.step);
                    break;
                case 'step_end':
                    options.onStepEnd(data.step);
                    if (data.status === 'failed' && data.error) {
                        options.onLog(`Step failed: ${formatError(data.error)}`);
                    }
                    break;
                case 'log':
                    options.onLog(data.message);
                    break;
                case 'cmd':
                    options.onLog(`$ ${data.cmd}`);
                    break;
                case 'report':
                    reportPromises.push(options.capabilities.reports.process({
                        kind: 'report',
                        reportType: data.reportType,
                        path: data.path,
                    }, data.task));
                    break;
            }
        },
    };
    // Set up abort controller for timeout
    const controller = new AbortController();
    const timeoutMs = parseTimeout(timeout);
    const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
    try {
        // Filter to specific task if requested
        const runOptions = {
            workspace: workdir,
            failFast,
            secrets,
            reporter,
            signal: controller.signal,
        };
        // Run the plan
        const result = await runPlan(registry, plan, runOptions);
        await Promise.allSettled(reportPromises);
        // If a specific task was requested, filter results
        if (task) {
            const taskResult = result.tasks[task];
            if (!taskResult) {
                throw new Error(`Task "${task}" not found in plan "${plan}"`);
            }
            return {
                plan: result.plan,
                duration: taskResult.duration,
                tasks: { [task]: taskResult },
            };
        }
        return result;
    }
    finally {
        clearTimeout(timeoutId);
    }
}
//# sourceMappingURL=runner.js.map