import type { FlowBoardDraft, FlowBoardSheetSnapshotData, FlowBoardSnapshotData, } from "@/lib/db/schema"; export function isRecord(value: unknown): value is Record { return typeof value === "object" && value !== null && !Array.isArray(value); } function normalizeRecord(value: unknown): Record | null { return isRecord(value) ? value : null; } const DEFAULT_FLOW_BOARD_CANVAS = { backgroundColor: "#f7f8fb", gridColor: "#d8dde8", gridEnabled: false, gridSize: 24, }; function normalizeCanvasColor(value: unknown, fallback: string) { return typeof value === "string" && /^#[0-9a-f]{6}$/i.test(value) ? value.toLowerCase() : fallback; } function normalizeFlowBoardCanvas(value: unknown): Record { const canvas = isRecord(value) ? value : {}; const requestedGridSize = typeof canvas.gridSize === "number" && Number.isFinite(canvas.gridSize) ? Math.round(canvas.gridSize) : DEFAULT_FLOW_BOARD_CANVAS.gridSize; return { backgroundColor: normalizeCanvasColor( canvas.backgroundColor, DEFAULT_FLOW_BOARD_CANVAS.backgroundColor ), gridColor: normalizeCanvasColor( canvas.gridColor, DEFAULT_FLOW_BOARD_CANVAS.gridColor ), gridEnabled: typeof canvas.gridEnabled === "boolean" ? canvas.gridEnabled : DEFAULT_FLOW_BOARD_CANVAS.gridEnabled, gridSize: Math.min(64, Math.max(8, requestedGridSize)), }; } function normalizeSheetId(value: unknown, index: number) { const id = typeof value === "string" ? value.trim().slice(0, 128) : ""; return id || `sheet-${index + 1}`; } function normalizeSheetTitle(value: unknown, index: number) { const title = typeof value === "string" ? value.trim().slice(0, 128) : ""; return title || `画布 ${index + 1}`; } function normalizeFlowBoardSheet( value: unknown, index: number ): FlowBoardSheetSnapshotData | null { if (!isRecord(value)) { return null; } return { id: normalizeSheetId(value.id, index), title: normalizeSheetTitle(value.title, index), children: Array.isArray(value.children) ? value.children.filter(isRecord) : [], viewport: normalizeRecord(value.viewport), theme: normalizeRecord(value.theme), canvas: normalizeFlowBoardCanvas(value.canvas), }; } export function normalizeFlowBoardTitle(value: unknown) { const title = String(value || "").trim(); return title.slice(0, 256) || "未命名流程白板"; } export function normalizeFlowBoardSnapshot( value: unknown ): FlowBoardSnapshotData { const raw = isRecord(value) ? value : {}; const legacyChildren = Array.isArray(raw.children) ? raw.children.filter(isRecord) : []; const normalizedSheets = Array.isArray(raw.sheets) ? raw.sheets .map(normalizeFlowBoardSheet) .filter( (sheet): sheet is FlowBoardSheetSnapshotData => Boolean(sheet) ) : []; const sheets = normalizedSheets.length > 0 ? normalizedSheets : [ { id: "sheet-1", title: "画布 1", children: legacyChildren, viewport: normalizeRecord(raw.viewport), theme: normalizeRecord(raw.theme), canvas: normalizeFlowBoardCanvas(raw.canvas), }, ]; const requestedActiveSheetId = typeof raw.activeSheetId === "string" ? raw.activeSheetId.trim() : ""; const activeSheet = sheets.find((sheet) => sheet.id === requestedActiveSheetId) || sheets[0]; const activeChildren = Array.isArray(raw.children) ? legacyChildren : activeSheet.children; const activeViewport = isRecord(raw.viewport) ? raw.viewport : activeSheet.viewport; const activeTheme = isRecord(raw.theme) ? raw.theme : activeSheet.theme; const activeCanvas = isRecord(raw.canvas) ? normalizeFlowBoardCanvas(raw.canvas) : normalizeFlowBoardCanvas(activeSheet.canvas); const nextSheets = sheets.map((sheet) => sheet.id === activeSheet.id ? { ...sheet, children: activeChildren, viewport: activeViewport, theme: activeTheme, canvas: activeCanvas, } : sheet ); const updatedAt = typeof raw.updatedAt === "string" && raw.updatedAt.trim() ? raw.updatedAt : new Date().toISOString(); return { version: 2, activeSheetId: activeSheet.id, sheets: nextSheets, children: activeChildren, viewport: activeViewport, theme: activeTheme, canvas: activeCanvas, updatedAt, }; } export function mapFlowBoardDraft(draft: FlowBoardDraft) { return { id: draft.id, title: draft.title || "未命名流程白板", snapshot: draft.snapshot ?? { children: [] }, createdAt: draft.createdAt?.toISOString() ?? null, updatedAt: draft.updatedAt?.toISOString() ?? null, }; } export function mapFlowBoardDraftSummary(draft: FlowBoardDraft) { return { id: draft.id, title: draft.title || "未命名流程白板", createdAt: draft.createdAt?.toISOString() ?? null, updatedAt: draft.updatedAt?.toISOString() ?? null, }; }