import { ref, computed, type Ref, type ComputedRef } from 'vue' import type { ProtocolStep, ProtocolStepType, ProtocolStepStatus } from '../types' import { BUILT_IN_PROTOCOL_TEMPLATES, type ParameterDefinition, type StepTemplate, } from './protocolTemplateCatalog' export type { ParameterDefinition, StepTemplate } from './protocolTemplateCatalog' // Validation result export interface ValidationResult { valid: boolean errors: Record } export interface UseProtocolTemplatesReturn { builtInTemplates: StepTemplate[] customTemplates: Ref allTemplates: ComputedRef getTemplateByType: (type: ProtocolStepType) => StepTemplate | undefined getTemplateById: (id: string) => StepTemplate | undefined saveCustomTemplate: (template: StepTemplate) => void deleteCustomTemplate: (templateId: string) => void validateStep: (step: ProtocolStep, template: StepTemplate) => ValidationResult createStepFromTemplate: (template: StepTemplate, overrides?: Partial) => ProtocolStep formatParameterValue: (value: unknown, param: ParameterDefinition) => string } // Storage key for custom templates const STORAGE_KEY = 'mint-custom-protocol-templates' function isStepTemplate(value: unknown): value is StepTemplate { if (!value || typeof value !== 'object') return false const candidate = value as Partial return ( typeof candidate.id === 'string' && typeof candidate.type === 'string' && typeof candidate.name === 'string' && Array.isArray(candidate.parameters) ) } function loadCustomTemplates(): StepTemplate[] { try { const stored = localStorage.getItem(STORAGE_KEY) if (stored) { const parsed = JSON.parse(stored) return Array.isArray(parsed) ? parsed.filter(isStepTemplate) : [] } } catch { // Ignore errors } return [] } function saveCustomTemplatesToStorage(templates: StepTemplate[]) { try { localStorage.setItem(STORAGE_KEY, JSON.stringify(templates)) } catch { // Ignore errors } } /** Provides built-in and custom protocol step templates with validation and step creation helpers. */ export function useProtocolTemplates(): UseProtocolTemplatesReturn { const customTemplates = ref(loadCustomTemplates()) const allTemplates = computed(() => { return [...BUILT_IN_PROTOCOL_TEMPLATES, ...customTemplates.value] }) function getTemplateByType(type: ProtocolStepType): StepTemplate | undefined { // First check custom templates, then built-in return ( customTemplates.value.find((t) => t.type === type) || BUILT_IN_PROTOCOL_TEMPLATES.find((t) => t.type === type) ) } function getTemplateById(id: string): StepTemplate | undefined { return ( customTemplates.value.find((t) => t.id === id) || BUILT_IN_PROTOCOL_TEMPLATES.find((t) => t.id === id) ) } function saveCustomTemplate(template: StepTemplate) { const newTemplate = { ...template, isBuiltIn: false, id: template.id || `custom-${Date.now()}`, } const index = customTemplates.value.findIndex((t) => t.id === newTemplate.id) if (index >= 0) { customTemplates.value[index] = newTemplate } else { customTemplates.value.push(newTemplate) } saveCustomTemplatesToStorage(customTemplates.value) } function deleteCustomTemplate(templateId: string) { const index = customTemplates.value.findIndex((t) => t.id === templateId) if (index >= 0) { customTemplates.value.splice(index, 1) saveCustomTemplatesToStorage(customTemplates.value) } } function validateStep(step: ProtocolStep, template: StepTemplate): ValidationResult { const errors: Record = {} if (!step.name || step.name.trim() === '') { errors.name = 'Step name is required' } for (const param of template.parameters) { if (param.required) { const value = step.parameters?.[param.key] if (value === undefined || value === null || value === '') { errors[param.key] = `${param.label} is required` } } if (param.type === 'number' || param.type === 'temperature' || param.type === 'duration') { const value = step.parameters?.[param.key] if (value !== undefined && value !== null && value !== '') { const numValue = Number(value) if (isNaN(numValue)) { errors[param.key] = `${param.label} must be a number` } else { if (param.min !== undefined && numValue < param.min) { errors[param.key] = `${param.label} must be at least ${param.min}` } if (param.max !== undefined && numValue > param.max) { errors[param.key] = `${param.label} must be at most ${param.max}` } } } } } return { valid: Object.keys(errors).length === 0, errors, } } function createStepFromTemplate( template: StepTemplate, overrides?: Partial ): ProtocolStep { // Build default parameters from template const parameters: Record = {} for (const param of template.parameters) { if (param.default !== undefined) { parameters[param.key] = param.default } } return { id: `step-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`, type: template.type, name: template.name, description: template.description, duration: template.defaultDuration, status: 'pending' as ProtocolStepStatus, parameters, order: 0, ...overrides, } } function formatParameterValue(value: unknown, param: ParameterDefinition): string { if (value === undefined || value === null || value === '') { return '-' } if (param.type === 'select' && param.options) { const option = param.options.find((o) => o.value === value) if (option) return option.label } if (param.type === 'concentration' && typeof value === 'object') { const conc = value as { value: number; unit: string } return `${conc.value} ${conc.unit}` } const strValue = String(value) if (param.unit) { return `${strValue} ${param.unit}` } return strValue } return { builtInTemplates: BUILT_IN_PROTOCOL_TEMPLATES, customTemplates, allTemplates, getTemplateByType, getTemplateById, saveCustomTemplate, deleteCustomTemplate, validateStep, createStepFromTemplate, formatParameterValue, } }