[tab] src/capture/actionDeduplicator.ts (typescript)
/**
 * Action Deduplicator Module
 * Prevents double-capturing of user actions that are seen by both
 * VS Code API and filesystem watcher
 */

import { computeContentHash } from './filesystemWatcher'
import type { RecentAction } from '../types'

// Configuration
const DEDUP_WINDOW_MS = 500 // Time window for deduplication

// Recent user actions tracked for deduplication
// Key: file path, Value: recent action info
const recentUserActions = new Map<string, RecentAction>()

// Cleanup interval
let cleanupIntervalId: NodeJS.Timeout | null = null
const CLEANUP_INTERVAL_MS = 5000 // Clean up old entries every 5 seconds

/**
 * Record a user action captured via VS Code API
 * This marks the action so that duplicate filesystem events can be detected
 */
export function recordUserAction(filePath: string, contentHash: string): void {
	recentUserActions.set(filePath, {
		timestamp: Date.now(),
		contentHash
	})
}

/**
 * Check if a filesystem event is a duplicate of a recent user action
 * Returns true if the event should be skipped (it's a duplicate)
 */
export function isDuplicateAction(filePath: string, contentHash: string): boolean {
	const recent = recentUserActions.get(filePath)
	if (!recent) {
		return false
	}

	const timeDiff = Date.now() - recent.timestamp
	if (timeDiff >= DEDUP_WINDOW_MS) {
		// Outside deduplication window
		return false
	}

	// Within window and same content hash = duplicate
	return recent.contentHash === contentHash
}

/**
 * Record a user action from a document change event
 * Convenience method that computes the hash from document content
 */
export function recordUserActionFromContent(filePath: string, content: string): void {
	const hash = computeContentHash(content)
	recordUserAction(filePath, hash)
}

/**
 * Check if a filesystem event is a duplicate using file content
 * Convenience method that computes the hash from content
 */
export function isDuplicateFromContent(filePath: string, content: string): boolean {
	const hash = computeContentHash(content)
	return isDuplicateAction(filePath, hash)
}

/**
 * Clean up old entries from the deduplication map
 */
function cleanupOldEntries(): void {
	const now = Date.now()
	const expiredPaths: string[] = []

	for (const [filePath, action] of recentUserActions) {
		if (now - action.timestamp > DEDUP_WINDOW_MS * 2) {
			// Entry is well past the deduplication window, safe to remove
			expiredPaths.push(filePath)
		}
	}

	for (const path of expiredPaths) {
		recentUserActions.delete(path)
	}
}

/**
 * Initialize the action deduplicator
 */
export function initializeActionDeduplicator(): void {
	// Start periodic cleanup
	cleanupIntervalId = setInterval(cleanupOldEntries, CLEANUP_INTERVAL_MS)
}

/**
 * Cleanup the action deduplicator
 */
export function cleanupActionDeduplicator(): void {
	if (cleanupIntervalId) {
		clearInterval(cleanupIntervalId)
		cleanupIntervalId = null
	}
	recentUserActions.clear()
}

/**
 * Reset the deduplicator state
 */
export function resetActionDeduplicator(): void {
	recentUserActions.clear()
}

/**
 * Get the number of tracked actions (for debugging/testing)
 */
export function getTrackedActionCount(): number {
	return recentUserActions.size
}