/** Escape for safe text nodes (not HTML injection). */
function appendText(parent, text) {
    if (!text) return;
    parent.appendChild(document.createTextNode(text));
}

const PLAYER_CHOSE_RE = /^\[Player chose:\s*(.+)\]$/s;
const RP_TOKEN_RE = /("(?:[^"\\]|\\.)*"|«[^»]*»|'(?:[^'\\]|\\.)*'|\*\*[^*]+\*\*|\*[^*]+\*)/g;

const OUTCOME_CLASS = {
    'critical failure': 'outcome-crit-fail',
    failure: 'outcome-fail',
    success: 'outcome-success',
    'critical success': 'outcome-crit-success',
};

export function isPlayerChoiceContent(text) {
    return PLAYER_CHOSE_RE.test((text || '').trim());
}

export function parsePlayerChoice(text) {
    const m = (text || '').trim().match(PLAYER_CHOSE_RE);
    return m ? m[1].trim() : null;
}

function appendPlayerChoice(frag, label) {
    const wrap = document.createElement('span');
    wrap.className = 'rp-choice';
    const tag = document.createElement('span');
    tag.className = 'rp-choice-tag';
    tag.textContent = '🔘 Выбор';
    wrap.appendChild(tag);
    appendText(wrap, ' ');
    const lab = document.createElement('span');
    lab.className = 'rp-choice-label';
    lab.textContent = label;
    wrap.appendChild(lab);
    frag.appendChild(wrap);
}

/**
 * Build DOM fragment for RP formatting (dialogue, action, player choice).
 */
export function buildRpFormatFragment(text) {
    const frag = document.createDocumentFragment();
    if (!text) return frag;

    const trimmed = text.trim();
    const choiceLabel = parsePlayerChoice(trimmed);
    if (choiceLabel !== null) {
        appendPlayerChoice(frag, choiceLabel);
        return frag;
    }

    let last = 0;
    for (const m of trimmed.matchAll(RP_TOKEN_RE)) {
        const idx = m.index ?? 0;
        if (idx > last) appendText(frag, trimmed.slice(last, idx));

        const token = m[0];
        if (
            (token.startsWith('"') && token.endsWith('"'))
            || (token.startsWith('«') && token.endsWith('»'))
            || (token.startsWith("'") && token.endsWith("'"))
        ) {
            const span = document.createElement('span');
            span.className = 'rp-dialogue';
            span.textContent = token;
            frag.appendChild(span);
        } else if (token.startsWith('**') && token.endsWith('**')) {
            const em = document.createElement('em');
            em.className = 'rp-action';
            em.textContent = token.slice(2, -2);
            frag.appendChild(em);
        } else if (token.startsWith('*') && token.endsWith('*')) {
            const em = document.createElement('em');
            em.className = 'rp-action';
            em.textContent = token.slice(1, -1);
            frag.appendChild(em);
        } else {
            appendText(frag, token);
        }
        last = idx + token.length;
    }
    if (last < trimmed.length) appendText(frag, trimmed.slice(last));
    return frag;
}

/**
 * Dice override block on user bubble (intent struck through → resolution).
 */
export function buildDiceOverrideFragment(resolution) {
    const { intent_text, resolution_text, roll, outcome } = resolution;
    const wrap = document.createElement('div');
    wrap.className = 'dice-user-override';

    const badge = document.createElement('div');
    badge.className = `dice-user-badge ${OUTCOME_CLASS[outcome] || ''}`;
    badge.textContent = `🎲 ${roll} · ${outcome || 'roll'}`;
    wrap.appendChild(badge);

    const row = document.createElement('div');
    row.className = 'dice-user-row';

    const struck = document.createElement('span');
    struck.className = 'dice-intent-struck';
    struck.textContent = intent_text || '';
    row.appendChild(struck);

    const arrow = document.createElement('span');
    arrow.className = 'dice-intent-arrow';
    arrow.textContent = '→';
    row.appendChild(arrow);

    const resolved = document.createElement('span');
    resolved.className = 'dice-intent-resolved';
    resolved.textContent = resolution_text || '';
    row.appendChild(resolved);

    wrap.appendChild(row);
    return wrap;
}

export function applyDiceOverrideToBubble(bubble, resolution) {
    if (!resolution?.resolution_text) return;
    bubble.dataset.diceOverride = '1';
    const raw = bubble.dataset.raw ?? '';
    const frag = document.createDocumentFragment();
    if (raw && !isPlayerChoiceContent(raw)) {
        frag.appendChild(buildRpFormatFragment(raw));
        frag.appendChild(document.createElement('br'));
    } else if (raw) {
        frag.appendChild(buildRpFormatFragment(raw));
        frag.appendChild(document.createElement('br'));
    }
    frag.appendChild(buildDiceOverrideFragment(resolution));
    bubble.innerHTML = '';
    bubble.appendChild(frag);
    bubble.classList.add('rp-formatted', 'has-dice-override');
}

export function applyRpFormatToBubble(bubble) {
    if (bubble.dataset.diceOverride === '1' && bubble._diceResolution) {
        applyDiceOverrideToBubble(bubble, bubble._diceResolution);
        return;
    }
    const raw = bubble.dataset.raw ?? bubble.textContent ?? '';
    bubble.dataset.raw = raw;
    bubble.innerHTML = '';
    bubble.appendChild(buildRpFormatFragment(raw));
    bubble.classList.add('rp-formatted');
}

export function applyRpPlainToBubble(bubble) {
    const raw = bubble.dataset.raw ?? bubble.textContent ?? '';
    bubble.dataset.raw = raw;
    bubble.textContent = raw;
    bubble.classList.remove('rp-formatted');
}

export function attachFormatToggle(wrapper, bubble) {
    const btn = document.createElement('button');
    btn.type = 'button';
    btn.className = 'format-btn';
    btn.textContent = '↩ Текст';
    let showingPlain = false;

    const syncBtn = () => {
        btn.textContent = showingPlain ? '✨' : '↩ Текст';
        btn.title = showingPlain ? 'Форматировать' : 'Показать оригинал';
    };

    btn.addEventListener('click', (e) => {
        e.preventDefault();
        e.stopPropagation();
        if (showingPlain) {
            if (bubble._diceResolution) {
                applyDiceOverrideToBubble(bubble, bubble._diceResolution);
            } else {
                applyRpFormatToBubble(bubble);
            }
            showingPlain = false;
        } else {
            applyRpPlainToBubble(bubble);
            showingPlain = true;
        }
        syncBtn();
    });

    syncBtn();
    wrapper.appendChild(btn);
    return btn;
}

export function initBubbleContent(bubble, rawText, { formatted = true, actionResolution = null } = {}) {
    bubble.dataset.raw = rawText ?? '';
    bubble._diceResolution = actionResolution || null;
    if (actionResolution?.resolution_text && formatted) {
        bubble.dataset.diceOverride = '1';
        applyDiceOverrideToBubble(bubble, actionResolution);
        return;
    }
    if (formatted && rawText) {
        applyRpFormatToBubble(bubble);
    } else {
        bubble.textContent = rawText ?? '';
        bubble.classList.remove('rp-formatted', 'has-dice-override');
    }
}
