/** * Template utility functions. * * Pure functions for parsing and formatting template data. * These functions have no React dependencies and can be used * in both TUI and CLI contexts. */ import type { XOTemplate, XOTemplateAction } from "@xo-cash/types"; /** * Formatted template list item data. */ export interface FormattedTemplateItem { /** The display label for the template */ label: string; /** The template description */ description?: string; /** Whether the template data is valid */ isValid: boolean; } /** * Formatted action list item data. */ export interface FormattedActionItem { /** The display label for the action */ label: string; /** The action description */ description?: string; /** Number of roles that can start this action */ roleCount: number; /** Whether the action data is valid */ isValid: boolean; } /** * A unique starting action (deduplicated by action identifier). * Multiple roles that can start the same action are counted * but not shown as separate entries. */ export interface UniqueStartingAction { actionIdentifier: string; name: string; description?: string; roleCount: number; } /** * Role information from a template. */ export interface TemplateRole { roleId: string; name: string; description?: string; } /** * Format a template for display in a list. * * @param template - The template to format * @param index - Optional index for numbered display * @returns Formatted item data for display */ export function formatTemplateListItem( template: XOTemplate | null | undefined, index?: number, ): FormattedTemplateItem { if (!template) { return { label: "", description: undefined, isValid: false, }; } const name = template.name || "Unnamed Template"; const prefix = index !== undefined ? `${index + 1}. ` : ""; return { label: `${prefix}${name}`, description: template.description, isValid: true, }; } /** * Format an action for display in a list. * * @param actionId - The action identifier * @param action - The action definition from the template * @param roleCount - Number of roles that can start this action * @param index - Optional index for numbered display * @returns Formatted item data for display */ export function formatActionListItem( actionId: string, action: XOTemplateAction | null | undefined, roleCount: number = 1, index?: number, ): FormattedActionItem { if (!actionId) { return { label: "", description: undefined, roleCount: 0, isValid: false, }; } const name = action?.name || actionId; const prefix = index !== undefined ? `${index + 1}. ` : ""; const roleSuffix = roleCount > 1 ? ` (${roleCount} roles)` : ""; return { label: `${prefix}${name}${roleSuffix}`, description: action?.description, roleCount, isValid: true, }; } /** * Deduplicate starting actions from a template. * Multiple roles that can start the same action are counted * but returned as a single entry. * * @param template - The template to process * @param startingActions - Array of { action, role } pairs * @returns Array of unique starting actions with role counts */ export function deduplicateStartingActions( template: XOTemplate, startingActions: Array<{ action: string; role: string }>, ): UniqueStartingAction[] { const actionMap = new Map(); for (const sa of startingActions) { if (actionMap.has(sa.action)) { actionMap.get(sa.action)!.roleCount++; } else { const actionDef = template.actions?.[sa.action]; actionMap.set(sa.action, { actionIdentifier: sa.action, name: actionDef?.name || sa.action, description: actionDef?.description, roleCount: 1, }); } } return Array.from(actionMap.values()); } /** * Get all roles from a template. * * @param template - The template to process * @returns Array of role information */ export function getTemplateRoles(template: XOTemplate): TemplateRole[] { if (!template.roles) return []; return Object.entries(template.roles).map(([roleId, role]) => { // Handle case where role might be a string instead of object const roleObj = typeof role === "object" ? role : null; return { roleId, name: roleObj?.name || roleId, description: roleObj?.description, }; }); } /** * Get roles that can start a specific action. * * @param template - The template to check * @param actionIdentifier - The action to check * @returns Array of role information for roles that can start this action */ export function getRolesForAction( template: XOTemplate, actionIdentifier: string, ): TemplateRole[] { const startEntries = (template.start ?? []).filter( (s) => s.action === actionIdentifier, ); return startEntries.map((entry) => { const roleDef = template.roles?.[entry.role || '']; const roleObj = typeof roleDef === "object" ? roleDef : null; // TODO: This is ugly. Lot of conditionals. Need to take a much closer look at this. return { roleId: entry.role || '', name: roleObj?.name || entry.role || '', description: roleObj?.description, }; }); } /** * Get template name safely. * * @param template - The template * @returns The template name or a default */ export function getTemplateName( template: XOTemplate | null | undefined, ): string { return template?.name || "Unknown Template"; } /** * Get template description safely. * * @param template - The template * @returns The template description or undefined */ export function getTemplateDescription( template: XOTemplate | null | undefined, ): string | undefined { return template?.description; } /** * Get action name safely. * * @param template - The template containing the action * @param actionId - The action identifier * @returns The action name or the action ID as fallback */ export function getActionName( template: XOTemplate | null | undefined, actionId: string, ): string { return template?.actions?.[actionId]?.name || actionId; } /** * Get action description safely. * * @param template - The template containing the action * @param actionId - The action identifier * @returns The action description or undefined */ export function getActionDescription( template: XOTemplate | null | undefined, actionId: string, ): string | undefined { return template?.actions?.[actionId]?.description; }