// tools/orchestrate/session-handlers.ts — register, heartbeat, status handlers
import { randomUUID } from 'node:crypto';
import { specStore } from '../../storage/index.js';
import { toHeartbeatResult, toLockResult, toUnlockResult, isLockExpired, } from '../orchestrate-locking.js';
import { checkSpecReadiness } from '../../engine/readiness-checker.js';
import { saveState, getCleanState } from './state.js';
import { ok } from './utils.js';
// ---------------------------------------------------------------------------
// Action: register
// ---------------------------------------------------------------------------
async function handleRegisterCore(projectId, sessionId, specId, fileOwnership) {
    const state = await getCleanState(projectId);
    const now = new Date().toISOString();
    const newSessionId = sessionId ?? `agent-${randomUUID().slice(0, 8)}`;
    const existingIdx = state.sessions.findIndex((s) => s.sessionId === newSessionId);
    if (existingIdx !== -1) {
        const existingSession = state.sessions[existingIdx];
        if (!existingSession) {
            throw new Error(`Session at index ${existingIdx} unexpectedly missing`);
        }
        existingSession.status = 'active';
        existingSession.lastActivity = now;
        if (specId && !existingSession.activeSpecs.includes(specId)) {
            existingSession.activeSpecs.push(specId);
        }
        /* v8 ignore next 3 */
        if (fileOwnership?.length) {
            existingSession.fileOwnership = [...(existingSession.fileOwnership ?? []), ...fileOwnership];
        }
        await saveState(projectId, state);
        const result = {
            action: 'register',
            sessionId: newSessionId,
            sessions: state.sessions,
            message: `Re-activated existing session '${newSessionId}'`,
        };
        return ok(result);
    }
    const conflicts = [];
    if (specId) {
        for (const activeSession of state.sessions.filter((s) => s.status === 'active' && s.activeSpecs.includes(specId))) {
            conflicts.push({
                type: 'spec-overlap',
                agents: [newSessionId, activeSession.sessionId],
                resource: specId,
                resolution: `Spec '${specId}' is already being worked on by '${activeSession.sessionId}'. Coordinate or use lock/unlock.`,
                severity: 'warning',
            });
        }
    }
    const newSession = {
        sessionId: newSessionId,
        agentPlatform: 'custom',
        projectId,
        activeSpecs: specId ? [specId] : [],
        lockedResources: [],
        startedAt: now,
        lastActivity: now,
        status: 'active',
        fileOwnership: fileOwnership ?? [],
    };
    state.sessions.push(newSession);
    state.conflicts.push(...conflicts);
    await saveState(projectId, state);
    const result = {
        action: 'register',
        sessionId: newSessionId,
        sessions: state.sessions,
        conflicts: conflicts.length > 0 ? conflicts : undefined,
        message: `Agent session '${newSessionId}' registered${conflicts.length > 0 ? ` with ${conflicts.length} conflict warning(s)` : ''}`,
    };
    return ok(result);
}
export async function handleRegister(projectId, sessionId, specId, fileOwnership) {
    // SPEC-039: readiness pre-check before spec execution (lenient mode, non-blocking)
    if (specId) {
        const specToCheck = await specStore.getSpec(projectId, specId);
        /* v8 ignore start */
        if (specToCheck) {
            const readiness = await checkSpecReadiness(specToCheck, 'lenient');
            if (!readiness.ready) {
                const registerResult = await handleRegisterCore(projectId, sessionId, specId, fileOwnership);
                const warning = `\n\n[READINESS WARNING] Spec '${specId}' score: ${readiness.score}/100. Blockers: ${readiness.issues.blockers.join('; ') || 'none'}. Run check_readiness for details.`;
                const text = registerResult.content[0] && 'text' in registerResult.content[0]
                    ? registerResult.content[0].text + warning
                    : warning;
                return { content: [{ type: 'text', text }] };
            }
        }
        /* v8 ignore stop */
    }
    return handleRegisterCore(projectId, sessionId, specId, fileOwnership);
}
// ---------------------------------------------------------------------------
// Action: heartbeat
// ---------------------------------------------------------------------------
export async function handleHeartbeat(projectId, sessionId, lockTtlMinutes) {
    const state = await getCleanState(projectId);
    const { mutated, toolResult } = toHeartbeatResult(sessionId, state, lockTtlMinutes);
    if (mutated) {
        await saveState(projectId, mutated);
    }
    return toolResult;
}
// ---------------------------------------------------------------------------
// Action: lock / unlock
// ---------------------------------------------------------------------------
export async function handleLock(projectId, sessionId, resourcePath, specId, lockTtlMinutes, fileOwnership) {
    const state = await getCleanState(projectId);
    const { mutated, toolResult } = toLockResult(sessionId, state, resourcePath, specId, lockTtlMinutes, fileOwnership);
    if (mutated) {
        await saveState(projectId, mutated);
    }
    return toolResult;
}
export async function handleUnlock(projectId, sessionId, resourcePath, specId) {
    const state = await getCleanState(projectId);
    const { mutated, toolResult } = toUnlockResult(sessionId, state, resourcePath, specId);
    if (mutated) {
        await saveState(projectId, mutated);
    }
    return toolResult;
}
// ---------------------------------------------------------------------------
// Action: status
// ---------------------------------------------------------------------------
export async function handleStatus(projectId, experienceLevel) {
    const state = await getCleanState(projectId);
    const activeSessions = state.sessions.filter((s) => s.status === 'active');
    const idleSessions = state.sessions.filter((s) => s.status === 'idle');
    const disconnectedSessions = state.sessions.filter((s) => s.status === 'disconnected');
    const activeLocks = state.locks.filter((l) => !isLockExpired(l));
    const summary = {
        action: 'status',
        totalSessions: state.sessions.length,
        activeSessions: activeSessions.length,
        idleSessions: idleSessions.length,
        disconnectedSessions: disconnectedSessions.length,
        activeLocks: activeLocks.length,
        unresolvedConflicts: state.conflicts.length,
        sessions: state.sessions,
        locks: activeLocks,
        conflicts: state.conflicts.length > 0 ? state.conflicts : undefined,
        message: `${activeSessions.length} active, ${idleSessions.length} idle, ${disconnectedSessions.length} disconnected agent(s). ${activeLocks.length} active lock(s).`,
        experienceLevel: experienceLevel ?? 'expert',
    };
    await saveState(projectId, state);
    return ok(summary);
}
// ---------------------------------------------------------------------------
// Action: resolve-conflict
// ---------------------------------------------------------------------------
export async function handleResolveConflict(projectId, sessionId, specId, resourcePath) {
    const state = await getCleanState(projectId);
    const resourceId = resourcePath ?? specId;
    let resolvedCount = 0;
    state.conflicts = state.conflicts.filter((conflict) => {
        const matchesSession = !sessionId || conflict.agents.includes(sessionId);
        const matchesResource = !resourceId || conflict.resource === resourceId;
        if (matchesSession && matchesResource) {
            resolvedCount++;
            return false;
        }
        return true;
    });
    if (resourceId && sessionId) {
        const lockIdx = state.locks.findIndex((l) => l.resourceId === resourceId && l.lockedBy !== sessionId);
        if (lockIdx !== -1) {
            const lock = state.locks[lockIdx];
            /* v8 ignore next 3 -- defensive: lock always exists at found index */
            if (!lock) {
                throw new Error(`Lock at index ${lockIdx} unexpectedly missing`);
            }
            const prevOwner = lock.lockedBy;
            state.locks.splice(lockIdx, 1);
            const prevSession = state.sessions.find((s) => s.sessionId === prevOwner);
            if (prevSession) {
                prevSession.lockedResources = prevSession.lockedResources.filter((lr) => lr.resourceId !== resourceId);
            }
        }
    }
    await saveState(projectId, state);
    const result = {
        action: 'resolve-conflict',
        sessionId,
        conflicts: state.conflicts.length > 0 ? state.conflicts : undefined,
        message: resolvedCount > 0
            ? `Resolved ${resolvedCount} conflict(s)${resourceId ? ` for resource '${resourceId}'` : ''}`
            : 'No matching conflicts found to resolve',
    };
    return ok(result);
}
//# sourceMappingURL=session-handlers.js.map