WME — RPP

WME - Add a new place of residence (G). Opens edit (pencil) on LMB+G and auto-fills Street/City + Building number/letter with optional increment.

// ==UserScript==
// @name            WME — RPP
// @version         1.8
// @description     WME - Add a new place of residence (G). Opens edit (pencil) on LMB+G and auto-fills Street/City + Building number/letter with optional increment.
// @match           /^https:\/\/(www|beta)\.waze\.com\/(?!user\/)(.{2,6}\/)?editor\/?.*$/
// @include         https://*.waze.com/*/editor*
// @include         https://*.waze.com/editor*
// @include         https://*.waze.com/map-editor*
// @include         https://*.waze.com/beta_editor*
// @copyright       2025, Michalito_78
// @author          Michalito_78
// @run-at          document-end
// @grant           none
// @license         MIT
// @icon            https://www.google.com/s2/favicons?sz=64&domain=waze.com
// @namespace       https://greasyfork.org/pl/users/1504072
// ==/UserScript==

/*
 Changelog:
  1.0  New script
  1.1  Moving the script to the tab
  1.2  Added building number, letter, increment
  1.3  Added radiobuttons (number/letter)
  1.4  Added checkbox ON/OFF number
  1.5  Added checkbox ON/OFF script
  1.6  Fix HTML
  1.7  Cleaner structure + accurate LOG
  1.8  Fix checkbox ON / OFF script
*/

(function () {
    'use strict';

    // ──────────────────────────────────────────────────────────────────────────────
    // KONFIGURACJA / CONFIG
    // ──────────────────────────────────────────────────────────────────────────────

    /** Identyfikatory niestandardowych elementów sterujących panelem*/
    const IDS = {
        street: 'wme-rpp-street', // __________________________________________ Input: Nazwa ulicy / Street
        building: 'wme-rpp-building', // ______________________________________ Input: Numer budynku / Building number
        letter: 'wme-rpp-letter', // __________________________________________ Input: Litera / Letter
        increment: 'wme-rpp-increment', // ____________________________________ Input: Przyrost / Increment
        copyToggle: 'wme-rpp-copy-toggle', // _________________________________ Checkbox: ON/OFF numeracja / numbering
        scriptToggle: 'wme-rpp-script-toggle', // _____________________________ Checkbox: ON/OFF script
        radioBuilding: 'wme-rpp-radio-building', // ___________________________ RadioButton numer budynku
        radioLetter: 'wme-rpp-radio-letter', //  ______________________________ RadioButton litera
        tabRoot: 'wme-rpp-tab', // ____________________________________________ Zakładka w dziale skrypty
        userName: 'wme-rpp-user-name', // _____________________________________ Nazwa użytkownika
        userPoints: 'wme-rpp-user-edits', // __________________________________ ilość punktów
    };

    /** Indentyfikatory localstore */
    const LS = {
        street: 'wmeRppStreet', // ____________________________________________ Localstore Nazwa ulicy / Street
        building: 'wmeRppBuildingNumber', // __________________________________ Localstore Numer budynku / Building number
        letter: 'wmeRppBuildingLetter', // ____________________________________ Localstore Litera / Letter
        increment: 'wmeRppIncrement', // ______________________________________ Localstore Przyrost / Increment
    };

    /** Stan skryptu  */
    let state = {
        isGPressed: false, // _________________________________________________ Czy wciśnięty przycisk G
        pencilOpen: false, // _________________________________________________ Czy weszliśmy do edycji (klikniety ołówek)
    };


    /** Polski Alfabet */
    const POLISH_ALPHABET = [
        'A','B','C','D','E','F','G','H','I','J','K','L','Ł',
        'M','N','O','P','R','S','T','U','W','X','Y','Z','AA','AB','AC'
    ];

    /** Elementy zastępcze autouzupełniania WME. */
    const WME_PLACEHOLDERS = {
        street: 'Wpisz ulicę', // _____________________________________________ Pole tekstowe ulica
        city:   'Wpisz miasto', // ____________________________________________ Pole tekstowe miasto
        building: 'Dodaj numer domu', // ______________________________________ Pole tekstowe numer domu
    };

    /** Skrót klawiszowy używany razem z lewym przyciskiem myszy (LPM) do uruchomienia skryptu */
    const HOTKEY = 'g';

    // ──────────────────────────────────────────────────────────────────────────────
    // LOGGER
    // ──────────────────────────────────────────────────────────────────────────────

    const LOG_PREFIX = '[WME - RPP]';

    const log = {
        info:  (...a) => console.info(`${LOG_PREFIX} [INFO]`, ...a),
        warn:  (...a) => console.warn(`${LOG_PREFIX} [WARN]`, ...a),
        error: (...a) => console.error(`${LOG_PREFIX} [ERROR]`, ...a),
        ok:    (...a) => console.log(`${LOG_PREFIX} ✅`, ...a),
    };

    // ──────────────────────────────────────────────────────────────────────────────
    // UTILS
    // ──────────────────────────────────────────────────────────────────────────────

    /** Pomocnik funkcji do opóźnienia wykonania. */
    function debounce(fn, wait = 300) {
        let t;
        return (...args) => {
            clearTimeout(t);
            t = setTimeout(() => fn(...args), wait);
        };
    }

    /** Odczyt pierwszej liczby całkowitej znalezionej w ciągu. */
    function readInt(value, def = 0) {
        const m = String(value ?? '').trim().match(/-?\d+/);
        return m ? parseInt(m[0], 10) : def;
    }

    /** Przeskok po kolejnych literach alfabetu. */
    function incrementLetter(letterRaw, inc) {
        const n = POLISH_ALPHABET.length;
        let cur = String(letterRaw || '').trim().toUpperCase();
        if (!cur) cur = 'A';
        let index = POLISH_ALPHABET.indexOf(cur);
        if (index === -1) index = POLISH_ALPHABET.indexOf('A');
        const newIndex = (index + (inc % n) + n) % n;
        return POLISH_ALPHABET[newIndex];
    }

    /** Wyszukanie pasujących elementów do rozwijanych list. */
    function deepQueryAll(selector, root = document) {
        const out = [];
        try { out.push(...root.querySelectorAll(selector)); } catch (e) { /* ignore */ }
        const all = root.querySelectorAll('*');
        for (const el of all) {
            if (el.shadowRoot) {
                try { out.push(...el.shadowRoot.querySelectorAll(selector)); } catch (e) { /* ignore */ }
                out.push(...deepQueryAll(selector, el.shadowRoot));
            }
        }
        return Array.from(new Set(out));
    }

    /** Wybierz pierwszy element z listy. */
    function pickVisible(arr) {
        return arr.find(el => el && el.offsetParent !== null) || arr[0] || null;
    }

    /** Znajdywanie podobieństw w liście rozwijanej. */
    function setReactValue(input, value) {
        if (!input) return false;
        const setter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value')?.set;
        if (!setter) {
            input.value = value;
        } else {
            setter.call(input, value);
        }
        input.dispatchEvent(new Event('input', { bubbles: true, composed: true }));
        return true;
    }

    /** Algorytm dopasowania. */
    function levenshtein(a, b) {
        const m = [];
        for (let i = 0; i <= b.length; i++) m[i] = [i];
        for (let j = 0; j <= a.length; j++) m[0][j] = j;
        for (let i = 1; i <= b.length; i++) {
            for (let j = 1; j <= a.length; j++) {
                m[i][j] = b.charAt(i-1) === a.charAt(j-1)
                    ? m[i-1][j-1]
                : Math.min(m[i-1][j-1]+1, m[i][j-1]+1, m[i-1][j]+1);
            }
        }
        return m[b.length][a.length];
    }

    // ──────────────────────────────────────────────────────────────────────────────
    // WME FIELD ACCESSORS
    // ──────────────────────────────────────────────────────────────────────────────

    /** Wyszukiwanie danych wejściowych z WME (pole miasto, ulica). */
    function findWmeInput(which) {
        let placeholder = WME_PLACEHOLDERS[which];
        if (!placeholder) return null;
        return (pickVisible(deepQueryAll(`input[placeholder="${placeholder}"]`))); // ______________________ @type {HTMLInputElement}
    }

    /** Próba autouzupełnienia pól miasto i ulica. Przy niezgodności stopujemy autouzupełnianie i zostawiamy otwartą rozwijaną listę. */
    function pickAutocompleteOption(typedLower, maxTries = 3) {
        return new Promise(resolve => {
            let tries = 0;
            const timer = setInterval(() => {
                const options = deepQueryAll('[role="option"]');
                if (options.length) {
                    let best = options.find(opt => (opt.textContent || '').trim().toLowerCase().startsWith(typedLower));
                    if (!best) {
                        let bestScore = Infinity;
                        for (const opt of options) {
                            const label = (opt.textContent || '').trim().toLowerCase();
                            const diff = levenshtein(label, typedLower);
                            if (diff < bestScore) {
                                bestScore = diff;
                                best = opt;
                            }
                        }
                    }
                    if (best) {
                        best.dispatchEvent(new MouseEvent('mousedown', { bubbles: true }));
                        best.click();
                        clearInterval(timer);
                        resolve(true);
                        return;
                    }
                }
                if (++tries >= maxTries) {
                    clearInterval(timer);
                    resolve(false);
                }
            }, 220);
        });
    }

    /** Wpisywanie autouzupełnienia. */
    async function writeToAutocompleteField(which, value) {
        const input = findWmeInput(which);
        if (!input) {
            log.warn(`Nie znaleziono pola ${which === 'street' ? 'Ulica/Street' : 'Miasto/City'}.`);
            return false;
        }
        setReactValue(input, value);

        // Otwórz menu rozwijane
        input.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown', bubbles: true }));
        input.dispatchEvent(new KeyboardEvent('keyup', { key: 'ArrowDown', bubbles: true }));

        const ok = await pickAutocompleteOption(String(value).toLowerCase());
        if (!ok) {
            // Czasami wWME wymaga kliknięcia
            setTimeout(() => {
                input.dispatchEvent(new MouseEvent('mousedown', { bubbles: true }));
                input.dispatchEvent(new MouseEvent('mouseup', { bubbles: true }));
                input.dispatchEvent(new MouseEvent('click', { bubbles: true }));
                input.focus();
            }, 80);
            log.warn(`Nie udało się zatwierdzić wartości w polu ${which}.`);
        } else {
            log.ok(`${which === 'street' ? 'Ulica/Street' : 'Miasto/City'} wybrana.`);
        }
        return ok;
    }

    /** Kopiowanie numeru budynku. */
    function writeToBuildingNumber(value) {
        const input = findWmeInput('building');
        if (!input) {
            log.warn('Nie znaleziono pola Numer budynku / Building number.');
            return false;
        }
        return setReactValue(input, String(value ?? ''));
    }

    // ──────────────────────────────────────────────────────────────────────────────
    // CORE LOGIC
    // ──────────────────────────────────────────────────────────────────────────────

    /** Pobierz cyfrę z przyrostu + odczyt z locarstore.  */
    function getIncrementSafe() {
        const raw = document.getElementById(IDS.increment)?.value ?? localStorage.getItem(LS.increment) ?? '0';
        return readInt(raw, 0);
    }

    /** Zastosuj przyrost do cyfry + zapis do locarstore. */
    function applyNumberIncrement(baseNum, inc) {
        if (Number.isNaN(baseNum)) {
            log.warn('Numer budynku nie jest liczbą – pomijam przyrost.');
            return;
        }
        const next = baseNum + inc;
        const el = /** @type {HTMLInputElement} */ (document.getElementById(IDS.building));
        if (el) el.value = String(next);
        localStorage.setItem(LS.building, String(next));
    }

    /** Zastosuj przyrost do litery+ zapis do locarstore. */
    function applyLetterIncrement(curLetter, inc) {
        const next = incrementLetter(curLetter, inc);
        const el = /** @type {HTMLInputElement} */ (document.getElementById(IDS.letter));
        if (el) el.value = next;
        localStorage.setItem(LS.letter, next);
    }

    /** Główna akcja wywoływana po otwarciu edycji (ołówek).
    - Wypełnia pola zgodnie z ustawieniami panelu.
    - Kopiuje wartość panelu „Ulica” do WME Ulica i Miasto (jak w oryginalnym działaniu)
    - Kopiuje numer budynku (cyfra / cyfra + litera) do WME „Dodaj numer domu”
    - Stosuje inkrementację do cyfry lub litery, jeśli jest włączona. */
    async function performFill() {
        const onOff = /** @type {HTMLInputElement} */ (document.getElementById(IDS.scriptToggle));
        const copyToggle = /** @type {HTMLInputElement} */ (document.getElementById(IDS.copyToggle));
        const streetSrc = /** @type {HTMLInputElement} */ (document.getElementById(IDS.street));
        const buildingSrc = /** @type {HTMLInputElement} */ (document.getElementById(IDS.building));
        const letterSrc = /** @type {HTMLInputElement} */ (document.getElementById(IDS.letter));
        const radioBuilding = /** @type {HTMLInputElement} */ (document.getElementById(IDS.radioBuilding));
        const radioLetter = /** @type {HTMLInputElement} */ (document.getElementById(IDS.radioLetter));

        if (!onOff?.checked) {
            log.info('Skrypt jest wyłączony (ON/OFF).');
            return;
        }

        const streetVal = String(streetSrc?.value || '').trim();
        if (!streetVal) {
            alert('Pole "Nazwa ulicy / Street" w panelu jest puste.');
            return;
        }

        const inc = getIncrementSafe();
        const rawBuilding = String(buildingSrc?.value ?? '');
        const baseNum = readInt(rawBuilding, NaN);
        const curLetter = String(letterSrc?.value || '').toUpperCase();

        if (copyToggle?.checked) {
            if (radioBuilding?.checked) {
                if (!Number.isNaN(baseNum)) {
                    writeToBuildingNumber(String(baseNum));
                } else {
                    log.warn('Numer budynku nie jest liczbą – nie wpisuję do adresu.');
                }
            } else if (radioLetter?.checked) {
                const numberPart = rawBuilding || '';
                const letterPart = curLetter || '';
                writeToBuildingNumber(`${numberPart}${letterPart}`);
            } else {
                // Default: number + letter if present
                const numberPart = rawBuilding || '';
                const letterPart = curLetter || '';
                writeToBuildingNumber(`${numberPart}${letterPart}`);
            }
        }

        if (copyToggle?.checked) {
            if (radioBuilding?.checked && !Number.isNaN(baseNum)) {
                applyNumberIncrement(baseNum, inc);
            }
            if (radioLetter?.checked) {
                applyLetterIncrement(curLetter, inc);
            }
        }

        await writeToAutocompleteField('street', streetVal);
        await writeToAutocompleteField('city', streetVal);

        log.ok('Wypełnianie zakończone.');
    }

    /** Zatwierdzenie całości. */
    function openEditAndFill() {
        setTimeout(() => {
            const button = pickVisible(deepQueryAll("i.w-icon.w-icon-pencil-fill.edit-button[aria-disabled='false']"));
            if (!button) {
                log.warn('Nie znaleziono przycisku edycji (ołówek).');
                return;
            }
            state.pencilOpen = true;
            button.dispatchEvent(new MouseEvent('mousedown', { bubbles: true }));
            button.click();

            setTimeout(() => {
                const saveBtn = pickVisible(deepQueryAll('.save-button'));
                if (saveBtn) {
                    saveBtn.addEventListener('click', (ev) => {
                        // We do not block default save; just a hook to observe and reset state
                        log.info('Kliknięto "Zatwierdź" / Save.');
                        state.pencilOpen = false;
                    }, { once: true });
                }
            }, 500);

            setTimeout(performFill, 500);
        }, 150);
    }

    // ──────────────────────────────────────────────────────────────────────────────
    // HOTKEY + LPM HANDLERS
    // ──────────────────────────────────────────────────────────────────────────────

    document.addEventListener('keydown', (e) => {
        if ((e.key === HOTKEY || e.key === HOTKEY.toUpperCase()) &&
            !e.shiftKey && !e.ctrlKey && !e.altKey && !e.metaKey) {
            state.isGPressed = true;
        }
    });

    document.addEventListener('keyup', (e) => {
        if ((e.key === HOTKEY || e.key === HOTKEY.toUpperCase())) {
            // state.isGPressed = false;
        }
    });

    document.addEventListener('click', (e) => {
        if (state.isGPressed && e.button === 0) {
            openEditAndFill();
            state.isGPressed = false;
            e.stopPropagation();
        }
    });

    log.info('Aktywne: LPM + G → otwórz edycję i wstaw Ulica+Miasto+Numer.');

    // ──────────────────────────────────────────────────────────────────────────────
    // CONTROL PANEL (TAB)
    // ──────────────────────────────────────────────────────────────────────────────

    /** Pobieranie nazwy użytkownika. */
    function readUserInfoSafe() {
        try {
            const box = document.querySelector('#toolbar > div > div > wz-user-box');
            const name = box?.shadowRoot
            ?.querySelector('div > wz-menu > div.user-details-wrapper > div:nth-child(2) > div > wz-h5')
            ?.textContent?.trim() || '';
            const edits = document.querySelector('#toolbar > div > div > wz-user-box > span > wz-user-box-score:nth-child(2)')?.value || '';
            return { name, edits: String(edits) };
        } catch (e) {
            return { name: '', edits: '' };
        }
    }

    /** Inicjacja panelu. */
    function initializeControlPanel() {
        const tabs = document.querySelector('#user-info .nav-tabs');
        const content = document.querySelector('#user-info .tab-content');
        if (!tabs || !content) return;

        // unikaj duplikatów
        if (document.getElementById(IDS.tabRoot)) return;

        // Nazwa nowej zakładki
        const li = document.createElement('li');
        const a = document.createElement('a');
        a.href = `#${IDS.tabRoot}`;
        a.setAttribute('data-toggle', 'tab');
        a.textContent = '\u2200 RPP';
        li.appendChild(a);
        tabs.appendChild(li);

        // HTML panela
        const section = document.createElement('section');
        section.id = IDS.tabRoot;
        section.className = 'tab-pane';
        section.style.padding = '6px 8px';

        const { name: USER, edits: NUMBER_EDITS } = readUserInfoSafe();

        section.innerHTML = `
      <label id="user-label" style="display:block;margin-bottom:5px;font-weight:bold;font-size:12px;">
        Ilość edycji użytkownika: <span id="${IDS.userName}"></span>
      </label>
      <div id="${IDS.userPoints}"
           style="width:95%;padding:6px;font-size:10px;border:1px solid #ccc;border-radius:6px;margin-bottom:1px;background-color:#f9f9f9;">
      </div>

      <hr style="width:100%;text-align:left;margin: 10px 0;">

      <div style="display:flex;align-items:center;gap:8px; margin: 10px 70px 20px;font-size:10px;">
        <input id="${IDS.scriptToggle}" type="checkbox" checked />
        <label for="${IDS.scriptToggle}" style="margin:0;">ON / OFF skrypt / script</label>
      </div>

      <label for="${IDS.street}" style="display:block;margin-bottom:5px;font-weight:bold;font-size:12px;">
        Nazwa ulicy / Wsi <br> Street / Name of the village
      </label>
      <input id="${IDS.street}" type="text" placeholder="Twoja notatka... / Your note..."
             style="width:95%;padding:6px;font-size:18px;border:1px solid #ccc;border-radius:6px;margin-bottom:12px;margin-bottom:1px" />

      <hr style="width:100%;text-align:left;margin: 10px 0;">

      <div style="display:flex;align-items:center;gap:8px;margin: 10px 40px 20px;font-size:10px;">
        <input id="${IDS.copyToggle}" type="checkbox" checked />
        <label for="${IDS.copyToggle}" style="margin:0;font-size:12px;">ON / OFF numeracja / numbering</label>
      </div>

      <div style="display:flex;align-items:flex-end;gap:10px;margin-bottom:12px;">
        <div style="margin-top:5px;">
          <input type="radio" name="wme-rpp-mode" id="${IDS.radioBuilding}" />
          <label style="font-size:14px;" for="${IDS.radioBuilding}">Nr. budynku / Building number</label>
        </div>
        <div>
          <input id="${IDS.building}" type="number" placeholder="np. 10"
                 style="width:55px;padding:6px;font-size:12px;border:1px solid #ccc;border-radius:6px;" />
        </div>
      </div>

      <div style="display:flex;align-items:flex-end;gap:10px;margin-bottom:12px;color:gray;">
        <div style="margin-top:5px;">
          <input type="radio" name="wme-rpp-mode" id="${IDS.radioLetter}" />
          <label style="font-size:14px;" for="${IDS.radioLetter}">Litera / Letter</label>
        </div>
        <div>
          <input id="${IDS.letter}" type="text" placeholder="np. A" maxlength="2"
                 style="margin-left:115px;width:55px;padding:6px;font-size:12px;border:1px solid #ccc;border-radius:6px;text-transform:uppercase;" />
        </div>
      </div>

      <hr style="width:100%;text-align:left;margin: 10px 0;">

      <label for="${IDS.increment}" style="display:block;font-weight:bold;margin: 10px 0;font-size:14px;">
        Przyrost / Increase
      </label>
      <input id="${IDS.increment}" type="number" value="0"
             style="width:55px;padding:6px;font-size:13px;border:1px solid #ccc;border-radius:6px;" />
    `;

        // Przypisanie użytkownika i jego edycji
        content.appendChild(section);
        section.querySelector(`#${IDS.userName}`).textContent = USER || '(?)';
        section.querySelector(`#${IDS.userPoints}`).textContent = NUMBER_EDITS || '';

        /** Ustawienia początkowe po starcie */
        const radioBuilding = /** @type {HTMLInputElement} */ (section.querySelector(`#${IDS.radioBuilding}`));
        const radioLetter = /** @type {HTMLInputElement} */ (section.querySelector(`#${IDS.radioLetter}`));
        const inputLetter = /** @type {HTMLInputElement} */ (section.querySelector(`#${IDS.letter}`));
        radioBuilding.checked = true;
        inputLetter.disabled = true;

        // Przywracanie zapisanych wartości z localstore
        const stStreet = localStorage.getItem(LS.street) ?? '';
        const stBuilding = localStorage.getItem(LS.building) ?? '';
        const stIncrement = localStorage.getItem(LS.increment) ?? '0';
        const stLetter = (localStorage.getItem(LS.letter) ?? '').toUpperCase();

        /** @type {HTMLInputElement} */
        (section.querySelector(`#${IDS.street}`)).value = stStreet;
        /** @type {HTMLInputElement} */
        (section.querySelector(`#${IDS.building}`)).value = stBuilding;
        /** @type {HTMLInputElement} */
        (section.querySelector(`#${IDS.increment}`)).value = stIncrement;
        /** @type {HTMLInputElement} */
        (section.querySelector(`#${IDS.letter}`)).value = stLetter;

        // ponowne zapisanie do localstore
        const saveStreet = debounce((v) => localStorage.setItem(LS.street, v));
        const saveBuilding = debounce((v) => localStorage.setItem(LS.building, v));
        const saveIncrement = debounce((v) => localStorage.setItem(LS.increment, v));
        const saveLetter = debounce((v) => localStorage.setItem(LS.letter, v.toUpperCase()));

        section.querySelector(`#${IDS.street}`).addEventListener('input', (e) => {
            saveStreet(/** @type {HTMLInputElement} */(e.target).value);
        });
        section.querySelector(`#${IDS.building}`).addEventListener('input', (e) => {
            saveBuilding(/** @type {HTMLInputElement} */(e.target).value);
        });
        section.querySelector(`#${IDS.increment}`).addEventListener('input', (e) => {
            saveIncrement(/** @type {HTMLInputElement} */(e.target).value);
        });
        section.querySelector(`#${IDS.letter}`).addEventListener('input', (e) => {
            const inp = /** @type {HTMLInputElement} */(e.target);
            let v = String(inp.value || '').toUpperCase();

            // Zezwolenie na polskie znaki i dwuliterowa litery
            v = v.replace(/[^A-ZŁ]/gi, '').toUpperCase().slice(0, 2);

            // Wpisanie z ręki innej litery niż w alfabecie jest przyjmowane
            inp.value = v;
            saveLetter(v);

            // Przełaczanie miedzy NR a litera kiedy pojawi się litera
            if (v) {
                radioLetter.checked = true;
                inputLetter.disabled = false;
            } else {
                radioBuilding.checked = true;
                inputLetter.disabled = true;
            }
        });

        // checkbox ON / OFF numeracja / numbering
        section.querySelector(`#${IDS.copyToggle}`).addEventListener('change', function () {
            const isChecked = /** @type {HTMLInputElement} */(this).checked;
            const buildingInput = /** @type {HTMLInputElement} */ (document.getElementById(IDS.building));
            const letterInput = /** @type {HTMLInputElement} */ (document.getElementById(IDS.letter));
            const buildingLbl = section.querySelector(`label[for="${IDS.radioBuilding}"]`);
            const letterLbl = section.querySelector(`label[for="${IDS.radioLetter}"]`);

            radioBuilding.disabled = !isChecked;
            radioLetter.disabled = !isChecked;
            buildingInput.disabled = !isChecked;
            radioBuilding.checked = true;
            radioLetter.checked = false;
            // wyłączenie wprowadzania liter gdy OFF
            letterInput.disabled = true;

            if(!isChecked) letterInput.value = "A";
            if (buildingLbl) buildingLbl.style.color = isChecked ? 'black' : 'gray';
            if (letterLbl) letterLbl.style.color = 'gray';
        });

        // checkbox ON / OFF całego skryptu
        section.querySelector(`#${IDS.scriptToggle}`).addEventListener('change', function () {
            const isChecked = /** @type {HTMLInputElement} */(this).checked;
            const copyToggle = /** @type {HTMLInputElement} */ (section.querySelector(`#${IDS.copyToggle}`));

            if(isChecked) {
                setTimeout(() => {
                    copyToggle.checked = true;
                    copyToggle.dispatchEvent(new Event('change'));
                }, 200);


            }
            else{
                copyToggle.checked = false;
                copyToggle.dispatchEvent(new Event('change')); // Też uruchomi "OFF" stan
                radioLetter.checked = false;
                radioBuilding.checked = false;
            }
            // Disable all inputs except master toggle
            const inputs = section.querySelectorAll('input, textarea, select');
            inputs.forEach(input => {
                if (input.id !== IDS.scriptToggle) input.disabled = !isChecked;
            });

            // Labels (except user label)
            const labels = section.querySelectorAll('label');
            labels.forEach(label => {
                if (label.getAttribute('for') !== IDS.scriptToggle && label.id !== 'user-label') {
                    label.style.color = isChecked ? 'black' : 'gray';
                }
            });

            // Elementy div (oprócz pola edycji użytkownika)
            const divs = section.querySelectorAll('div');
            divs.forEach(div => {
                if (div.id !== IDS.userPoints) {
                    div.style.color = isChecked ? 'black' : 'gray';
                }
            });

            // Elementy span (oprócz nazwy użytkownika)
            const spans = section.querySelectorAll('span');
            spans.forEach(span => {
                if (span.id !== IDS.userName) {
                    span.style.color = isChecked ? 'black' : 'gray';
                }
            });

            // Kolor przy OFF i ON
            section.querySelectorAll('hr').forEach(hr => {
                /** @type {HTMLElement} */ (hr).style.borderColor = isChecked ? '#ccc' : '#eee';
            });
        });

        // Radiobutton litera
        section.querySelector(`#${IDS.radioLetter}`).addEventListener('change', function () {
            const isChecked = /** @type {HTMLInputElement} */(this).checked;
            const letterInput = /** @type {HTMLInputElement} */ (document.getElementById(IDS.letter));
            const letterLbl = section.querySelector(`label[for="${IDS.radioLetter}"]`);
            if (isChecked) {
                if (!letterInput.value) letterInput.value = 'A';
                if (letterLbl) letterLbl.style.color = 'black';
                letterInput.disabled = false;
            }
        });

        // Radiobutton Nmeracja budynków
        section.querySelector(`#${IDS.radioBuilding}`).addEventListener('change', function () {
            const isChecked = /** @type {HTMLInputElement} */(this).checked;
            const letterInput = /** @type {HTMLInputElement} */ (document.getElementById(IDS.letter));
            const letterLbl = section.querySelector(`label[for="${IDS.radioLetter}"]`);
            if (isChecked) {
                if (letterLbl) letterLbl.style.color = 'gray';
                letterInput.disabled = true;
                letterInput.value = "A";
            }
        });

        log.ok('Panel RPP dodany.');
    }

    /** Oczekiwanie na wyciągnięcie danych użytkownika i inicjacja panelu. */
    function ensurePanelWhenReady() {
        function isDomReady() {
            const userInfo = document.getElementById('user-info');
            if (!userInfo) return false;
            return (
                userInfo.getElementsByClassName('nav-tabs').length > 0 &&
                userInfo.getElementsByClassName('tab-content').length > 0
            );
        }

        function tryInit() {
            const tabsList = document.querySelector('#user-tabs ul.nav-tabs');
            const tabsContent = document.querySelector('#user-tabs .tab-content');
            if (tabsList && tabsContent) {
                initializeControlPanel();
                return true;
            }
            return false;
        }

        const observer = new MutationObserver(() => {
            if (tryInit()) observer.disconnect();
        });
        observer.observe(document.body, { childList: true, subtree: true });

        const poll = () => {
            if (isDomReady()) {
                initializeControlPanel();
            } else {
                setTimeout(poll, 500);
            }
        };
        poll();
    }

    ensurePanelWhenReady();

    // ──────────────────────────────────────────────────────────────────────────────
    // ULEPSZENIE WIDOCZNOŚCI LISTBOXA
    // ──────────────────────────────────────────────────────────────────────────────
    (function improveListboxVisibility() {
        const style = document.createElement('style');
        style.textContent = `
      [role="listbox"] {
        display: block !important;
        visibility: visible !important;
        opacity: 1 !important;
      }
    `;
        document.head.appendChild(style);
    })();

})();