import type { CanvasViewOperationPlan } from "@/types/canvas/agent-protocol"; type CanvasAgentViewNodeLike = { id: string; type?: string; position?: { x?: number; y?: number }; size?: { width?: number; height?: number }; data?: unknown; createdAt?: string | number | Date; updatedAt?: string | number | Date; }; type CanvasAgentViewportLike = { x: number; y: number; zoom: number; }; type CanvasAgentStageLike = { width: number; height: number; }; const VIEWPORT_ANCHOR_X_RATIO = 0.52; const VIEWPORT_ANCHOR_Y_RATIO = 0.46; function toFiniteNumber(value: unknown, fallback = 0) { const numericValue = Number(value); return Number.isFinite(numericValue) ? numericValue : fallback; } function getNodeTimestamp(value: unknown) { if (typeof value === "number" && Number.isFinite(value)) return value; if (value instanceof Date) return value.getTime(); if (typeof value !== "string" || !value.trim()) return 0; const timestamp = Date.parse(value); return Number.isFinite(timestamp) ? timestamp : 0; } function getCanvasAgentResultScore(node: CanvasAgentViewNodeLike) { const data = node.data && typeof node.data === "object" && !Array.isArray(node.data) ? (node.data as Record) : {}; const metadata = data.metadata && typeof data.metadata === "object" && !Array.isArray(data.metadata) ? (data.metadata as Record) : {}; const generatedByAgent = metadata.generatedByAgent === true || metadata.latestGeneratedHighlight === true || typeof metadata.canvasAgentOperationId === "string"; if (!generatedByAgent) return 0; return Math.max( getNodeTimestamp(metadata.recentlyGeneratedAt), getNodeTimestamp(metadata.agentGeneratedAt), getNodeTimestamp(metadata.agentReplacedAt), getNodeTimestamp(node.updatedAt), getNodeTimestamp(node.createdAt), 1 ); } export function resolveCanvasViewOperationTargetNodeIds(params: { plan: Pick; nodes: CanvasAgentViewNodeLike[]; selectedNodeIds?: string[]; maxLatestResults?: number; }) { const explicitNodeIds = Array.isArray(params.plan.nodeIds) ? params.plan.nodeIds.map((id) => String(id || "").trim()).filter(Boolean) : []; if (explicitNodeIds.length > 0) return Array.from(new Set(explicitNodeIds)); const anchorNodeId = String(params.plan.anchorNodeId || "").trim(); if (anchorNodeId) return [anchorNodeId]; if (params.plan.operation === "focus_selection") { return Array.isArray(params.selectedNodeIds) ? params.selectedNodeIds.map((id) => String(id || "").trim()).filter(Boolean) : []; } if (params.plan.operation === "focus_latest_result") { const maxLatestResults = Math.max(1, Math.min(8, params.maxLatestResults || 4)); return params.nodes .map((node) => ({ id: node.id, score: getCanvasAgentResultScore(node), })) .filter((item) => item.id && item.score > 0) .sort((a, b) => b.score - a.score) .slice(0, maxLatestResults) .map((item) => item.id); } return []; } export function computeCanvasAgentPanViewport(params: { currentViewport: CanvasAgentViewportLike; center: { x?: number; y?: number }; stage: CanvasAgentStageLike; zoom?: number; }) { const currentZoom = Math.max(0.1, toFiniteNumber(params.currentViewport.zoom, 1)); const zoom = Math.max( 0.1, Math.min(4, toFiniteNumber(params.zoom, currentZoom)) ); const centerX = toFiniteNumber(params.center.x, 0); const centerY = toFiniteNumber(params.center.y, 0); const stageWidth = Math.max(1, toFiniteNumber(params.stage.width, 1200)); const stageHeight = Math.max(1, toFiniteNumber(params.stage.height, 800)); return { x: stageWidth * VIEWPORT_ANCHOR_X_RATIO - centerX * zoom, y: stageHeight * VIEWPORT_ANCHOR_Y_RATIO - centerY * zoom, zoom, }; }