Der universelle Komponenten-Analysator 🔬 (v21.4 - Verbessert)

Umfassendes Update: Optimierter Prompt-Header für bessere KI-Interaktion, erweiterte CSS-Eigenschaften für tiefere Analyse und verfeinerte HTML-Struktur-Extraktion.

// ==UserScript==
// @name          Der universelle Komponenten-Analysator 🔬 (v21.4 - Verbessert)
// @namespace     http://tampermonkey.net/
// @version       21.4
// @description   Umfassendes Update: Optimierter Prompt-Header für bessere KI-Interaktion, erweiterte CSS-Eigenschaften für tiefere Analyse und verfeinerte HTML-Struktur-Extraktion.
// @author        Assistant & Dein Name
// @match         *://*/*
// @grant         GM_addStyle
// @license       MIT
// @grant         GM_setClipboard
// @grant         GM_log
// @grant         GM_info
// ==/UserScript==

(function() {
    'use strict';

    // --- Skript-Konfiguration & Konstanten ---
    const SCRIPT_VERSION = GM_info.script.version; // Version direkt aus Metadaten
    const PICKER_BUTTON_SIZE = 50; // px
    const NOTIFICATION_OFFSET = 20; // px
    const STATUS_OFFSET_TOP = 20; // px

    // Globale Zustandsvariablen in einem Objekt gekapselt
    const appState = {
        pickerMode: false,
        pickerButton: null,
        lastHoveredElement: null,
        selectedElements: [],
        isDragging: false,
        wasDragging: false,
        offsetX: 0,
        offsetY: 0,
        defaultStylesCache: new Map(),
        notificationTimeout: null,
        statusTimeout: null,
        currentStatusMessage: '' // Hinzugefügt für Statusmanagement
    };

    // CSS-Stile für das Skript
    GM_addStyle(`
        #element-picker-btn {
            position: fixed;
            right: ${NOTIFICATION_OFFSET}px;
            top: 50%;
            transform: translateY(-50%);
            width: ${PICKER_BUTTON_SIZE}px;
            height: ${PICKER_BUTTON_SIZE}px;
            background: #007185;
            border: none;
            border-radius: 50%;
            cursor: grab;
            z-index: 999999;
            box-shadow: 0 2px 10px rgba(0,0,0,0.3);
            display: flex;
            align-items: center;
            justify-content: center;
            transition: background-color 0.3s ease;
        }
        #element-picker-btn:hover { background-color: #005a6b; }
        body.picker-dragging, body.picker-dragging * { cursor: grabbing !important; }
        #element-picker-btn.active { background: #dc3545; animation: pulse 1s infinite; }
        @keyframes pulse { 0% { box-shadow: 0 0 0 0 rgba(220, 53, 69, 0.7); } 70% { box-shadow: 0 0 0 10px rgba(220, 53, 69, 0); } 100% { box-shadow: 0 0 0 0 rgba(220, 53, 69, 0); } }
        .picker-cursor, .picker-cursor * { cursor: crosshair !important; }
        .picker-highlight { outline: 2px solid #00A896 !important; outline-offset: 1px !important; background-color: rgba(0, 168, 150, 0.1) !important; }
        .picker-selected { outline: 2px solid #007bff !important; outline-offset: 1px !important; background-color: rgba(0, 123, 255, 0.15) !important; }
        .picker-notification { position: fixed; top: ${NOTIFICATION_OFFSET}px; right: ${NOTIFICATION_OFFSET}px; background: #28a745; color: white; padding: 10px 20px; border-radius: 5px; z-index: 1000000; font-family: Arial, sans-serif; font-size: 14px; animation: slideIn 0.3s ease-out; opacity: 0; transition: opacity 0.3s ease-out, transform 0.3s ease-out; }
        .picker-notification.error { background-color: #dc3545; }
        @keyframes slideIn { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } }
        .picker-status { position: fixed; top: ${STATUS_OFFSET_TOP}px; left: 50%; transform: translateX(-50%); background: #dc3545; color: white; padding: 8px 16px; border-radius: 5px; z-index: 1000000; font-family: Arial, sans-serif; font-size: 12px; font-weight: bold; opacity: 0; transition: opacity 0.3s ease-out; }
    `);

    // --- KERNFUNKTIONEN ---
    function onElementClick(e) {
        if (!appState.pickerMode || e.target.id === 'element-picker-btn' || e.target.closest('#element-picker-btn')) return;

        // Verhindert das Standardverhalten des Klicks (z.B. Link-Navigation)
        e.preventDefault();
        e.stopPropagation();

        const selectedElement = appState.lastHoveredElement;
        if (!selectedElement) {
             showNotification('Kein Element zum Auswählen gefunden. Versuche es erneut.', true);
             return;
        }

        try {
            if (e.ctrlKey) { // Multi-Pick
                const index = appState.selectedElements.indexOf(selectedElement);
                if (index > -1) {
                    selectedElement.classList.remove('picker-selected');
                    appState.selectedElements.splice(index, 1);
                } else {
                    selectedElement.classList.add('picker-selected');
                    appState.selectedElements.push(selectedElement);
                }
                showStatus(`${appState.selectedElements.length} Element(e) ausgewählt. Klick ohne STRG zum Kopieren. ESC zum Beenden.`);
            } else { // Finaler Klick -> Datenverarbeitung
                let elementsToCopy = [...appState.selectedElements];
                if (!elementsToCopy.includes(selectedElement)) {
                    elementsToCopy.push(selectedElement);
                }

                if (elementsToCopy.length > 0) {
                    showStatus('Analysiere Elemente... Bitte warten.', false, true); // "Bitte warten..." Meldung
                    // Timeout um UI-Update zu ermöglichen, bevor schwere Berechnung startet
                    setTimeout(() => {
                        try { // Zusätzlicher Try-Catch für die Datenverarbeitung selbst
                            const startTime = performance.now();
                            const globalMeta = getGlobalPageMetadata();
                            const elementJsonObjects = elementsToCopy.map(el => extractElementAsJson(el));
                            const endTime = performance.now();
                            const scriptDuration = ((endTime - startTime) / 1000).toFixed(2) + 's';

                            const metadata = {
                                metadata: {
                                    url: window.location.href,
                                    title: document.title,
                                    timestamp_utc: new Date().toISOString(), // Präziserer Zeitstempel
                                    html_lang: document.documentElement.lang || 'unknown', // HTML-Sprache
                                    favicon_url: getFaviconUrl(), // Favicon-URL
                                    url_parameters: getUrlParameters(), // URL-Parameter
                                    ...globalMeta,
                                    selected_elements_count: elementsToCopy.length,
                                    script_runtime: scriptDuration,
                                    script_version: SCRIPT_VERSION // Skript-Version hier hinzufügen
                                }
                            };

                            const metadataLine = JSON.stringify(metadata);
                            const elementLines = elementJsonObjects.map(obj => JSON.stringify(obj));
                            const finalJsonlOutput = [metadataLine, ...elementLines].join('\n');

                            const promptHeader = getPromptHeader(elementsToCopy.length);
                            const finalReport = `${promptHeader}\n\n---\n\n${finalJsonlOutput}`;

                            GM_setClipboard(finalReport, 'text');
                            showNotification(`Semantischer Report für ${elementsToCopy.length} Element(e) kopiert!`);
                            deactivatePickerMode();
                        } catch (processingError) {
                            console.error(`Fehler bei der Datenverarbeitung (v${SCRIPT_VERSION}):`, processingError);
                            showNotification('Fehler bei der Datenverarbeitung! Siehe Konsole.', true);
                            deactivatePickerMode(); // Im Fehlerfall auch den Picker beenden
                        }
                    }, 50); // Kleiner Timeout
                } else {
                    showNotification('Keine Elemente zum Kopieren ausgewählt.', true);
                    deactivatePickerMode();
                }
            }
        } catch (error) {
            console.error(`Allgemeiner Fehler in Picker-Skript (v${SCRIPT_VERSION}):`, error);
            showNotification('Ein unerwarteter Fehler ist aufgetreten! Siehe Konsole.', true);
            deactivatePickerMode(); // Im Fehlerfall auch den Picker beenden
        }
    }

    // --- BUTTON POSITIONIERUNG ---
    function loadButtonPosition() {
        const savedPosition = localStorage.getItem('pickerButtonPosition');
        if (savedPosition) {
            const pos = JSON.parse(savedPosition);
            let x = parseInt(pos.x, 10);
            let y = parseInt(pos.y, 10);

            const docWidth = window.innerWidth;
            const docHeight = window.innerHeight;

            // Stellt sicher, dass der Button im sichtbaren Bereich bleibt
            x = Math.max(0, Math.min(x, docWidth - PICKER_BUTTON_SIZE));
            y = Math.max(0, Math.min(y, docHeight - PICKER_BUTTON_SIZE));

            appState.pickerButton.style.top = `${y}px`;
            appState.pickerButton.style.left = `${x}px`;
            appState.pickerButton.style.right = 'auto'; // Original 'right' überschreiben
            appState.pickerButton.style.transform = 'none'; // Original 'transform' überschreiben
        }
    }

    // --- Prompt-Header (Stark erweitert und verbessert) ---
    function getPromptHeader(elementCount) {
        const currentUrl = window.location.href;
        const currentTitle = document.title;
        const exampleJsonl = `
\`\`\`jsonl
{"metadata": {"url": "https://example.com/page", "title": "Example Page", "timestamp_utc": "2024-06-11T12:00:00.000Z", "html_lang": "en", "script_version": "21.4"}}
{"tagName": "h1", "id": "main-title", "domPath": "body > div > h1#main-title", "textContent": "Welcome!", "css": {"font-size": "32px", "color": "rgb(34, 34, 34)"}}
{"tagName": "button", "classes": ["btn", "btn-primary"], "textContent": "Click Me", "isInteractive": true, "eventListeners": ["click"], "geometry": {"x": 100, "y": 200, "width": 120, "height": 40}}
{"tagName": "img", "src": "/images/logo.png", "alt": "Company Logo", "geometry": {"width": 150, "height": 50}}
\`\`\`
`;
        return `Du bist ein spezialisierter KI-Analyst für Webseiten-Komponenten. Deine Aufgabe ist es, die vom Benutzer bereitgestellten HTML-Element-Daten zu interpretieren und basierend auf einer der folgenden Rollen zu antworten.

**Kontext des Exports:**
* **Aktuelle URL:** ${currentUrl}
* **Seitentitel:** ${currentTitle}
* **Export-Datum (UTC):** ${new Date().toISOString()}
* **Skript-Version:** ${SCRIPT_VERSION}

**WICHTIGE ANWEISUNG FÜR DICH, DIE KI:**
1.  **Wähle EINE Rolle:** Lies die drei Persona-Abschnitte unten. Wähle diejenige Rolle, die am besten zur nachfolgenden Benutzerfrage passt.
2.  **Lösche die anderen Rollen:** Entferne die Abschnitte der beiden *nicht* gewählten Rollen vollständig aus deiner Antwort.
3.  **Lösche diesen Anweisungsblock:** Auch dieser Block sollte in deiner finalen Antwort nicht mehr enthalten sein.
4.  **Formuliere deine Antwort als Experte der gewählten Rolle:** Gib eine präzise, hilfsbereite und relevante Antwort auf die Benutzerfrage, basierend auf den bereitgestellten Daten und deiner gewählten Persona. Sei dabei so detailliert oder prägnant, wie es die Aufgabe erfordert.
5.  **Füge KEINE zusätzlichen Informationen hinzu, die nicht in den bereitgestellten Daten enthalten sind.** Wenn eine Information nicht in den JSONL-Daten zu finden ist, weise darauf hin, anstatt zu spekulieren.
6.  **Erkenne Unstimmigkeiten:** Solltest du eine Unstimmigkeit oder einen möglichen Fehler in den bereitgestellten Daten erkennen, weise bitte höflich darauf hin.

---

### Persona 1: Der Userscript-Entwickler

**Deine Rolle:** Du bist ein erfahrener JavaScript-Entwickler, spezialisiert auf die Erstellung von robusten Userscripts (z.B. für Tampermonkey oder Greasemonkey).
**Dein Ziel:** Erstelle ein funktionierendes Userscript auf Basis der bereitgestellten JSONL-Daten, das die vom Benutzer gewünschte Aufgabe automatisiert (z.B. Elemente klicken, Text extrahieren, Formulare ausfüllen).
**Dein Fokus:**
* **Stabile Selektoren:** Bevorzuge IDs, \`data-*\`-Attribute oder robuste Klassenkombinationen aus dem \`domPath\`. Nutze \`document.querySelector\` oder \`document.querySelectorAll\`. Vermeide unspezifische nth-child/nth-of-type Selektoren, wo immer möglich.
* **Struktur & Kontext:** Verstehe die Beziehungen zwischen Elementen durch \`domPath\` und \`structureTree\`.
* **Interaktion:** Beachte \`isInteractive\`, \`tagName\` (\`button\`, \`a\`, \`input\`) und \`eventListeners\` für Aktionen. Berücksichtige die \`geometry\` für sichtbare Interaktionen.
* **Code-Qualität:** Dein Code sollte sauber, kommentiert und robust gegenüber kleinen Änderungen an der Webseite sein. Nutze Warten-Funktionen (\`waitForElement\`), falls Elemente dynamisch geladen werden.
* **Fehlerbehandlung:** Implementiere grundlegende Fehlerbehandlung und Logging.

**Beispiel für eine Benutzerfrage, die du beantworten würdest:**
"Ich möchte ein Userscript erstellen, das automatisch auf den Button mit dem Text 'Weiter' klickt, sobald er erscheint. Kannst du mir den JavaScript-Code dafür geben, der die Element-ID oder eine robuste Klasse nutzt?"

---

### Persona 2: Der CSS-Stylist / Frontend-Entwickler

**Deine Rolle:** Du bist ein versierter Frontend-Entwickler mit einem scharfen Auge für CSS und Design.
**Dein Ziel:** Erstelle CSS-Code, um das Aussehen der ausgewählten Elemente nach den Wünschen des Benutzers zu verändern oder Layout-Probleme zu beheben.
**Dein Fokus:**
* **Bestehende Stile:** Analysiere das \`css\`-Feld, um die aktuellen, vom Standard abweichenden Stile zu verstehen und zu überschreiben oder zu ergänzen.
* **Präzise Selektoren:** Nutze die \`id\` und \`classes\` der Elemente, um genaue CSS-Selektoren zu erstellen. Berücksichtige auch \`domPath\` für kontextbezogene Selektoren.
* **Struktur:** Berücksichtige die \`structureTree\` und den \`domPath\` für den Einfluss von Änderungen auf Kind- oder Elternelemente (z.B. mit Kombinatoren wie \`>\` oder \`+\`).
* **Spezifität & \`!important\`:** Erstelle Regeln mit ausreichender Spezifität. Erkläre, wann und warum \`!important\` eventuell notwendig (aber zu vermeiden) ist.
* **Responsive Design:** Denke an verschiedene Bildschirmgrößen und den Einfluss deiner Änderungen auf das Layout.

**Beispiel für eine Benutzerfrage, die du beantworten würdest:**
"Wie kann ich die Schriftgröße dieses Titels auf 24px erhöhen und ihn zentrieren, ohne andere Elemente zu beeinflussen? Bitte gib mir den nötigen CSS-Code."

---

### Persona 3: Der Daten-Analyst & -Extraktor

**Deine Rolle:** Du bist ein präziser Daten-Analyst. Deine Aufgabe ist es, aus den Webseiten-Fragmenten die reinen Informationen zu extrahieren und strukturiert darzustellen.
**Dein Ziel:** Fasse den semantischen Inhalt der ausgewählten Elemente zusammen, erkenne Muster und präsentiere die extrahierten Daten in einem sauberen Format (z.B. Markdown-Tabelle, JSON).
**Dein Fokus:**
* **Inhalt:** Konzentriere dich auf das \`textContent\`-Feld, um die Kerninformationen zu gewinnen. Beachte auch \`src\` und \`alt\` für Bilder, \`href\` für Links und \`inputValue\` für Formularfelder.
* **Bedeutung:** Nutze \`domPath\`, \`tagName\`, \`ariaRole\`, \`ariaLabel\`, \`schemaOrgData\` und die Metadaten der Seite, um den Kontext und die Art der Daten zu bestimmen.
* **Gruppierung:** Fasse Elemente, die offensichtlich zusammengehören (z.B. ein Bild, eine Überschrift und ein Preis), zu einer einzigen logischen Einheit zusammen.
* **Mustererkennung:** Identifiziere wiederkehrende Muster in Listen oder Tabellen und schlage effektive Extraktionsstrategien vor.
* **Präsentation:** Gib die extrahierten Daten ohne schmückendes Beiwerk, aber sauber formatiert (z.B. als Tabelle oder strukturiertes JSON), aus.

**Beispiel für eine Benutzerfrage, die du beantworten würdest:**
"Ich habe mehrere Produktinformationen ausgewählt. Kannst du mir eine Tabelle mit den Produktnamen, Preisen und zugehörigen Links erstellen?"

---

**DATENBESTAND:**
* Es sind **${elementCount} Objekt(e)** zur Analyse vorhanden.
* Die Daten folgen dem **JSONL-Format** (ein JSON-Objekt pro Zeile).
* **Erste Zeile:** Globale Metadaten zur gesamten Seite.
* **Weitere Zeilen:** Semantisch angereicherte Daten der einzelnen, ausgewählten HTML-Elemente.

${exampleJsonl}

---
`;
    }

    // --- Datenextraktion ---
    function extractElementAsJson(element) {
        const data = { tagName: element.tagName.toLowerCase() };
        data.domPath = getDomPath(element);

        if (element.id) { data.id = element.id; }
        // Robusterer Check für className
        if (element.className && typeof element.className === 'string') {
            const classes = element.className.split(' ').filter(c => !c.startsWith('picker-'));
            if (classes.length > 0) { data.classes = classes; }
        }

        const role = element.getAttribute('role');
        if (role) data.ariaRole = role;
        const label = element.getAttribute('aria-label');
        if (label) data.ariaLabel = label;
        const hidden = element.getAttribute('aria-hidden');
        if (hidden) data.ariaHidden = hidden === 'true'; // Convert to boolean

        // Erweitert um data-Attribute
        const dataAttributes = {};
        for (const attr of element.attributes) {
            if (attr.name.startsWith('data-')) {
                dataAttributes[attr.name] = attr.value;
            }
        }
        if (Object.keys(dataAttributes).length > 0) {
            data.dataAttributes = dataAttributes;
        }

        const interactiveTags = ['A', 'BUTTON', 'INPUT', 'SELECT', 'TEXTAREA', 'DETAILS'];
        data.isInteractive = interactiveTags.includes(element.tagName) || element.hasAttribute('onclick') || window.getComputedStyle(element).cursor === 'pointer';

        // Erfassung von Event Listenern (Best-Effort, da JS keine direkte API bietet)
        const eventListeners = getEventListeners(element);
        if (eventListeners.length > 0) {
            data.eventListeners = eventListeners;
        }

        const rect = element.getBoundingClientRect();
        data.geometry = {
            x: Math.round(rect.x), y: Math.round(rect.y),
            width: Math.round(rect.width), height: Math.round(rect.height),
            inViewport: isElementInViewport(rect)
        };

        const textContent = element.innerText?.trim();
        if (textContent) { data.textContent = textContent; }

        // Spezielle Attribute für bestimmte Tags
        if (element.tagName === 'A' && element.href) { data.href = element.href; }
        if (element.tagName === 'IMG' && element.src) { data.src = element.src; }
        if (element.tagName === 'IMG' && element.alt) { data.alt = element.alt; }
        if (element.tagName === 'INPUT' && element.type) { data.inputType = element.type; }
        if (element.tagName === 'INPUT' && element.value !== undefined) { data.inputValue = element.value; } // Achtung bei Passwörtern!
        if (element.tagName === 'FORM' && element.action) { data.formAction = element.action; }
        if (element.tagName === 'FORM' && element.method) { data.formMethod = element.method; }
        if (element.tagName === 'LINK' && element.rel) { data.linkRel = element.rel; }
        if (element.tagName === 'LINK' && element.href) { data.linkHref = element.href; }
        if (element.tagName === 'META' && element.name) { data.metaName = element.name; }
        if (element.tagName === 'META' && element.property) { data.metaProperty = element.property; }
        if (element.tagName === 'META' && element.content) { data.metaContent = element.content; }
        if (element.tagName === 'IFRAME' && element.src) { data.iframeSrc = element.src; }
        if (element.tagName === 'SVG') { data.svgViewBox = element.getAttribute('viewBox'); }
        if (element.tagName === 'CANVAS') { data.canvasWidth = element.width; data.canvasHeight = element.height; }


        // Strukturbaum als JSON-Objekt
        data.structureTree = buildChildTree(element, 0, 2); // Max-Tiefe von 2 Ebenen

        // Kompaktes HTML der direkten Kinder (nicht des gesamten Baums, um Größe zu begrenzen)
        const childHtml = Array.from(element.children).map(child => child.outerHTML).join('');
        data.rawChildrenHTML = compactHTML(childHtml);

        data.css = getCompressedCss(element); // CSS als Objekt

        return data;
    }

    // --- Semantische Helferfunktionen ---
    function getGlobalPageMetadata() {
        const globalData = {};
        const description = document.querySelector('meta[name="description"]');
        if (description) globalData.metaDescription = description.content.trim();
        const canonical = document.querySelector('link[rel="canonical"]');
        if (canonical) globalData.canonicalUrl = canonical.href;

        const schemaScripts = document.querySelectorAll('script[type="application/ld+json"]');
        if (schemaScripts.length > 0) {
            const schemaTypes = new Set();
            schemaScripts.forEach(script => {
                try {
                    const jsonData = JSON.parse(script.textContent);
                    // Handle single object and array of objects
                    const types = (Array.isArray(jsonData) ? jsonData : [jsonData])
                        .map(item => item['@type'])
                        .flat()
                        .filter(Boolean); // Filtert undefined/null
                    types.forEach(type => schemaTypes.add(type));
                } catch (e) { GM_log(`[${SCRIPT_VERSION}] Fehler beim Parsen von Schema.org JSON: ${e.message}`); }
            });
            if (schemaTypes.size > 0) {
                globalData.schemaOrgData = Array.from(schemaTypes);
            }
        }
        return globalData;
    }

    function getDomPath(element) {
        if (!element || element === document.body) return element ? element.tagName.toLowerCase() : '';
        const path = [];
        while (element && element.tagName !== 'HTML') { // Bis zum HTML-Tag gehen
            let selector = element.tagName.toLowerCase();
            if (element.id) { selector += `#${element.id}`; }
            // Robusterer Check für className
            if (element.className && typeof element.className === 'string') {
                const classes = element.className.split(' ').filter(c => c && !c.startsWith('picker-'));
                if (classes.length > 0) { selector += `.${classes.join('.')}`; }
            }
            // Index für gleiche Geschwisterelemente hinzufügen, falls keine ID/Klasse
            if (!element.id && (!element.className || (typeof element.className === 'string' && element.className.split(' ').filter(c => c && !c.startsWith('picker-')).length === 0))) {
                let siblingCount = 0;
                let originalIndex = 0;
                if (element.parentElement) {
                    for (let i = 0; i < element.parentElement.children.length; i++) {
                        if (element.parentElement.children[i].tagName === element.tagName) {
                            if (element.parentElement.children[i] === element) {
                                originalIndex = siblingCount;
                            }
                            siblingCount++;
                        }
                    }
                }
                if (siblingCount > 1) { // Nur hinzufügen, wenn es mehrere Geschwister des gleichen Typs gibt
                    selector += `:nth-of-type(${originalIndex + 1})`;
                }
            }
            path.unshift(selector);
            element = element.parentElement;
        }
        return path.join(' > ');
    }

    function isElementInViewport(rect) {
        return ( rect.top >= 0 && rect.left >= 0 && rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && rect.right <= (window.innerWidth || document.documentElement.clientWidth) );
    }

    // Verbesserte CSS-Extraktion: Rückgabe als Objekt, mehr relevante Properties
    function getCompressedCss(element) {
        const hadHighlight = element.classList.contains('picker-highlight');
        if (hadHighlight) element.classList.remove('picker-highlight');

        const tagName = element.tagName;
        if (!appState.defaultStylesCache.has(tagName)) {
            const dummy = document.createElement(tagName);
            document.body.appendChild(dummy);
            appState.defaultStylesCache.set(tagName, window.getComputedStyle(dummy));
            document.body.removeChild(dummy);
        }

        const styles = window.getComputedStyle(element);
        const defaultStyles = appState.defaultStylesCache.get(tagName);
        const cssProps = {};
        const relevantProperties = [
            'display', 'position', 'flex-direction', 'justify-content', 'align-items', 'flex-wrap', 'gap',
            'width', 'height', 'min-width', 'max-width', 'min-height', 'max-height',
            'margin', 'margin-top', 'margin-right', 'margin-bottom', 'margin-left',
            'padding', 'padding-top', 'padding-right', 'padding-bottom', 'padding-left',
            'border', 'border-top', 'border-right', 'border-bottom', 'border-left',
            'border-radius', 'box-sizing', // Wichtig für Layout
            'font-family', 'font-size', 'font-weight', 'font-style', 'color', 'background-color',
            'text-align', 'line-height', 'text-decoration', 'text-transform', 'letter-spacing', 'word-spacing',
            'box-shadow', 'opacity', 'visibility', 'overflow', 'overflow-x', 'overflow-y',
            'z-index', 'cursor', 'pointer-events', // Für Interaktivität
            'white-space', 'word-break', 'float', 'clear',
            'top', 'bottom', 'left', 'right', // Für absolute/fixed positionierte Elemente
            'background-image', 'background-size', 'background-position', 'background-repeat', 'background-attachment',
            'transform', 'transform-origin', 'transition', 'animation',
            'outline', 'outline-offset', // Für Fokus/Interaktion
            'box-shadow', 'filter', // Visuelle Effekte
            'clip-path', 'mask', // Komplexere Formen
            'content', // Für pseudo-Elemente
            'vertical-align', // Für Inline-Elemente
            'writing-mode', 'direction', // Textfluss
            'grid-template-columns', 'grid-template-rows', 'grid-gap', 'grid-column', 'grid-row' // CSS Grid
        ];

        for (const prop of relevantProperties) {
            const value = styles.getPropertyValue(prop);
            if (value !== defaultStyles.getPropertyValue(prop) && value !== '' && value !== 'initial' && value !== 'unset') {
                cssProps[prop] = value;
            }
        }
        if (hadHighlight) element.classList.add('picker-highlight');
        return Object.keys(cssProps).length > 0 ? cssProps : null;
    }

    // Strukturbaum als JSON-Objekt
    function buildChildTree(element, level = 0, maxLevel = 2) {
        if (level >= maxLevel) return null;

        const childrenData = [];
        for (const child of element.children) {
            // Ignoriere Skripte und Style-Tags im Strukturbaum
            if (child.tagName === 'SCRIPT' || child.tagName === 'STYLE' || child.tagName === 'NOSCRIPT') continue;

            const childObj = {
                tagName: child.tagName.toLowerCase(),
                id: child.id || undefined,
                classes: (child.className && typeof child.className === 'string') ? child.className.replace(/picker-\w+/g, '').trim().split(' ').filter(c => c) : undefined
            };

            // Füge einen Text-Snippet hinzu, falls vorhanden und nicht zu lang
            const childTextContent = child.textContent?.trim();
            if (childTextContent) {
                childObj.textContentSnippet = childTextContent.substring(0, 100) + (childTextContent.length > 100 ? '...' : '');
            }
            if (child.tagName === 'IMG' && child.src) { childObj.src = child.src; } // Bilder im Baum
            if (child.tagName === 'A' && child.href) { childObj.href = child.href; } // Links im Baum


            // Rekursiver Aufruf für tiefere Kinder
            if (child.children.length > 0) {
                const nestedChildren = buildChildTree(child, level + 1, maxLevel);
                if (nestedChildren && nestedChildren.length > 0) {
                    childObj.children = nestedChildren;
                }
            }
            childrenData.push(childObj);
        }
        return childrenData.length > 0 ? childrenData : null;
    }

    // HTML-Kompression: Vorsichtiger mit Whitespaces
    function compactHTML(html) {
        // Ersetzt mehrere Whitespaces, Zeilenumbrüche und Tabs durch ein einzelnes Leerzeichen.
        // Entfernt Whitespace direkt zwischen schließenden und öffnenden Tags (z.B. `> <`).
        // Entfernt führende/endende Whitespaces.
        return html.replace(/(\s*\n\s*|\s{2,}|\t)/g, ' ')
                   .replace(/>\s+</g, '><')
                   .trim();
    }

    // --- EVENT LISTENER ERFASSUNG (Best-Effort & Heuristiken) ---
    function getEventListeners(element) {
        const listeners = new Set(); // Nutze Set, um Duplikate zu vermeiden

        // 1. Inline-Event-Handler-Attribute
        const inlineEventAttributes = [
            'onclick', 'ondblclick', 'onmousedown', 'onmouseup', 'onmousemove', 'onmouseover', 'onmouseout',
            'onmouseenter', 'onmouseleave', 'oncontextmenu', 'onfocus', 'onblur', 'onchange',
            'onsubmit', 'onreset', 'onselect', 'oninput', 'onkeydown', 'onkeyup', 'onkeypress',
            'onload', 'onerror', 'onresize', 'onscroll', 'oncut', 'oncopy', 'onpaste'
        ];

        for (const attrName of inlineEventAttributes) {
            if (element.hasAttribute(attrName)) {
                listeners.add(attrName.substring(2)); // z.B. 'click' statt 'onclick'
            }
        }

        // 2. Heuristik für gängige interaktive Elemente
        if (element.tagName === 'A' && element.href) {
            listeners.add('click');
        }
        if (element.tagName === 'BUTTON') {
            listeners.add('click');
        }
        if (element.tagName === 'INPUT' || element.tagName === 'SELECT' || element.tagName === 'TEXTAREA') {
            listeners.add('change');
            listeners.add('input');
            listeners.add('focus');
            listeners.add('blur');
        }
        // Für Formulare
        if (element.tagName === 'FORM') {
            listeners.add('submit');
        }
        // Für Medienelemente
        if (element.tagName === 'AUDIO' || element.tagName === 'VIDEO') {
            listeners.add('play');
            listeners.add('pause');
            listeners.add('ended');
        }
        // Drag and Drop
        if (element.hasAttribute('draggable') && element.getAttribute('draggable') === 'true') {
            listeners.add('dragstart');
            listeners.add('dragend');
        }


        // 3. jQuery Events abfangen (falls jQuery auf der Seite geladen ist)
        // Dies bleibt eine "Best-Effort"-Heuristik und funktioniert nicht immer zuverlässig in Userscript-Sandboxes.
        try {
            if (typeof jQuery === 'function' && jQuery._data && jQuery._data(element, 'events')) {
                for (const eventType in jQuery._data(element, 'events')) {
                    listeners.add(eventType);
                }
            }
        } catch (e) {
            // Dies ist ein erwarteter Fehler, wenn jQuery nicht oder anders geladen ist. Kein GM_log hier, um Spam zu vermeiden.
        }

        return Array.from(listeners); // Konvertiere Set zurück in Array
    }

    function getFaviconUrl() {
        let favicon = document.querySelector("link[rel~='icon']");
        if (favicon) {
            return favicon.href;
        }
        // Fallback für gängige Favicon-Pfade
        return `${window.location.origin}/favicon.ico`;
    }

    function getUrlParameters() {
        const params = {};
        const url = new URL(window.location.href);
        for (const [key, value] of url.searchParams.entries()) {
            params[key] = value;
        }
        return Object.keys(params).length > 0 ? params : null;
    }

    // --- UI-HELPER ---
    function onMouseOver(e) {
        if (!appState.pickerMode) return;
        const targetElement = e.target;
        if (!targetElement || targetElement.id === 'element-picker-btn' || targetElement.closest('#element-picker-btn')) {
            if (appState.lastHoveredElement) {
                appState.lastHoveredElement.classList.remove('picker-highlight');
                appState.lastHoveredElement = null;
            }
            return;
        }
        if (appState.lastHoveredElement !== targetElement) {
            if (appState.lastHoveredElement) {
                appState.lastHoveredElement.classList.remove('picker-highlight');
            }
            appState.lastHoveredElement = targetElement;
            appState.lastHoveredElement.classList.add('picker-highlight');
        }
    }

    function onMouseOut(e) {
        // Aktuell keine spezifische Logik erforderlich
    }

    function activatePickerMode() {
        appState.pickerMode = true;
        appState.pickerButton.classList.add('active');
        document.body.classList.add('picker-cursor');
        showStatus('Picker aktiv. STRG+Klick für Mehrfachauswahl. ESC zum Beenden.');
        document.addEventListener('click', onElementClick, true);
        document.addEventListener('mouseover', onMouseOver, true);
        document.addEventListener('mouseout', onMouseOut, true);
        document.addEventListener('keydown', onKeyDown, true);
        // Zusätzlicher Tipp für neue Nutzer
        if (!localStorage.getItem('pickerModeFirstTime')) {
            showNotification('Klicke auf Elemente, um sie auszuwählen!', false);
            localStorage.setItem('pickerModeFirstTime', 'true');
        }
    }

    function makeButtonDraggable(button) {
        button.addEventListener('mousedown', (e) => {
            if (e.button !== 0 || appState.pickerMode) return; // Nur linker Mausklick und nicht im Picker-Modus
            appState.isDragging = true;
            appState.wasDragging = false;
            document.body.classList.add('picker-dragging');
            button.style.cursor = 'grabbing';
            button.style.transition = 'none'; // Übergänge beim Dragging deaktivieren

            const rect = button.getBoundingClientRect();
            // Setze Position absolut, damit top/left funktionieren
            button.style.left = `${rect.left}px`;
            button.style.top = `${rect.top}px`;
            button.style.right = 'auto'; // Original "right" Attribut aufheben
            button.style.transform = 'none'; // Original "transform" Attribut aufheben

            appState.offsetX = e.clientX - rect.left;
            appState.offsetY = e.clientY - rect.top;

            document.addEventListener('mousemove', onDragMove);
            document.addEventListener('mouseup', onDragEnd);
        });

        function onDragMove(e) {
            if (!appState.isDragging) return;
            e.preventDefault(); // Verhindert z.B. Textauswahl beim Ziehen
            appState.wasDragging = true;

            let newX = e.clientX - appState.offsetX;
            let newY = e.clientY - appState.offsetY;

            // Beschränkt den Button auf den sichtbaren Bereich
            newX = Math.max(0, Math.min(newX, window.innerWidth - button.offsetWidth));
            newY = Math.max(0, Math.min(newY, window.innerHeight - button.offsetHeight));

            button.style.left = `${newX}px`;
            button.style.top = `${newY}px`;
        }

        function onDragEnd() {
            if (!appState.isDragging) return;
            appState.isDragging = false;
            document.body.classList.remove('picker-dragging');
            button.style.cursor = 'grab';
            button.style.transition = 'background-color 0.3s ease'; // Transition wieder aktivieren

            document.removeEventListener('mousemove', onDragMove);
            document.removeEventListener('mouseup', onDragEnd);

            if (appState.wasDragging) {
                // Speichere die Position nur, wenn wirklich gezogen wurde
                const position = { x: button.style.left, y: button.style.top };
                localStorage.setItem('pickerButtonPosition', JSON.stringify(position));
            }
        }
    }

    function togglePickerMode() {
        if (appState.wasDragging) {
            // Wenn der Klick nur das Ende eines Drag-Vorgangs war, nicht den Picker-Modus umschalten
            appState.wasDragging = false;
            return;
        }
        appState.pickerMode = !appState.pickerMode;
        if (appState.pickerMode) {
            activatePickerMode();
        } else {
            deactivatePickerMode();
        }
    }

    function deactivatePickerMode() {
        appState.pickerMode = false;
        if(appState.pickerButton) appState.pickerButton.classList.remove('active');
        document.body.classList.remove('picker-cursor');

        // Styles der markierten und ausgewählten Elemente zurücksetzen
        if (appState.lastHoveredElement) {
            appState.lastHoveredElement.classList.remove('picker-highlight');
        }
        appState.selectedElements.forEach(el => el.classList.remove('picker-selected'));
        appState.selectedElements = []; // Auswahl leeren
        appState.lastHoveredElement = null;

        removeStatus(); // Statusmeldung entfernen

        // Event Listener entfernen, um Speicherlecks und Konflikte zu vermeiden
        document.removeEventListener('click', onElementClick, true);
        document.removeEventListener('mouseover', onMouseOver, true);
        document.removeEventListener('mouseout', onMouseOut, true);
        document.removeEventListener('keydown', onKeyDown, true);
    }

    function onKeyDown(e) {
        if (!appState.pickerMode) return;
        if (e.key === 'Escape') {
            e.preventDefault();
            deactivatePickerMode();
            showNotification('Picker-Modus beendet.'); // Meldung beim Beenden
        }
    }

    function createPipetteIcon() {
        const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
        svg.setAttribute("width", "24");
        svg.setAttribute("height", "24");
        svg.setAttribute("viewBox", "0 0 24 24");
        svg.setAttribute("fill", "white");
        const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
        path.setAttribute("d", "M20.71 5.63l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-3.12 3.12-1.93-1.91-1.41 1.41 1.42 1.42L3 16.25V21h4.75l8.92-8.92 1.42 1.42 1.41-1.41-1.91-1.93 3.12-3.12c.4-.4.4-1.03 0-1.41zM6.92 19L5 17.08l8.06-8.06 1.92 1.92L6.92 19z");
        svg.appendChild(path);
        return svg;
    }

    function createPickerButton() {
        const button = document.createElement('button');
        button.id = 'element-picker-btn';
        button.title = 'Komponenten-Analysator starten';
        button.appendChild(createPipetteIcon());
        button.addEventListener('click', togglePickerMode);
        document.body.appendChild(button);
        return button;
    }

    function showNotification(message, isError = false) {
        // Bestehende Notification entfernen, um Überlappung zu vermeiden
        if (appState.notificationTimeout) {
            clearTimeout(appState.notificationTimeout);
            const existingNotification = document.querySelector('.picker-notification');
            if (existingNotification && existingNotification.parentNode) {
                existingNotification.parentNode.removeChild(existingNotification);
            }
        }

        const n = document.createElement('div');
        n.className = 'picker-notification';
        if (isError) n.classList.add('error');
        n.textContent = message;
        document.body.appendChild(n);

        // Animation und Timeout
        setTimeout(() => {
            n.style.opacity = '1';
        }, 10); // Kleiner Delay für CSS-Transition

        appState.notificationTimeout = setTimeout(() => {
            n.style.opacity = '0';
            setTimeout(() => {
                if (n.parentNode) n.parentNode.removeChild(n);
                appState.notificationTimeout = null;
            }, 300); // Entspricht CSS-Transition
        }, 4000); // Notification bleibt 4 Sekunden sichtbar
    }

    function showStatus(message, removeAfterDelay = true, isTemporary = false) {
        // Wenn es eine temporäre "Bitte warten..." Nachricht ist, die vorherige entfernen, aber nicht das Timeout von dauerhaften Nachrichten.
        if (isTemporary && appState.statusTimeout) {
             clearTimeout(appState.statusTimeout);
        }

        let statusElement = document.getElementById('picker-status');
        if (!statusElement) {
            statusElement = document.createElement('div');
            statusElement.id = 'picker-status';
            statusElement.className = 'picker-status';
            document.body.appendChild(statusElement);
        }

        // Wenn der Text der gleiche ist und es nicht temporär ist, und der Status schon sichtbar ist, nichts tun
        if (statusElement.textContent === message && !isTemporary && statusElement.style.opacity === '1') {
            return;
        }

        statusElement.textContent = message;
        statusElement.style.opacity = '1'; // Sichtbarkeit sicherstellen

        if (removeAfterDelay) {
            // Timer für automatische Entfernung der Statusmeldung
            if (appState.statusTimeout) {
                clearTimeout(appState.statusTimeout);
            }
            appState.statusTimeout = setTimeout(() => {
                removeStatus();
                appState.statusTimeout = null;
            }, 5000); // Statusmeldung bleibt 5 Sekunden sichtbar
        } else {
            // Wenn nicht removeAfterDelay, dann Timeout löschen, falls gesetzt
            if (appState.statusTimeout) {
                clearTimeout(appState.statusTimeout);
                appState.statusTimeout = null;
            }
        }
        appState.currentStatusMessage = message; // Aktuelle Statusmeldung speichern
    }

    function removeStatus() {
        const e = document.getElementById('picker-status');
        if (e) {
            e.style.opacity = '0';
            setTimeout(() => {
                if (e.parentNode) e.parentNode.removeChild(e);
            }, 300); // Entspricht CSS-Transition
        }
        appState.currentStatusMessage = ''; // Statusmeldung leeren
    }

    function init() {
        // Verhindert doppelte Ausführung
        if (document.getElementById('element-picker-btn')) return;

        appState.pickerButton = createPickerButton();
        makeButtonDraggable(appState.pickerButton);
        loadButtonPosition();

        GM_log(`Universal Component Analyzer (v${SCRIPT_VERSION}) wurde geladen.`);
        // Zeigt den initialen Tooltip für den Button
        if (!localStorage.getItem('pickerButtonInitialTooltipShown')) {
            showNotification('Klicke auf die Pipette, um den Element-Selektor zu starten.', false);
            localStorage.setItem('pickerButtonInitialTooltipShown', 'true');
        }
    }

    // Skript starten, sobald das DOM bereit ist
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }
})();