<!-- Jinja2 Macro: 定义一个通用的节点结构 -->
{% macro render_node(node_data, node_instance_id, x=100, y=100) %}
<div class="node-box" id="{{ node_instance_id }}" data-node-type="{{ node_data.id }}"
    style="top: {{ y }}px; left: {{ x }}px; width: 280px;" x-data="{
        x: {{ x }},
        y: {{ y }},
        w: 280,
        h: 0,
        dragging: false,
        resizing: false,
        lastMouseX: 0,
        lastMouseY: 0,
        startDrag(e) {
            if (e.target.closest('.node-header')) {
                this.dragging = true;
                this.lastMouseX = e.clientX;
                this.lastMouseY = e.clientY;
                window.addEventListener('mousemove', this.handleDrag.bind(this));
                window.addEventListener('mouseup', this.stopDrag.bind(this));
            }
        },
        handleDrag(e) {
            if (this.dragging) {
                const zoom = window.currentZoom || 1;
                const dx = (e.clientX - this.lastMouseX) / zoom;
                const dy = (e.clientY - this.lastMouseY) / zoom;

                // 计算连续移动值
                this.x += dx;
                this.y += dy;

                // 应用 30px 的网格吸附
                const snapX = Math.round(this.x / 30) * 30;
                const snapY = Math.round(this.y / 30) * 30;

                this.lastMouseX = e.clientX;
                this.lastMouseY = e.clientY;

                $el.style.left = snapX + 'px';
                $el.style.top = snapY + 'px';

                if (window.updateLines) window.updateLines();
            }
        },
        stopDrag() {
            this.dragging = false;
            window.removeEventListener('mousemove', this.handleDrag);
            window.removeEventListener('mouseup', this.stopDrag);
        },
        startResize(e) {
            e.preventDefault();
            e.stopPropagation();
            this.resizing = true;
            this.lastMouseX = e.clientX;
            this.lastMouseY = e.clientY;
            // 获取当前宽高作为基准
            const rect = $el.getBoundingClientRect();
            const zoom = window.currentZoom || 1;
            this.w = rect.width / zoom;
            this.h = rect.height / zoom;
            window.addEventListener('mousemove', this.handleResize.bind(this));
            window.addEventListener('mouseup', this.stopResize.bind(this));
        },
        handleResize(e) {
            if (this.resizing) {
                const zoom = window.currentZoom || 1;
                const dx = (e.clientX - this.lastMouseX) / zoom;
                const dy = (e.clientY - this.lastMouseY) / zoom;
                this.w += dx;
                this.h += dy;
                this.lastMouseX = e.clientX;
                this.lastMouseY = e.clientY;
                $el.style.width = Math.max(250, this.w) + 'px';
                $el.style.height = Math.max(280, this.h) + 'px';
                if (window.updateLines) window.updateLines();
            }
        },
        stopResize() {
            this.resizing = false;
            window.removeEventListener('mousemove', this.handleResize);
            window.removeEventListener('mouseup', this.stopResize);
        }
    }" @mousedown="startDrag">
    <!-- 标题栏 -->
    <div class="node-header">
        <span class="node-title">{{ node_data.name }}</span>
        <div class="header-tools">
            <div class="status-dot"></div>
            <button class="node-delete-btn" @click.stop="window.removeNode('{{ node_instance_id }}')"
                title="删除节点">×</button>
        </div>
    </div>

    <!-- 输入区域 -->
    <div class="node-body">
        <div class="node-inputs">
            {% for input in node_data.inputs %}
            <div class="slot-group">
                <div class="slot input-slot" id="{{ node_instance_id }}_in_{{ input.name }}"
                    data-node-id="{{ node_instance_id }}" data-slot-name="{{ input.name }}" data-slot-type="input"
                    data-data-type="{{ input.data_type }}" onmousedown="startConnectionDrag(event, this)">
                    <div class="slot-dot"></div>
                    <label>{{ input.label }}</label>
                </div>
                {% if input.ui_widget == 'text_area' %}
                <textarea name="{{ input.name }}" placeholder="在此输入内容..." class="node-input-field"></textarea>
                {% elif input.ui_widget == 'text_input' %}
                <input type="text" name="{{ input.name }}" placeholder="在此输入..." class="node-input-field">
                {% elif input.ui_widget == 'select' %}
                <select name="{{ input.name }}" class="node-input-field">
                    {% for option in input.options %}
                    <option value="{{ option }}">{{ option }}</option>
                    {% endfor %}
                </select>
                {% elif input.ui_widget == 'file_upload' %}
                <label class="file-upload-label">
                    <input type="file" name="{{ input.name }}" class="hidden-input" hx-post="/api/upload_asset"
                        hx-encoding="multipart/form-data" hx-target="#content-{{ node_instance_id }}">
                    <div class="upload-box">
                        <span class="upload-icon">↑</span>
                        <span class="upload-text">点击上传或直接粘贴</span>
                    </div>
                </label>
                {% elif input.ui_widget == 'hidden' %}
                <input type="hidden" name="{{ input.name }}">
                {% endif %}
            </div>
            {% endfor %}
        </div>

        <!-- 预览/输出区域 -->
        <div class="node-content" id="content-{{ node_instance_id }}">
            <div class="placeholder">
                <div class="loader-ripple" hx-indicator></div>
                <span>等待操作...</span>
            </div>
        </div>

        <!-- 输出插槽 -->
        <div class="node-outputs">
            {% for output in node_data.outputs %}
            <div class="slot output-slot" id="{{ node_instance_id }}_out_{{ output.name }}"
                data-node-id="{{ node_instance_id }}" data-slot-name="{{ output.name }}" data-slot-type="output"
                data-data-type="{{ output.data_type }}" onmousedown="startConnectionDrag(event, this)">
                <label>{{ output.label }}</label>
                <div class="slot-dot"></div>
            </div>
            {% endfor %}
        </div>
    </div>

    <!-- 缩放句柄 -->
    <div class="node-resizer" @mousedown="startResize"></div>
</div>
{% endmacro %}