// 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/classlist
 * @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 (same pattern as dashboard) ── */
      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(); }
    
      /* ── module state ── */
      var CL_VIEW = 'participation'; // 'participation' | 'activities'
    
      /* ── avatar colours (cycling by name) ── */
      var AV_COLORS = ['#5b9bd5','#8b72d4','#3aab7a','#c98a2a','#d95f5f','#2aafaa','#e07ba0'];
    
      /* ── helpers de data ── */
      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 getDaysAgo(date) {
        if (!date) return 999;
        var d = Math.floor((Date.now() - date.getTime()) / 86400000);
        return d < 0 ? 0 : d;
      }
    
      function fmtDate(d) {
        if (!d) return '—';
        var dd = String(d.getDate()).padStart(2, '0');
        var mm = String(d.getMonth() + 1).padStart(2, '0');
        return dd + '/' + mm + '/' + d.getFullYear();
      }
    
      /* ── session time calculation ── */
      var SESSION_GAP = 30 * 60 * 1000;  // 30 min
      var MAX_SESSION = 60 * 60 * 1000;  // teto 60 min
    
      function calcSessionTimes(logs) {
        var byStudent = {};
        logs.forEach(function (r) {
          var n = norm(r.nomecompleto);
          var d = parseDate(r);
          if (!n || !d) return;
          if (!byStudent[n]) byStudent[n] = [];
          byStudent[n].push(d.getTime());
        });
        var result = {};
        Object.keys(byStudent).forEach(function (n) {
          var times = byStudent[n].sort(function (a, b) { return a - b; });
          var total = 0;
          var sessionStart = times[0];
          var prev = times[0];
          for (var i = 1; i < times.length; i++) {
            var gap = times[i] - prev;
            if (gap > SESSION_GAP) {
              total += Math.min(prev - sessionStart + 2 * 60 * 1000, MAX_SESSION);
              sessionStart = times[i];
            }
            prev = times[i];
          }
          total += Math.min(prev - sessionStart + 2 * 60 * 1000, MAX_SESSION);
          result[n] = total;
        });
        return result;
      }
    
      function fmtTime(ms) {
        if (!ms || ms <= 0) return '—';
        var totalMin = Math.round(ms / 60000);
        if (totalMin < 1) return '< 1min';
        if (totalMin < 60) return totalMin + 'min';
        var h = Math.floor(totalMin / 60);
        var m = totalMin % 60;
        return h + 'h' + (m > 0 ? ' ' + m + 'min' : '');
      }
    
      /* ── cor por dias sem acesso ── */
      function agoColor(ago) {
        if (ago === 0) return 'var(--green)';
        if (ago <= 2)  return 'var(--teal)';
        if (ago <= 6)  return 'var(--amber)';
        return 'var(--red)';
      }
    
      /* ── participation bar colour ── */
      function partColor(pct) {
        if (pct >= 70) return 'var(--green)';
        if (pct >= 40) return 'var(--blue)';
        return 'var(--muted)';
      }
    
      /* ── cor do tempo ── */
      function timeColor(ms) {
        if (ms >= 3600000) return 'var(--green)';
        if (ms >= 900000)  return 'var(--blue)';
        return 'var(--muted)';
      }
    
      /* ── getLastAccess: date of last log entry per student ── */
      function getLastAccess(logs) {
        var m = {};
        logs.forEach(function (r) {
          var n = norm(r.nomecompleto);
          var d = parseDate(r);
          if (!n || !d) return;
          if (!m[n] || d > m[n]) m[n] = d;
        });
        return m;
      }
    
      /* ── email do aluno ── */
      function getEmailForStudent(name, logs) {
        var found = null;
        logs.some(function (r) {
          if (norm(r.nomecompleto) === name && norm(r.email)) {
            found = norm(r.email);
            return true;
          }
          return false;
        });
        return found || '';
      }
    
      /* ── activity coverage calculation ── */
      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 (/^área de texto/i.test(n)) return true;
        if (/^mídia/i.test(n)) return true;
        return false;
      }
    
      function isSubmission(log) {
        var ev   = norm(log.nomedoevento).toLowerCase();
        var comp = norm(log.componente).toLowerCase();
        // Tarefa: submission submitted ou file uploaded (translate_action output)
        if (comp === 'assignment' || comp === 'tarefa') {
          return ev === 'submission submitted' || ev === 'file uploaded' ||
                 ev.includes('submit') || ev.includes('upload');
        }
        // Quiz
        if (comp === 'quiz' || comp === 'h5p' || comp === 'scorm package') {
          return ev === 'quiz attempt submitted' || ev.includes('attempt submitted');
        }
        // Forum
        if (comp === 'forum' || comp === 'fórum') {
          return ev === 'forum post created' || ev === 'discussion created' ||
                 ev.includes('post_created') || ev.includes('subscription_created') ||
                 ev.includes('post created') || ev.includes('discussion created') ||
                 ev.includes('subscription created');
        }
        return false;
      }
    
      /* ── calculate participation score ── */
      function calcParticipation(name, logs, grades) {
        // Cobertura de atividades (30%)
        var allActs = new Set();
        var myActs  = new Set();
        logs.forEach(function (r) {
          var ctx = norm(r.contextodoevento);
          if (!ctx || isCourseGeneral(ctx)) return;
          allActs.add(ctx);
          if (norm(r.nomecompleto) === name) myActs.add(ctx);
        });
        var coverScore = allActs.size > 0
          ? Math.min(100, Math.round((myActs.size / allActs.size) * 100))
          : 0;
    
        // Nota (50%)
        var grade = null;
        if (grades && grades.length) {
          grades.some(function (g) {
            // tenta casar pelo nome (parcial) ou email
            var gname = (norm(g['First name']) + ' ' + norm(g['Last name'])).trim();
            if (gname.toLowerCase() === name.toLowerCase()) {
              var k = Object.keys(g).find(function (x) {
                return x.toLowerCase().includes('course total') || x.toLowerCase().includes('total do curso');
              });
              if (k) {
                var n = parseFloat(String(g[k]).replace(',', '.'));
                if (!isNaN(n)) grade = n;
              }
              return true;
            }
            return false;
          });
        }
        var gradeScore = grade !== null ? Math.min(100, Math.round((grade / 100) * 100)) : 0;
    
        // Entrega (20%)
        var hasDelivery = logs.some(function (r) {
          return norm(r.nomecompleto) === name && isSubmission(r);
        });
        var deliveryScore = hasDelivery ? 100 : 0;
    
        var hasGrades = grades && grades.length > 0;
        if (hasGrades && grade !== null) {
          return Math.round(gradeScore * 0.50 + coverScore * 0.30 + deliveryScore * 0.20);
        } else {
          return Math.round(coverScore * 0.60 + deliveryScore * 0.40);
        }
      }
    
      /* ── dots de atividade por aluno (modo "Ver atividades") ── */
      function buildActDots(name, grades) {
        if (!grades || !grades.length) {
          return '<span class="cl-no-grades-hint">' + esc(tr('cl_no_grades_hint')) + '</span>';
        }
    
        // Usar o cache global de notas processado pelo grades.js (formato act_N)
        var cache = window.MWA_GRADES_CACHE || [];
        var actNames   = window.MWA_ACT_NAMES   || {};
        var actCmids   = window.MWA_ACT_CMIDS   || {};
        var actModules = window.MWA_ACT_MODULES  || {};
        var wwwroot    = (Store.getConfig().wwwroot || '');
    
        // Procura o aluno no cache processado
        var gradeObj = null;
        cache.some(function (g) {
          if (g.name.toLowerCase() === name.toLowerCase()) { gradeObj = g; return true; }
          return false;
        });
    
        // Fallback: tenta formato antigo (First name + Last name)
        if (!gradeObj) {
          var rawRow = null;
          grades.some(function (g) {
            if (g['__mwa_type__'] === 'activity_names') return false;
            var gname = (norm(g['First name']) + ' ' + norm(g['Last name'])).trim();
            if (gname.toLowerCase() === name.toLowerCase()) { rawRow = g; return true; }
            return false;
          });
          if (!rawRow) {
            return '<span class="cl-no-grades-hint">' + esc(tr('cl_no_grade_for_student')) + '</span>';
          }
          // Formato antigo: usa actGrades do rawRow diretamente
          gradeObj = { actGrades: {} };
          Object.keys(rawRow).forEach(function (k) {
            var m = k.match(/^act_(\d+)$/);
            if (m) {
              var v = rawRow[k];
              var n = parseFloat(String(v).replace(',', '.'));
              gradeObj.actGrades[m[1]] = isNaN(n) ? null : n;
            }
          });
        }
    
        var actGrades = gradeObj.actGrades || {};
        var seqs      = Object.keys(actNames).length
          ? Object.keys(actNames).sort(function (a, b) { return Number(a) - Number(b); })
          : Object.keys(actGrades).sort(function (a, b) { return Number(a) - Number(b); });
    
        if (!seqs.length) {
          return '<span class="cl-no-grades-hint">' + esc(tr('cl_no_activities_found')) + '</span>';
        }
    
        return '<div class="cl-act-dots">' + seqs.map(function (seq) {
          var val     = actGrades[String(seq)];
          var num     = (val === null || val === undefined) ? NaN : parseFloat(String(val).replace(',', '.'));
          var done    = !isNaN(num) && num > 0;
          var missing = val === null || val === undefined || val === '-' || val === '' || (!isNaN(num) && num === 0);
          var cls     = done ? 'done' : missing ? 'missing' : 'pending';
    
          // Tooltip: nome real da atividade
          var actName = actNames[Number(seq)] || ('Atividade ' + seq);
          var tooltip = esc(actName) + (done ? ' (' + num.toFixed(1) + ' pts)' : missing ? ' (—)' : '');
    
          // URL da atividade no Moodle
          var cmid   = actCmids[Number(seq)] || 0;
          var modname = actModules[Number(seq)] || '';
          var url     = (cmid && modname && wwwroot)
            ? wwwroot + '/mod/' + modname + '/view.php?id=' + cmid
            : '';
    
          var inner = url
            ? '<a href="' + url + '" target="_blank" style="color:inherit;text-decoration:none;">' + seq + '</a>'
            : String(seq);
    
          return '<span class="cl-act-dot ' + cls + '" title="' + tooltip + '">' + inner + '</span>';
        }).join('') + '</div>';
      }
    
      /* ════════════════════════════════════════════
         RENDER PRINCIPAL
      ════════════════════════════════════════════ */
      function renderClassList() {
        var dash  = window.MWADashboard || {};
        var state = dash.state || {};
        var logs   = state.logs   || [];
        var grades = state.grades || [];
    
        if (!logs.length) {
          var tb = document.getElementById('clTableBody');
          if (tb) Store.renderHtml(tb, '<tr><td colspan="7" class="cl-empty">' + esc(tr('no_data')) + '</td></tr>');
          return;
        }
    
        var lastAccess   = getLastAccess(logs);
        var sessionTimes = calcSessionTimes(logs);
    
        // Group interactions by student
        var byName = {};
        logs.forEach(function (r) {
          var n = norm(r.nomecompleto);
          if (!n) return;
          byName[n] = (byName[n] || 0) + 1;
        });
    
        var sortMode = (document.getElementById('clSort') || {}).value || 'interactions';
    
        var entries = Object.keys(byName).map(function (n) {
          return { name: n, count: byName[n] };
        });
    
        // Sorting
        entries.sort(function (a, b) {
          switch (sortMode) {
            case 'alpha':
              return a.name.localeCompare(b.name, 'pt-BR');
            case 'ago':
              return getDaysAgo(lastAccess[b.name]) - getDaysAgo(lastAccess[a.name]);
            case 'participation':
              return calcParticipation(b.name, logs, grades) - calcParticipation(a.name, logs, grades);
            case 'time':
              return (sessionTimes[b.name] || 0) - (sessionTimes[a.name] || 0);
            default: // interactions desc
              return b.count - a.count;
          }
        });
    
        // Update 4th column header according to mode
        var th4 = document.querySelector('#clTable thead tr th:nth-child(4)');
        if (th4) {
          th4.textContent = CL_VIEW === 'activities'
            ? tr('cl_th_deliveries')
            : tr('cl_th_participation');
        }
    
        // Gera linhas
        var html = entries.map(function (e) {
          var n    = e.name;
          var cnt  = e.count;
          var last = lastAccess[n];
          var ago  = getDaysAgo(last);
          var email = getEmailForStudent(n, logs);
          var ms   = sessionTimes[n] || 0;
          var part = calcParticipation(n, logs, grades);
          var pc   = partColor(part);
          var ac   = agoColor(ago);
          var tc   = timeColor(ms);
    
          // Avatar
          var initials = n.split(/\s+/).filter(Boolean).slice(0, 2).map(function (w) { return w[0]; }).join('').toUpperCase();
          var ci       = Math.abs((n.charCodeAt(0) || 0) + (n.charCodeAt(1) || 0)) % AV_COLORS.length;
          var avatarBg = AV_COLORS[ci];
    
          // Column 4: participation or activities
          var col4Html = CL_VIEW === 'activities'
            ? buildActDots(n, grades)
            : '<div class="cl-part-wrap">'
                + '<div class="cl-part-bar-bg"><div class="cl-part-bar-fill" style="width:' + part + '%;background:' + pc + ';"></div></div>'
                + '<span class="cl-part-pct" style="color:' + pc + ';">' + part + '%</span>'
              + '</div>';
    
          return '<tr data-student="' + esc(n) + '">'
            + '<td style="width:27%;">'
              + '<div class="cl-student-cell">'
                + '<div class="cl-avatar" style="background:' + avatarBg + ';">' + esc(initials) + '</div>'
                + '<div style="min-width:0;flex:1;overflow:hidden;">'
                  + '<div class="cl-student-name" onclick="window.showPage&&window.showPage(\'studentprofile\')">' + esc(n) + '</div>'
                  + '<div class="cl-student-email">' + esc(email || tr('no_email')) + '</div>'
                + '</div>'
              + '</div>'
            + '</td>'
            + '<td style="width:14%;white-space:nowrap;">'
              + '<div style="font-size:.82rem;font-weight:600;color:' + ac + ';">' + fmtDate(last) + '</div>'
              + '<div class="cl-student-sub">' + cnt.toLocaleString('pt-BR') + ' ' + tr('interactions') + '</div>'
            + '</td>'
            + '<td style="width:8%;text-align:center;">'
              + '<span class="cl-days" style="color:' + ac + ';">' + ago + 'd</span>'
            + '</td>'
            + '<td style="width:22%;">' + col4Html + '</td>'
            + '<td style="width:10%;text-align:center;">'
              + '<span class="cl-time" style="color:' + tc + ';">' + fmtTime(ms) + '</span>'
            + '</td>'
            + '<td style="width:8%;text-align:center;">'
              + '<span class="cl-inter">' + cnt.toLocaleString('pt-BR') + '</span>'
            + '</td>'
            + '<td style="width:5%;padding-right:.75rem;">'
              + '<div class="cl-btn-group">'
                + '<button class="cl-profile-btn" title="' + esc(tr('cl_open_profile')) + '" '
                + 'data-gname="' + esc(n) + '" onclick="window.MWAGrades&&window.MWAGrades.openProfile(this)">👤</button>'
                + '<button class="cl-profile-btn" title=""  data-i18n-attr="title:cl_send_msg_title" '
                + 'data-sname="' + esc(n) + '" data-semail="' + esc(email || '') + '" '
                + 'onclick="window.MWAInterventions&&window.MWAInterventions.openByEl(this)">✉️</button>'
              + '</div>'
            + '</td>'
            + '</tr>';
        }).join('');
    
        var tb = document.getElementById('clTableBody');
        if (tb) Store.renderHtml(tb, html || '<tr><td colspan="7" class="cl-empty">' + esc(tr('no_data')) + '</td></tr>');
    
        // Atualiza count
        var cnt = document.getElementById('clCount');
        if (cnt) cnt.textContent = entries.length + ' ' + tr('students');
      }
    
      /* ── Toggle participation ↔ activities ── */
      function toggleClassListView() {
        CL_VIEW = CL_VIEW === 'participation' ? 'activities' : 'participation';
        var btn = document.getElementById('clToggleBtn');
        if (btn) {
          btn.textContent = CL_VIEW === 'activities'
            ? '📊 ' + tr('cl_view_participation')
            : '📋 ' + tr('cl_view_activities');
          btn.classList.toggle('active', CL_VIEW === 'activities');
        }
        renderClassList();
      }
    
      /* ── Filtro de busca (cliente, sem re-render) ── */
      function filterClassList(q) {
        var rows = document.querySelectorAll('#clTableBody tr');
        var term = (q || '').toLowerCase().trim();
        var visible = 0;
        rows.forEach(function (tr) {
          var name = (tr.dataset.student || '').toLowerCase();
          var show = !term || name.indexOf(term) !== -1;
          tr.style.display = show ? '' : 'none';
          if (show) visible++;
        });
        var cnt = document.getElementById('clCount');
        if (cnt) cnt.textContent = term
          ? visible + ' ' + tr('cl_result') + (visible !== 1 ? 's' : '')
          : (Object.keys({}).length || '') + ' ' + tr('students');
      }
    
      /* Public API */
      window.MWAClassList = {
        render: renderClassList,
        toggle: toggleClassListView,
        filter: filterClassList
      };
    
    })();

    return window.MWAClassList;
});
