/* ============================================================================
 * Progress Analytics — front-end controller.
 *
 * One data endpoint (ajax/data.php?action=dashboard) returns everything for the
 * current scope; this file renders the KPI tiles, three Chart.js charts and the
 * DataTable, and wires drill-down (click a school row -> that class; click a
 * class row -> that student). All numbers come from the server; this file only
 * presents them.
 * ========================================================================== */
(function () {
  'use strict';

  var API = 'ajax/data.php';

  // Design tokens — mirror the CSS custom properties (Chart.js needs real values).
  var COLOR = {
    series1: '#2a78d6', series1Soft: 'rgba(42,120,214,.14)',
    series2: '#eb6834', series2Soft: 'rgba(235,104,52,.14)',
    grid: '#e1e0d9', axis: '#c3c2b7', muted: '#898781', ink: '#0b0b0b'
  };
  // Ordered A->W performance ramp (grade is redundantly labelled on the axis,
  // so colour reinforces rather than solely carries meaning).
  var GRADE_COLORS = {
    A: '#0ca30c', B: '#1baf7a', C: '#eda100', D: '#eb6834',
    E: '#e34948', F: '#d03b3b', W: '#b23b3b', U: '#9a3b3b', '?': '#9aa0a6'
  };

  var charts = {}; // live Chart instances by key
  var dt = null;   // DataTable instance
  var lastTable = null; // last breakdown payload, for CSV export
  var state = {
    level: 'school', year: '', term: '', cls: '', subject: '', student: ''
  };

  // ── DOM helpers ───────────────────────────────────────────────────────────
  function $(id) { return document.getElementById(id); }
  function fmt(v, dp) {
    if (v === null || v === undefined || v === '') return '—';
    if (typeof v === 'number') return v.toFixed(dp === undefined ? 1 : dp);
    return String(v);
  }
  function esc(s) {
    return String(s == null ? '' : s).replace(/[&<>"']/g, function (c) {
      return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c];
    });
  }

  // ── Fetch ─────────────────────────────────────────────────────────────────
  function api(params) {
    var qs = Object.keys(params).map(function (k) {
      return encodeURIComponent(k) + '=' + encodeURIComponent(params[k]);
    }).join('&');
    return fetch(API + '?' + qs, { credentials: 'same-origin' }).then(function (r) {
      return r.json();
    });
  }

  // ── Scope <-> filter UI ───────────────────────────────────────────────────
  function readFilters() {
    state.year = $('paYear').value;
    state.term = $('paTerm').value;
    state.cls = $('paClass').value;
    state.subject = $('paSubject').value;
    state.student = $('paStudent').value;
  }

  function applyLevelUI() {
    // Toggle which filter fields are relevant to the active view.
    document.querySelectorAll('.pa-field-student')[0].style.display =
      state.level === 'student' ? '' : 'none';
    // Class field is a filter in school view, a selector in class/student view.
    var classField = document.querySelector('.pa-field-class');
    classField.querySelector('label').textContent =
      state.level === 'school' ? 'Class' : 'Class *';
    document.querySelectorAll('#paViewTabs button').forEach(function (b) {
      b.classList.toggle('active', b.dataset.level === state.level);
    });
  }

  function scopeParams() {
    var p = { action: 'dashboard', level: state.level };
    if (state.year) p.academic_year = state.year;
    if (state.term) p.term = state.term;
    if (state.subject) p.subjects = state.subject;
    if (state.level === 'school') {
      if (state.cls) p.classes = state.cls;
    } else if (state.level === 'class') {
      if (state.cls) p.classes = state.cls;
    } else if (state.level === 'student') {
      if (state.cls) p.classes = state.cls;
      if (state.student) p.student_id = state.student;
    }
    return p;
  }

  // ── Crumb / titles per level ──────────────────────────────────────────────
  function updateCrumbs(payload) {
    var crumb = $('paScopeCrumb');
    if (state.level === 'school') {
      crumb.innerHTML = 'Whole school overview';
    } else if (state.level === 'class') {
      crumb.innerHTML = 'Class' + (state.cls ? ' <span class="chip">' + esc(state.cls) + '</span>' : ' <span class="chip">select a class</span>');
    } else {
      var nm = payload && payload.student ? payload.student.name : '';
      crumb.innerHTML = 'Student' + (nm ? ' <span class="chip">' + esc(nm) + '</span>' : ' <span class="chip">select a student</span>');
    }
    var period = (state.term || 'All terms') + (state.year ? ' · ' + state.year : '');
    $('paPeriodCrumb').textContent = period;

    $('paTableTitle').textContent =
      state.level === 'school' ? 'Class breakdown'
      : state.level === 'class' ? 'Student breakdown'
      : 'Subject breakdown';
    $('paSubjTitle').textContent = 'Subject performance';
    $('paTrendTitle').textContent = 'Performance trend';
    $('paTrendHint').textContent =
      state.level === 'student' ? 'This student vs the class average, across terms'
      : 'Average score across terms';
  }

  // ── KPI tiles ─────────────────────────────────────────────────────────────
  function renderKpis(k) {
    var tiles = [
      { lbl: 'Average score', val: fmt(k.avg_score, 1), tab: true, sub: k.results + ' results' },
      { lbl: 'Pass rate', val: k.pass_rate == null ? '—' : fmt(k.pass_rate, 1) + '%', tab: true, sub: '≥ ' + fmt(k.pass_mark, 0) + ' marks' },
      { lbl: state.level === 'student' ? 'Best' : 'Top score', val: fmt(k.top_score, 1), tab: true, sub: 'Highest result' },
      { lbl: 'Lowest', val: fmt(k.low_score, 1), tab: true, sub: 'Lowest result' },
      { lbl: state.level === 'student' ? 'Subjects' : 'Students', val: state.level === 'student' ? k.results : k.students, tab: true, sub: state.level === 'student' ? 'graded' : 'with results' },
      { lbl: 'Data coverage', val: k.coverage_pct == null ? '—' : fmt(k.coverage_pct, 0) + '%', tab: true, sub: k.coverage_students + ' / ' + k.coverage_expected + ' students' }
    ];
    $('paKpis').innerHTML = tiles.map(function (t) {
      return '<div class="pa-kpi"><div class="lbl">' + esc(t.lbl) + '</div>' +
        '<div class="val' + (t.tab ? ' tabnum' : '') + '">' + esc(t.val) + '</div>' +
        '<div class="sub">' + esc(t.sub) + '</div></div>';
    }).join('');

    // Coverage warning: partial marks make averages unreliable.
    var warn = $('paCoverageWarn');
    if (k.coverage_pct != null && k.coverage_pct < 90 && k.coverage_expected > 0) {
      warn.style.display = 'block';
      warn.innerHTML = '<i class="fas fa-triangle-exclamation me-1"></i>Only ' +
        fmt(k.coverage_pct, 0) + '% of students in scope have marks entered — averages may not be representative yet.';
    } else {
      warn.style.display = 'none';
    }
    if (k.unrecorded_results > 0) {
      warn.style.display = 'block';
      warn.innerHTML = (warn.innerHTML || '') +
        '<div class="mt-1"><i class="fas fa-circle-info me-1"></i>' + k.unrecorded_results +
        ' result(s) have no class recorded (older data) and are excluded from class views.</div>';
    }
  }

  // ── Student header card ───────────────────────────────────────────────────
  function renderStudentHeader(payload) {
    var box = $('paStudentHeader');
    if (state.level !== 'student' || !payload.student) { box.style.display = 'none'; return; }
    var s = payload.student;
    box.style.display = 'block';
    box.innerHTML =
      '<div class="d-flex align-items-center gap-3">' +
      '<div class="rounded-circle bg-light d-flex align-items-center justify-content-center" style="width:52px;height:52px">' +
      '<i class="fas fa-user-graduate text-secondary fa-lg"></i></div>' +
      '<div><div class="fw-bold" style="font-size:1.05rem">' + esc(s.name) + '</div>' +
      '<div class="small text-muted">' + esc(s.admission_no || '') + ' · ' + esc(s.class || '') +
      (s.sex ? ' · ' + esc(s.sex) : '') + '</div></div></div>';
  }

  // ── Charts ────────────────────────────────────────────────────────────────
  function baseOpts(extra) {
    var o = {
      responsive: true, maintainAspectRatio: false,
      interaction: { mode: 'index', intersect: false },
      plugins: {
        legend: { display: false, labels: { color: COLOR.ink, boxWidth: 12, usePointStyle: true } },
        tooltip: { backgroundColor: '#111', padding: 10, cornerRadius: 8, titleColor: '#fff', bodyColor: '#eee' }
      },
      scales: {
        x: { grid: { display: false }, ticks: { color: COLOR.muted, font: { size: 11 } }, border: { color: COLOR.axis } },
        y: { beginAtZero: true, suggestedMax: 100, grid: { color: COLOR.grid }, ticks: { color: COLOR.muted, font: { size: 11 } }, border: { display: false } }
      }
    };
    return Object.assign(o, extra || {});
  }

  function renderTrend(trend) {
    var ds = trend.series.map(function (s, i) {
      var c = i === 0 ? COLOR.series1 : COLOR.series2;
      var soft = i === 0 ? COLOR.series1Soft : COLOR.series2Soft;
      return {
        label: s.name, data: s.data, borderColor: c, backgroundColor: soft,
        borderWidth: 2, tension: .3, fill: i === 0 && trend.series.length === 1,
        pointRadius: 4, pointHoverRadius: 6, pointBackgroundColor: c, spanGaps: true
      };
    });
    var showLegend = trend.series.length > 1;
    make('paTrendChart', 'line', { labels: trend.labels, datasets: ds },
      baseOpts({ plugins: { legend: { display: showLegend, position: 'top', align: 'end', labels: { color: COLOR.ink, boxWidth: 12, usePointStyle: true } },
        tooltip: { backgroundColor: '#111', padding: 10, cornerRadius: 8 } } }));
  }

  function renderSubjects(subjects) {
    var labels = subjects.map(function (s) { return s.subject; });
    var isStudent = state.level === 'student';
    var datasets = [{
      label: isStudent ? 'This student' : 'Average',
      data: subjects.map(function (s) { return s.avg_score; }),
      backgroundColor: COLOR.series1, borderRadius: 4, borderSkipped: false, barPercentage: .82, categoryPercentage: .8
    }];
    if (isStudent && subjects.some(function (s) { return s.class_avg != null; })) {
      datasets.push({
        label: 'Class average',
        data: subjects.map(function (s) { return s.class_avg; }),
        backgroundColor: COLOR.series2, borderRadius: 4, borderSkipped: false, barPercentage: .82, categoryPercentage: .8
      });
    }
    make('paSubjectChart', 'bar', { labels: labels, datasets: datasets },
      baseOpts({
        plugins: { legend: { display: datasets.length > 1, position: 'top', align: 'end', labels: { color: COLOR.ink, boxWidth: 12, usePointStyle: true } },
          tooltip: { backgroundColor: '#111', padding: 10, cornerRadius: 8 } },
        scales: {
          x: { grid: { display: false }, ticks: { color: COLOR.muted, font: { size: 10 }, maxRotation: 40, minRotation: 0 }, border: { color: COLOR.axis } },
          y: { beginAtZero: true, suggestedMax: 100, grid: { color: COLOR.grid }, ticks: { color: COLOR.muted }, border: { display: false } }
        }
      }));
  }

  function renderGrades(dist) {
    var colors = dist.labels.map(function (g) { return GRADE_COLORS[g] || COLOR.series1; });
    make('paGradeChart', 'bar', {
      labels: dist.labels,
      datasets: [{ label: 'Results', data: dist.counts, backgroundColor: colors, borderRadius: 4, borderSkipped: false }]
    }, baseOpts({
      scales: {
        x: { grid: { display: false }, ticks: { color: COLOR.ink, font: { weight: '700' } }, border: { color: COLOR.axis } },
        y: { beginAtZero: true, grid: { color: COLOR.grid }, ticks: { color: COLOR.muted, precision: 0 }, border: { display: false } }
      }
    }));
  }

  function make(id, type, data, options) {
    if (charts[id]) { charts[id].destroy(); }
    charts[id] = new Chart($(id).getContext('2d'), { type: type, data: data, options: options });
  }

  // ── DataTable ─────────────────────────────────────────────────────────────
  var NUM_KEYS = { avg_score: 1, pass_rate: 1, top_score: 1, total_score: 1, class_score: 1, exam_score: 1, students: 0, results: 0, subjects: 0, position: 0 };

  function renderTable(table) {
    var cols = table.columns, rows = table.rows;
    $('paTableHead').innerHTML = cols.map(function (c) {
      var num = (c.key in NUM_KEYS) ? ' class="num"' : '';
      return '<th' + num + '>' + esc(c.label) + '</th>';
    }).join('');

    var drillLevel = state.level === 'school' ? 'class' : state.level === 'class' ? 'student' : null;

    var body = rows.map(function (r) {
      var attrs = '';
      if (drillLevel === 'class' && r.class_name && r.class_name.indexOf('(') !== 0) {
        attrs = ' class="pa-drill" data-drill="class" data-class="' + esc(r.class_name) + '"';
      } else if (drillLevel === 'student' && r.student_id) {
        attrs = ' class="pa-drill" data-drill="student" data-student="' + r.student_id + '" data-class="' + esc(r.class_name || state.cls) + '"';
      }
      var tds = cols.map(function (c) {
        var v = r[c.key];
        var num = (c.key in NUM_KEYS);
        if (c.key === 'grade' && v) {
          var gc = GRADE_COLORS[v] || '#666';
          return '<td><span class="pa-badge-grade" style="background:' + gc + '">' + esc(v) + '</span></td>';
        }
        if (num) v = fmt(v, NUM_KEYS[c.key]);
        else v = esc(v == null ? '—' : v);
        return '<td' + (num ? ' class="num"' : '') + '>' + v + '</td>';
      }).join('');
      return '<tr' + attrs + '>' + tds + '</tr>';
    }).join('');

    $('paTableBody').innerHTML = body || '';
    $('paTableHint').textContent = rows.length + ' row' + (rows.length === 1 ? '' : 's');

    if (!rows.length) {
      $('paTableBody').innerHTML = '<tr><td colspan="' + cols.length + '" class="pa-empty">No results for this selection.</td></tr>';
      if (dt) { try { dt.destroy(); } catch (e) {} dt = null; }
      return;
    }

    // DataTables is an enhancement, not a requirement: the plain table is
    // already in the DOM above. Only upgrade it when jQuery + the plugin are
    // actually present, and never let a plugin error break the page.
    var jq = window.jQuery;
    if (jq && jq.fn && typeof jq.fn.DataTable === 'function') {
      try {
        if (dt) { dt.destroy(); dt = null; }
        dt = jq('#paTable').DataTable({
          paging: rows.length > 15, searching: rows.length > 8, info: false,
          lengthChange: false, pageLength: 15, order: [],
          language: { search: '', searchPlaceholder: 'Filter…' }
        });
      } catch (e) {
        if (window.console) { console.warn('[progress] DataTable enhancement skipped', e); }
      }
    }
  }

  // ── Load ──────────────────────────────────────────────────────────────────
  function load() {
    readFilters();
    // Guard: class/student views need a class chosen.
    if ((state.level === 'class' || state.level === 'student') && !state.cls) {
      $('paScopeCrumb').innerHTML = state.level === 'class'
        ? 'Class <span class="chip">select a class above</span>'
        : 'Student <span class="chip">select a class &amp; student</span>';
      return;
    }
    if (state.level === 'student' && !state.student) {
      updateCrumbs(null); return;
    }

    $('paApply').disabled = true;
    api(scopeParams()).then(function (res) {
      $('paApply').disabled = false;
      if (!res || !res.success) { return; }
      lastTable = res.table;

      // Each section renders independently: one failing renderer (e.g. a
      // blocked DataTables/jQuery CDN throwing inside renderTable) must never
      // stop the others — that was the original "cohort panels always empty"
      // bug. safe() isolates and logs instead of cascading into the .catch.
      safe('crumbs', function () { updateCrumbs(res); });
      safe('kpis', function () { renderKpis(res.kpis); });
      safe('studentHeader', function () { renderStudentHeader(res); });
      safe('trend', function () { renderTrend(res.trend); });
      safe('subjects', function () { renderSubjects(res.subjects); });
      safe('grades', function () { renderGrades(res.distribution); });

      // P2 cohort panels — school/class only. Rendered BEFORE the DataTable so a
      // table-library failure can't hide them.
      var cohort = state.level !== 'student';
      document.querySelectorAll('.pa-cohort').forEach(function (el) {
        el.style.display = cohort ? '' : 'none';
      });
      if (cohort) {
        safe('movement', function () { renderMovement(res.movement); });
        safe('attendance', function () { renderAttendance(res.attendance); });
        safe('heatmap', function () { renderHeatmap(res.heatmap); });
      }

      safe('table', function () { renderTable(res.table); });
      // A new view invalidates any previous AI summary.
      safe('aiReset', resetAiSummary);
    }).catch(function (e) {
      $('paApply').disabled = false;
      if (window.console) { console.error('[progress] load failed', e); }
    });
  }

  // Run a render step in isolation so a failure logs rather than cascading.
  function safe(label, fn) {
    try { fn(); }
    catch (e) { if (window.console) { console.error('[progress] ' + label + ' failed', e); } }
  }

  /* ── P4: AI summary (premium) ──────────────────────────────────────────── */
  function resetAiSummary() {
    var body = $('paAiBody'), meta = $('paAiMeta');
    if (!body) { return; }
    body.classList.remove('loading');
    body.innerHTML = '<span class="text-muted small">Generate a plain-English briefing of the current view.</span>';
    if (meta) { meta.textContent = ''; }
  }

  function generateAiSummary() {
    var body = $('paAiBody'), meta = $('paAiMeta'), btn = $('paAiBtn');
    if (!body) { return; }
    body.classList.add('loading');
    body.textContent = 'Writing a summary of the current view…';
    if (meta) { meta.textContent = ''; }
    if (btn) { btn.disabled = true; }

    var p = scopeParams();
    p.action = 'insight';
    api(p).then(function (res) {
      if (btn) { btn.disabled = false; }
      body.classList.remove('loading');
      if (!res || !res.success) {
        body.innerHTML = '<span class="text-muted small">' + esc((res && res.message) || 'Could not generate a summary.') + '</span>';
        return;
      }
      // The model returns short sentences or bullet lines; render "- " lines as a list.
      body.innerHTML = aiNarrativeHtml(res.narrative || '');
      if (meta && res.provider) { meta.textContent = 'Generated by AI (' + esc(res.provider) + '). Always verify against the figures above.'; }
    }).catch(function () {
      if (btn) { btn.disabled = false; }
      body.classList.remove('loading');
      body.innerHTML = '<span class="text-muted small">Could not reach the AI service.</span>';
    });
  }

  function aiNarrativeHtml(text) {
    var lines = String(text).split(/\r?\n/).map(function (l) { return l.trim(); }).filter(Boolean);
    var bullets = lines.filter(function (l) { return /^[-*•]/.test(l); });
    if (bullets.length >= 2 && bullets.length === lines.length) {
      return '<ul>' + lines.map(function (l) {
        return '<li>' + esc(l.replace(/^[-*•]\s*/, '')) + '</li>';
      }).join('') + '</ul>';
    }
    return lines.map(esc).join('<br>');
  }

  /* ── P2: Rank movement ─────────────────────────────────────────────────── */
  function renderMovement(mv) {
    var body = $('paMovementBody');
    if (!mv || !mv.to || (!mv.improved.length && !mv.declined.length)) {
      body.innerHTML = '<div class="pa-empty">Needs two terms of data to show movement.</div>';
      $('paMovementHint').textContent = 'Change in average between terms';
      return;
    }
    $('paMovementHint').textContent = 'Change from ' + mv.from.label + ' to ' + mv.to.label;
    function col(title, list, dir) {
      var rows = list.map(function (d) {
        var sign = d.delta > 0 ? '+' : '';
        return '<div class="pa-move-row"><div><div class="pa-move-name">' + esc(d.name) + '</div>' +
          '<div class="pa-move-sub">' + esc(d.class_name || '') + ' · ' + fmt(d.from, 1) + ' → ' + fmt(d.to, 1) + '</div></div>' +
          '<span class="pa-delta ' + dir + '">' + sign + fmt(d.delta, 1) + '</span></div>';
      }).join('') || '<div class="pa-move-sub text-muted py-2">None</div>';
      return '<div class="pa-move-col"><h6 class="mt-1">' + title + '</h6>' + rows + '</div>';
    }
    body.innerHTML = '<div class="row"><div class="col-6">' +
      col('▲ Improved', mv.improved, 'up') + '</div><div class="col-6">' +
      col('▼ Declined', mv.declined, 'down') + '</div></div>';
  }

  /* ── P2: Attendance vs score scatter ───────────────────────────────────── */
  function renderAttendance(att) {
    var note = $('paAttNote');
    if (!att || !att.points || !att.points.length) {
      if (charts.paAttChart) { charts.paAttChart.destroy(); delete charts.paAttChart; }
      $('paAttChart').getContext('2d').clearRect(0, 0, 9999, 9999);
      note.textContent = att && !att.has_window
        ? 'Pick a specific term to line attendance up with scores.'
        : 'No attendance recorded for this period.';
      return;
    }
    var pts = att.points.map(function (p) {
      return { x: p.attendance, y: p.score, name: p.name };
    });
    make('paAttChart', 'scatter', {
      datasets: [{
        data: pts, backgroundColor: COLOR.series1,
        pointRadius: 5, pointHoverRadius: 7, borderColor: '#fff', borderWidth: 1
      }]
    }, {
      responsive: true, maintainAspectRatio: false,
      plugins: {
        legend: { display: false },
        tooltip: {
          backgroundColor: '#111', padding: 10, cornerRadius: 8,
          callbacks: {
            label: function (c) {
              return c.raw.name + ' — ' + c.raw.x + '% present, ' + c.raw.y + ' avg';
            }
          }
        }
      },
      scales: {
        x: { title: { display: true, text: 'Attendance %', color: COLOR.muted }, min: 0, max: 100,
             grid: { color: COLOR.grid }, ticks: { color: COLOR.muted }, border: { color: COLOR.axis } },
        y: { title: { display: true, text: 'Average score', color: COLOR.muted }, beginAtZero: true, suggestedMax: 100,
             grid: { color: COLOR.grid }, ticks: { color: COLOR.muted }, border: { display: false } }
      }
    });
    if (att.correlation == null) {
      note.textContent = 'Not enough students to measure a correlation.';
    } else {
      var r = att.correlation, strength =
        Math.abs(r) < 0.2 ? 'no' : Math.abs(r) < 0.4 ? 'a weak' : Math.abs(r) < 0.6 ? 'a moderate' : 'a strong';
      note.textContent = 'Correlation r = ' + r + ' — ' + strength + ' ' +
        (r >= 0 ? 'positive' : 'negative') + ' link between attendance and scores.';
    }
  }

  /* ── P2: Heatmap (blue sequential ramp; numbers are in every cell) ──────── */
  function heatColor(score) {
    if (score == null) return null;
    var t = Math.max(0, Math.min(1, score / 100));
    // Interpolate #cde2fb (low) → #0d366b (high) in RGB.
    var lo = [205, 226, 251], hi = [13, 54, 107];
    var r = Math.round(lo[0] + (hi[0] - lo[0]) * t);
    var g = Math.round(lo[1] + (hi[1] - lo[1]) * t);
    var b = Math.round(lo[2] + (hi[2] - lo[2]) * t);
    return { bg: 'rgb(' + r + ',' + g + ',' + b + ')', ink: t > 0.55 ? '#fff' : '#0b0b0b' };
  }

  function renderHeatmap(hm) {
    var wrap = $('paHeatWrap');
    if (!hm || !hm.rows.length || !hm.subjects.length) {
      wrap.innerHTML = '<div class="pa-empty">No data to chart for this selection.</div>';
      $('paHeatLegend').textContent = '';
      return;
    }
    $('paHeatTitle').textContent = hm.row_label + ' × subject heatmap';
    $('paHeatLegend').innerHTML = 'weaker <span style="display:inline-block;width:90px;height:10px;vertical-align:middle;' +
      'background:linear-gradient(90deg,#cde2fb,#0d366b);border-radius:3px;margin:0 6px"></span> stronger';

    var head = '<tr><th></th>' + hm.subjects.map(function (s) {
      return '<th class="rot"><div>' + esc(s.name) + '</div></th>';
    }).join('') + '<th class="rowavg">Avg</th></tr>';

    var body = hm.rows.map(function (r) {
      var tds = hm.subjects.map(function (s) {
        var v = r.cells[s.id];
        if (v == null) return '<td class="cell empty">·</td>';
        var c = heatColor(v);
        return '<td class="cell" style="background:' + c.bg + ';color:' + c.ink + '" title="' + esc(r.name) + ' — ' + esc(s.name) + ': ' + fmt(v, 1) + '">' + fmt(v, 0) + '</td>';
      }).join('');
      var avgc = heatColor(r.avg);
      var avgCell = avgc ? '<td class="rowavg" style="color:' + (r.avg >= 55 ? avgc.bg : '#0b0b0b') + '">' + fmt(r.avg, 1) + '</td>' : '<td class="rowavg">—</td>';
      var drill = hm.mode === 'class'
        ? ' class="pa-drill" data-heatclass="' + esc(r.name) + '"'
        : '';
      return '<tr' + drill + '><td class="rowname">' + esc(r.name) + '</td>' + tds + avgCell + '</tr>';
    }).join('');

    wrap.innerHTML = '<table class="pa-heat">' + head + body + '</table>' +
      (hm.truncated ? '<div class="small text-muted mt-2">Showing the top ' + hm.rows.length + ' rows.</div>' : '');
  }

  /* ── P2: CSV export of the current breakdown table ─────────────────────── */
  function exportCsv() {
    if (!lastTable || !lastTable.rows.length) { return; }
    var cols = lastTable.columns;
    var head = cols.map(function (c) { return csvCell(c.label); }).join(',');
    var lines = lastTable.rows.map(function (r) {
      return cols.map(function (c) { return csvCell(r[c.key]); }).join(',');
    });
    var csv = [head].concat(lines).join('\r\n');
    var blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
    var a = document.createElement('a');
    var name = 'progress_' + state.level + (state.cls ? '_' + state.cls.replace(/\s+/g, '') : '') +
      (state.term ? '_' + state.term.replace(/\s+/g, '') : '') + '.csv';
    a.href = URL.createObjectURL(blob);
    a.download = name;
    document.body.appendChild(a); a.click(); document.body.removeChild(a);
  }
  function csvCell(v) {
    if (v == null) return '';
    var s = String(v);
    return /[",\r\n]/.test(s) ? '"' + s.replace(/"/g, '""') + '"' : s;
  }

  /* ── PDF export (server-rendered via Dompdf) ───────────────────────────── */
  function exportPdf() {
    if (!lastTable) { return; } // nothing loaded yet
    var p = scopeParams();
    delete p.action; // export_pdf.php is its own endpoint
    var qs = Object.keys(p).map(function (k) {
      return encodeURIComponent(k) + '=' + encodeURIComponent(p[k]);
    }).join('&');
    window.open('export_pdf.php?' + qs, '_blank');
  }

  /* ── P2: Compare mode ──────────────────────────────────────────────────── */
  function runCompare() {
    var by = $('paCompareBy').value;
    readFilters();
    var p = { action: 'compare', level: state.level === 'student' ? 'class' : state.level, compare_by: by };
    if (state.year) p.academic_year = state.year;
    if (state.term) p.term = state.term;
    if (state.subject) p.subjects = state.subject;
    if (state.cls) p.classes = state.cls;
    if (by === 'class') {
      p.b_class = $('paCompareBClass').value;
      if (!p.classes) { $('paCompareResult').innerHTML = '<div class="pa-empty">Choose a primary class in the filters above first.</div>'; return; }
      if (!p.b_class) { $('paCompareResult').innerHTML = '<div class="pa-empty">Pick a class to compare against.</div>'; return; }
    } else {
      p.b_term = $('paCompareBTerm').value;
      if (!p.term) { $('paCompareResult').innerHTML = '<div class="pa-empty">Pick a primary term in the filters above first.</div>'; return; }
      if (!p.b_term) { $('paCompareResult').innerHTML = '<div class="pa-empty">Pick a term to compare against.</div>'; return; }
    }
    $('paCompareResult').innerHTML = '<div class="pa-empty">Comparing…</div>';
    api(p).then(function (res) {
      if (!res || !res.success) { $('paCompareResult').innerHTML = '<div class="pa-empty">Comparison failed.</div>'; return; }
      renderCompare(res);
    });
  }

  function renderCompare(res) {
    function metric(label, a, b, unit, hi) {
      // hi=true -> higher is better; mark the winner.
      var aWin = '', bWin = '';
      if (a != null && b != null && a !== b) {
        var aBetter = hi ? a > b : a < b;
        aWin = aBetter ? 'pa-cmp-win' : 'pa-cmp-lose';
        bWin = aBetter ? 'pa-cmp-lose' : 'pa-cmp-win';
      }
      return { label: label, a: a, b: b, unit: unit || '', aWin: aWin, bWin: bWin };
    }
    var ka = res.a.kpis, kb = res.b.kpis;
    var metrics = [
      metric('Average', ka.avg_score, kb.avg_score, '', true),
      metric('Pass rate', ka.pass_rate, kb.pass_rate, '%', true),
      metric('Top score', ka.top_score, kb.top_score, '', true),
      metric('Students', ka.students, kb.students, '', true),
      metric('Coverage', ka.coverage_pct, kb.coverage_pct, '%', true)
    ];
    function side(which) {
      var lbl = which === 'a' ? res.a.label : res.b.label;
      var rows = metrics.map(function (m) {
        var v = m[which], win = m[which + 'Win'];
        return '<div class="pa-cmp-metric"><span>' + esc(m.label) + '</span>' +
          '<span class="v ' + win + '">' + (v == null ? '—' : fmt(v, 1) + m.unit) + '</span></div>';
      }).join('');
      return '<div class="pa-cmp-side"><div class="name">' + esc(lbl) + '</div>' + rows + '</div>';
    }
    $('paCompareResult').innerHTML = '<div class="pa-cmp-grid">' + side('a') + side('b') + '</div>';
  }

  // ── Student picker population ─────────────────────────────────────────────
  function loadStudents(cls) {
    var sel = $('paStudent');
    if (!cls) { sel.innerHTML = '<option value="">Select a class first…</option>'; return; }
    sel.innerHTML = '<option value="">Loading…</option>';
    api({ action: 'students', class: cls }).then(function (res) {
      var opts = '<option value="">Select a student…</option>';
      (res.students || []).forEach(function (s) {
        opts += '<option value="' + s.id + '">' + esc(s.label) + '</option>';
      });
      sel.innerHTML = opts;
    });
  }

  // ── Wiring ────────────────────────────────────────────────────────────────
  function switchLevel(level) {
    state.level = level;
    applyLevelUI();
    if (level === 'student' && $('paClass').value) { loadStudents($('paClass').value); }
    load();
  }

  document.addEventListener('DOMContentLoaded', function () {
    applyLevelUI();

    document.querySelectorAll('#paViewTabs button').forEach(function (b) {
      b.addEventListener('click', function () { switchLevel(b.dataset.level); });
    });
    $('paApply').addEventListener('click', load);
    $('paClass').addEventListener('change', function () {
      if (state.level === 'student') { loadStudents(this.value); }
    });
    $('paStudent').addEventListener('change', load);
    [ 'paYear', 'paTerm', 'paSubject' ].forEach(function (id) {
      $(id).addEventListener('change', function () { if (state.level === 'school') load(); });
    });

    // Drill-down: click a table row to descend a level.
    $('paTable').addEventListener('click', function (e) {
      var tr = e.target.closest('tr.pa-drill');
      if (!tr) return;
      if (tr.dataset.drill === 'class') {
        $('paClass').value = tr.dataset.class;
        switchLevel('class');
      } else if (tr.dataset.drill === 'student') {
        var cls = tr.dataset.class;
        if (cls) $('paClass').value = cls;
        state.level = 'student'; applyLevelUI();
        loadStudentsThen(cls, tr.dataset.student);
      }
    });

    // Heatmap drill-down: click a class row (school view) to open that class.
    $('paHeatWrap').addEventListener('click', function (e) {
      var tr = e.target.closest('tr[data-heatclass]');
      if (!tr) return;
      $('paClass').value = tr.dataset.heatclass;
      switchLevel('class');
    });

    // CSV + PDF export.
    $('paExportCsv').addEventListener('click', exportCsv);
    var pdfBtn = $('paExportPdf');
    if (pdfBtn) { pdfBtn.addEventListener('click', exportPdf); }

    // AI summary (premium; button only present when licensed).
    var aiBtn = $('paAiBtn');
    if (aiBtn) { aiBtn.addEventListener('click', generateAiSummary); }

    // Compare controls.
    $('paCompareBy').addEventListener('change', function () {
      var byTerm = this.value === 'term';
      $('paCompareBClassWrap').style.display = byTerm ? 'none' : '';
      $('paCompareBTermWrap').style.display = byTerm ? '' : 'none';
    });
    $('paCompareRun').addEventListener('click', runCompare);

    load(); // initial school view
  });

  // Populate the student picker, preselect, then load.
  function loadStudentsThen(cls, studentId) {
    var sel = $('paStudent');
    sel.innerHTML = '<option value="">Loading…</option>';
    api({ action: 'students', class: cls }).then(function (res) {
      var opts = '<option value="">Select a student…</option>';
      (res.students || []).forEach(function (s) {
        opts += '<option value="' + s.id + '"' + (String(s.id) === String(studentId) ? ' selected' : '') + '>' + esc(s.label) + '</option>';
      });
      sel.innerHTML = opts;
      load();
    });
  }
})();
