import type { BrandKit } from "@/domain/brandKit"; import { isBrandKit, withBrandKitDefaults } from "@/domain/brandKit"; import { request } from "@/infrastructure/designGateway/request"; const legacyStoragePrefix = "moteva:brand-kits:v1"; const migrationPrefix = "moteva:brand-kits:server-migration:v1"; const migrations = new Map>(); type APIBrandKit = { id: string; name: string; document: string; isDefault: boolean; createdAt: string; updatedAt: string; }; type APIBrandKitListResponse = { brandKits: APIBrandKit[]; }; type APIBrandKitResponse = { brandKit: APIBrandKit; }; export const brandKitRepository = { async list(scope: string | null) { if (!scope) return []; await migrateLegacyKits(scope); return listRemote(); }, async save(scope: string | null, kit: BrandKit) { assertUserScope(scope); const response = await request(`/api/brand-kits/${encodeURIComponent(kit.id)}`, { method: "PUT", body: JSON.stringify({ document: JSON.stringify(serializableBrandKit(kit)), isDefault: kit.applyToNewProjects }) }); return fromAPIKit(response.brandKit); }, async remove(scope: string | null, id: string) { assertUserScope(scope); await request<{ id: string; deleted: boolean }>(`/api/brand-kits/${encodeURIComponent(id)}`, { method: "DELETE" }); return listRemote(); } }; export function brandKitStorageScope(userId?: string) { return userId?.trim() || null; } async function listRemote() { const response = await request("/api/brand-kits"); return response.brandKits.map(fromAPIKit).sort((left, right) => right.updatedAt.localeCompare(left.updatedAt)); } function fromAPIKit(value: APIBrandKit): BrandKit { const parsed: unknown = JSON.parse(value.document); if (!isBrandKit(parsed)) throw new Error("Invalid Brand Kit document returned by the server"); return withBrandKitDefaults({ ...parsed, id: value.id, name: value.name, applyToNewProjects: value.isDefault, createdAt: value.createdAt, updatedAt: value.updatedAt }); } function serializableBrandKit(kit: BrandKit): BrandKit { return { version: 1, id: kit.id, name: kit.name, brandName: kit.brandName, tagline: kit.tagline, summary: kit.summary, audience: kit.audience, positioning: kit.positioning, personality: kit.personality, voice: { ...kit.voice }, logos: kit.logos.map((logo) => ({ ...logo })), coverImage: kit.coverImage ? { ...kit.coverImage } : undefined, colorGroups: kit.colorGroups.map((group) => ({ ...group, colors: group.colors.map((color) => ({ ...color })) })), typography: kit.typography.map((item) => ({ ...item, fontFile: item.fontFile ? { ...item.fontFile } : undefined })), referenceImages: kit.referenceImages.map((image) => ({ ...image })), visual: { ...kit.visual }, applyToNewProjects: kit.applyToNewProjects, createdAt: kit.createdAt, updatedAt: kit.updatedAt }; } function migrateLegacyKits(scope: string) { const active = migrations.get(scope); if (active) return active; const migration = runLegacyMigration(scope).finally(() => migrations.delete(scope)); migrations.set(scope, migration); return migration; } async function runLegacyMigration(scope: string) { if (typeof window === "undefined") return; const marker = migrationKey(scope); if (window.localStorage.getItem(marker) === "done") return; const local = readLegacyKits(scope); if (local.length > 0) { const remote = await listRemote(); const remoteIds = new Set(remote.map((kit) => kit.id)); for (const kit of local) { if (!remoteIds.has(kit.id)) await brandKitRepository.save(scope, kit); } } window.localStorage.removeItem(legacyStorageKey(scope)); window.localStorage.setItem(marker, "done"); } function readLegacyKits(scope: string) { if (typeof window === "undefined") return []; const raw = window.localStorage.getItem(legacyStorageKey(scope)); if (!raw) return []; try { const parsed: unknown = JSON.parse(raw); if (!Array.isArray(parsed)) return []; return parsed.filter(isBrandKit).map(withBrandKitDefaults); } catch { return []; } } function legacyStorageKey(scope: string) { return `${legacyStoragePrefix}:${encodeURIComponent(scope)}`; } function migrationKey(scope: string) { return `${migrationPrefix}:${encodeURIComponent(scope)}`; } function assertUserScope(scope: string | null): asserts scope is string { if (!scope) throw new Error("Authenticated user scope is required"); }