<?php
// Notification Rules — no-code builder (two-pane, filterable, theme-aware).
// Variables: $baseUrl, $assetsUrl, $project, $user, $navItems, $pageTitle,
//            $tables, $tableFields, $roles, $groups, $users, $rules,
//            $notifEnabled, $miscUrl
$slug = (string) ($project['slug'] ?? '');
?>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
    <title><?= htmlspecialchars($project['name'] ?? 'Project') ?> — Notifications</title>
    <?php include constant('BASE_PATH') . '/core/views/project-head.php'; ?>
    <link rel="stylesheet" href="<?= htmlspecialchars($assetsUrl) ?>/css/module-guide.css?v=<?= (int) @filemtime(constant('BASE_PATH') . '/public/assets/css/module-guide.css') ?>">
    <style>
        /* All colours come from the console theme tokens (--pp-*) so the module
           tracks light / dark / dracula / gruvbox exactly like the rest of the app. */
        :root {
            --nr-bd: var(--pp-border);
            --nr-bd2: var(--pp-border-strong);
            --nr-mut: var(--pp-text-muted);
            --nr-bg: var(--pp-app-bg);
            --nr-sf: var(--pp-surface);
            --nr-soft: var(--pp-surface-soft);
            --nr-muted: var(--pp-surface-muted);
            --nr-pri: var(--pp-accent);
            --nr-pri-soft: var(--pp-accent-soft);
            --nr-on: var(--pp-accent-contrast);
            --nr-txt: var(--pp-text);
            --nr-input: var(--pp-input-bg);
            --nr-shadow: var(--pp-shadow-color);
            --nr-nav: 60px;
        }
        body { background: var(--nr-bg); color: var(--nr-txt); }

        /* ── instant, theme-aware tooltip ── */
        [data-tip] { position: relative; }
        [data-tip]:hover::after, [data-tip]:focus-visible::after {
            content: attr(data-tip);
            position: absolute; top: calc(100% + 7px); left: 50%; transform: translateX(-50%);
            background: var(--pp-navbar-bg); color: #f4f6fb;
            font-size: .72rem; font-weight: 600; line-height: 1.35; text-align: center;
            white-space: normal; width: max-content; max-width: 230px;
            padding: 6px 10px; border-radius: 8px; z-index: 4000; pointer-events: none;
            box-shadow: 0 8px 22px var(--nr-shadow); border: 1px solid var(--nr-bd2);
            opacity: 0; animation: nrTip .09s ease-out forwards;
        }
        @keyframes nrTip { from { opacity: 0; transform: translateX(-50%) translateY(-3px); } to { opacity: 1; transform: translateX(-50%) translateY(0); } }
        @media (prefers-reduced-motion: reduce) { [data-tip]:hover::after, [data-tip]:focus-visible::after { animation: none; opacity: 1; } }

        /* ── two-pane shell ── */
        .nr-shell { display:flex; align-items:stretch; padding-top:var(--nr-nav); min-height:100vh; }
        .nr-side { flex:0 0 266px; width:266px; background:var(--nr-sf); border-right:1px solid var(--nr-bd);
            position:sticky; top:var(--nr-nav); height:calc(100vh - var(--nr-nav)); overflow-y:auto; padding:16px 12px; }
        .nr-main { flex:1; min-width:0; padding:20px 26px 70px; }
        .nr-inner { max-width:1000px; }

        /* ── sidebar ── */
        .nr-search { position:relative; margin-bottom:14px; }
        .nr-search input { width:100%; height:38px; border:1px solid var(--nr-bd); border-radius:10px; padding:0 12px 0 34px; font:inherit; font-size:.86rem; color:var(--nr-txt); background:var(--nr-input); }
        .nr-search input:focus { outline:none; border-color:var(--nr-pri); box-shadow:0 0 0 3px var(--nr-pri-soft); }
        .nr-search i { position:absolute; left:11px; top:50%; transform:translateY(-50%); color:var(--nr-mut); }
        .nr-facet { margin-bottom:15px; }
        .nr-facet-h { font-size:.66rem; font-weight:800; text-transform:uppercase; letter-spacing:.07em; color:var(--nr-mut); opacity:.85; padding:0 8px; margin:0 0 5px; }
        .nr-fitem { display:flex; align-items:center; gap:8px; width:100%; border:0; background:transparent; text-align:left;
            padding:7px 8px; border-radius:8px; cursor:pointer; color:var(--nr-txt); font-size:.855rem; font-weight:600; }
        .nr-fitem:hover { background:var(--nr-muted); }
        .nr-fitem.on { background:var(--nr-pri); color:var(--nr-on); }
        .nr-fitem .nr-lbl { min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
        .nr-fitem .nr-fc { margin-left:auto; font-size:.7rem; font-weight:800; background:var(--nr-muted); color:var(--nr-mut); border-radius:999px; padding:1px 8px; min-width:22px; text-align:center; }
        .nr-fitem.on .nr-fc { background:color-mix(in srgb, var(--nr-on) 26%, transparent); color:var(--nr-on); }
        .nr-dot { width:9px; height:9px; border-radius:50%; flex:0 0 auto; }
        .nr-fitem code { font-size:.8rem; background:transparent; color:inherit; }

        /* ── main header ── */
        .nr-head { display:flex; align-items:flex-start; justify-content:space-between; gap:16px; flex-wrap:wrap; margin-bottom:14px; }
        .nr-head h1 { font-size:1.4rem; font-weight:800; letter-spacing:-.02em; margin:0; color:var(--nr-txt); }
        .nr-head p { margin:.3rem 0 0; color:var(--nr-mut); font-size:.86rem; max-width:64ch; }
        .nr-actions { display:flex; gap:8px; flex-wrap:wrap; }
        .nr-btn { display:inline-flex; align-items:center; gap:7px; height:38px; padding:0 15px; border:1px solid var(--nr-bd);
            border-radius:10px; background:var(--nr-sf); color:var(--nr-txt); font-weight:650; font-size:.86rem; cursor:pointer; transition:.12s; }
        .nr-btn:hover { border-color:var(--nr-pri); color:var(--nr-pri); }
        .nr-btn.pri { background:var(--nr-pri); border-color:var(--nr-pri); color:var(--nr-on); box-shadow:0 5px 16px -4px var(--nr-shadow); }
        .nr-btn.pri:hover { color:var(--nr-on); filter:brightness(1.05); }

        /* ── template strip ── */
        .nr-tpl { display:flex; gap:8px; flex-wrap:wrap; margin-bottom:14px; }
        .nr-tpl button { display:inline-flex; align-items:center; gap:7px; border:1px dashed var(--nr-bd); border-radius:10px; background:var(--nr-sf);
            padding:8px 12px; cursor:pointer; transition:.14s; font-size:.82rem; font-weight:600; color:var(--nr-txt); }
        .nr-tpl button:hover { border-color:var(--nr-pri); border-style:solid; color:var(--nr-pri); }
        .nr-tpl i { color:var(--nr-pri); }

        /* ── results bar ── */
        .nr-resbar { display:flex; align-items:center; gap:10px; flex-wrap:wrap; margin-bottom:12px; min-height:26px; }
        .nr-count { font-size:.82rem; color:var(--nr-mut); font-weight:650; }
        .nr-chip { display:inline-flex; align-items:center; gap:6px; font-size:.76rem; font-weight:700; background:var(--nr-pri-soft); color:var(--nr-pri);
            border:1px solid var(--nr-bd); border-radius:999px; padding:3px 6px 3px 10px; }
        .nr-chip code { background:transparent; color:inherit; }
        .nr-chip button { border:0; background:transparent; color:inherit; cursor:pointer; display:grid; place-items:center; width:16px; height:16px; border-radius:50%; }
        .nr-chip button:hover { background:color-mix(in srgb, var(--nr-pri) 22%, transparent); }

        /* ── rule groups + rows ── */
        .nr-grp-h { display:flex; align-items:center; gap:8px; margin:16px 0 8px; font-size:.72rem; font-weight:800; text-transform:uppercase; letter-spacing:.05em; color:var(--nr-mut); }
        .nr-grp-h:first-child { margin-top:0; }
        .nr-grp-h code { font-size:.78rem; background:var(--nr-muted); border-radius:5px; padding:1px 7px; color:var(--nr-txt); letter-spacing:0; text-transform:none; font-weight:700; }
        .nr-grp-h .nr-gc { background:var(--nr-muted); color:var(--nr-mut); border-radius:999px; padding:0 8px; font-size:.68rem; }
        .nr-list { display:flex; flex-direction:column; gap:9px; }
        .nr-card { display:flex; align-items:center; gap:13px; background:var(--nr-sf); border:1px solid var(--nr-bd); border-radius:12px; padding:11px 14px; transition:.12s; }
        .nr-card:hover { border-color:var(--nr-bd2); box-shadow:0 4px 14px -8px var(--nr-shadow); }
        .nr-card.off { opacity:.55; }
        .nr-ic { flex:0 0 38px; width:38px; height:38px; border-radius:10px; display:grid; place-items:center; font-size:1.05rem; }
        .nr-b { min-width:0; flex:1; }
        .nr-b .t { font-weight:750; color:var(--nr-txt); font-size:.92rem; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
        .nr-b .d { font-size:.79rem; color:var(--nr-mut); margin-top:2px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
        .nr-b .d code { background:var(--nr-muted); border-radius:5px; padding:1px 6px; font-size:.75rem; color:var(--nr-txt); }
        .nr-badge { font-size:.66rem; font-weight:800; text-transform:uppercase; letter-spacing:.04em; padding:3px 9px; border-radius:999px; white-space:nowrap; }
        .nr-sw { position:relative; width:40px; height:22px; flex:0 0 auto; }
        .nr-sw input { position:absolute; opacity:0; width:100%; height:100%; margin:0; cursor:pointer; }
        .nr-sw span { position:absolute; inset:0; border-radius:999px; background:var(--nr-bd2); transition:.15s; }
        .nr-sw span::after { content:""; position:absolute; top:2px; left:2px; width:18px; height:18px; border-radius:50%; background:var(--nr-sf); transition:.15s; box-shadow:0 1px 3px var(--nr-shadow); }
        .nr-sw input:checked + span { background:var(--nr-pri); }
        .nr-sw input:checked + span::after { transform:translateX(18px); }
        .nr-mini { border:0; background:transparent; color:var(--nr-mut); cursor:pointer; width:32px; height:32px; border-radius:8px; }
        .nr-mini:hover { background:var(--nr-muted); color:var(--nr-pri); }
        .nr-mini.del:hover { color:#e5484d; }
        .nr-empty { text-align:center; padding:48px 20px; color:var(--nr-mut); background:var(--nr-sf); border:1px dashed var(--nr-bd); border-radius:16px; }
        .nr-empty i { font-size:2rem; display:block; margin-bottom:10px; opacity:.4; }

        /* ── disabled hero (Notifications OFF in Misc) ── */
        .nr-off-wrap { max-width:640px; margin:0 auto; padding:calc(var(--nr-nav) + 40px) 22px 60px; text-align:center; }
        .nr-off-card { background:var(--nr-sf); border:1px solid var(--nr-bd); border-radius:20px; padding:44px 36px; box-shadow:0 20px 60px -30px var(--nr-shadow); }
        .nr-off-ic { width:70px; height:70px; margin:0 auto 18px; border-radius:18px; display:grid; place-items:center; font-size:2rem; color:var(--nr-mut); background:var(--nr-muted); }
        .nr-off-card h1 { font-size:1.5rem; font-weight:800; color:var(--nr-txt); margin:0 0 8px; letter-spacing:-.02em; }
        .nr-off-card p { color:var(--nr-mut); font-size:.94rem; line-height:1.6; margin:0 auto 22px; max-width:46ch; }
        .nr-off-steps { text-align:left; max-width:400px; margin:0 auto 24px; display:flex; flex-direction:column; gap:9px; }
        .nr-off-step { display:flex; gap:11px; align-items:flex-start; font-size:.86rem; color:var(--nr-txt); }
        .nr-off-step b { flex:0 0 22px; width:22px; height:22px; border-radius:50%; background:var(--nr-pri-soft); color:var(--nr-pri); display:grid; place-items:center; font-size:.76rem; font-weight:800; }
        /* color !important: the console's dark-theme link rule
           (html[data-pp-theme="dark"] a:not(.btn)…) is higher-specificity and would
           paint this button's label with the accent — invisible on the accent bg. */
        .nr-off-cta { display:inline-flex; align-items:center; gap:9px; height:46px; padding:0 24px; border-radius:12px; background:var(--nr-pri); color:var(--nr-on) !important; font-weight:700; font-size:.95rem; text-decoration:none; box-shadow:0 8px 22px -8px var(--nr-shadow); transition:.14s; }
        .nr-off-cta:hover { color:var(--nr-on) !important; filter:brightness(1.06); transform:translateY(-1px); }
        .nr-off-guide { display:block; margin-top:16px; font-size:.84rem; color:var(--nr-mut); background:none; border:0; cursor:pointer; }
        .nr-off-guide:hover { color:var(--nr-pri); }

        /* editor slide-over */
        .nr-ov { position:fixed; inset:0; z-index:1080; background:color-mix(in srgb, var(--pp-navbar-bg) 55%, transparent); backdrop-filter:blur(3px); display:none; }
        .nr-ov.open { display:block; }
        .nr-panel { position:absolute; top:0; right:0; bottom:0; width:min(480px,100%); background:var(--nr-sf); box-shadow:-16px 0 50px var(--nr-shadow);
            display:flex; flex-direction:column; transform:translateX(20px); opacity:0; transition:.22s; }
        .nr-ov.open .nr-panel { transform:none; opacity:1; }
        .nr-panel-h { display:flex; align-items:center; justify-content:space-between; padding:16px 20px; border-bottom:1px solid var(--nr-bd); }
        .nr-panel-h b { font-size:1.05rem; color:var(--nr-txt); }
        .nr-panel-b { flex:1; overflow-y:auto; padding:18px 20px; display:flex; flex-direction:column; gap:15px; }
        .nr-panel-f { display:flex; gap:8px; justify-content:flex-end; padding:14px 20px; border-top:1px solid var(--nr-bd); background:var(--nr-soft); }
        .nr-fld label { display:block; font-size:.72rem; font-weight:800; text-transform:uppercase; letter-spacing:.05em; color:var(--nr-mut); margin-bottom:5px; }
        .nr-in { width:100%; height:40px; border:1px solid var(--nr-bd); border-radius:10px; padding:0 12px; font:inherit; font-size:.9rem; color:var(--nr-txt); background:var(--nr-input); }
        textarea.nr-in { height:auto; min-height:64px; padding:9px 12px; resize:vertical; }
        select.nr-in option { color:var(--nr-txt); background:var(--nr-sf); }
        .nr-in:focus { outline:none; border-color:var(--nr-pri); box-shadow:0 0 0 3px var(--nr-pri-soft); }
        .nr-seg { display:flex; gap:4px; background:var(--nr-muted); border-radius:10px; padding:3px; }
        .nr-seg button { flex:1; height:32px; border:0; background:transparent; border-radius:7px; font:inherit; font-size:.82rem; font-weight:650; color:var(--nr-mut); cursor:pointer; }
        .nr-seg button.on { background:var(--nr-sf); color:var(--nr-pri); box-shadow:0 1px 3px var(--nr-shadow); }
        .nr-chips { display:flex; flex-wrap:wrap; gap:5px; margin-top:6px; }
        .nr-chips button { font-size:.72rem; border:1px solid var(--nr-bd); background:var(--nr-soft); color:var(--nr-mut); border-radius:6px; padding:2px 7px; cursor:pointer; }
        .nr-chips button:hover { border-color:var(--nr-pri); color:var(--nr-pri); }
        .nr-hint { font-size:.75rem; color:var(--nr-mut); }
        .nr-hint code { background:var(--nr-muted); color:var(--nr-txt); border-radius:4px; padding:0 4px; }

        @media (max-width:820px) {
            .nr-shell { flex-direction:column; }
            .nr-side { position:static; width:auto; flex:none; height:auto; border-right:0; border-bottom:1px solid var(--nr-bd); }
        }
    </style>
</head>
<body>
    <?php include constant('BASE_PATH') . '/core/views/project-navbar.php'; ?>

<?php if (!$notifEnabled): ?>
    <!-- ═══ Notifications are OFF in Misc → Advanced ═══ -->
    <div class="nr-off-wrap">
        <div class="nr-off-card">
            <div class="nr-off-ic"><i class="bi bi-bell-slash"></i></div>
            <h1>Notifications are turned off</h1>
            <p>This module builds no-code notification rules — but notifications must be enabled for the project first. Turn them on in the <strong>Misc</strong> module, then come back here to create rules.</p>
            <div class="nr-off-steps">
                <div class="nr-off-step"><b>1</b><div>Open <strong>Misc → Advanced</strong>.</div></div>
                <div class="nr-off-step"><b>2</b><div>Tick <strong>Enable Notifications</strong> and confirm the table name.</div></div>
                <div class="nr-off-step"><b>3</b><div>Return here to add rules, then <strong>Build</strong> the project.</div></div>
            </div>
            <a class="nr-off-cta" href="<?= htmlspecialchars($miscUrl) ?>" data-tip="Jump to Misc → Advanced to switch notifications on"><i class="bi bi-gear-fill"></i> Enable in Misc → Advanced</a>
            <button type="button" class="nr-off-guide" data-bs-toggle="modal" data-bs-target="#notifGuideModal" data-tip="Open the full Notifications guide"><i class="bi bi-question-circle"></i> Read the Notifications guide</button>
        </div>
    </div>
<?php else: ?>
    <!-- ═══ two-pane rule builder ═══ -->
    <div class="nr-shell">
        <aside class="nr-side">
            <div class="nr-search">
                <i class="bi bi-search"></i>
                <input type="search" id="nrQ" placeholder="Search rules…" autocomplete="off" data-tip="Filter rules by name, title, message or table">
            </div>
            <div id="nrSide"><!-- facets rendered by JS --></div>
        </aside>

        <main class="nr-main">
            <div class="nr-inner">
                <div class="nr-head">
                    <div>
                        <h1><i class="bi bi-bell-fill" style="color:var(--nr-pri)"></i> Notification Rules</h1>
                        <p>Send an in-app notification when a record is added, edited or deleted — no code. On <b>Build</b>, each enabled rule is compiled into the table's events.</p>
                    </div>
                    <div class="nr-actions">
                        <button type="button" class="nr-btn" data-bs-toggle="modal" data-bs-target="#notifGuideModal" data-tip="Open the full Notifications guide with examples"><i class="bi bi-question-circle"></i> Guide</button>
                        <button class="nr-btn" id="nrAdd" data-tip="Create a new notification rule"><i class="bi bi-plus-lg"></i> Add rule</button>
                        <button class="nr-btn pri" id="nrSave" data-tip="Save all rules — then Build the project to apply them"><i class="bi bi-save"></i> Save</button>
                    </div>
                </div>

                <div class="nr-tpl" id="nrTpl">
                    <button data-tpl="admin-add" data-tip="Template: when a record is added, notify everyone with the admin role"><i class="bi bi-magic"></i> Notify admins on new record</button>
                    <button data-tpl="owner-edit" data-tip="Template: when a record is edited, notify the record's owner"><i class="bi bi-magic"></i> Tell the owner on change</button>
                    <button data-tpl="all-add" data-tip="Template: when a record is added, broadcast to everyone"><i class="bi bi-magic"></i> Announce new records</button>
                </div>

                <div class="nr-resbar">
                    <span class="nr-count" id="nrCount"></span>
                    <span id="nrChipsActive" style="display:flex;gap:6px;flex-wrap:wrap"></span>
                </div>

                <div id="nrList"></div>
            </div>
        </main>
    </div>

    <!-- editor slide-over -->
    <div class="nr-ov" id="nrOv">
        <div class="nr-panel">
            <div class="nr-panel-h"><b id="nrPanelTitle">New rule</b><button class="nr-mini" id="nrClose" data-tip="Close without saving"><i class="bi bi-x-lg"></i></button></div>
            <div class="nr-panel-b">
                <div class="nr-fld"><label>Rule name (optional)</label><input class="nr-in" id="nrName" placeholder="e.g. New order alert"></div>
                <div class="nr-fld"><label>When a record is…</label>
                    <div class="nr-seg" id="nrTrigger">
                        <button data-v="add" class="on">Added</button><button data-v="edit">Edited</button><button data-v="delete">Deleted</button>
                    </div>
                </div>
                <div class="nr-fld"><label>In table</label><select class="nr-in" id="nrTable"></select></div>
                <div class="nr-fld"><label>Notify</label>
                    <select class="nr-in" id="nrAud">
                        <option value="everyone">Everyone</option>
                        <option value="role">A role</option>
                        <option value="group">A group</option>
                        <option value="users">Specific users</option>
                        <option value="owner">The record's owner</option>
                        <option value="actor">The person who did it</option>
                    </select>
                </div>
                <div class="nr-fld" id="nrValWrap" style="display:none"><label id="nrValLabel">Value</label><div id="nrValCtl"></div></div>
                <div class="nr-fld"><label>Severity</label>
                    <div class="nr-seg" id="nrType">
                        <button data-v="info" class="on">Info</button><button data-v="success">Success</button><button data-v="warning">Warning</button><button data-v="error">Error</button>
                    </div>
                </div>
                <div class="nr-fld"><label>Title</label><input class="nr-in" id="nrTitle" placeholder="e.g. New record"><div class="nr-chips" id="nrChipsT"></div></div>
                <div class="nr-fld"><label>Message</label><textarea class="nr-in" id="nrMsg" placeholder="e.g. A new record was added"></textarea><div class="nr-chips" id="nrChipsM"></div><div class="nr-hint" style="margin-top:5px">Insert a field with <code>{field}</code> — e.g. <code>{name}</code>. <code>{id}</code> = the record id.</div></div>
                <div class="nr-fld"><label>Link</label>
                    <select class="nr-in" id="nrLink">
                        <option value="record">Open the record (view page)</option>
                        <option value="none">No link</option>
                        <option value="custom">Custom URL…</option>
                    </select>
                    <input class="nr-in" id="nrCustom" placeholder="/path?id={id}" style="display:none;margin-top:8px">
                </div>
            </div>
            <div class="nr-panel-f"><button class="nr-btn" id="nrCancel">Cancel</button><button class="nr-btn pri" id="nrApply"><i class="bi bi-check-lg"></i> Add rule</button></div>
        </div>
    </div>
<?php endif; ?>

    <?php include __DIR__ . '/partials/guide.php'; ?>

    <?php include constant('BASE_PATH') . '/core/views/project-scripts.php'; ?>
    <script src="<?= htmlspecialchars($assetsUrl) ?>/js/module-guide.js"></script>
<?php if ($notifEnabled): ?>
    <script>window.PPN = <?= json_encode([
        'saveUrl' => rtrim((string) $baseUrl, '/') . '/project/' . $slug . '/notifications/save',
        'tables'  => array_map(static fn($t) => $t['name'], $tables),
        'fields'  => $tableFields,
        'roles'   => $roles,
        'groups'  => $groups,
        'users'   => $users,
        'rules'   => $rules,
    ], JSON_UNESCAPED_SLASHES); ?>;</script>
    <script>
    (function () {
        var P = window.PPN || {};
        var rules = Array.isArray(P.rules) ? P.rules.slice() : [];
        var editIdx = -1, dirty = false;
        var filters = { q: '', trigger: '', ntype: '', status: '', table: '' };
        var $ = function (id) { return document.getElementById(id); };
        var esc = function (s) { return String(s == null ? '' : s).replace(/[&<>"]/g, function (c) { return ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' })[c]; }); };
        // Semantic severity hues stay constant across themes; their tinted
        // backgrounds mix with the current surface so they read in light & dark.
        var TYPE = { info: { c: '#3b82f6', ic: 'info-circle', l: 'Info' }, success: { c: '#22c55e', ic: 'check-circle', l: 'Success' }, warning: { c: '#f59e0b', ic: 'exclamation-triangle', l: 'Warning' }, error: { c: '#ef4444', ic: 'x-circle', l: 'Error' } };
        var TRIG = { add: 'added', edit: 'edited', delete: 'deleted' };
        var TRIGLBL = { add: 'Added', edit: 'Edited', delete: 'Deleted' };
        var tint = function (c) { return 'color-mix(in srgb,' + c + ' 16%,var(--pp-surface))'; };

        function audienceLabel(a) {
            a = a || {}; var t = a.type, v = a.value;
            if (t === 'everyone') return 'everyone';
            if (t === 'actor') return 'the person who did it';
            if (t === 'owner') return 'the owner (' + esc(v || '?') + ')';
            if (t === 'role') return 'role “' + esc(v || '?') + '”';
            if (t === 'group') { var g = (P.groups || []).filter(function (x) { return x.id == v; })[0]; return 'group “' + esc(g ? g.name : v) + '”'; }
            if (t === 'users') { var n = (Array.isArray(v) ? v : []).length; return n + ' user' + (n === 1 ? '' : 's'); }
            return '—';
        }

        // ── filtering ──
        function statusOf(r) { return r.enabled === false ? 'disabled' : 'enabled'; }
        function matches(r, skip) {
            if (skip !== 'q' && filters.q) {
                var hay = ((r.name || '') + ' ' + (r.title || '') + ' ' + (r.message || '') + ' ' + (r.table || '')).toLowerCase();
                if (hay.indexOf(filters.q.toLowerCase()) < 0) return false;
            }
            if (skip !== 'trigger' && filters.trigger && r.trigger !== filters.trigger) return false;
            if (skip !== 'ntype' && filters.ntype && (r.ntype || 'info') !== filters.ntype) return false;
            if (skip !== 'status' && filters.status && statusOf(r) !== filters.status) return false;
            if (skip !== 'table' && filters.table && (r.table || '') !== filters.table) return false;
            return true;
        }
        function facetCount(cat, val) {
            return rules.filter(function (r) {
                if (!matches(r, cat)) return false;
                if (cat === 'trigger') return r.trigger === val;
                if (cat === 'ntype') return (r.ntype || 'info') === val;
                if (cat === 'status') return statusOf(r) === val;
                if (cat === 'table') return (r.table || '') === val;
                return true;
            }).length;
        }

        function facetItem(cat, val, label, extra, tip) {
            var on = filters[cat] === val;
            return '<button class="nr-fitem' + (on ? ' on' : '') + '" data-f="' + cat + '" data-v="' + esc(val) + '"' + (tip ? ' data-tip="' + esc(tip) + '"' : '') + '>' +
                (extra || '') + '<span class="nr-lbl">' + label + '</span><span class="nr-fc">' + facetCount(cat, val) + '</span></button>';
        }

        function renderSide() {
            var host = $('nrSide'); if (!host) return;
            var allOn = !filters.trigger && !filters.ntype && !filters.status && !filters.table;
            var html = '<button class="nr-fitem' + (allOn ? ' on' : '') + '" data-f="all" data-v="" data-tip="Show every rule and clear all filters"><span class="nr-lbl">All rules</span><span class="nr-fc">' + rules.length + '</span></button>';

            html += '<div class="nr-facet"><p class="nr-facet-h">Trigger</p>' +
                ['add', 'edit', 'delete'].map(function (t) { return facetItem('trigger', t, TRIGLBL[t], '', 'Only rules that fire when a record is ' + TRIG[t]); }).join('') + '</div>';

            html += '<div class="nr-facet"><p class="nr-facet-h">Severity</p>' +
                ['info', 'success', 'warning', 'error'].map(function (t) {
                    return facetItem('ntype', t, TYPE[t].l, '<span class="nr-dot" style="background:' + TYPE[t].c + '"></span>', 'Only ' + TYPE[t].l.toLowerCase() + '-severity rules');
                }).join('') + '</div>';

            html += '<div class="nr-facet"><p class="nr-facet-h">Status</p>' +
                facetItem('status', 'enabled', 'Enabled', '', 'Only rules that are turned on') +
                facetItem('status', 'disabled', 'Disabled', '', 'Only rules that are turned off') + '</div>';

            // Tables — only those that have rules, sorted, with counts.
            var tcount = {};
            rules.forEach(function (r) { var t = r.table || '—'; tcount[t] = (tcount[t] || 0) + 1; });
            var tbls = Object.keys(tcount).sort();
            if (tbls.length) {
                html += '<div class="nr-facet"><p class="nr-facet-h">Tables</p>' +
                    tbls.map(function (t) { return facetItem('table', t, '<code>' + esc(t) + '</code>', '', 'Only rules on the ' + t + ' table'); }).join('') + '</div>';
            }
            host.innerHTML = html;
        }

        function activeChips() {
            var host = $('nrChipsActive'); if (!host) return;
            var chips = [];
            var add = function (cat, txt) { chips.push('<span class="nr-chip">' + txt + '<button data-clear="' + cat + '" data-tip="Remove this filter" title="Clear">&times;</button></span>'); };
            if (filters.q) add('q', 'search: “' + esc(filters.q) + '”');
            if (filters.trigger) add('trigger', TRIGLBL[filters.trigger]);
            if (filters.ntype) add('ntype', TYPE[filters.ntype].l);
            if (filters.status) add('status', filters.status.charAt(0).toUpperCase() + filters.status.slice(1));
            if (filters.table) add('table', '<code>' + esc(filters.table) + '</code>');
            host.innerHTML = chips.join('');
        }

        function rowHtml(r, i) {
            var ty = TYPE[r.ntype] || TYPE.info;
            return '<div class="nr-card' + (r.enabled === false ? ' off' : '') + '">' +
                '<span class="nr-ic" style="color:' + ty.c + ';background:' + tint(ty.c) + '"><i class="bi bi-' + ty.ic + '"></i></span>' +
                '<div class="nr-b"><div class="t">' + esc(r.name || (r.title || 'Notification')) + '</div>' +
                '<div class="d">When <b>' + (TRIG[r.trigger] || r.trigger) + '</b> → notify <b>' + audienceLabel(r.audience) + '</b></div></div>' +
                '<span class="nr-badge" style="color:' + ty.c + ';background:' + tint(ty.c) + '">' + ty.l + '</span>' +
                '<label class="nr-sw" data-tip="Enable or disable this rule"><input type="checkbox"' + (r.enabled === false ? '' : ' checked') + ' data-tog="' + i + '"><span></span></label>' +
                '<button class="nr-mini" data-edit="' + i + '" data-tip="Edit this rule"><i class="bi bi-pencil"></i></button>' +
                '<button class="nr-mini del" data-del="' + i + '" data-tip="Delete this rule"><i class="bi bi-trash"></i></button>' +
                '</div>';
        }

        function render() {
            renderSide(); activeChips();
            var host = $('nrList'); if (!host) return;
            var count = $('nrCount');

            if (!rules.length) {
                if (count) count.textContent = '';
                host.innerHTML = '<div class="nr-empty"><i class="bi bi-bell-slash"></i>No rules yet. Add one, or use a one-click template above.</div>';
                return;
            }
            // keep original indices so toggle/edit/delete map back to rules[]
            var visible = rules.map(function (r, i) { return { r: r, i: i }; }).filter(function (o) { return matches(o.r); });
            if (count) count.textContent = visible.length + ' of ' + rules.length + ' rule' + (rules.length === 1 ? '' : 's');

            if (!visible.length) {
                host.innerHTML = '<div class="nr-empty"><i class="bi bi-search"></i>No rules match these filters.</div>';
                return;
            }
            // group by table
            var groups = {};
            visible.forEach(function (o) { var t = o.r.table || '—'; (groups[t] = groups[t] || []).push(o); });
            host.innerHTML = Object.keys(groups).sort().map(function (t) {
                var rowsHtml = groups[t].map(function (o) { return rowHtml(o.r, o.i); }).join('');
                return '<div class="nr-grp-h"><code>' + esc(t) + '</code><span class="nr-gc">' + groups[t].length + '</span></div>' +
                    '<div class="nr-list">' + rowsHtml + '</div>';
            }).join('');
        }

        function seg(id, val) { [].forEach.call($(id).querySelectorAll('button'), function (b) { b.classList.toggle('on', b.getAttribute('data-v') === val); }); }
        function segVal(id) { var b = $(id).querySelector('button.on'); return b ? b.getAttribute('data-v') : ''; }

        function fieldChips(table) {
            var fs = (P.fields || {})[table] || [];
            var mk = function (host, target) {
                $(host).innerHTML = fs.map(function (f) { return '<button type="button" data-ins="' + esc(f) + '" data-t="' + target + '" data-tip="Insert this field&#39;s value">{' + esc(f) + '}</button>'; }).join('') +
                    '<button type="button" data-ins="id" data-t="' + target + '" data-tip="Insert the record id">{id}</button>';
            };
            mk('nrChipsT', 'nrTitle'); mk('nrChipsM', 'nrMsg');
        }

        function audienceCtl() {
            var t = $('nrAud').value, wrap = $('nrValWrap'), ctl = $('nrValCtl'), lbl = $('nrValLabel');
            var tbl = $('nrTable').value;
            if (t === 'everyone' || t === 'actor') { wrap.style.display = 'none'; ctl.innerHTML = ''; return; }
            wrap.style.display = '';
            if (t === 'role') { lbl.textContent = 'Role'; ctl.innerHTML = '<select class="nr-in" id="nrVal">' + (P.roles || []).map(function (r) { return '<option>' + esc(r) + '</option>'; }).join('') + '</select>'; }
            else if (t === 'group') { lbl.textContent = 'Group'; ctl.innerHTML = '<select class="nr-in" id="nrVal">' + (P.groups || []).map(function (g) { return '<option value="' + g.id + '">' + esc(g.name) + '</option>'; }).join('') + '</select>'; }
            else if (t === 'owner') { lbl.textContent = 'Owner field (a user-id column)'; var fs = (P.fields || {})[tbl] || []; ctl.innerHTML = '<select class="nr-in" id="nrVal">' + fs.map(function (f) { return '<option' + (/user|owner|created_by|assigned/i.test(f) ? ' selected' : '') + '>' + esc(f) + '</option>'; }).join('') + '</select>'; }
            else if (t === 'users') { lbl.textContent = 'Users (pick one or more)'; ctl.innerHTML = '<select class="nr-in" id="nrVal" multiple size="6" style="height:auto">' + (P.users || []).map(function (u) { return '<option value="' + u.id + '">' + esc(u.name) + '</option>'; }).join('') + '</select>'; }
        }

        function openEditor(idx, preset) {
            editIdx = (typeof idx === 'number') ? idx : -1;
            var r = preset || (editIdx > -1 ? rules[editIdx] : { trigger: 'add', table: (filters.table || P.tables[0] || ''), audience: { type: 'everyone', value: '' }, ntype: 'info', link: 'record', title: '', message: '', name: '' });
            $('nrPanelTitle').textContent = editIdx > -1 ? 'Edit rule' : 'New rule';
            $('nrApply').innerHTML = '<i class="bi bi-check-lg"></i> ' + (editIdx > -1 ? 'Save rule' : 'Add rule');
            $('nrTable').innerHTML = (P.tables || []).map(function (t) { return '<option' + (t === r.table ? ' selected' : '') + '>' + esc(t) + '</option>'; }).join('');
            $('nrName').value = r.name || '';
            seg('nrTrigger', r.trigger || 'add'); seg('nrType', r.ntype || 'info');
            $('nrAud').value = (r.audience && r.audience.type) || 'everyone';
            $('nrTitle').value = r.title || ''; $('nrMsg').value = r.message || '';
            $('nrLink').value = r.link || 'record'; $('nrCustom').value = r.custom || '';
            $('nrCustom').style.display = r.link === 'custom' ? '' : 'none';
            audienceCtl(); fieldChips(r.table || P.tables[0] || '');
            var v = r.audience && r.audience.value;
            var vc = $('nrVal');
            if (vc) { if (vc.multiple && Array.isArray(v)) { [].forEach.call(vc.options, function (o) { o.selected = v.map(String).indexOf(o.value) > -1; }); } else if (v != null) { vc.value = v; } }
            $('nrOv').classList.add('open');
        }
        function closeEditor() { $('nrOv').classList.remove('open'); }

        function collect() {
            var t = $('nrAud').value, vc = $('nrVal'), val = '';
            if (vc) { if (vc.multiple) { val = [].filter.call(vc.options, function (o) { return o.selected; }).map(function (o) { return parseInt(o.value, 10); }); } else { val = vc.value; } }
            return {
                id: (editIdx > -1 && rules[editIdx] ? rules[editIdx].id : 'nr_' + Date.now().toString(36)),
                name: $('nrName').value.trim(),
                table: $('nrTable').value,
                trigger: segVal('nrTrigger'),
                enabled: (editIdx > -1 && rules[editIdx]) ? rules[editIdx].enabled !== false : true,
                audience: { type: t, value: val },
                ntype: segVal('nrType'),
                title: $('nrTitle').value.trim(),
                message: $('nrMsg').value.trim(),
                link: $('nrLink').value,
                custom: $('nrCustom').value.trim()
            };
        }

        var TEMPLATES = {
            'admin-add': { trigger: 'add', audience: { type: 'role', value: (P.roles || []).filter(function (r) { return /admin/i.test(r); })[0] || (P.roles || [])[0] || 'admin' }, ntype: 'success', title: 'New record', message: 'A new record was added.', link: 'record', name: 'Notify admins on new record' },
            'owner-edit': { trigger: 'edit', audience: { type: 'owner', value: '' }, ntype: 'info', title: 'Record updated', message: 'Your record was updated.', link: 'record', name: 'Tell the owner on change' },
            'all-add': { trigger: 'add', audience: { type: 'everyone', value: '' }, ntype: 'info', title: 'New record', message: 'A new record was added.', link: 'record', name: 'Announce new records' }
        };

        function toast(m) { var t = document.createElement('div'); t.textContent = m; t.style.cssText = 'position:fixed;left:50%;bottom:22px;transform:translateX(-50%);background:var(--pp-navbar-bg);color:#f4f6fb;padding:9px 16px;border-radius:10px;font-size:.85rem;z-index:2000;box-shadow:0 12px 30px var(--pp-shadow-color)'; document.body.appendChild(t); setTimeout(function () { t.remove(); }, 2200); }

        function save() {
            fetch(P.saveUrl, { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ rules: rules }) })
                .then(function (r) { return r.json(); }).then(function (j) { if (j && j.success) { dirty = false; toast('Saved ' + j.count + ' rule' + (j.count === 1 ? '' : 's') + ' — Build to apply.'); } else { toast('Could not save.'); } })
                .catch(function () { toast('Network error.'); });
        }

        // ── wiring ──
        document.addEventListener('click', function (e) {
            var b;
            if ((b = e.target.closest('[data-f]'))) {
                var cat = b.getAttribute('data-f'), val = b.getAttribute('data-v');
                if (cat === 'all') { filters.trigger = filters.ntype = filters.status = filters.table = ''; }
                else { filters[cat] = (filters[cat] === val) ? '' : val; }
                render();
            }
            else if ((b = e.target.closest('[data-clear]'))) { var c = b.getAttribute('data-clear'); filters[c] = ''; if (c === 'q') $('nrQ').value = ''; render(); }
            else if ((b = e.target.closest('[data-edit]'))) openEditor(parseInt(b.getAttribute('data-edit'), 10));
            else if ((b = e.target.closest('[data-del]'))) { rules.splice(parseInt(b.getAttribute('data-del'), 10), 1); dirty = true; render(); }
            else if ((b = e.target.closest('[data-tpl]'))) openEditor(-1, JSON.parse(JSON.stringify(TEMPLATES[b.getAttribute('data-tpl')])));
            else if (e.target.closest('#nrAdd')) openEditor(-1);
            else if (e.target.closest('#nrSave')) save();
            else if (e.target.closest('#nrClose') || e.target.closest('#nrCancel') || e.target === $('nrOv')) closeEditor();
            else if (e.target.closest('#nrApply')) { var r = collect(); if (!r.message) { toast('Add a message.'); return; } if (editIdx > -1) rules[editIdx] = r; else rules.push(r); dirty = true; render(); closeEditor(); }
            else if ((b = e.target.closest('[data-ins]'))) { var tg = $(b.getAttribute('data-t')); var tok = '{' + b.getAttribute('data-ins') + '}'; tg.value = (tg.value + (tg.value && !/\s$/.test(tg.value) ? ' ' : '') + tok); tg.focus(); }
            else if ((b = e.target.closest('.nr-seg button'))) { seg(b.parentNode.id, b.getAttribute('data-v')); }
        });
        document.addEventListener('change', function (e) {
            if (e.target.id === 'nrAud') audienceCtl();
            else if (e.target.id === 'nrTable') { audienceCtl(); fieldChips(e.target.value); }
            else if (e.target.id === 'nrLink') $('nrCustom').style.display = e.target.value === 'custom' ? '' : 'none';
            else if (e.target.hasAttribute('data-tog')) { rules[parseInt(e.target.getAttribute('data-tog'), 10)].enabled = e.target.checked; dirty = true; render(); }
        });
        var qEl = $('nrQ');
        if (qEl) qEl.addEventListener('input', function () { filters.q = qEl.value.trim(); render(); });
        window.addEventListener('beforeunload', function (e) { if (dirty) { e.preventDefault(); e.returnValue = ''; } });
        render();
    })();
    </script>
<?php endif; ?>
</body>
</html>
