/* ═══════════════════════════════════════════════════════════════
   ProjectManager – Main JavaScript
═══════════════════════════════════════════════════════════════ */

document.addEventListener('DOMContentLoaded', () => {

    // ── Mobile sidebar ────────────────────────────────────────────
    const menuBtn       = document.getElementById('menuBtn');
    const sidebar       = document.getElementById('sidebar');
    const sidebarOverlay = document.getElementById('sidebarOverlay');
    const sidebarToggle = document.getElementById('sidebarToggle');

    function openSidebar() {
        sidebar?.classList.add('open');
        sidebarOverlay?.classList.add('open');
        document.body.style.overflow = 'hidden';
    }
    function closeSidebar() {
        sidebar?.classList.remove('open');
        sidebarOverlay?.classList.remove('open');
        document.body.style.overflow = '';
    }
    menuBtn?.addEventListener('click', openSidebar);
    sidebarToggle?.addEventListener('click', closeSidebar);
    sidebarOverlay?.addEventListener('click', closeSidebar);

    // ── Dropdowns ─────────────────────────────────────────────────
    function initDropdown(btnId, dropdownId) {
        const btn = document.getElementById(btnId);
        const dd  = document.getElementById(dropdownId);
        if (!btn || !dd) return;

        btn.addEventListener('click', (e) => {
            e.stopPropagation();
            const isOpen = dd.classList.contains('open');
            closeAllDropdowns();
            if (!isOpen) dd.classList.add('open');
        });
    }
    function closeAllDropdowns() {
        document.querySelectorAll('.notif-dropdown, .user-dropdown').forEach(d => d.classList.remove('open'));
    }
    document.addEventListener('click', closeAllDropdowns);

    initDropdown('notifBtn', 'notifDropdown');
    initDropdown('userMenuBtn', 'userDropdown');

    // Mark all as read when the notification dropdown is opened
    document.getElementById('notifBtn')?.addEventListener('click', () => {
        const dd = document.getElementById('notifDropdown');
        if (!dd?.classList.contains('open')) return; // will be opened after this tick
        requestAnimationFrame(async () => {
            const unread = document.querySelectorAll('.notif-item.unread');
            if (!unread.length) return;
            await _notifPost('read-all', {});
            unread.forEach(el => el.classList.remove('unread'));
            unread.forEach(el => el.querySelector('.notif-act-btn:not(.del)')?.remove());
            const badge = document.querySelector('.notif-badge');
            if (badge) badge.style.display = 'none';
        });
    });

    // ── Notifications ─────────────────────────────────────────────
    const _base = typeof BASE_URL !== 'undefined' ? BASE_URL : '';
    const _csrf = () => typeof CSRF_TOKEN !== 'undefined' ? CSRF_TOKEN : '';

    async function loadNotifications() {
        const list = document.getElementById('notifList');
        if (!list) return;
        _bindNotifList(list);
        try {
            const [r1, r2] = await Promise.all([
                fetch(_base + '/api/notifications/unread'),
                fetch(_base + '/api/notifications/recent'),
            ]);
            if (!r1.ok || !r2.ok) return;
            const { count } = await r1.json();
            const notifs    = await r2.json();
            _setBadge(count);
            _renderList(list, notifs);
        } catch {}
    }

    function _setBadge(count) {
        let badge = document.querySelector('.notif-badge');
        const btn = document.getElementById('notifBtn');
        if (!btn) return;
        if (count > 0) {
            if (!badge) {
                badge = document.createElement('span');
                badge.className = 'notif-badge';
                btn.appendChild(badge);
            }
            badge.textContent = count > 99 ? '99' : count;
            badge.style.display = '';
        } else if (badge) {
            badge.style.display = 'none';
        }
    }

    function _fmtTime(ts) {
        const d = new Date(ts.replace(' ', 'T'));
        const now = new Date();
        const diff = (now - d) / 1000;
        if (diff < 60)  return 'Gerade eben';
        if (diff < 3600) return Math.floor(diff / 60) + ' Min.';
        if (diff < 86400) return Math.floor(diff / 3600) + ' Std.';
        return d.toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit' });
    }

    // DOM builder. Children are appended as text nodes when string, otherwise as-is.
    // Using textContent for data fields removes the XSS distance from manual escaping.
    function _el(tag, attrs, children) {
        const e = document.createElement(tag);
        if (attrs) {
            for (const [k, v] of Object.entries(attrs)) {
                if (v == null || v === false) continue;
                if (k === 'class') e.className = v;
                else e.setAttribute(k, v);
            }
        }
        if (children) {
            for (const c of children) {
                if (c == null) continue;
                e.appendChild(typeof c === 'string' ? document.createTextNode(c) : c);
            }
        }
        return e;
    }

    // Parse a static SVG string from source code into a node. Input is never user data.
    function _svg(html) {
        const t = document.createElement('template');
        t.innerHTML = html.trim();
        return t.content.firstElementChild;
    }

    function _renderList(list, notifs) {
        if (notifs.length === 0) {
            list.replaceChildren(_el('div', { class: 'notif-empty' }, ['Keine Benachrichtigungen']));
            return;
        }
        const items = notifs.map(n => {
            const isInvite = n.related_type === 'invite' && n.related_id;
            const id   = parseInt(n.id) || 0;
            const rid  = parseInt(n.related_id) || 0;
            const isUnread = !parseInt(n.is_read);
            const readClass = isUnread ? 'unread' : '';
            const title   = String(n.title ?? '');
            const message = String(n.message ?? '');

            if (isInvite) {
                return _el('div', {
                    class: ('notif-item notif-item-invite ' + readClass).trim(),
                    'data-id': id,
                    'data-invite-id': rid,
                }, [
                    _el('div', { class: 'notif-item-body' }, [
                        _el('div', { class: 'notif-item-title' }, [title]),
                        _el('div', { class: 'notif-item-msg'   }, [message]),
                        _el('div', { class: 'notif-item-time'  }, [_fmtTime(n.created_at)]),
                    ]),
                    _el('div', { class: 'notif-invite-actions' }, [
                        _el('button', { class: 'btn btn-xs btn-primary', 'data-notif-action': 'invite-accept' }, ['Annehmen']),
                        _el('button', { class: 'btn btn-xs btn-ghost',   'data-notif-action': 'invite-decline' }, ['Ablehnen']),
                    ]),
                ]);
            }

            const taskUrl = (n.related_type === 'task' && rid) ? `${_base}/task/${rid}` : '';
            const actions = _el('div', { class: 'notif-actions' }, []);
            if (isUnread) {
                actions.appendChild(_el('button', {
                    class: 'notif-act-btn',
                    'data-notif-action': 'mark-read',
                    title: 'Als gelesen markieren',
                }, [_svg('<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" width="12" height="12"><polyline points="20 6 9 17 4 12"/></svg>')]));
            }
            actions.appendChild(_el('button', {
                class: 'notif-act-btn del',
                'data-notif-action': 'delete',
                title: 'Löschen',
            }, [_svg('<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="12" height="12"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14H6L5 6"/></svg>')]));

            return _el('div', {
                class: ('notif-item ' + readClass).trim(),
                'data-id': id,
                'data-task-url': taskUrl,
            }, [
                _el('div', { class: 'notif-item-body', 'data-notif-action': 'navigate' }, [
                    _el('div', { class: 'notif-item-title' }, [title]),
                    _el('div', { class: 'notif-item-msg'   }, [message]),
                    _el('div', { class: 'notif-item-time'  }, [_fmtTime(n.created_at)]),
                ]),
                actions,
            ]);
        });
        list.replaceChildren(...items);
    }

    // Event delegation: one listener per list, no inline handlers in innerHTML.
    function _bindNotifList(list) {
        if (!list || list.dataset.bound === '1') return;
        list.dataset.bound = '1';
        list.addEventListener('click', (e) => {
            const trigger = e.target.closest('[data-notif-action]');
            if (!trigger || !list.contains(trigger)) return;
            const item = trigger.closest('.notif-item');
            if (!item) return;
            const id  = parseInt(item.dataset.id) || 0;
            const action = trigger.dataset.notifAction;
            switch (action) {
                case 'mark-read':
                    e.stopPropagation();
                    notifMarkRead(id, trigger);
                    break;
                case 'delete':
                    e.stopPropagation();
                    notifDelete(id, trigger);
                    break;
                case 'navigate':
                    notifNavigate(id, trigger, item.dataset.taskUrl || null);
                    break;
                case 'invite-accept':
                    notifAcceptInvite(parseInt(item.dataset.inviteId) || 0, id, trigger);
                    break;
                case 'invite-decline':
                    notifDeclineInvite(parseInt(item.dataset.inviteId) || 0, id, trigger);
                    break;
            }
        });
    }

    // Poll every 60s
    loadNotifications();
    setInterval(loadNotifications, 60000);

    // ── Auto-dismiss alerts ───────────────────────────────────────
    document.querySelectorAll('.alert').forEach(el => {
        setTimeout(() => el.style.transition = 'opacity .4s', 4000);
        setTimeout(() => { el.style.opacity = '0'; setTimeout(() => el.remove(), 400); }, 5000);
    });

    // ── Confirm delete forms ──────────────────────────────────────
    document.querySelectorAll('form[data-confirm]').forEach(form => {
        form.addEventListener('submit', e => {
            if (!confirm(form.dataset.confirm)) e.preventDefault();
        });
    });

    // ── Modal close on Escape ─────────────────────────────────────
    document.addEventListener('keydown', e => {
        if (e.key === 'Escape') {
            document.querySelectorAll('.modal.open').forEach(m => m.classList.remove('open'));
            closeAllDropdowns();
        }
    });
});

// ── Modal helpers ─────────────────────────────────────────────
function openModal(id) {
    document.getElementById(id)?.classList.add('open');
    document.body.style.overflow = 'hidden';
}
function closeModal(id) {
    document.getElementById(id)?.classList.remove('open');
    document.body.style.overflow = '';
}

// ── Notification actions ──────────────────────────────────────
async function _notifPost(endpoint, body) {
    const base = typeof BASE_URL !== 'undefined' ? BASE_URL : '';
    const csrf = typeof CSRF_TOKEN !== 'undefined' ? CSRF_TOKEN : '';
    await fetch(base + '/api/notifications/' + endpoint, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ ...body, csrf_token: csrf }),
    });
}

function _notifDecrBadge(n = 1) {
    const badge = document.querySelector('.notif-badge');
    if (!badge) return;
    const next = Math.max(0, (parseInt(badge.textContent) || 0) - n);
    badge.textContent = next;
    badge.style.display = next > 0 ? '' : 'none';
}

async function notifMarkRead(id, btn) {
    await _notifPost('read', { id });
    const item = btn.closest('.notif-item');
    if (!item) return;
    item.classList.remove('unread');
    btn.remove();
    _notifDecrBadge(1);
}

async function notifDelete(id, btn) {
    await _notifPost('delete', { id });
    const item = btn.closest('.notif-item');
    const wasUnread = item?.classList.contains('unread');
    item?.remove();
    if (wasUnread) _notifDecrBadge(1);
    const list = document.getElementById('notifList');
    if (list && !list.querySelector('.notif-item')) {
        list.innerHTML = '<div class="notif-empty">Keine Benachrichtigungen</div>';
    }
}

async function notifNavigate(id, bodyEl, url) {
    const item = bodyEl.closest('.notif-item');
    if (item?.classList.contains('unread')) {
        await _notifPost('read', { id });
        item.classList.remove('unread');
        item.querySelector('.notif-act-btn:not(.del)')?.remove();
        _notifDecrBadge(1);
    }
    if (url) window.location.href = url;
}

async function notifClearAll() {
    await _notifPost('delete-all', {});
    const list = document.getElementById('notifList');
    if (list) list.innerHTML = '<div class="notif-empty">Keine Benachrichtigungen</div>';
    const badge = document.querySelector('.notif-badge');
    if (badge) badge.style.display = 'none';
}

async function notifAcceptInvite(invitationId, notifId, btn) {
    btn.disabled = true;
    const base = typeof BASE_URL !== 'undefined' ? BASE_URL : '';
    const csrf = typeof CSRF_TOKEN !== 'undefined' ? CSRF_TOKEN : '';
    const res = await fetch(base + '/api/invitations/accept', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ invitation_id: invitationId, csrf_token: csrf }),
    });
    const data = await res.json();
    if (data.success) {
        const item = btn.closest('.notif-item');
        if (item) { item.style.opacity = '0'; setTimeout(() => item.remove(), 300); }
        _notifDecrBadge(1);
        const list = document.getElementById('notifList');
        if (list && !list.querySelector('.notif-item')) {
            list.innerHTML = '<div class="notif-empty">Keine Benachrichtigungen</div>';
        }
    } else {
        btn.disabled = false;
        alert(data.error || 'Fehler beim Annehmen der Einladung.');
    }
}

async function notifDeclineInvite(invitationId, notifId, btn) {
    btn.disabled = true;
    const base = typeof BASE_URL !== 'undefined' ? BASE_URL : '';
    const csrf = typeof CSRF_TOKEN !== 'undefined' ? CSRF_TOKEN : '';
    const res = await fetch(base + '/api/invitations/decline', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ invitation_id: invitationId, csrf_token: csrf }),
    });
    const data = await res.json();
    if (data.success) {
        const item = btn.closest('.notif-item');
        if (item) { item.style.opacity = '0'; setTimeout(() => item.remove(), 300); }
        _notifDecrBadge(1);
        const list = document.getElementById('notifList');
        if (list && !list.querySelector('.notif-item')) {
            list.innerHTML = '<div class="notif-empty">Keine Benachrichtigungen</div>';
        }
    } else {
        btn.disabled = false;
        alert(data.error || 'Fehler beim Ablehnen der Einladung.');
    }
}

// ── HTML escape helper ────────────────────────────────────────
function escHtml(str) {
    const div = document.createElement('div');
    div.appendChild(document.createTextNode(String(str)));
    return div.innerHTML;
}

// ── Theme: expose API endpoint inline ─────────────────────────
// Add to ApiController via routing (handled there)

// ── Delegated event handlers (CSP migration: removes need for inline on*=) ──
// Pattern: <button data-click="funcName">…</button> → window.funcName(btn, event)
// Submit-confirm: <form data-confirm="Wirklich löschen?"> → blocks if user cancels
//
// Migration mapping:
//   onclick="foo()"                  → data-click="foo"
//   onchange="updateX(this.value)"   → data-change="updateX"  (handler reads el.value)
//   oninput="updateY(this)"          → data-input="updateY"
//   onsubmit="return confirm('m')"   → data-confirm="m"      (no JS function needed)
//   onclick="return confirm('m')"    → data-confirm-click="m"
//   onkeydown="navKey(event)"        → data-keydown="navKey"
//   onclick="event.stopPropagation()" → data-stop-propagation
//   onclick="location.href='/x'"      → data-href="/x"
//   onclick="this.parentElement.remove()" → data-remove-parent
//   onchange="this.form.submit()"     → data-auto-submit (on the input/select)
//   onclick="openModal('x')"          → data-modal-open="x"
//   onclick="closeModal('x')"         → data-modal-close="x"
//   onclick="if(event.target===this)closeX()" → data-modal-backdrop="closeX"
//   onclick="togglePassword('x')"     → data-pw-toggle="x"
//
// Handlers receive (element, event). Hover/focus/blur for cosmetic effects should
// be migrated to CSS pseudo-classes (:hover, :focus) rather than JS handlers.
(function () {
    function dispatch(eventType, attrName) {
        document.addEventListener(eventType, function (e) {
            const el = e.target.closest('[data-' + attrName + ']');
            if (!el) return;
            const fnName = el.dataset[attrName];
            const fn = window[fnName];
            if (typeof fn !== 'function') {
                console.warn('Plenvo: data-' + attrName + '="' + fnName + '" but window.' + fnName + ' is not a function');
                return;
            }
            fn(el, e);
        });
    }

    dispatch('click',   'click');
    dispatch('change',  'change');
    dispatch('input',   'input');
    dispatch('keydown', 'keydown');

    // Confirm-on-submit: cancel aborts. Capture-phase so it runs before bubbled handlers.
    document.addEventListener('submit', function (e) {
        const form = e.target.closest('form[data-confirm]');
        if (!form) return;
        if (!confirm(form.dataset.confirm)) e.preventDefault();
    }, true);

    // Confirm-on-click: capture-phase so cancel reliably suppresses the click.
    document.addEventListener('click', function (e) {
        const el = e.target.closest('[data-confirm-click]');
        if (!el) return;
        if (!confirm(el.dataset.confirmClick)) {
            e.preventDefault();
            e.stopPropagation();
            e.stopImmediatePropagation();
        }
    }, true);

    // Generic submit dispatcher.
    document.addEventListener('submit', function (e) {
        const form = e.target.closest('form[data-submit]');
        if (!form) return;
        const fnName = form.dataset.submit;
        const fn = window[fnName];
        if (typeof fn === 'function') fn(form, e);
    });

    // Stop propagation on click (replaces onclick="event.stopPropagation()").
    document.addEventListener('click', function (e) {
        if (e.target.closest('[data-stop-propagation]')) e.stopPropagation();
    });

    // Navigate on click (replaces onclick="location.href='/x'"). Doesn't fire
    // when the user clicks an inner <a>, <button>, <input>, <select>, <textarea>,
    // or any element that opts out via data-stop-propagation.
    document.addEventListener('click', function (e) {
        const el = e.target.closest('[data-href]');
        if (!el) return;
        if (e.target.closest('a,button,input,select,textarea,[data-stop-propagation]') !== el
            && e.target.closest('a,button,input,select,textarea,[data-stop-propagation]')) {
            return;
        }
        window.location.href = el.dataset.href;
    });

    // Remove parent (replaces onclick="this.parentElement.remove()").
    document.addEventListener('click', function (e) {
        const el = e.target.closest('[data-remove-parent]');
        if (!el) return;
        el.parentElement?.remove();
    });

    // Auto-submit on change (replaces onchange="this.form.submit()").
    document.addEventListener('change', function (e) {
        const el = e.target.closest('[data-auto-submit]');
        if (!el || !el.form) return;
        el.form.submit();
    });

    // Modal helpers (replace inline openModal/closeModal calls).
    document.addEventListener('click', function (e) {
        const opener = e.target.closest('[data-modal-open]');
        if (opener && typeof window.openModal === 'function') {
            window.openModal(opener.dataset.modalOpen);
            return;
        }
        const closer = e.target.closest('[data-modal-close]');
        if (closer && typeof window.closeModal === 'function') {
            window.closeModal(closer.dataset.modalClose);
            return;
        }
    });

    // Modal backdrop close: only when the click target is the backdrop itself,
    // not a descendant. Replaces onclick="if(event.target===this)closeFoo()".
    document.addEventListener('click', function (e) {
        if (!e.target.matches('[data-modal-backdrop]')) return;
        const fnName = e.target.dataset.modalBackdrop;
        const fn = window[fnName];
        if (typeof fn === 'function') fn();
    });

    // Password show/hide (replaces onclick="togglePassword('id')").
    document.addEventListener('click', function (e) {
        const el = e.target.closest('[data-pw-toggle]');
        if (!el) return;
        const input = document.getElementById(el.dataset.pwToggle);
        if (!input) return;
        input.type = input.type === 'password' ? 'text' : 'password';
    });

    // Select-on-focus (replaces onclick="this.select()"): focus instead of click
    // covers both keyboard navigation and pointer clicks, and avoids the
    // "click selects, then a second click deselects" UX bug.
    document.addEventListener('focus', function (e) {
        const el = e.target.closest && e.target.closest('[data-select-on-focus]');
        if (el && typeof el.select === 'function') el.select();
    }, true);
})();
