/**
 * Reject Agent Run Use Case
 *
 * Rejects a paused agent run (waiting_approval status) and
 * spawns a resume worker to iterate with user feedback.
 * Appends rejection feedback to spec.yaml for tracking.
 *
 * Optionally accepts a {@link SupervisorActor}. When provided, the
 * rejection is attributed to that actor in the audit log
 * (`actor_id = user:<id>` or `supervisor:<id>`) and the "user always
 * wins" invariant from spec 093 is enforced — see
 * {@link ApproveAgentRunUseCase} for the full description.
 */
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
    return function (target, key) { decorator(target, key, paramIndex); }
};
import { injectable, inject } from 'tsyringe';
import yaml from 'js-yaml';
import { randomUUID } from 'node:crypto';
import { readFileSync } from 'node:fs';
import { join } from 'node:path';
import { AgentRunStatus } from '../../../domain/generated/output.js';
import { SUPERVISOR_ACTOR_NAMESPACE_SUPERVISOR, SUPERVISOR_ACTOR_NAMESPACE_USER, } from '../../../domain/value-objects/supervisor-actor.js';
const GATE_DECISION_FIELD = 'gate.rejection';
function isGateDecisionField(field) {
    if (!field)
        return false;
    return field === 'gate.approval' || field === 'gate.rejection';
}
let RejectAgentRunUseCase = class RejectAgentRunUseCase {
    agentRunRepository;
    processService;
    featureRepository;
    phaseTimingRepository;
    worktreePaths;
    nodeHelpers;
    phaseTimingContext;
    activityLog;
    constructor(agentRunRepository, processService, featureRepository, phaseTimingRepository, worktreePaths, nodeHelpers, phaseTimingContext, activityLog) {
        this.agentRunRepository = agentRunRepository;
        this.processService = processService;
        this.featureRepository = featureRepository;
        this.phaseTimingRepository = phaseTimingRepository;
        this.worktreePaths = worktreePaths;
        this.nodeHelpers = nodeHelpers;
        this.phaseTimingContext = phaseTimingContext;
        this.activityLog = activityLog;
    }
    async execute(id, feedback, attachments, actor) {
        const run = await this.agentRunRepository.findById(id);
        if (!run) {
            return { rejected: false, reason: 'Agent run not found' };
        }
        if (actor?.namespace === SUPERVISOR_ACTOR_NAMESPACE_SUPERVISOR) {
            const blockedBy = await this.findPriorUserGateDecision(id);
            if (blockedBy) {
                return {
                    rejected: false,
                    reason: 'User has already acted on this gate; supervisor cannot override',
                };
            }
        }
        const REJECTABLE_STATUSES = new Set([
            AgentRunStatus.waitingApproval,
            AgentRunStatus.failed,
            AgentRunStatus.interrupted,
        ]);
        if (!REJECTABLE_STATUSES.has(run.status)) {
            return {
                rejected: false,
                reason: `Agent run is not in a rejectable state (status: ${run.status})`,
            };
        }
        // Validate non-empty feedback
        if (!feedback?.trim()) {
            return { rejected: false, reason: 'Feedback is required for rejection' };
        }
        // Look up feature for spec path
        const feature = run.featureId ? await this.featureRepository.findById(run.featureId) : null;
        if (!feature?.specPath) {
            return { rejected: false, reason: 'Feature has no spec path' };
        }
        // Read and update spec.yaml with rejection feedback
        const specDir = feature.specPath;
        let iteration = 1;
        try {
            const specContent = readFileSync(join(specDir, 'spec.yaml'), 'utf-8');
            const spec = yaml.load(specContent);
            const existingFeedback = Array.isArray(spec?.rejectionFeedback)
                ? spec.rejectionFeedback
                : [];
            iteration = existingFeedback.length + 1;
            // Derive the rejected phase from the agent run's current node
            const rejectedPhase = run.result?.startsWith('node:') ? run.result.slice(5) : undefined;
            const newEntry = {
                iteration,
                message: feedback,
                phase: rejectedPhase,
                timestamp: new Date().toISOString(),
                ...(attachments && attachments.length > 0 ? { attachments } : {}),
            };
            spec.rejectionFeedback = [...existingFeedback, newEntry];
            this.nodeHelpers.writeSpecFileAtomic(specDir, 'spec.yaml', this.nodeHelpers.safeYamlDump(spec));
        }
        catch {
            // If spec.yaml can't be read, still proceed with iteration 1
        }
        // Update run status to running (NOT cancelled)
        const now = new Date();
        await this.agentRunRepository.updateStatus(id, AgentRunStatus.running, {
            updatedAt: now,
        });
        // Record approval wait duration
        try {
            const timings = await this.phaseTimingRepository.findByRunId(id);
            const waitingTiming = timings.find((t) => t.waitingApprovalAt && !t.approvalWaitMs);
            if (waitingTiming) {
                const waitStart = waitingTiming.waitingApprovalAt instanceof Date
                    ? waitingTiming.waitingApprovalAt.getTime()
                    : Number(waitingTiming.waitingApprovalAt);
                const approvalWaitMs = BigInt(now.getTime() - waitStart);
                await this.phaseTimingRepository.updateApprovalWait(waitingTiming.id, {
                    approvalWaitMs,
                });
            }
        }
        catch {
            // Non-fatal
        }
        // Record rejected lifecycle event
        await this.phaseTimingContext.recordLifecycleEvent('run:rejected', id, this.phaseTimingRepository);
        // Spawn worker with rejection payload
        const rejectionPayload = {
            rejected: true,
            feedback,
            iteration,
            ...(attachments && attachments.length > 0 ? { attachments } : {}),
        };
        // Derive worktree path with fallback — the mapper conditionally sets
        // worktreePath only when the DB column is non-null, so compute it if missing.
        const worktreePath = feature.worktreePath ??
            this.worktreePaths.getWorktreePath(feature.repositoryPath, feature.branch);
        this.processService.spawn(run.featureId ?? '', id, feature.repositoryPath, feature.specPath, worktreePath, {
            resume: true,
            approvalGates: run.approvalGates,
            threadId: run.threadId,
            resumeFromInterrupt: true,
            push: feature?.push ?? false,
            openPr: feature?.openPr ?? false,
            forkAndPr: feature?.forkAndPr ?? false,
            commitSpecs: feature?.commitSpecs ?? true,
            ciWatchEnabled: feature?.ciWatchEnabled ?? true,
            enableEvidence: feature?.enableEvidence ?? false,
            commitEvidence: feature?.commitEvidence ?? false,
            resumePayload: JSON.stringify(rejectionPayload),
            agentType: run.agentType,
            ...(run.modelId ? { model: run.modelId } : {}),
            ...(feature.fast ? { fast: true } : {}),
        });
        if (actor) {
            await this.recordGateDecision(id, actor, 'rejected', now);
        }
        return {
            rejected: true,
            reason: 'Rejected and iterating',
            iteration,
            iterationWarning: iteration >= 5,
        };
    }
    /** See {@link ApproveAgentRunUseCase} for the rationale. */
    async findPriorUserGateDecision(runId) {
        try {
            const entries = await this.activityLog.listByWorkItem(runId);
            return entries.find((e) => isGateDecisionField(e.fieldName) &&
                typeof e.actorId === 'string' &&
                e.actorId.startsWith(`${SUPERVISOR_ACTOR_NAMESPACE_USER}:`));
        }
        catch {
            return undefined;
        }
    }
    async recordGateDecision(runId, actor, verdict, timestamp) {
        try {
            const entry = {
                id: randomUUID(),
                workItemId: runId,
                fieldName: GATE_DECISION_FIELD,
                oldValue: undefined,
                newValue: verdict,
                actorId: actor.value,
                createdAt: timestamp,
                updatedAt: timestamp,
            };
            await this.activityLog.create(entry);
        }
        catch {
            // Audit-log outage must not block the gate transition itself.
        }
    }
};
RejectAgentRunUseCase = __decorate([
    injectable(),
    __param(0, inject('IAgentRunRepository')),
    __param(1, inject('IFeatureAgentProcessService')),
    __param(2, inject('IFeatureRepository')),
    __param(3, inject('IPhaseTimingRepository')),
    __param(4, inject('IWorktreePathProvider')),
    __param(5, inject('INodeHelpers')),
    __param(6, inject('IPhaseTimingContext')),
    __param(7, inject('IActivityLogRepository')),
    __metadata("design:paramtypes", [Object, Object, Object, Object, Object, Object, Object, Object])
], RejectAgentRunUseCase);
export { RejectAgentRunUseCase };
