// tools/spec-lock-handler.ts — Business logic for spec locking tools (SPEC-301)
import { getLock, saveLock, deleteLock, listActiveLocks, isExpired, } from '../storage/spec-lock-store.js';
import { listActiveCrossProcessLocks } from '../engine/safety/cross-process-lock.js';
// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------
const MIN_TTL_MINUTES = 1;
const MAX_TTL_MINUTES = 480;
const DEFAULT_TTL_MINUTES = 30;
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
function clampTtl(ttlMinutes) {
    const ttl = ttlMinutes ?? DEFAULT_TTL_MINUTES;
    return Math.min(Math.max(ttl, MIN_TTL_MINUTES), MAX_TTL_MINUTES);
}
function buildExpiresAt(ttlMinutes) {
    return new Date(Date.now() + ttlMinutes * 60 * 1000).toISOString();
}
// ---------------------------------------------------------------------------
// Handlers
// ---------------------------------------------------------------------------
/** Acquire a lock on a spec. Fails if already locked by a different holder. */
export async function handleLockSpec(input) {
    const { projectPath, specId, lockHolder, reason } = input;
    const ttlMinutes = clampTtl(input.ttlMinutes);
    try {
        const existing = await getLock(projectPath, specId);
        if (existing !== null && !isExpired(existing) && existing.lockHolder !== lockHolder) {
            return {
                content: [
                    {
                        type: 'text',
                        text: `Spec ${specId} is already locked by "${existing.lockHolder}" ` +
                            `(expires ${existing.expiresAt}). ` +
                            `Use unlock_spec with force=true to override.`,
                    },
                ],
                isError: true,
            };
        }
        const now = new Date().toISOString();
        const lock = {
            specId,
            lockHolder,
            lockedAt: now,
            expiresAt: buildExpiresAt(ttlMinutes),
            ...(reason !== undefined ? { reason } : {}),
        };
        await saveLock(projectPath, specId, lock);
        return {
            content: [
                {
                    type: 'text',
                    text: `Spec ${specId} locked by "${lockHolder}" for ${String(ttlMinutes)} minutes ` +
                        `(expires ${lock.expiresAt}).`,
                },
            ],
        };
    }
    catch (error) {
        const message = error instanceof Error ? error.message : String(error);
        return {
            content: [{ type: 'text', text: `Failed to lock spec: ${message}` }],
            isError: true,
        };
    }
}
/** Release a lock on a spec. */
export async function handleUnlockSpec(input) {
    const { projectPath, specId, lockHolder, force = false } = input;
    try {
        const existing = await getLock(projectPath, specId);
        if (existing === null || isExpired(existing)) {
            return {
                content: [{ type: 'text', text: `Spec ${specId} is not locked.` }],
            };
        }
        if (existing.lockHolder !== lockHolder && !force) {
            return {
                content: [
                    {
                        type: 'text',
                        text: `Cannot unlock: spec ${specId} is held by "${existing.lockHolder}", ` +
                            `not "${lockHolder}". Use force=true to override.`,
                    },
                ],
                isError: true,
            };
        }
        await deleteLock(projectPath, specId);
        return {
            content: [{ type: 'text', text: `Spec ${specId} unlocked successfully.` }],
        };
    }
    catch (error) {
        const message = error instanceof Error ? error.message : String(error);
        return {
            content: [{ type: 'text', text: `Failed to unlock spec: ${message}` }],
            isError: true,
        };
    }
}
/** List all active (non-expired) locks for a project. */
export async function handleListLocks(input) {
    const { projectPath } = input;
    try {
        // SPEC-301: legacy manual locks (data/projects/.../locks/*.json)
        const legacyLocks = await listActiveLocks(projectPath);
        // SPEC-719: cross-process disk locks (data/.locks/planu/*.lock)
        const crossProcessLocks = await listActiveCrossProcessLocks(projectPath).catch(() => []);
        const totalCount = legacyLocks.length + crossProcessLocks.length;
        if (totalCount === 0) {
            return {
                content: [{ type: 'text', text: 'No active locks found.' }],
            };
        }
        const legacyRows = legacyLocks.map((l) => `[manual] ${l.specId}: held by "${l.lockHolder}" until ${l.expiresAt}` +
            (l.reason !== undefined ? ` (${l.reason})` : ''));
        const crossRows = crossProcessLocks.map((l) => `[cross-process] ${l.specId}: pid=${String(l.pid)} session=${l.sessionId} host=${l.hostname}` +
            ` acquired=${l.acquiredAt}` +
            (l.reason !== undefined ? ` reason=${l.reason}` : ''));
        const allRows = [...legacyRows, ...crossRows];
        return {
            content: [
                {
                    type: 'text',
                    text: `Active locks (${String(totalCount)}):\n${allRows.join('\n')}`,
                },
            ],
        };
    }
    catch (error) {
        const message = error instanceof Error ? error.message : String(error);
        return {
            content: [{ type: 'text', text: `Failed to list locks: ${message}` }],
            isError: true,
        };
    }
}
/** Check whether a specific spec is currently locked. */
export async function handleCheckSpecLock(input) {
    const { projectPath, specId } = input;
    try {
        const lock = await getLock(projectPath, specId);
        let result;
        if (lock === null || isExpired(lock)) {
            result = { locked: false };
        }
        else {
            result = {
                locked: true,
                lockHolder: lock.lockHolder,
                expiresAt: lock.expiresAt,
                ...(lock.reason !== undefined ? { reason: lock.reason } : {}),
            };
        }
        const text = result.locked
            ? `Spec ${specId} is locked by "${result.lockHolder}" until ${result.expiresAt ?? ''}.` +
                (result.reason !== undefined ? ` Reason: ${result.reason}` : '')
            : `Spec ${specId} is not locked.`;
        return {
            content: [{ type: 'text', text }],
            structuredContent: result,
        };
    }
    catch (error) {
        const message = error instanceof Error ? error.message : String(error);
        return {
            content: [{ type: 'text', text: `Failed to check lock: ${message}` }],
            isError: true,
        };
    }
}
//# sourceMappingURL=spec-lock-handler.js.map