"use client";
'use strict';

// src/astrlabe/utils/workflow-validator.ts
var EN_MESSAGES = {
  "graph.noAgents": "At least one agent node is required",
  "graph.missingSource": 'Edge "{edgeId}" references non-existent source node "{source}"',
  "graph.missingTarget": 'Edge "{edgeId}" references non-existent target node "{target}"',
  "graph.orphanNode": 'Node "{label}" ({nodeType}) is not connected to any other node',
  "graph.toolToNonAgent": 'Tool "{label}" can only connect to agent nodes, not {targetType} nodes',
  "graph.ruleNoAgent": 'Rule "{label}" must have an incoming connection from an agent',
  "graph.cycleDetected": "Agent nodes form a cycle; workflows must be a DAG",
  "graph.multipleStartNodes": "Only one Start node is allowed",
  "graph.multipleEndNodes": "Only one End node is allowed",
  "graph.startHasIncoming": "Start node cannot have incoming connections",
  "graph.endHasOutgoing": "End node cannot have outgoing connections",
  "graph.iterationNoOutgoing": "Iteration node must have at least one outgoing connection",
  "node.required": "is required",
  "node.requiredWhenLanguageSet": "is required when language is set",
  "node.codeShapeRequired": "either {language, code} (UI) or {operation, ...} (engine) must be set",
  "node.mustBeArray": "must be an array",
  "node.mustBeReferenceArray": "must be an array of reference strings",
  "node.mustBeNonEmptyReferenceArray": "must be a non-empty array of reference strings",
  "node.assignmentNeedsSourceOrValue": "must set either `source` or `value`",
  "node.invalidAggregationMode": "must be one of: array | object | concatenate",
  "node.invalidListOperation": "must be one of: filter | map | sort | limit | deduplicate | flatten | count | sum | avg",
  "node.invalidLogicalOperator": "must be `and` or `or`",
  "node.mustBeNumber": "must be a number",
  "node.invalidHttpMethod": "must be one of: GET | POST | PUT | PATCH | DELETE",
  "node.providerIdentityRequired": "must set `connectionId`, `modelId`, or inline `provider` + `model`",
  "node.inputOrInstructionsRequired": "is required (or `instructions`)",
  "node.invalidExtractionMode": "must be one of: text | table | structured",
  "node.s3WriteSourceOrBodyRequired": "Either Source URL or Body must be provided",
  "node.unknownType": "unknown node type `{nodeType}`"
};
var PT_BR_MESSAGES = {
  "graph.noAgents": "Pelo menos um n\xF3 de agente \xE9 necess\xE1rio",
  "graph.missingSource": 'A aresta "{edgeId}" referencia um n\xF3 de origem inexistente "{source}"',
  "graph.missingTarget": 'A aresta "{edgeId}" referencia um n\xF3 de destino inexistente "{target}"',
  "graph.orphanNode": 'O n\xF3 "{label}" ({nodeType}) n\xE3o est\xE1 conectado a nenhum outro n\xF3',
  "graph.toolToNonAgent": 'A ferramenta "{label}" s\xF3 pode se conectar a n\xF3s de agente, n\xE3o a n\xF3s {targetType}',
  "graph.ruleNoAgent": 'A regra "{label}" deve ter uma conex\xE3o de entrada vinda de um agente',
  "graph.cycleDetected": "Os n\xF3s de agente formam um ciclo; workflows devem ser um DAG",
  "graph.multipleStartNodes": "Apenas um n\xF3 Start \xE9 permitido",
  "graph.multipleEndNodes": "Apenas um n\xF3 End \xE9 permitido",
  "graph.startHasIncoming": "O n\xF3 Start n\xE3o pode ter conex\xF5es de entrada",
  "graph.endHasOutgoing": "O n\xF3 End n\xE3o pode ter conex\xF5es de sa\xEDda",
  "graph.iterationNoOutgoing": "O n\xF3 Iteration deve ter pelo menos uma conex\xE3o de sa\xEDda",
  "node.required": "\xE9 obrigat\xF3rio",
  "node.requiredWhenLanguageSet": "\xE9 obrigat\xF3rio quando language est\xE1 definido",
  "node.codeShapeRequired": "\xE9 necess\xE1rio definir {language, code} (UI) ou {operation, ...} (engine)",
  "node.mustBeArray": "deve ser um array",
  "node.mustBeReferenceArray": "deve ser um array de strings de refer\xEAncia",
  "node.mustBeNonEmptyReferenceArray": "deve ser um array n\xE3o vazio de strings de refer\xEAncia",
  "node.assignmentNeedsSourceOrValue": "deve definir `source` ou `value`",
  "node.invalidAggregationMode": "deve ser um de: array | object | concatenate",
  "node.invalidListOperation": "deve ser um de: filter | map | sort | limit | deduplicate | flatten | count | sum | avg",
  "node.invalidLogicalOperator": "deve ser `and` ou `or`",
  "node.mustBeNumber": "deve ser um n\xFAmero",
  "node.invalidHttpMethod": "deve ser um de: GET | POST | PUT | PATCH | DELETE",
  "node.providerIdentityRequired": "deve definir `connectionId`, `modelId` ou `provider` + `model` inline",
  "node.inputOrInstructionsRequired": "\xE9 obrigat\xF3rio (ou `instructions`)",
  "node.invalidExtractionMode": "deve ser um de: text | table | structured",
  "node.s3WriteSourceOrBodyRequired": "Forne\xE7a Source URL ou Body",
  "node.unknownType": "tipo de n\xF3 desconhecido `{nodeType}`"
};
var MESSAGE_CATALOG = {
  en: EN_MESSAGES,
  "pt-BR": PT_BR_MESSAGES,
  es: EN_MESSAGES,
  fr: EN_MESSAGES,
  de: EN_MESSAGES,
  it: EN_MESSAGES
};
function normalizeLocale(locale) {
  if (!locale) {
    return "en";
  }
  if (locale in MESSAGE_CATALOG) {
    return locale;
  }
  const lowercase = locale.toLowerCase();
  if (lowercase.startsWith("pt")) {
    return "pt-BR";
  }
  if (lowercase.startsWith("es")) {
    return "es";
  }
  if (lowercase.startsWith("fr")) {
    return "fr";
  }
  if (lowercase.startsWith("de")) {
    return "de";
  }
  if (lowercase.startsWith("it")) {
    return "it";
  }
  return "en";
}
function template(messageKey, params, locale) {
  const catalog = MESSAGE_CATALOG[normalizeLocale(locale)] ?? EN_MESSAGES;
  const message = catalog[messageKey] ?? EN_MESSAGES[messageKey];
  return message.replace(/\{([^}]+)\}/g, (_, key) => String(params?.[key] ?? ""));
}
function isRecord(value) {
  return typeof value === "object" && value !== null && !Array.isArray(value);
}
function getConfig(node) {
  return isRecord(node.data.config) ? node.data.config : {};
}
function getString(config, field) {
  const value = config[field];
  return typeof value === "string" ? value : void 0;
}
function hasNonEmptyString(config, field) {
  const value = getString(config, field);
  return typeof value === "string" && value.trim().length > 0;
}
function getArray(config, field) {
  const value = config[field];
  return Array.isArray(value) ? value : void 0;
}
function getNumber(config, field) {
  const value = config[field];
  return typeof value === "number" ? value : void 0;
}
function pushNodeIssue(issues, node, locale, code, field, params) {
  issues.push({
    nodeId: node.id,
    nodeType: node.type,
    field,
    code,
    params,
    message: template(code, params, locale)
  });
}
function pushIssue(issues, locale, code, params) {
  issues.push({
    code,
    params,
    message: template(code, params, locale)
  });
}
function ensureProviderIdentifiable(node, config, issues, locale) {
  const hasConnection = hasNonEmptyString(config, "connectionId");
  const hasModelId = hasNonEmptyString(config, "modelId");
  const hasInline = hasNonEmptyString(config, "provider") && hasNonEmptyString(config, "model");
  if (!hasConnection && !hasModelId && !hasInline) {
    pushNodeIssue(issues, node, locale, "node.providerIdentityRequired", "provider");
  }
}
function formatNodeIssue(issue, nodeMap) {
  const label = nodeMap.get(issue.nodeId)?.data.label ?? issue.nodeId;
  const fieldRef = issue.field ? ` (${issue.field})` : "";
  return `Node "${label}" [${issue.nodeType}]${fieldRef}: ${issue.message}`;
}
function validateNodeConfig(node, options = {}) {
  const locale = options.locale;
  const config = getConfig(node);
  const issues = [];
  const nodeType = node.type;
  switch (nodeType) {
    case "start":
    case "note":
    case "group":
    case "dashboard_output":
      break;
    case "end": {
      if ("outputVariables" in config && !Array.isArray(config.outputVariables)) {
        pushNodeIssue(issues, node, locale, "node.mustBeReferenceArray", "outputVariables");
      } else if (Array.isArray(config.outputs)) {
        config.outputs.forEach((entry, index) => {
          if (!isRecord(entry) || typeof entry.key !== "string" || entry.key.trim().length === 0) {
            pushNodeIssue(issues, node, locale, "node.required", `outputs[${index}].key`);
          }
          if (!isRecord(entry) || typeof entry.from !== "string" || entry.from.trim().length === 0) {
            pushNodeIssue(issues, node, locale, "node.required", `outputs[${index}].from`);
          }
        });
      }
      break;
    }
    case "code": {
      const hasLanguage = "language" in config;
      const hasOperation = "operation" in config;
      if (hasLanguage) {
        if (!hasNonEmptyString(config, "code")) {
          pushNodeIssue(issues, node, locale, "node.requiredWhenLanguageSet", "code");
        }
      } else if (!hasOperation) {
        pushNodeIssue(issues, node, locale, "node.codeShapeRequired", "operation");
      }
      break;
    }
    case "iteration_start": {
      for (const field of ["iteratorVariable", "itemVariable", "indexVariable"]) {
        if (!hasNonEmptyString(config, field)) {
          pushNodeIssue(issues, node, locale, "node.required", field);
        }
      }
      break;
    }
    case "entity":
      if (!hasNonEmptyString(config, "entityMasterId")) {
        pushNodeIssue(issues, node, locale, "node.required", "entityMasterId");
      }
      break;
    case "datasource":
      if (!hasNonEmptyString(config, "datasourceId")) {
        pushNodeIssue(issues, node, locale, "node.required", "datasourceId");
      }
      break;
    case "model_provider":
      if (!hasNonEmptyString(config, "providerType")) {
        pushNodeIssue(issues, node, locale, "node.required", "providerType");
      }
      break;
    case "variable_assigner": {
      const assignments = getArray(config, "assignments");
      if (!assignments) {
        pushNodeIssue(issues, node, locale, "node.mustBeArray", "assignments");
        return issues;
      }
      assignments.forEach((entry, index) => {
        const record = isRecord(entry) ? entry : {};
        const target = typeof record.target === "string" && record.target.trim().length > 0;
        const legacyTarget = typeof record.key === "string" && record.key.trim().length > 0;
        if (!target && !legacyTarget) {
          pushNodeIssue(issues, node, locale, "node.required", `assignments[${index}].target`);
        }
        const source = typeof record.source === "string" && record.source.trim().length > 0;
        const legacySource = typeof record.from === "string" && record.from.trim().length > 0;
        const hasValue = "value" in record;
        if (!source && !legacySource && !hasValue) {
          pushNodeIssue(issues, node, locale, "node.assignmentNeedsSourceOrValue", `assignments[${index}]`);
        }
      });
      break;
    }
    case "variable_aggregator": {
      if ("inputVariables" in config) {
        const inputVariables = getArray(config, "inputVariables");
        if (!inputVariables || inputVariables.length === 0) {
          pushNodeIssue(issues, node, locale, "node.mustBeNonEmptyReferenceArray", "inputVariables");
        }
        if (!hasNonEmptyString(config, "outputVariable")) {
          pushNodeIssue(issues, node, locale, "node.required", "outputVariable");
        }
        const aggregationMode = getString(config, "aggregationMode");
        if (aggregationMode && !["array", "object", "concatenate"].includes(aggregationMode)) {
          pushNodeIssue(issues, node, locale, "node.invalidAggregationMode", "aggregationMode");
        }
      }
      break;
    }
    case "list_operator": {
      const operation = getString(config, "operation") ?? "";
      if (!operation) {
        pushNodeIssue(issues, node, locale, "node.required", "operation");
      } else if (!["filter", "map", "sort", "limit", "deduplicate", "flatten", "count", "sum", "avg"].includes(operation)) {
        pushNodeIssue(issues, node, locale, "node.invalidListOperation", "operation");
      }
      const inputVariable = getString(config, "inputVariable");
      const legacySource = getString(config, "source");
      if (!(inputVariable && inputVariable.trim()) && !(legacySource && legacySource.trim())) {
        pushNodeIssue(issues, node, locale, "node.required", "inputVariable");
      }
      break;
    }
    case "template_transform":
      if (!hasNonEmptyString(config, "template")) {
        pushNodeIssue(issues, node, locale, "node.required", "template");
      }
      break;
    case "answer": {
      const outputTemplate = getString(config, "outputTemplate");
      const legacyAnswer = getString(config, "answer");
      if (!(outputTemplate && outputTemplate.trim()) && !(legacyAnswer && legacyAnswer.trim())) {
        pushNodeIssue(issues, node, locale, "node.required", "outputTemplate");
      }
      break;
    }
    case "if_else": {
      const conditions = getArray(config, "conditions");
      if (!conditions || conditions.length === 0) {
        pushNodeIssue(issues, node, locale, "node.mustBeArray", "conditions");
      } else {
        conditions.forEach((condition, index) => {
          const record = isRecord(condition) ? condition : {};
          if (typeof record.variable !== "string" || record.variable.trim().length === 0) {
            pushNodeIssue(issues, node, locale, "node.required", `conditions[${index}].variable`);
          }
          if (typeof record.operator !== "string" || record.operator.trim().length === 0) {
            pushNodeIssue(issues, node, locale, "node.required", `conditions[${index}].operator`);
          }
        });
      }
      const logicalOperator = getString(config, "logicalOperator");
      if (logicalOperator && !["and", "or"].includes(logicalOperator)) {
        pushNodeIssue(issues, node, locale, "node.invalidLogicalOperator", "logicalOperator");
      }
      break;
    }
    case "iteration": {
      const iteratorVariable = getString(config, "iteratorVariable");
      const legacySource = getString(config, "source");
      if (!(iteratorVariable && iteratorVariable.trim()) && !(legacySource && legacySource.trim())) {
        pushNodeIssue(issues, node, locale, "node.required", "iteratorVariable");
      }
      if ("maxIterations" in config && getNumber(config, "maxIterations") === void 0) {
        pushNodeIssue(issues, node, locale, "node.mustBeNumber", "maxIterations");
      }
      break;
    }
    case "http_request": {
      const method = (getString(config, "method") ?? "").toUpperCase();
      if (!["GET", "POST", "PUT", "PATCH", "DELETE"].includes(method)) {
        pushNodeIssue(issues, node, locale, "node.invalidHttpMethod", "method");
      }
      if (!hasNonEmptyString(config, "url")) {
        pushNodeIssue(issues, node, locale, "node.required", "url");
      }
      break;
    }
    case "s3_write": {
      if (!hasNonEmptyString(config, "key")) {
        pushNodeIssue(issues, node, locale, "node.required", "key");
      }
      const hasSource = hasNonEmptyString(config, "sourceUrl");
      const hasBody = hasNonEmptyString(config, "body");
      if (!hasSource && !hasBody) {
        pushNodeIssue(issues, node, locale, "node.s3WriteSourceOrBodyRequired", "sourceUrl");
      }
      break;
    }
    case "rule":
      break;
    case "agent":
      if (!hasNonEmptyString(config, "userPrompt")) {
        pushNodeIssue(issues, node, locale, "node.required", "userPrompt");
      }
      ensureProviderIdentifiable(node, config, issues, locale);
      break;
    case "question_classifier": {
      ensureProviderIdentifiable(node, config, issues, locale);
      const input = getString(config, "input");
      const instructions = getString(config, "instructions");
      if (!(input && input.trim()) && !(instructions && instructions.trim())) {
        pushNodeIssue(issues, node, locale, "node.inputOrInstructionsRequired", "input");
      }
      const categories = getArray(config, "categories") ?? getArray(config, "classes");
      if (!categories || categories.length === 0) {
        pushNodeIssue(issues, node, locale, "node.mustBeArray", "categories");
      }
      break;
    }
    case "parameter_extractor": {
      ensureProviderIdentifiable(node, config, issues, locale);
      if (!hasNonEmptyString(config, "input")) {
        pushNodeIssue(issues, node, locale, "node.required", "input");
      }
      const parameters = getArray(config, "parameters");
      if (!parameters || parameters.length === 0) {
        pushNodeIssue(issues, node, locale, "node.mustBeArray", "parameters");
      }
      break;
    }
    case "tool":
      if (!hasNonEmptyString(config, "toolId")) {
        pushNodeIssue(issues, node, locale, "node.required", "toolId");
      }
      break;
    case "agent_tool":
      if (!hasNonEmptyString(config, "agentId")) {
        pushNodeIssue(issues, node, locale, "node.required", "agentId");
      }
      if (!hasNonEmptyString(config, "toolId")) {
        pushNodeIssue(issues, node, locale, "node.required", "toolId");
      }
      break;
    case "knowledge_base":
      if (!hasNonEmptyString(config, "sourceId")) {
        pushNodeIssue(issues, node, locale, "node.required", "sourceId");
      }
      if ("topK" in config && getNumber(config, "topK") === void 0) {
        pushNodeIssue(issues, node, locale, "node.mustBeNumber", "topK");
      }
      if ("similarityThreshold" in config && getNumber(config, "similarityThreshold") === void 0) {
        pushNodeIssue(issues, node, locale, "node.mustBeNumber", "similarityThreshold");
      }
      break;
    case "document_extractor": {
      const extractionMode = getString(config, "extractionMode");
      if (extractionMode && !["text", "table", "structured"].includes(extractionMode)) {
        pushNodeIssue(issues, node, locale, "node.invalidExtractionMode", "extractionMode");
      }
      break;
    }
    case "audio_input":
      if (!hasNonEmptyString(config, "url")) {
        pushNodeIssue(issues, node, locale, "node.required", "url");
      }
      break;
    case "speech_transcriber":
      if (!hasNonEmptyString(config, "audioUrl")) {
        pushNodeIssue(issues, node, locale, "node.required", "audioUrl");
      }
      break;
    default:
      pushNodeIssue(issues, node, locale, "node.unknownType", void 0, { nodeType });
      break;
  }
  return issues;
}
function validateGraphNodeConfigs(graph, options = {}) {
  return graph.nodes.flatMap((node) => validateNodeConfig(node, options));
}
function validateWorkflowGraph(graph, options = {}) {
  const locale = options.locale;
  const issues = [];
  const nodeMap = /* @__PURE__ */ new Map();
  for (const node of graph.nodes) {
    nodeMap.set(node.id, node);
  }
  const agentNodes = graph.nodes.filter((node) => node.type === "agent");
  if (agentNodes.length === 0) {
    pushIssue(issues, locale, "graph.noAgents");
  }
  for (const edge of graph.edges) {
    if (!nodeMap.has(edge.source)) {
      pushIssue(issues, locale, "graph.missingSource", { edgeId: edge.id, source: edge.source });
    }
    if (!nodeMap.has(edge.target)) {
      pushIssue(issues, locale, "graph.missingTarget", { edgeId: edge.id, target: edge.target });
    }
  }
  const connectedNodeIds = /* @__PURE__ */ new Set();
  for (const edge of graph.edges) {
    connectedNodeIds.add(edge.source);
    connectedNodeIds.add(edge.target);
  }
  for (const node of graph.nodes) {
    if (node.type === "note") continue;
    if (!connectedNodeIds.has(node.id)) {
      pushIssue(issues, locale, "graph.orphanNode", {
        label: node.data.label ?? node.id,
        nodeType: node.type
      });
    }
  }
  for (const edge of graph.edges) {
    const sourceNode = nodeMap.get(edge.source);
    const targetNode = nodeMap.get(edge.target);
    if (!sourceNode || !targetNode) continue;
    if (sourceNode.type === "tool" && targetNode.type !== "agent") {
      pushIssue(issues, locale, "graph.toolToNonAgent", {
        label: sourceNode.data.label ?? sourceNode.id,
        targetType: targetNode.type
      });
    }
  }
  const ruleNodes = graph.nodes.filter((node) => node.type === "rule");
  for (const ruleNode of ruleNodes) {
    const incomingEdges = graph.edges.filter((edge) => edge.target === ruleNode.id);
    const hasAgentSource = incomingEdges.some((edge) => nodeMap.get(edge.source)?.type === "agent");
    if (incomingEdges.length === 0 || !hasAgentSource) {
      pushIssue(issues, locale, "graph.ruleNoAgent", {
        label: ruleNode.data.label ?? ruleNode.id
      });
    }
  }
  if (agentNodes.length > 1) {
    const cycleIssue = detectCycle(agentNodes, graph.edges, nodeMap, locale);
    if (cycleIssue) {
      issues.push(cycleIssue);
    }
  }
  const startNodes = graph.nodes.filter((node) => node.type === "start");
  if (startNodes.length > 1) {
    pushIssue(issues, locale, "graph.multipleStartNodes");
  }
  const endNodes = graph.nodes.filter((node) => node.type === "end");
  if (endNodes.length > 1) {
    pushIssue(issues, locale, "graph.multipleEndNodes");
  }
  for (const startNode of startNodes) {
    const incomingEdges = graph.edges.filter((edge) => edge.target === startNode.id);
    if (incomingEdges.length > 0) {
      pushIssue(issues, locale, "graph.startHasIncoming");
    }
  }
  for (const endNode of endNodes) {
    const outgoingEdges = graph.edges.filter((edge) => edge.source === endNode.id);
    if (outgoingEdges.length > 0) {
      pushIssue(issues, locale, "graph.endHasOutgoing");
    }
  }
  const iterationNodes = graph.nodes.filter((node) => node.type === "iteration");
  for (const iterationNode of iterationNodes) {
    const outgoingEdges = graph.edges.filter((edge) => edge.source === iterationNode.id);
    if (outgoingEdges.length === 0) {
      pushIssue(issues, locale, "graph.iterationNoOutgoing");
    }
  }
  const nodeIssues = validateGraphNodeConfigs(graph, options);
  const errors = [
    ...issues.map((issue) => issue.message),
    ...nodeIssues.map((issue) => formatNodeIssue(issue, nodeMap))
  ];
  return {
    valid: errors.length === 0,
    errors,
    issues,
    nodeIssues
  };
}
function topologicalSortAgents(agentNodes, edges, nodeMap) {
  const adjacency = /* @__PURE__ */ new Map();
  const inDegree = /* @__PURE__ */ new Map();
  for (const node of agentNodes) {
    adjacency.set(node.id, []);
    inDegree.set(node.id, 0);
  }
  for (const edge of edges) {
    const sourceNode = nodeMap.get(edge.source);
    const targetNode = nodeMap.get(edge.target);
    if (!sourceNode || !targetNode) continue;
    if (sourceNode.type === "agent" && targetNode.type === "agent") {
      adjacency.get(edge.source)?.push(edge.target);
      inDegree.set(edge.target, (inDegree.get(edge.target) ?? 0) + 1);
    }
  }
  const queue = agentNodes.filter((node) => (inDegree.get(node.id) ?? 0) === 0).map((node) => node.id);
  const sortedIds = [];
  while (queue.length > 0) {
    const nodeId = queue.shift();
    if (!nodeId) break;
    sortedIds.push(nodeId);
    const neighbors = adjacency.get(nodeId) ?? [];
    for (const neighborId of neighbors) {
      const newInDegree = (inDegree.get(neighborId) ?? 1) - 1;
      inDegree.set(neighborId, newInDegree);
      if (newInDegree === 0) {
        queue.push(neighborId);
      }
    }
  }
  if (sortedIds.length !== agentNodes.length) {
    throw new Error("Cycle detected in agent graph");
  }
  return sortedIds.map((nodeId) => nodeMap.get(nodeId)).filter((node) => Boolean(node));
}
function detectCycle(agentNodes, edges, nodeMap, locale) {
  try {
    topologicalSortAgents(agentNodes, edges, nodeMap);
    return null;
  } catch {
    return {
      code: "graph.cycleDetected",
      message: template("graph.cycleDetected", void 0, locale)
    };
  }
}

exports.topologicalSortAgents = topologicalSortAgents;
exports.validateGraphNodeConfigs = validateGraphNodeConfigs;
exports.validateNodeConfig = validateNodeConfig;
exports.validateWorkflowGraph = validateWorkflowGraph;
//# sourceMappingURL=chunk-DJDZIRM6.js.map
//# sourceMappingURL=chunk-DJDZIRM6.js.map