import { computed, ref, type ComputedRef, type Ref } from 'vue' import { useExperimentSave, type UseExperimentSaveOptions } from './useExperimentSave' import { createTemplateCollection, ensureTemplateFromCollection, extractTemplateCollection, type BioTemplateEnvelope, type TemplateCollection, type TemplateCollectionEnvelope, } from '../templates' export type TemplateCollectionInput = | TemplateCollectionEnvelope | BioTemplateEnvelope | Array> export interface UseTemplateCollectionOptions extends UseExperimentSaveOptions { /** Initial template collection or factory; defaults to an empty collection. */ initial?: TemplateCollectionInput | (() => TemplateCollectionInput) } export interface UseTemplateCollectionReturn { /** Current experiment id from platform injection, URL query, or URL path. */ currentExperimentId: ComputedRef /** Whether a current experiment id is available. */ hasCurrentExperiment: ComputedRef /** Full template collection envelope ready to save into design_data. */ templateCollection: Ref /** Template map keyed by template_id. */ templates: ComputedRef /** Collection-level metadata, or an empty object. */ metadata: ComputedRef> /** Template ids currently present in the collection. */ templateIds: ComputedRef /** Whether the collection contains at least one template. */ hasTemplates: ComputedRef /** Whether a load or delete operation is in progress. */ isLoading: Ref /** Whether a save operation is in progress. */ isSaving: Ref /** Error message from the last failed operation, or null. */ error: Ref /** Timestamp of the last successful load, or null. */ lastLoadedAt: Ref /** Timestamp of the last successful save, or null. */ lastSavedAt: Ref /** Replace local state from a template envelope, collection envelope, or raw design_data. */ apply: (value: unknown, metadata?: Record) => TemplateCollectionEnvelope /** Reset local state to the configured initial collection. */ reset: () => TemplateCollectionEnvelope /** Add or replace one template in the local collection. */ setTemplate: (template: BioTemplateEnvelope) => TemplateCollectionEnvelope /** Remove one template from the local collection. */ removeTemplate: (templateId: string) => boolean /** Read one template from the local collection, returning null when missing. */ getTemplate: = BioTemplateEnvelope>( templateId: string, ) => TTemplate | null /** Read one template from the local collection, throwing when missing or mismatched. */ requireTemplate: = BioTemplateEnvelope>( templateId: string, ) => TTemplate /** Load template design_data for an experiment and apply it locally. */ load: (experimentId: number) => Promise /** Load template design_data for the current experiment and apply it locally. */ loadCurrent: () => Promise /** Save the local template collection as experiment design_data. */ save: (experimentId: number) => Promise /** Save the local template collection as current experiment design_data. */ saveCurrent: () => Promise /** Require and return the current experiment id, or throw when missing. */ requireCurrentExperimentId: () => number } /** * Manages a biology template collection in the current experiment's design_data. * * @deprecated Scheduled for removal in MINT 1.4; no SDK replacement. */ export function useTemplateCollection( options: UseTemplateCollectionOptions = {}, ): UseTemplateCollectionReturn { const experimentSave = useExperimentSave(options) const templateCollection = ref(createInitialCollection(options)) const templates = computed(() => extractTemplateCollection(templateCollection.value)) const metadata = computed(() => templateCollection.value.metadata ?? {}) const templateIds = computed(() => Object.keys(templates.value)) const hasTemplates = computed(() => templateIds.value.length > 0) function apply(value: unknown, metadata?: Record): TemplateCollectionEnvelope { templateCollection.value = normalizeTemplateCollection(value, metadata) return templateCollection.value } function reset(): TemplateCollectionEnvelope { templateCollection.value = createInitialCollection(options) return templateCollection.value } function setTemplate(template: BioTemplateEnvelope): TemplateCollectionEnvelope { const nextTemplates = { ...templates.value, [template.template_id]: template, } templateCollection.value = createTemplateCollectionEnvelope( Object.values(nextTemplates), templateCollection.value.metadata, ) return templateCollection.value } function removeTemplate(templateId: string): boolean { if (!templates.value[templateId]) return false const nextTemplates = { ...templates.value } delete nextTemplates[templateId] templateCollection.value = createTemplateCollectionEnvelope( Object.values(nextTemplates), templateCollection.value.metadata, ) return true } function getTemplate< TTemplate extends BioTemplateEnvelope = BioTemplateEnvelope, >(templateId: string): TTemplate | null { return (templates.value[templateId] as TTemplate | undefined) ?? null } function requireTemplate< TTemplate extends BioTemplateEnvelope = BioTemplateEnvelope, >(templateId: string): TTemplate { return ensureTemplateFromCollection(templateCollection.value, templateId) } async function load(experimentId: number): Promise { const data = await experimentSave.loadDesign(experimentId) return data === null ? null : apply(data) } async function loadCurrent(): Promise { const data = await experimentSave.loadCurrentDesign() return data === null ? null : apply(data) } async function save(experimentId: number): Promise { return experimentSave.saveDesign( experimentId, templateCollection.value as unknown as Record, ) } async function saveCurrent(): Promise { return experimentSave.saveCurrentDesign( templateCollection.value as unknown as Record, ) } return { currentExperimentId: experimentSave.currentExperimentId, hasCurrentExperiment: experimentSave.hasCurrentExperiment, templateCollection, templates, metadata, templateIds, hasTemplates, isLoading: experimentSave.isLoading, isSaving: experimentSave.isSaving, error: experimentSave.error, lastLoadedAt: experimentSave.lastLoadedAt, lastSavedAt: experimentSave.lastSavedAt, apply, reset, setTemplate, removeTemplate, getTemplate, requireTemplate, load, loadCurrent, save, saveCurrent, requireCurrentExperimentId: experimentSave.requireCurrentExperimentId, } } function createInitialCollection(options: UseTemplateCollectionOptions): TemplateCollectionEnvelope { const initial = typeof options.initial === 'function' ? options.initial() : options.initial if (initial === undefined) return { templates: {} } return normalizeTemplateCollection(initial) } function normalizeTemplateCollection( value: unknown, metadata?: Record, ): TemplateCollectionEnvelope { if (Array.isArray(value)) { return createTemplateCollectionEnvelope(value, metadata) } return createTemplateCollectionEnvelope( Object.values(extractTemplateCollection(value)), readCollectionMetadata(value) ?? metadata, ) } function createTemplateCollectionEnvelope( templates: Array>, metadata?: Record, ): TemplateCollectionEnvelope { return metadata === undefined ? createTemplateCollection(templates) : createTemplateCollection(templates, metadata) } function readCollectionMetadata(value: unknown): Record | undefined { if ( typeof value !== 'object' || value === null || Array.isArray(value) || !('metadata' in value) ) { return undefined } const metadata = (value as { metadata?: unknown }).metadata if ( typeof metadata === 'object' && metadata !== null && !Array.isArray(metadata) ) { return metadata as Record } return undefined }