window.addEventListener('DOMContentLoaded', () => {
    const recordBtn = document.getElementById('recordBtn');
    const cancelZone = document.getElementById('cancelZone');
    const lockZone = document.getElementById('lockZone');
    const mainContent = document.getElementById('mainContent');

    let mediaRecorder;
    let chunks = [];
    let cancelRect;
    let lockRect;
    let recordingCanceled = false;
    let recordingLocked = false;   // nagrywa dalej po zwolnieniu palca
    let overLock = false;          // kulka jest nad strefą blokady
    let gestureActive = false;     // trwa przeciąganie zapoczątkowane na kulce
    let recordingReady = false;
    let initialLeft = null;
    let initialTop = null;
    let homeLeft = null;           // spoczynkowa pozycja kulki, zapisana raz na nagranie
    let homeTop = null;
    let lastX = null;
    let lastY = null;
    let selectedXPaths = [];       // elementy wskazane w tym nagraniu, w kolejności wskazania
    let selectionHashes = [];      // hash ekranu, na którym wskazano dany element
    let selectionTimes = [];       // moment wskazania, sekundy od startu nagrania
    let recordingStartedAt = null; // performance.now() ze startu nagrania — zero osi czasu
    let lastVisitHash = null;      // ostatni ekran dopisany do ścieżki tego nagrania

    const RECORD_BTN_XPATH = '//*[@id="recordBtn"]';

    // Sentinel wpisywany w miejsce xpatha, gdy pozycja opisuje samo bycie na ekranie,
    // a nie wskazanie elementu. Celowo nie jest poprawnym xpathem — serwer rozpoznaje
    // go po wartości (lib/user_selection.py: SCROLLED_XPATH).
    const SCROLLED_XPATH = '__scrolled__';

    // Moment na osi nagrania: sekundy od mediaRecorder.start(), z jednym miejscem
    // po przecinku. Ta sama chwila jest zerem nagranego audio, więc agent może
    // zestawić kliknięcie z tym, co użytkownik mówił w tej sekundzie.
    function selectionOffsetSeconds() {
        if (recordingStartedAt === null) return 0;
        const elapsed = (performance.now() - recordingStartedAt) / 1000;
        return Math.max(0, Math.round(elapsed * 10) / 10);
    }

    // Dopisuje „użytkownik był w tej sekundzie na tym ekranie” do tych samych trzech list
    // co zaznaczenia, z sentinelem zamiast xpatha. Jedno nagranie może objąć kilka ekranów
    // (kulka docuje, karuzelę można przewijać dalej), a polecenie może dotyczyć każdego
    // z nich — także takiego, na którym użytkownik nic nie kliknął, tylko o nim mówił.
    function recordScreenVisit(hash) {
        const screen = hash ?? window.pageCarousel?.currentHash ?? '';
        if (screen === lastVisitHash) return;   // ta sama strona, nie ma nowej informacji

        lastVisitHash = screen;
        selectedXPaths.push(SCROLLED_XPATH);
        selectionHashes.push(screen);
        selectionTimes.push(selectionOffsetSeconds());
    }

    function showZones() {
        // wyczyść podświetlenie z poprzedniego nagrania (inaczej strefa
        // startuje podświetlona, jeśli ostatnio nad nią puszczono przycisk)
        cancelZone.classList.remove('is-over');
        lockZone.classList.remove('is-over', 'is-locked');

        for (const zone of [cancelZone, lockZone]) {
            zone.style.visibility = 'visible';
            zone.style.opacity = '1';
        }
    }

    function hideZones() {
        cancelZone.classList.remove('is-over');
        lockZone.classList.remove('is-over', 'is-locked');

        for (const zone of [cancelZone, lockZone]) {
            zone.style.visibility = 'hidden';
            zone.style.opacity = '0';
        }
    }

    function addRecordingStyle() {
        recordBtn.classList.add('recording');
        showZones();
    }

    function removeRecordingStyle() {
        recordBtn.classList.remove('recording', 'locked');
        hideZones();
    }

    function resetRecordButtonPosition() {
        recordBtn.style.left = '';
        recordBtn.style.top = '';
        recordBtn.style.position = '';
    }

    function showStatusMessage(text) {
        const p = document.createElement('p');
        p.textContent = text;
        mainContent.appendChild(p);
    }

    function highlightElement(el, startX, startY) {
        const elRect = el.getBoundingClientRect();

        // koło startowe (jak przycisk) w punkcie puszczenia
        const START_SIZE = 80;
        const sx = startX - START_SIZE / 2;
        const sy = startY - START_SIZE / 2;

        const distance = Math.hypot(
            (elRect.left + elRect.width / 2) - startX,
            (elRect.top + elRect.height / 2) - startY
        );
        const morphDuration = Math.min(700, Math.max(300, distance * 0.8));

        const ov = document.createElement('div');
        ov.className = 'record-wave';
        ov.style.left = `${sx}px`;
        ov.style.top = `${sy}px`;
        ov.style.width = `${START_SIZE}px`;
        ov.style.height = `${START_SIZE}px`;
        ov.style.borderRadius = '50%';
        document.body.appendChild(ov);

        // FAZA 1: koło -> prostokąt obejmujący element (półprzezroczyste wypełnienie)
        const morph = ov.animate([
            { left: `${sx}px`, top: `${sy}px`, width: `${START_SIZE}px`, height: `${START_SIZE}px`, borderRadius: '50%' },
            { left: `${elRect.left}px`, top: `${elRect.top}px`, width: `${elRect.width}px`, height: `${elRect.height}px`, borderRadius: '4px' }
        ], { duration: morphDuration, easing: 'ease-out', fill: 'forwards' });

        morph.onfinish = () => {
            // utrwal geometrię prostokąta
            ov.style.left = `${elRect.left}px`;
            ov.style.top = `${elRect.top}px`;
            ov.style.width = `${elRect.width}px`;
            ov.style.height = `${elRect.height}px`;
            ov.style.borderRadius = '4px';

            // FAZA 2: znika wypełnienie -> zostaje sama obwódka -> znika i obwódka
            const fade = ov.animate([
                { backgroundColor: 'rgba(255,0,0,0.25)', borderColor: 'rgba(255,0,0,1)', boxShadow: '0 0 12px rgba(255,0,0,0.7)' },
                { backgroundColor: 'rgba(255,0,0,0)',    borderColor: 'rgba(255,0,0,1)', boxShadow: '0 0 12px rgba(255,0,0,0.7)', offset: 0.35 },
                { backgroundColor: 'rgba(255,0,0,0)',    borderColor: 'rgba(255,0,0,1)', boxShadow: '0 0 12px rgba(255,0,0,0.7)', offset: 0.7 },
                { backgroundColor: 'rgba(255,0,0,0)',    borderColor: 'rgba(255,0,0,0)', boxShadow: '0 0 12px rgba(255,0,0,0)' }
            ], { duration: 900, easing: 'ease-out', fill: 'forwards' });

            fade.onfinish = () => ov.remove();
        };
    }

    function getXPathForElement(el) {
        const mainContent = document.getElementById('mainContent');
        if (!mainContent || !mainContent.contains(el) || el === mainContent) return null;

        if (el.id) return `//*[@id="${el.id}"]`;

        let topLevel = el;
        while (topLevel.parentNode !== mainContent) {
            topLevel = topLevel.parentNode;
            if (!topLevel) return null;
        }

        if (el === topLevel) return '.';

        const parts = [];
        while (el !== topLevel) {
            let index = 1;
            let sibling = el.previousSibling;
            while (sibling) {
                if (sibling.nodeType === Node.ELEMENT_NODE && sibling.nodeName === el.nodeName) {
                    index++;
                }
                sibling = sibling.previousSibling;
            }
            const tag = el.nodeName.toLowerCase();
            const part = (index > 1) ? `${tag}[${index}]` : tag;
            parts.unshift(part);
            el = el.parentNode;
        }
        return './' + parts.join('/');
    }

    async function startRecording() {
        try {
            const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
            addRecordingStyle();
            mediaRecorder = new MediaRecorder(stream);
            chunks = [];
            selectedXPaths = [];
            selectionHashes = [];
            selectionTimes = [];
            recordingStartedAt = null;
            lastVisitHash = null;
            recordingCanceled = false;
            recordingLocked = false;
            overLock = false;
            recordingReady = false;

            mediaRecorder.ondataavailable = e => chunks.push(e.data);

            mediaRecorder.onstop = async () => {
                removeRecordingStyle();
                recordingReady = false;

                if (recordingCanceled) {
                    showStatusMessage('❌ Nagranie anulowane.');
                    return;
                }

                const blob = new Blob(chunks, { type: 'audio/webm' });
                const formData = new FormData();
                formData.append('audio', blob, 'audio.webm');

				// Jedna pozycja na każde zdarzenie, w kolejności zajścia: xpath elementu,
				// hash ekranu i moment w sekundach od startu nagrania. Tą samą trójką jadą
				// odwiedziny ekranu (xpath = SCROLLED_XPATH), więc serwer odtwarza z tego
				// całą ścieżkę: gdzie użytkownik był i co po drodze wskazał.
				selectedXPaths.forEach((xpath, i) => {
				    formData.append('xpath', xpath);
				    formData.append('xpath_hash', selectionHashes[i] ?? '');
				    formData.append('xpath_time', selectionTimes[i].toFixed(1));
				});

				// hash strony oglądanej przy wysyłce — widok pokazywany, gdy nic nie zaznaczono
				const pageHash = window.pageCarousel?.currentHash;
				if (pageHash) {
				    formData.append('hash', pageHash);
				}

                // adres wysyłki jest złożony z PIN-u podanego na starcie (pin-gate.js)
                const uploadUrl = window.pinEndpoints?.uploadUrl;
                if (!uploadUrl) {
                    showStatusMessage('🔒 Brak PIN-u — nagranie nie zostało wysłane.');
                    return;
                }

                try {
                    await fetch(uploadUrl, {
                        method: 'POST',
                        body: formData
                    });

                    // showStatusMessage(`✅ Wysłano nagranie + ${selectedXPaths.length} zaznaczeń`);

                } catch (error) {
                    console.error('Błąd wysyłania:', error);
                    showStatusMessage('⚠️ Błąd wysyłania nagrania.');
                }
            };

            mediaRecorder.onstart = () => {
                recordingReady = true;
            };

            // zero osi czasu zaznaczeń ustawiamy tuż przed startem, żeby pokrywało się
            // z początkiem nagranego audio
            recordingStartedAt = performance.now();

            // ekran, na którym polecenie się zaczęło — pierwszy punkt ścieżki (0.0s).
            // Dzięki temu agent zawsze wie, co użytkownik miał przed sobą, mówiąc.
            recordScreenVisit(window.pageCarousel?.currentHash ?? '');

            mediaRecorder.start();

        } catch (error) {
            console.error('Błąd dostępu do mikrofonu:', error);
            showStatusMessage('❌ Nie udało się uzyskać dostępu do mikrofonu.');
        }
    }

    // kulka zostaje w strefie blokady, nagranie leci dalej bez trzymania palca
    function enterLockedMode() {
        recordingLocked = true;
        recordingCanceled = false;
        overLock = false;

        recordBtn.classList.add('locked');
        lockZone.classList.remove('is-over');
        lockZone.classList.add('is-locked');

        recordBtn.style.position = 'fixed';
        recordBtn.style.left = `${lockRect.left}px`;
        recordBtn.style.top = `${lockRect.top}px`;

        // gest się skończył, ale nagranie trwa — zerujemy tylko dane gestu
        gestureActive = false;
        initialLeft = null;
        initialTop = null;
        lastX = null;
        lastY = null;
    }

    function finishRecording() {
        if (mediaRecorder?.state === "recording") {
            mediaRecorder.stop();
            mediaRecorder.stream.getTracks().forEach(track => track.stop());
        } else {
            removeRecordingStyle();
            recordingReady = false;
        }

        recordingLocked = false;
        overLock = false;

        resetRecordButtonPosition();
        initialLeft = null;
        initialTop = null;
        homeLeft = null;
        homeTop = null;

        if (lastX !== null && lastY !== null) {
            captureSelectionAt(lastX, lastY);
        }

        lastX = null;
        lastY = null;
    }

    // Dopisuje element spod punktu do zaznaczeń i podświetla go. Wspólne dla
    // upuszczenia kulki i dla kliknięcia w trybie blokady — oba mają dać ten sam efekt.
    function captureSelectionAt(x, y) {
        const el = document.elementFromPoint(x, y);
        if (!el) return null;

        const xpath = getXPathForElement(el);
        if (!xpath || xpath === RECORD_BTN_XPATH) return null;

        // każde kliknięcie to osobna pozycja — powrót do tego samego elementu po
        // kilku sekundach jest informacją dla agenta, nie duplikatem do zwinięcia.
        // XPath i hash pochodzą z tego samego DOM-u: zaznaczyć da się tylko element
        // żywego (środkowego) slajdu, a currentHash to właśnie hash tej strony.
        selectedXPaths.push(xpath);
        selectionHashes.push(window.pageCarousel?.currentHash ?? '');
        selectionTimes.push(selectionOffsetSeconds());

        if (el !== recordBtn) {
            highlightElement(el, x, y);
        }

        return xpath;
    }

    // puszczenie palca: nad strefą blokady przechodzi w tryb bez trzymania,
    // w każdym innym przypadku kończy nagranie
    function handleRelease() {
        if (!gestureActive) return;
        gestureActive = false;

        if (overLock && !recordingLocked && mediaRecorder?.state === "recording") {
            enterLockedMode();
            return;
        }

        finishRecording();
    }

    function isInside(rect, x, y) {
        return x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom;
    }

    function handleMove(x, y) {
        if (!recordingReady || initialLeft === null || initialTop === null) return;

        lastX = x;
        lastY = y;

        // martwa strefa liczona względem pozycji spoczynkowej, nie punktu chwytu —
        // w trybie zablokowanym kulka startuje ze strefy blokady, nie ze środka panelu
        const deltaX = Math.abs(x - (homeLeft + 30));
        const deltaY = Math.abs(y - (homeTop + 30));

        if (deltaX < 30 && deltaY < 30) {
            resetRecordButtonPosition();
            cancelZone.classList.remove('is-over');
            lockZone.classList.remove('is-over');
            recordingCanceled = false;
            overLock = false;
            return;
        }

        recordBtn.style.position = 'fixed';
        recordBtn.style.left = `${x - 30}px`;
        recordBtn.style.top = `${y - 30}px`;

        const overCancel = isInside(cancelRect, x, y);
        cancelZone.classList.toggle('is-over', overCancel);
        recordingCanceled = overCancel;

        // strefa blokady nie podświetla się, gdy tryb już trwa (jest wtedy w stanie is-locked)
        overLock = !recordingLocked && isInside(lockRect, x, y);
        lockZone.classList.toggle('is-over', overLock);
    }

    function beginGesture() {
        cancelRect = cancelZone.getBoundingClientRect();
        lockRect = lockZone.getBoundingClientRect();

        const rect = recordBtn.getBoundingClientRect();
        initialLeft = rect.left;
        initialTop = rect.top;

        // pozycja spoczynkowa zapamiętana raz na nagranie — kolejny gest zaczyna się
        // ze strefy blokady i nie może jej nadpisać
        if (homeLeft === null) {
            homeLeft = rect.left;
            homeTop = rect.top;
        }

        gestureActive = true;
    }

    function pressRecordButton() {
        beginGesture();

        if (recordingLocked) {
            // ponowne złapanie kulki: nagranie już trwa, więc tylko wznawiamy gest
            // i gasimy pulsowanie — puszczenie i tak zakończy nagrywanie
            recordBtn.classList.remove('locked');
            return;
        }

        startRecording();
    }

    // Przewinięcie karuzeli w trakcie nagrania jest częścią polecenia, nie tłem: agent
    // musi wiedzieć, na którym ekranie użytkownik był w której sekundzie. Karuzela woła
    // to za każdym razem, gdy przed użytkownikiem staje inna strona.
    window.pageCarousel?.onLiveChange?.((hash) => {
        if (mediaRecorder?.state !== 'recording') return;
        recordScreenVisit(hash);
    });

    // MYSZ
    recordBtn.addEventListener('mousedown', () => {
        pressRecordButton();
    });

    document.addEventListener('mousemove', e => {
        handleMove(e.clientX, e.clientY);
    });

    document.addEventListener('mouseup', handleRelease);

    // W trybie blokady kliknięcie w treść zaznacza element tak, jakby upuszczono
    // na niego kulkę. Obsługuje też dotyk — przeglądarka syntetyzuje click po tapnięciu.
    document.addEventListener('click', e => {
        if (!recordingLocked || gestureActive) return;

        // przeciągnięcie karuzeli myszą kończy się zdarzeniem click (w dotyku gasi je
        // preventDefault z touchmove) — zmiana ekranu nie może zaznaczyć elementu pod kursorem
        if (window.pageCarousel?.justSwiped) return;

        // kliknięcia w samą kulkę i w strefy sterują nagraniem, nie zaznaczają
        for (const control of [recordBtn, cancelZone, lockZone]) {
            if (control.contains(e.target)) return;
        }

        captureSelectionAt(e.clientX, e.clientY);
    });

    // DOTYK
    recordBtn.addEventListener('touchstart', e => {
        e.preventDefault();
        pressRecordButton();
    }, { passive: false });

    document.addEventListener('touchmove', e => {
        const touch = e.touches[0];
        handleMove(touch.clientX, touch.clientY);
    }, { passive: false });

    document.addEventListener('touchend', handleRelease);
    document.addEventListener('touchcancel', handleRelease);
});
