import { FacadeHandlerError, FacadeRequest, FacadeResponse, FacadeSerialQueue, createRequest, isFacadeRequest } from "./facade-envelope.js"; export const START_SCHEMA = "freecad-web-start/module/v1" as const; export const START_COMMAND = "start.moduleImport" as const; export const START_DESKTOP_COMMAND = "Start:desktop:Start_Start" as const; export const START_IMPORT_SVG_COMMAND = "start.importSvg" as const; export interface StartImportSvgPayload { schema: "freecad-web-start/import-svg/v1"; module: "Start"; action: "import"; path: string; content?: string } export interface StartImportSvgResult { schema: "freecad-web-start/import-svg/v1"; module: "Start"; command: "Start_ImportSVG"; status: "imported"; document: string; object: string; label: string; recomputed: true; objectCount: number; objects: Array<{ object: string; length: number; valid: true; isNull: false }> } export interface StartDesktopPayload { schema: "freecad-web-start/desktop/v1"; module: "Start"; action: "Start_Start"; args: Record } export interface StartDesktopResult { schema: "freecad-web-start/desktop/v1"; module: "Start"; command: "Start_Start"; action: "show"; status: "exposed"; view: "StartView"; active: true; documentCount: number; activeDocument: string | null; documents: Array<{ name: string; label: string }>; args: Record } export const START_FIXTURE = { schema: START_SCHEMA, module: "Start", action: "module-import", sourceEntry: "src/Mod/Start/App/AppStart.cpp" } as const; export interface StartPayload { schema: typeof START_SCHEMA; module: "Start"; action: "module-import"; sourceEntry: string } export interface StartResult { schema: typeof START_SCHEMA; module: "Start"; action: "module-import"; sourceEntry: typeof START_FIXTURE.sourceEntry; moduleAvailable: true; nativeTypeCount: 0; pythonApiCount: 0; resourcesReleased: true } export interface StartKernel { ccall(name: string, type: "number" | "string", argTypes?: string[], args?: unknown[]): number | string } export type StartRequest = FacadeRequest; export type StartResponse = FacadeResponse; export function createStartRequest(id: string, payload: StartPayload = START_FIXTURE): StartRequest { return createRequest(id, START_COMMAND, payload, "start"); } export function isStartRequest(value: unknown): value is StartRequest { if (!isFacadeRequest(value)) return false; const request = value as StartRequest; const payload = request.payload; return request.command === START_COMMAND && payload?.schema === START_SCHEMA && payload?.module === "Start" && payload?.action === "module-import" && typeof payload?.sourceEntry === "string"; } export async function handleStart(payload: StartPayload, signal: AbortSignal, source: StartKernel | Promise): Promise { if (signal.aborted) throw new FacadeHandlerError("cancelled", "request was cancelled"); if (!isStartRequest(createStartRequest("validation", payload)) || payload.sourceEntry !== START_FIXTURE.sourceEntry) throw new FacadeHandlerError("revision_mismatch", "Start source entry is not locked", { field: "sourceEntry" }); const kernel = await source; if (signal.aborted) throw new FacadeHandlerError("cancelled", "request was cancelled"); if (kernel.ccall("fc_web_start_initialize", "number") !== 1) throw new FacadeHandlerError("start_failed", String(kernel.ccall("fc_web_start_error", "string"))); const result = { schema: START_SCHEMA, module: "Start", action: "module-import", sourceEntry: START_FIXTURE.sourceEntry, moduleAvailable: Number(kernel.ccall("fc_web_start_module_available", "number")) === 1, nativeTypeCount: Number(kernel.ccall("fc_web_start_type_count", "number")), pythonApiCount: Number(kernel.ccall("fc_web_start_python_api_count", "number")), resourcesReleased: Number(kernel.ccall("fc_web_start_resources_released", "number")) === 1 } as StartResult; if (!result.moduleAvailable || result.nativeTypeCount !== 0 || result.pythonApiCount !== 0 || !result.resourcesReleased) throw new FacadeHandlerError("result_mismatch", "Start module result invalid"); return result; } export function dispatchStart(queue: FacadeSerialQueue, request: StartRequest, source: StartKernel | Promise): Promise { return queue.dispatch(request, (payload, signal) => handleStart(payload, signal, source)); } export type StartDesktopRequest = FacadeRequest; export function createStartDesktopRequest(id: string, args: Record = {}): StartDesktopRequest { return createRequest(id, START_DESKTOP_COMMAND, { schema: "freecad-web-start/desktop/v1", module: "Start", action: "Start_Start", args }, "start"); } export function isStartDesktopRequest(value: unknown): value is StartDesktopRequest { return isFacadeRequest(value) && value.command === START_DESKTOP_COMMAND; } export async function handleStartDesktop(payload: StartDesktopPayload, signal: AbortSignal, source: StartKernel | Promise): Promise { if (signal.aborted) throw new FacadeHandlerError("cancelled", "request was cancelled"); const kernel = await source; const raw = String(kernel.ccall("fc_web_dispatch_json", "string", ["string"], [JSON.stringify({ cmd: "Start_Start", args: payload.args || {} })])); let result: StartDesktopResult; try { result = JSON.parse(raw) as StartDesktopResult; } catch { throw new FacadeHandlerError("start_failed", raw); } if (!result.active || result.status !== "exposed" || result.view !== "StartView" || !Array.isArray(result.documents) || result.documentCount !== result.documents.length) throw new FacadeHandlerError("start_failed", "Start view model is invalid"); return { ...result, schema: "freecad-web-start/desktop/v1", module: "Start", command: "Start_Start", args: payload.args || {} }; } export function dispatchStartDesktop(queue: FacadeSerialQueue, request: StartDesktopRequest, source: StartKernel | Promise): Promise> { return queue.dispatch(request, (payload, signal) => handleStartDesktop(payload, signal, source)); } export type StartImportSvgRequest = FacadeRequest; export function createStartImportSvgRequest(id: string, path: string, content = ""): StartImportSvgRequest { return createRequest(id, START_IMPORT_SVG_COMMAND, { schema: "freecad-web-start/import-svg/v1", module: "Start", action: "import", path, content }, "start"); } export function isStartImportSvgRequest(value: unknown): value is StartImportSvgRequest { if (!isFacadeRequest(value) || value.command !== START_IMPORT_SVG_COMMAND) return false; const payload = (value as StartImportSvgRequest).payload; return payload?.schema === "freecad-web-start/import-svg/v1" && payload.module === "Start" && payload.action === "import" && typeof payload.path === "string" && (payload.content === undefined || typeof payload.content === "string"); } export async function handleStartImportSvg(payload: StartImportSvgPayload, signal: AbortSignal, source: StartKernel | Promise): Promise { if (signal.aborted) throw new FacadeHandlerError("cancelled", "request was cancelled"); if (!isStartImportSvgRequest(createRequest("validation", START_IMPORT_SVG_COMMAND, payload))) throw new FacadeHandlerError("invalid_request", "SVG input requires a path and optional text content"); if (!payload.path.toLowerCase().endsWith(".svg")) throw new FacadeHandlerError("invalid_path", "Start SVG import requires a .svg path", { path: payload.path }); const kernel = await source; if (signal.aborted) throw new FacadeHandlerError("cancelled", "request was cancelled"); const raw = String(kernel.ccall("fc_web_dispatch_json", "string", ["string"], [JSON.stringify({ cmd: "Start_ImportSVG", args: { path: payload.path, content: payload.content ?? "" } })])); let native: Omit; try { native = JSON.parse(raw); } catch { throw new FacadeHandlerError("import_failed", raw); } if (!native || native.command !== "Start_ImportSVG" || native.status !== "imported" || typeof native.document !== "string" || !native.document || typeof native.object !== "string" || !native.object || native.recomputed !== true || !Number.isSafeInteger(native.objectCount) || !Array.isArray(native.objects) || !native.objects.length || native.objectCount < native.objects.length || native.objects.some(object => !object || typeof object.object !== "string" || !object.object || object.valid !== true || object.isNull !== false || !Number.isFinite(object.length) || object.length <= 0) || !native.objects.some(object => object.object === native.object) || new Set(native.objects.map(object => object.object)).size !== native.objects.length) throw new FacadeHandlerError("import_failed", "Start SVG import result is invalid"); return { ...native, schema: "freecad-web-start/import-svg/v1", module: "Start" }; } export function dispatchStartImportSvg(queue: FacadeSerialQueue, request: StartImportSvgRequest, source: StartKernel | Promise): Promise> { return queue.dispatch(request, (payload, signal) => handleStartImportSvg(payload, signal, source)); }