import type { PaginatedListMeta, ParsedRemittanceList } from '@/types' type UnknownRecord = Record function isRecord(value: unknown): value is UnknownRecord { return typeof value === 'object' && value !== null && !Array.isArray(value) } /** * Unwraps the Ghabilee API envelope `{ success, data }` when present. * * `data` is usually a record (the paginated `{ items, response }` shape), * but some endpoints (e.g. geography's flat provinces/cities lists) * return a bare array — `isRecord` alone would reject that and this * function would fall through to returning the *wrapper* itself, which * no caller actually wants. Callers expecting a flat list (like * services/geography.ts) already narrow with `Array.isArray(...)` * themselves. */ // Typed unwrap is the public API for service callers; the param appears only in the return position by design. // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters -- intentional typed unwrap helper export function unwrapApiPayload(raw: unknown): T { if (!isRecord(raw)) { return {} as T } if (raw.success === true && (isRecord(raw.data) || Array.isArray(raw.data))) { // Arrays are returned as-is for list endpoints; callers narrow with Array.isArray. return raw.data as T } return raw as T } /** Unwrap API envelope to unknown (record or array). Prefer for typed narrowing. */ export function unwrapApiDataUnknown(raw: unknown): unknown { if (!isRecord(raw)) return raw if (raw.success === true && 'data' in raw) return raw.data return raw } function formatSortValue(value: unknown): string { if (typeof value === 'string') return value if (typeof value === 'number' || typeof value === 'boolean' || typeof value === 'bigint') return String(value) return '' } function toNumber(value: unknown, fallback: number): number { const parsed = Number(value) return Number.isFinite(parsed) ? parsed : fallback } function extractItems(payload: UnknownRecord, itemsKey: string): unknown[] { const keyed = payload[itemsKey] if (Array.isArray(keyed)) { return keyed } if (Array.isArray(payload.items)) { return payload.items } return [] } function extractListMeta(payload: UnknownRecord, fallbackPage: number, fallbackPageSize: number): PaginatedListMeta { const response = isRecord(payload.response) ? payload.response : {} const meta = isRecord(payload.meta) ? payload.meta : {} const page = toNumber(response.page ?? meta.page, fallbackPage) const pageSize = toNumber(response.pageSize ?? meta.limit ?? meta.pageSize, fallbackPageSize) const totalItemsCount = toNumber(response.totalItemsCount ?? meta.total ?? meta.totalItemsCount, 0) const totalPages = toNumber(response.totalPages ?? meta.total_page ?? meta.totalPages, Math.ceil(totalItemsCount / pageSize) || 0) const filtersSource = response.filters ?? meta.filters const filters = isRecord(filtersSource) ? Object.fromEntries(Object.entries(filtersSource).map(([key, value]) => [key, String(value)])) : {} return { page, pageSize, totalItemsCount, totalPages, sort: formatSortValue(response.sort ?? meta.sort), filters, ...(response.resultType != null ? { resultType: toNumber(response.resultType, 0) } : {}), ...(typeof response.fileData === 'string' ? { fileData: response.fileData } : {}), } } /** * Parses a paginated list response from the Ghabilee API (or legacy raw shape). */ export function parseRemittanceList( raw: unknown, itemsKey = 'items', fallbackPage = 1, fallbackPageSize = 20 ): ParsedRemittanceList { const payload = unwrapApiPayload(raw) return { items: extractItems(payload, itemsKey) as T[], pagination: extractListMeta(payload, fallbackPage, fallbackPageSize), } } /** * Derives itemsKey from the last non-empty URL segment (e.g. .../users → users). */ export function getItemsKeyFromUrl(url: string): string { const path = url.split('?')[0]?.replace(/\/+$/, '') ?? '' const segments = path.split('/').filter(Boolean) const last = segments[segments.length - 1] return last || 'items' } /** * Extracts base64 Excel payload from a list export response. */ export function extractExportFileData(raw: unknown): string | undefined { const payload = unwrapApiPayload(raw) const response = isRecord(payload.response) ? payload.response : undefined const fileData = response?.fileData return typeof fileData === 'string' && fileData.length > 0 ? fileData : undefined }