const state = {
    data: null,
    rules: [],
};

const el = (sel) => document.querySelector(sel);

function setStatus(msg, type = "info") {
    const s = el('#status');
    s.textContent = msg || '';
    s.className = `status ${type}`;
}

async function fetchJSON(url, options = {}) {
    const res = await fetch(url, options);
    const data = await res.json();
    if (!res.ok || data.status === 'error') {
        const message = data && data.message ? data.message : `HTTP ${res.status}`;
        throw new Error(message);
    }
    return data;
}

function ruleRow(rule, index) {
    const div = document.createElement('div');
    div.className = 'rule-row';
    div.innerHTML = `
    <div><textarea class="comment autoresize" rows="2" placeholder="Comentario">${rule.__comment || ''}</textarea></div>
    <div><textarea class="pattern autoresize" rows="2" placeholder="Pattern">${rule.pattern || ''}</textarea></div>
    <div><input class="replacement" value="${rule.replacement || ''}" placeholder="Replacement"/></div>
    <div>
      <select class="action">
        ${['replace', 'remove_line', 'remove_block', 'add_before', 'add_after'].map(a => `<option value="${a}" ${rule.action === a ? 'selected' : ''}>${a}</option>`).join('')}
      </select>
    </div>
    <div>
      <select class="type">
        ${['string', 'regex', 'left', 'right', 'substring'].map(t => `<option value="${t}" ${rule.type === t ? 'selected' : ''}>${t}</option>`).join('')}
      </select>
    </div>
    <div><input class="priority" type="number" value="${Number.isInteger(rule.priority) ? rule.priority : 5}"/></div>
    <div class="actions">
      <button class="up">↑</button>
      <button class="down">↓</button>
      <button class="dup">⧉</button>
      <button class="del">✕</button>
      <button class="test">Edicion y Test</button>
    </div>
  `;

    // bindings
    const cEl = div.querySelector('.comment');
    const pEl = div.querySelector('.pattern');
    cEl.addEventListener('input', (e) => { state.rules[index].__comment = e.target.value; autoResize(e.target); });
    pEl.addEventListener('input', (e) => { state.rules[index].pattern = e.target.value; autoResize(e.target); });
    // Nota: no auto-redimensionar en el render inicial para mantener 2 líneas visibles
    div.querySelector('.replacement').addEventListener('input', (e) => state.rules[index].replacement = e.target.value);
    div.querySelector('.action').addEventListener('change', (e) => state.rules[index].action = e.target.value);
    div.querySelector('.type').addEventListener('change', (e) => state.rules[index].type = e.target.value);
    div.querySelector('.priority').addEventListener('input', (e) => state.rules[index].priority = parseInt(e.target.value, 10));

    div.querySelector('.up').addEventListener('click', () => moveRule(index, -1));
    div.querySelector('.down').addEventListener('click', () => moveRule(index, 1));
    div.querySelector('.del').addEventListener('click', () => removeRule(index));
    div.querySelector('.dup').addEventListener('click', () => duplicateRule(index));
    div.querySelector('.test').addEventListener('click', () => openModal(index));

    return div;
}

function render() {
    const list = el('#rulesList');
    list.innerHTML = '';
    state.rules
        .map((r, i) => [r, i])
        .sort((a, b) => (a[0].priority ?? 9999) - (b[0].priority ?? 9999) || a[1] - b[1])
        .forEach(([r, i]) => list.appendChild(ruleRow(r, i)));

    const raw = {
        ...state.data,
        rules: state.rules,
    };
    el('#rawJson').value = JSON.stringify(raw, null, 2);
}

function moveRule(index, delta) {
    const newIndex = index + delta;
    if (newIndex < 0 || newIndex >= state.rules.length) return;
    const tmp = state.rules[index];
    state.rules[index] = state.rules[newIndex];
    state.rules[newIndex] = tmp;
    render();
}

function removeRule(index) {
    state.rules.splice(index, 1);
    render();
}

function duplicateRule(index) {
    const original = state.rules[index];
    const copy = JSON.parse(JSON.stringify(original));
    state.rules.splice(index + 1, 0, copy);
    render();
}

function addRule() {
    state.rules.push({
        __comment: '',
        pattern: '',
        replacement: '',
        action: 'replace',
        type: 'string',
        priority: 5,
    });
    render();
}

async function loadAll() {
    try {
        setStatus('Cargando...', 'info');
        const [meta, rulesRes] = await Promise.all([
            fetchJSON('/api/meta'),
            fetchJSON('/api/rules'),
        ]);
        state.data = rulesRes.data;
        state.rules = Array.isArray(rulesRes.data.rules) ? JSON.parse(JSON.stringify(rulesRes.data.rules)) : [];
        el('#metaInfo').textContent = meta.exists ? `${meta.path} (${meta.size} bytes) — modificado: ${meta.modified}` : 'Archivo no encontrado';
        render();
        setStatus('Listo', 'success');
    } catch (e) {
        setStatus(`Error: ${e.message}`, 'error');
    }
}

async function saveAll() {
    try {
        setStatus('Guardando...', 'info');
        // Fusionar: tomar el JSON avanzado si es válido, pero SIEMPRE usar las reglas del estado actual
        const payloadFromState = { ...state.data, rules: state.rules };
        let payload = payloadFromState;
        try {
            const parsed = JSON.parse(el('#rawJson').value);
            payload = { ...parsed, rules: state.rules };
        } catch (_) { /* si el JSON está inválido, persistimos el estado visible */ }
        const res = await fetchJSON('/api/rules', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(payload),
        });
        setStatus(`${res.message} (backup: ${res.backup})`, 'success');
        await loadAll();
    } catch (e) {
        setStatus(`Error: ${e.message}`, 'error');
    }
}

function wireUI() {
    const bind = (selector, event, handler) => {
        const node = el(selector);
        if (node) node.addEventListener(event, handler);
        return node;
    };

    bind('#btnReload', 'click', loadAll);
    bind('#btnSave', 'click', saveAll);
    bind('#btnAdd', 'click', addRule);
    bind('#btnShutdown', 'click', async () => {
        try {
            await fetchJSON('/_shutdown', { method: 'POST' });
        } catch (_) { }
        try { window.close(); } catch (_) { }
    });
    // modal
    bind('#modalClose', 'click', closeModal);
    bind('#btnModalCancel', 'click', closeModal);
    bind('#btnModalApply', 'click', applyModalToRule);
    bind('#btnModalTest', 'click', testModalRule);
}

document.addEventListener('DOMContentLoaded', async () => {
    wireUI();
    await loadAll();
    // Iniciar heartbeat periódico para indicar presencia del cliente
    try {
        const beat = async () => {
            try {
                await fetch('/api/heartbeat', { cache: 'no-store' });
            } catch (_) { /* noop */ }
        };
        // primer latido inmediato y luego cada 10s
        beat();
        setInterval(beat, 10000);
    } catch (_) { /* noop */ }
});

// --- Auto-resize helpers ---
function autoResize(t) {
    t.style.height = 'auto';
    t.style.height = (t.scrollHeight) + 'px';
}

// --- Modal logic ---
let modalIndex = -1;
function openModal(index) {
    modalIndex = index;
    const r = state.rules[index];
    // fill selects
    el('#mAction').innerHTML = ['replace', 'remove_line', 'remove_block', 'add_before', 'add_after']
        .map(a => `<option value="${a}" ${r.action === a ? 'selected' : ''}>${a}</option>`).join('');
    el('#mType').innerHTML = ['string', 'regex', 'left', 'right', 'substring']
        .map(t => `<option value="${t}" ${r.type === t ? 'selected' : ''}>${t}</option>`).join('');
    el('#mPattern').value = r.pattern || '';
    el('#mReplacement').value = r.replacement || '';
    el('#mOriginal').textContent = '';
    el('#mProcessed').textContent = '';
    updateActionHelp();
    el('#mAction').addEventListener('change', updateActionHelp, { once: false });
    document.getElementById('modal').classList.remove('hidden');
}

function closeModal() {
    document.getElementById('modal').classList.add('hidden');
    modalIndex = -1;
}

function applyModalToRule() {
    if (modalIndex < 0) return;
    const r = state.rules[modalIndex];
    r.pattern = el('#mPattern').value;
    r.replacement = el('#mReplacement').value;
    r.action = el('#mAction').value;
    r.type = el('#mType').value;
    closeModal();
    render();
}

async function testModalRule() {
    try {
        const payload = {
            pattern: el('#mPattern').value,
            replacement: el('#mReplacement').value,
            action: el('#mAction').value,
            type: el('#mType').value,
            max_chars: 4000,
        };
        const res = await fetchJSON('/api/test_pattern', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(payload),
        });
        el('#mOriginal').textContent = res.original_excerpt || '';
        el('#mProcessed').textContent = res.processed_excerpt || '';
        el('#modalMeta').textContent = `Longitud original: ${res.total_len}`;
    } catch (e) {
        el('#modalMeta').textContent = `Error: ${e.message}`;
    }
}

function actionHelpText(action) {
    const map = {
        replace: 'Reemplaza coincidencias de "pattern" por "replacement". Respeta el "type": string/substring (reemplazo literal), regex (expresión regular), left/right (aplica por línea al inicio/fin).',
        remove_line: 'Elimina las líneas que coinciden con "pattern" según el "type". Si la línea siguiente queda en blanco, también se elimina.',
        remove_block: 'Elimina bloques completos que coincidan con el patrón multi-línea. Usa "....." como comodín para cualquier texto entre medio. Ej: "Inicio.....Fin" borrará todo desde la línea con "Inicio" hasta la línea con "Fin".',
        add_before: 'Inserta el contenido de "replacement" en una nueva línea antes de cada línea que coincida con "pattern" (según el "type").',
        add_after: 'Inserta el contenido de "replacement" en una nueva línea después de cada línea que coincida con "pattern" (según el "type").',
    };
    return map[action] || '';
}

function updateActionHelp() {
    const sel = el('#mAction');
    if (!sel) return;
    const txt = actionHelpText(sel.value);
    const node = el('#mActionHelp');
    if (node) node.textContent = txt;
}


