// engine/living-spec/drift-watcher.ts — AC-6: In-memory drift state cache
// Provides a configurable TTL cache for drift detection results.
const DEFAULT_TTL_SECONDS = 60;
/**
 * Simple glob-to-regex converter for exclude patterns.
 * Supports *, **, and ? wildcards without external deps.
 */
export function globToRegex(pattern) {
    let regex = '';
    let i = 0;
    while (i < pattern.length) {
        const ch = pattern.charAt(i);
        if (ch === '*') {
            const next = i + 1 < pattern.length ? pattern.charAt(i + 1) : '';
            if (next === '*') {
                regex += '.*';
                i += 2;
                if (i < pattern.length && pattern.charAt(i) === '/') {
                    i++;
                }
                continue;
            }
            regex += '[^/]*';
        }
        else if (ch === '?') {
            regex += '[^/]';
        }
        else if ('.+^${}()|[]\\'.includes(ch)) {
            regex += `\\${ch}`;
        }
        else {
            regex += ch;
        }
        i++;
    }
    return new RegExp(`^${regex}$`);
}
/**
 * Check if a file path matches any exclude pattern.
 */
export function matchesExcludePattern(filePath, patterns) {
    return patterns.some((p) => globToRegex(p).test(filePath));
}
/**
 * Create a drift watcher with in-memory caching and configurable TTL.
 */
export function createDriftWatcher(options) {
    const ttlSeconds = options.ttlSeconds ?? DEFAULT_TTL_SECONDS;
    const excludePatterns = options.excludePatterns ?? [];
    const cache = new Map();
    let running = false;
    let startedAt = null;
    function isExpired(entry) {
        return new Date(entry.expiresAt).getTime() < Date.now();
    }
    return {
        start() {
            if (running) {
                return;
            }
            running = true;
            startedAt = new Date().toISOString();
        },
        stop() {
            running = false;
            startedAt = null;
            cache.clear();
        },
        status() {
            for (const [key, entry] of cache) {
                if (isExpired(entry)) {
                    cache.delete(key);
                }
            }
            return {
                running,
                startedAt,
                cacheSize: cache.size,
                projectId: options.projectId,
            };
        },
        invalidate(specId) {
            if (specId) {
                cache.delete(specId);
            }
            else {
                cache.clear();
            }
        },
        getCachedDrift(specId) {
            const entry = cache.get(specId);
            if (!entry) {
                return null;
            }
            if (isExpired(entry)) {
                cache.delete(specId);
                return null;
            }
            if (excludePatterns.length > 0) {
                const filtered = {
                    ...entry,
                    data: {
                        ...entry.data,
                        changedFiles: entry.data.changedFiles.filter((f) => !matchesExcludePattern(f.file, excludePatterns)),
                    },
                };
                return filtered;
            }
            return entry;
        },
        setCachedDrift(specId, data) {
            if (!running) {
                return;
            }
            const now = new Date();
            const expiresAt = new Date(now.getTime() + ttlSeconds * 1000).toISOString();
            cache.set(specId, {
                specId,
                data,
                cachedAt: now.toISOString(),
                expiresAt,
            });
        },
    };
}
//# sourceMappingURL=drift-watcher.js.map