import { getAuthToken } from "@/utils/token"; import { NONE_SELECT_VALUE, type EventRecord, type ImageRecord, type SelectOption } from "./types"; interface BrapiPagination { currentPage: number; pageSize: number; totalCount: number; totalPages: number; } interface BrapiListResponse { metadata: { pagination: BrapiPagination; status: Array>; datafiles: Array>; }; result: { data: T[]; }; } interface BrapiSingleResponse { metadata: { pagination: BrapiPagination; status: Array>; datafiles: Array>; }; result: T; } interface StudyResponse { studyDbId: string; studyName: string | null; } interface ObservationUnitResponse { observationUnitDbId: string; observationUnitName: string | null; } interface ObservationResponse { observationDbId: string; observationUnitName: string | null; observationVariableName: string | null; } type EventPayload = Partial>; type ImagePayload = Partial>; const apiBase = () => { if (typeof window !== "undefined") return ""; return process.env.API_BASE_URL || process.env.NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000"; }; async function request(path: string, init?: RequestInit): Promise { const token = getAuthToken(); const response = await fetch(`${apiBase()}${path}`, { ...init, headers: { "Content-Type": "application/json", ...(token ? { Authorization: `Bearer ${token}` } : {}), ...(init?.headers || {}), }, }); if (!response.ok) { const detail = await response.text(); throw new Error(detail || `Request failed: ${response.status}`); } return response.json() as Promise; } const optionalText = (value: unknown) => { const normalized = String(value ?? "").trim(); if (!normalized || normalized === NONE_SELECT_VALUE) return null; return normalized; }; const requiredText = (value: unknown, message: string) => { const normalized = optionalText(value); if (!normalized) throw new Error(message); return normalized; }; const csvToArray = (value: unknown) => { const normalized = optionalText(value); if (!normalized) return []; return normalized .split(",") .map((part) => part.trim()) .filter(Boolean); }; const arrayToCsv = (value: unknown) => { if (!Array.isArray(value)) return null; return value.map((item) => String(item)).filter(Boolean).join(", "); }; const mapEvent = (event: EventRecord): EventRecord => { const startDate = event.start_date || event.eventDateRange?.startDate || null; const endDate = event.end_date || event.eventDateRange?.endDate || null; const unitIds = Array.isArray(event.observationUnitDbIds) ? event.observationUnitDbIds : []; return { ...event, id: event.eventDbId || event.id, event_type: event.event_type || event.eventType || null, event_type_db_id: event.event_type_db_id || event.eventTypeDbId || null, event_description: event.event_description || event.eventDescription || null, study_db_id: event.study_db_id || event.studyDbId || null, observation_unit_db_ids: event.observation_unit_db_ids || arrayToCsv(unitIds), start_date: startDate, end_date: endDate, }; }; const mapImage = (image: ImageRecord): ImageRecord => { const observationIds = Array.isArray(image.observationDbIds) ? image.observationDbIds : []; return { ...image, id: image.imageDbId || image.id, image_name: image.image_name || image.imageName || null, image_file_name: image.image_file_name || image.imageFileName || null, image_url: image.image_url || image.imageURL || null, mime_type: image.mime_type || image.mimeType || null, study_db_id: image.study_db_id || image.studyDbId || null, observation_unit_db_id: image.observation_unit_db_id || image.observationUnitDbId || null, observation_db_ids: image.observation_db_ids || arrayToCsv(observationIds), image_time_stamp: image.image_time_stamp || image.imageTimeStamp || null, image_file_size: image.image_file_size ?? image.imageFileSize ?? null, image_width: image.image_width ?? image.imageWidth ?? null, image_height: image.image_height ?? image.imageHeight ?? null, }; }; const eventBody = (payload: EventPayload) => ({ eventType: requiredText(payload.event_type, "Event type is required"), eventTypeDbId: optionalText(payload.event_type_db_id), eventDescription: optionalText(payload.event_description), studyDbId: optionalText(payload.study_db_id), observationUnitDbIds: csvToArray(payload.observation_unit_db_ids), eventDateRange: { startDate: optionalText(payload.start_date), endDate: optionalText(payload.end_date), }, }); const imageBody = (payload: ImagePayload) => ({ imageName: requiredText(payload.image_name, "Image name is required"), imageFileName: optionalText(payload.image_file_name), imageURL: optionalText(payload.image_url), mimeType: optionalText(payload.mime_type), description: optionalText(payload.description), studyDbId: optionalText(payload.study_db_id), observationUnitDbId: optionalText(payload.observation_unit_db_id), observationDbIds: csvToArray(payload.observation_db_ids), imageTimeStamp: optionalText(payload.image_time_stamp), imageFileSize: null, imageWidth: null, imageHeight: null, }); export async function fetchEventRows(): Promise { const response = await request>("/brapi/v2/events?page=0&pageSize=10"); return response.result.data.map(mapEvent); } export async function fetchImageRows(): Promise { const response = await request>("/brapi/v2/images?page=0&pageSize=10"); return response.result.data.map(mapImage); } export async function fetchEventImageOptions(): Promise<{ studies: SelectOption[]; observationUnits: SelectOption[]; observations: SelectOption[]; }> { const [studies, observationUnits, observations] = await Promise.all([ request>("/brapi/v2/studies?page=0&pageSize=10"), request>("/brapi/v2/observationunits?page=0&pageSize=10"), request>("/brapi/v2/observations?page=0&pageSize=10"), ]); return { studies: studies.result.data.map((study) => ({ value: study.studyDbId, label: study.studyName || study.studyDbId, })), observationUnits: observationUnits.result.data.map((unit) => ({ value: unit.observationUnitDbId, label: unit.observationUnitName || unit.observationUnitDbId, })), observations: observations.result.data.map((observation) => ({ value: observation.observationDbId, label: `${observation.observationDbId}${observation.observationUnitName ? ` / ${observation.observationUnitName}` : ""}${observation.observationVariableName ? ` / ${observation.observationVariableName}` : ""}`, })), }; } export async function createEventRow(payload: EventPayload): Promise { const response = await request>("/brapi/v2/events", { method: "POST", body: JSON.stringify({ eventDbId: requiredText(payload.id, "Event ID is required"), ...eventBody(payload), }), }); return mapEvent(response.result.data[0]); } export async function updateEventRow(id: string, payload: EventPayload): Promise { const requestedId = optionalText(payload.id); if (requestedId && requestedId !== id) { throw new Error("Event ID is immutable. Create a new record instead."); } const response = await request>(`/brapi/v2/events/${encodeURIComponent(id)}`, { method: "PUT", body: JSON.stringify(eventBody(payload)), }); return mapEvent(response.result.data[0]); } export async function deleteEventRow(id: string): Promise { await request>(`/brapi/v2/events/${encodeURIComponent(id)}`, { method: "DELETE", }); } export async function createImageRow(payload: ImagePayload): Promise { const response = await request>("/brapi/v2/images", { method: "POST", body: JSON.stringify({ imageDbId: requiredText(payload.id, "Image ID is required"), ...imageBody(payload), }), }); return mapImage(response.result.data[0]); } export async function updateImageRow(id: string, payload: ImagePayload): Promise { const requestedId = optionalText(payload.id); if (requestedId && requestedId !== id) { throw new Error("Image ID is immutable. Create a new record instead."); } const response = await request>(`/brapi/v2/images/${encodeURIComponent(id)}`, { method: "PUT", body: JSON.stringify(imageBody(payload)), }); return mapImage(response.result.data[0]); } export async function deleteImageRow(id: string): Promise { await request>(`/brapi/v2/images/${encodeURIComponent(id)}`, { method: "DELETE", }); }