document.addEventListener('DOMContentLoaded', () => {
    const chatEl = document.getElementById('chat');
    const promptEl = document.getElementById('prompt');
    const sendBtn = document.getElementById('send');
    const abortBtn = document.getElementById('abort');
    const clearBtn = document.getElementById('clear');
    const aiCloudEl = document.getElementById('ai-cloud');

    const state = {
        abortRequested: false,
        isStreaming: false,
        renderTimer: null,
        abortController: null,
        reader: null,
    };

    marked.setOptions({breaks: true});

    function renderMarkdown(text) {
        return DOMPurify.sanitize(marked.parse(text));
    }

    function scrollChatToBottom() {
        if (!chatEl) {
            return;
        }

        chatEl.scrollTop = chatEl.scrollHeight;
    }

    function enhanceChatLinks(container = chatEl) {
        if (!container) {
            return;
        }

        container.querySelectorAll('a[href]').forEach((link) => {
            link.setAttribute('target', '_blank');
            link.setAttribute('rel', 'noopener noreferrer');
        });
    }

    function addMessage(role, html = '', extra = '') {
        const msg = document.createElement('div');
        msg.className = 'message ' + role;

        const bubble = document.createElement('div');
        bubble.className = 'bubble ' + extra;
        bubble.innerHTML = html;

        msg.appendChild(bubble);
        chatEl.appendChild(msg);

        enhanceChatLinks(bubble);
        scrollChatToBottom();

        return bubble;
    }

    function addLoader() {
        return addMessage('assistant', 'AI is thinking…', 'loader');
    }

    function hasMeaningfulChildContent(element) {
        return element.querySelector(
            'img, table, pre, code, ul, ol, h1, h2, h3, h4, h5, h6, a, hr'
        ) !== null;
    }

    function cleanupEmptyBlocks(container) {
        container.querySelectorAll('p, div, li, blockquote').forEach((el) => {
            const html = el.innerHTML
                .replace(/<br\s*\/?>/gi, '')
                .replace(/&nbsp;/gi, '')
                .trim();

            const text = (el.textContent || '').trim();

            if (html === '' && text === '') {
                el.remove();
                return;
            }

            if (text === '' && !hasMeaningfulChildContent(el)) {
                el.remove();
            }
        });
    }

    function stripAllThinkContent(container) {
        const blockSelector = 'p, div, li, blockquote';
        const thinkSpans = Array.from(container.querySelectorAll('.think'));

        if (thinkSpans.length === 0) {
            return;
        }

        const handledBlocks = new Set();

        thinkSpans.forEach((span) => {
            const block = span.closest(blockSelector) || span.parentElement;

            if (!block || handledBlocks.has(block)) {
                return;
            }

            handledBlocks.add(block);

            const thinksInBlock = Array.from(block.querySelectorAll('.think'));
            const lastThinkInBlock = thinksInBlock[thinksInBlock.length - 1];

            if (!lastThinkInBlock) {
                return;
            }

            let node = block.firstChild;

            while (node) {
                const next = node.nextSibling;
                const isLastThink = node === lastThinkInBlock;

                node.remove();

                if (isLastThink) {
                    break;
                }

                node = next;
            }

            while (
                block.firstChild &&
                (
                    (block.firstChild.nodeType === Node.TEXT_NODE &&
                        block.firstChild.textContent.trim() === '') ||
                    (block.firstChild.nodeType === Node.ELEMENT_NODE &&
                        block.firstChild.tagName === 'BR')
                )
                ) {
                block.firstChild.remove();
            }
        });

        cleanupEmptyBlocks(container);
    }

    function hasNonThinkContent(container) {
        const clone = container.cloneNode(true);

        stripAllThinkContent(clone);
        cleanupEmptyBlocks(clone);

        if ((clone.textContent || '').trim() !== '') {
            return true;
        }

        return hasMeaningfulChildContent(clone);
    }

    function cleanupThinkSpans(container) {
        if (!container) {
            return;
        }

        const thinkSpans = Array.from(container.querySelectorAll('.think'));

        if (thinkSpans.length === 0) {
            return;
        }

        if (hasNonThinkContent(container)) {
            stripAllThinkContent(container);
            return;
        }

        if (thinkSpans.length <= 1) {
            return;
        }

        const blockSelector = 'p, div, li, blockquote';
        const lastThink = thinkSpans[thinkSpans.length - 1];
        const lastBlock = lastThink.closest(blockSelector) || lastThink.parentElement;

        thinkSpans.slice(0, -1).forEach((span) => {
            const block = span.closest(blockSelector) || span.parentElement;

            if (block && block !== lastBlock) {
                block.remove();
                return;
            }

            if (block === lastBlock) {
                span.remove();
            }
        });

        if (lastBlock && lastBlock.contains(lastThink)) {
            let node = lastBlock.firstChild;

            while (node && node !== lastThink) {
                const next = node.nextSibling;
                node.remove();
                node = next;
            }

            while (
                lastThink.nextSibling &&
                (
                    (lastThink.nextSibling.nodeType === Node.TEXT_NODE &&
                        lastThink.nextSibling.textContent.trim() === '') ||
                    (lastThink.nextSibling.nodeType === Node.ELEMENT_NODE &&
                        lastThink.nextSibling.tagName === 'BR')
                )
                ) {
                lastThink.nextSibling.remove();
            }
        }

        cleanupEmptyBlocks(container);
    }

    function renderBubbleContent(bubble, raw) {
        bubble.innerHTML = renderMarkdown(raw);
        cleanupThinkSpans(bubble);
        enhanceChatLinks(bubble);
        scrollChatToBottom();
    }

    function setBusyUi(isBusy) {
        state.isStreaming = isBusy;

        sendBtn.disabled = isBusy;
        abortBtn.disabled = !isBusy;
        clearBtn.disabled = isBusy;

        if (isBusy) {
            aiCloudEl?.classList.remove('d-none');
            return;
        }

        aiCloudEl?.classList.add('d-none');
    }

    function clearScheduledRender() {
        if (!state.renderTimer) {
            return;
        }

        clearTimeout(state.renderTimer);
        state.renderTimer = null;
    }

    function scheduleRender(bubble, getRaw) {
        if (state.renderTimer) {
            return;
        }

        state.renderTimer = setTimeout(() => {
            try {
                renderBubbleContent(bubble, getRaw());
            } finally {
                state.renderTimer = null;
            }
        }, 100);
    }

    function parseSseEvents(buffer) {
        const normalizedBuffer = buffer.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
        const chunks = normalizedBuffer.split('\n\n');

        return {
            events: chunks.slice(0, -1),
            rest: chunks[chunks.length - 1] || '',
        };
    }

    function readSseEvent(rawEvent) {
        const lines = rawEvent.split('\n');
        let eventName = 'message';
        const dataLines = [];

        lines.forEach((line) => {
            if (line.startsWith('event:')) {
                eventName = line.slice(6).trim() || 'message';
                return;
            }

            if (line.startsWith('data:')) {
                dataLines.push(line.slice(5).trimStart());
            }
        });

        return {
            eventName,
            data: dataLines.join('\n'),
        };
    }

    function finalizeStream(bubble, raw) {
        clearScheduledRender();
        renderBubbleContent(bubble, raw);
    }

    async function releaseStreamResources() {
        try {
            await state.reader?.cancel();
        } catch (err) {
            console.debug('Reader cancel failed:', err);
        }

        state.reader = null;
        state.abortController = null;
    }

    async function loadHistory() {
        try {
            const res = await fetch('/history');

            if (!res.ok) {
                console.error('History request failed with status:', res.status);
                return;
            }

            const messages = await res.json();

            messages.forEach((message) => {
                const bubble = addMessage(message.role);
                renderBubbleContent(bubble, message.text);
            });

            enhanceChatLinks(chatEl);
        } catch (err) {
            console.error('History load failed:', err);
        }
    }

    loadHistory();

    promptEl.addEventListener('keydown', (event) => {
        if (event.key === 'Enter' && !event.shiftKey) {
            event.preventDefault();
            sendBtn.click();
        }
    });

    sendBtn.addEventListener('click', async () => {
        const prompt = promptEl.value.trim();

        if (!prompt || state.isStreaming) {
            return;
        }

        addMessage('user', renderMarkdown(prompt));
        promptEl.value = '';

        const bubble = addLoader();
        let raw = '';
        let firstChunk = true;
        let sseBuffer = '';

        state.abortRequested = false;
        state.abortController = new AbortController();

        setBusyUi(true);

        try {
            const res = await fetch('/ask-sse', {
                method: 'POST',
                headers: {'Content-Type': 'application/json'},
                body: JSON.stringify({
                    prompt,
                    fullContext: false,
                }),
                signal: state.abortController.signal,
            });

            if (!res.ok) {
                throw new Error(`HTTP ${res.status} ${res.statusText}`);
            }

            if (!res.body) {
                throw new Error('SSE response has no body');
            }

            state.reader = res.body.getReader();
            const decoder = new TextDecoder();

            while (!state.abortRequested) {
                const {value, done} = await state.reader.read();

                if (done) {
                    break;
                }

                sseBuffer += decoder.decode(value, {stream: true});

                const parsed = parseSseEvents(sseBuffer);
                sseBuffer = parsed.rest;

                for (const rawEvent of parsed.events) {
                    if (!rawEvent.trim()) {
                        continue;
                    }

                    const {eventName, data} = readSseEvent(rawEvent);

                    if (!data && eventName !== 'done') {
                        continue;
                    }

                    if (eventName === 'done' || data === '[DONE]') {
                        finalizeStream(bubble, raw);
                        state.abortRequested = true;
                        break;
                    }

                    if (firstChunk) {
                        bubble.classList.remove('loader');
                        bubble.innerHTML = '';
                        firstChunk = false;
                    }

                    raw += data;
                    scheduleRender(bubble, () => raw);
                }
            }

            if (!state.abortRequested && sseBuffer.trim() !== '') {
                const trailingEvent = readSseEvent(sseBuffer);

                if (trailingEvent.data && trailingEvent.data !== '[DONE]') {
                    if (firstChunk) {
                        bubble.classList.remove('loader');
                        bubble.innerHTML = '';
                        firstChunk = false;
                    }

                    raw += trailingEvent.data;
                }
            }

            if (!state.abortRequested) {
                finalizeStream(bubble, raw);
            }
        } catch (err) {
            if (err?.name === 'AbortError' || state.abortRequested) {
                console.info('SSE request aborted by user');
            } else {
                console.error('SSE stream failed:', err);

                bubble.classList.remove('loader');

                if (raw.trim() !== '') {
                    raw += `\n\n<em>Stream error: ${String(err.message || err)}</em>`;
                    renderBubbleContent(bubble, raw);
                } else {
                    bubble.innerHTML = `<em>Stream error: ${String(err.message || err)}</em>`;
                    enhanceChatLinks(bubble);
                    scrollChatToBottom();
                }
            }
        } finally {
            clearScheduledRender();
            await releaseStreamResources();
            setBusyUi(false);
        }
    });

    abortBtn.addEventListener('click', async () => {
        if (!state.isStreaming) {
            return;
        }

        state.abortRequested = true;
        state.abortController?.abort();
        await releaseStreamResources();
        setBusyUi(false);
        addMessage('assistant', '<em>[aborted]</em>');
    });

    clearBtn.addEventListener('click', async () => {
        if (state.isStreaming) {
            return;
        }

        try {
            await fetch('/history/delete', {method: 'POST'});
        } catch (err) {
            console.error('History delete failed:', err);
        }

        chatEl.innerHTML = '';
        addMessage('assistant', '<em>History cleared.</em>');
    });
});