import { matchEventPattern } from "@open-mercato/shared/lib/events/patterns";
import { findWithDecryption } from "@open-mercato/shared/lib/encryption/find";
import { testLinearRegex } from "@open-mercato/shared/lib/regex/linear";
import {
  WorkflowEventTrigger,
  WorkflowDefinition,
  WorkflowInstance
} from "../data/entities.js";
import { startWorkflow, executeWorkflow } from "./workflow-executor.js";
import { createLogger } from "@open-mercato/shared/lib/logger";
const logger = createLogger("workflows").child({ component: "event-trigger" });
const TRIGGER_CACHE_TTL = 5 * 60 * 1e3;
const MAX_WORKFLOW_REGEX_PATTERN_LENGTH = 200;
const MAX_WORKFLOW_REGEX_INPUT_LENGTH = 1e4;
function getNestedValue(obj, path) {
  if (obj === null || obj === void 0) return void 0;
  const parts = path.split(".");
  let current = obj;
  for (const part of parts) {
    if (current === null || current === void 0) return void 0;
    if (typeof current !== "object") return void 0;
    current = current[part];
  }
  return current;
}
function getQuantifierEnd(pattern, index) {
  const char = pattern[index];
  if (char === "*" || char === "+" || char === "?") return index;
  if (char !== "{") return null;
  const closeIndex = pattern.indexOf("}", index + 1);
  if (closeIndex === -1) return null;
  const body = pattern.slice(index + 1, closeIndex);
  return /^[0-9]+(?:,[0-9]*)?$/.test(body) ? closeIndex : null;
}
function isSafeWorkflowRegexPattern(pattern) {
  if (pattern.length > MAX_WORKFLOW_REGEX_PATTERN_LENGTH) return false;
  const groupStack = [{ hasAlternation: false, hasQuantifier: false }];
  let inCharClass = false;
  let lastClosedGroup = null;
  let lastAtomWasQuantified = false;
  for (let index = 0; index < pattern.length; index += 1) {
    const char = pattern[index];
    if (char === "\\") {
      const next = pattern[index + 1];
      if (!inCharClass && (/[1-9]/.test(next ?? "") || next === "k" && pattern[index + 2] === "<")) {
        return false;
      }
      index += 1;
      lastClosedGroup = null;
      lastAtomWasQuantified = false;
      continue;
    }
    if (inCharClass) {
      if (char === "]") {
        inCharClass = false;
        lastAtomWasQuantified = false;
      }
      lastClosedGroup = null;
      continue;
    }
    if (char === "[") {
      inCharClass = true;
      lastClosedGroup = null;
      lastAtomWasQuantified = false;
      continue;
    }
    if (char === "(") {
      if (pattern[index + 1] === "?") {
        if (pattern[index + 2] !== ":") return false;
        index += 2;
      }
      groupStack.push({ hasAlternation: false, hasQuantifier: false });
      lastClosedGroup = null;
      lastAtomWasQuantified = false;
      continue;
    }
    if (char === ")") {
      if (groupStack.length === 1) return false;
      lastClosedGroup = groupStack.pop();
      lastAtomWasQuantified = false;
      continue;
    }
    if (char === "|") {
      groupStack[groupStack.length - 1].hasAlternation = true;
      lastClosedGroup = null;
      lastAtomWasQuantified = false;
      continue;
    }
    const quantifierEnd = getQuantifierEnd(pattern, index);
    if (quantifierEnd !== null) {
      if (lastAtomWasQuantified) return false;
      if (lastClosedGroup?.hasAlternation || lastClosedGroup?.hasQuantifier) return false;
      groupStack[groupStack.length - 1].hasQuantifier = true;
      lastClosedGroup = null;
      lastAtomWasQuantified = true;
      index = quantifierEnd;
      if (pattern[index + 1] === "?") {
        index += 1;
      }
      continue;
    }
    lastClosedGroup = null;
    lastAtomWasQuantified = false;
  }
  return groupStack.length === 1 && !inCharClass;
}
function evaluateCondition(condition, payload) {
  const value = getNestedValue(payload, condition.field);
  const expected = condition.value;
  switch (condition.operator) {
    case "eq":
      return value === expected;
    case "neq":
      return value !== expected;
    case "gt":
      return typeof value === "number" && typeof expected === "number" && value > expected;
    case "gte":
      return typeof value === "number" && typeof expected === "number" && value >= expected;
    case "lt":
      return typeof value === "number" && typeof expected === "number" && value < expected;
    case "lte":
      return typeof value === "number" && typeof expected === "number" && value <= expected;
    case "contains":
      if (typeof value === "string" && typeof expected === "string") {
        return value.includes(expected);
      }
      if (Array.isArray(value)) {
        return value.includes(expected);
      }
      return false;
    case "startsWith":
      return typeof value === "string" && typeof expected === "string" && value.startsWith(expected);
    case "endsWith":
      return typeof value === "string" && typeof expected === "string" && value.endsWith(expected);
    case "in":
      return Array.isArray(expected) && expected.includes(value);
    case "notIn":
      return Array.isArray(expected) && !expected.includes(value);
    case "exists":
      return value !== void 0 && value !== null;
    case "notExists":
      return value === void 0 || value === null;
    case "regex":
      if (typeof value !== "string" || typeof expected !== "string") return false;
      if (value.length > MAX_WORKFLOW_REGEX_INPUT_LENGTH) return false;
      if (!isSafeWorkflowRegexPattern(expected)) return false;
      {
        const result = testLinearRegex(expected, value, {
          maxPatternLength: MAX_WORKFLOW_REGEX_PATTERN_LENGTH,
          maxInputLength: MAX_WORKFLOW_REGEX_INPUT_LENGTH
        });
        return result.ok ? result.matched : false;
      }
    default:
      return false;
  }
}
function evaluateFilterConditions(conditions, payload) {
  if (!conditions || conditions.length === 0) return true;
  return conditions.every((condition) => evaluateCondition(condition, payload));
}
function mapEventToContext(mapping, payload) {
  const context = {};
  if (!mapping || mapping.length === 0) return context;
  for (const item of mapping) {
    const value = getNestedValue(payload, item.sourceExpression);
    context[item.targetKey] = value !== void 0 ? value : item.defaultValue;
  }
  return context;
}
const GLOBAL_TRIGGER_CACHE_KEY = "__openMercatoWorkflowTriggerCache__";
function getTriggerCache() {
  const existing = globalThis[GLOBAL_TRIGGER_CACHE_KEY];
  if (existing) return existing;
  const created = /* @__PURE__ */ new Map();
  globalThis[GLOBAL_TRIGGER_CACHE_KEY] = created;
  return created;
}
function getCacheKey(tenantId, organizationId) {
  return `${tenantId}:${organizationId}`;
}
async function loadLegacyTriggers(em, tenantId, organizationId) {
  const postgresEm = em;
  const legacyTriggers = await findWithDecryption(
    postgresEm,
    WorkflowEventTrigger,
    {
      tenantId,
      organizationId,
      enabled: true,
      deletedAt: null
    },
    {
      orderBy: { priority: "DESC", createdAt: "ASC" }
    },
    { tenantId, organizationId }
  );
  const definitionIds = [...new Set(legacyTriggers.map((t) => t.workflowDefinitionId))];
  const definitions = definitionIds.length > 0 ? await findWithDecryption(postgresEm, WorkflowDefinition, {
    id: { $in: definitionIds },
    tenantId,
    organizationId,
    enabled: true,
    deletedAt: null
  }, {}, { tenantId, organizationId }) : [];
  const definitionMap = new Map(definitions.map((d) => [d.id, d]));
  return legacyTriggers.filter((t) => definitionMap.has(t.workflowDefinitionId)).map((t) => {
    const def = definitionMap.get(t.workflowDefinitionId);
    return {
      id: t.id,
      triggerId: t.id,
      name: t.name,
      description: t.description,
      eventPattern: t.eventPattern,
      config: t.config ?? null,
      enabled: t.enabled,
      priority: t.priority,
      workflowDefinitionId: t.workflowDefinitionId,
      workflowId: def.workflowId,
      workflowVersion: def.version,
      source: "legacy",
      tenantId: t.tenantId,
      organizationId: t.organizationId
    };
  });
}
async function loadEmbeddedTriggers(em, tenantId, organizationId) {
  const postgresEm = em;
  const definitions = await findWithDecryption(
    postgresEm,
    WorkflowDefinition,
    {
      tenantId,
      organizationId,
      enabled: true,
      deletedAt: null
    },
    {},
    { tenantId, organizationId }
  );
  const triggers = [];
  for (const def of definitions) {
    const embeddedTriggers = def.definition?.triggers;
    if (!embeddedTriggers || embeddedTriggers.length === 0) continue;
    for (const trigger of embeddedTriggers) {
      if (!trigger.enabled) continue;
      triggers.push({
        id: `${def.id}:${trigger.triggerId}`,
        triggerId: trigger.triggerId,
        name: trigger.name,
        description: trigger.description ?? null,
        eventPattern: trigger.eventPattern,
        config: trigger.config ?? null,
        enabled: trigger.enabled,
        priority: trigger.priority,
        workflowDefinitionId: def.id,
        workflowId: def.workflowId,
        workflowVersion: def.version,
        source: "embedded",
        tenantId,
        organizationId
      });
    }
  }
  return triggers;
}
async function loadTriggersForTenant(em, tenantId, organizationId, cacheService) {
  const cacheKey = getCacheKey(tenantId, organizationId);
  const cache = getTriggerCache();
  const cached = cache.get(cacheKey);
  if (cached && Date.now() - cached.cachedAt < TRIGGER_CACHE_TTL) {
    return cached.triggers;
  }
  const [legacyTriggers, embeddedTriggers] = await Promise.all([
    loadLegacyTriggers(em, tenantId, organizationId),
    loadEmbeddedTriggers(em, tenantId, organizationId)
  ]);
  const allTriggers = [...legacyTriggers, ...embeddedTriggers].sort((a, b) => b.priority - a.priority);
  cache.set(cacheKey, {
    triggers: allTriggers,
    cachedAt: Date.now()
  });
  return allTriggers;
}
function invalidateTriggerCache(tenantId, organizationId) {
  const cache = getTriggerCache();
  if (organizationId) {
    const cacheKey = getCacheKey(tenantId, organizationId);
    cache.delete(cacheKey);
  } else {
    for (const key of cache.keys()) {
      if (key.startsWith(`${tenantId}:`)) {
        cache.delete(key);
      }
    }
  }
}
async function findMatchingTriggers(em, context) {
  const triggers = await loadTriggersForTenant(
    em,
    context.tenantId,
    context.organizationId
  );
  return triggers.filter((trigger) => {
    if (!matchEventPattern(context.eventName, trigger.eventPattern)) {
      return false;
    }
    if (!evaluateFilterConditions(trigger.config?.filterConditions, context.payload)) {
      return false;
    }
    return true;
  });
}
async function checkConcurrencyLimit(em, trigger) {
  const maxInstances = trigger.config?.maxConcurrentInstances;
  if (!maxInstances) return true;
  const runningCount = await em.count(WorkflowInstance, {
    definitionId: trigger.workflowDefinitionId,
    status: { $in: ["RUNNING", "WAITING_FOR_ACTIVITIES"] },
    tenantId: trigger.tenantId,
    organizationId: trigger.organizationId,
    deletedAt: null
  });
  return runningCount < maxInstances;
}
async function processEventTriggers(em, container, context) {
  const result = {
    triggered: 0,
    skipped: 0,
    errors: [],
    instances: []
  };
  const triggers = await findMatchingTriggers(em, context);
  if (triggers.length === 0) {
    return result;
  }
  for (const trigger of triggers) {
    try {
      const canStart = await checkConcurrencyLimit(em, trigger);
      if (!canStart) {
        logger.debug("Skipping trigger: max concurrent instances reached", { triggerId: trigger.id, triggerName: trigger.name });
        result.skipped++;
        continue;
      }
      const mappedContext = mapEventToContext(trigger.config?.contextMapping, context.payload);
      const payloadId = context.payload?.id;
      const payloadEntityType = context.payload?.entityType;
      const initialContext = {
        // Include raw event payload fields (e.g., id, organizationId, tenantId)
        ...context.payload,
        // Override with explicit mappings if provided
        ...mappedContext,
        __trigger: {
          triggerId: trigger.id,
          triggerName: trigger.name,
          eventName: context.eventName,
          eventPayload: context.payload,
          triggeredAt: (/* @__PURE__ */ new Date()).toISOString(),
          source: trigger.source
        }
      };
      const instance = await startWorkflow(em, {
        workflowId: trigger.workflowId,
        version: trigger.workflowVersion,
        initialContext,
        metadata: {
          initiatedBy: `trigger:${trigger.id}`,
          // Include entityId and entityType for widget discovery
          entityId: payloadId,
          entityType: payloadEntityType || trigger.config?.entityType,
          labels: {
            trigger_id: trigger.id,
            trigger_name: trigger.name,
            event_name: context.eventName,
            trigger_source: trigger.source
          }
        },
        tenantId: context.tenantId,
        organizationId: context.organizationId
      });
      result.triggered++;
      result.instances.push({
        triggerId: trigger.id,
        instanceId: instance.id
      });
      executeWorkflow(em.fork(), container, instance.id).catch((err) => {
        logger.error("Error executing workflow", { instanceId: instance.id, err });
      });
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : String(error);
      logger.error("Error processing trigger", { triggerId: trigger.id, triggerName: trigger.name, err: error });
      result.errors.push({
        triggerId: trigger.id,
        error: errorMessage
      });
    }
  }
  return result;
}
export {
  evaluateFilterConditions,
  findMatchingTriggers,
  invalidateTriggerCache,
  loadTriggersForTenant,
  mapEventToContext,
  processEventTriggers
};
//# sourceMappingURL=event-trigger-service.js.map
