// lib/schema.js — shared constants + normalization that repairs whatever the model returns
// into a guaranteed-valid assignment structure. Runs on both server and client.

export const ASSIGNMENT_TYPES = [
  { id: "quiz", label: "Quiz", hint: "Short check for understanding" },
  { id: "test", label: "Test", hint: "Full exam with mixed sections" },
  { id: "worksheet", label: "Worksheet", hint: "Guided practice to work through" },
  { id: "discussion", label: "Discussion questions", hint: "Open prompts with facilitation notes" },
  { id: "case_study", label: "Case study", hint: "A scenario plus analysis questions" },
];

export const QUESTION_TYPES = [
  { id: "multiple_choice", label: "Multiple choice" },
  { id: "true_false", label: "True / False" },
  { id: "short_answer", label: "Short answer" },
  { id: "essay", label: "Essay" },
  { id: "fill_blank", label: "Fill in the blank" },
  { id: "matching", label: "Matching" },
];

export const GRADE_LEVELS = [
  "Kindergarten", "Grade 1", "Grade 2", "Grade 3", "Grade 4", "Grade 5",
  "Grade 6", "Grade 7", "Grade 8", "Grade 9", "Grade 10", "Grade 11", "Grade 12",
  "College — introductory", "College — advanced", "Adult education",
];

export const DIFFICULTIES = ["Easy", "Medium", "Hard", "Mixed"];

export const DEFAULT_POINTS = {
  multiple_choice: 2,
  true_false: 1,
  short_answer: 3,
  essay: 10,
  fill_blank: 2,
  matching: 4,
  discussion: 5,
};

export function questionTypeLabel(type) {
  if (type === "discussion") return "Discussion prompt";
  return QUESTION_TYPES.find((t) => t.id === type)?.label || type;
}

export function newId() {
  return "q_" + Date.now().toString(36) + Math.random().toString(36).slice(2, 8);
}

function str(v, fallback = "") {
  if (v == null) return fallback;
  return String(v).trim() || fallback;
}

function num(v, fallback) {
  const n = Number(v);
  return Number.isFinite(n) && n >= 0 ? n : fallback;
}

function arr(v) {
  return Array.isArray(v) ? v : [];
}

// Normalize one question object from the model (or the editor) into a valid shape.
// Returns null if the question is hopeless and should be dropped.
export function normalizeQuestion(raw) {
  if (!raw || typeof raw !== "object") return null;
  let type = str(raw.type).toLowerCase().replace(/[\s-]+/g, "_");
  const aliases = {
    multiplechoice: "multiple_choice", mcq: "multiple_choice", multiple_choice_question: "multiple_choice",
    truefalse: "true_false", tf: "true_false",
    shortanswer: "short_answer", short_response: "short_answer",
    fillblank: "fill_blank", fill_in_the_blank: "fill_blank", fitb: "fill_blank", cloze: "fill_blank",
    match: "matching", open_ended: "discussion", discussion_prompt: "discussion",
  };
  type = aliases[type] || type;
  const known = ["multiple_choice", "true_false", "short_answer", "essay", "fill_blank", "matching", "discussion"];
  if (!known.includes(type)) return null;

  const q = {
    id: str(raw.id) || newId(),
    type,
    question: str(raw.question || raw.prompt || raw.text),
    points: num(raw.points, DEFAULT_POINTS[type]),
    explanation: str(raw.explanation),
    sourceRef: str(raw.sourceRef || raw.source_ref || raw.source),
    verification: raw.verification && typeof raw.verification === "object"
      ? { status: raw.verification.status === "warn" ? "warn" : raw.verification.status === "pass" ? "pass" : "unchecked", note: str(raw.verification.note) }
      : { status: "unchecked", note: "" },
  };
  if (!q.question) return null;

  if (type === "multiple_choice") {
    let options = arr(raw.options || raw.choices).map((o) => str(typeof o === "object" ? o?.text : o)).filter(Boolean);
    if (options.length < 2) return null;
    options = options.slice(0, 6);
    let ci = raw.correctIndex ?? raw.correct_index;
    if (ci == null && raw.correctAnswer != null) {
      // Model may give the answer as a letter ("B") or the option text.
      const ca = str(raw.correctAnswer);
      const letter = ca.match(/^[A-F]$/i);
      if (letter) ci = letter[0].toUpperCase().charCodeAt(0) - 65;
      else {
        const idx = options.findIndex((o) => o.toLowerCase() === ca.toLowerCase());
        ci = idx >= 0 ? idx : 0;
      }
    }
    ci = Math.min(Math.max(num(ci, 0), 0), options.length - 1);
    q.options = options;
    q.correctIndex = ci;
  } else if (type === "true_false") {
    let ans = raw.correctAnswer ?? raw.answer;
    if (typeof ans === "string") ans = /^(t|true|yes)/i.test(ans.trim());
    q.correctAnswer = Boolean(ans);
  } else if (type === "short_answer") {
    q.sampleAnswer = str(raw.sampleAnswer || raw.sample_answer || raw.answer || raw.correctAnswer);
    q.keyPoints = arr(raw.keyPoints || raw.key_points).map((k) => str(k)).filter(Boolean);
  } else if (type === "essay") {
    q.sampleResponse = str(raw.sampleResponse || raw.sample_response || raw.sampleAnswer || raw.answer);
    q.rubric = arr(raw.rubric).map((r) => {
      if (typeof r === "string") return { criterion: str(r), points: 0, description: "" };
      return { criterion: str(r?.criterion || r?.name), points: num(r?.points, 0), description: str(r?.description) };
    }).filter((r) => r.criterion);
  } else if (type === "fill_blank") {
    // Ensure the question text actually contains blanks
    let text = q.question.replace(/_{2,}/g, "______");
    let answers = arr(raw.answers || raw.blanks).map((a) => str(typeof a === "object" ? a?.answer : a)).filter(Boolean);
    if (!answers.length && raw.answer) answers = [str(raw.answer)];
    if (!answers.length) return null;
    const blanks = (text.match(/______/g) || []).length;
    if (blanks === 0) return null;
    q.question = text;
    q.answers = answers.slice(0, blanks).concat(Array(Math.max(0, blanks - answers.length)).fill("")).map((a) => a || "(answer missing — edit me)");
  } else if (type === "matching") {
    const pairs = arr(raw.pairs || raw.items).map((p) => ({ left: str(p?.left || p?.term), right: str(p?.right || p?.definition || p?.match) }))
      .filter((p) => p.left && p.right);
    if (pairs.length < 2) return null;
    q.pairs = pairs.slice(0, 10);
    q.points = num(raw.points, q.pairs.length);
  } else if (type === "discussion") {
    q.talkingPoints = arr(raw.talkingPoints || raw.talking_points || raw.keyPoints).map((k) => str(k)).filter(Boolean);
    q.followUps = arr(raw.followUps || raw.follow_ups).map((k) => str(k)).filter(Boolean);
    q.sampleResponse = str(raw.sampleResponse || raw.sample_response);
  }
  return q;
}

export function normalizeAssignment(raw, config) {
  const questions = arr(raw?.questions).map(normalizeQuestion).filter(Boolean);
  return {
    title: str(raw?.title, "Untitled assignment"),
    instructions: str(raw?.instructions),
    caseStudy: str(raw?.caseStudy || raw?.case_study || raw?.scenario),
    assignmentType: config.assignmentType,
    gradeLevel: config.gradeLevel,
    subject: config.subject,
    difficulty: config.difficulty,
    questions,
  };
}

export function totalPoints(questions) {
  return (questions || []).reduce((s, q) => s + (Number(q.points) || 0), 0);
}

// Create a blank question of a given type for the "Add question" menu.
export function blankQuestion(type) {
  const base = { id: newId(), type, question: "", points: DEFAULT_POINTS[type] || 2, explanation: "", sourceRef: "", verification: { status: "unchecked", note: "" } };
  if (type === "multiple_choice") return { ...base, options: ["", "", "", ""], correctIndex: 0 };
  if (type === "true_false") return { ...base, correctAnswer: true };
  if (type === "short_answer") return { ...base, sampleAnswer: "", keyPoints: [] };
  if (type === "essay") return { ...base, sampleResponse: "", rubric: [] };
  if (type === "fill_blank") return { ...base, question: "______", answers: [""] };
  if (type === "matching") return { ...base, pairs: [{ left: "", right: "" }, { left: "", right: "" }], points: 2 };
  if (type === "discussion") return { ...base, talkingPoints: [], followUps: [], sampleResponse: "" };
  return base;
}
