import { existsSync } from 'node:fs';
import path from 'node:path';
export class WorkflowCompletionError extends Error {
    completionReason;
    constructor(message, completionReason) {
        super(message);
        this.name = 'WorkflowCompletionError';
        this.completionReason = completionReason;
    }
}
export function runVerification(check, output, stepName, injectedTaskText, options = {}, sideEffects = {}) {
    const cwd = options.cwd ?? process.cwd();
    const fail = (message) => {
        const observedAt = new Date().toISOString();
        sideEffects.recordStepToolSideEffect?.(stepName, {
            type: 'verification_observed',
            detail: message,
            observedAt,
            raw: { passed: false, type: check.type, value: check.value },
        });
        sideEffects.getOrCreateStepEvidenceRecord?.(stepName).evidence.coordinationSignals.push({
            kind: 'verification_failed',
            source: 'verification',
            text: message,
            observedAt,
            value: check.value,
        });
        if (options.allowFailure) {
            return {
                passed: false,
                completionReason: 'failed_verification',
                error: message,
            };
        }
        throw new WorkflowCompletionError(message, 'failed_verification');
    };
    switch (check.type) {
        case 'output_contains': {
            const token = check.value;
            if (!checkOutputContains(output, token, injectedTaskText)) {
                return fail(`Verification failed for "${stepName}": output does not contain "${token}"`);
            }
            break;
        }
        case 'exit_code':
            if (!checkExitCode(check.value)) {
                return fail(`Verification failed for "${stepName}": exit code did not match "${check.value}"`);
            }
            break;
        case 'file_exists':
            if (!checkFileExists(check.value, cwd)) {
                return fail(`Verification failed for "${stepName}": file "${check.value}" does not exist`);
            }
            break;
        case 'custom':
            return { passed: false };
        default:
            break;
    }
    if (options.completionMarkerFound === false) {
        sideEffects.log?.(`[${stepName}] Verification passed without legacy STEP_COMPLETE marker; allowing completion`);
    }
    const observedAt = new Date().toISOString();
    const successMessage = options.completionMarkerFound === false
        ? 'Verification passed without legacy STEP_COMPLETE marker'
        : 'Verification passed';
    sideEffects.recordStepToolSideEffect?.(stepName, {
        type: 'verification_observed',
        detail: successMessage,
        observedAt,
        raw: { passed: true, type: check.type, value: check.value },
    });
    sideEffects.getOrCreateStepEvidenceRecord?.(stepName).evidence.coordinationSignals.push({
        kind: 'verification_passed',
        source: 'verification',
        text: successMessage,
        observedAt,
        value: check.value,
    });
    return {
        passed: true,
        completionReason: 'completed_verified',
    };
}
export function stripInjectedTaskEcho(output, injectedTaskText) {
    if (!injectedTaskText) {
        return output;
    }
    const candidates = [
        injectedTaskText,
        injectedTaskText.replace(/\r\n/g, '\n'),
        injectedTaskText.replace(/\n/g, '\r\n'),
    ].filter((candidate, index, all) => candidate.length > 0 && all.indexOf(candidate) === index);
    for (const candidate of candidates) {
        const start = output.indexOf(candidate);
        if (start !== -1) {
            return output.slice(0, start) + output.slice(start + candidate.length);
        }
    }
    return output;
}
export function checkExitCode(_expectedExitCode) {
    // Existing runner semantics treat process success as established before this
    // verification hook runs, so this check is currently an unconditional pass.
    return true;
}
export function checkOutputContains(output, token, injectedTaskText) {
    if (!token) {
        return false;
    }
    return stripInjectedTaskEcho(output, injectedTaskText).includes(token);
}
export function checkFileExists(filePath, cwd = process.cwd()) {
    const normalizedCwd = path.resolve(cwd);
    const resolved = path.isAbsolute(filePath) ? path.resolve(filePath) : path.resolve(normalizedCwd, filePath);
    // Relative artifact paths stay scoped to the workflow cwd; absolute paths
    // are already explicit and are allowed for temp/output artifacts.
    if (!path.isAbsolute(filePath) &&
        !resolved.startsWith(normalizedCwd + path.sep) &&
        resolved !== normalizedCwd) {
        return false;
    }
    return existsSync(resolved);
}
//# sourceMappingURL=verification.js.map