import { computed, ref, type ComputedRef, type Ref } from 'vue' import { useApi } from './useApi' import { useRequestSyncState } from './useRequestSyncState' import { createBioTemplatePresetCollection } from '../templates' import type { CreateLcmsBatchCollectionOptions, CreateQpcrExpressionCollectionOptions, CreateWellPlateScreenCollectionOptions, TemplatePresetId, } from '../templates' import { getInjectedPlatformContext, resolveCurrentExperimentId, } from './platformContextHelpers' type TemplatePresetCollectionOptions = | CreateWellPlateScreenCollectionOptions | CreateQpcrExpressionCollectionOptions | CreateLcmsBatchCollectionOptions export interface UseExperimentSaveOptions { /** Default plugin_id for all save/load calls. */ pluginId?: string /** Default schema version for design saves (defaults to "1.0"). */ schemaVersion?: string /** Override API base URL. */ apiBaseUrl?: string } export interface UseExperimentSaveReturn { /** Current experiment id from platform injection, URL query, or URL path. */ currentExperimentId: ComputedRef /** Whether a current experiment id is available. */ hasCurrentExperiment: ComputedRef /** Whether a save operation is in progress. */ isSaving: Ref /** Whether a load or delete operation is in progress. */ isLoading: 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 /** Save design data for an experiment. */ saveDesign: (experimentId: number, data: Record) => Promise /** Save design data for the current experiment. */ saveCurrentDesign: (data: Record) => Promise /** Create and save a built-in biology template preset as experiment design data. */ saveTemplatePreset: ( experimentId: number, name: TemplatePresetId | string, options?: TemplatePresetCollectionOptions, ) => Promise /** Create and save a built-in biology template preset for the current experiment. */ saveCurrentTemplatePreset: ( name: TemplatePresetId | string, options?: TemplatePresetCollectionOptions, ) => Promise /** Save analysis result for an experiment. */ saveAnalysis: (experimentId: number, result: Record) => Promise /** Save analysis result for the current experiment. */ saveCurrentAnalysis: (result: Record) => Promise /** Save design and/or analysis in one call. */ save: (experimentId: number, opts: { design?: Record analysis?: Record }) => Promise /** Load design data for an experiment. */ loadDesign: (experimentId: number) => Promise | null> /** Load design data for the current experiment. */ loadCurrentDesign: () => Promise | null> /** Load analysis result for an experiment. */ loadAnalysis: (experimentId: number) => Promise | null> /** Load analysis result for the current experiment. */ loadCurrentAnalysis: () => Promise | null> /** Delete design data for an experiment. */ deleteDesign: (experimentId: number) => Promise /** Delete design data for the current experiment. */ deleteCurrentDesign: () => Promise /** Delete analysis result for an experiment. */ deleteAnalysis: (experimentId: number) => Promise /** Delete analysis result for the current experiment. */ deleteCurrentAnalysis: () => Promise /** Require and return the current experiment id, or throw when missing. */ requireCurrentExperimentId: () => number } /** * Persists experiment design, analysis, and built-in template preset data through the platform API. * * @deprecated Scheduled for removal in MINT 1.4; no SDK replacement. */ export function useExperimentSave( options: UseExperimentSaveOptions = {}, ): UseExperimentSaveReturn { const injectedContext = getInjectedPlatformContext() const api = useApi({ baseUrl: options.apiBaseUrl ?? injectedContext?.platformApiUrl }) const pluginId = options.pluginId ?? injectedContext?.plugin?.id const schemaVersion = options.schemaVersion ?? '1.0' const currentExperimentId = computed(() => { return resolveCurrentExperimentId(injectedContext) }) const hasCurrentExperiment = computed(() => currentExperimentId.value !== undefined) const request = useRequestSyncState('Experiment request failed.') const isSaving = ref(false) const isLoading = ref(false) const error = request.error const lastLoadedAt = request.lastLoadedAt const lastSavedAt = request.lastSavedAt function requireCurrentExperimentId(): number { const id = currentExperimentId.value if (id === undefined) { throw new Error('[MINT SDK] No current experiment is selected.') } return id } function currentExperimentIdOrError(action: string): number | undefined { const id = currentExperimentId.value if (id === undefined) { request.setError(`No current experiment is selected for ${action}`) return undefined } return id } async function saveDesign( experimentId: number, data: Record, ): Promise { if (!pluginId) { request.setError('pluginId is required for saveDesign') return false } isSaving.value = true try { await request.run(async () => { await api.put(`/experiments/${experimentId}/data`, { plugin_id: pluginId, data, schema_version: schemaVersion, }) }, { success: 'save' }) return true } catch { return false } finally { isSaving.value = false } } async function saveCurrentDesign(data: Record): Promise { const experimentId = currentExperimentIdOrError('saveDesign') return experimentId === undefined ? false : saveDesign(experimentId, data) } async function saveTemplatePreset( experimentId: number, name: TemplatePresetId | string, presetOptions: TemplatePresetCollectionOptions = {}, ): Promise { const collection = createBioTemplatePresetCollection(name, presetOptions) return saveDesign(experimentId, collection as unknown as Record) } async function saveCurrentTemplatePreset( name: TemplatePresetId | string, presetOptions: TemplatePresetCollectionOptions = {}, ): Promise { const experimentId = currentExperimentIdOrError('saveTemplatePreset') return experimentId === undefined ? false : saveTemplatePreset(experimentId, name, presetOptions) } async function saveAnalysis( experimentId: number, result: Record, ): Promise { if (!pluginId) { request.setError('pluginId is required for saveAnalysis') return false } isSaving.value = true try { await request.run(async () => { await api.put(`/experiments/${experimentId}/results/${pluginId}`, { result, }) }, { success: 'save' }) return true } catch { return false } finally { isSaving.value = false } } async function saveCurrentAnalysis(result: Record): Promise { const experimentId = currentExperimentIdOrError('saveAnalysis') return experimentId === undefined ? false : saveAnalysis(experimentId, result) } async function save( experimentId: number, opts: { design?: Record; analysis?: Record }, ): Promise { if ((opts.design || opts.analysis) && !pluginId) { request.setError('pluginId is required for save') return false } isSaving.value = true try { await request.run(async () => { if (opts.design) { await api.put(`/experiments/${experimentId}/data`, { plugin_id: pluginId, data: opts.design, schema_version: schemaVersion, }) } if (opts.analysis) { await api.put(`/experiments/${experimentId}/results/${pluginId}`, { result: opts.analysis, }) } }, { success: 'save' }) return true } catch { return false } finally { isSaving.value = false } } async function loadDesign( experimentId: number, ): Promise | null> { isLoading.value = true try { return await request.run( () => api.get>(`/experiments/${experimentId}/data`), { success: 'load' }, ) } catch { return null } finally { isLoading.value = false } } async function loadCurrentDesign(): Promise | null> { const experimentId = currentExperimentIdOrError('loadDesign') return experimentId === undefined ? null : loadDesign(experimentId) } async function loadAnalysis( experimentId: number, ): Promise | null> { if (!pluginId) { request.setError('pluginId is required for loadAnalysis') return null } isLoading.value = true try { return await request.run( () => api.get>( `/experiments/${experimentId}/results/${pluginId}`, ), { success: 'load' }, ) } catch { return null } finally { isLoading.value = false } } async function loadCurrentAnalysis(): Promise | null> { const experimentId = currentExperimentIdOrError('loadAnalysis') return experimentId === undefined ? null : loadAnalysis(experimentId) } async function deleteDesign(experimentId: number): Promise { isLoading.value = true try { await request.run(() => api.delete(`/experiments/${experimentId}/data`)) return true } catch { return false } finally { isLoading.value = false } } async function deleteCurrentDesign(): Promise { const experimentId = currentExperimentIdOrError('deleteDesign') return experimentId === undefined ? false : deleteDesign(experimentId) } async function deleteAnalysis(experimentId: number): Promise { if (!pluginId) { request.setError('pluginId is required for deleteAnalysis') return false } isLoading.value = true try { await request.run(() => api.delete(`/experiments/${experimentId}/results/${pluginId}`)) return true } catch { return false } finally { isLoading.value = false } } async function deleteCurrentAnalysis(): Promise { const experimentId = currentExperimentIdOrError('deleteAnalysis') return experimentId === undefined ? false : deleteAnalysis(experimentId) } return { currentExperimentId, hasCurrentExperiment, isSaving, isLoading, error, lastLoadedAt, lastSavedAt, saveDesign, saveCurrentDesign, saveTemplatePreset, saveCurrentTemplatePreset, saveAnalysis, saveCurrentAnalysis, save, loadDesign, loadCurrentDesign, loadAnalysis, loadCurrentAnalysis, deleteDesign, deleteCurrentDesign, deleteAnalysis, deleteCurrentAnalysis, requireCurrentExperimentId, } }