"use client";
import { type NodeProps, Position } from "@xyflow/react";
import { HiddenHandles } from "@/components/attack-paths/graph/nodes/hidden-handles";
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/components/shadcn/tooltip";
import { resolveNodeColors, resolveNodeVisual } from "@/lib/attack-paths";
import { RESOURCE_NODE_DIMENSIONS } from "@/lib/attack-paths/node-dimensions";
import { getNodeLabelDisplay } from "@/lib/attack-paths/node-label-lines";
import type { GraphNode } from "@/types/attack-paths";
interface ResourceNodeData {
graphNode: GraphNode;
hasFindings?: boolean;
[key: string]: unknown;
}
const NODE_WIDTH = RESOURCE_NODE_DIMENSIONS.WIDTH;
const NODE_HEIGHT = RESOURCE_NODE_DIMENSIONS.HEIGHT;
const NAME_MAX_CHARS = RESOURCE_NODE_DIMENSIONS.LABEL_MAX_CHARS;
const NAME_MAX_LINES = RESOURCE_NODE_DIMENSIONS.LABEL_MAX_LINES;
const BADGE_SIZE = 44;
const BADGE_RADIUS = BADGE_SIZE / 2;
const BADGE_CENTER_X = NODE_WIDTH / 2;
const BADGE_CENTER_Y = 26;
const BADGE_LEFT_X = BADGE_CENTER_X - BADGE_RADIUS;
const BADGE_RIGHT_X = BADGE_CENTER_X + BADGE_RADIUS;
const ICON_SIZE = 28;
const ICON_X = BADGE_CENTER_X - ICON_SIZE / 2;
const ICON_Y = BADGE_CENTER_Y - ICON_SIZE / 2;
const TEXT_X = BADGE_CENTER_X;
const NAME_Y = 66;
const NAME_LINE_HEIGHT = 13;
const TYPE_Y = 118;
const toIconTestId = (description: string): string =>
`attack-path-node-icon-${description
.toLowerCase()
.replace(/[^a-z0-9]+/g, "-")
.replace(/(^-|-$)/g, "")}`;
export const ResourceNode = ({ data, selected }: NodeProps) => {
const { graphNode, hasFindings } = data as ResourceNodeData;
const { fillColor, borderColor } = resolveNodeColors({
labels: graphNode.labels,
properties: graphNode.properties,
selected,
hasFindings,
});
const badgeStrokeWidth = selected ? 4 : hasFindings ? 3 : 1.5;
const glowRadius = selected ? 31 : hasFindings ? 29 : 0;
const glowOpacity = selected ? 0.32 : hasFindings ? 0.26 : 0;
const visual = resolveNodeVisual(graphNode);
const Icon = visual.Icon;
const displayName = getNodeLabelDisplay(
visual.displayName,
NAME_MAX_CHARS,
NAME_MAX_LINES,
);
const typeLabel = visual.description;
const iconLabel = `${visual.description} icon`;
const nodeSvg = (
);
return (
<>
{displayName.isTruncated ? (
{nodeSvg}
{visual.displayName}
) : (
nodeSvg
)}
>
);
};