import type { SelectOption } from '../types' import { isRecord } from '../utils/records' export interface ExperimentDesignDataResponse { experiment_id?: number plugin_id?: string data?: Record schema_version?: string updated_at?: string | null } export interface ExtractExperimentSamplesOptions { includeControls?: boolean } function isPlatformDesignDataResponse(value: Record): boolean { return ( 'data' in value && ( 'experiment_id' in value || 'plugin_id' in value || 'schema_version' in value || 'updated_at' in value ) ) } /** Return the plugin-defined design_data payload from common platform and plugin response shapes. */ export function unwrapExperimentDesignData( rawData: Record | null | undefined, ): Record | null { if (!rawData) return null if (isRecord(rawData.data) && isPlatformDesignDataResponse(rawData)) { return rawData.data } if (isRecord(rawData.design_data)) { return rawData.design_data } if (isRecord(rawData.designData)) { return rawData.designData } return rawData } function shouldIncludeSample(record: Record, options: ExtractExperimentSamplesOptions): boolean { if (options.includeControls) return true const rawType = record.sample_type ?? record.sampleType ?? record.type ?? record.well_type ?? record.wellType const type = typeof rawType === 'string' ? rawType.toLowerCase() : '' return !['blank', 'qc'].includes(type) } function readSampleValue(record: Record): string | null { const candidates = [ record.sample_name, record.sampleName, record.sample_id, record.sampleId, record.name, record.id, record.label, ] for (const candidate of candidates) { if (typeof candidate === 'string' && candidate.trim()) { return candidate } if (typeof candidate === 'number' && Number.isFinite(candidate)) { return String(candidate) } } return null } function readSampleLabel(record: Record, fallback: string): string { const label = record.name ?? record.label ?? record.sample_name ?? record.sampleName return typeof label === 'string' && label.trim() ? label : fallback } function readSampleDescription(record: Record): string | undefined { const parts = [ record.group, record.batch, record.batch_id, record.batchId, record.plate_id, record.plateId, record.well_id, record.wellId, ].filter((value): value is string | number => (typeof value === 'string' && value.trim().length > 0) || typeof value === 'number', ) return parts.length > 0 ? parts.map(String).join(' / ') : undefined } function addSampleRecord( options: SelectOption[], seen: Set, value: unknown, extractOptions: ExtractExperimentSamplesOptions, ): void { if (typeof value === 'string') { const trimmed = value.trim() if (trimmed && !seen.has(trimmed)) { seen.add(trimmed) options.push({ value: trimmed, label: trimmed }) } return } if (!isRecord(value) || !shouldIncludeSample(value, extractOptions)) return const sampleValue = readSampleValue(value) if (!sampleValue || seen.has(sampleValue)) return seen.add(sampleValue) options.push({ value: sampleValue, label: readSampleLabel(value, sampleValue), description: readSampleDescription(value), }) } function addSampleArray( options: SelectOption[], seen: Set, value: unknown, extractOptions: ExtractExperimentSamplesOptions, ): void { if (!Array.isArray(value)) return for (const sample of value) { addSampleRecord(options, seen, sample, extractOptions) } } /** Extract selectable sample options from raw or wrapped experiment design_data. */ export function extractSampleOptionsFromDesignData( rawData: Record | null | undefined, options: ExtractExperimentSamplesOptions = {}, ): SelectOption[] { const designData = unwrapExperimentDesignData(rawData) if (!designData) return [] const sampleOptions: SelectOption[] = [] const seen = new Set() addSampleArray(sampleOptions, seen, designData.samples, options) if (isRecord(designData.templates)) { for (const template of Object.values(designData.templates)) { if (!isRecord(template)) continue const templateData = isRecord(template.data) ? template.data : template addSampleArray(sampleOptions, seen, templateData.samples, options) } } if (Array.isArray(designData.plates)) { for (const plate of designData.plates) { if (!isRecord(plate)) continue addSampleArray(sampleOptions, seen, plate.samples, options) } } return sampleOptions } /** Extract SampleSelector-compatible string values from raw or wrapped experiment design_data. */ export function extractSampleNamesFromDesignData( rawData: Record | null | undefined, options: ExtractExperimentSamplesOptions = {}, ): string[] { return extractSampleOptionsFromDesignData(rawData, options).map(sample => sample.value) }