"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.coerceWorkflowPlanValue = coerceWorkflowPlanValue;
exports.coerceWorkflowStepValue = coerceWorkflowStepValue;
exports.normalizeWorkflowPlan = normalizeWorkflowPlan;
exports.replaceWorkflowRefStepWithExpandedSteps = replaceWorkflowRefStepWithExpandedSteps;
exports.replaceWorkflowExpansionSubtreeWithWorkflowRef = replaceWorkflowExpansionSubtreeWithWorkflowRef;
exports.normalizeWorkflowStep = normalizeWorkflowStep;
exports.buildWorkflowResultFromDecision = buildWorkflowResultFromDecision;
const catalog_1 = require("../../i18n/catalog");
const types_1 = require("../types");
const coercion_support_1 = require("./coercion-support");
const plan_step_normalization_support_1 = require("./plan-step-normalization-support");
const WORKFLOW_PLAN_FIELD_NAMES = new Set(["steps"]);
function coerceWorkflowPlanValue(value) {
    if (value === null) {
        return null;
    }
    if (!(0, coercion_support_1.isRecord)(value)) {
        return null;
    }
    const plan = value;
    if (!(0, coercion_support_1.hasOnlyRecordKeys)(value, WORKFLOW_PLAN_FIELD_NAMES)) {
        return null;
    }
    const steps = (0, coercion_support_1.coerceArrayItems)(plan.steps, coerceWorkflowStep);
    if (steps === null || !areWorkflowStepDependenciesValid(steps)) {
        return null;
    }
    return {
        steps,
    };
}
function coerceWorkflowStepValue(value) {
    return coerceWorkflowStep(value);
}
function coerceWorkflowStep(value) {
    if (!(0, coercion_support_1.isRecord)(value)) {
        return null;
    }
    const step = value;
    if (typeof step.id !== "string" ||
        step.id.length === 0 ||
        typeof step.title !== "string" ||
        step.title.length === 0 ||
        typeof step.objective !== "string" ||
        step.objective.length === 0) {
        return null;
    }
    const sharedFields = coerceWorkflowStepSharedFields(step);
    if (sharedFields === null) {
        return null;
    }
    const optionalFields = coerceWorkflowStepOptionalFields(step);
    if (optionalFields === null) {
        return null;
    }
    if (step.kind === "child_issue") {
        return coerceChildIssueWorkflowStep(step, sharedFields, optionalFields);
    }
    const result = coerceNullableWorkflowStepResult(step.result);
    if (result === undefined) {
        return null;
    }
    const coercedStep = {
        ...sharedFields,
        result,
    };
    (0, plan_step_normalization_support_1.assignCoercedRawWorkflowStepReferenceFields)(coercedStep, step, optionalFields);
    return coercedStep;
}
function coerceWorkflowStepSharedFields(step) {
    const instructions = (0, coercion_support_1.coerceStringArray)(step.instructions);
    const doneWhen = (0, coercion_support_1.coerceStringArray)(step.doneWhen);
    const humanHandoffWhen = (0, coercion_support_1.coerceStringArray)(step.humanHandoffWhen);
    if (!Object.hasOwn(step, "dependsOnStepIds") ||
        instructions === null ||
        doneWhen === null ||
        humanHandoffWhen === null ||
        !isWorkflowStepKind(step.kind) ||
        typeof step.id !== "string" ||
        step.id.length === 0 ||
        typeof step.title !== "string" ||
        step.title.length === 0 ||
        typeof step.objective !== "string" ||
        step.objective.length === 0) {
        return null;
    }
    const dependsOnStepIds = (0, coercion_support_1.coerceStringArray)(step.dependsOnStepIds);
    if (dependsOnStepIds === null) {
        return null;
    }
    return {
        id: step.id,
        kind: step.kind,
        title: step.title,
        objective: step.objective,
        instructions,
        doneWhen,
        humanHandoffWhen,
        dependsOnStepIds,
    };
}
function coerceWorkflowStepOptionalFields(step) {
    if (hasUnsupportedWorkflowStepFields(step)) {
        return null;
    }
    const originWorkflowRef = coerceWorkflowOriginWorkflowRef(step.originWorkflowRef);
    if (step.originWorkflowRef !== undefined && originWorkflowRef === null) {
        return null;
    }
    const childIssueRef = step.childIssueRef === undefined
        ? undefined
        : (0, coercion_support_1.coerceNullableString)(step.childIssueRef);
    const replacementForStepIds = step.replacementForStepIds === undefined
        ? undefined
        : (0, coercion_support_1.coerceStringArray)(step.replacementForStepIds);
    const suggestedTemplate = step.suggestedTemplate === undefined
        ? undefined
        : step.suggestedTemplate === null
            ? null
            : (0, coercion_support_1.coerceNullableWorkflowTemplateSelector)(step.suggestedTemplate);
    const bindingHints = step.bindingHints === undefined
        ? undefined
        : (0, coercion_support_1.coerceWorkflowChildBindingHints)(step.bindingHints);
    const childFields = coerceWorkflowStepChildOptionalFields(step);
    if ((step.childIssueRef !== undefined && childIssueRef === undefined) ||
        (step.suggestedTemplate !== undefined && suggestedTemplate === undefined) ||
        (step.replacementForStepIds !== undefined &&
            replacementForStepIds === null) ||
        (step.bindingHints !== undefined &&
            (bindingHints === undefined || bindingHints === null)) ||
        childFields === null) {
        return null;
    }
    const optionalFields = {
        originWorkflowRef,
    };
    if (childIssueRef !== undefined) {
        optionalFields.childIssueRef = childIssueRef;
    }
    if (replacementForStepIds !== undefined && replacementForStepIds !== null) {
        optionalFields.replacementForStepIds = replacementForStepIds;
    }
    if (suggestedTemplate !== undefined) {
        optionalFields.suggestedTemplate = suggestedTemplate;
    }
    if (bindingHints !== undefined && bindingHints !== null) {
        optionalFields.bindingHints = bindingHints;
    }
    if (childFields.inputs !== undefined) {
        optionalFields.inputs = childFields.inputs;
    }
    if (childFields.outputs !== undefined) {
        optionalFields.outputs = childFields.outputs;
    }
    return optionalFields;
}
function hasUnsupportedWorkflowStepFields(step) {
    return (!(0, coercion_support_1.hasOnlyRecordKeys)(step, plan_step_normalization_support_1.WORKFLOW_STEP_FIELD_NAMES) ||
        !(0, plan_step_normalization_support_1.areWorkflowStepReferenceMetadataFieldsValid)(step));
}
function coerceWorkflowStepChildOptionalFields(step) {
    const inputs = step.inputs === undefined ? undefined : (0, coercion_support_1.coerceStringArray)(step.inputs);
    const outputs = step.outputs === undefined ? undefined : (0, coercion_support_1.coerceStringArray)(step.outputs);
    if (inputs === null || outputs === null) {
        return null;
    }
    const childFields = {};
    if (inputs !== undefined) {
        childFields.inputs = inputs;
    }
    if (outputs !== undefined) {
        childFields.outputs = outputs;
    }
    return childFields;
}
function coerceChildIssueWorkflowStep(step, sharedFields, optionalFields) {
    const childKey = (0, coercion_support_1.coerceWorkflowIssueChildKey)(step.childKey);
    if (childKey === null ||
        optionalFields.inputs === undefined ||
        optionalFields.outputs === undefined) {
        return null;
    }
    const result = coerceNullableWorkflowStepResult(step.result);
    if (result === undefined) {
        return null;
    }
    const childStep = {
        ...sharedFields,
        childKey,
        childIssueRef: optionalFields.childIssueRef === undefined
            ? null
            : (optionalFields.childIssueRef ?? null),
        inputs: optionalFields.inputs,
        outputs: optionalFields.outputs,
        result,
    };
    (0, plan_step_normalization_support_1.assignCoercedRawWorkflowStepReferenceFields)(childStep, step, optionalFields);
    (0, plan_step_normalization_support_1.assignCoercedWorkflowStepOptionalFields)(childStep, optionalFields);
    return childStep;
}
function coerceNullableWorkflowStepResult(value) {
    if (value === null || value === undefined) {
        return null;
    }
    return (0, coercion_support_1.coerceWorkflowResult)(value) ?? undefined;
}
function normalizeWorkflowPlan(plan) {
    if (plan === null) {
        return null;
    }
    const steps = plan.steps.map((step) => normalizeWorkflowStep(step));
    return {
        steps,
    };
}
function replaceWorkflowRefStepWithExpandedSteps(workflowState, input) {
    const parentPlan = workflowState.plan;
    if (parentPlan === null) {
        throw new Error("Workflow plan is required for nested workflow expansion.");
    }
    if (input.stepIndex < 0 ||
        input.stepIndex >= parentPlan.steps.length ||
        input.childPlan.steps.length === 0) {
        throw new Error("Nested workflow expansion input is invalid.");
    }
    const workflowRefStep = parentPlan.steps[input.stepIndex];
    if (workflowRefStep === undefined) {
        throw new Error("Nested workflow expansion step was not found.");
    }
    const childStepIds = new Set(input.childPlan.steps.map((step) => step.id));
    const rootChildStepIds = new Set(input.childPlan.steps
        .filter((step) => step.dependsOnStepIds.every((stepId) => !childStepIds.has(stepId)))
        .map((step) => step.id));
    const leafChildStepIds = resolveLeafWorkflowStepIds(input.childPlan.steps);
    const expandedSteps = input.childPlan.steps.map((step) => {
        const dependsOnStepIds = rootChildStepIds.has(step.id)
            ? uniqueStringValues([
                ...workflowRefStep.dependsOnStepIds,
                ...step.dependsOnStepIds,
            ])
            : step.dependsOnStepIds;
        if (step.kind === "workflow_ref") {
            return normalizeWorkflowStep({
                ...step,
                dependsOnStepIds,
                sourceTemplateName: step.sourceTemplateName ?? input.sourceTemplateName,
                sourceTemplateDigest: step.sourceTemplateDigest ?? input.sourceTemplateDigest,
                expansionGroupId: step.expansionGroupId ?? step.callSiteId ?? step.id,
                originSubtreeKey: appendWorkflowOriginSubtreeKey(input.originSubtreeKey, step.expansionGroupId ?? step.callSiteId ?? step.id),
                originWorkflowRef: step.originWorkflowRef ?? input.originWorkflowRef,
                result: null,
            });
        }
        return normalizeWorkflowStep({
            ...step,
            dependsOnStepIds,
            sourceTemplateName: input.sourceTemplateName,
            sourceTemplateDigest: input.sourceTemplateDigest,
            expansionGroupId: input.expansionGroupId,
            originSubtreeKey: input.originSubtreeKey,
            originWorkflowRef: input.originWorkflowRef,
            result: null,
        });
    });
    const downstreamDependencyIds = leafChildStepIds.length > 0
        ? leafChildStepIds
        : input.childPlan.steps.map((step) => step.id);
    const outerSteps = parentPlan.steps.map((step) => step.id === workflowRefStep.id
        ? step
        : normalizeWorkflowStep({
            ...step,
            dependsOnStepIds: replaceDependencyId(step.dependsOnStepIds, workflowRefStep.id, downstreamDependencyIds),
        }));
    const nextSteps = [
        ...outerSteps.slice(0, input.stepIndex),
        ...expandedSteps,
        ...outerSteps.slice(input.stepIndex + 1),
    ];
    if (!areWorkflowStepDependenciesValid(nextSteps)) {
        throw new Error("Nested workflow expansion produced an invalid plan.");
    }
    return {
        ...workflowState,
        plan: {
            steps: nextSteps,
        },
    };
}
function replaceWorkflowExpansionSubtreeWithWorkflowRef(workflowState, input) {
    const parentPlan = workflowState.plan;
    if (parentPlan === null) {
        throw new Error("Workflow plan is required for nested workflow replan.");
    }
    const matchingIndexes = parentPlan.steps.flatMap((step, index) => isStepInWorkflowOriginSubtree(step, input.originSubtreeKey) &&
        step.result === null
        ? [index]
        : []);
    if (matchingIndexes.length === 0) {
        throw new Error("Workflow origin subtree was not found.");
    }
    const firstIndex = matchingIndexes[0];
    const lastIndex = matchingIndexes.at(-1);
    if (firstIndex === undefined || lastIndex === undefined) {
        throw new Error("Workflow origin subtree indexes were not found.");
    }
    const matchingSteps = matchingIndexes.flatMap((index) => {
        const step = parentPlan.steps[index];
        return step === undefined ? [] : [step];
    });
    const materializedChildIssueStep = matchingSteps.find((step) => (0, types_1.isWorkflowChildIssueStep)(step) && step.childIssueRef !== null);
    if (materializedChildIssueStep !== undefined) {
        throw new Error(`Workflow origin subtree ${input.originSubtreeKey} contains materialized child issue step ${materializedChildIssueStep.id}.`);
    }
    const matchingStepIds = new Set(matchingSteps.map((step) => step.id));
    const restoredStepDependencies = uniqueStringValues(matchingSteps.flatMap((step) => step.dependsOnStepIds.filter((stepId) => !matchingStepIds.has(stepId))));
    const restoredStep = {
        id: input.originWorkflowRef.expansionGroupId,
        kind: "workflow_ref",
        title: (0, catalog_1.translate)(input.locale, "workflow.workflowRefStep.title", {
            templatePath: input.originWorkflowRef.targetTemplateName,
        }),
        objective: (0, catalog_1.translate)(input.locale, "workflow.workflowRefStep.objective", {
            templatePath: input.originWorkflowRef.targetTemplateName,
        }),
        instructions: [
            (0, catalog_1.translate)(input.locale, "workflow.workflowRefStep.instruction", {
                templatePath: input.originWorkflowRef.targetTemplateName,
            }),
        ],
        doneWhen: [
            (0, catalog_1.translate)(input.locale, "workflow.workflowRefStep.doneWhen", {
                templatePath: input.originWorkflowRef.targetTemplateName,
            }),
        ],
        humanHandoffWhen: [
            (0, catalog_1.translate)(input.locale, "workflow.workflowRefStep.handoffWhen", {
                templatePath: input.originWorkflowRef.targetTemplateName,
            }),
        ],
        targetTemplateName: input.originWorkflowRef.targetTemplateName,
        callSiteId: input.originWorkflowRef.callSiteId,
        sourceTemplateName: input.originWorkflowRef.sourceTemplateName,
        sourceTemplateDigest: input.originWorkflowRef.sourceTemplateDigest,
        expansionGroupId: input.originWorkflowRef.expansionGroupId,
        originSubtreeKey: input.originSubtreeKey,
        dependsOnStepIds: restoredStepDependencies,
        result: null,
    };
    const removedStepIds = new Set(matchingSteps.map((step) => step.id));
    const outerSteps = parentPlan.steps.map((step) => removedStepIds.has(step.id)
        ? step
        : normalizeWorkflowStep({
            ...step,
            dependsOnStepIds: replaceDependencyIds(step.dependsOnStepIds, removedStepIds, restoredStep.id),
        }));
    const nextSteps = [
        ...outerSteps.slice(0, firstIndex),
        restoredStep,
        ...outerSteps.slice(lastIndex + 1),
    ];
    if (!areWorkflowStepDependenciesValid(nextSteps)) {
        throw new Error("Nested workflow restoration produced an invalid plan.");
    }
    return {
        ...workflowState,
        plan: {
            steps: nextSteps,
        },
    };
}
function resolveLeafWorkflowStepIds(steps) {
    const stepIds = new Set(steps.map((step) => step.id));
    const dependedStepIds = new Set(steps.flatMap((step) => step.dependsOnStepIds.filter((stepId) => stepIds.has(stepId))));
    return steps
        .filter((step) => !dependedStepIds.has(step.id))
        .map((step) => step.id);
}
function replaceDependencyId(dependencyIds, targetId, replacementIds) {
    return uniqueStringValues(dependencyIds.flatMap((dependencyId) => dependencyId === targetId ? [...replacementIds] : [dependencyId]));
}
function replaceDependencyIds(dependencyIds, targetIds, replacementId) {
    return uniqueStringValues(dependencyIds.flatMap((dependencyId) => targetIds.has(dependencyId) ? [replacementId] : [dependencyId]));
}
function uniqueStringValues(values) {
    return [...new Set(values)];
}
function normalizeWorkflowStep(step) {
    const normalizedStep = {
        id: step.id,
        kind: step.kind,
        title: step.title,
        objective: step.objective,
        instructions: step.instructions,
        doneWhen: step.doneWhen,
        humanHandoffWhen: step.humanHandoffWhen,
        dependsOnStepIds: [...step.dependsOnStepIds],
        result: (0, coercion_support_1.coerceWorkflowResult)(step.result),
    };
    (0, plan_step_normalization_support_1.assignNormalizedWorkflowStepReferenceFields)(normalizedStep, step);
    (0, plan_step_normalization_support_1.assignNormalizedWorkflowStepOptionalFields)(normalizedStep, step);
    return normalizedStep;
}
function areWorkflowStepDependenciesValid(steps) {
    const ids = new Set();
    for (const step of steps) {
        if (ids.has(step.id)) {
            return false;
        }
        ids.add(step.id);
    }
    if (!steps.every((step) => step.dependsOnStepIds.every((dependencyId) => dependencyId !== step.id && ids.has(dependencyId)))) {
        return false;
    }
    return !hasWorkflowStepDependencyCycle(steps);
}
function hasWorkflowStepDependencyCycle(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));
}
function buildWorkflowResultFromDecision(decision) {
    return (0, types_1.buildWorkflowResultWithDefaults)({
        outcome: decision.outcome,
        resultSummary: decision.result.resultSummary,
        evidence: [...decision.result.evidence],
        humanComment: decision.result.humanComment,
    });
}
function coerceWorkflowOriginWorkflowRef(value) {
    if (!(0, coercion_support_1.isRecord)(value)) {
        return value === undefined ? null : null;
    }
    const originWorkflowRef = value;
    if (!(0, plan_step_normalization_support_1.hasRequiredWorkflowOriginReferenceFields)(originWorkflowRef)) {
        return null;
    }
    return (0, plan_step_normalization_support_1.pickWorkflowOriginReferenceFields)(originWorkflowRef);
}
function isStepInWorkflowOriginSubtree(step, originSubtreeKey) {
    return (step.originSubtreeKey === originSubtreeKey ||
        step.originSubtreeKey?.startsWith(`${originSubtreeKey}>`) === true);
}
function appendWorkflowOriginSubtreeKey(parentOriginSubtreeKey, expansionGroupId) {
    return `${parentOriginSubtreeKey}>${expansionGroupId}`;
}
function isWorkflowStepKind(value) {
    return (value === "task" || value === "workflow_ref" || value === "child_issue");
}
