import { BOARD_AUDIT_ACTION_RULES, wrapAuditValue, type AuditActionSource, type FormattedAuditValue, } from "@/domain/work-orders/utils/board-audit-action-rules"; function looksLikeHumanSentence(action: string): boolean { return /\s/.test(action); } function humanizeFieldLabel(fieldName: string): string { const spaced = fieldName .replace(/([a-z])([A-Z])/g, "$1 $2") .replace(/[_-]+/g, " ") .trim() .toLowerCase(); return spaced || "field"; } function normalizeKey(value: string): string { return value .trim() .toLowerCase() .replace(/[\s_-]+/g, ""); } /** JSON array strings → `"a", "b", "c"` instead of `["a","b","c"]`. */ function formatJsonArrayDisplayValue(raw: string): string | null { const trimmed = raw.trim(); if (!trimmed.startsWith("[") || !trimmed.endsWith("]")) return null; try { const parsed: unknown = JSON.parse(trimmed); if (!Array.isArray(parsed)) return null; const items = parsed .map((item) => String(item ?? "").trim()) .filter(Boolean) .map((item) => `"${item}"`); return items.join(", "); } catch { return null; } } /** Parse ISO / date-only / pipe-separated API values → unique mm/dd/yyyy. */ function formatAuditDisplayValue(raw: string): FormattedAuditValue { const trimmed = raw.trim(); if (!trimmed) return { text: trimmed, isQuotedList: false }; const jsonArray = formatJsonArrayDisplayValue(trimmed); if (jsonArray !== null) return { text: jsonArray, isQuotedList: true }; const parts = trimmed .split("|") .map((part) => part.trim()) .filter(Boolean); const dates: string[] = []; for (const part of parts) { const isoMatch = part.match(/^(\d{4})-(\d{2})-(\d{2})(?:T|$)/); if (isoMatch) { const [, year, month, day] = isoMatch; dates.push(`${month}/${day}/${year}`); continue; } const date = new Date(part); if (!Number.isNaN(date.getTime()) && /\d{4}/.test(part)) { const month = String(date.getMonth() + 1).padStart(2, "0"); const day = String(date.getDate()).padStart(2, "0"); const year = String(date.getFullYear()); dates.push(`${month}/${day}/${year}`); continue; } return { text: trimmed, isQuotedList: false }; } const unique = [...new Set(dates)]; if (unique.length === 0) return { text: trimmed, isQuotedList: false }; if (unique.length === 1) return { text: unique[0]!, isQuotedList: false }; return { text: unique.join(" – "), isQuotedList: false }; } /** * Builds a design-style audit sentence (the part after `Name · `), * e.g. `Changed status to "Incomplete"`, from board API fields. */ export function formatBoardAuditAction(entry: AuditActionSource): string { const action = entry.action?.trim() ?? ""; const fieldName = entry.fieldName?.trim() ?? ""; const oldValue = formatAuditDisplayValue(entry.oldValue?.trim() ?? ""); const newValue = formatAuditDisplayValue(entry.newValue?.trim() ?? ""); if (action && looksLikeHumanSentence(action)) { return action; } const fieldKey = normalizeKey(fieldName); const actionKey = normalizeKey(action); const ruleContext = { entry, fieldKey, actionKey, oldValue, newValue }; for (const rule of BOARD_AUDIT_ACTION_RULES) { const result = rule(ruleContext); if (result !== null) return result; } return formatGenericAuditFallback({ action, fieldName, oldValue, newValue }); } function formatGenericAuditFallback(args: { action: string; fieldName: string; oldValue: FormattedAuditValue; newValue: FormattedAuditValue; }): string { const { action, fieldName, oldValue, newValue } = args; if (!action && !fieldName && !oldValue.text && !newValue.text) { return "Work order created in the system."; } const label = humanizeFieldLabel(fieldName || action || "field"); if (oldValue.text && newValue.text) { return `Changed ${label} from ${wrapAuditValue(oldValue)} to ${wrapAuditValue(newValue)}`; } if (newValue.text) { return `Changed ${label} to ${wrapAuditValue(newValue)}`; } if (oldValue.text) { return `Changed ${label} from ${wrapAuditValue(oldValue)} to "—"`; } // Never surface a raw audit enum such as "FieldChanged" as the sentence. if (fieldName && action) return `Updated ${label}`; return "Updated work order"; }