import { logInfo } from '../utils';

/**
 * AsyncLockManager: Prevents race conditions by ensuring only one task
 * can access a specific resource (e.g., a file path) at a time.
 *
 * Includes timeout protection to prevent indefinite lock-waiting,
 * and proper cleanup on acquisition failure.
 *
 * ─ v2 (unique token) ─────────────────────────────────────────────────────
 * 옛 구현은 cleanup 분기에서 `this.locks.get(resourceId) === previousLock.then(() => newLock)`
 * 으로 *Promise 객체 동일성* 을 비교했는데, `.then(...)` 은 매 호출마다 *새 Promise
 * instance* 를 반환해서 사실상 *항상 false* — cleanup 이 안 됨. 또 release 시점의
 * `delete(resourceId)` 도 latest 검증 없이 무조건 호출돼서, 같은 resource 에 연쇄
 * 호출이 있으면 다른 task 의 entry 를 silent 로 지우는 race.
 *
 * 각 entry 에 고유 symbol token 을 부여하고, cleanup / release 시 *내 token 이 아직
 * Map 의 latest 인지* 비교해서 안전하게 정리한다.
 */

interface LockEntry {
    /** Previous lock chain + new lock — await 대상. */
    promise: Promise<void>;
    /** 이 entry 의 고유 식별자 — cleanup 시 자기 것만 지우게. */
    token: symbol;
}

export class AsyncLockManager {
    private locks: Map<string, LockEntry> = new Map();
    private static readonly DEFAULT_TIMEOUT_MS = 30_000;

    /**
     * Acquires a lock for a specific resource.
     * If the resource is already locked, waits until the previous task finishes.
     * Times out after `timeoutMs` to prevent deadlocks.
     *
     * @returns A release function that MUST be called when the work is done (use try/finally).
     */
    public async acquire(resourceId: string, timeoutMs: number = AsyncLockManager.DEFAULT_TIMEOUT_MS): Promise<() => void> {
        const previousEntry = this.locks.get(resourceId);
        const previousPromise = previousEntry?.promise ?? Promise.resolve();

        const token = Symbol(`lock:${resourceId}`);
        let release: () => void;
        const newPromise = new Promise<void>((resolve) => {
            release = resolve;
        });

        const myEntry: LockEntry = {
            promise: previousPromise.then(() => newPromise),
            token,
        };
        this.locks.set(resourceId, myEntry);

        // Wait for previous lock with a timeout to prevent deadlocks.
        const timeoutPromise = new Promise<never>((_, reject) => {
            setTimeout(() => reject(new Error(`Lock acquisition timed out for resource: ${resourceId}`)), timeoutMs);
        });

        try {
            await Promise.race([previousPromise, timeoutPromise]);
        } catch (error) {
            // 내 token 이 아직 latest 면만 정리 — newer entry 가 있으면 그 task 가 관리.
            if (this.locks.get(resourceId)?.token === token) {
                this.locks.delete(resourceId);
            }
            release!();
            throw error;
        }

        logInfo(`Lock acquired for: ${resourceId}`);

        return () => {
            logInfo(`Lock released for: ${resourceId}`);
            release();
            // 내 token 이 latest 일 때만 Map 정리 — newer entry 가 등록돼 있으면
            // 그 task 가 자기 release 시 정리. 옛 코드는 무조건 delete 해서 race.
            if (this.locks.get(resourceId)?.token === token) {
                this.locks.delete(resourceId);
            }
        };
    }

    /**
     * Returns the number of currently held locks (for diagnostics).
     */
    public getActiveLockCount(): number {
        return this.locks.size;
    }
}

// Export as a singleton for the entire agent process
export const lockManager = new AsyncLockManager();
