// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.

/**
 * @module     block_mwa_dashboard/activities
 * @copyright  2026 Bruno Porto
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */

/**
 * Note: This module generates HTML markup directly in JavaScript strings for
 * performance reasons — the dashboard renders large dynamic datasets (student lists,
 * heatmaps, charts) that require frequent partial updates. All user-supplied data is
 * escaped via the esc() helper before insertion into the DOM.
 * See: https://docs.moodle.org/dev/JavaScript_Modules#HTML_generation
 */
define(['block_mwa_dashboard/dashboardstore'], function(Store) {

    'use strict';

    var window = Store.windowFacade();

    (function () {
      'use strict';
    
      /* ── tr() reads Store.getStrings() directly ── */
      function tr(key) {
        var S = Store.getStrings() || {};
        var v = Object.prototype.hasOwnProperty.call(S, key) ? S[key] : '';
        if (typeof v === 'string' && v && !/^\[\[.*\]\]$/.test(v)) return v;
        return key;
      }
      function esc(v) {
        return String(v === undefined || v === null ? '' : v)
          .replace(/[&<>"']/g, function (c) {
            return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c];
          });
      }
      function norm(v) { return (v === undefined || v === null) ? '' : String(v).trim(); }
    
      var CURRENT_FILTER = 'all';
      var ACT_DATA = [];
    
      /* ── tipo de evento — usa os valores REAIS retornados pelo PHP ── */
      function getEventType(r) {
        var comp = norm(r.componente||r.component||'').toLowerCase();
        var modtype = (r._modtype||'').toLowerCase();
        var ev   = norm(r.nomedoevento).toLowerCase();
    
        if (comp === 'forum' || comp === 'fórum' || comp === 'glossary')
          return 'forum';
        if (comp === 'assignment' || comp === 'tarefa')
          return 'tarefa';
        if (comp === 'quiz' || comp === 'h5p' || comp === 'scorm package' || comp === 'lesson' || comp === 'choice')
          return 'quiz';
        if (comp === 'page' || comp === 'book' || comp === 'url' || comp === 'file' || comp === 'folder' || comp === 'wiki')
          return 'video';
        if (comp === 'system' || ev === 'user logged in' || ev === 'user logged out' || ev === 'course viewed')
          return 'login';
        return 'outro';
      }
    
      function isCourseGeneral(name) {
        var n = norm(name);
        if (!n) return true;
        if (/^curso\s*:/i.test(n)) return true;
        if (/^sistema$/i.test(n)) return true;
        if (/^system$/i.test(n)) return true;
        if (/^área de texto/i.test(n)) return true;
        if (/^mídia/i.test(n)) return true;
        return false;
      }
    
      /* ── isSubmission — usa os valores REAIS do translate_action ── */
      function isSubmission(r) {
        var ev   = norm(r.nomedoevento).toLowerCase();
        var comp = norm(r.componente).toLowerCase();
    
        // Tarefas: envio real
        if (comp === 'assignment' || comp === 'tarefa') {
          return ev === 'submission submitted' ||
                 ev === 'file uploaded' ||
                 ev.includes('submit') ||
                 ev.includes('upload');
        }
    
        // Quiz / H5P / SCORM: tentativa submetida
        if (comp === 'quiz' || comp === 'h5p' || comp === 'scorm package' || comp === 'lesson') {
          return ev === 'quiz attempt submitted' ||
                 ev.includes('attempt_submitted') ||
                 ev.includes('attempt submitted');
        }
    
        // Forum: real participation = post created OR discussion created
        // O translate_action mapeia: 'post_created' → 'Forum post created'
        //                            'discussion_created' → 'Discussion created'
        // O evento 'Discussion subscription created' vem do action 'subscription_created'
        // not in the map, so it arrives as 'subscription_created' literal
        if (comp === 'forum' || comp === 'fórum') {
          return ev === 'forum post created' ||
                 ev === 'discussion created' ||
                 ev === 'content created' ||
                 ev.includes('post_created') ||
                 ev.includes('post created') ||
                 ev.includes('discussion_created') ||
                 ev.includes('discussion created') ||
                 ev.includes('subscription_created') ||
                 ev.includes('subscription created') ||
                 ev.includes('user_subscribed') ||
                 ev.includes('user subscribed');
        }
    
        return false;
      }
    
      function parseDate(log) {
        if (log._ts) return new Date(Number(log._ts) * 1000);
        var s = norm(log.hora);
        var m = s.match(/(\d{2})\/(\d{2})\/(\d{2}),\s*(\d{2}):(\d{2})/);
        if (m) return new Date(2000 + Number(m[3]), Number(m[2]) - 1, Number(m[1]), Number(m[4]), Number(m[5]));
        return null;
      }
    
      function fmtTime(ms) {
        if (!ms || ms <= 0) return '—';
        var min = Math.round(ms / 60000);
        if (min < 1) return '< 1min';
        if (min < 60) return min + 'min';
        return Math.floor(min / 60) + 'h ' + (min % 60) + 'min';
      }
    
      function emailFor(name, logs) {
        var found = '';
        logs.some(function (r) {
          if (norm(r.nomecompleto) === name && norm(r.email)) { found = norm(r.email); return true; }
          return false;
        });
        return found;
      }
    
      /* ── calcular tempo por aluno num recurso ── */
      var GAP_MS = 5  * 60 * 1000;  // 5 min gap = new session
      var CAP_MS = 30 * 60 * 1000;  // 30 min cap per session
    
      function calcTimesForResource(timestamps) {
        // timestamps: { nomealuno: [ts1, ts2, ...] }
        var result = {};
        Object.keys(timestamps).forEach(function (name) {
          var sorted = timestamps[name].slice().sort(function (a, b) { return a - b; });
          var total = 0;
          for (var i = 1; i < sorted.length; i++) {
            var gap = sorted[i] - sorted[i - 1];
            if (gap < GAP_MS) total += Math.min(gap, CAP_MS);
          }
          if (sorted.length === 1) total = 60000; // single event = estimated 1 min
          result[name] = total;
        });
        return result;
      }
    
      var TYPE_COLORS = { forum: 'var(--blue)', quiz: 'var(--amber)', tarefa: 'var(--green)', video: 'var(--teal)', login: 'var(--purple)', outro: 'var(--muted)' };
      var TYPE_ICONS  = { forum: '💬', quiz: '❓', tarefa: '✏️', video: '📄', login: '🔑', outro: '📌' };
    
      /* ════════════════════════════════════════════
         BUILD actMap + coleta timestamps por recurso
      ════════════════════════════════════════════ */
      function buildActMap(logs) {
        var actMap   = {};
        var timeMap  = {};   // { recurso: { aluno: [timestamps] } }
        var emailMap = {};
    
        logs.forEach(function (r) {
          var name = norm(r.nomecompleto);
          var k    = norm(r.contextodoevento) || norm(r.nomedoevento) || 'Outro';
          if (isCourseGeneral(k)) return;
          var t = getEventType(r);
          if (t === 'outro' || t === 'login') return;
    
          if (!actMap[k]) actMap[k] = { count: 0, students: new Set(), concluded: new Set(), submitted: new Set(), type: t };
          actMap[k].count++;
          actMap[k].students.add(name);
          if (isSubmission(r)) actMap[k].submitted.add(name);
    
          // coleta email
          if (norm(r.email)) emailMap[name] = norm(r.email);
    
          // collect timestamp for time calculation
          var d = parseDate(r);
          if (d && name) {
            if (!timeMap[k]) timeMap[k] = {};
            if (!timeMap[k][name]) timeMap[k][name] = [];
            timeMap[k][name].push(d.getTime());
          }
        });
    
        // Completion logic by type
        Object.values(actMap).forEach(function (v) {
          if (v.type === 'tarefa' || v.type === 'quiz') {
            if (v.submitted.size > 0) v.concluded = new Set(v.submitted);
          } else if (v.type === 'forum') {
            if (v.submitted.size > 0) v.concluded = new Set(v.submitted);
          } else {
            v.concluded = new Set(v.students);
          }
        });
    
        return { actMap: actMap, timeMap: timeMap, emailMap: emailMap };
      }
    
      /* ════════════════════════════════════════════
         RENDER KPIs
      ════════════════════════════════════════════ */
      function renderKPIs(arr, allStudents, filteredCount) {
        var container = document.getElementById('actKpisRow');
        if (!container) return;
        var totalAcc = arr.reduce(function (s, e) { return s + e[1].count; }, 0);
        var avgPct   = arr.length
          ? Math.round(arr.reduce(function (s, e) { return s + e[1].students.size; }, 0) / arr.length / Math.max(1, allStudents.length) * 100)
          : 0;
    
        Store.renderHtml(container, '<div class="kpi c-blue"><div class="kpi-label">' + tr('act_kpi_unique') + '</div><div class="kpi-value">' + arr.length + '</div><div class="kpi-sub">' + filteredCount + ' ' + tr('act_kpi_unique_shown') + '</div></div>'
        + '<div class="kpi c-green"><div class="kpi-label">' + tr('act_kpi_total_acc') + '</div><div class="kpi-value">' + totalAcc.toLocaleString('pt-BR') + '</div></div>'
        + '<div class="kpi c-amber"><div class="kpi-label">' + tr('act_kpi_students') + '</div><div class="kpi-value">' + allStudents.length + '</div></div>'
        + '<div class="kpi c-teal"><div class="kpi-label">' + tr('act_kpi_avg') + '</div><div class="kpi-value">' + avgPct + '%</div><div class="kpi-sub">' + tr('act_kpi_avg_delta') + '</div></div>');
      }
    
      /* ════════════════════════════════════════════
         RENDER GRID DE CARDS
      ════════════════════════════════════════════ */
      function renderGrid(filtered, allStudents, logs, emailMap, timeMap) {
        var grid = document.getElementById('actResourceGrid');
        if (!grid) return;
        if (!filtered.length) { Store.renderHtml(grid, '<div class="act-empty">' + esc(tr('no_data')) + '</div>'); return; }
    
        ACT_DATA = [];
    
        var html = filtered.map(function (entry, idx) {
          var k = entry[0], v = entry[1];
          var pct      = Math.round(v.students.size / Math.max(1, allStudents.length) * 100);
          var color    = TYPE_COLORS[v.type] || 'var(--muted)';
          var concPct  = v.students.size > 0 ? Math.round(v.concluded.size / v.students.size * 100) : 0;
          var barColor = pct >= 70 ? 'var(--green)' : pct >= 40 ? 'var(--blue)' : 'var(--red)';
    
          var accessedSt    = Array.from(v.students).sort(function (a, b) { return a.localeCompare(b, 'pt-BR'); });
          var concludedSt   = Array.from(v.concluded).sort(function (a, b) { return a.localeCompare(b, 'pt-BR'); });
          var pendingSt     = accessedSt.filter(function (s) { return !v.concluded.has(s); });
          var notAccessedSt = allStudents.filter(function (s) { return !v.students.has(s); }).sort(function (a, b) { return a.localeCompare(b, 'pt-BR'); });
    
          // Tempo por aluno neste recurso
          var stuTimes   = timeMap[k] ? calcTimesForResource(timeMap[k]) : {};
          var timeValues = Object.values(stuTimes);
          var avgMs      = timeValues.length ? timeValues.reduce(function (a, b) { return a + b; }, 0) / timeValues.length : 0;
    
          // Emails de alunos sem acesso e pendentes
          var noAccEmails  = notAccessedSt.map(function (s) { return emailMap[s] || ''; }).filter(Boolean);
          var pendEmails   = pendingSt.map(function (s) { return emailMap[s] || ''; }).filter(Boolean);
    
          var concludedLabel = v.type === 'forum' ? tr('act_label_posted') : (v.type === 'tarefa' || v.type === 'quiz') ? tr('act_label_submitted') : tr('act_label_accessed');
          var pendingLabel   = v.type === 'forum' ? tr('act_label_saw_not_posted') : (v.type === 'tarefa' || v.type === 'quiz') ? tr('act_label_saw_not_submitted') : tr('act_label_pending');
    
          var tagsDone = concludedSt.map(function (s) {
            return '<span class="act-stag act-stag-done" title="' + esc(s) + '" onclick="window.goToStudentProfile&&window.goToStudentProfile(' + JSON.stringify(s) + ')">' + esc(s.split(' ')[0]) + ' ✓</span>';
          }).join('');
          var tagsPend = pendingSt.map(function (s) {
            return '<span class="act-stag act-stag-pend" title="' + esc(s) + '" onclick="window.goToStudentProfile&&window.goToStudentProfile(' + JSON.stringify(s) + ')">' + esc(s.split(' ')[0]) + ' ✕</span>';
          }).join('');
          var tagsNone = notAccessedSt.map(function (s) {
            return '<span class="act-stag act-stag-none" title="' + esc(s) + '" onclick="window.goToStudentProfile&&window.goToStudentProfile(' + JSON.stringify(s) + ')">' + esc(s.split(' ')[0]) + '</span>';
          }).join('');
    
          // Ranking de tempo por aluno para o modal (top por tempo)
          var timeRanking = Object.entries(stuTimes)
            .sort(function (a, b) { return b[1] - a[1]; })
            .slice(0, 10);
    
          ACT_DATA[idx] = {
            title: k, type: v.type, color: color,
            allStudentsCount: allStudents.length,
            totalAcc: v.count,
            concludedSt: concludedSt, pendingSt: pendingSt, notAccessedSt: notAccessedSt,
            concPct: concPct, pct: pct,
            concludedLabel: concludedLabel, pendingLabel: pendingLabel,
            tagsDone: tagsDone, tagsPend: tagsPend, tagsNone: tagsNone,
            avgMs: avgMs, timeRanking: timeRanking,
            noAccEmails: noAccEmails,
            pendEmails: pendEmails
          };
    
          return '<div class="act-card" onclick="window.MWAActivities.openModal(' + idx + ')">'
            + '<div class="act-card-title-row">'
              + '<div class="act-card-dot" style="background:' + color + ';"></div>'
              + '<div class="act-card-name">' + esc(k) + '</div>'
              + '<span class="act-card-arrow">↗</span>'
            + '</div>'
            + '<div class="act-card-bar-wrap">'
              + '<div class="act-card-bar-bg"><div class="act-card-bar-fill" style="width:' + pct + '%;background:' + barColor + ';"></div></div>'
              + '<span class="act-card-bar-pct" style="color:' + barColor + ';">' + pct + '%</span>'
            + '</div>'
            + '<div class="act-card-stats">'
              + '<div class="act-card-stat"><div class="act-card-stat-label">' + tr('act_col_students') + '</div><div class="act-card-stat-val" style="color:var(--text);">' + v.students.size + '</div></div>'
              + '<div class="act-card-stat"><div class="act-card-stat-label">' + tr('act_col_accesses') + '</div><div class="act-card-stat-val" style="color:var(--blue);">' + v.count.toLocaleString('pt-BR') + '</div></div>'
              + (v.concluded.size > 0
                  ? '<div class="act-card-stat"><div class="act-card-stat-label">' + tr('act_col_submitted') + '</div><div class="act-card-stat-val" style="color:var(--green);">' + concPct + '%</div></div>'
                  : '')
            + '</div>'
          + '</div>';
        }).join('');
    
        Store.renderHtml(grid, html);
      }
    
      /* ════════════════════════════════════════════
         MODAL COMPLETO — igual à imagem
      ════════════════════════════════════════════ */
      function openModal(idx) {
        var d = ACT_DATA[idx];
        if (!d) return;
    
        var overlay = document.createElement('div');
        overlay.className = 'act-modal-overlay';
        overlay.id = 'actModalOverlay';
        overlay.onclick = function (e) { if (e.target === overlay) closeModal(); };
    
        var icon      = TYPE_ICONS[d.type] || '📌';
        var color     = d.color;
        var barColor  = d.pct >= 70 ? 'var(--green)' : d.pct >= 40 ? 'var(--blue)' : 'var(--red)';
        var total     = d.concludedSt.length + d.pendingSt.length;
    
        // ── Modal KPIs: Participated / Pending / Completion rate ──
        var kpiHtml =
          '<div style="display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin-bottom:14px;">'
          + '<div style="background:var(--panel2);border:1px solid var(--line);border-radius:12px;padding:12px 14px;text-align:center;">'
              + '<div style="font-size:.65rem;font-weight:900;text-transform:uppercase;letter-spacing:.07em;color:var(--muted);margin-bottom:4px;">' + tr('act_modal_participated') + '</div>'
              + '<div style="font-size:1.6rem;font-weight:900;color:var(--green);">' + d.concludedSt.length + '</div>'
          + '</div>'
          + '<div style="background:var(--panel2);border:1px solid var(--line);border-radius:12px;padding:12px 14px;text-align:center;">'
              + '<div style="font-size:.65rem;font-weight:900;text-transform:uppercase;letter-spacing:.07em;color:var(--muted);margin-bottom:4px;">' + tr('act_modal_pending') + '</div>'
              + '<div style="font-size:1.6rem;font-weight:900;color:' + (d.pendingSt.length > 0 ? 'var(--red)' : 'var(--green)') + ';">' + d.pendingSt.length + '</div>'
          + '</div>'
          + '<div style="background:var(--panel2);border:1px solid var(--line);border-radius:12px;padding:12px 14px;text-align:center;">'
              + '<div style="font-size:.65rem;font-weight:900;text-transform:uppercase;letter-spacing:.07em;color:var(--muted);margin-bottom:4px;">' + tr('act_modal_completion_rate') + '</div>'
              + '<div style="font-size:1.6rem;font-weight:900;color:' + barColor + ';">' + d.concPct + '%</div>'
          + '</div>'
          + '</div>';
    
        // ── Barra de progresso geral ──
        var progressHtml =
          '<div style="display:flex;align-items:center;gap:8px;margin-bottom:16px;">'
          + '<div style="flex:1;height:8px;background:#e8ecf5;border-radius:4px;overflow:hidden;">'
            + '<div style="width:' + d.pct + '%;height:100%;background:' + barColor + ';border-radius:4px;transition:width .4s;"></div>'
          + '</div>'
          + '<span style="font-size:.78rem;font-weight:900;color:' + barColor + ';min-width:36px;">' + d.pct + '%</span>'
          + '</div>';
    
        // ── Tags de alunos ──
        var tagsHtml = '';
        if (d.concludedSt.length) {
          tagsHtml += '<div class="act-tags-label">' + esc(d.concludedLabel) + ' (' + d.concludedSt.length + '):</div>'
            + '<div class="act-tags-wrap" style="margin-bottom:10px;">' + d.tagsDone + '</div>';
        }
        if (d.pendingSt.length) {
          tagsHtml += '<div class="act-tags-label" style="margin-top:6px;">' + esc(d.pendingLabel) + ' (' + d.pendingSt.length + '):</div>'
            + '<div class="act-tags-wrap" style="margin-bottom:10px;">' + d.tagsPend + '</div>';
        }
        if (d.notAccessedSt.length) {
          tagsHtml += '<div class="act-tags-label" style="margin-top:6px;">' + tr('act_label_no_access') + ' (' + d.notAccessedSt.length + '):</div>'
            + '<div class="act-tags-wrap" style="margin-bottom:10px;">' + d.tagsNone + '</div>';
        }
    
        // ── Tempo por aluno ──
        var timeHtml = '';
        if (d.timeRanking.length) {
          var maxT = d.timeRanking[0][1] || 1;
          timeHtml = '<div style="margin-top:14px;padding-top:14px;border-top:1px solid var(--line);">'
            + '<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:10px;">'
              + '<span style="font-size:.78rem;font-weight:900;color:var(--text);">⏱ ' + tr('act_modal_time_per_student') + '</span>'
              + '<span style="font-size:.72rem;color:var(--muted);">' + tr('act_modal_avg') + ': ' + fmtTime(d.avgMs) + '</span>'
            + '</div>'
            + d.timeRanking.map(function (entry) {
                var name  = entry[0];
                var ms    = entry[1];
                var parts = name.split(/\s+/).filter(Boolean);
                var short = parts.slice(0, 2).join(' ');
                var pct   = Math.round(ms / maxT * 100);
                var tclr  = ms >= 600000 ? 'var(--green)' : ms >= 120000 ? 'var(--amber)' : 'var(--red)';
                return '<div style="display:flex;align-items:center;gap:10px;margin-bottom:7px;">'
                  + '<div style="width:110px;font-size:.75rem;font-weight:700;color:var(--text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;flex-shrink:0;">' + esc(short) + '</div>'
                  + '<div style="flex:1;height:6px;background:#e8ecf5;border-radius:3px;overflow:hidden;">'
                    + '<div style="width:' + pct + '%;height:100%;background:' + tclr + ';border-radius:3px;"></div>'
                  + '</div>'
                  + '<span style="font-size:.72rem;font-weight:700;color:' + tclr + ';min-width:40px;text-align:right;">' + fmtTime(ms) + '</span>'
                  + '</div>';
              }).join('')
          + '</div>';
        }
    
        // ── Footer: Mensagem pendentes + sem acesso + Fechar ──
        var footerHtml = '<div style="display:flex;justify-content:flex-end;gap:8px;padding:14px 20px 16px;border-top:1px solid var(--line);flex-wrap:wrap;">'
          + (d.pendingSt.length
              ? '<button class="act-msg-btn" id="actMsgPend">'
                + '✉️ ' + tr('act_msg_pending') + ' (' + d.pendingSt.length + ')'
                + '</button>'
              : '')
          + (d.notAccessedSt.length
              ? '<button class="act-msg-btn" id="actMsgNoAcc">'
                + '✉️ ' + tr('act_msg_no_access') + ' (' + d.notAccessedSt.length + ')'
                + '</button>'
              : '')
          + '<button style="background:var(--panel);border:1px solid var(--line2);border-radius:12px;padding:9px 18px;font-family:inherit;font-size:.8rem;font-weight:800;color:var(--muted);cursor:pointer;" '
            + 'onclick="window.MWAActivities.closeModal()">' + tr('act_modal_close') + '</button>'
        + '</div>';
    
        Store.renderHtml(overlay, '<div class="act-modal">'
    
            // ── Header ──
            + '<div class="act-modal-head">'
              + '<span style="font-size:1.1rem;flex-shrink:0;">' + icon + '</span>'
              + '<div style="flex:1;min-width:0;">'
                + '<div class="act-modal-title">' + esc(d.title) + '</div>'
                + '<div style="font-size:.7rem;color:var(--muted);margin-top:2px;">'
                  + esc(d.type) + ' · ' + d.concludedSt.length + d.pendingSt.length + ' ' + tr('act_col_students') + ' · ' + d.totalAcc.toLocaleString('pt-BR') + ' ' + tr('act_col_accesses')
                + '</div>'
              + '</div>'
              + '<button class="act-modal-close" onclick="window.MWAActivities.closeModal()">✕</button>'
            + '</div>'
    
            // ── Body ──
            + '<div class="act-modal-body">'
              + kpiHtml
              + progressHtml
              + tagsHtml
              + timeHtml
            + '</div>'
    
            // ── Footer ──
            + footerHtml
    
          + '</div>');
    
        (document.getElementById('block-mwa-dashboard-app') || document.body).appendChild(overlay);
        var handler = function (e) { if (e.key === 'Escape') { closeModal(); document.removeEventListener('keydown', handler); } };
        document.addEventListener('keydown', handler);

        // ── Message button handlers ──
        var btnPend  = document.getElementById('actMsgPend');
        var btnNoAcc = document.getElementById('actMsgNoAcc');
        if (btnPend) {
          btnPend.onclick = function () {
            var targets = d.pendingSt.map(function (s) {
              var ei = d.pendingSt.indexOf(s);
              return { name: s, email: (d.pendEmails && d.pendEmails[ei]) || '', userid: 0 };
            });
            if (window.MWAActionCenter && window.MWAActionCenter.openBulkModal) {
              closeModal();
              window.MWAActionCenter.openBulkModal(targets);
            }
          };
        }
        if (btnNoAcc) {
          btnNoAcc.onclick = function () {
            var targets = d.notAccessedSt.map(function (s) {
              var ei = d.notAccessedSt.indexOf(s);
              return { name: s, email: (d.noAccEmails && d.noAccEmails[ei]) || '', userid: 0 };
            });
            if (window.MWAActionCenter && window.MWAActionCenter.openBulkModal) {
              closeModal();
              window.MWAActionCenter.openBulkModal(targets);
            }
          };
        }
      }
    
      function closeModal() {
        var el = document.getElementById('actModalOverlay');
        if (el) el.remove();
      }
    
      /* ════════════════════════════════════════════
         FILTER + RENDER PRINCIPAL
      ════════════════════════════════════════════ */
      function setFilter(type, btn) {
        CURRENT_FILTER = type;
        document.querySelectorAll('.act-tab').forEach(function (b) { b.classList.remove('active'); });
        if (btn) btn.classList.add('active');
        renderActivities();
      }
    
      function renderActivities() {
        var dash  = window.MWADashboard || {};
        var state = dash.state || {};
        var logs  = state.logs || [];
    
        var built    = buildActMap(logs);
        var actMap   = built.actMap;
        var timeMap  = built.timeMap;
        var emailMap = built.emailMap;
    
        var arr      = Object.entries(actMap).sort(function (a, b) { return b[1].count - a[1].count; });
        var filtered = CURRENT_FILTER === 'all'
          ? arr
          : arr.filter(function (e) { return e[1].type === CURRENT_FILTER; });
    
        // allStudents = todos os alunos que tocaram em pelo menos uma atividade
        var seen = {};
        var allStudents = [];
        logs.forEach(function (r) {
          var n   = norm(r.nomecompleto);
          var ctx = norm(r.contextodoevento) || norm(r.nomedoevento);
          if (!n || isCourseGeneral(ctx)) return;
          if (getEventType(r) === 'login' || getEventType(r) === 'outro') return;
          if (!seen[n]) { seen[n] = true; allStudents.push(n); }
        });
    
        renderKPIs(arr, allStudents, filtered.length);
        renderGrid(filtered, allStudents, logs, emailMap, timeMap);
      }
    
      /* Public API */
      window.MWAActivities = {
        render:    renderActivities,
        setFilter: setFilter,
        openModal: openModal,
        closeModal: closeModal
      };
    
    })();

    return window.MWAActivities;
});
