import type { AppConfig, Calibration, Mat3, Vec2, RenderStyle } from './types'; export const DEFAULT_RENDER: RenderStyle = { cutColor: '#00e5ff', rapidColor: '#ff9800', lineWidth: 1.5 }; function isFiniteNumber(n: unknown): n is number { return typeof n === 'number' && Number.isFinite(n); } function isVec2Array(v: unknown): v is Vec2[] { return Array.isArray(v) && v.every((p) => Array.isArray(p) && p.length === 2 && p.every(isFiniteNumber)); } function parseCalibration(c: unknown): Calibration | null { if (!c || typeof c !== 'object') return null; const obj = c as Record; if (!isVec2Array(obj.imagePoints) || !isVec2Array(obj.machinePoints)) return null; if (!Array.isArray(obj.homography) || obj.homography.length !== 9) return null; if (!obj.homography.every(isFiniteNumber)) return null; if (obj.imagePoints.length !== obj.machinePoints.length || obj.imagePoints.length < 4) return null; return { imagePoints: obj.imagePoints, machinePoints: obj.machinePoints, homography: obj.homography as Mat3, }; } function parseRenderStyle(r: unknown): RenderStyle { const obj = (r && typeof r === 'object' ? r : {}) as Record; return { cutColor: typeof obj.cutColor === 'string' ? obj.cutColor : DEFAULT_RENDER.cutColor, rapidColor: typeof obj.rapidColor === 'string' ? obj.rapidColor : DEFAULT_RENDER.rapidColor, lineWidth: isFiniteNumber(obj.lineWidth) ? obj.lineWidth : DEFAULT_RENDER.lineWidth, }; } export async function loadConfig(url: string): Promise { const res = await fetch(url); if (!res.ok) throw new Error(`Failed to load ${url}: ${res.status}`); const raw = (await res.json()) as Record; return { streamUrl: typeof raw.streamUrl === 'string' ? raw.streamUrl : '', calibration: parseCalibration(raw.calibration), renderDefaults: parseRenderStyle(raw.renderDefaults), }; }