import { __rest } from "tslib";
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { useState, useRef, useMemo, useEffect } from 'react';
import { observer } from 'mobx-react';
import { css } from '@patternfly/react-styles';
import { Tooltip, TooltipPosition } from '@patternfly/react-core';
import CheckCircleIcon from '@patternfly/react-icons/dist/esm/icons/check-circle-icon';
import ExclamationCircleIcon from '@patternfly/react-icons/dist/esm/icons/exclamation-circle-icon';
import ExclamationTriangleIcon from '@patternfly/react-icons/dist/esm/icons/exclamation-triangle-icon';
import styles from '../../css/topology-components';
import { isNode, LabelPosition, NodeStatus, TopologyQuadrant } from '../../types';
import Decorator from '../decorators/Decorator';
import { Layer } from '../layers';
import { TOP_LAYER } from '../../const';
import { createSvgIdUrl, StatusModifier, useCombineRefs, useHover } from '../../utils';
import NodeLabel from './labels/NodeLabel';
import NodeShadows, { NODE_SHADOW_FILTER_ID_DANGER, NODE_SHADOW_FILTER_ID_HOVER } from './NodeShadows';
import { DEFAULT_DECORATOR_RADIUS, getDefaultShapeDecoratorCenter, getShapeComponent } from './shapes';
const StatusQuadrant = TopologyQuadrant.upperLeft;
const getStatusIcon = (status) => {
    switch (status) {
        case NodeStatus.danger:
            return _jsx(ExclamationCircleIcon, { className: "pf-m-danger" });
        case NodeStatus.warning:
            return _jsx(ExclamationTriangleIcon, { className: "pf-m-warning" });
        case NodeStatus.success:
            return _jsx(CheckCircleIcon, { className: "pf-m-success" });
        default:
            return null;
    }
};
const SCALE_UP_TIME = 200;
const DefaultNodeInner = observer(({ className, element, selected, hover, scaleNode, showLabel = true, label, secondaryLabel, labelClassName, labelPosition, scaleLabel, truncateLength, labelIconClass, labelIcon, labelIconPadding, nodeStatus, showStatusBackground, showStatusDecorator = false, statusDecoratorTooltip, getCustomShape, getShapeDecoratorCenter, onStatusDecoratorClick, badge, badgeColor, badgeTextColor, badgeBorderColor, badgeClassName, badgeLocation, onSelect, children, attachments, dragNodeRef, dragging, edgeDragging, canDrop, dropTarget, dndDropRef, onHideCreateConnector, onShowCreateConnector, onContextMenu, contextMenuOpen, raiseLabelOnHover = true, hideContextMenuKebab }) => {
    const [hovered, hoverRef] = useHover();
    const status = nodeStatus || element.getNodeStatus();
    const refs = useCombineRefs(hoverRef, dragNodeRef);
    const { width, height } = element.getDimensions();
    const isHover = hover !== undefined ? hover : hovered;
    const [nodeScale, setNodeScale] = useState(1);
    const decoratorRef = useRef(null);
    const statusDecorator = useMemo(() => {
        if (!status || !showStatusDecorator) {
            return null;
        }
        const icon = getStatusIcon(status);
        if (!icon) {
            return null;
        }
        const { x, y } = getShapeDecoratorCenter
            ? getShapeDecoratorCenter(StatusQuadrant, element)
            : getDefaultShapeDecoratorCenter(StatusQuadrant, element);
        const decorator = (_jsx(Decorator, { x: x, y: y, radius: DEFAULT_DECORATOR_RADIUS, showBackground: true, onClick: (e) => onStatusDecoratorClick(e, element), icon: _jsx("g", Object.assign({ className: css(styles.topologyNodeDecoratorStatus) }, { children: icon })), ariaLabel: status, innerRef: decoratorRef }));
        if (statusDecoratorTooltip) {
            return (_jsx(Tooltip, Object.assign({ triggerRef: decoratorRef, content: statusDecoratorTooltip, position: TooltipPosition.left }, { children: decorator })));
        }
        return decorator;
    }, [status, showStatusDecorator, getShapeDecoratorCenter, element, statusDecoratorTooltip, onStatusDecoratorClick]);
    useEffect(() => {
        if (isHover) {
            onShowCreateConnector && onShowCreateConnector();
        }
        else {
            onHideCreateConnector && onHideCreateConnector();
        }
    }, [isHover, onShowCreateConnector, onHideCreateConnector]);
    const ShapeComponent = (getCustomShape && getCustomShape(element)) || getShapeComponent(element);
    const groupClassName = css(styles.topologyNode, className, isHover && 'pf-m-hover', (dragging || edgeDragging) && 'pf-m-dragging', canDrop && 'pf-m-highlight', canDrop && dropTarget && 'pf-m-drop-target', selected && 'pf-m-selected', StatusModifier[status]);
    const backgroundClassName = css(styles.topologyNodeBackground, showStatusBackground && StatusModifier[status], showStatusBackground && selected && 'pf-m-selected');
    let filter;
    if (status === 'danger') {
        filter = createSvgIdUrl(NODE_SHADOW_FILTER_ID_DANGER);
    }
    else if (isHover || dragging || edgeDragging || dropTarget) {
        filter = createSvgIdUrl(NODE_SHADOW_FILTER_ID_HOVER);
    }
    const nodeLabelPosition = labelPosition || element.getLabelPosition();
    const scale = element.getGraph().getScale();
    const animationRef = useRef(null);
    const scaleGoal = useRef(1);
    const nodeScaled = useRef(false);
    useEffect(() => {
        if (!scaleNode || scale >= 1) {
            setNodeScale(1);
            nodeScaled.current = false;
            if (animationRef.current) {
                cancelAnimationFrame(animationRef.current);
                animationRef.current = 0;
            }
        }
        else {
            scaleGoal.current = 1 / scale;
            const scaleDelta = scaleGoal.current - scale;
            const initTime = performance.now();
            const bumpScale = (bumpTime) => {
                const scalePercent = (bumpTime - initTime) / SCALE_UP_TIME;
                const nextScale = Math.min(scale + scaleDelta * scalePercent, scaleGoal.current);
                setNodeScale(nextScale);
                if (nextScale < scaleGoal.current) {
                    animationRef.current = requestAnimationFrame(bumpScale);
                }
                else {
                    nodeScaled.current = true;
                    animationRef.current = 0;
                }
            };
            if (nodeScaled.current) {
                setNodeScale(scaleGoal.current);
            }
            else if (!animationRef.current) {
                animationRef.current = requestAnimationFrame(bumpScale);
            }
        }
        return () => {
            if (animationRef.current) {
                cancelAnimationFrame(animationRef.current);
                animationRef.current = 0;
            }
        };
    }, [scale, scaleNode]);
    // counter scale label
    const counterScale = (scale, scaleMin, scaleMax, valueMin, valueMax) => {
        if (scale >= scaleMax) {
            return valueMin;
        }
        else if (scale <= scaleMin) {
            return valueMax;
        }
        return valueMin + (1 - (scale - scaleMin) / (scaleMax - scaleMin)) * (valueMax - valueMin);
    };
    const labelScale = scaleLabel ? counterScale(scale, 0.35, 0.85, 1, 1.6) : 1;
    const labelPositionScale = scaleLabel ? Math.min(1, 1 / labelScale) : 1;
    const { translateX, translateY } = useMemo(() => {
        if (!scaleNode) {
            return { translateX: 0, translateY: 0 };
        }
        const bounds = element.getBounds();
        const translateX = bounds.width / 2 - (bounds.width / 2) * nodeScale;
        const translateY = bounds.height / 2 - (bounds.height / 2) * nodeScale;
        return { translateX, translateY };
    }, [element, nodeScale, scaleNode]);
    const renderLabel = () => {
        if (!showLabel || !(label || element.getLabel())) {
            return null;
        }
        let labelX;
        let labelY;
        const labelPaddingX = 8;
        const labelPaddingY = 4;
        if (nodeLabelPosition === LabelPosition.right) {
            labelX = (width + labelPaddingX) * labelPositionScale;
            labelY = height / 2;
        }
        else if (nodeLabelPosition === LabelPosition.left) {
            labelX = 0;
            labelY = height / 2 - labelPaddingY;
        }
        else if (nodeLabelPosition === LabelPosition.top) {
            labelX = width / 2;
            labelY = labelPaddingY + labelPaddingY / 2;
        }
        else {
            labelX = (width / 2) * labelPositionScale;
            labelY = height + labelPaddingY + labelPaddingY / 2;
        }
        const nodeLabel = (_jsx("g", Object.assign({ transform: raiseLabelOnHover && isHover
                ? `${scaleNode ? `translate(${translateX}, ${translateY})` : ''} scale(${nodeScale})`
                : undefined }, { children: _jsx("g", Object.assign({ transform: `scale(${labelScale})` }, { children: _jsx(NodeLabel, Object.assign({ className: css(styles.topologyNodeLabel, labelClassName), x: labelX, y: labelY * labelPositionScale, position: nodeLabelPosition, paddingX: 8, paddingY: 4, secondaryLabel: secondaryLabel, truncateLength: truncateLength, status: status, badge: badge, badgeColor: badgeColor, badgeTextColor: badgeTextColor, badgeBorderColor: badgeBorderColor, badgeClassName: badgeClassName, badgeLocation: badgeLocation, onContextMenu: onContextMenu, contextMenuOpen: contextMenuOpen, hideContextMenuKebab: hideContextMenuKebab, hover: isHover, labelIconClass: labelIconClass, labelIcon: labelIcon, labelIconPadding: labelIconPadding }, { children: label || element.getLabel() })) })) })));
        if (isHover && raiseLabelOnHover) {
            return _jsx(Layer, Object.assign({ id: TOP_LAYER }, { children: nodeLabel }));
        }
        return nodeLabel;
    };
    return (_jsxs("g", Object.assign({ className: groupClassName, transform: `${scaleNode ? `translate(${translateX}, ${translateY})` : ''} scale(${nodeScale})` }, { children: [_jsx(NodeShadows, {}), _jsxs("g", Object.assign({ ref: refs, onClick: onSelect, onContextMenu: onContextMenu }, { children: [ShapeComponent && (_jsx(ShapeComponent, { className: backgroundClassName, element: element, width: width, height: height, dndDropRef: dndDropRef, filter: filter })), renderLabel(), children] })), statusDecorator, attachments] })));
});
const DefaultNode = (_a) => {
    var { element, showLabel = true, showStatusDecorator = false } = _a, rest = __rest(_a, ["element", "showLabel", "showStatusDecorator"]);
    if (!isNode(element)) {
        throw new Error('DefaultNode must be used only on Node elements');
    }
    return (_jsx(DefaultNodeInner, Object.assign({ element: element, showLabel: showLabel, showStatusDecorator: showStatusDecorator }, rest)));
};
export default DefaultNode;
//# sourceMappingURL=DefaultNode.js.map