// Copyright (c) 2026 Hellmai Ltd
// SPDX-License-Identifier: AGPL-3.0-only
import { getCurrentSessionForWU } from '@lumenflow/agent/auto-session';
import { die } from '@lumenflow/core/error-handler';
import { getGitForCwd } from '@lumenflow/core/git-adapter';
import { ENV_VARS } from '@lumenflow/core/wu-constants';
import { auditOwnershipOverride, normalizeUsername } from './wu-done-preflight.js';
async function resolveCurrentSession(wuId) {
    try {
        const session = getCurrentSessionForWU({ wuId });
        return {
            sessionId: session?.session_id ?? null,
            wuId: session?.wu_id ?? null,
        };
    }
    catch {
        return {
            sessionId: null,
            wuId: null,
        };
    }
}
async function resolveCurrentUser() {
    try {
        const configuredEmail = (await getGitForCwd().getConfigValue('user.email')).trim();
        return configuredEmail || null;
    }
    catch {
        return process.env[ENV_VARS.GIT_USER] || process.env[ENV_VARS.USER] || null;
    }
}
async function buildOverrideAuditEntry(input, currentSession, currentUser) {
    return {
        timestamp: new Date().toISOString(),
        wu_id: input.wuId,
        action: input.action,
        assigned_to: input.doc.assigned_to ?? null,
        claimed_session: input.doc.session_id ?? null,
        active_session: currentSession.sessionId,
        active_session_wu: currentSession.wuId,
        modified_by: currentUser,
        reason: input.overrideReason ?? null,
        git_commit: (await getGitForCwd().getCommitHash()).trim(),
    };
}
function getOverrideReasonError() {
    return `--override-owner requires --reason "<why you're modifying another agent's WU>"`;
}
function buildCrossSessionError(wuId, action, commandExample, currentSession, claimedSessionId) {
    return (`\n❌ CLAIM OWNERSHIP VIOLATION: ${wuId} was claimed by a different session.\n\n` +
        `   Claimed session: ${claimedSessionId ?? 'none'}\n` +
        `   Active session: ${currentSession.sessionId ?? 'none'}\n` +
        `   Active session WU: ${currentSession.wuId ?? 'none'}\n\n` +
        `   WIP limits are stop signals. Do not ${action} another agent's WU to free a lane.\n\n` +
        `   📋 Options:\n` +
        `      1. Wait for the owning WU to complete or block itself\n` +
        `      2. Choose a different lane or fix the lane assignment\n` +
        `      3. Coordinate with the owning agent via mem:signal / mem:inbox / wu:escalate\n\n` +
        `   ⚠️  To override (manual recovery only):\n` +
        `      ${commandExample} --override-owner --reason "<why>"\n\n` +
        `   AGENTS: NEVER use --override-owner without explicit instruction.\n`);
}
function buildActiveSessionMismatchError(wuId, action, commandExample, currentSession) {
    return (`\n❌ SESSION OWNERSHIP VIOLATION: Active session is attached to another WU.\n\n` +
        `   Target WU: ${wuId}\n` +
        `   Active session WU: ${currentSession.wuId ?? 'none'}\n` +
        `   Active session: ${currentSession.sessionId ?? 'none'}\n\n` +
        `   Do not ${action} a different WU from the current agent session just to clear a lane.\n` +
        `   Switch to the owning WU session, or use explicit manual override with approval.\n\n` +
        `   ⚠️  To override (manual recovery only):\n` +
        `      ${commandExample} --override-owner --reason "<why>"\n`);
}
function buildAssignedOwnerError(wuId, action, commandExample, assignedTo, currentUser) {
    return (`\n❌ OWNERSHIP VIOLATION: ${wuId} is assigned to someone else.\n\n` +
        `   Assigned to: ${assignedTo}\n` +
        `   Current user: ${currentUser ?? 'unknown'}\n\n` +
        `   Do not ${action} another agent's WU to free a lane.\n\n` +
        `   ⚠️  To override (manual recovery only):\n` +
        `      ${commandExample} --override-owner --reason "<why>"\n\n` +
        `   AGENTS: NEVER use --override-owner without explicit instruction.\n`);
}
export async function validateStateMutationOwnership(input) {
    const currentSession = await resolveCurrentSession(input.wuId);
    const currentUser = await resolveCurrentUser();
    const assignedTo = typeof input.doc.assigned_to === 'string' && input.doc.assigned_to.trim().length > 0
        ? input.doc.assigned_to
        : null;
    const claimedSessionId = typeof input.doc.session_id === 'string' && input.doc.session_id.trim().length > 0
        ? input.doc.session_id
        : null;
    const allowOverride = async (error) => {
        if (!input.overrideOwner) {
            return { valid: false, error, auditEntry: null };
        }
        if (!input.overrideReason) {
            return {
                valid: false,
                error: getOverrideReasonError(),
                auditEntry: null,
            };
        }
        return {
            valid: true,
            error: null,
            auditEntry: await buildOverrideAuditEntry(input, currentSession, currentUser),
        };
    };
    // WU-2562: session-id identity short-circuit.
    //
    // If the current agent's session_id matches the session_id recorded on
    // the WU YAML at claim time, the current agent IS the one who claimed
    // this WU — regardless of what wu_id the shared current.json singleton
    // happens to be pointing at. This is stronger evidence of ownership
    // than the singleton's wu_id field, which can go stale when:
    //   - A parallel agent claimed a different WU and left current.json behind
    //   - The active agent claimed multiple WUs sequentially and current.json
    //     still reflects an earlier claim
    //   - wu:prep / wu:done / test runs touch the session file between claim
    //     and mutate
    //
    // Must run BEFORE the wuId-mismatch check below, otherwise stale singleton
    // data masks legitimate ownership. See WU-2562 for the full incident.
    if (claimedSessionId && currentSession.sessionId === claimedSessionId) {
        return { valid: true, error: null, auditEntry: null };
    }
    if (currentSession.wuId && currentSession.wuId !== input.wuId) {
        return allowOverride(buildActiveSessionMismatchError(input.wuId, input.action, input.commandExample, currentSession));
    }
    if (claimedSessionId) {
        if (currentSession.sessionId === claimedSessionId) {
            return { valid: true, error: null, auditEntry: null };
        }
        // WU-2514: For release actions, allow the same user to release stale claims
        // without --override-owner. The claimed session is gone (agent restarted),
        // but the assigned user should be able to clean up their own stray claims.
        if (input.allowStaleRelease && assignedTo && currentUser) {
            if (normalizeUsername(assignedTo) === normalizeUsername(currentUser)) {
                return { valid: true, error: null, auditEntry: null };
            }
        }
        return allowOverride(buildCrossSessionError(input.wuId, input.action, input.commandExample, currentSession, claimedSessionId));
    }
    if (assignedTo) {
        if (currentUser && normalizeUsername(assignedTo) === normalizeUsername(currentUser)) {
            return { valid: true, error: null, auditEntry: null };
        }
        return allowOverride(buildAssignedOwnerError(input.wuId, input.action, input.commandExample, assignedTo, currentUser));
    }
    return { valid: true, error: null, auditEntry: null };
}
export async function assertStateMutationOwnership(input) {
    const result = await validateStateMutationOwnership(input);
    if (!result.valid) {
        die(result.error ?? `Ownership validation failed for ${input.wuId}`);
    }
    if (result.auditEntry) {
        console.log(`\n⚠️  --override-owner: ${input.action} ${input.wuId} despite ownership mismatch`);
        console.log(`   Reason: ${input.overrideReason}\n`);
        // WU-2526: worktreePath null here — audit file is gitignored so cwd fallback is safe.
        // wu:done caller uses its own auditOwnershipOverride with derivedWorktree.
        auditOwnershipOverride(result.auditEntry, null);
    }
}
//# sourceMappingURL=wu-state-mutation-ownership.js.map