// lib/canvas/qti.js — render the neutral Canvas IR (map.js) into a Canvas QTI 1.2
// assessment XML document. Structure mirrors Canvas's own QTI export / parser fixtures
// (instructure/qti) so the package imports cleanly into Classic Quizzes (and, via the
// "import as New Quizzes" checkbox, New Quizzes too).

const xmlEsc = (s) =>
  String(s ?? "")
    .replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");

const matHtml = (html) =>
  `<material><mattext texttype="text/html">${xmlEsc(html)}</mattext></material>`;
const matPlain = (text) =>
  `<material><mattext texttype="text/plain">${xmlEsc(text)}</mattext></material>`;

const OUTCOMES =
  `<outcomes><decvar maxvalue="100" minvalue="0" varname="SCORE" vartype="Decimal"/></outcomes>`;

const metaField = (label, entry) =>
  `<qtimetadatafield><fieldlabel>${label}</fieldlabel><fieldentry>${xmlEsc(entry)}</fieldentry></qtimetadatafield>`;

// Even split of 100 points across n blanks/pairs, last absorbs the rounding remainder.
function addValues(n) {
  const base = Math.floor((10000 / n)) / 100;
  const vals = Array(n).fill(base);
  vals[n - 1] = Math.round((100 - base * (n - 1)) * 100) / 100;
  return vals.map((v) => v.toFixed(2));
}

function generalFeedback(it) {
  const html = it.feedback?.neutralHtml;
  if (!html) return { block: "", cond: "" };
  return {
    block: `<itemfeedback ident="general_fb"><flow_mat>${matHtml(html)}</flow_mat></itemfeedback>`,
    cond: `<respcondition continue="Yes"><conditionvar><other/></conditionvar>` +
      `<displayfeedback feedbacktype="Response" linkrefid="general_fb"/></respcondition>`,
  };
}

function wrapItem(it, presentationInner, conditions, extraMeta = "") {
  const fb = generalFeedback(it);
  const meta =
    metaField("question_type", it.canvasType) +
    metaField("points_possible", String(it.points ?? 0)) +
    extraMeta;
  return `<item ident="${xmlEsc(it.ident)}" title="${xmlEsc(it.title || "Question")}">
<itemmetadata><qtimetadata>${meta}</qtimetadata></itemmetadata>
<presentation>${matHtml(it.promptHtml || "")}${presentationInner}</presentation>
<resprocessing>${OUTCOMES}${conditions}${fb.cond}</resprocessing>${fb.block}</item>`;
}

function renderChoice(it, cardinality) {
  const labels = it.choices
    .map((c) => `<response_label ident="${c.ident}">${matHtml(c.html)}</response_label>`)
    .join("");
  const present =
    `<response_lid ident="response1" rcardinality="${cardinality}"><render_choice>${labels}</render_choice></response_lid>`;
  const correct = it.choices.filter((c) => c.correct);
  const pick = (correct[0] || it.choices[0]);
  const cond =
    `<respcondition continue="No"><conditionvar><varequal respident="response1">${pick.ident}</varequal></conditionvar>` +
    `<setvar action="Set" varname="SCORE">100</setvar></respcondition>`;
  const extraMeta = metaField("original_answer_ids", it.choices.map((c) => c.ident).join(","));
  return wrapItem(it, present, cond, extraMeta);
}

function renderEssay(it) {
  const present =
    `<response_str ident="response1" rcardinality="Single"><render_fib><response_label ident="answer1" rshuffle="No"/></render_fib></response_str>`;
  const cond = `<respcondition continue="No"><conditionvar><other/></conditionvar></respcondition>`;
  return wrapItem(it, present, cond);
}

function renderFillBlanks(it) {
  const present = it.blanks.map((b) => {
    const labels = b.answers
      .map((a) => `<response_label ident="${a.ident}">${matPlain(a.textPlain)}</response_label>`)
      .join("");
    return `<response_lid ident="${b.respIdent}">${matPlain(b.name)}<render_choice>${labels}</render_choice></response_lid>`;
  }).join("");
  const add = addValues(it.blanks.length);
  const conds = it.blanks.map((b, i) =>
    `<respcondition><conditionvar><varequal respident="${b.respIdent}">${b.answers[0].ident}</varequal></conditionvar>` +
    `<setvar varname="SCORE" action="Add">${add[i]}</setvar></respcondition>`
  ).join("");
  return wrapItem(it, present, conds);
}

function renderMatching(it) {
  const rights = it.rights
    .map((r) => `<response_label ident="${r.ident}">${matPlain(r.textPlain)}</response_label>`)
    .join("");
  const present = it.lefts.map((l) =>
    `<response_lid ident="${l.respIdent}" rcardinality="Single">${matPlain(l.textPlain)}` +
    `<render_choice>${rights}</render_choice></response_lid>`
  ).join("");
  const add = addValues(it.lefts.length);
  const conds = it.lefts.map((l, i) =>
    `<respcondition><conditionvar><varequal respident="${l.respIdent}">${l.correctRightIdent}</varequal></conditionvar>` +
    `<setvar varname="SCORE" action="Add">${add[i]}</setvar></respcondition>`
  ).join("");
  return wrapItem(it, present, conds);
}

function renderTextOnly(it) {
  const meta = metaField("question_type", "text_only_question") + metaField("points_possible", "0");
  return `<item ident="${xmlEsc(it.ident)}" title="${xmlEsc(it.title || "Text")}">
<itemmetadata><qtimetadata>${meta}</qtimetadata></itemmetadata>
<presentation>${matHtml(it.promptHtml || "")}</presentation>
<resprocessing>${OUTCOMES}<respcondition continue="No"><conditionvar><other/></conditionvar></respcondition></resprocessing></item>`;
}

function renderItem(it) {
  switch (it.canvasType) {
    case "multiple_choice_question": return renderChoice(it, "Single");
    case "true_false_question": return renderChoice(it, "Single");
    case "essay_question": return renderEssay(it);
    case "fill_in_multiple_blanks_question": return renderFillBlanks(it);
    case "matching_question": return renderMatching(it);
    case "text_only_question": return renderTextOnly(it);
    default: return "";
  }
}

export function renderAssessmentXml(ir, assessmentIdent) {
  const items = ir.items.map(renderItem).filter(Boolean).join("\n");
  return `<?xml version="1.0" encoding="UTF-8"?>
<questestinterop xmlns="http://www.imsglobal.org/xsd/ims_qtiasiv1p2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.imsglobal.org/xsd/ims_qtiasiv1p2 http://www.imsglobal.org/xsd/ims_qtiasiv1p2p1.xsd">
<assessment ident="${xmlEsc(assessmentIdent)}" title="${xmlEsc(ir.quiz.title)}">
<qtimetadata>
<qtimetadatafield><fieldlabel>cc_maxattempts</fieldlabel><fieldentry>1</fieldentry></qtimetadatafield>
</qtimetadata>
<section ident="root_section">
${items}
</section>
</assessment>
</questestinterop>`;
}

export { xmlEsc };
