import { env } from "cloudflare:workers";
import { getAppIdentity, forbidden, unauthorised } from "../../api-auth";

export const dynamic = "force-dynamic";

type ModuleRow = { id: number; title: string; position: number };
type LessonRow = { moduleId: number; title: string; lessonType: string; summary: string; mediaUrl: string | null; durationMinutes: number; position: number };

export async function GET(request: Request) {
  const user = await getAppIdentity();
  if (!user) return unauthorised();
  if (!["creator", "admin"].includes(user.role)) return forbidden();
  const format = new URL(request.url).searchParams.get("format") === "scorm2004" ? "scorm2004" : "scorm12";
  const modules = await env.DB.prepare(
    "SELECT id,title,position FROM course_modules WHERE product_id=1 ORDER BY position"
  ).all<ModuleRow>();
  const lessons = await env.DB.prepare(
    `SELECT l.module_id AS moduleId,l.title,l.lesson_type AS lessonType,l.summary,l.media_url AS mediaUrl,
      l.duration_minutes AS durationMinutes,l.position
     FROM course_lessons l JOIN course_modules m ON m.id=l.module_id
     WHERE m.product_id=1 ORDER BY m.position,l.position`
  ).all<LessonRow>();
  if (!modules.results.length || !lessons.results.length) {
    return Response.json({ error: "Add at least one module and lesson before exporting." }, { status: 400 });
  }

  const title = "Community Project Management";
  const manifest = buildManifest(format, title);
  const html = buildCourseHtml(title, modules.results, lessons.results, format);
  const files = [
    { name: "imsmanifest.xml", data: manifest },
    { name: "index.html", data: html },
    { name: "styles.css", data: courseCss },
    { name: "runtime.js", data: runtimeJs(format) },
    { name: "course.json", data: JSON.stringify({ title, format, modules: modules.results, lessons: lessons.results, exportedBy: user.email }, null, 2) },
  ];
  const zip = makeZip(files.map((file) => ({ name: file.name, data: new TextEncoder().encode(file.data) })));
  const label = format === "scorm2004" ? "scorm-2004" : "scorm-1.2";
  return new Response(zip, {
    headers: {
      "content-type": "application/zip",
      "content-disposition": `attachment; filename="community-project-management-${label}.zip"`,
      "cache-control": "private, no-store",
    },
  });
}

function buildManifest(format: "scorm12" | "scorm2004", title: string) {
  const safeTitle = xml(title);
  if (format === "scorm2004") return `<?xml version="1.0" encoding="UTF-8"?>
<manifest identifier="CONNECTPOINT-CPM-2004" version="1.0"
 xmlns="http://www.imsglobal.org/xsd/imscp_v1p1"
 xmlns:adlcp="http://www.adlnet.org/xsd/adlcp_v1p3"
 xmlns:imsss="http://www.imsglobal.org/xsd/imsss"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
 <metadata><schema>ADL SCORM</schema><schemaversion>2004 4th Edition</schemaversion></metadata>
 <organizations default="ORG-1"><organization identifier="ORG-1"><title>${safeTitle}</title><item identifier="ITEM-1" identifierref="RES-1"><title>${safeTitle}</title></item></organization></organizations>
 <resources><resource identifier="RES-1" type="webcontent" adlcp:scormType="sco" href="index.html"><file href="index.html"/><file href="styles.css"/><file href="runtime.js"/><file href="course.json"/></resource></resources>
</manifest>`;
  return `<?xml version="1.0" encoding="UTF-8"?>
<manifest identifier="CONNECTPOINT-CPM-12" version="1.0"
 xmlns="http://www.imsproject.org/xsd/imscp_rootv1p1p2"
 xmlns:adlcp="http://www.adlnet.org/xsd/adlcp_rootv1p2"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
 <metadata><schema>ADL SCORM</schema><schemaversion>1.2</schemaversion></metadata>
 <organizations default="ORG-1"><organization identifier="ORG-1"><title>${safeTitle}</title><item identifier="ITEM-1" identifierref="RES-1"><title>${safeTitle}</title></item></organization></organizations>
 <resources><resource identifier="RES-1" type="webcontent" adlcp:scormtype="sco" href="index.html"><file href="index.html"/><file href="styles.css"/><file href="runtime.js"/><file href="course.json"/></resource></resources>
</manifest>`;
}

function buildCourseHtml(title: string, modules: ModuleRow[], lessons: LessonRow[], format: string) {
  const sections = modules.map((module) => {
    const cards = lessons.filter((lesson) => lesson.moduleId === module.id).map((lesson, index) => `<article class="lesson" data-lesson>
      <div class="lesson-number">${index + 1}</div><div><span>${html(lesson.lessonType)} · ${lesson.durationMinutes} min</span><h2>${html(lesson.title)}</h2><p>${html(lesson.summary)}</p>
      ${lesson.mediaUrl ? `<a href="${html(lesson.mediaUrl)}" target="_blank" rel="noopener">Open linked learning resource →</a>` : ""}
      <button type="button" data-complete>Mark lesson complete</button></div></article>`).join("");
    return `<section><p class="module-label">MODULE ${module.position}</p><h1>${html(module.title)}</h1>${cards}</section>`;
  }).join("");
  return `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>${html(title)}</title><link rel="stylesheet" href="styles.css"></head>
<body><header><div class="brand">connect<span>point</span></div><div><strong>${html(title)}</strong><small>${format === "scorm2004" ? "SCORM 2004" : "SCORM 1.2"} course</small></div><div class="progress"><b id="progress">0%</b><span>complete</span></div></header>
<main><div class="intro"><span>LEARN · BUILD · PROVE</span><h1>${html(title)}</h1><p>Practical learning designed to lead to demonstrable project experience.</p></div>${sections}<div class="finish"><h2>You have completed this course.</h2><p>Your result has been sent to the learning platform.</p><button id="finish">Finish course</button></div></main><script src="runtime.js"></script></body></html>`;
}

const courseCss = `:root{--blue:#08a9df;--deep:#063c5c;--lime:#b6d532;--line:#dce6e8}*{box-sizing:border-box}body{margin:0;background:#f4f8f7;color:#183b4d;font-family:Arial,sans-serif}header{height:72px;background:#fff;border-bottom:1px solid var(--line);display:flex;align-items:center;gap:22px;padding:12px 28px;position:sticky;top:0}header .brand{font-size:24px;font-weight:900;color:var(--blue)}header .brand span{color:var(--lime)}header>div:nth-child(2){display:flex;flex-direction:column;flex:1;border-left:1px solid var(--line);padding-left:22px}header small{color:#7b8c93;margin-top:3px}.progress{display:flex;flex-direction:column;text-align:right}.progress b{color:var(--deep);font-size:20px}.progress span{font-size:9px;color:#84959c}main{max-width:860px;margin:0 auto;padding:55px 24px 90px}.intro{background:linear-gradient(120deg,#063c5c,#087291);color:#fff;border-radius:18px;padding:48px;margin-bottom:36px}.intro>span,.module-label{font-size:9px;letter-spacing:.17em;font-weight:800}.intro h1{font-family:Georgia,serif;font-size:38px;margin:15px 0 10px}.intro p{color:#c9dce4;line-height:1.6}.module-label{color:#84959c;margin-top:38px}section>h1{font-family:Georgia,serif;color:var(--deep)}.lesson{display:grid;grid-template-columns:42px 1fr;gap:16px;background:#fff;border:1px solid var(--line);border-radius:13px;padding:23px;margin:12px 0}.lesson-number{width:36px;height:36px;border-radius:10px;background:#e6f7fc;color:#078ebf;display:grid;place-items:center;font-weight:800}.lesson span{font-size:8px;letter-spacing:.12em;color:#8a999f;text-transform:uppercase}.lesson h2{font-family:Georgia,serif;color:var(--deep);margin:7px 0;font-size:20px}.lesson p{color:#687e88;line-height:1.6;font-size:13px}.lesson a{color:#078ebf;font-size:12px}.lesson button,.finish button{display:block;border:0;border-radius:8px;background:var(--lime);padding:10px 14px;font-weight:800;color:#284314;margin-top:16px;cursor:pointer}.lesson.done{border-color:#b9d36d;background:#fbfdf7}.lesson.done button{background:#e8f3c9}.finish{display:none;text-align:center;background:#fff;border-radius:16px;padding:38px;margin-top:30px}.finish.show{display:block}.finish h2{font-family:Georgia,serif;color:var(--deep)}`;

function runtimeJs(format: "scorm12" | "scorm2004") {
  return `const FORMAT="${format}";let api=null;let complete=new Set();const lessons=[...document.querySelectorAll("[data-lesson]")];
function findApi(){let w=window;for(let i=0;i<10&&w;i++){if(FORMAT==="scorm2004"&&w.API_1484_11)return w.API_1484_11;if(FORMAT==="scorm12"&&w.API)return w.API;try{w=w.parent}catch{return null}}return null}
function init(){api=findApi();if(!api)return;if(FORMAT==="scorm2004"){api.Initialize("");api.SetValue("cmi.completion_status","incomplete")}else{api.LMSInitialize("");api.LMSSetValue("cmi.core.lesson_status","incomplete")}}
function save(){const pct=Math.round(complete.size/Math.max(1,lessons.length)*100);document.getElementById("progress").textContent=pct+"%";if(!api)return;if(FORMAT==="scorm2004"){api.SetValue("cmi.progress_measure",String(pct/100));api.Commit("")}else{api.LMSSetValue("cmi.core.score.raw",String(pct));api.LMSCommit("")}if(pct===100)document.querySelector(".finish").classList.add("show")}
lessons.forEach((lesson,i)=>lesson.querySelector("[data-complete]").addEventListener("click",()=>{complete.add(i);lesson.classList.add("done");lesson.querySelector("button").textContent="✓ Lesson complete";save()}));
document.getElementById("finish").addEventListener("click",()=>{if(api){if(FORMAT==="scorm2004"){api.SetValue("cmi.completion_status","completed");api.SetValue("cmi.success_status","passed");api.SetValue("cmi.score.raw","100");api.Commit("");api.Terminate("")}else{api.LMSSetValue("cmi.core.lesson_status","passed");api.LMSSetValue("cmi.core.score.raw","100");api.LMSCommit("");api.LMSFinish("")}}document.getElementById("finish").textContent="Completion saved"});
window.addEventListener("load",init);`;
}

function makeZip(files: Array<{ name: string; data: Uint8Array }>) {
  const local: Uint8Array[] = [];
  const central: Uint8Array[] = [];
  let offset = 0;
  for (const file of files) {
    const name = new TextEncoder().encode(file.name);
    const crc = crc32(file.data);
    const localHeader = bytes(30 + name.length);
    const localView = new DataView(localHeader.buffer);
    localView.setUint32(0, 0x04034b50, true); localView.setUint16(4, 20, true); localView.setUint16(6, 0x0800, true);
    localView.setUint32(14, crc, true); localView.setUint32(18, file.data.length, true); localView.setUint32(22, file.data.length, true); localView.setUint16(26, name.length, true);
    localHeader.set(name, 30); local.push(localHeader, file.data);
    const centralHeader = bytes(46 + name.length);
    const centralView = new DataView(centralHeader.buffer);
    centralView.setUint32(0, 0x02014b50, true); centralView.setUint16(4, 20, true); centralView.setUint16(6, 20, true); centralView.setUint16(8, 0x0800, true);
    centralView.setUint32(16, crc, true); centralView.setUint32(20, file.data.length, true); centralView.setUint32(24, file.data.length, true); centralView.setUint16(28, name.length, true); centralView.setUint32(42, offset, true);
    centralHeader.set(name, 46); central.push(centralHeader);
    offset += localHeader.length + file.data.length;
  }
  const centralSize = central.reduce((sum, part) => sum + part.length, 0);
  const end = bytes(22); const endView = new DataView(end.buffer);
  endView.setUint32(0, 0x06054b50, true); endView.setUint16(8, files.length, true); endView.setUint16(10, files.length, true); endView.setUint32(12, centralSize, true); endView.setUint32(16, offset, true);
  return concat([...local, ...central, end]);
}

function crc32(data: Uint8Array) {
  let crc = 0xffffffff;
  for (const value of data) {
    crc ^= value;
    for (let bit = 0; bit < 8; bit++) crc = (crc >>> 1) ^ (crc & 1 ? 0xedb88320 : 0);
  }
  return (crc ^ 0xffffffff) >>> 0;
}
function bytes(length: number) { return new Uint8Array(length); }
function concat(parts: Uint8Array[]) { const output = bytes(parts.reduce((sum, part) => sum + part.length, 0)); let offset = 0; for (const part of parts) { output.set(part, offset); offset += part.length; } return output; }
function html(value: string) { return value.replace(/[&<>"']/g, (char) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" }[char]!)); }
function xml(value: string) { return html(value); }
