import { shallowRef, computed, type Ref, type ComputedRef } from 'vue' import { useApi, type UseApiReturn } from './useApi' import { useRequestSyncState } from './useRequestSyncState' import { extractSampleNamesFromDesignData, extractSampleOptionsFromDesignData, unwrapExperimentDesignData, } from './experimentDesignData' import type { TreeNode, SummaryData, SelectOption } from '../types' export interface UseExperimentDataOptions { apiBaseUrl?: string immediate?: boolean } export interface UseExperimentDataReturn { /** Raw experiment data response from `/experiments/{id}/data`, or null. */ data: Ref | null> /** Plugin-defined design_data payload unwrapped from platform response shapes, or null. */ designData: ComputedRef | null> /** Sample values extracted from design_data for SampleSelector-style components. */ sampleNames: ComputedRef /** Sample options extracted from design_data for select/dropdown components. */ sampleOptions: ComputedRef[]> /** Hierarchy tree rows normalised from `tree_data` or `treeData`. */ treeData: ComputedRef /** Table rows normalised from `table_data` or `tableData`. */ tableData: ComputedRef[]> /** Summary payload normalised from `summary_data` or `summaryData`, or null. */ summaryData: ComputedRef /** Whether experiment data is currently loading. */ isLoading: Ref /** Error message from the last failed fetch, or null. */ error: Ref /** Timestamp of the last successful experiment data fetch, or null. */ lastLoadedAt: Ref /** Fetch experiment data for an experiment id. */ fetch: (experimentId: number) => Promise | null> /** Refetch the most recently fetched experiment id. */ refresh: () => Promise /** Clear local experiment data and current fetch target. */ clear: () => void } /** Fetches and normalises experiment output data (tree, table, summary) from the platform API. */ export function useExperimentData( options: UseExperimentDataOptions = {}, ): UseExperimentDataReturn { const data = shallowRef | null>(null) const request = useRequestSyncState('Failed to fetch experiment data') const isLoading = request.loading const error = request.error const lastLoadedAt = request.lastLoadedAt let lastExperimentId: number | null = null let api: UseApiReturn | null = null function getApi(): UseApiReturn { api ??= useApi({ baseUrl: options.apiBaseUrl }) return api } const designData = computed | null>(() => unwrapExperimentDesignData(data.value), ) const sampleNames = computed(() => extractSampleNamesFromDesignData(designData.value), ) const sampleOptions = computed[]>(() => extractSampleOptionsFromDesignData(designData.value), ) const treeData = computed(() => { if (!designData.value) return [] const tree = designData.value.tree_data ?? designData.value.treeData return Array.isArray(tree) ? tree as TreeNode[] : [] }) const tableData = computed[]>(() => { if (!designData.value) return [] const table = designData.value.table_data ?? designData.value.tableData return Array.isArray(table) ? table as Record[] : [] }) const summaryData = computed(() => { if (!designData.value) return null const summary = designData.value.summary_data ?? designData.value.summaryData if (summary && typeof summary === 'object' && 'metadata' in (summary as Record)) { return summary as SummaryData } return null }) async function fetchData(experimentId: number): Promise | null> { lastExperimentId = experimentId try { data.value = await request.run( () => getApi().get>(`/experiments/${experimentId}/data`), { success: 'load', errorMessage: 'Failed to fetch experiment data' }, ) return data.value } catch { data.value = null return null } } async function refresh(): Promise { if (lastExperimentId !== null) { await fetchData(lastExperimentId) } } function clear(): void { data.value = null lastExperimentId = null request.clearError() } return { data, designData, sampleNames, sampleOptions, treeData, tableData, summaryData, isLoading, error, lastLoadedAt, fetch: fetchData, refresh, clear, } }