// engine/hooks/handlers/on-security-check.ts — on-security-check hook handler (SPEC-138)
// Security-sensitive file change detection with cooldown and dependency health.
// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------
const SECURITY_KEYWORDS = [
    'auth',
    'token',
    'secret',
    'password',
    'credential',
    'key',
    'license',
];
const DEPENDENCY_FILES = [
    'package.json',
    'package-lock.json',
    'yarn.lock',
    'pnpm-lock.yaml',
    'bun.lockb',
    'Gemfile',
    'Gemfile.lock',
    'requirements.txt',
    'Pipfile',
    'Pipfile.lock',
    'go.mod',
    'go.sum',
    'Cargo.toml',
    'Cargo.lock',
];
/** Cooldown window: 5 minutes in milliseconds */
const SECURITY_COOLDOWN_MS = 5 * 60 * 1000;
// ---------------------------------------------------------------------------
// Module-level state (keyed by projectPath for per-project isolation)
// ---------------------------------------------------------------------------
const lastSecurityCheckAtMap = new Map();
const lastSecurityCheckResultMap = new Map();
// ---------------------------------------------------------------------------
// Path classification
// ---------------------------------------------------------------------------
/**
 * Returns true if the given path contains security-sensitive keywords.
 */
export function isSecuritySensitiveFile(path) {
    const lower = path.toLowerCase();
    return SECURITY_KEYWORDS.some((keyword) => lower.includes(keyword));
}
/**
 * Returns true if the given path is a dependency manifest or lockfile.
 */
export function isDependencyFile(path) {
    const basename = path.split('/').pop() ?? path;
    return DEPENDENCY_FILES.some((dep) => basename === dep);
}
// ---------------------------------------------------------------------------
// Cooldown management
// ---------------------------------------------------------------------------
/**
 * Returns true if the security check is currently in cooldown for the given project.
 */
/**
 * Returns true if any project's security check is in cooldown (no arg),
 * or if the specific project's check is in cooldown (with arg).
 */
export function isInCooldown(projectPath) {
    if (projectPath !== undefined) {
        const lastAt = lastSecurityCheckAtMap.get(projectPath) ?? 0;
        if (lastAt === 0) {
            return false;
        }
        return Date.now() - lastAt < SECURITY_COOLDOWN_MS;
    }
    // No project specified: return true if any project is in cooldown
    const now = Date.now();
    for (const lastAt of lastSecurityCheckAtMap.values()) {
        if (lastAt > 0 && now - lastAt < SECURITY_COOLDOWN_MS) {
            return true;
        }
    }
    return false;
}
/**
 * Returns remaining cooldown time in seconds for the given project (or last project if omitted).
 */
export function getCooldownRemainingSeconds(projectPath) {
    if (projectPath !== undefined) {
        if (!isInCooldown(projectPath)) {
            return 0;
        }
        const lastAt = lastSecurityCheckAtMap.get(projectPath) ?? 0;
        const elapsed = Date.now() - lastAt;
        return Math.max(0, Math.round((SECURITY_COOLDOWN_MS - elapsed) / 1000));
    }
    // No project specified: return max remaining across all projects
    let max = 0;
    const now = Date.now();
    for (const lastAt of lastSecurityCheckAtMap.values()) {
        if (lastAt > 0) {
            const remaining = Math.max(0, Math.round((SECURITY_COOLDOWN_MS - (now - lastAt)) / 1000));
            if (remaining > max) {
                max = remaining;
            }
        }
    }
    return max;
}
/**
 * Reset the security cooldown timestamp.
 * If projectPath is provided, resets only that project.
 * If omitted, resets all projects.
 * Useful for testing.
 */
export function resetSecurityCooldown(projectPath) {
    if (projectPath !== undefined) {
        lastSecurityCheckAtMap.delete(projectPath);
    }
    else {
        lastSecurityCheckAtMap.clear();
    }
}
/**
 * Reset security cooldown for all projects.
 * Useful for testing.
 */
export function resetAllSecurityCooldowns() {
    lastSecurityCheckAtMap.clear();
}
// ---------------------------------------------------------------------------
// Dependency health findings
// ---------------------------------------------------------------------------
/**
 * Build WorkerFindings for dependency file changes.
 */
export function buildDependencyHealthFindings(depFiles) {
    return depFiles.map((file) => ({
        severity: 'info',
        file,
        message: `Dependency file changed: ${file}`,
        suggestion: 'Review dependency changes for security vulnerabilities or breaking updates',
        category: 'dependency-health',
    }));
}
// ---------------------------------------------------------------------------
// Cache accessors
// ---------------------------------------------------------------------------
/**
 * Get the cached result from the last security check run.
 * If projectPath is omitted, returns the most recently set result across all projects.
 */
export function getLastSecurityCheckResult(projectPath) {
    if (projectPath !== undefined) {
        return lastSecurityCheckResultMap.get(projectPath) ?? null;
    }
    let last = null;
    for (const v of lastSecurityCheckResultMap.values()) {
        last = v ?? null;
    }
    return last;
}
/**
 * Reset the security check result cache.
 * If projectPath is provided, clears only that project's cache.
 * If omitted, clears all projects.
 * Useful for testing.
 */
export function resetSecurityCheckCache(projectPath) {
    if (projectPath !== undefined) {
        lastSecurityCheckResultMap.delete(projectPath);
    }
    else {
        lastSecurityCheckResultMap.clear();
    }
}
/**
 * Reset security check result cache for all projects.
 * Useful for testing.
 */
export function resetAllSecurityCheckCaches() {
    lastSecurityCheckResultMap.clear();
}
// ---------------------------------------------------------------------------
// Handler
// ---------------------------------------------------------------------------
/**
 * Handle on-security-check event for security-sensitive file changes.
 * Implements a 5-minute cooldown to avoid repeated alerts.
 * Also detects dependency file changes and emits dependency-health findings.
 * Caches results as WorkerRunResult.
 * Fire-and-forget — all errors are caught with console.warn.
 */
export function handleOnSecurityCheck(event, projectPath) {
    const startedAt = new Date().toISOString();
    const startMs = Date.now();
    try {
        const findings = [];
        const changedFiles = [event.path];
        // Dependency health check (always runs, no cooldown)
        const depFiles = changedFiles.filter((f) => isDependencyFile(f));
        if (depFiles.length > 0) {
            const depFindings = buildDependencyHealthFindings(depFiles);
            findings.push(...depFindings);
            for (const depFile of depFiles) {
                console.warn(`[hooks] on-security-check: dependency file changed: ${depFile} project=${projectPath}`);
            }
        }
        // Security-sensitive file check (with cooldown)
        if (!isSecuritySensitiveFile(event.path)) {
            // Still cache result if dependency findings exist
            if (findings.length > 0) {
                lastSecurityCheckResultMap.set(projectPath, buildRunResult(startedAt, startMs, 'success', findings, 1));
            }
            return Promise.resolve();
        }
        if (isInCooldown(projectPath)) {
            const remaining = getCooldownRemainingSeconds(projectPath);
            console.warn(`[hooks] on-security-check: cooldown active, skipping check for ${event.path} (${String(remaining)}s remaining)`);
            if (findings.length > 0) {
                lastSecurityCheckResultMap.set(projectPath, buildRunResult(startedAt, startMs, 'skipped', findings, 1));
            }
            return Promise.resolve();
        }
        lastSecurityCheckAtMap.set(projectPath, Date.now());
        console.warn(`[hooks] on-security-check: security check triggered for ${event.path} type=${event.type} project=${projectPath}`);
        findings.push({
            severity: 'high',
            file: event.path,
            message: `Security-sensitive file changed: ${event.path}`,
            suggestion: 'Review security-sensitive file changes carefully before merging',
            category: 'security',
        });
        lastSecurityCheckResultMap.set(projectPath, buildRunResult(startedAt, startMs, 'success', findings, 1));
    }
    catch (err) {
        console.warn(`[hooks] on-security-check: unexpected error for ${event.path}`, err);
        lastSecurityCheckResultMap.set(projectPath, buildRunResult(startedAt, startMs, 'error', [], 0, String(err)));
    }
    return Promise.resolve();
}
// ---------------------------------------------------------------------------
// Internal helpers
// ---------------------------------------------------------------------------
function buildRunResult(startedAt, startMs, status, findings, analyzedFiles, error) {
    return {
        workerName: 'on-security-check',
        startedAt,
        completedAt: new Date().toISOString(),
        durationMs: Date.now() - startMs,
        status,
        findings,
        analyzedFiles,
        ...(error !== undefined ? { error } : {}),
    };
}
//# sourceMappingURL=on-security-check.js.map