/**
 * Task execution for skills.
 * Callback-based state updates for CLI and Ink rendering.
 */
import { prepareFiles, analyzeFile, aggregateUsage, deduplicateFindings, generateSummary, } from '../../sdk/runner.js';
import chalk from 'chalk';
import figures from 'figures';
import { Verbosity } from './verbosity.js';
import { ICON_CHECK, ICON_SKIPPED } from './icons.js';
import { formatDuration } from './formatters.js';
/**
 * Run a single skill task.
 */
export async function runSkillTask(options, fileConcurrency, callbacks) {
    const { name, displayName = name, failOn, resolveSkill, context, runnerOptions = {} } = options;
    const startTime = Date.now();
    try {
        // Resolve the skill
        const skill = await resolveSkill();
        // Prepare files (parse patches into hunks)
        const { files: preparedFiles, skippedFiles } = prepareFiles(context, {
            contextLines: runnerOptions.contextLines,
        });
        if (preparedFiles.length === 0) {
            // No files to analyze - skip
            callbacks.onSkillSkipped(name);
            return {
                name,
                report: {
                    skill: skill.name,
                    summary: 'No code changes to analyze',
                    findings: [],
                    usage: { inputTokens: 0, outputTokens: 0, costUSD: 0 },
                    skippedFiles: skippedFiles.length > 0 ? skippedFiles : undefined,
                },
                failOn,
            };
        }
        // Initialize file states
        const fileStates = preparedFiles.map((file) => ({
            filename: file.filename,
            status: 'pending',
            currentHunk: 0,
            totalHunks: file.hunks.length,
            findings: [],
        }));
        // Notify skill start
        callbacks.onSkillStart({
            name,
            displayName,
            status: 'running',
            startTime,
            files: fileStates,
            findings: [],
        });
        // Build PR context for inclusion in prompts (if available)
        const prContext = context.pullRequest
            ? {
                changedFiles: context.pullRequest.files.map((f) => f.filename),
                title: context.pullRequest.title,
                body: context.pullRequest.body,
            }
            : undefined;
        // Process files with concurrency
        const processFile = async (prepared, index) => {
            const filename = prepared.filename;
            // Update file state to running
            callbacks.onFileUpdate(name, filename, { status: 'running' });
            const fileCallbacks = {
                skillStartTime: startTime,
                onHunkStart: (hunkNum, totalHunks) => {
                    callbacks.onFileUpdate(name, filename, {
                        currentHunk: hunkNum,
                        totalHunks,
                    });
                },
                onHunkComplete: (_hunkNum, findings) => {
                    // Accumulate findings for this file
                    const current = fileStates[index];
                    if (current) {
                        current.findings.push(...findings);
                    }
                },
                onLargePrompt: callbacks.onLargePrompt
                    ? (lineRange, chars, estimatedTokens) => {
                        callbacks.onLargePrompt?.(name, filename, lineRange, chars, estimatedTokens);
                    }
                    : undefined,
                onPromptSize: callbacks.onPromptSize
                    ? (lineRange, systemChars, userChars, totalChars, estimatedTokens) => {
                        callbacks.onPromptSize?.(name, filename, lineRange, systemChars, userChars, totalChars, estimatedTokens);
                    }
                    : undefined,
            };
            const result = await analyzeFile(skill, prepared, context.repoPath, runnerOptions, fileCallbacks, prContext);
            // Update file state to done
            callbacks.onFileUpdate(name, filename, {
                status: 'done',
                findings: result.findings,
            });
            return { findings: result.findings, usage: result.usage, failedHunks: result.failedHunks };
        };
        // Process files in batches with concurrency
        const allResults = [];
        for (let i = 0; i < preparedFiles.length; i += fileConcurrency) {
            const batch = preparedFiles.slice(i, i + fileConcurrency);
            const batchResults = await Promise.all(batch.map((file, batchIndex) => processFile(file, i + batchIndex)));
            allResults.push(...batchResults);
        }
        // Build report
        const duration = Date.now() - startTime;
        const allFindings = allResults.flatMap((r) => r.findings);
        const allUsage = allResults.map((r) => r.usage).filter((u) => u !== undefined);
        const totalFailedHunks = allResults.reduce((sum, r) => sum + r.failedHunks, 0);
        const uniqueFindings = deduplicateFindings(allFindings);
        const report = {
            skill: skill.name,
            summary: generateSummary(skill.name, uniqueFindings),
            findings: uniqueFindings,
            usage: aggregateUsage(allUsage),
            durationMs: duration,
        };
        if (skippedFiles.length > 0) {
            report.skippedFiles = skippedFiles;
        }
        if (totalFailedHunks > 0) {
            report.failedHunks = totalFailedHunks;
        }
        // Notify skill complete
        callbacks.onSkillUpdate(name, {
            status: 'done',
            durationMs: duration,
            findings: uniqueFindings,
            usage: report.usage,
        });
        callbacks.onSkillComplete(name, report);
        return { name, report, failOn };
    }
    catch (err) {
        const errorMessage = err instanceof Error ? err.message : String(err);
        callbacks.onSkillError(name, errorMessage);
        return { name, error: err, failOn };
    }
}
/**
 * Run multiple skill tasks with optional concurrency.
 * Uses callbacks to report progress for Ink rendering.
 */
export async function runSkillTasks(tasks, options, callbacks) {
    const { mode, verbosity, concurrency } = options;
    // File-level concurrency (within each skill)
    const fileConcurrency = 5;
    // Create default callbacks that output to console
    const defaultCallbacks = {
        onSkillStart: (_skill) => {
            // We don't log start - we'll log completion with duration
        },
        onSkillUpdate: () => { },
        onFileUpdate: () => { },
        onSkillComplete: (name, report) => {
            if (verbosity === Verbosity.Quiet)
                return;
            const task = tasks.find((t) => t.name === name);
            const displayName = task?.displayName ?? name;
            const duration = report.durationMs ? ` ${chalk.dim(`[${formatDuration(report.durationMs)}]`)}` : '';
            if (mode.isTTY) {
                console.error(`${chalk.green(ICON_CHECK)} ${displayName}${duration}`);
            }
            else {
                console.log(`${ICON_CHECK} ${displayName}`);
            }
        },
        onSkillSkipped: (name) => {
            if (verbosity === Verbosity.Quiet)
                return;
            const task = tasks.find((t) => t.name === name);
            const displayName = task?.displayName ?? name;
            if (mode.isTTY) {
                console.error(`${chalk.yellow(ICON_SKIPPED)} ${displayName} ${chalk.dim('[skipped]')}`);
            }
            else {
                console.log(`${ICON_SKIPPED} ${displayName} [skipped]`);
            }
        },
        onSkillError: (name, error) => {
            if (verbosity === Verbosity.Quiet)
                return;
            const task = tasks.find((t) => t.name === name);
            const displayName = task?.displayName ?? name;
            if (mode.isTTY) {
                console.error(`${chalk.red('\u2717')} ${displayName} - ${chalk.red(error)}`);
            }
            else {
                console.error(`\u2717 ${displayName} - Error: ${error}`);
            }
        },
        // Warn about large prompts (always shown unless quiet)
        onLargePrompt: (skillName, filename, lineRange, chars, estimatedTokens) => {
            if (verbosity === Verbosity.Quiet)
                return;
            const location = `${filename}:${lineRange}`;
            const size = `${Math.round(chars / 1000)}k chars (~${Math.round(estimatedTokens / 1000)}k tokens)`;
            if (mode.isTTY) {
                console.error(`${chalk.yellow(figures.warning)}  Large prompt for ${location}: ${size}`);
            }
            else {
                console.error(`WARN: Large prompt for ${location}: ${size}`);
            }
        },
        // Debug mode: show prompt sizes
        onPromptSize: verbosity >= Verbosity.Debug
            ? (_skillName, filename, lineRange, systemChars, userChars, totalChars, estimatedTokens) => {
                const location = `${filename}:${lineRange}`;
                if (mode.isTTY) {
                    console.error(chalk.dim(`[debug] Prompt for ${location}: system=${systemChars}, user=${userChars}, total=${totalChars} chars (~${estimatedTokens} tokens)`));
                }
                else {
                    console.error(`DEBUG: Prompt for ${location}: system=${systemChars}, user=${userChars}, total=${totalChars} chars (~${estimatedTokens} tokens)`);
                }
            }
            : undefined,
    };
    const effectiveCallbacks = callbacks ?? defaultCallbacks;
    // Output SKILLS header
    if (verbosity !== Verbosity.Quiet && tasks.length > 0) {
        if (mode.isTTY) {
            console.error(chalk.bold('SKILLS'));
        }
        else {
            console.error('SKILLS');
        }
    }
    const results = [];
    if (concurrency <= 1) {
        // Sequential execution
        for (const task of tasks) {
            const result = await runSkillTask(task, fileConcurrency, effectiveCallbacks);
            results.push(result);
        }
    }
    else {
        // Parallel execution with concurrency limit
        for (let i = 0; i < tasks.length; i += concurrency) {
            const batch = tasks.slice(i, i + concurrency);
            const batchResults = await Promise.all(batch.map((task) => runSkillTask(task, fileConcurrency, effectiveCallbacks)));
            results.push(...batchResults);
        }
    }
    return results;
}
//# sourceMappingURL=tasks.js.map