"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.validateStepBatchReconciliationRequirements = validateStepBatchReconciliationRequirements;
exports.validateWorkflowPlanStepGraph = validateWorkflowPlanStepGraph;
const constants_1 = require("./constants");
const types_1 = require("./types");
const validation_issues_1 = require("./validation-issues");
const workflow_surface_1 = require("./workflow-surface");
function validateStepBatchReconciliationRequirements(decision, workflow, decisionMode, context) {
    if (decisionMode !== types_1.WORKER_RUN_MODES.workflowReconcileStepBatch) {
        if (decision.stepResults !== undefined) {
            (0, validation_issues_1.addWorkerZodValidationIssue)(context, {
                path: ["stepResults"],
                message: "stepResults is only allowed when workflow mode is workflow_reconcile_step_batch.",
            });
        }
        return;
    }
    const expectedStepIds = new Set(workflow?.stepBatchReconciliation?.steps.map((step) => step.id) ?? []);
    if (decision.stepResults === undefined || decision.stepResults.length === 0) {
        (0, validation_issues_1.addWorkerZodValidationIssue)(context, {
            path: ["stepResults"],
            message: "stepResults is required when workflow mode is workflow_reconcile_step_batch.",
        });
        return;
    }
    const receivedStepIds = new Set();
    decision.stepResults.forEach((stepResult, index) => {
        if (receivedStepIds.has(stepResult.stepId)) {
            (0, validation_issues_1.addWorkerZodValidationIssue)(context, {
                path: ["stepResults", index, "stepId"],
                message: "stepResults must not contain duplicate stepId values.",
            });
        }
        receivedStepIds.add(stepResult.stepId);
        if (!expectedStepIds.has(stepResult.stepId)) {
            (0, validation_issues_1.addWorkerZodValidationIssue)(context, {
                path: ["stepResults", index, "stepId"],
                message: "stepResults must reference only steps from the active step batch.",
            });
        }
        validateStepResultProceedEvidence(stepResult, context, [
            "stepResults",
            index,
        ]);
        validateStepResultHumanFacingComment(stepResult, context, [
            "stepResults",
            index,
        ]);
    });
    for (const expectedStepId of expectedStepIds) {
        if (!receivedStepIds.has(expectedStepId)) {
            (0, validation_issues_1.addWorkerZodValidationIssue)(context, {
                path: ["stepResults"],
                message: "stepResults must include a result for every active step batch step.",
            });
            return;
        }
    }
    validateBatchOutcomeConsistency(decision, context);
}
function validateWorkflowPlanStepGraph(decision, workflow, context) {
    if (workflow?.mode !== types_1.WORKER_RUN_MODES.workflowPlan ||
        decision.plan === undefined ||
        decision.plan === null) {
        return;
    }
    const workflowPlan = decision.plan;
    const stepIds = new Set(workflowPlan.steps.map((step) => step.id));
    const seenStepIds = new Set();
    workflowPlan.steps.forEach((step, index) => {
        if (seenStepIds.has(step.id)) {
            (0, validation_issues_1.addWorkerZodValidationIssue)(context, {
                path: ["plan", "steps", index, "id"],
                message: "Workflow plan step IDs must be unique.",
            });
        }
        seenStepIds.add(step.id);
        if (!(0, workflow_surface_1.isIssueWorkflowSurface)(workflow) && step.kind === "child_issue") {
            (0, validation_issues_1.addWorkerZodValidationIssue)(context, {
                path: ["plan", "steps", index, "kind"],
                message: "child_issue steps are only allowed on issue workflow surface.",
            });
        }
        if (step.dependsOnStepIds.some((dependencyId) => dependencyId === step.id || !stepIds.has(dependencyId))) {
            (0, validation_issues_1.addWorkerZodValidationIssue)(context, {
                path: ["plan", "steps", index, "dependsOnStepIds"],
                message: "dependsOnStepIds must reference other step IDs from the same plan.",
            });
        }
    });
    if (hasPlanStepDependencyCycle(workflowPlan.steps)) {
        (0, validation_issues_1.addWorkerZodValidationIssue)(context, {
            path: ["plan", "steps"],
            message: "dependsOnStepIds must not contain cycles.",
        });
    }
}
function validateBatchOutcomeConsistency(decision, context) {
    const stepResults = decision.stepResults ?? [];
    if (stepResults.some((stepResult) => stepResult.outcome === constants_1.WORKER_OUTCOMES.fail)) {
        if (decision.outcome !== constants_1.WORKER_OUTCOMES.fail) {
            addBatchOutcomeMismatchIssue(context);
        }
        return;
    }
    if (stepResults.some((stepResult) => stepResult.outcome === constants_1.WORKER_OUTCOMES.waitHuman)) {
        if (decision.outcome !== constants_1.WORKER_OUTCOMES.waitHuman) {
            addBatchOutcomeMismatchIssue(context);
        }
        return;
    }
    if (decision.outcome === constants_1.WORKER_OUTCOMES.proceed) {
        return;
    }
    addBatchOutcomeMismatchIssue(context);
}
function addBatchOutcomeMismatchIssue(context) {
    (0, validation_issues_1.addWorkerZodValidationIssue)(context, {
        path: ["outcome"],
        message: "workflow_reconcile_step_batch outcome must match stepResults: fail if any step failed, wait_human if any step waits for human input, or proceed when every step proceeded.",
    });
}
function validateStepResultProceedEvidence(decision, context, pathPrefix) {
    if (decision.outcome !== constants_1.WORKER_OUTCOMES.proceed) {
        return;
    }
    if (decision.result.evidence.some((entry) => entry.trim().length > 0)) {
        return;
    }
    (0, validation_issues_1.addWorkerZodValidationIssue)(context, {
        path: [...pathPrefix, "result", "evidence"],
        message: "proceed decisions must include non-empty completion evidence in result.evidence.",
    });
}
function validateStepResultHumanFacingComment(decision, context, pathPrefix) {
    if (decision.outcome === constants_1.WORKER_OUTCOMES.proceed) {
        return;
    }
    if (decision.result.humanComment?.trim()) {
        return;
    }
    (0, validation_issues_1.addWorkerZodValidationIssue)(context, {
        path: [...pathPrefix, "result", "humanComment"],
        message: "wait_human and fail stepResults must include the human-facing reason in result.humanComment.",
    });
}
function hasPlanStepDependencyCycle(steps) {
    const dependencyIdsByStepId = new Map(steps.map((step) => [step.id, step.dependsOnStepIds]));
    const visiting = new Set();
    const visited = new Set();
    const visit = (stepId) => {
        if (visited.has(stepId)) {
            return false;
        }
        if (visiting.has(stepId)) {
            return true;
        }
        visiting.add(stepId);
        for (const dependencyId of dependencyIdsByStepId.get(stepId) ?? []) {
            if (visit(dependencyId)) {
                return true;
            }
        }
        visiting.delete(stepId);
        visited.add(stepId);
        return false;
    };
    return steps.some((step) => visit(step.id));
}
