import type { LocaleCode } from '../i18n/messages'; export type HelpArticleSummary = { slug: string; title: string; summary: string; version_introduced?: string; requires_app_version?: string | null; status?: string; translation_state?: string; last_reviewed_at?: string; owner?: string; updated_at?: string; related?: Array<{ slug: string; title?: string }>; }; export type HelpArticleDetail = HelpArticleSummary & { body_markdown?: string; body_html?: string; source_path?: string; }; export interface HelpListResult { articles: HelpArticleSummary[]; servedFromCache: boolean; } export interface HelpArticleResult { article: HelpArticleDetail; servedFromCache: boolean; } const AUDIENCE = 'guest'; const LIST_CACHE_TTL = 1000 * 60 * 60 * 6; // 6 hours const DETAIL_CACHE_TTL = 1000 * 60 * 60 * 24; // 24 hours interface CacheRecord { storedAt: number; data: T; } function listCacheKey(locale: LocaleCode): string { return `help:list:${AUDIENCE}:${locale}`; } function detailCacheKey(locale: LocaleCode, slug: string): string { return `help:article:${AUDIENCE}:${locale}:${slug}`; } function readCache(key: string, ttl: number): CacheRecord | null { if (typeof window === 'undefined') { return null; } try { const raw = window.localStorage.getItem(key); if (!raw) { return null; } const parsed = JSON.parse(raw) as CacheRecord; if (!parsed?.data || !parsed?.storedAt) { return null; } if (Date.now() - parsed.storedAt > ttl) { return null; } return parsed; } catch (error) { console.warn('[HelpApi] Failed to read cache', error); return null; } } function writeCache(key: string, data: T): void { if (typeof window === 'undefined') { return; } try { const payload: CacheRecord = { storedAt: Date.now(), data, }; window.localStorage.setItem(key, JSON.stringify(payload)); } catch (error) { console.warn('[HelpApi] Failed to write cache', error); } } async function requestJson(url: string): Promise { const response = await fetch(url, { headers: { 'Accept': 'application/json', }, credentials: 'same-origin', }); if (!response.ok) { const error = new Error('Help request failed'); (error as any).status = response.status; throw error; } const payload = await response.json(); return payload as T; } export async function getHelpArticles(locale: LocaleCode, options?: { forceRefresh?: boolean }): Promise { const cacheKey = listCacheKey(locale); const cached = readCache(cacheKey, LIST_CACHE_TTL); if (cached && !options?.forceRefresh) { return { articles: cached.data, servedFromCache: true }; } try { const params = new URLSearchParams({ audience: AUDIENCE, locale, }); const data = await requestJson<{ data?: HelpArticleSummary[] }>(`/api/v1/help?${params.toString()}`); const articles = Array.isArray(data?.data) ? data.data : []; writeCache(cacheKey, articles); return { articles, servedFromCache: false }; } catch (error) { if (cached) { return { articles: cached.data, servedFromCache: true }; } console.error('[HelpApi] Failed to fetch help articles', error); throw error; } } export async function getHelpArticle(slug: string, locale: LocaleCode): Promise { const cacheKey = detailCacheKey(locale, slug); const cached = readCache(cacheKey, DETAIL_CACHE_TTL); if (cached) { return { article: cached.data, servedFromCache: true }; } try { const params = new URLSearchParams({ audience: AUDIENCE, locale, }); const data = await requestJson<{ data?: HelpArticleDetail }>(`/api/v1/help/${encodeURIComponent(slug)}?${params.toString()}`); const article = data?.data ?? { slug, title: slug, summary: '' }; writeCache(cacheKey, article); return { article, servedFromCache: false }; } catch (error) { if (cached) { return { article: cached.data, servedFromCache: true }; } console.error('[HelpApi] Failed to fetch help article', error); throw error; } }