/*
 * 通用消息操作层：提供长按/右键引用回复、回复草稿和引用块渲染。
 * 作者: @ccfork
 */
(function () {
    const LONG_PRESS_MS = 520;
    let activeMenu = null;

    function escapeHtml(text) {
        const div = document.createElement('div');
        div.textContent = text == null ? '' : String(text);
        return div.innerHTML;
    }

    function htmlToText(text) {
        const div = document.createElement('div');
        div.innerHTML = String(text || '');
        return (div.textContent || div.innerText || '').replace(/\s+/g, ' ').trim();
    }

    function plainText(text) {
        return String(text || '').replace(/\s+/g, ' ').trim();
    }

    function senderName(sender, selfSender = 'user') {
        if (sender === selfSender) return '我';
        return sender === 'admin' ? '小纸条' : '对方';
    }

    function previewMessage(message, maxLength = 80) {
        const text = String(message?.preview || message?.message || '').replace(/\[bq:[^\]]+\]/g, '[表情]').trim();
        const preview = text || (message?.type === 'image' ? '[图片]' : '消息');
        return preview.length > maxLength ? `${preview.slice(0, maxLength)}...` : preview;
    }

    function normalizeReply(reply) {
        if (!reply || typeof reply !== 'object') return null;
        const id = Number(reply.id || reply.message_id || reply.reply_to_id || 0);
        const sender = reply.sender === 'admin' ? 'admin' : 'user';
        const type = reply.type === 'image' ? 'image' : 'text';
        const preview = previewMessage({ preview: reply.preview || reply.message || (type === 'image' ? '[图片]' : '') }, 96);
        if (!id && !preview) return null;
        return { id, sender, type, preview };
    }

    function getReplyPayload(reply) {
        const normalized = normalizeReply(reply);
        if (!normalized) return null;
        return {
            id: normalized.id,
            sender: normalized.sender,
            type: normalized.type,
            preview: normalized.preview
        };
    }

    function messageFromElement(node) {
        if (!node) return null;
        return {
            id: Number(node.dataset.messageId || 0),
            sender: node.dataset.sender === 'admin' ? 'admin' : 'user',
            type: node.dataset.type === 'image' ? 'image' : 'text',
            recalled: node.dataset.recalled === '1',
            preview: node.dataset.preview || htmlToText(node.querySelector('.message-bubble')?.innerHTML || '')
        };
    }

    function renderReplyBlock(reply, options = {}) {
        const normalized = normalizeReply(reply);
        if (!normalized) return '';
        const selfSender = options.selfSender || 'user';
        return `<button class="message-reply-block" type="button" data-reply-target="${escapeHtml(String(normalized.id || ''))}" title="查看引用消息"><span class="message-reply-sender">${escapeHtml(senderName(normalized.sender, selfSender))}</span><span class="message-reply-preview">${escapeHtml(normalized.preview)}</span></button>`;
    }

    function createReplyBar(reply, onCancel, selfSender = 'user') {
        const normalized = normalizeReply(reply);
        if (!normalized) return null;
        const bar = document.createElement('div');
        bar.className = 'reply-draft';
        bar.innerHTML = `
            <div class="reply-draft-mark"></div>
            <div class="reply-draft-main">
                <strong>回复 ${escapeHtml(senderName(normalized.sender, selfSender))}</strong>
                <span>${escapeHtml(normalized.preview)}</span>
            </div>
            <button class="reply-draft-close" type="button" aria-label="取消引用" title="取消引用">×</button>
        `;
        bar.querySelector('.reply-draft-close')?.addEventListener('click', onCancel);
        return bar;
    }

    function closeMenu() {
        if (!activeMenu) return;
        activeMenu.remove();
        activeMenu = null;
    }

    function placeMenu(menu, x, y) {
        document.body.appendChild(menu);
        const rect = menu.getBoundingClientRect();
        const gap = 10;
        const left = Math.min(Math.max(gap, x), window.innerWidth - rect.width - gap);
        const top = Math.min(Math.max(gap, y), window.innerHeight - rect.height - gap);
        menu.style.left = `${left}px`;
        menu.style.top = `${top}px`;
    }

    function openMenu({ x, y, message, onReply, onRecall, canRecall }) {
        closeMenu();
        const replyButton = message?.recalled
            ? ''
            : '<button type="button" data-action="reply">引用回复</button>';
        const recallButton = !message?.recalled && canRecall?.(message)
            ? '<button type="button" data-action="recall">撤回</button>'
            : '';
        if (!replyButton && !recallButton) return;
        const menu = document.createElement('div');
        menu.className = 'message-action-menu';
        menu.innerHTML = `${replyButton}${recallButton}`;
        menu.addEventListener('pointerdown', (event) => event.stopPropagation());
        menu.addEventListener('click', (event) => {
            const button = event.target.closest('[data-action]');
            if (!button) return;
            event.preventDefault();
            closeMenu();
            if (button.dataset.action === 'reply') onReply?.(message);
            if (button.dataset.action === 'recall') onRecall?.(message);
        });
        activeMenu = menu;
        placeMenu(menu, x, y);
        setTimeout(() => {
            document.addEventListener('pointerdown', closeMenu, { once: true });
        }, 0);
    }

    function cssEscape(value) {
        return window.CSS?.escape ? window.CSS.escape(value) : value.replace(/"/g, '\\"');
    }

    function highlightTarget(container, id) {
        if (!container || !id) return;
        const target = container.querySelector(`[data-message-id="${cssEscape(String(id))}"]`);
        if (!target) return;
        target.scrollIntoView({ behavior: 'smooth', block: 'center' });
        target.classList.add('message-highlight');
        setTimeout(() => target.classList.remove('message-highlight'), 1400);
    }

    function bindMessageActions({ container, input, draftHost, onReplyChange, onRecall, canRecall, selfSender = 'user' }) {
        let reply = null;
        let longPressTimer = null;
        let pressTarget = null;
        let startPoint = null;
        let suppressClickUntil = 0;

        function emit() {
            onReplyChange?.(getReplyPayload(reply));
        }

        function renderDraft() {
            if (!draftHost) return;
            draftHost.querySelector('.reply-draft')?.remove();
            const bar = createReplyBar(reply, () => setReply(null), selfSender);
            if (bar) draftHost.prepend(bar);
        }

        function setReply(message) {
            reply = getReplyPayload(message);
            renderDraft();
            emit();
            if (reply) {
                input?.focus();
                input?.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
            }
        }

        function clearReply() {
            setReply(null);
        }

        function openFromEvent(event, target) {
            const message = messageFromElement(target);
            if (!message?.id) return;
            const x = event.clientX || startPoint?.x || window.innerWidth / 2;
            const y = event.clientY || startPoint?.y || window.innerHeight / 2;
            suppressClickUntil = Date.now() + 650;
            openMenu({ x, y, message, onReply: setReply, onRecall, canRecall });
        }

        container.addEventListener('contextmenu', (event) => {
            const target = event.target.closest('[data-message-id]');
            if (!target || !container.contains(target)) return;
            event.preventDefault();
            openFromEvent(event, target);
        });

        container.addEventListener('pointerdown', (event) => {
            if (event.pointerType === 'mouse') return;
            const target = event.target.closest('[data-message-id]');
            if (!target || !container.contains(target)) return;
            pressTarget = target;
            startPoint = { x: event.clientX, y: event.clientY };
            longPressTimer = setTimeout(() => openFromEvent(event, pressTarget), LONG_PRESS_MS);
        });

        container.addEventListener('pointermove', (event) => {
            if (!startPoint || !longPressTimer) return;
            if (Math.abs(event.clientX - startPoint.x) > 10 || Math.abs(event.clientY - startPoint.y) > 10) {
                clearTimeout(longPressTimer);
                longPressTimer = null;
            }
        });

        ['pointerup', 'pointercancel', 'pointerleave'].forEach((name) => {
            container.addEventListener(name, () => {
                if (longPressTimer) clearTimeout(longPressTimer);
                longPressTimer = null;
                pressTarget = null;
                startPoint = null;
            });
        });

        container.addEventListener('click', (event) => {
            if (Date.now() < suppressClickUntil) {
                event.preventDefault();
                event.stopPropagation();
                return;
            }
            const target = event.target.closest('[data-reply-target]');
            if (!target) return;
            event.preventDefault();
            event.stopPropagation();
            highlightTarget(container, target.dataset.replyTarget);
        });

        return {
            getReply: () => getReplyPayload(reply),
            setReply,
            clearReply,
            closeMenu
        };
    }

    window.MessageActions = {
        bindMessageActions,
        getReplyPayload,
        normalizeReply,
        previewMessage,
        renderReplyBlock,
        senderName,
        escapeHtml
    };
})();
