import type { Doc } from "./_generated/dataModel" export type AutomationTrigger = | "TICKET_CREATED" | "STATUS_CHANGED" | "PRIORITY_CHANGED" | "QUEUE_CHANGED" | "COMMENT_ADDED" | "TICKET_RESOLVED" export type AutomationConditionOperator = "AND" | "OR" export type AutomationConditionField = | "companyId" | "queueId" | "categoryId" | "subcategoryId" | "priority" | "status" | "channel" | "formTemplate" | "chatEnabled" | "tag" export type AutomationConditionComparator = | "eq" | "neq" | "in" | "not_in" | "contains" | "not_contains" | "is_true" | "is_false" export type AutomationCondition = { field: AutomationConditionField op: AutomationConditionComparator value?: unknown } export type AutomationConditionGroup = { op: AutomationConditionOperator conditions: AutomationCondition[] } export type TicketForAutomation = Pick< Doc<"tickets">, | "tenantId" | "status" | "priority" | "channel" | "queueId" | "companyId" | "categoryId" | "subcategoryId" | "tags" | "formTemplate" | "chatEnabled" > function normalizeId(value: unknown): string | null { if (!value) return null return String(value) } function normalizeString(value: unknown): string | null { if (typeof value !== "string") return null const trimmed = value.trim() return trimmed.length > 0 ? trimmed : null } function normalizeStringArray(value: unknown): string[] { if (!Array.isArray(value)) return [] return value.map((item) => normalizeString(item)).filter((item): item is string => Boolean(item)) } function normalizeBoolean(value: unknown): boolean | null { if (typeof value === "boolean") return value return null } function compareValue(actual: string | null, op: AutomationConditionComparator, expected: unknown): boolean { if (op === "eq") return actual === normalizeId(expected) if (op === "neq") return actual !== normalizeId(expected) if (op === "in") { const list = normalizeStringArray(expected).map((v) => v) return actual !== null && list.includes(actual) } if (op === "not_in") { const list = normalizeStringArray(expected).map((v) => v) return actual === null || !list.includes(actual) } return false } export function evaluateAutomationConditions( ticket: TicketForAutomation, group: AutomationConditionGroup | null | undefined ): boolean { if (!group || !Array.isArray(group.conditions) || group.conditions.length === 0) return true const op = group.op === "OR" ? "OR" : "AND" const results = group.conditions.map((condition) => { const field = condition.field const operator = condition.op if (field === "companyId") return compareValue(normalizeId(ticket.companyId), operator, condition.value) if (field === "queueId") return compareValue(normalizeId(ticket.queueId), operator, condition.value) if (field === "categoryId") return compareValue(normalizeId(ticket.categoryId), operator, condition.value) if (field === "subcategoryId") return compareValue(normalizeId(ticket.subcategoryId), operator, condition.value) if (field === "priority") return compareValue(normalizeString(ticket.priority), operator, condition.value) if (field === "status") return compareValue(normalizeString(ticket.status), operator, condition.value) if (field === "channel") return compareValue(normalizeString(ticket.channel), operator, condition.value) if (field === "formTemplate") return compareValue(normalizeString(ticket.formTemplate), operator, condition.value) if (field === "chatEnabled") { const expectedBool = normalizeBoolean(condition.value) if (operator === "is_true") return ticket.chatEnabled === true if (operator === "is_false") return ticket.chatEnabled !== true if (operator === "eq") return expectedBool !== null ? Boolean(ticket.chatEnabled) === expectedBool : false if (operator === "neq") return expectedBool !== null ? Boolean(ticket.chatEnabled) !== expectedBool : false return false } if (field === "tag") { const tag = normalizeString(condition.value) if (!tag) return false const has = (ticket.tags ?? []).includes(tag) if (operator === "contains" || operator === "eq") return has if (operator === "not_contains" || operator === "neq") return !has return false } return false }) return op === "OR" ? results.some(Boolean) : results.every(Boolean) }