<?php
// /elms/lecturer/assessments/grade.php — Lecturer grading page (hardened)
// - Works with student table named `studentss` (and other common variants)
// - Safely resolves student names (full_name / student_name / name / first+last)
// - Uses LEFT JOIN everywhere to avoid hiding rows if a student record is missing
// - Keeps your existing UI (scale, analytics, CAC template, voice feedback, report modal)

declare(strict_types=1);
@header('Content-Type: text/html; charset=utf-8');
if (session_status() === PHP_SESSION_NONE) session_start();
date_default_timezone_set('Asia/Colombo');

$DEBUG = isset($_GET['debug']) && $_GET['debug'] === '1';
if ($DEBUG) { ini_set('display_errors','1'); ini_set('display_startup_errors','1'); error_reporting(E_ALL); }
else { error_reporting(E_ALL & ~E_NOTICE & ~E_WARNING & ~E_DEPRECATED); }

/* ---- auth ---- */
$role = strtolower((string)($_SESSION['user']['role'] ?? $_SESSION['role'] ?? ''));
if (!in_array($role, ['lecturer','course_coordinator','coordinator','admin','super_admin','branch_admin'], true)) {
  http_response_code(403);
  echo 'Forbidden';
  exit;
}

/* ---- utils ---- */
function esc($s){ return htmlspecialchars((string)$s, ENT_QUOTES|ENT_SUBSTITUTE, 'UTF-8'); }
function ensure_dir(string $p){ if(!is_dir($p)) @mkdir($p,0775,true); }
function file_ext($p){ return strtolower((string)pathinfo((string)$p, PATHINFO_EXTENSION)); }
function table_exists(mysqli $db, string $t): bool {
  $t = $db->real_escape_string($t);
  $r = $db->query("SHOW TABLES LIKE '{$t}'");
  $ok = $r && $r->num_rows>0; if ($r) $r->free();
  return $ok;
}
function col_exists(mysqli $db,string $t,string $c):bool{
  $t = str_replace('`','``',$t); $c = $db->real_escape_string($c);
  $r = $db->query("SHOW COLUMNS FROM `$t` LIKE '$c'");
  $ok = $r && $r->num_rows>0; if($r) $r->free(); return $ok;
}

/* ---- env / paths ---- */
$ROOT = rtrim($_SERVER['DOCUMENT_ROOT'] ?? dirname(__DIR__,2), '/');
$script = (string)($_SERVER['SCRIPT_NAME'] ?? '/elms/lecturer/assessments/grade.php');
$parts  = explode('/', trim($script,'/'));           // ["elms", ...]
$rootWeb = '/'.($parts[0] ?? 'elms');
$docRoot = rtrim((string)($_SERVER['DOCUMENT_ROOT'] ?? ''), '/');

/* ---- DB include ---- */
$tried=[]; $try=function(string $p)use(&$tried){ $tried[]=$p; if(is_file($p)){ require_once $p; return true; } return false; };
$try($ROOT.'/elms/includes/db.php') || $try(__DIR__.'/../../includes/db.php') || $try(__DIR__.'/../includes/db.php');

if (!isset($mysqli) || !($mysqli instanceof mysqli)) {
  if (isset($conn) && $conn instanceof mysqli) $mysqli = $conn;
}
if (!isset($mysqli) || !($mysqli instanceof mysqli)) {
  http_response_code(500);
  echo "<h3>DB not initialized</h3><pre>".esc(implode("\n",$tried))."</pre>";
  exit;
}
@$mysqli->set_charset('utf8mb4');

/* ---- dynamic column map (lms_submissions) ---- */
$C_SCORE  = col_exists($mysqli,'lms_submissions','final_score') ? 'final_score' : (col_exists($mysqli,'lms_submissions','score') ? 'score' : null);
$C_TOTAL  = col_exists($mysqli,'lms_submissions','total_marks') ? 'total_marks' : null;
$C_OBT    = col_exists($mysqli,'lms_submissions','obtained_marks') ? 'obtained_marks' : null;
$C_GRADE  = col_exists($mysqli,'lms_submissions','grade_letter') ? 'grade_letter' : null;
$C_STATUS = col_exists($mysqli,'lms_submissions','workflow_status') ? 'workflow_status' : (col_exists($mysqli,'lms_submissions','status')? 'status' : null);
$C_FEED   = col_exists($mysqli,'lms_submissions','feedback_html') ? 'feedback_html' : (col_exists($mysqli,'lms_submissions','feedback') ? 'feedback' : (col_exists($mysqli,'lms_submissions','remarks') ? 'remarks' : null));
$C_FILE   = col_exists($mysqli,'lms_submissions','file_path') ? 'file_path' : (col_exists($mysqli,'lms_submissions','submission_file') ? 'submission_file' : null);
$C_GRADED = col_exists($mysqli,'lms_submissions','graded_at') ? 'graded_at' : null;
$C_PUB    = col_exists($mysqli,'lms_submissions','is_published') ? 'is_published' : (col_exists($mysqli,'lms_submissions','published') ? 'published' : null);
$C_DRAFT  = col_exists($mysqli,'lms_submissions','is_draft') ? 'is_draft' : null;
$C_FEEDF  = col_exists($mysqli,'lms_submissions','feedback_file') ? 'feedback_file' : null;
$C_VOICE  = col_exists($mysqli,'lms_submissions','voice_feedback') ? 'voice_feedback' : null;
$HAS_SUBMITTED_AT = col_exists($mysqli,'lms_submissions','submitted_at');

/* ---- inputs ---- */
$id = (int)($_GET['id'] ?? 0);
if ($id<=0){ echo 'Invalid submission.'; exit; }

/* ---- detect students table + name/ref columns (includes studentss) ---- */
$studentTable = null;
foreach (['studentss','students','lms_students','student','lms_student'] as $cand) {
  if (table_exists($mysqli,$cand)) { $studentTable = $cand; break; }
}

$stuFirst = $stuLast = null;
$stuNameCol = 'id';
if ($studentTable) {
  if (col_exists($mysqli,$studentTable,'full_name'))        $stuNameCol = 'full_name';
  elseif (col_exists($mysqli,$studentTable,'student_name')) $stuNameCol = 'student_name';
  elseif (col_exists($mysqli,$studentTable,'name'))         $stuNameCol = 'name';
  elseif (col_exists($mysqli,$studentTable,'first_name') || col_exists($mysqli,$studentTable,'last_name')) {
    $stuFirst = col_exists($mysqli,$studentTable,'first_name') ? 'first_name' : null;
    $stuLast  = col_exists($mysqli,$studentTable,'last_name')  ? 'last_name'  : null;
  }
}
$stuNameExpr = ($stuFirst || $stuLast)
  ? "TRIM(CONCAT(COALESCE(st.`$stuFirst`,''),' ',COALESCE(st.`$stuLast`,'')))"
  : "st.`$stuNameCol`";

$stuRefCol = null;
foreach (['reference_no','ref_no','student_ref','ref'] as $cand) {
  if ($studentTable && col_exists($mysqli,$studentTable,$cand)) { $stuRefCol=$cand; break; }
}

/* ---- load submission + assessment + (optional) student ---- */
$assTitleCol = col_exists($mysqli,'lms_assessments','title') ? 'title' : (col_exists($mysqli,'lms_assessments','name')?'name':'id');

$sub = null; $fatalMsg = null;

if ($studentTable) {
  $selectRef = $stuRefCol ? ", st.`$stuRefCol` AS student_ref" : "";
  $sql="SELECT s.*, a.`$assTitleCol` AS assessment_title, a.id AS assessment_id,
               $stuNameExpr AS student_name, st.id AS student_id $selectRef
        FROM lms_submissions s
        JOIN lms_assessments a ON a.id=s.assessment_id
        LEFT JOIN $studentTable st ON st.id=s.student_id
        WHERE s.id=?";
} else {
  $sql="SELECT s.*, a.`$assTitleCol` AS assessment_title, a.id AS assessment_id
        FROM lms_submissions s
        JOIN lms_assessments a ON a.id=s.assessment_id
        WHERE s.id=?";
}
if ($st=$mysqli->prepare($sql)) {
  $st->bind_param('i',$id);
  if ($st->execute()) { $sub=$st->get_result()->fetch_assoc(); }
  $st->close();
} else {
  $fatalMsg = 'Query prepare failed: '.$mysqli->error;
}
if(!$sub){ echo 'Not found.'; exit; }

$assessment_id = (int)$sub['assessment_id'];
$student_id    = (int)($sub['student_id'] ?? 0);

/* ---- next submission for Save & Next ---- */
$next_id=null;
if ($HAS_SUBMITTED_AT){
  if ($st=$mysqli->prepare("SELECT s2.id FROM lms_submissions s2
                            WHERE s2.assessment_id=? AND (s2.submitted_at > ? OR (s2.submitted_at = ? AND s2.id > ?))
                            ORDER BY s2.submitted_at, s2.id LIMIT 1")) {
    $sa=$sub['submitted_at'] ?? '1970-01-01 00:00:00';
    $st->bind_param('issi',$assessment_id,$sa,$sa,$id);
    $st->execute(); $r=$st->get_result()->fetch_assoc(); $st->close();
    if ($r) $next_id=(int)$r['id'];
  }
} else {
  if ($st=$mysqli->prepare("SELECT s2.id FROM lms_submissions s2 WHERE s2.assessment_id=? AND s2.id > ? ORDER BY s2.id LIMIT 1")){
    $st->bind_param('ii',$assessment_id,$id);
    $st->execute(); $r=$st->get_result()->fetch_assoc(); $st->close();
    if ($r) $next_id=(int)$r['id'];
  }
}

/* ---- student list (left panel quick switch) ---- */
$students=[];
if ($studentTable) {
  $q = "SELECT s.id,
               COALESCE($stuNameExpr, CONCAT('#',s.student_id)) AS nm,
               s.student_id
        FROM lms_submissions s
        LEFT JOIN $studentTable st ON st.id=s.student_id
        WHERE s.assessment_id=?
        ".($HAS_SUBMITTED_AT?"ORDER BY s.submitted_at, s.id":"ORDER BY s.id");
} else {
  $q = "SELECT s.id, CONCAT('#',s.student_id) AS nm, s.student_id
        FROM lms_submissions s
        WHERE s.assessment_id=?
        ".($HAS_SUBMITTED_AT?"ORDER BY s.submitted_at, s.id":"ORDER BY s.id");
}
if ($st=$mysqli->prepare($q)) {
  $st->bind_param('i',$assessment_id);
  $st->execute();
  $students = $st->get_result()->fetch_all(MYSQLI_ASSOC);
  $st->close();
}

/* ---- analytics (this assessment) ---- */
$avg=0; $hi=null; $lo=null; $cnt=0;
if ($C_OBT || $C_SCORE){
  $q = $C_OBT && $C_TOTAL
       ? "SELECT AVG(CASE WHEN $C_TOTAL>0 THEN 100*$C_OBT/$C_TOTAL ELSE NULL END) AS avgp,
                 MAX(CASE WHEN $C_TOTAL>0 THEN 100*$C_OBT/$C_TOTAL ELSE NULL END) AS hip,
                 MIN(CASE WHEN $C_TOTAL>0 THEN 100*$C_OBT/$C_TOTAL ELSE NULL END) AS lop,
                 COUNT(*) AS c FROM lms_submissions WHERE assessment_id=?"
       : "SELECT AVG($C_SCORE) AS avgp, MAX($C_SCORE) AS hip, MIN($C_SCORE) AS lop, COUNT(*) AS c
          FROM lms_submissions WHERE assessment_id=?";
  if ($st=$mysqli->prepare($q)) {
    $st->bind_param('i',$assessment_id);
    $st->execute(); $A=$st->get_result()->fetch_assoc(); $st->close();
    if ($A){ $avg=(float)$A['avgp']; $hi=$A['hip']; $lo=$A['lop']; $cnt=(int)$A['c']; }
  }
}

/* ---- student history ---- */
$history=[];
if ($C_OBT && $C_TOTAL){
  $q="SELECT s.id, a.`$assTitleCol` AS atitle, s.`$C_OBT` AS obt, s.`$C_TOTAL` AS tot, s.submitted_at
      FROM lms_submissions s JOIN lms_assessments a ON a.id=s.assessment_id
      WHERE s.student_id=? AND s.id<>?
      ".($HAS_SUBMITTED_AT?"ORDER BY s.submitted_at DESC":"ORDER BY s.id DESC")." LIMIT 8";
} else {
  $q="SELECT s.id, a.`$assTitleCol` AS atitle, s.`$C_SCORE` AS perc, s.submitted_at
      FROM lms_submissions s JOIN lms_assessments a ON a.id=s.assessment_id
      WHERE s.student_id=? AND s.id<>?
      ".($HAS_SUBMITTED_AT?"ORDER BY s.submitted_at DESC":"ORDER BY s.id DESC")." LIMIT 8";
}
if ($st=$mysqli->prepare($q)) {
  $st->bind_param('ii',$student_id,$id);
  $st->execute(); $history=$st->get_result()->fetch_all(MYSQLI_ASSOC); $st->close();
}

/* ---- Criteria Templates: template + saved selections ---- */
$CRITERIA_LOS = [];
$CRITERIA_IMAGE = null;

if ($st = $mysqli->prepare("SELECT info_json,image_path
                            FROM lms_assessment_criteria_sets
                            WHERE template_type='assessment' AND assessment_id=? AND status='active'
                            ORDER BY id DESC LIMIT 1")) {
  $st->bind_param('i', $assessment_id);
  $st->execute();
  if ($row = $st->get_result()->fetch_assoc()) {
    $data = json_decode((string)$row['info_json'], true);
    if (is_array($data) && isset($data['los']) && is_array($data['los'])) {
      $CRITERIA_LOS = $data['los'];
    }
    $CRITERIA_IMAGE = $row['image_path'] ?? null;
  }
  $st->close();
}

/* table for criteria selections (safe) */
$mysqli->query("CREATE TABLE IF NOT EXISTS lms_assessment_criteria_marks(
  submission_id INT PRIMARY KEY,
  assessment_id INT NOT NULL,
  student_id INT NOT NULL,
  selections_json LONGTEXT NOT NULL,
  updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");

$CRITERIA_SAVED = ['los'=>[]];
$r=$mysqli->query("SELECT selections_json FROM lms_assessment_criteria_marks WHERE submission_id=".$id);
if($r && $x=$r->fetch_assoc()){ $tmp=json_decode((string)$x['selections_json'],true); if(is_array($tmp)) $CRITERIA_SAVED=$tmp; }
if($r) $r->free();

/* ---- uploads ---- */
$uploadDir = $ROOT.'/uploads/feedback'; ensure_dir($uploadDir);

/* ---- scale ---- */
$scale = [ ['A', 80], ['B', 65], ['C', 50], ['D', 40], ['F', 0 ] ];
function grade_letter($pct, array $scale){
  foreach($scale as [$g,$min]) if($pct >= $min) return $g;
  return 'F';
}

/* ---- save ---- */
$msg='';
if ($_SERVER['REQUEST_METHOD']==='POST') {
  $action = (string)($_POST['action'] ?? 'save'); // save | save_next | draft | publish
  $total = is_numeric($_POST['total'] ?? null) ? (float)$_POST['total'] : null;
  $obt   = is_numeric($_POST['obt'] ?? null)   ? (float)$_POST['obt']   : null;
  $score_in = is_numeric($_POST['score'] ?? null) ? (float)$_POST['score'] : null;

  $pct = null;
  if ($total !== null && $total>0 && $obt !== null) $pct = 100.0 * $obt / $total;
  elseif ($score_in !== null) $pct = $score_in;

  $grade  = $pct !== null ? grade_letter($pct,$scale) : ($C_GRADE ? ($sub[$C_GRADE] ?? null) : null);
  $status = trim((string)($_POST['status'] ?? 'keep'));
  $remarksHtml = (string)($_POST['remarks_html'] ?? '');

  // feedback file
  $savedFileRel=null;
  if (!empty($_FILES['feedback_file']['name']) && is_uploaded_file($_FILES['feedback_file']['tmp_name'])) {
    $ext = strtolower(pathinfo($_FILES['feedback_file']['name'], PATHINFO_EXTENSION)) ?: 'bin';
    $fname = 'fb_'.date('Ymd_His').'_'.$id.'.'.$ext;
    if (move_uploaded_file($_FILES['feedback_file']['tmp_name'], $uploadDir.'/'.$fname)) {
      $savedFileRel = '/uploads/feedback/'.$fname;
    }
  }

  // voice file or inline recorded
  $savedVoiceRel=null;
  if (!empty($_FILES['voice_file']['name']) && is_uploaded_file($_FILES['voice_file']['tmp_name'])) {
    $ext = strtolower(pathinfo($_FILES['voice_file']['name'], PATHINFO_EXTENSION) ?: 'webm');
    $fname = 'voice_'.date('Ymd_His').'_'.$id.'.'.$ext;
    if (move_uploaded_file($_FILES['voice_file']['tmp_name'], $uploadDir.'/'.$fname)) {
      $savedVoiceRel = '/uploads/feedback/'.$fname;
    }
  } elseif (!empty($_POST['voice_blob_b64'])) {
    $b64 = (string)$_POST['voice_blob_b64'];
    if (preg_match('#^data:audio/[^;]+;base64,(.+)$#',$b64,$m)) {
      $raw = base64_decode($m[1], true);
      if ($raw!==false) {
        $fname='voice_'.date('Ymd_His').'_'.$id.'.webm';
        if (file_put_contents($uploadDir.'/'.$fname,$raw)!==false) {
          $savedVoiceRel = '/uploads/feedback/'.$fname;
        }
      }
    }
  }

  $parts=[]; $types=''; $vals=[];
  if ($C_TOTAL && $total!==null){ $parts[]="`$C_TOTAL`=?"; $types.='d'; $vals[]=$total; }
  if ($C_OBT && $obt!==null)   { $parts[]="`$C_OBT`=?";   $types.='d'; $vals[]=$obt; }
  if ($C_SCORE && $pct!==null) { $parts[]="`$C_SCORE`=?"; $types.='d'; $vals[]=$pct; }
  if ($C_GRADE && $grade!==null){$parts[]="`$C_GRADE`=?"; $types.='s'; $vals[]=$grade; }
  if ($C_STATUS)               { $parts[]="`$C_STATUS`=?";$types.='s'; $vals[]=$status; }
  if ($C_FEED)                 { $parts[]="`$C_FEED`=?";  $types.='s'; $vals[]=$remarksHtml; }
  if ($C_FEEDF && $savedFileRel){$parts[]="`$C_FEEDF`=?"; $types.='s'; $vals[]=$savedFileRel; }
  if ($C_VOICE && $savedVoiceRel){$parts[]="`$C_VOICE`=?";$types.='s'; $vals[]=$savedVoiceRel; }
  if ($C_PUB){   $pub   = ($action==='publish') ? 1 : ((int)($sub[$C_PUB] ?? 0)); $parts[]="`$C_PUB`=?";   $types.='i'; $vals[]=$pub; }
  if ($C_DRAFT){ $draft = ($action==='draft')   ? 1 : 0;                            $parts[]="`$C_DRAFT`=?"; $types.='i'; $vals[]=$draft; }
  if ($C_GRADED && $action!=='draft'){ $parts[]="`$C_GRADED`=NOW()"; }

  if ($parts){
    $sql="UPDATE lms_submissions SET ".implode(', ',$parts)." WHERE id=?";
    $types.='i'; $vals[]=$id;
    if ($st=$mysqli->prepare($sql)) { $st->bind_param($types, ...$vals); $ok=$st->execute(); $st->close(); } else { $ok=false; }

    /* save criteria selections */
    if (isset($_POST['criteria_json'])) {
      $critJson = (string)$_POST['criteria_json'];
      if ($critJson !== '' && ($st=$mysqli->prepare("INSERT INTO lms_assessment_criteria_marks
            (submission_id,assessment_id,student_id,selections_json)
            VALUES (?,?,?,?)
            ON DUPLICATE KEY UPDATE selections_json=VALUES(selections_json), updated_at=NOW()"))) {
        $st->bind_param('iiis', $id, $assessment_id, $student_id, $critJson);
        $st->execute(); $st->close();
      }
    }

    // refresh
    if ($st=$mysqli->prepare("SELECT * FROM lms_submissions WHERE id=?")){
      $st->bind_param('i',$id); $st->execute();
      $fresh=$st->get_result()->fetch_assoc(); $st->close();
      if ($fresh) $sub=array_merge($sub,$fresh);
    }

    if ($ok && $action==='save_next' && $next_id){ header("Location: grade.php?id=".$next_id); exit; }
    if ($ok && $action==='publish') { $msg='Published to student.'; }
    elseif ($ok && $action==='draft'){ $msg='Draft saved (not published).'; }
    else { $msg = $ok ? 'Saved.' : 'Save failed.'; }
  } else {
    $msg='No editable columns in table.';
  }
}

/* ---- derived display ---- */
$nowSri = date('Y-m-d H:i:s').' (Sri Lanka)';
$tot = $C_TOTAL && isset($sub[$C_TOTAL]) ? (float)$sub[$C_TOTAL] : null;
$obt = $C_OBT   && isset($sub[$C_OBT])   ? (float)$sub[$C_OBT]   : null;
$pct = null;
if ($tot && $tot>0 && $obt!==null) $pct = 100.0*$obt/$tot;
elseif ($C_SCORE && isset($sub[$C_SCORE])) $pct = (float)$sub[$C_SCORE];
$curGrade = $C_GRADE && !empty($sub[$C_GRADE]) ? (string)$sub[$C_GRADE] : ($pct!==null ? grade_letter($pct,$scale) : '—');

/* ---- resolve submission file safely to a web path ---- */
$rawFile = $C_FILE ? (string)($sub[$C_FILE] ?? '') : '';
$diag = ['bases'=>[], 'cands'=>[], 'chosen'=>null];

$to_web = function(string $raw) use($rootWeb,$docRoot,&$diag): string {
  $raw = trim(str_replace('\\','/',$raw));
  if ($raw==='') return '';
  if (strpos($raw,'..')!==false) return '';

  $allowed = [
    $rootWeb.'/uploads/assessments',
    $rootWeb.'/uploads/assessment_submissions',
    $rootWeb.'/uploads/submissions',
    $rootWeb.'/uploads/answers',
    $rootWeb.'/uploads',
  ];
  $diag['bases']=$allowed;

  $cands=[];
  if (preg_match('~^https?://~i',$raw)) { $cands[]=$raw; }
  elseif ($raw[0]=='/') { $cands[]=$raw; $cands[]=$rootWeb.$raw; }
  else {
    $cands[]=$raw;
    foreach($allowed as $b){ $cands[]=$b.'/'.ltrim($raw,'/'); }
    if (strpos($raw,'uploads/')===0) { $cands[]='/'.ltrim($raw,'/'); $cands[]=$rootWeb.'/'.ltrim($raw,'/'); }
  }
  $cands=array_values(array_unique($cands));
  $diag['cands']=$cands;

  foreach($cands as $p){
    if (preg_match('~^https?://~i',$p)) return $p;
    $abs = $docRoot.$p;
    if (is_file($abs)) {
      foreach($allowed as $base){
        $baseAbs = realpath($docRoot.$base);
        $target  = realpath($abs);
        if ($baseAbs && $target && strpos($target,$baseAbs)===0) { $diag['chosen']=$p; return $p; }
      }
    }
  }
  return '';
};

$filePath = $rawFile ? $to_web($rawFile) : '';
$ext = file_ext($filePath);

/* ---- nav links ---- */
$baseLecturer = dirname($_SERVER['PHP_SELF']);
$linkDashboard = rtrim($baseLecturer.'/../index.php','/');
$linkAssess    = rtrim($baseLecturer.'/index.php','/');
$linkLogout    = rtrim($baseLecturer.'/../logout.php','/');
?>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Grade — <?= esc($sub['assessment_title'] ?? 'Assessment') ?></title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
:root{ --bg:#0b1524; --bg2:#0e1a2e; --text:#d7e3ff; --muted:#8fa3c9; --brand:#6c5ce7; --brand2:#9b88ff; --line:#e5e7eb; --ink:#111827; --soft:#f6f8ff; }
*{box-sizing:border-box} body{margin:0;font:14px/1.5 system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Cantarell,"Noto Sans",Arial;}
.shell{display:flex; min-height:100vh; background:var(--soft);}
.sidebar{width:280px; background:linear-gradient(180deg,var(--bg),var(--bg2)); color:var(--text); padding:18px 16px; position:sticky; top:0; height:100vh; overflow:auto}
.brand{font-weight:800; font-size:22px; margin-bottom:10px}
.rolebox{font-size:12px; color:var(--muted); text-transform:uppercase; letter-spacing:.7px; margin-bottom:14px}
.nav a{display:flex; align-items:center; gap:10px; padding:10px 12px; color:var(--text); text-decoration:none; border-radius:12px; margin:3px 0}
.nav a:hover{background:rgba(255,255,255,.06)} .nav a.active{background:linear-gradient(90deg,var(--brand),var(--brand2)); color:#fff}
.nav .emoji{width:22px; text-align:center}
.slab{background:rgba(255,255,255,.06); border-radius:10px; padding:10px 12px; margin-top:12px}
.list a{display:block; padding:8px 10px; border-radius:8px; text-decoration:none; color:#e9edff}
.list a.active{background:rgba(255,255,255,.12); font-weight:600}
.list small{color:#a9b7dd}
.main{flex:1; padding:20px 24px}
.top{display:flex; gap:12px; justify-content:space-between; align-items:end; margin-bottom:12px}
.title{margin:0; font-size:22px; font-weight:800}
.muted{color:#6b7280; font-size:12px}
.two{display:grid; grid-template-columns: 1.2fr .8fr; gap:14px}
.card{background:#fff; border:1px solid var(--line); border-radius:16px; box-shadow:0 6px 14px rgba(17,24,39,.04)}
.body{padding:14px}
.row{display:flex; flex-wrap:wrap; gap:12px}
.col{flex:1 1 220px}
label{display:block; font-weight:600; margin:6px 0}
.input,.select,textarea,.btn{width:100%; border:1px solid var(--line); border-radius:10px; padding:9px 10px; font-size:14px}
.btn{background:var(--brand); color:#fff; border:none; cursor:pointer; height:38px}
.btn.secondary{background:#eef2ff; color:#3730a3}
.btn.danger{background:#ef4444; color:#fff}
.btnbar{display:flex; gap:8px; flex-wrap:wrap}
.grid2{display:grid; grid-template-columns:repeat(2,1fr); gap:10px}
.grid3{display:grid; grid-template-columns:repeat(3,1fr); gap:10px}
.pill{display:inline-block; background:#eef2ff; color:#3730a3; font-weight:700; font-size:12px; padding:4px 10px; border-radius:999px}
.table{width:100%; border-collapse:collapse}
.table th{background:#f2f4ff; text-align:left; font-size:13px; padding:10px}
.table td{border-top:1px solid var(--line); padding:10px}
.preview{height:420px; border:1px dashed #cbd5e1; border-radius:12px; overflow:hidden; background:#f8fafc}
.hist{max-height:220px; overflow:auto}
.toolbar{display:flex; gap:6px; margin-bottom:6px}
.tool{padding:6px 10px; border:1px solid var(--line); background:#fff; border-radius:8px; cursor:pointer}
.notice{margin:8px 0; color:#065f46; background:#ecfdf5; border:1px solid #a7f3d0; padding:8px 10px; border-radius:10px}
@media (max-width:1100px){ .two{grid-template-columns:1fr} .sidebar{display:none} .main{padding:16px} }

/* ===== Report popup styles ===== */
.report-overlay{position:fixed;inset:0;background:rgba(15,23,42,.65);display:none;align-items:center;justify-content:center;z-index:9999}
.report-box{width:min(1000px,92vw);height:min(86vh,1000px);background:#fff;border-radius:14px;overflow:hidden;box-shadow:0 20px 60px rgba(0,0,0,.35);display:flex;flex-direction:column}
.report-bar{display:flex;align-items:center;justify-content:space-between;padding:8px 10px;background:#0f172a;color:#fff}
.report-bar .title{font-weight:700}
.report-bar .actions{display:flex;gap:8px}
.report-iframe{border:0;flex:1;width:100%;background:#fafafa}
.report-btn{background:#eef2ff;border:0;color:#111;font:14px system-ui;padding:6px 10px;border-radius:10px;cursor:pointer}
.report-btn.primary{background:#10b981;color:#fff}
@media print {.report-overlay{display:none!important}}
</style>
</head>
<body>
<div class="shell">
  <aside class="sidebar">
    <div class="brand">Lecturer</div>
    <div class="rolebox"><?= strtoupper(esc($role)) ?></div>
    <nav class="nav">
      <a href="<?= esc($linkDashboard) ?>"><span class="emoji">🏠</span><span>Dashboard</span></a>
      <a class="active" href="<?= esc($linkAssess) ?>"><span class="emoji">📝</span><span>Assessments</span></a>
      <a href="<?= esc($linkLogout) ?>"><span class="emoji">🔒</span><span>Logout</span></a>
    </nav>

    <div class="slab">
      <div style="font-weight:700; margin-bottom:6px">Students (this assessment)</div>
      <div class="list">
        <?php foreach($students as $s): $active = ((int)$s['id']===$id); ?>
          <a class="<?= $active?'active':'' ?>" href="grade.php?id=<?= (int)$s['id'] ?>">
            <?= esc($s['nm'] ?? ('#'.$s['student_id'])) ?>
          </a>
        <?php endforeach; ?>
      </div>
    </div>

    <div class="slab">
      <div style="font-weight:700; margin-bottom:6px">Student history</div>
      <div class="hist">
        <table class="table">
          <thead><tr><th>Assessment</th><th style="width:80px">Score</th></tr></thead>
          <tbody>
          <?php if(!$history): ?>
            <tr><td colspan="2"><small>No history.</small></td></tr>
          <?php else: foreach($history as $h): ?>
            <tr>
              <td><?= esc($h['atitle'] ?? '') ?><br><small class="muted"><?= esc($h['submitted_at'] ?? '') ?></small></td>
              <td>
                <?php if(isset($h['obt']) && isset($h['tot']) && $h['tot']>0): ?>
                  <span class="pill"><?= round(100*$h['obt']/$h['tot']) ?>%</span>
                <?php else: ?>
                  <span class="pill"><?= isset($h['perc']) ? round($h['perc']) : '—' ?>%</span>
                <?php endif; ?>
              </td>
            </tr>
          <?php endforeach; endif; ?>
          </tbody>
        </table>
      </div>
    </div>
  </aside>

  <main class="main">
    <div class="top">
      <div>
        <h2 class="title" style="margin-bottom:4px">Grade — <?= esc($sub['assessment_title'] ?? 'Assessment') ?></h2>
        <div class="muted">
          Student: <b><?= esc($sub['student_name'] ?? ('#'.$student_id)) ?></b>
          <?php if(!empty($sub['student_ref'])): ?> · Ref: <?= esc($sub['student_ref']) ?><?php endif; ?>
          · Submitted: <?= esc($sub['submitted_at'] ?? '') ?>
        </div>
      </div>
      <div class="muted"><?= esc($nowSri) ?></div>
    </div>

    <?php if($msg): ?><div class="notice"><?= esc($msg) ?></div><?php endif; ?>
    <?php if($DEBUG): ?>
      <pre class="notice" style="white-space:pre-wrap; color:#111">
raw file from DB: <?= esc($rawFile) . "\n" ?>
normalized web path: <?= esc($filePath ?: '(none)') . "\n" ?>
allowed bases:
- <?= esc(implode("\n- ", $diag['bases'])) . "\n" ?>
candidates tried:
- <?= esc(implode("\n- ", $diag['cands'])) . "\n" ?>
chosen: <?= esc((string)$diag['chosen']) . "\n" ?>
      </pre>
    <?php endif; ?>
    <?php if($fatalMsg): ?><div class="notice" style="background:#fef3c7;border-color:#fde68a;color:#7c2d12"><?= esc($fatalMsg) ?></div><?php endif; ?>

    <div class="two">

      <!-- MAIN GRADING -->
      <section class="card">
        <div class="body">
          <form id="gradeForm" method="post" enctype="multipart/form-data">
            <input type="hidden" name="remarks_html" id="remarks_html">
            <input type="hidden" name="voice_blob_b64" id="voice_blob_b64">
            <input type="hidden" name="criteria_json" id="criteria_json">

            <div class="grid3">
              <div>
                <label>Total Marks</label>
                <input class="input" type="number" step="0.01" name="total" id="total" value="<?= $tot!==null?esc((string)$tot):'' ?>" placeholder="e.g., 100">
              </div>
              <div>
                <label>Obtained Marks</label>
                <input class="input" type="number" step="0.01" name="obt" id="obt" value="<?= $obt!==null?esc((string)$obt):'' ?>" placeholder="e.g., 78">
              </div>
              <div>
                <label>Percentage</label>
                <input class="input" type="number" step="0.01" name="score" id="score" value="<?= $pct!==null?esc((string)$pct):'' ?>" placeholder="auto" readonly>
              </div>
            </div>

            <div class="grid3" style="margin-top:8px">
              <div>
                <label>Grade</label>
                <div class="input" id="gradeBox" style="font-weight:800"><?= esc($curGrade) ?></div>
              </div>
              <div>
                <label>Status</label>
                <?php $curStatus = (string)($C_STATUS ? ($sub[$C_STATUS] ?? 'keep') : 'keep'); ?>
                <select class="select" name="status" id="status">
                  <?php foreach(['pending','graded','resubmission','late','keep'] as $s): ?>
                    <option value="<?= $s ?>" <?= strtolower($curStatus)===$s?'selected':'' ?>><?= ucfirst($s) ?></option>
                  <?php endforeach; ?>
                </select>
              </div>
              <div>
                <label>Feedback file</label>
                <input class="input" type="file" name="feedback_file">
              </div>
            </div>

            <!-- Covered Assessment Criteria -->
            <div class="card" style="margin-top:12px">
              <div class="body">
                <div style="display:flex; justify-content:space-between; align-items:center; gap:12px; flex-wrap:wrap">
                  <div style="font-weight:700">Covered Assessment Criteria</div>
                  <div class="row" style="gap:10px; align-items:flex-end; min-width:300px">
                    <div class="col">
                      <label>Points per code</label>
                      <input class="input" type="number" step="0.01" id="points_per_code" value="1">
                    </div>
                    <div class="col">
                      <label>Total codes</label>
                      <input class="input" id="total_codes" readonly>
                    </div>
                    <div class="col">
                      <label>Selected</label>
                      <input class="input" id="selected_codes" readonly>
                    </div>
                  </div>
                </div>
                <div id="cacRows" style="margin-top:10px"></div>
                <?php if(!empty($CRITERIA_IMAGE)): ?>
                  <div class="muted" style="margin-top:6px"><a href="<?= esc($CRITERIA_IMAGE) ?>" target="_blank">Open criteria reference</a></div>
                <?php endif; ?>
              </div>
            </div>

            <!-- Feedback (rich text) -->
            <div class="card" style="margin-top:12px">
              <div class="body">
                <label>Feedback</label>
                <div class="toolbar">
                  <button type="button" class="tool" onclick="fmt('bold')"><b>B</b></button>
                  <button type="button" class="tool" onclick="fmt('italic')"><i>I</i></button>
                  <button type="button" class="tool" onclick="fmt('insertUnorderedList')">• List</button>
                  <button type="button" class="tool" onclick="mkLink()">Link</button>
                  <button type="button" class="tool" onclick="clearFmt()">Clear</button>
                </div>
                <div id="editor" contenteditable="true" style="min-height:120px;border:1px solid var(--line);border-radius:10px;padding:10px;background:#fff;"><?= $C_FEED ? ($sub[$C_FEED] ?? '') : '' ?></div>
              </div>
            </div>

            <!-- Voice feedback -->
            <div class="card" style="margin-top:12px">
              <div class="body">
                <label>Voice feedback (optional)</label>
                <div class="row">
                  <div class="col">
                    <input class="input" type="file" name="voice_file" accept="audio/*">
                    <small class="muted">Or record below.</small>
                  </div>
                  <div class="col">
                    <div class="btnbar">
                      <button type="button" class="btn secondary" id="recBtn">Start Recording</button>
                      <button type="button" class="btn secondary" id="stopBtn" disabled>Stop</button>
                      <audio id="audioPrev" controls style="width:100%"></audio>
                    </div>
                  </div>
                </div>
              </div>
            </div>

            <!-- Actions -->
            <div class="row" style="margin-top:12px">
              <div class="col btnbar">
                <button class="btn" onclick="return submitWith('save')">Save</button>
                <?php if($next_id): ?><button class="btn" onclick="return submitWith('save_next')">Save & Next</button><?php endif; ?>
                <button class="btn secondary" onclick="return submitWith('draft')">Save Draft</button>
                <button class="btn" style="background:#10b981" onclick="return submitWith('publish')">Publish to Student</button>
                <button class="btn secondary" type="button" onclick="openReportModal()">Report</button>
                <a class="btn secondary" href="index.php" style="text-decoration:none;display:inline-flex;align-items:center;justify-content:center">Back to list</a>
              </div>
            </div>
          </form>
        </div>
      </section>

      <!-- RIGHT: Preview + Scale + Analytics -->
      <section class="card">
        <div class="body">
          <div style="font-weight:700; margin-bottom:8px">Submission preview</div>
          <div class="preview">
            <?php if($filePath): ?>
              <?php if(in_array($ext,['pdf'])): ?>
                <object type="application/pdf" data="<?= esc($filePath) ?>#toolbar=1&navpanes=0" width="100%" height="100%"></object>
              <?php elseif(in_array($ext,['jpg','jpeg','png','gif','webp'])): ?>
                <img src="<?= esc($filePath) ?>" alt="submission" style="width:100%;height:100%;object-fit:contain">
              <?php elseif(in_array($ext,['txt','py','js','ts','java','php','css','html','c','cpp','md','json','xml','sql'])): ?>
                <iframe src="<?= esc($filePath) ?>" style="border:0;width:100%;height:100%;background:#0b1020;color:#e5e7eb"></iframe>
              <?php else: ?>
                <iframe src="<?= esc($filePath) ?>" style="border:0;width:100%;height:100%"></iframe>
              <?php endif; ?>
            <?php else: ?>
              <div class="muted" style="padding:12px">No file attached or the file is not inside the allowed uploads folders.</div>
            <?php endif; ?>
          </div>

          <div style="margin-top:14px" class="grid2">
            <div>
              <div style="font-weight:700; margin-bottom:6px">Grading scale</div>
              <table class="table">
                <thead><tr><th>Grade</th><th>Min %</th></tr></thead>
                <tbody>
                <?php foreach($scale as [$g,$min]): ?>
                  <tr><td><?= $g ?></td><td><?= $min ?>%</td></tr>
                <?php endforeach; ?>
                </tbody>
              </table>
            </div>
            <div>
              <div style="font-weight:700; margin-bottom:6px">Analytics (this assessment)</div>
              <table class="table">
                <tbody>
                  <tr><td>Submissions</td><td><span class="pill"><?= (int)$cnt ?></span></td></tr>
                  <tr><td>Average</td><td><span class="pill"><?= $avg?round($avg,1):'—' ?>%</span></td></tr>
                  <tr><td>Highest</td><td><span class="pill"><?= $hi!==null?round($hi):'—' ?>%</span></td></tr>
                  <tr><td>Lowest</td><td><span class="pill"><?= $lo!==null?round($lo):'—' ?>%</span></td></tr>
                </tbody>
              </table>
            </div>
          </div>
        </div>
      </section>

    </div>

    <footer style="margin-top:14px" class="muted">© <?= date('Y') ?> EdenCampus</footer>
  </main>
</div>

<!-- ===== Report Modal (loads report.php?id=...) ===== -->
<div id="reportModal" class="report-overlay" onclick="if(event.target===this) closeReportModal()">
  <div class="report-box">
    <div class="report-bar">
      <div class="title">Assessment Report</div>
      <div class="actions">
        <button class="report-btn" type="button" onclick="printReport()">Print</button>
        <button class="report-btn" type="button" onclick="popoutReport()">Open in new tab</button>
        <button class="report-btn primary" type="button" onclick="closeReportModal()">Close</button>
      </div>
    </div>
    <iframe id="reportFrame" class="report-iframe" src="about:blank" title="Report"></iframe>
  </div>
</div>

<script>
/* ===== Compute % + grade ===== */
const total = document.getElementById('total');
const obt   = document.getElementById('obt');
const score = document.getElementById('score');
const gradeBox = document.getElementById('gradeBox');
function letter(p){ if (p>=80) return 'A'; if (p>=65) return 'B'; if (p>=50) return 'C'; if (p>=40) return 'D'; return 'F'; }
function recalc(){
  const t = parseFloat(total.value); const o = parseFloat(obt.value);
  let pct = null;
  if (!isNaN(t) && t>0 && !isNaN(o)) pct = 100*o/t;
  if (pct!==null){ score.value = pct.toFixed(2); gradeBox.textContent = letter(pct); }
  else if (score.value){ const p=parseFloat(score.value); if(!isNaN(p)) gradeBox.textContent = letter(p); }
}
[total, obt].forEach(el=>el.addEventListener('input', recalc));

/* ===== Rich text editor ===== */
const ed = document.getElementById('editor');
function fmt(cmd){ document.execCommand(cmd,false,null); ed.focus(); }
function mkLink(){ const url = prompt('URL:','https://'); if(!url) return; document.execCommand('createLink', false, url); ed.focus(); }
function clearFmt(){ ed.innerHTML=''; ed.focus(); }

/* ===== Voice recorder ===== */
let mediaRec=null, chunks=[];
const recBtn=document.getElementById('recBtn'), stopBtn=document.getElementById('stopBtn'), audioPrev=document.getElementById('audioPrev');
const voiceHidden=document.getElementById('voice_blob_b64');
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
  recBtn.onclick = async () => {
    try{
      const stream = await navigator.mediaDevices.getUserMedia({audio:true});
      chunks=[]; mediaRec = new MediaRecorder(stream);
      mediaRec.ondataavailable = e => { if (e.data && e.data.size>0) chunks.push(e.data); };
      mediaRec.onstop = async () => {
        const blob = new Blob(chunks,{type:'audio/webm'});
        audioPrev.src = URL.createObjectURL(blob);
        const b64 = await new Promise(res=>{ const r=new FileReader(); r.onload=()=>res(r.result); r.readAsDataURL(blob); });
        voiceHidden.value = b64;
      };
      mediaRec.start(); recBtn.disabled=true; stopBtn.disabled=false;
    }catch(e){ alert('Mic access failed'); }
  };
  stopBtn.onclick = () => { if (mediaRec && mediaRec.state!=='inactive') mediaRec.stop(); recBtn.disabled=false; stopBtn.disabled=true; };
} else { recBtn.disabled=true; stopBtn.disabled=true; }

/* ===== Submit helpers ===== */
function submitWith(kind){
  document.getElementById('remarks_html').value = ed.innerHTML;
  const act = document.createElement('input'); act.type='hidden'; act.name='action'; act.value=kind;
  document.getElementById('gradeForm').appendChild(act);
  return true;
}

/* ===== Covered Assessment Criteria (render + total) ===== */
const LOS    = <?= json_encode($CRITERIA_LOS,   JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES) ?>;
const SAVED  = <?= json_encode($CRITERIA_SAVED, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES) ?>;
const cacRoot         = document.getElementById('cacRows');
const hiddenCriteria  = document.getElementById('criteria_json');
const pointsPerCodeEl = document.getElementById('points_per_code');
const totalCodesEl    = document.getElementById('total_codes');
const selectedCodesEl = document.getElementById('selected_codes');

function escHtml(s){return String(s||'').replace(/[&<>"']/g, m=>({ '&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;' }[m]));}
function parseCodes(lo){
  let raw = lo.codes ?? lo.codes_csv ?? lo.codesLabel ?? lo.codes_label ?? lo.codes_text ?? '';
  if (Array.isArray(raw)) return raw.filter(Boolean);
  raw = String(raw||'');
  const m = raw.match(/[PMD]\d+/gi);
  return m ? Array.from(new Set(m.map(x=>x.toUpperCase()))) : [];
}
function savedSetFor(code){
  const found = (SAVED && Array.isArray(SAVED.los)) ? SAVED.los.find(x=>x && x.code===code) : null;
  return new Set(found && Array.isArray(found.achieved) ? found.achieved : []);
}
function renderCAC(){
  if (!Array.isArray(LOS) || !LOS.length){
    cacRoot.innerHTML = '<div class="muted">No criteria template attached to this assessment.</div>';
    hiddenCriteria.value = '';
    return;
  }
  let totalCodes = 0;
  const rows = LOS.map((lo,i)=>{
    const code = lo.code || ('LO'+(i+1));
    const text = lo.text || '';
    const codes = parseCodes(lo);
    totalCodes += codes.length;
    const have = savedSetFor(code);
    const chips = codes.map(c=>{
      const checked = have.has(c) ? 'checked' : '';
      return `<label style="display:inline-flex;align-items:center;gap:6px;border:1px solid var(--line);border-radius:999px;padding:6px 10px;margin:3px;background:#f9fbff;cursor:pointer">
                <input type="checkbox" class="cac" data-lo="${code}" value="${c}" ${checked}> ${c}
              </label>`;
    }).join('');
    return `<div style="display:grid;grid-template-columns:90px 1fr;gap:12px;border-top:1px solid var(--line);padding:10px 0">
              <div style="font-weight:700">${code}</div>
              <div>
                <div>${escHtml(text)}</div>
                <div style="margin-top:6px">${chips || '<span class="muted">No P/M/D codes</span>'}</div>
              </div>
            </div>`;
  });
  cacRoot.innerHTML = rows.join('');
  totalCodesEl.value = totalCodes;

  cacRoot.addEventListener('change', e=>{ if (e.target && e.target.classList.contains('cac')) serializeCAC(); });
  pointsPerCodeEl.addEventListener('input', serializeCAC);

  serializeCAC();
}
function serializeCAC(){
  const map = {};
  const boxes = cacRoot.querySelectorAll('input.cac');
  let selected = 0, totalN = boxes.length;

  boxes.forEach(cb=>{
    const lo = cb.getAttribute('data-lo'); (map[lo] = map[lo] || new Set());
    if (cb.checked){ map[lo].add(cb.value); selected++; }
  });

  const data = {los: Object.keys(map).map(k => ({code:k, achieved:[...map[k]]}))};
  hiddenCriteria.value = JSON.stringify(data);
  selectedCodesEl.value = selected;

  const per = parseFloat(pointsPerCodeEl.value) || 0;
  const totalPts = totalN * per;
  const gotPts   = selected * per;
  if (!isNaN(totalPts)) total.value = totalPts.toFixed(2);
  if (!isNaN(gotPts))   obt.value   = gotPts.toFixed(2);
  recalc();
}
renderCAC();

/* ===== Report popup controls ===== */
function getReportUrl(){
  return 'report.php?id=' + <?= (int)$id ?>;
}
function openReportModal(){
  const url   = getReportUrl();
  const layer = document.getElementById('reportModal');
  const frame = document.getElementById('reportFrame');
  frame.src = url;
  layer.style.display = 'flex';
  document.addEventListener('keydown', escCloseReport);
}
function closeReportModal(){
  const layer = document.getElementById('reportModal');
  const frame = document.getElementById('reportFrame');
  layer.style.display = 'none';
  frame.src = 'about:blank';
  document.removeEventListener('keydown', escCloseReport);
}
function escCloseReport(e){ if(e.key === 'Escape') closeReportModal(); }
function printReport(){
  const f = document.getElementById('reportFrame');
  if (f && f.contentWindow){ f.contentWindow.focus(); f.contentWindow.print(); }
}
function popoutReport(){
  const url = document.getElementById('reportFrame').src || getReportUrl();
  window.open(url, 'edenReport', 'width=1020,height=820,menubar=no,toolbar=no,location=no,status=no');
}
</script>
</body>
</html>
