viz/specializers/flowchart/flowchart-visualizer.js

/**
 * Visualisiert Regelbäume als interaktives Flowchart.
 * Nutzt DOM-Elemente für Styling (Karten, Farbbalken).
 * @fileoverview
 */

class FlowchartVisualizer {
    /**
     * @param {string} containerId - DOM ID des Containers.
     * @param {DecisionTree} tree - Der Regelbaum.
     * @param {function} toggleCallback - Callback(ruleName).
     */
    constructor(containerId, tree, toggleCallback) {
        this.container = document.getElementById(containerId);
        this.tree = tree;
        this.onToggle = toggleCallback;
        this.prefix = containerId;
    }

    render() {
        if (!this.container || !this.tree) return;
        this.container.innerHTML = '';
        this._renderNode(this.tree.root, this.container);
    }

    _renderNode(node, parentEl) {
        if (!node) return;

        // Haupt-Karte erstellen
        const card = document.createElement('div');
        card.className = 'fc-node';
        // ID für Highlighting: containerId-Regelname
        card.id = `${this.prefix}-${node.name.replace(/\s+/g, '-')}`;

        // Typen-Klassen
        if (!node.active) card.classList.add('inactive');
        if (node.children) card.classList.add('fc-type-group');
        if (node.conditionFn) card.classList.add('fc-type-cond');

        // Inhalt (Checkbox + Titel)
        const content = document.createElement('div');
        content.className = 'fc-content';

        const checkbox = document.createElement('input');
        checkbox.type = 'checkbox';
        checkbox.checked = node.active;
        checkbox.onclick = (e) => {
            e.stopPropagation(); 
            if (this.onToggle) this.onToggle(node.name);
        };

        const label = document.createElement('span');
        label.className = 'fc-label';
        // Icons
        let icon = '⚡'; // Atomic
        if (node.children) icon = '📂';
        if (node.conditionFn) icon = '◇';
        
        label.innerHTML = `${icon} ${node.name}`;

        content.appendChild(checkbox);
        content.appendChild(label);
        card.appendChild(content);

        // Beschreibung
        if (node.description) {
            const desc = document.createElement('div');
            desc.className = 'fc-desc';
            desc.innerText = node.description;
            card.appendChild(desc);
        }

        parentEl.appendChild(card);

        // --- REKURSION FÜR KINDER ---

        // Fall 1: Verzweigung (Condition) -> Nebeneinander
        if (node.conditionFn) {
            const branchContainer = document.createElement('div');
            branchContainer.className = 'fc-branches';

            // Ja-Zweig
            if (node.thenNode) {
                const branchYes = document.createElement('div');
                branchYes.className = 'fc-branch';
                branchYes.innerHTML = '<div class="fc-branch-label">JA</div>';
                this._renderNode(node.thenNode, branchYes);
                branchContainer.appendChild(branchYes);
            }

            // Nein-Zweig
            if (node.elseNode) {
                const branchNo = document.createElement('div');
                branchNo.className = 'fc-branch';
                branchNo.innerHTML = '<div class="fc-branch-label">NEIN</div>';
                this._renderNode(node.elseNode, branchNo);
                branchContainer.appendChild(branchNo);
            }
            parentEl.appendChild(branchContainer);
        }
        // Fall 2: Gruppe -> Untereinander (eingerückt)
        else if (node.children && node.children.length > 0) {
            const childContainer = document.createElement('div');
            childContainer.className = 'fc-children';
            node.children.forEach(child => {
                this._renderNode(child, childContainer);
            });
            parentEl.appendChild(childContainer);
        }
    }

    // --- HIGHLIGHTING API ---

    highlightCheck(name) {
        this._setClass(name, 'checking');
    }

    highlightSuccess(name) {
        this._setClass(name, 'success');
    }

    clearHighlights() {
        this.container.querySelectorAll('.checking, .success').forEach(el => {
            el.classList.remove('checking', 'success');
        });
    }

    _setClass(name, cls) {
        const id = `${this.prefix}-${name.replace(/\s+/g, '-')}`;
        const el = document.getElementById(id);
        if (el) {
            // Alte Klassen entfernen, um Konflikte zu vermeiden
            el.classList.remove('checking', 'success');
            el.classList.add(cls);
            // Auto-Scroll, damit man sieht, wo die KI ist
            if (cls === 'checking') {
                el.scrollIntoView({ behavior: "smooth", block: "nearest" });
            }
        }
    }

    _removeClass(name, cls) {
        const id = `${this.prefix}-${name.replace(/\s+/g, '-')}`;
        const el = document.getElementById(id);
        if (el) {
            el.classList.remove(cls);
        }
    }
}