// JNM Home Education Program - Complete Backend API Server
const express = require('express');
const cors = require('cors');
const bodyParser = require('body-parser');
const dotenv = require('dotenv');
const { Pool } = require('pg');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const multer = require('multer');
const path = require('path');
const fs = require('fs');

dotenv.config();

const app = express();
const PORT = process.env.PORT || 5000;
const deploymentMode = String(process.env.NODE_ENV || '').toLowerCase();
if ((deploymentMode === 'production' || deploymentMode === 'staging') && !process.env.JWT_SECRET) {
  throw new Error('JWT_SECRET is required for staging and production.');
}

// Database connection
const pool = new Pool({
  connectionString: process.env.DATABASE_URL || undefined,
  user: process.env.DB_USER || 'homeschool_user',
  password: process.env.DB_PASSWORD,
  host: process.env.DB_HOST || 'localhost',
  port: process.env.DB_PORT || 5432,
  database: process.env.DB_NAME || 'jnm_homeschool_db'
});

// Middleware
const allowedOrigins = String(process.env.FRONTEND_ORIGIN || '')
  .split(',').map(value => value.trim()).filter(Boolean);
app.use(cors({
  origin(origin, callback) {
    if (!origin || allowedOrigins.length === 0 || allowedOrigins.includes(origin)) return callback(null, true);
    return callback(new Error('Origin is not allowed by JNM CORS settings.'));
  },
  credentials: true
}));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use('/uploads', express.static('uploads'));

// Create uploads directory
if (!fs.existsSync('./uploads')) fs.mkdirSync('./uploads');

// File upload config
const storage = multer.diskStorage({
  destination: (req, file, cb) => cb(null, './uploads'),
  filename: (req, file, cb) => cb(null, Date.now() + '-' + file.originalname)
});
const upload = multer({ storage, limits: { fileSize: 10 * 1024 * 1024 } });

// JWT middleware
const authenticateToken = (req, res, next) => {
  const authHeader = req.headers['authorization'];
  const token = authHeader && authHeader.split(' ')[1];
  if (!token) return res.status(401).json({ error: 'Access denied' });
  jwt.verify(token, process.env.JWT_SECRET || 'jnm_secret_key', (err, user) => {
    if (err) return res.status(403).json({ error: 'Invalid token' });
    req.user = user;
    next();
  });
};

// Optional JWT reader for endpoints that can be opened in demo mode,
// but should only count learning time for an actual student login.
const optionalAuthenticateToken = (req, res, next) => {
  const authHeader = req.headers['authorization'];
  const token = authHeader && authHeader.split(' ')[1];
  if (!token) return next();
  jwt.verify(token, process.env.JWT_SECRET || 'jnm_secret_key', (err, user) => {
    if (!err && user) req.user = user;
    next();
  });
};

function isStudentLearningSession(req, studentId) {
  return !!(req && req.user && req.user.role === 'student' && String(req.user.id) === String(studentId));
}

// Letter grade calculator
function getLetterGrade(percentage) {
  if (percentage >= 97) return 'A+';
  if (percentage >= 93) return 'A';
  if (percentage >= 90) return 'A-';
  if (percentage >= 87) return 'B+';
  if (percentage >= 83) return 'B';
  if (percentage >= 80) return 'B-';
  if (percentage >= 77) return 'C+';
  if (percentage >= 73) return 'C';
  if (percentage >= 70) return 'C-';
  if (percentage >= 67) return 'D+';
  if (percentage >= 63) return 'D';
  if (percentage >= 60) return 'D-';
  return 'F';
}

function parseJsonList(value, fallback = []) {
  if (!value) return fallback;
  if (Array.isArray(value)) return value;
  try {
    const parsed = typeof value === 'string' ? JSON.parse(value) : value;
    return Array.isArray(parsed) ? parsed : fallback;
  } catch {
    return fallback;
  }
}
function meaningfulWords(value) {
  return String(value || '')
    .replace(/[^A-Za-z0-9'’\s-]/g, ' ')
    .split(/\s+/)
    .map(w => w.trim())
    .filter(w => /[A-Za-z0-9]/.test(w));
}
function isMeaningfulText(value, minWords = 1) {
  return meaningfulWords(value).length >= minWords;
}
function practicePromptLooksLikeWriting(p) {
  const text = String([p?.question, p?.hint].filter(Boolean).join(' ')).toLowerCase();
  return /\b(write|explain|describe|reflect|respond|sentence|paragraph|thesis|essay|journal|in your own words|tell why|tell how)\b/.test(text);
}
function isSpellingAssignmentRow(row) {
  const text = String([row?.subject, row?.course_name, row?.title, row?.assignment_type, row?.lesson_title].filter(Boolean).join(' ')).toLowerCase();
  return text.includes('spelling');
}
function writingNextStepFromResult(writing) {
  if (!writing) return '';
  if (writing.score >= 90) return 'Keep using strong details and clear sentences in your next writing assignment.';
  if (writing.score >= 75) return 'Next time, add one more detail from the passage to make your writing stronger.';
  if (writing.score >= 60) return 'Next time, make sure your answer includes a clear idea and details from the passage.';
  if (writing.details?.copied_prompt) return 'Next time, answer the prompt in your own words instead of copying the question.';
  return 'Next time, write a complete response that answers the prompt and connects back to the passage.';
}
function validateRequiredStudentWork({ questions = [], answers = [], written_response = '', practice = [], practice_responses = {}, skipPractice = false }) {
  const errors = [];
  const answerAt = (idx) => answers && answers[idx] !== undefined && answers[idx] !== null ? answers[idx] : written_response;

  questions.forEach((q, idx) => {
    const type = String(q?.type || '').toLowerCase();
    const value = answerAt(idx);
    if (type === 'multiple_choice' || type === 'true_false') {
      if (!String(value || '').trim()) errors.push(`Question ${idx + 1} needs an answer.`);
    } else if (type === 'written_response') {
      if (!isMeaningfulText(value, 5)) errors.push(`Question ${idx + 1} needs a complete written response.`);
    } else {
      if (!isMeaningfulText(value, 1)) errors.push(`Question ${idx + 1} needs an answer.`);
    }
  });

  if (!skipPractice && Array.isArray(practice) && practice.length > 0) {
    practice.forEach((p, idx) => {
      const value = practice_responses && typeof practice_responses === 'object' ? practice_responses[idx] : '';
      const choices = Array.isArray(p?.options) ? p.options : [];
      if (choices.length > 0) {
        if (!String(value || '').trim()) errors.push(`Practice ${idx + 1} needs a selected answer.`);
      } else if (practicePromptLooksLikeWriting(p)) {
        if (!isMeaningfulText(value, 5)) errors.push(`Practice ${idx + 1} needs a complete response before continuing.`);
      } else if (!isMeaningfulText(value, 1)) {
        errors.push(`Practice ${idx + 1} needs a response before continuing.`);
      }
    });
  }
  return errors;
}


function autoGradeShortWriting(answer, prompt = '', passageText = '') {
  const text = String(answer || '').trim();
  if (!text) {
    return {
      score: 0,
      is_correct: false,
      needs_review: false,
      label: 'Needs Revision',
      feedback: 'No written response was provided.',
      details: { word_count: 0, sentence_count: 0, strengths: [], improvements: ['Write a response that answers the prompt.'] }
    };
  }

  const promptLower = String(prompt || '').toLowerCase();
  const words = text.match(/\b[\w'\u2019]+\b/g) || [];
  const sentences = text.split(/[.!?]+/).map(s => s.trim()).filter(Boolean);
  const startsCapital = /^[A-Z]/.test(text);
  const hasEndPunctuation = /[.!?]$/.test(text);
  const possibleRunOn = sentences.some(s => (s.match(/\b(and|but|because|so)\b/gi) || []).length >= 4 && s.split(/\s+/).length > 35);
  const capitalizationIssues = (text.match(/(?:^|[.!?]\s+)[a-z]/g) || []).length;

  const normalizeForPromptCheck = (value) => String(value || '')
    .toLowerCase()
    .replace(/[^a-z0-9\s]/g, ' ')
    .replace(/\s+/g, ' ')
    .trim();
  const normalizedAnswer = normalizeForPromptCheck(text);
  const normalizedPrompt = normalizeForPromptCheck(prompt);

  const wantsOneSentence = /\bone sentence\b/.test(promptLower);
  const wantsParagraph = /\bparagraph\b|body paragraph|essay/.test(promptLower);
  const explicitSentenceReq = promptLower.match(/at least\s+(\d+)\s*(?:to\s*\d+\s*)?sentences?/);
  const requiredSentences = wantsOneSentence ? 1 : explicitSentenceReq ? parseInt(explicitSentenceReq[1], 10) : wantsParagraph ? 3 : 1;
  const requiresEvidence = /\b(detail|details|evidence|support|from the lesson|from the passage|from the text|from the story|using details|use at least one)\b/i.test(promptLower);
  const figurativePrompt = /simile|metaphor|personification|figurative language/.test(promptLower);
  const explanationPrompt = /explain|what it means|meaning|means/.test(promptLower);

  // Detect when the student copied the prompt instead of answering it.
  if (normalizedPrompt && normalizedPrompt.length > 35) {
    const promptWordsForCopy = normalizedPrompt.split(' ').filter(Boolean);
    const answerWordsForCopy = normalizedAnswer.split(' ').filter(Boolean);
    const promptStart = promptWordsForCopy.slice(0, Math.min(12, promptWordsForCopy.length)).join(' ');
    const promptSet = new Set(promptWordsForCopy.filter(w => w.length > 3));
    const answerSet = new Set(answerWordsForCopy.filter(w => w.length > 3));
    let overlap = 0;
    answerSet.forEach(w => { if (promptSet.has(w)) overlap++; });
    const overlapRatio = answerSet.size ? overlap / answerSet.size : 0;
    const copiedPrompt = (promptStart.length > 30 && normalizedAnswer.includes(promptStart)) ||
      (overlapRatio >= 0.78 && answerWordsForCopy.length <= promptWordsForCopy.length + 10);
    if (copiedPrompt) {
      return {
        score: 15,
        is_correct: false,
        needs_review: false,
        label: 'Needs Revision',
        feedback: 'What can improve: This response repeats the question but does not answer it yet. Give a real answer in your own words.',
        details: { word_count: answerWordsForCopy.length, sentence_count: sentences.length, copied_prompt: true, strengths: [], improvements: ['Answer the prompt in your own words instead of copying the question.'] }
      };
    }
  }

  // Passage/detail detection only matters when the prompt asks for support/details.
  const hasSignalPhrase = /because|for example|the text|the passage|the story|the lesson|shows|states|says|detail|evidence|explains|according to|it also|one reason|another reason|this shows|this means|which means/i.test(text) || /"[^"]+"/.test(text);
  let hasPassageEvidence = false;
  if (requiresEvidence && passageText && passageText.length > 40) {
    const stopWords = new Set(['that','this','with','from','they','them','their','have','been','were','would','could','should','about','which','there','other','some','what','when','into','each','than','also','more','very','just','like','then','than','much','many','only','over','such','after','before','between','through','during','because','these','those','first','every','still','another','being','where','while','most','make','made','even','both','does','done']);
    const passageWords = passageText.toLowerCase().match(/\b[a-z]{4,}\b/g) || [];
    const sigPassageWords = [...new Set(passageWords.filter(w => !stopWords.has(w)))];
    const answerLower = text.toLowerCase();
    let matchCount = 0;
    sigPassageWords.forEach(pw => { if (answerLower.includes(pw)) matchCount++; });
    hasPassageEvidence = matchCount >= 2;
  }
  const hasEvidence = !requiresEvidence || hasSignalPhrase || hasPassageEvidence;

  // Prompt-specific figurative language grading. If the prompt asks for one sentence,
  // do not penalize the student for giving one strong sentence.
  const hasSimile = /\b(like|as)\b/i.test(text);
  const hasMetaphor = /\b(is|was|are|were)\b\s+(?:a|an|the)?\s*[a-z]+/i.test(text) && !hasSimile;
  const hasPersonification = /\b(stars?|wind|sun|moon|clouds?|trees?|flowers?|rain|storm|ocean|river|leaves?)\b[^.!?]{0,40}\b(danced|whispered|sang|smiled|cried|ran|raced|watched|hugged|called)\b/i.test(text);
  const hasFigurativeLanguage = hasSimile || hasMetaphor || hasPersonification;
  const hasExplanation = !explanationPrompt || /\bmeans|meaning|which means|this means|it means|shows|represents|because|explains\b/i.test(text);
  if (figurativePrompt && hasFigurativeLanguage && hasExplanation && words.length >= 10 && startsCapital && hasEndPunctuation) {
    const strengths = ['You answered the prompt.', 'You used figurative language.', 'You explained what it means.', 'Capitalization looks good.', 'Punctuation was used correctly.'];
    const improvements = [];
    let score = 92;
    if (words.length >= 16) { score += 5; strengths.push('Your response includes helpful detail.'); }
    if (sentences.length < requiredSentences) score -= 5;
    score = Math.max(80, Math.min(100, score));
    return {
      score,
      is_correct: score >= 70,
      needs_review: false,
      label: score >= 90 ? 'Excellent Response' : 'Good Response',
      feedback: 'What you did well: ' + strengths.join(' '),
      details: { word_count: words.length, sentence_count: sentences.length, prompt_required_sentences: requiredSentences, strengths, improvements, figurative_language_detected: true }
    };
  }

  let score = 0;
  const strengths = [];
  const improvements = [];

  // 1. Prompt answered (20 pts)
  if (words.length >= 8) { score += 20; strengths.push('You answered the prompt.'); }
  else { score += Math.min(12, words.length * 2); improvements.push('Make sure your response directly answers the question.'); }

  // 2. Detail level based on what the prompt actually asks for (20 pts)
  const detailTarget = wantsOneSentence ? 12 : explicitSentenceReq ? 30 : wantsParagraph ? 35 : 15;
  if (words.length >= detailTarget) { score += 20; strengths.push('You included enough detail for this prompt.'); }
  else if (words.length >= Math.max(8, Math.round(detailTarget * 0.65))) { score += 14; improvements.push('Add one more detail or example to make your response stronger.'); }
  else { score += Math.min(10, Math.round(words.length / Math.max(1, detailTarget) * 10)); improvements.push('Add more detail to fully answer the question.'); }

  // 3. Sentence count follows the prompt (15 pts)
  if (sentences.length >= requiredSentences) { score += 15; strengths.push(requiredSentences === 1 ? 'You wrote a complete sentence.' : 'You wrote enough complete sentences for the prompt.'); }
  else { score += Math.max(3, Math.round((sentences.length / requiredSentences) * 10)); improvements.push(requiredSentences === 1 ? 'Write a complete sentence.' : `Try to write at least ${requiredSentences} complete sentences for this prompt.`); }

  // 4. Evidence/support only when requested (15 pts)
  if (hasEvidence) { score += 15; if (requiresEvidence) strengths.push('You used details to support your answer.'); }
  else { improvements.push('Include a detail, reason, or example from the lesson to support your answer.'); }

  // 5. Capitalization (10 pts)
  if (startsCapital && capitalizationIssues === 0) { score += 10; strengths.push('Capitalization looks good.'); }
  else { score += startsCapital ? 6 : 0; improvements.push('Make sure every sentence starts with a capital letter.'); }

  // 6. Punctuation (10 pts)
  if (hasEndPunctuation) { score += 10; strengths.push('Punctuation was used correctly.'); }
  else { improvements.push('Make sure every sentence ends with a period, exclamation point, or question mark.'); }

  // 7. Clarity and completeness (10 pts)
  const completeEnough = words.length >= Math.max(10, Math.round(detailTarget * 0.75)) && sentences.length >= requiredSentences;
  if (completeEnough && !possibleRunOn) { score += 10; strengths.push('Your response is clear.'); }
  else if (completeEnough && possibleRunOn) { score += 6; improvements.push('Try breaking long sentences into shorter ones.'); }
  else { improvements.push('Finish your thought completely so the reader can understand your answer.'); }

  score = Math.max(0, Math.min(100, score));
  const label = score >= 90 ? 'Excellent Response' : score >= 75 ? 'Good Response' : score >= 55 ? 'Needs More Detail' : score >= 30 ? 'Needs Revision' : 'Manual Review Needed';
  const needsManual = score < 30 && words.length > 5;
  return {
    score,
    is_correct: score >= 60,
    needs_review: needsManual,
    label,
    feedback: strengths.length > 0
      ? 'What you did well: ' + strengths.join(' ') + (improvements.length > 0 ? ' What can improve: ' + improvements.join(' ') : '')
      : 'What can improve: ' + improvements.join(' '),
    details: { word_count: words.length, sentence_count: sentences.length, prompt_required_sentences: requiredSentences, starts_capital: startsCapital, ending_punctuation: hasEndPunctuation, uses_evidence: requiresEvidence ? hasEvidence : null, strengths, improvements }
  };
}

function rotateOptions(correct, distractors, seed = 0) {
  const base = [correct, ...distractors].filter(Boolean).slice(0, 4);
  while (base.length < 4) base.push('Not enough information is given in the text');
  const shift = Math.abs(parseInt(seed || 0, 10)) % base.length;
  return base.slice(shift).concat(base.slice(0, shift));
}

function q(id, type, question, options, correct_answer) {
  return { id, type, question, options, correct_answer };
}


function improveMathQuestionsForContext(row, original) {
  const titleText = String([row.title, row.lesson_title, row.unit_title, row.description].filter(Boolean).join(' ')).toLowerCase();
  const useIds = [1,2,3,4,5].map((n, i) => original[i]?.id || n);

  if (titleText.includes('decimal place value') || (titleText.includes('place value') && titleText.includes('decimal'))) {
    return [
      q(useIds[0], 'multiple_choice', 'In the number 4.72, what is the value of the 7?', rotateOptions('0.7, or seven tenths', ['7 ones', '0.07, or seven hundredths', '70'], 1), '0.7, or seven tenths'),
      q(useIds[1], 'multiple_choice', 'Which digit is in the hundredths place in 6.384?', rotateOptions('8', ['3', '6', '4'], 2), '8'),
      q(useIds[2], 'multiple_choice', 'Which number has a 5 in the tenths place?', rotateOptions('2.53', ['2.35', '25.3', '0.25'], 3), '2.53'),
      q(useIds[3], 'multiple_choice', 'Which decimal is greater?', rotateOptions('3.7', ['3.65', '3.07', '3.607'], 4), '3.7'),
      q(useIds[4], 'short_answer', 'Round 8.736 to the nearest tenth.', null, '8.7')
    ];
  }

  if (titleText.includes('adding and subtracting decimals') || titleText.includes('add decimals') || titleText.includes('subtract decimals')) {
    return [
      q(useIds[0], 'short_answer', 'Add: 4.56 + 1.23', null, '5.79'),
      q(useIds[1], 'multiple_choice', 'Why should decimal points be lined up before adding or subtracting decimals?', rotateOptions('So each place value is added or subtracted with the same place value.', ['So the numbers look longer.', 'So the answer always has more digits.', 'So the decimal point can be ignored.'], 2), 'So each place value is added or subtracted with the same place value.'),
      q(useIds[2], 'short_answer', 'Subtract: 6.40 - 2.15', null, '4.25'),
      q(useIds[3], 'multiple_choice', 'Which expression is set up correctly?', rotateOptions('3.50 + 2.75', ['35.0 + 2.75', '3.50 + 27.5', '350 + 2.75'], 3), '3.50 + 2.75'),
      q(useIds[4], 'short_answer', 'Maya spent $3.45 on a notebook and $2.30 on pencils. How much did she spend in all?', null, '5.75')
    ];
  }

  if (titleText.includes('comparing decimals')) {
    return [
      q(useIds[0], 'multiple_choice', 'Which decimal is greater?', rotateOptions('0.8', ['0.08', '0.080', '0.008'], 1), '0.8'),
      q(useIds[1], 'multiple_choice', 'Which comparison is true?', rotateOptions('4.25 < 4.52', ['4.25 > 4.52', '4.25 = 4.52', '4.52 < 4.05'], 2), '4.25 < 4.52'),
      q(useIds[2], 'multiple_choice', 'Why is 0.6 greater than 0.06?', rotateOptions('Six tenths is greater than six hundredths.', ['Six hundredths is greater than six tenths.', 'They are equal because both have a 6.', 'The zero before the decimal makes 0.06 larger.'], 3), 'Six tenths is greater than six hundredths.'),
      q(useIds[3], 'short_answer', 'Write the greater decimal: 7.09 or 7.9', null, '7.9'),
      q(useIds[4], 'multiple_choice', 'Put these decimals in order from least to greatest: 2.4, 2.04, 2.44', rotateOptions('2.04, 2.4, 2.44', ['2.44, 2.4, 2.04', '2.4, 2.04, 2.44', '2.04, 2.44, 2.4'], 4), '2.04, 2.4, 2.44')
    ];
  }

  if (titleText.includes('rounding decimals')) {
    return [
      q(useIds[0], 'short_answer', 'Round 3.46 to the nearest tenth.', null, '3.5'),
      q(useIds[1], 'multiple_choice', 'When rounding 7.284 to the nearest hundredth, which digit helps you decide?', rotateOptions('The thousandths digit, 4', ['The ones digit, 7', 'The tenths digit, 2', 'The hundredths digit, 8'], 2), 'The thousandths digit, 4'),
      q(useIds[2], 'short_answer', 'Round 12.738 to the nearest hundredth.', null, '12.74'),
      q(useIds[3], 'multiple_choice', 'Which number rounds to 5.6 when rounded to the nearest tenth?', rotateOptions('5.58', ['5.64', '5.49', '5.04'], 3), '5.58'),
      q(useIds[4], 'short_answer', 'Round 9.95 to the nearest whole number.', null, '10')
    ];
  }

  return original;
}


function assignmentTextBundle(row) {
  return String([row?.title, row?.assignment_title, row?.lesson_title, row?.unit_title, row?.passage_title, row?.source_title, row?.description].filter(Boolean).join(' '));
}

function normalizeAssignmentTitleAndTopic(row) {
  if (!row) return row;
  const text = assignmentTextBundle(row).toLowerCase();
  const passage = String(row.reading_passage || row.source_text || '').toLowerCase();
  const hasOneReading = !!(row.reading_passage || row.source_text) && !(row.second_reading_passage || row.comparison_passage || row.text_b);
  const setTitle = (value) => {
    if (row.title !== undefined) row.title = value;
    if (row.assignment_title !== undefined) row.assignment_title = value;
  };

  if (text.includes('figurative language')) {
    setTitle(/quiz/.test(text) ? 'Figurative Language Quiz' : 'Figurative Language Practice');
    row.lesson_title = 'Figurative Language: Similes, Metaphors, and Personification';
    row.passage_title = 'Figurative Language Mini-Passage';
    row.passage_type = 'literary_skill';
    row.reading_passage = 'Writers use figurative language to help readers picture ideas in a stronger way. A simile compares two things using the words like or as. For example, “The classroom was as quiet as a library” is a simile. A metaphor compares two things without using like or as. For example, “Her smile was sunshine” is a metaphor. Personification gives human actions or feelings to something that is not human. For example, “The wind whispered through the trees” gives the wind a human action.';
  }

  if (/comparing texts/i.test(text) && !(row.second_reading_passage || row.comparison_passage || row.text_b)) {
    if (/sofia|liam|new student/i.test(passage)) {
      setTitle(/quiz/.test(text) ? 'Character Actions and Theme Quiz: The New Student' : 'Character Actions and Theme Practice: The New Student');
      row.lesson_title = 'Character Actions and Theme: The New Student';
    } else {
      setTitle(String(row.title || row.assignment_title || 'Reading Comprehension').replace(/Comparing Texts/gi, 'Reading Comprehension').trim());
      row.lesson_title = String(row.lesson_title || 'Reading Comprehension').replace(/Comparing Texts/gi, 'Reading Comprehension').trim();
    }
  }
  applyGradeFiveLanguageArtsStoryContent(row);
  return row;
}


function getGradeFiveElectiveTopicVideo(row) {
  const gradeNumber = parseInt(row?.grade_level ?? row?.grade ?? '', 10);
  if (gradeNumber !== 5) return null;
  const subject = String(row?.subject || row?.course_subject || row?.course_name || '').toLowerCase();

  // primaryText is the exact lesson/assignment topic. It must win over the broad unit title.
  // Example: Unit title "Body Systems and Wellness" should not override "Sleep Routine Reflection".
  const primaryText = String([
    row?.title, row?.assignment_title, row?.lesson_title, row?.skill_name
  ].filter(Boolean).join(' ')).toLowerCase();

  const topicText = String([
    row?.title, row?.assignment_title, row?.lesson_title, row?.skill_name,
    row?.description, row?.instructions, row?.unit_title
  ].filter(Boolean).join(' ')).toLowerCase();

  const video = (title, source, url, notes) => ({ title, source, url, notes });

  if (subject.includes('health') || subject.includes('nutrition')) {
    if (primaryText.includes('sleep routine') || primaryText.includes('sleep') || primaryText.includes('rest')) {
      return video('All About Sleep for Kids | Why Is Sleep Important?', 'Twinkl USA', 'https://www.youtube.com/watch?v=xXGnjtLyUiI', 'Grade-appropriate lesson about why sleep matters and how rest supports health and focus.');
    }
    if (primaryText.includes('safety plan') || primaryText.includes('safe choices') || primaryText.includes('safe choice') || primaryText.includes('safety')) {
      return video('Making Safe Choices | Personal Safety for Kids', 'Twinkl USA', 'https://www.youtube.com/watch?v=fuVytfTPVzQ', 'Student-facing safety lesson about making safe choices. Replaces the lower-quality Grade 5 health slide video.');
    }
    if (primaryText.includes('body systems') || primaryText.includes('muscles') || primaryText.includes('bones') || primaryText.includes('heart') || primaryText.includes('lungs')) {
      return video('Human Body Systems Video Lesson For Kids', 'GenerationGenius', 'https://www.youtube.com/watch?v=VUNx9ENMLmE', 'Direct public video about human body systems.');
    }
    if (primaryText.includes('hygiene') || primaryText.includes('germs') || primaryText.includes('handwashing') || primaryText.includes('wash')) {
      return video('Germs for Kids', 'Learn Bright', 'https://www.youtube.com/watch?v=z6n-37RTKRo', 'Direct public video about germs and hygiene.');
    }
    if (primaryText.includes('hydration') || primaryText.includes('water habit') || primaryText.includes('drink water')) {
      return video('Healthy Hydration for Kids', 'Hospital for Special Surgery', 'https://www.youtube.com/watch?v=U-NNTDpHwzQ', 'Grade-appropriate hydration video explaining why water matters and how to choose healthy drinks.');
    }
    if (topicText.includes('food group') || topicText.includes('balanced plate') || topicText.includes('myplate')) {
      return video('Food Groups and MyPlate', 'Educational YouTube Video', 'https://www.youtube.com/watch?v=L7QOUiQCb5E', 'Direct public video about MyPlate and food groups.');
    }
    if (topicText.includes('hydration') || topicText.includes('water') || topicText.includes('energy')) {
      return video('Healthy Hydration for Kids', 'Hospital for Special Surgery', 'https://www.youtube.com/watch?v=U-NNTDpHwzQ', 'Grade-appropriate hydration video explaining why water matters and how to choose healthy drinks.');
    }
    if (topicText.includes('sleep') || topicText.includes('rest') || topicText.includes('focus')) {
      return video('All About Sleep for Kids | Why Is Sleep Important?', 'Twinkl USA', 'https://www.youtube.com/watch?v=xXGnjtLyUiI', 'Grade-appropriate lesson about why sleep matters and how rest supports health and focus.');
    }
    if (topicText.includes('safe choices') || topicText.includes('safety') || topicText.includes('safe choice')) {
      return video('Making Safe Choices | Personal Safety for Kids', 'Twinkl USA', 'https://www.youtube.com/watch?v=fuVytfTPVzQ', 'Student-facing safety lesson about making safe choices. Replaces the lower-quality Grade 5 health slide video.');
    }
  }

  if (subject.includes('music')) {
    if (primaryText.includes('clap a rhythm') || primaryText.includes('clap rhythm') || primaryText.includes('beat vs') || primaryText.includes('beat and rhythm')) {
      return video('Rhythm Clap Along - Level 1 to 3', 'Rhythm Music Lesson', 'https://www.youtube.com/watch?v=jYkT0hGTYJM', 'Direct public clap-along rhythm practice video.');
    }
    if (primaryText.includes('create your rhythm') || primaryText.includes('create a rhythm') || primaryText.includes('rhythm pattern') || primaryText.includes('rhythm creation')) {
      return video('How to Create Your Own Rhythm Composition', 'Educational YouTube Video', 'https://www.youtube.com/watch?v=lXTH8w3GnQc', 'Direct public video about composing a simple rhythm pattern.');
    }
    if (primaryText.includes('high and low sound') || primaryText.includes('high or low') || primaryText.includes('melody and pitch') || primaryText.includes('pitch')) {
      return video('High or Low? Learn and Practice High and Low Sounds', 'Elementary Music Lesson', 'https://www.youtube.com/watch?v=jcAa9G64HdA', 'Direct public video about high and low sounds/pitch.');
    }
    if (primaryText.includes('music styles') || primaryText.includes('genres') || primaryText.includes('compare two music styles')) {
      return video('Music Genres for Kids! Learn Music Genres', 'Name That Tune', 'https://www.youtube.com/watch?v=jD79wiJ_02k', 'Student-facing music genres video that gives examples of pop, rock, jazz, and classical styles.');
    }
    if (primaryText.includes('tempo') || primaryText.includes('dynamics')) {
      return video('CLIBURN KIDS Musical Opposites: Tempo & Dynamics', 'Cliburn', 'https://www.youtube.com/watch?v=XlszJiJsz4w', 'Direct public video about tempo and dynamics.');
    }
    if (primaryText.includes('instrument famil') || primaryText.includes('instrument sounds') || primaryText.includes('orchestra')) {
      return video('The Instruments of the Orchestra Song', 'Hopscotch', 'https://www.youtube.com/watch?v=JVY4tj-JTBc', 'Direct public video about instruments and instrument families.');
    }

    if (topicText.includes('clap a rhythm') || topicText.includes('clap rhythm')) {
      return video('Rhythm Clap Along - Level 1 to 3', 'Rhythm Music Lesson', 'https://www.youtube.com/watch?v=jYkT0hGTYJM', 'Direct public clap-along rhythm practice video.');
    }
    if (topicText.includes('rhythm pattern') || topicText.includes('create a rhythm') || topicText.includes('rhythm creation')) {
      return video('How to Create Your Own Rhythm Composition', 'Educational YouTube Video', 'https://www.youtube.com/watch?v=lXTH8w3GnQc', 'Direct public video about composing a simple rhythm pattern.');
    }
    if (topicText.includes('melody') || topicText.includes('pitch') || topicText.includes('high and low sound')) {
      return video('High or Low? Learn and Practice High and Low Sounds', 'Elementary Music Lesson', 'https://www.youtube.com/watch?v=jcAa9G64HdA', 'Direct public video about high and low sounds/pitch.');
    }
    if (topicText.includes('tempo') || topicText.includes('dynamics')) {
      return video('CLIBURN KIDS Musical Opposites: Tempo & Dynamics', 'Cliburn', 'https://www.youtube.com/watch?v=XlszJiJsz4w', 'Direct public video about tempo and dynamics.');
    }
    if (topicText.includes('music styles') || topicText.includes('genres')) {
      return video('Music Genres for Kids! Learn Music Genres', 'Name That Tune', 'https://www.youtube.com/watch?v=jD79wiJ_02k', 'Student-facing music genres video that gives examples of pop, rock, jazz, and classical styles.');
    }
    if (topicText.includes('beat vs') || topicText.includes('beat and rhythm') || topicText.includes('rhythm quiz')) {
      return video('Rhythm or Beat? Learn to Tell the Difference!', 'Educational YouTube Video', 'https://www.youtube.com/watch?v=NzNBUfFlFGs', 'Direct public video explaining beat and rhythm.');
    }
    if (topicText.includes('instrument famil') || topicText.includes('instrument sounds') || topicText.includes('orchestra')) {
      return video('The Instruments of the Orchestra Song', 'Hopscotch', 'https://www.youtube.com/watch?v=JVY4tj-JTBc', 'Direct public video about instruments and instrument families.');
    }
  }

  if (subject.includes('physical') || subject === 'pe' || subject.includes('fitness')) {
    if (primaryText.includes('warm-up') || primaryText.includes('warm up') || primaryText.includes('cool down') || primaryText.includes('stretch')) {
      return video('Kids 5 Minute HIIT Exercise with Warm Up Cool Down and Stretch', 'Educational YouTube Video', 'https://www.youtube.com/watch?v=y20dt7egJ24', 'Direct public video about warming up, exercising, cooling down, and stretching.');
    }
    if (primaryText.includes('balance') || primaryText.includes('coordination')) {
      return video('Balance and Coordination Exercises for Kids', 'Educational YouTube Video', 'https://www.youtube.com/watch?v=oCn2kfuIMzs', 'Direct public video about balance and coordination exercises.');
    }
    if (primaryText.includes('agility')) {
      return video('The Best Agility Ladder Movements for Kids', 'Educational YouTube Video', 'https://www.youtube.com/watch?v=-KYq-iw4fck', 'Direct public video about agility movements for kids.');
    }
    if (primaryText.includes('heart') || primaryText.includes('lungs') || primaryText.includes('muscles') || primaryText.includes('heart rate')) {
      return video('Exercise | Science for Kids', 'Educational YouTube Video', 'https://www.youtube.com/watch?v=R_bvmCVK55g', 'Direct public video explaining how exercise helps the heart, lungs, and muscles.');
    }
    if (primaryText.includes('weekly fitness') || primaryText.includes('fitness reflection') || primaryText.includes('healthy fitness') || primaryText.includes('fitness habits') || primaryText.includes('physical exercise')) {
      return video('Physical Exercise for Children | Benefits of Sports', 'Smile and Learn', 'https://www.youtube.com/watch?v=lSuekPtI_Kc', 'Student-facing video explaining how physical activity helps the body; better fit for weekly fitness reflection than a workout-only clip.');
    }
    if (topicText.includes('backyard fitness') || topicText.includes('fitness circuit') || topicText.includes('circuit')) {
      return video('Kids Workout and Warm Up', 'Prime Coaching Sport', 'https://www.youtube.com/watch?v=ZPLHTRCmKNU', 'Direct public warm-up and movement video for kids.');
    }
    if (topicText.includes('warm-up') || topicText.includes('warm up') || topicText.includes('cool down') || topicText.includes('stretch')) {
      return video('Kids 5 Minute HIIT Exercise with Warm Up Cool Down and Stretch', 'Educational YouTube Video', 'https://www.youtube.com/watch?v=y20dt7egJ24', 'Direct public video about warming up, exercising, cooling down, and stretching.');
    }
    if (topicText.includes('balance') || topicText.includes('coordination')) {
      return video('Balance and Coordination Exercises for Kids', 'Educational YouTube Video', 'https://www.youtube.com/watch?v=oCn2kfuIMzs', 'Direct public video about balance and coordination exercises.');
    }
    if (topicText.includes('agility')) {
      return video('The Best Agility Ladder Movements for Kids', 'Educational YouTube Video', 'https://www.youtube.com/watch?v=-KYq-iw4fck', 'Direct public video about agility movements for kids.');
    }
    if (topicText.includes('heart') || topicText.includes('lungs') || topicText.includes('muscles') || topicText.includes('heart rate')) {
      return video('Exercise | Science for Kids', 'Educational YouTube Video', 'https://www.youtube.com/watch?v=R_bvmCVK55g', 'Direct public video explaining how exercise helps the heart, lungs, and muscles.');
    }
    if (topicText.includes('weekly fitness') || topicText.includes('fitness reflection') || topicText.includes('healthy fitness') || topicText.includes('fitness habits') || topicText.includes('physical exercise')) {
      return video('Physical Exercise for Children | Benefits of Sports', 'Smile and Learn', 'https://www.youtube.com/watch?v=lSuekPtI_Kc', 'Student-facing video explaining how physical activity helps the body; better fit for weekly fitness reflection than a workout-only clip.');
    }
  }

  if (subject.includes('art')) {
    if (primaryText.includes('texture rubbing') || primaryText.includes('texture') || primaryText.includes('pattern in art')) {
      return video('Texture Rubbing with Crayons', 'Educational YouTube Video', 'https://www.youtube.com/watch?v=KHGamhZ64l8', 'Direct public video showing a texture rubbing activity.');
    }
    if (primaryText.includes('abstract shape') || primaryText.includes('abstract shape artwork')) {
      return video('Abstract Art Lesson for Kids', 'Artbx', 'https://www.youtube.com/watch?v=wr_zOdu_cc8', 'Student-facing abstract art lesson for kids; use instead of parent/teacher-facing project instruction.');
    }
    if (primaryText.includes('abstract art') || primaryText.includes('realistic and abstract')) {
      return video('Basic Introduction to Abstract Art', 'Elementary Art Teacher', 'https://www.youtube.com/watch?v=iEV4PZKI9Es', 'Short student-facing introduction to abstract art for elementary students.');
    }
    if (primaryText.includes('drawing from observation') || primaryText.includes('observation drawing') || primaryText.includes('observation')) {
      return video('Drawing From Observation. What is Observational Drawing?', 'Educational YouTube Video', 'https://www.youtube.com/watch?v=si35jr6BXZM', 'Direct public video about observational drawing.');
    }
    if (primaryText.includes('color wheel') || primaryText.includes('colour wheel') || primaryText.includes('color mood') || primaryText.includes('mood')) {
      return video('The Color Wheel! Educational Lesson for Kids and Beginners', 'Educational YouTube Video', 'https://www.youtube.com/watch?v=Mv17dLR1WiI', 'Direct public video about the color wheel.');
    }
    if (primaryText.includes('line') || primaryText.includes('shape') || primaryText.includes('elements of art') || primaryText.includes('pattern project')) {
      return video('Elements of Art: Shape', 'KQED Arts', 'https://www.youtube.com/watch?v=bJzGkZwkHt4', 'Student-facing video explaining shape as an element of art.');
    }
    if (topicText.includes('texture rubbing') || topicText.includes('texture') || topicText.includes('pattern in art')) {
      return video('Texture Rubbing with Crayons', 'Educational YouTube Video', 'https://www.youtube.com/watch?v=KHGamhZ64l8', 'Direct public video showing a texture rubbing activity.');
    }
    if (topicText.includes('abstract shape') || topicText.includes('abstract shape artwork')) {
      return video('Abstract Art Lesson for Kids', 'Artbx', 'https://www.youtube.com/watch?v=wr_zOdu_cc8', 'Student-facing abstract art lesson for kids; use instead of parent/teacher-facing project instruction.');
    }
    if (topicText.includes('abstract art') || topicText.includes('realistic and abstract')) {
      return video('Basic Introduction to Abstract Art', 'Elementary Art Teacher', 'https://www.youtube.com/watch?v=iEV4PZKI9Es', 'Short student-facing introduction to abstract art for elementary students.');
    }
    if (topicText.includes('color wheel') || topicText.includes('colour wheel') || topicText.includes('color mood') || topicText.includes('mood')) {
      return video('The Color Wheel! Educational Lesson for Kids and Beginners', 'Educational YouTube Video', 'https://www.youtube.com/watch?v=Mv17dLR1WiI', 'Direct public video about the color wheel.');
    }
    if (topicText.includes('drawing from observation') || topicText.includes('observation drawing') || topicText.includes('observation')) {
      return video('Drawing From Observation. What is Observational Drawing?', 'Educational YouTube Video', 'https://www.youtube.com/watch?v=si35jr6BXZM', 'Direct public video about observational drawing.');
    }
    if (topicText.includes('line') || topicText.includes('shape') || topicText.includes('elements of art') || topicText.includes('pattern project')) {
      return video('Elements of Art: Shape', 'KQED Arts', 'https://www.youtube.com/watch?v=bJzGkZwkHt4', 'Student-facing video explaining shape as an element of art.');
    }
    if (topicText.includes('portfolio') || topicText.includes('reflection') || topicText.includes('art critique') || topicText.includes('artist statement')) {
      return video('Artist Statement Lesson', 'ASFM Upper Elementary Art', 'https://www.youtube.com/watch?v=r19-kdusltM', 'Upper elementary art video explaining how to write an artist statement for an art project; replaces the blurry artist-statement video.');
    }
  }

  return null;
}

function applyTopicVideo(row) {
  if (!row) return row;
  normalizeAssignmentTitleAndTopic(row);
  const subject = String(row.subject || '').toLowerCase();
  const text = String([row.lesson_title, row.title, row.unit_title, row.passage_title].filter(Boolean).join(' ')).toLowerCase();
  const lessonOnlyText = String([row.lesson_title, row.title, row.passage_title, row.source_title, row.description, row.instructions].filter(Boolean).join(' ')).toLowerCase();
  const currentUrl = String(row.video_url || '').trim();
  const lowerUrl = currentUrl.toLowerCase();
  const courseNameForVideo = String(row.course_name || row.course || row.name || '').toLowerCase();
  const combinedVideoText = String([subject, courseNameForVideo, text, lessonOnlyText].filter(Boolean).join(' ')).toLowerCase();
  const isSpellingVideoRow = combinedVideoText.includes('spelling');
  const setVideo = (title, source, url, notes = '') => {
    row.video_title = title;
    row.video_source = source;
    row.video_url = url;
    row.video_verified = true;
    row.video_status = 'verified';
    row.video_notes = notes || 'Direct topic-matching lesson video.';
    return row;
  };
  const hideVideo = (reason = 'No verified direct topic-matching video is available yet.') => {
    row.video_verified = false;
    row.video_status = 'needs_review';
    row.video_notes = reason;
    return row;
  };

  const electiveTopicVideo = getGradeFiveElectiveTopicVideo(row);
  if (electiveTopicVideo) {
    return setVideo(electiveTopicVideo.title, electiveTopicVideo.source, electiveTopicVideo.url, electiveTopicVideo.notes);
  }

  // If a reading assignment only has one passage, do not label it as Comparing Texts.
  const hasOneReading = !!(row.reading_passage || row.source_text) && !(row.second_reading_passage || row.comparison_passage || row.text_b);
  if (hasOneReading && /comparing texts/i.test(String([row.title, row.lesson_title].filter(Boolean).join(' ')))) {
    row.title = String(row.title || '').replace(/Comparing Texts/gi, 'Reading Comprehension').trim();
    row.lesson_title = String(row.lesson_title || '').replace(/Comparing Texts/gi, 'Reading Comprehension').trim();
    if (/sofia|liam|new student/i.test(String(row.reading_passage || ''))) {
      row.title = String(row.title || 'Reading Comprehension').replace(/Reading Comprehension\s*(Practice|Quiz)?/i, 'Reading Comprehension: The New Student').trim();
      row.lesson_title = 'Character Actions and Theme: The New Student';
    }
  }

  // Grade 5 Spelling videos must be available from the assignment detail itself, not only the unit/lesson header.
  // Some databases store Grade 5 Spelling with a generic Language Arts subject, so check the course name/title too.
  if (isSpellingVideoRow) {
    const spellingAssignmentType = String([row.assignment_type, row.weight_category, row.interactive_type, row.step_type].filter(Boolean).join(' ')).toLowerCase();
    const spellingTitleText = String([row.title, row.assignment_title, row.lesson_title].filter(Boolean).join(' ')).toLowerCase();
    const isDedicatedSpellingStrategyVideo = spellingAssignmentType.includes('spelling_strategy_video') || spellingTitleText.includes('spelling strategy video') || spellingTitleText.includes('strategy video');
    if (!isDedicatedSpellingStrategyVideo) {
      return hideVideo('Video badge hidden: spelling word lists, sentence practice, missing-letter practice, audio spelling, and spelling tests should not show a video unless a dedicated strategy video is attached.');
    }
    const spellingTopic = combinedVideoText;
    const weekMatch = spellingTopic.match(/week\s*(\d+)/i);
    const weekNumber = weekMatch ? parseInt(weekMatch[1], 10) : 1;
    const weekSlot = ((Math.max(1, weekNumber) - 1) % 6) + 1;
    if (spellingTopic.includes('syllable') || spellingTopic.includes('long words') || weekSlot === 2) {
      return setVideo('What are Syllables?', 'Miacademy & MiaPrep', 'https://www.youtube.com/watch?v=Um7ukvphdHY', 'Student-facing Miacademy video explaining syllables for spelling and word study.');
    }
    if (spellingTopic.includes('prefix') || spellingTopic.includes('suffix') || spellingTopic.includes('affix') || weekSlot === 3) {
      return setVideo('Affixes: pre-, dis-, -er, -y - Prefixes and Suffixes for Kids', 'Miacademy & MiaPrep', 'https://www.youtube.com/watch?v=0GiH_jYcox4', 'Student-facing Miacademy lesson about common prefixes and suffixes for spelling and word meaning.');
    }
    if (spellingTopic.includes('pattern') || spellingTopic.includes('plural') || spellingTopic.includes('-s') || spellingTopic.includes('-es') || weekSlot === 4) {
      return setVideo('Ending Patterns Review', 'Educational YouTube Video', 'https://www.youtube.com/watch?v=syUT9Nk_K2M', 'Student-facing spelling strategy video for noticing common spelling and ending patterns.');
    }
    if (spellingTopic.includes('root') || spellingTopic.includes('word family') || weekSlot === 5) {
      return setVideo('Word Parts - How to Use Prefixes, Root Words, and Suffixes to Understand Words', 'Miacademy & MiaPrep', 'https://www.youtube.com/watch?v=z_4-kcbV5K8', 'Student-facing Miacademy lesson for using prefixes, roots, and suffixes to understand and spell words.');
    }
    if (spellingTopic.includes('review') || spellingTopic.includes('tricky') || weekSlot === 6) {
      return setVideo('Latin and Greek Root Words - Language Skills for Kids', 'Miacademy & MiaPrep', 'https://www.youtube.com/watch?v=jWyX8vl6kMs', 'Student-facing Miacademy word-parts review video for spelling and vocabulary growth.');
    }
    return setVideo('What are Syllables?', 'Miacademy & MiaPrep', 'https://www.youtube.com/watch?v=Um7ukvphdHY', 'Student-facing Miacademy spelling/word-study video that helps students hear and break words into syllables.');
  }

  // Math videos should not use links that force Khan Academy signup.
  // Use direct public videos and match by the specific lesson/assignment title first.
  // Do not let a broad unit title like "Fractions" or "Volume and Measurement" make every lesson reuse one video.
  if (subject.includes('math')) {
    const mathTopic = lessonOnlyText || text;
    if (mathTopic.includes('decimal place value') || (mathTopic.includes('place value') && mathTopic.includes('decimal'))) {
      return setVideo('Decimal Place Value', 'Math Antics', 'https://www.youtube.com/watch?v=KG6ILNOiMgM', 'Public YouTube video embedded in the lesson player; no Khan sign-in page.');
    }
    if (mathTopic.includes('adding and subtracting decimals') || mathTopic.includes('add and subtract decimals') || (mathTopic.includes('adding decimals') && mathTopic.includes('subtract')) || (mathTopic.includes('add decimals') && mathTopic.includes('subtract decimals'))) {
      return setVideo('Adding and Subtracting Decimals - How To Use Algorithms', 'Miacademy & MiaPrep', 'https://www.youtube.com/watch?v=7J33RRYnfRU', 'Student-facing Miacademy lesson that teaches both adding decimals and subtracting decimals.');
    }
    if (mathTopic.includes('multiplying decimals') || mathTopic.includes('multiply decimals') || mathTopic.includes('decimal multiplication')) {
      return setVideo('How to Multiply Decimals', 'Miacademy & MiaPrep', 'https://www.youtube.com/watch?v=bzW4wI80uXU', 'Student-facing Miacademy lesson about multiplying decimals.');
    }
    if (mathTopic.includes('comparing decimals')) {
      return setVideo('How to Compare Decimals', 'Math with Mr. J', 'https://www.youtube.com/watch?v=NtLl_hT9esM', 'Public YouTube video embedded in the lesson player; no Khan sign-in page.');
    }
    if (mathTopic.includes('rounding decimals') || mathTopic.includes('round decimals')) {
      return setVideo('How to Round Decimals', 'Math with Mr. J', 'https://www.youtube.com/watch?v=P7ozJW8LSxw', 'Public YouTube video embedded in the lesson player; no Khan sign-in page.');
    }
    if (mathTopic.includes('multiplying fractions') || mathTopic.includes('multiply fractions')) {
      return setVideo('Math Antics - Multiplying Fractions', 'Math Antics', 'https://www.youtube.com/watch?v=qmfXyR7Z6Lk', 'Direct public video about multiplying fractions.');
    }
    if (mathTopic.includes('dividing fractions') || mathTopic.includes('divide fractions')) {
      return setVideo('Math Antics - Dividing Fractions', 'Math Antics', 'https://www.youtube.com/watch?v=4lkq3DgvmJo', 'Direct public video about dividing fractions.');
    }
    if (mathTopic.includes('mixed numbers') || mathTopic.includes('improper fractions')) {
      return setVideo('Math Antics - Mixed Numbers', 'Math Antics', 'https://www.youtube.com/watch?v=qk2oP6FZ6HA', 'Direct public video about mixed numbers and improper fractions.');
    }
    if (mathTopic.includes('customary') || mathTopic.includes('customary measurement')) {
      return setVideo('Customary Measurement Conversions', 'Math with Mr. J', 'https://www.youtube.com/watch?v=-lNebjbHwtc', 'Direct public video about customary measurement conversions.');
    }
    if (mathTopic.includes('metric') || mathTopic.includes('metric system')) {
      return setVideo('Math Antics - Intro to the Metric System', 'Math Antics', 'https://www.youtube.com/watch?v=BTbTQgq6Mt0', 'Direct public video introducing metric measurement.');
    }
    if (mathTopic.includes('convert') || mathTopic.includes('conversion') || mathTopic.includes('measurement conversion')) {
      return setVideo('5th Grade Math Unit Conversions', 'Aaron Daffern', 'https://www.youtube.com/watch?v=fdyFeEc8Wss', 'Grade 5 video about converting customary and metric units using basic operations.');
    }
    if (mathTopic.includes('volume of rectangular prism') || mathTopic.includes('volume of rectangular prisms') || mathTopic.includes('rectangular prism')) {
      return setVideo('Working With Volume - Volume Formulas for Rectangular Prisms', 'Miacademy & MiaPrep', 'https://www.youtube.com/watch?v=C3FFjSvV1IM', 'Student-facing fifth-grade Miacademy lesson focused on volume formulas for rectangular prisms.');
    }
    if (mathTopic.includes('volume')) {
      return setVideo('Introduction to Volume: Unit Cubes and Rectangular Prisms', 'Miacademy & MiaPrep', 'https://www.youtube.com/watch?v=kPNDJ6m9UoU', 'Student-facing fifth-grade Miacademy lesson introducing volume with unit cubes and rectangular prisms.');
    }
    if (mathTopic.includes('plotting points') || mathTopic.includes('plot points')) {
      return setVideo('How to Graph Ordered Pairs', 'Miacademy & MiaPrep', 'https://www.youtube.com/watch?v=1YXjTXsOf2o', 'Student-facing Miacademy lesson about graphing ordered pairs in the first quadrant.');
    }
    if (mathTopic.includes('ordered pairs') || mathTopic.includes('ordered pair')) {
      return setVideo('Introduction to the Coordinate Plane - Writing Ordered Pairs', 'Miacademy & MiaPrep', 'https://www.youtube.com/watch?v=s9veiG_ZS8o', 'Student-facing fifth-grade Miacademy lesson about writing ordered pairs on the coordinate plane.');
    }
    if (mathTopic.includes('graphing patterns') || mathTopic.includes('number patterns') || mathTopic.includes('relationship patterns')) {
      return setVideo('How to Graph Ordered Pairs', 'Miacademy & MiaPrep', 'https://www.youtube.com/watch?v=1YXjTXsOf2o', 'Student-facing Miacademy lesson about graphing points and ordered pairs; use until a more exact graphing-patterns video is approved.');
    }
    if (mathTopic.includes('coordinate plane') || mathTopic.includes('coordinate planes') || mathTopic.includes('graphing on the coordinate')) {
      return setVideo('Introduction to the Coordinate Plane - Writing Ordered Pairs', 'Miacademy & MiaPrep', 'https://www.youtube.com/watch?v=s9veiG_ZS8o', 'Student-facing fifth-grade Miacademy lesson introducing coordinate planes and ordered pairs.');
    }
    if (text.includes('fractions') || text.includes('volume and measurement') || text.includes('coordinate planes')) {
      return hideVideo('This math assignment is in a unit that needs a lesson-specific video. Do not reuse one broad unit video unless the lesson title matches the video topic.');
    }
  }

  if (subject.includes('science')) {
    if (text.includes('solar system') || text.includes('planets')) {
      return setVideo('The Solar System Planets | Educational Video for Kids', 'Educational YouTube Video', 'https://www.youtube.com/watch?v=ASQkz4XaphU', 'Direct public video about the planets in the solar system; replaces the prior embed that produced a redirect error.');
    }
    if (text.includes('stars and galaxies') || text.includes('star') || text.includes('galax')) {
      return setVideo('The Milky Way for Children, Galaxies and Space', 'FreeSchool', 'https://www.youtube.com/watch?v=RubnGwhcT6E', 'Student-facing astronomy lesson focused on the Milky Way and galaxies.');
    }
    if (text.includes('earth moon system') || text.includes('earth-moon') || text.includes('earth moon') || text.includes('sun earth moon') || text.includes('earth, sun and moon')) {
      return setVideo('5th Grade - Science - Earth, Sun, and Moon - Topic Overview', 'Miacademy & MiaPrep', 'https://www.youtube.com/watch?v=shN68vNkZHk', 'Student-facing Grade 5 topic overview about the Earth, Sun, and Moon relationship.');
    }
    if (text.includes('water cycle')) {
      return setVideo('The Water Cycle for Kids', 'Learn Bright', 'https://www.youtube.com/watch?v=TD3XSIE4ymo', 'Direct public video about evaporation, condensation, precipitation, and collection.');
    }
    if (text.includes('ocean currents') || text.includes('ocean current')) {
      return setVideo('How do ocean currents work?', 'TED-Ed', 'https://www.youtube.com/watch?v=p4pWafuvdrY', 'Short student-facing explanation of ocean currents; keep unless a stronger Grade 5 direct lesson is approved.');
    }
    if (text.includes('atmosphere')) {
      return setVideo('The Atmosphere for Kids', 'Smile and Learn', 'https://www.youtube.com/watch?v=G4ZIa3qkFkI', 'Direct public video about the atmosphere and Earth layers.');
    }
    if (text.includes('chemical vs physical') || text.includes('physical and chemical') || text.includes('chemical change') || text.includes('physical change')) {
      return setVideo('Physical and Chemical Changes for Kids', 'Homeschool Pop', 'https://www.youtube.com/watch?v=BOr76Zx48QM', 'Direct public video comparing physical changes and chemical changes.');
    }
    if (text.includes('properties of matter') || text.includes('physical properties')) {
      return setVideo("What's My Property?", 'Crash Course Kids', 'https://www.youtube.com/watch?v=nlSemv2fLN8', 'Direct public video about physical properties of matter.');
    }
    if (text.includes('matter')) {
      return setVideo("What's Matter?", 'Crash Course Kids', 'https://www.youtube.com/watch?v=ELchwUIlWa8', 'Direct public video introducing matter.');
    }
  }

  if (subject.includes('social')) {
    // Use lesson-specific words here. Do not let broad unit titles make every assignment reuse the same video.
    const socialTopic = lessonOnlyText;
    if (socialTopic.includes('bill of rights')) {
      return setVideo('Bill of Rights for Kids', 'Learn Bright', 'https://www.youtube.com/watch?v=skWNC3a8NkM', 'Direct public video about the first 10 amendments.');
    }
    if (socialTopic.includes('constitution')) {
      return setVideo('Constitution for Kids | What It Is and Why It Matters', 'Learn Bright', 'https://www.youtube.com/watch?v=jsTB7gSfDPI', 'Direct public video about the United States Constitution.');
    }
    if (socialTopic.includes('westward expansion') || socialTopic.includes('manifest destiny')) {
      return setVideo('Westward Expansion Explained For Kids', 'Learn Bright', 'https://www.youtube.com/watch?v=_9FLJEvDniU', 'Direct public video about westward expansion.');
    }
    if (socialTopic.includes('declaration of independence')) {
      return setVideo('Declaration of Independence for Kids', 'Learn Bright', 'https://www.youtube.com/watch?v=TiduzZTWFOU', 'Direct public video about the Declaration of Independence.');
    }
    if (socialTopic.includes('key battles') || socialTopic.includes('major battles') || socialTopic.includes('battles of the american revolution')) {
      return setVideo('Battles of the American Revolution', 'Early US History for Kids', 'https://www.youtube.com/watch?v=V9SQ4HxnWnM', 'Direct public video focused on Revolutionary War battles.');
    }
    if (socialTopic.includes('colonial life') || socialTopic.includes('colonial times')) {
      return setVideo('Colonial Times for Kids | Learn about colonial America', 'Learn Bright', 'https://www.youtube.com/watch?v=4f3LsMKzxdg', 'Direct public video about daily life in Colonial America.');
    }
    if (socialTopic.includes('road to revolution') || socialTopic.includes('events leading to the american revolution') || socialTopic.includes('causes of the american revolution') || socialTopic.includes('causes of the revolution')) {
      return setVideo('The American Revolutionary War for Kids', 'FreeSchool', 'https://www.youtube.com/watch?v=BWtW0gmh3kg', 'Direct public video for the Road to Revolution lesson. This should not be reused for every American Revolution unit assignment.');
    }
    if (socialTopic.includes('revolutionary war') && !socialTopic.includes('road to revolution')) {
      return setVideo('The American Revolutionary War for Kids', 'FreeSchool', 'https://www.youtube.com/watch?v=BWtW0gmh3kg', 'Direct public video for Revolutionary War lesson content only.');
    }
    if (socialTopic.includes('13 colonies') || socialTopic.includes('thirteen colonies')) {
      return setVideo('The 13 Original Colonies for Kids', 'Learn Bright', 'https://www.youtube.com/watch?v=UUmi3iNMawo', 'Direct public video introducing the 13 Colonies.');
    }
    if (text.includes('american revolution')) {
      return hideVideo('This assignment is in the American Revolution unit, but no verified direct topic-matching video has been assigned for this specific lesson yet.');
    }
  }

  if (subject.includes('language') || subject.includes('ela') || subject.includes('reading') || subject.includes('english')) {
    const elaTopic = lessonOnlyText || text;
    // Exact lesson topics must win before the generic reading-comprehension fallback.
    // This prevents Point of View, Comma Rules, and Academic Vocabulary from showing a broad reading-comprehension video.
    if (elaTopic.includes('figurative language')) {
      return setVideo('Figurative Language for Kids | Similes, Metaphors & Idioms', 'Homeschool Pop', 'https://www.youtube.com/watch?v=35PzoZWJpIM', 'Student-facing elementary lesson about similes, metaphors, and idioms.');
    }
    if (elaTopic.includes('point of view')) {
      return setVideo('Point of View for Kids: First and Third Person', 'Educational YouTube Video', 'https://www.youtube.com/watch?v=R9-ONtB1GBY', 'Student-facing point of view lesson for upper elementary reading. Do not replace point-of-view lessons with a generic reading-comprehension video.');
    }
    if (elaTopic.includes('persuasive writing')) {
      return setVideo('Persuasive Writing - Using Techniques To Convince Others', 'Miacademy & MiaPrep', 'https://www.youtube.com/watch?v=evIlRRK_rOE', 'Student-facing Grade 5 persuasive writing lesson.');
    }
    if (elaTopic.includes('narrative writing') || elaTopic.includes('narrative essays')) {
      return setVideo('Writing a Personal Narrative - Brainstorming a Story for Kids', 'Teaching Without Frills', 'https://www.youtube.com/watch?v=AxInWagSaA0', 'Direct public video introducing personal narrative writing for kids.');
    }
    if (elaTopic.includes('research papers') || elaTopic.includes('research paper') || elaTopic.includes('research writing')) {
      return setVideo('Intro to Research Writing', 'Educational YouTube Video', 'https://www.youtube.com/watch?v=1N0WB4gf3co', 'Direct topic video introducing research writing. Student practice/questions should still connect back to the assigned passage.');
    }
    if (elaTopic.includes('greek and latin roots') || elaTopic.includes('latin and greek roots') || elaTopic.includes('greek roots') || elaTopic.includes('latin roots') || elaTopic.includes('root words')) {
      return setVideo('Latin and Greek Root Words - Language Skills for Kids', 'Miacademy & MiaPrep', 'https://www.youtube.com/watch?v=jWyX8vl6kMs', 'Direct student-facing word-parts video for Greek and Latin roots.');
    }
    if (elaTopic.includes('academic vocabulary')) {
      return setVideo('5th Grade - Reading - Academic Words - Topic Overview', 'Education Galaxy', 'https://www.youtube.com/watch?v=fR5DGIpZ5H4', 'Student-facing topic overview for academic vocabulary/topic words; replaces the source label that showed a sign-in/bot screen.');
    }
    if (elaTopic.includes('context clues')) {
      return setVideo('Context Clues - How to Figure Out Unfamiliar Words in a Story', 'Miacademy & MiaPrep', 'https://www.youtube.com/watch?v=wDJZOBwtGMY', 'Student-facing fifth-grade Miacademy lesson about using context clues in a story.');
    }
    if (elaTopic.includes('preposition')) {
      return setVideo('Prepositions', 'Miacademy & MiaPrep', 'https://www.youtube.com/watch?v=QO-1vz7Tp-4', 'Student-facing lesson explaining prepositions and how they show relationship, location, direction, and time.');
    }
    if (elaTopic.includes('prefix') || elaTopic.includes('suffix') || elaTopic.includes('affix')) {
      return setVideo('Prefixes and Suffixes for Kids', 'Miacademy & MiaPrep', 'https://www.youtube.com/watch?v=0GiH_jYcox4', 'Direct topic video about prefixes and suffixes.');
    }
    if (elaTopic.includes('dictionary') || elaTopic.includes('reference tools')) {
      return setVideo('Dictionary Skills: Finding a Word', 'Miacademy & MiaPrep', 'https://www.youtube.com/watch?v=ss9NIh35RQY', 'Direct topic video about dictionary skills.');
    }
    if (elaTopic.includes('synonym') || elaTopic.includes('antonym')) {
      return setVideo('Synonyms and Antonyms', 'Educational YouTube Video', 'https://www.youtube.com/watch?v=PDI2xlOBcM4', 'Direct topic video about synonyms and antonyms.');
    }
    if (elaTopic.includes('comma')) {
      return setVideo('Grammar Vids for Kids: How To Use Commas', 'Grammar Vids for Kids', 'https://www.youtube.com/watch?v=dDmWWuqO7DY', 'Student-facing comma rules video. Do not reuse the reading-comprehension video for comma rules.');
    }
    if (elaTopic.includes('punctuation') || elaTopic.includes('capitalization')) {
      return setVideo('Capitalization and Punctuation', 'Educational YouTube Video', 'https://www.youtube.com/watch?v=7P98d_uLq6Y', 'Direct topic video about capitalization and punctuation.');
    }
    if (elaTopic.includes('reading comprehension') || elaTopic.includes('comparing texts') || elaTopic.includes('character actions and theme') || elaTopic.includes('the new student')) {
      return setVideo('Reading Comprehension for Kids | Practice Reading Comprehension Skills', 'Educational YouTube Video', 'https://www.youtube.com/watch?v=NqpbTN3diUc', 'Student-facing reading comprehension strategy video; keep this video available even when one student has already completed the assignment.');
    }
    if (elaTopic.includes('fragment') || elaTopic.includes('complete sentence') || elaTopic.includes('incomplete sentence')) {
      return setVideo('Sentence Fragments for Kids', 'Homeschool Pop', 'https://www.youtube.com/watch?v=ds0JHxYvkvQ', 'Direct topic video about sentence fragments and complete sentences.');
    }
    if (elaTopic.includes('subject-verb agreement') || elaTopic.includes('subject verb agreement')) {
      return setVideo('Subject-Verb Agreement', 'Periwinkle', 'https://www.youtube.com/watch?v=tySLFboxzyM', 'Direct Grade 5 grammar video about subject-verb agreement.');
    }
    if (elaTopic.includes('verb tense') || elaTopic.includes('tenses')) {
      return setVideo('Verb Tenses', 'Miacademy & MiaPrep', 'https://www.youtube.com/watch?v=fIL41ftZO08', 'Student-facing lesson about past, present, and future verb tenses.');
    }
    if (elaTopic.includes('active voice') || elaTopic.includes('passive voice')) {
      return setVideo('Active and Passive Voice', 'Periwinkle', 'https://www.youtube.com/watch?v=OkSv_HdfyLg', 'Direct Grade 5 grammar video about active and passive voice.');
    }
    if (elaTopic.includes('phrase') || elaTopic.includes('phrases')) {
      return setVideo('Phrases | English Grammar Grade 5', 'Periwinkle', 'https://www.youtube.com/watch?v=OoOLcwEYrw4', 'Direct Grade 5 grammar video about phrases.');
    }
    if (elaTopic.includes('parts of speech') || elaTopic.includes('grammar') || elaTopic.includes('noun') || elaTopic.includes('verb') || elaTopic.includes('adjective') || elaTopic.includes('adverb') || elaTopic.includes('pronoun')) {
      return setVideo('Parts of Speech | English Grammar Grade 5', 'Periwinkle', 'https://www.youtube.com/watch?v=40Gv90LigKY', 'Direct Grade 5 grammar video about parts of speech.');
    }
    if (text.includes('vocabulary')) {
      return hideVideo('This vocabulary assignment needs a direct topic-matching video. Do not reuse the Context Clues video for all vocabulary lessons.');
    }
  }

  if (subject.includes('spelling')) {
    const spellingAssignmentType = String([row.assignment_type, row.weight_category, row.interactive_type, row.step_type].filter(Boolean).join(' ')).toLowerCase();
    const spellingTitleText = String([row.title, row.assignment_title, row.lesson_title].filter(Boolean).join(' ')).toLowerCase();
    const isDedicatedSpellingStrategyVideo = spellingAssignmentType.includes('spelling_strategy_video') || spellingTitleText.includes('spelling strategy video') || spellingTitleText.includes('strategy video');
    if (!isDedicatedSpellingStrategyVideo) {
      return hideVideo('Video badge hidden: spelling word lists, sentence practice, missing-letter practice, audio spelling, and spelling tests should not show a video unless a dedicated strategy video is attached.');
    }
    const spellingTopic = lessonOnlyText || text;
    const weekMatch = spellingTopic.match(/week\s*(\d+)/i);
    const weekNumber = weekMatch ? parseInt(weekMatch[1], 10) : 1;
    const weekSlot = ((Math.max(1, weekNumber) - 1) % 6) + 1;
    if (spellingTopic.includes('syllable') || spellingTopic.includes('long words') || weekSlot === 2) {
      return setVideo('What are Syllables?', 'Miacademy & MiaPrep', 'https://www.youtube.com/watch?v=Um7ukvphdHY', 'Student-facing Miacademy video explaining syllables for spelling and word study.');
    }
    if (spellingTopic.includes('prefix') || spellingTopic.includes('suffix') || spellingTopic.includes('affix') || weekSlot === 3) {
      return setVideo('Affixes: pre-, dis-, -er, -y - Prefixes and Suffixes for Kids', 'Miacademy & MiaPrep', 'https://www.youtube.com/watch?v=0GiH_jYcox4', 'Student-facing Miacademy lesson about common prefixes and suffixes for spelling and word meaning.');
    }
    if (spellingTopic.includes('pattern') || spellingTopic.includes('plural') || spellingTopic.includes('-s') || spellingTopic.includes('-es') || weekSlot === 4) {
      return setVideo('Ending Patterns Review', 'Educational YouTube Video', 'https://www.youtube.com/watch?v=syUT9Nk_K2M', 'Direct student-facing spelling strategy video about noticing ending spelling patterns.');
    }
    if (spellingTopic.includes('root') || spellingTopic.includes('word family') || weekSlot === 5) {
      return setVideo('Word Parts - How to Use Prefixes, Root Words, and Suffixes to Understand Words', 'Miacademy & MiaPrep', 'https://www.youtube.com/watch?v=z_4-kcbV5K8', 'Student-facing Miacademy lesson for using prefixes, roots, and suffixes to understand and spell words.');
    }
    if (spellingTopic.includes('review') || spellingTopic.includes('tricky') || weekSlot === 6) {
      return setVideo('Latin and Greek Root Words - Language Skills for Kids', 'Miacademy & MiaPrep', 'https://www.youtube.com/watch?v=jWyX8vl6kMs', 'Student-facing Miacademy word-parts review video for spelling and vocabulary growth.');
    }
    return setVideo('What are Syllables?', 'Miacademy & MiaPrep', 'https://www.youtube.com/watch?v=Um7ukvphdHY', 'Student-facing Miacademy spelling/word-study video that helps students hear and break words into syllables.');
  }

  // For ELA, do not show broad Khan Academy Reading & Language Arts pages.
  // Until a direct verified topic video is stored, hide the video step instead of sending the student to a broad map.
  if (subject.includes('language') || subject.includes('ela') || subject.includes('reading') || subject.includes('english')) {
    if (/sofia|liam|new student/i.test(String(row.reading_passage || row.source_text || ''))) {
      return hideVideo('Hidden because the previous Reading & Language Arts link was too broad. Add a verified direct video about making inferences, character actions, or theme before showing this step.');
    }
  }

  // Aggressively hide broad/course-map links. A direct Khan Academy video usually includes /v/.
  const isKhanBroad = lowerUrl.includes('khanacademy.org') && !lowerUrl.includes('/v/');
  const isBroad = isKhanBroad || lowerUrl.includes('playlist') || lowerUrl.includes('course') || lowerUrl.endsWith('/ela') || lowerUrl.endsWith('/math') || /\/ela\/?$/.test(lowerUrl) || /\/math\/?$/.test(lowerUrl);
  if (isBroad) return hideVideo('Hidden because this is a broad course page, not a direct topic-matching lesson video.');

  row.video_status = row.video_verified ? 'verified' : (currentUrl ? 'needs_review' : 'no_video');
  return row;
}

function passageKind(row) {
  const t = String(row.passage_type || row.source_type || '').toLowerCase();
  const text = String((row.reading_passage || row.source_text || '')).toLowerCase();
  if (t.includes('inform') || t.includes('article') || t.includes('expository')) return 'informational';
  if (t.includes('story') || t.includes('literature') || t.includes('narrative') || /"[^"\n]+"/.test(text)) return 'story';
  return row.reading_passage || row.source_text ? 'informational' : 'general';
}


function isGradeFiveLanguageArtsRow(row) {
  const gradeNumber = parseInt(row?.grade_level ?? row?.grade ?? '', 10);
  const subject = String(row?.subject || row?.course_name || '').toLowerCase();
  return gradeNumber === 5 && (subject.includes('language') || subject.includes('ela') || subject.includes('reading') || subject.includes('english'));
}

function languageArtsSkillKey(row) {
  const text = String([
    row?.title, row?.assignment_title, row?.lesson_title, row?.unit_title,
    row?.description, row?.instructions, row?.skill_name, row?.passage_title
  ].filter(Boolean).join(' ')).toLowerCase();
  if (text.includes('persuasive writing')) return 'persuasive_writing';
  if (text.includes('narrative essays') || text.includes('narrative writing')) return 'narrative_writing';
  if (text.includes('research papers') || text.includes('research paper') || text.includes('research writing')) return 'research_papers';
  if (text.includes('point of view')) return 'point_of_view';
  if (text.includes('figurative language')) return 'figurative_language';
  return '';
}

function applyGradeFiveLanguageArtsStoryContent(row) {
  if (!row || !isGradeFiveLanguageArtsRow(row)) return row;
  const skill = languageArtsSkillKey(row);
  if (!skill) return row;
  const setTitle = (value) => {
    if (row.title !== undefined) row.title = value;
    if (row.assignment_title !== undefined) row.assignment_title = value;
  };

  if (skill === 'persuasive_writing') {
    if (/quiz/i.test(String(row.title || row.assignment_title || ''))) setTitle('Persuasive Writing Quiz');
    else if (/practice/i.test(String(row.title || row.assignment_title || ''))) setTitle('Persuasive Writing Practice');
    row.lesson_title = 'Persuasive Writing: A Letter for the School Garden';
    row.passage_title = 'A Letter for the School Garden';
    row.passage_type = 'persuasive_letter';
    row.passage_source = 'Original persuasive passage for JNM Home Education Program';
    row.reading_passage = `Dear Ms. Chen and Principal Rivera,

I believe our school should keep the garden project going next year. The empty patch behind the school used to be full of weeds, but Maya and her classmates turned it into a place where students could learn, work together, and feel proud of what they created.

First, the garden helps students learn responsibility. Maya and her classmates had to pull weeds, rake the soil, plant seeds, and water the garden every day. Those jobs showed that good results take patience and steady effort.

Second, the garden gives students a hands-on way to learn science. Students can watch seeds sprout, measure plant growth, observe insects, and understand how sunlight, soil, and water help plants grow.

Finally, the garden brings the school community together. Maya did not finish the work alone. Her friends helped her, Ms. Chen encouraged her, and everyone got to see the colorful tomatoes, sunflowers, and lettuce at the end of spring.

Please allow our class to continue the garden next year. With a simple schedule and help from students, the garden can keep teaching us responsibility, teamwork, and science in a way we can see and remember.

Sincerely,
Maya`;
    row.instructional_content = 'Persuasive writing is used to convince a reader to agree with an idea or take action. A strong persuasive response includes a clear claim, reasons, evidence, and a closing statement. In the letter, Maya makes a claim about keeping the school garden, supports it with reasons, and uses details from the garden project to persuade Ms. Chen and Principal Rivera.';
    row.vocabulary = [
      { term:'claim', definition:'The main opinion or position the writer wants the reader to believe.' },
      { term:'reason', definition:'An explanation that tells why the claim makes sense.' },
      { term:'evidence', definition:'A fact, example, or detail that supports a reason.' },
      { term:'persuade', definition:'To try to convince someone to agree or take action.' },
      { term:'call to action', definition:'A sentence that asks the reader to do something.' }
    ];
    row.examples = [
      { title:'Claim from Maya’s Letter', content:'“I believe our school should keep the garden project going next year.”', solution:'This is a claim because Maya clearly states what she wants the reader to believe.' },
      { title:'Reason and Evidence', content:'Maya says the garden teaches responsibility, then gives evidence about pulling weeds, planting seeds, and watering every day.', solution:'The reason is supported by specific details from the passage.' },
      { title:'Call to Action', content:'“Please allow our class to continue the garden next year.”', solution:'Maya asks the reader to take action.' }
    ];
    row.guided_practice = [
      { question:'Maya wants the school garden to continue. Write a persuasive claim Maya could use to convince Ms. Chen and Principal Rivera that the garden is a good idea.', hint:'Use Maya’s goal and the school garden. A strong claim clearly states what should happen.', answer:'Our school should keep the garden project next year because it helps students learn responsibility, teamwork, and science.' },
      { question:'Write one body paragraph from Maya’s point of view. Use two details from the letter, such as students watering the plants, learning science, or working together.', hint:'Start with one reason, then add two details from the passage as evidence.', answer:'The garden should continue because it helps students learn responsibility. In the letter, Maya explains that students pulled weeds, planted seeds, and watered the garden every day. These details show that the garden teaches students to keep working toward a goal.' }
    ];
    return row;
  }

  if (skill === 'research_papers') {
    if (/quiz/i.test(String(row.title || row.assignment_title || ''))) setTitle('Research Papers Quiz');
    else if (/test/i.test(String(row.title || row.assignment_title || ''))) setTitle('Writing Unit Test');
    else if (/practice/i.test(String(row.title || row.assignment_title || ''))) setTitle('Research Papers Practice');
    row.lesson_title = 'Research Papers: Amazing Butterflies';
    row.passage_title = 'Amazing Butterflies';
    row.passage_type = 'informational_article';
    row.passage_source = 'Original informational passage for JNM Home Education Program';
    row.reading_passage = `Butterflies are some of the most beautiful insects in the world. They can be found on every continent except Antarctica. There are more than 17,000 different species of butterflies.

A butterfly goes through four stages in its life. This is called metamorphosis. First, a butterfly starts as a tiny egg. The mother butterfly lays her eggs on a leaf. Second, a caterpillar hatches from the egg. The caterpillar eats leaves and grows bigger every day. Third, the caterpillar forms a chrysalis around its body. Inside the chrysalis, an amazing change happens. Fourth, a butterfly emerges with beautiful wings. It spreads its wings and flies away.

Butterflies are important to our planet. When a butterfly lands on a flower to drink nectar, pollen sticks to its body. When it flies to another flower, the pollen rubs off. This is called pollination, and it helps plants grow new seeds.

Butterflies have some interesting features. They taste with their feet! They also have thousands of tiny scales on their wings that create colorful patterns. Some butterflies migrate thousands of miles. The monarch butterfly travels from Canada to Mexico every fall. That is a journey of over 3,000 miles.

Unfortunately, many butterfly species are in danger. When forests are cut down or fields are sprayed with chemicals, butterflies lose their homes and food. We can help by planting flowers, avoiding pesticides, and protecting natural areas.`;
    row.instructional_content = 'A research paper explains a topic using facts from reliable sources. Writers make a clear claim or main idea, support it with evidence, and explain why the evidence matters. In “Amazing Butterflies,” the facts about metamorphosis, pollination, migration, and habitat loss can be used to write a research response about why butterflies are important and why people should protect them.';
    row.vocabulary = [
      { term:'research paper', definition:'A piece of writing that explains a topic using facts and evidence.' },
      { term:'claim', definition:'The main idea or point the writer wants to explain or prove.' },
      { term:'evidence', definition:'A fact, detail, or example from the text that supports the claim.' },
      { term:'explanation', definition:'The part of writing that tells how the evidence supports the claim.' },
      { term:'pollination', definition:'The movement of pollen from one flower to another, which helps plants make seeds.' },
      { term:'metamorphosis', definition:'The four-stage change from egg to caterpillar to chrysalis to butterfly.' }
    ];
    row.examples = [
      { title:'Claim from the Butterfly Passage', content:'Butterflies are important to plants and the environment.', solution:'This claim can be supported with the passage detail about butterflies moving pollen from flower to flower.' },
      { title:'Evidence from the Text', content:'The passage says pollen sticks to a butterfly’s body and rubs off on another flower.', solution:'This evidence supports the idea that butterflies help plants grow new seeds.' },
      { title:'Explanation', content:'Because pollination helps plants make seeds, protecting butterflies can also help protect plants.', solution:'The explanation connects the evidence back to the claim.' }
    ];
    row.guided_practice = [
      { question:'Using “Amazing Butterflies,” write a research claim about why butterflies are important to plants. Include the word pollination or explain how butterflies help flowers.', hint:'Use the passage detail about pollen sticking to a butterfly and rubbing off on another flower.', answer:'Butterflies are important to plants because they help with pollination, which allows plants to grow new seeds.' },
      { question:'Write one C-E-E response about why people should protect butterfly habitats. Use one fact from “Amazing Butterflies,” such as forests being cut down, chemicals harming fields, or monarch butterflies migrating over 3,000 miles.', hint:'C-E-E means claim, evidence, and explanation. Start with your point, add one fact from the passage, then explain why it matters.', answer:'People should protect butterfly habitats because many butterflies are in danger. The passage says butterflies lose homes and food when forests are cut down or fields are sprayed with chemicals. This matters because protecting natural areas helps butterflies survive.' }
    ];
    return row;
  }

  if (skill === 'narrative_writing') {
    if (/quiz/i.test(String(row.title || row.assignment_title || ''))) setTitle('Narrative Essays Quiz');
    else if (/practice/i.test(String(row.title || row.assignment_title || ''))) setTitle('Narrative Essays Practice');
    row.lesson_title = 'Narrative Essays: The Garden Project';
    row.passage_title = 'The Garden Project';
    row.passage_type = 'narrative_story';
    row.passage_source = 'Original narrative passage for JNM Home Education Program';
    row.reading_passage = `Maya looked at the empty patch of dirt behind her school. It was full of weeds and old leaves, but Maya saw something different. She saw rows of bright tomatoes, tall sunflowers, and leafy lettuce.

“We could grow a garden here,” Maya told her teacher, Ms. Chen.

Ms. Chen smiled. “That is a wonderful idea, Maya. But a garden takes a lot of work. Are you ready for that?”

Maya nodded. She made a plan. First, she asked her classmates to help. Together, they pulled the weeds and raked the soil until it was smooth and dark. Next, they dug small holes in neat rows. Then, they carefully placed seeds into each hole and covered them with dirt.

Every day after school, Maya and her friends watered the garden. They watched for tiny green sprouts pushing through the soil. After two weeks, small plants began to appear. Maya felt proud.

By the end of spring, the garden was bursting with color. Red tomatoes hung from thick green stems. Yellow sunflowers stood tall against the fence. Maya picked a tomato and took a bite. It was the best tomato she had ever tasted.

“We did it,” she said to her friends. “We turned dirt into something amazing.”

Ms. Chen put her hand on Maya’s shoulder. “You taught everyone that hard work and patience can turn a dream into something real.”`;
    row.instructional_content = 'A narrative essay tells a story. It usually includes characters, a setting, a problem or goal, a sequence of events, and a resolution. In “The Garden Project,” Maya has a goal, works through several steps, and learns that patience and hard work can turn an idea into something real.';
    row.vocabulary = [
      { term:'narrative', definition:'Writing that tells a story with characters and events.' },
      { term:'setting', definition:'Where and when a story takes place.' },
      { term:'sequence', definition:'The order in which events happen.' },
      { term:'resolution', definition:'How the problem or goal in a story is completed or solved.' },
      { term:'theme', definition:'A message or lesson the reader can learn from a story.' }
    ];
    row.examples = [
      { title:'Setting', content:'The story begins behind Maya’s school near an empty patch of dirt.', solution:'This tells where the story takes place.' },
      { title:'Sequence of Events', content:'Maya makes a plan, asks classmates to help, plants seeds, waters the garden, and watches the plants grow.', solution:'These events show the order of Maya’s work.' },
      { title:'Resolution', content:'By the end of spring, the garden is full of tomatoes, sunflowers, and lettuce.', solution:'Maya’s goal is completed.' }
    ];
    row.guided_practice = [
      { question:'Write the beginning of a narrative paragraph about Maya seeing the empty patch of dirt behind the school. Include the setting and what Maya imagines.', hint:'Use details from the story such as weeds, old leaves, tomatoes, sunflowers, or lettuce.', answer:'Maya stood behind the school and looked at the empty patch of dirt. Even though it was covered with weeds and old leaves, she imagined rows of tomatoes, sunflowers, and lettuce growing there.' },
      { question:'Write a short continuation of “The Garden Project” that shows what Maya might do next after the garden succeeds.', hint:'Keep Maya, Ms. Chen, the classmates, or the garden connected to your continuation.', answer:'After the garden succeeded, Maya asked Ms. Chen if the class could save seeds for the next school year. She wanted the next group of students to learn how patience and teamwork could help something grow.' }
    ];
    return row;
  }

  return row;
}

function gradeFiveLanguageArtsQuestions(row) {
  if (!row || !isGradeFiveLanguageArtsRow(row)) return null;
  const skill = languageArtsSkillKey(row);
  const original = Array.isArray(row.questions) ? row.questions : [];
  const ids = [1,2,3,4,5].map((n, i) => original[i]?.id || n);
  const isQuiz = isQuizOrTestAssignment(row);

  if (skill === 'persuasive_writing') {
    return [
      q(ids[0], 'multiple_choice', 'What claim does Maya make in “A Letter for the School Garden”?', rotateOptions('The school should keep the garden project going next year.', ['The school should remove every plant from the garden.', 'Students should stop working together after school.', 'Ms. Chen should cancel science lessons.'], 1), 'The school should keep the garden project going next year.'),
      q(ids[1], 'multiple_choice', 'Which detail from Maya’s letter best supports the reason that the garden teaches responsibility?', rotateOptions('Students pulled weeds, planted seeds, and watered the garden every day.', ['The letter begins with “Dear Ms. Chen and Principal Rivera.”', 'Maya signs her name at the end of the letter.', 'The school has a patch of dirt behind it.'], 2), 'Students pulled weeds, planted seeds, and watered the garden every day.'),
      q(ids[2], 'multiple_choice', 'How does Maya use science as a reason to persuade Ms. Chen and Principal Rivera?', rotateOptions('She explains that students can observe seeds sprout, measure plant growth, and learn how sunlight, soil, and water help plants grow.', ['She says students should never study plants.', 'She explains that gardens do not need water.', 'She says science is not connected to the garden.'], 3), 'She explains that students can observe seeds sprout, measure plant growth, and learn how sunlight, soil, and water help plants grow.'),
      q(ids[3], 'multiple_choice', 'Which sentence from the letter is Maya’s call to action?', rotateOptions('Please allow our class to continue the garden next year.', ['The empty patch behind the school used to be full of weeds.', 'Maya and her classmates turned it into a place where students could learn.', 'Her friends helped her, Ms. Chen encouraged her.'], 4), 'Please allow our class to continue the garden next year.'),
      q(ids[4], 'written_response', isQuiz ? 'Write a persuasive paragraph from Maya’s point of view asking Principal Rivera to keep the school garden. Use at least two details from “A Letter for the School Garden.”' : 'Write a short persuasive response Maya could give to Ms. Chen explaining why the school garden should continue. Use at least one detail from the passage.', null, null)
    ];
  }

  if (skill === 'research_papers') {
    return [
      q(ids[0], 'multiple_choice', 'Which claim is best supported by “Amazing Butterflies”?', rotateOptions('Butterflies are important because they help pollinate plants and need protected habitats.', ['Butterflies only live in Antarctica.', 'Butterflies are not connected to plants.', 'Butterflies never change during their life cycle.'], 1), 'Butterflies are important because they help pollinate plants and need protected habitats.'),
      q(ids[1], 'multiple_choice', 'Which detail from “Amazing Butterflies” is evidence that butterflies help plants grow?', rotateOptions('Pollen sticks to a butterfly’s body and rubs off when it lands on another flower.', ['Butterflies can be colorful.', 'The monarch butterfly travels in the fall.', 'There are many species of butterflies.'], 2), 'Pollen sticks to a butterfly’s body and rubs off when it lands on another flower.'),
      q(ids[2], 'multiple_choice', 'Which fact from the passage could support a research paragraph about butterfly life cycles?', rotateOptions('A butterfly changes from egg to caterpillar to chrysalis to adult butterfly.', ['A butterfly is a kind of rock.', 'Butterflies build nests in the ocean.', 'Butterflies do not go through changes.'], 3), 'A butterfly changes from egg to caterpillar to chrysalis to adult butterfly.'),
      q(ids[3], 'multiple_choice', 'Why does the passage mention forests being cut down and fields being sprayed with chemicals?', rotateOptions('To explain one reason many butterfly species lose homes and food.', ['To explain how butterflies build houses.', 'To show that butterflies do not need plants.', 'To prove that all insects are dangerous.'], 4), 'To explain one reason many butterfly species lose homes and food.'),
      q(ids[4], 'written_response', isQuiz ? 'Write a short research paragraph explaining why butterflies should be protected. Use at least two facts from “Amazing Butterflies.”' : 'Write a C-E-E response about why butterflies are important. Use at least one fact from “Amazing Butterflies” as evidence.', null, null)
    ];
  }

  if (skill === 'narrative_writing') {
    return [
      q(ids[0], 'multiple_choice', 'What is Maya’s goal at the beginning of “The Garden Project”?', rotateOptions('She wants to turn the empty patch of dirt behind the school into a garden.', ['She wants to leave school early.', 'She wants to stop her classmates from helping.', 'She wants to throw away all the seeds.'], 1), 'She wants to turn the empty patch of dirt behind the school into a garden.'),
      q(ids[1], 'multiple_choice', 'Which detail shows the setting of “The Garden Project”?', rotateOptions('The empty patch of dirt is behind Maya’s school.', ['Maya rides a bus to a museum.', 'The story takes place on a beach.', 'The garden is inside a grocery store.'], 2), 'The empty patch of dirt is behind Maya’s school.'),
      q(ids[2], 'multiple_choice', 'Which sequence of events from the story is in the correct order?', rotateOptions('Maya asks classmates to help, they pull weeds and plant seeds, then they water the garden and watch it grow.', ['Maya picks tomatoes, then plants seeds, then asks classmates to help.', 'Ms. Chen cancels the garden, then Maya waters tomatoes that are already grown.', 'The classmates eat tomatoes before the garden is planted.'], 3), 'Maya asks classmates to help, they pull weeds and plant seeds, then they water the garden and watch it grow.'),
      q(ids[3], 'multiple_choice', 'What does Ms. Chen mean when she says hard work and patience can turn a dream into something real?', rotateOptions('Maya’s idea became a real garden because she and her classmates kept working over time.', ['Maya’s dream happened without effort.', 'The garden grew because no one helped.', 'Patience means giving up when work is hard.'], 4), 'Maya’s idea became a real garden because she and her classmates kept working over time.'),
      q(ids[4], 'written_response', isQuiz ? 'Write a short narrative continuation of “The Garden Project.” Include Maya, the school garden, and one new event that happens after the garden succeeds.' : 'Explain how Maya changes from the beginning to the end of “The Garden Project.” Use at least one story detail in your answer.', null, null)
    ];
  }

  return null;
}


function buildGenericGradeFiveQuestions(row) {
  const subject = String(row?.subject || '').toLowerCase();
  const title = String(row?.title || row?.assignment_title || row?.lesson_title || 'this lesson');
  const titleLower = title.toLowerCase();
  const ids = [1, 2, 3, 4, 5];
  if (subject.includes('social')) {
    if (titleLower.includes('causes of the revolution')) {
      return [
        q(ids[0], 'multiple_choice', 'Which issue helped cause tension between the American colonists and Great Britain?', rotateOptions('British taxes and laws made many colonists feel they were being treated unfairly.', ['The colonists wanted to stop all farming.', 'Great Britain asked the colonists to move to Antarctica.', 'The colonists did not want any trade at all.'], 1), 'British taxes and laws made many colonists feel they were being treated unfairly.'),
        q(ids[1], 'multiple_choice', 'Why were events like the Boston Tea Party important?', rotateOptions('They showed that some colonists were protesting British control.', ['They ended every disagreement right away.', 'They were only about planting gardens.', 'They were part of the Civil War.'], 2), 'They showed that some colonists were protesting British control.'),
        q(ids[2], 'multiple_choice', 'What does “taxation without representation” mean?', rotateOptions('Colonists were taxed by a government where they did not have elected representatives.', ['Colonists voted on every British law.', 'Colonists paid no taxes at all.', 'Colonists were represented in Parliament equally.'], 3), 'Colonists were taxed by a government where they did not have elected representatives.'),
        q(ids[3], 'multiple_choice', 'Which answer best explains why the American Revolution began?', rotateOptions('Many colonists wanted more rights and control over their own government.', ['The colonists wanted fewer freedoms.', 'The colonies were fighting over the moon.', 'The colonies wanted to stop writing letters.'], 4), 'Many colonists wanted more rights and control over their own government.'),
        q(ids[4], 'written_response', 'Explain one cause of the American Revolution. Use at least one detail from the lesson in your answer.', null, null)
      ];
    }
  }
  if (subject.includes('science')) {
    if (titleLower.includes('properties of matter')) {
      return [
        q(ids[0], 'multiple_choice', 'Which property can be observed without changing the substance?', rotateOptions('Color', ['Burning', 'Rusting', 'Cooking'], 1), 'Color'),
        q(ids[1], 'multiple_choice', 'Which tool could help measure the mass of an object?', rotateOptions('A balance scale', ['A calendar', 'A map', 'A paintbrush'], 2), 'A balance scale'),
        q(ids[2], 'multiple_choice', 'Which object is most likely magnetic?', rotateOptions('An iron nail', ['A wooden pencil', 'A plastic spoon', 'A paper towel'], 3), 'An iron nail'),
        q(ids[3], 'multiple_choice', 'Why do scientists compare properties of matter?', rotateOptions('To identify, sort, and describe materials.', ['To make every material the same.', 'To avoid observing objects.', 'To remove all measurements.'], 4), 'To identify, sort, and describe materials.'),
        q(ids[4], 'written_response', 'Choose one object and describe two properties of matter it has. Explain how those properties help identify the object.', null, null)
      ];
    }
  }
  if (subject.includes('language') || subject.includes('reading') || subject.includes('ela') || subject.includes('english')) {
    return [
      q(ids[0], 'multiple_choice', `What is the main skill practiced in “${title}”?`, rotateOptions('Understanding the lesson skill and applying it to a passage or example.', ['Ignoring the text completely.', 'Solving a long division problem.', 'Memorizing random dates only.'], 1), 'Understanding the lesson skill and applying it to a passage or example.'),
      q(ids[1], 'multiple_choice', 'What should a strong answer include?', rotateOptions('A clear answer and a detail from the lesson or passage.', ['Only one unrelated word.', 'A copied question with no answer.', 'No explanation at all.'], 2), 'A clear answer and a detail from the lesson or passage.'),
      q(ids[2], 'multiple_choice', 'Why should students look back at the lesson before answering?', rotateOptions('It helps them use accurate details in their answer.', ['It makes the question disappear.', 'It changes the topic.', 'It removes the need to answer.'], 3), 'It helps them use accurate details in their answer.'),
      q(ids[3], 'multiple_choice', 'Which response is usually strongest?', rotateOptions('One that uses the lesson skill and explains the thinking.', ['One that is blank.', 'One that copies the prompt only.', 'One that talks about an unrelated topic.'], 4), 'One that uses the lesson skill and explains the thinking.'),
      q(ids[4], 'written_response', `Write a complete response that applies the skill from “${title}.” Use at least one detail from the lesson or passage.`, null, null)
    ];
  }
  return [];
}

function improveQuestionsForContext(row) {
  normalizeAssignmentTitleAndTopic(row);
  let original = Array.isArray(row.questions) ? row.questions : [];
  if (!Array.isArray(row.questions) && typeof row.questions === 'string') {
    try { original = JSON.parse(row.questions); } catch { original = []; }
  }
  const subject = String(row.subject || '').toLowerCase();
  const titleText = assignmentTextBundle(row).toLowerCase();
  const gradeNumber = parseInt(row.grade_level ?? row.grade ?? '', 10);
  const electiveKind = gradeFiveElectiveKind(row);
  if (gradeNumber === 5 && electiveKind) {
    const specific = gradeFiveElectiveQuestions(electiveKind, row.lesson_title || row.skill_name || row.unit_title, row.title || row.assignment_title, isQuizOrTestAssignment(row));
    if (specific.length > 0) return specific;
  }
  const languageArtsQuestions = gradeFiveLanguageArtsQuestions(row);
  if (languageArtsQuestions) return languageArtsQuestions;
  if (original.length === 0) return buildGenericGradeFiveQuestions(row);
  if (subject.includes('spelling')) return original;
  if (subject.includes('math') || subject.includes('mathematics')) return improveMathQuestionsForContext(row, original);

  if (titleText.includes('figurative language')) {
    const ids = [1,2,3,4,5].map((n, i) => original[i]?.id || n);
    return [
      q(ids[0], 'multiple_choice', 'Which sentence is a simile?', rotateOptions('The classroom was as quiet as a library.', ['The wind whispered through the trees.', 'Her smile was sunshine.', 'The homework sat on the desk.'], 1), 'The classroom was as quiet as a library.'),
      q(ids[1], 'multiple_choice', 'Which sentence is a metaphor?', rotateOptions('Her smile was sunshine.', ['The classroom was as quiet as a library.', 'The pencil rolled off the desk.', 'The rain tapped on the roof.'], 2), 'Her smile was sunshine.'),
      q(ids[2], 'multiple_choice', 'Which sentence uses personification?', rotateOptions('The wind whispered through the trees.', ['The dog ran across the yard.', 'The book was on the table.', 'The cup was blue.'], 3), 'The wind whispered through the trees.'),
      q(ids[3], 'multiple_choice', 'What does figurative language help a writer do?', rotateOptions('It helps readers picture ideas in a stronger or more creative way.', ['It removes all description from writing.', 'It makes every sentence a question.', 'It only tells the exact dictionary definition.'], 4), 'It helps readers picture ideas in a stronger or more creative way.'),
      q(ids[4], 'written_response', 'Write one sentence using a simile, metaphor, or personification. Then explain what it means.', null, null)
    ];
  }
  if (!subject.includes('language') && !subject.includes('ela') && !subject.includes('english') && !subject.includes('reading')) return original;
  if (!row.reading_passage && !row.source_text) return original;

  const kind = passageKind(row);
  const title = row.passage_title || row.source_title || row.lesson_title || row.title || 'the passage';
  const seed = parseInt(row.id || 0, 10);
  const useIds = [1,2,3,4,5].map((n, i) => original[i]?.id || n);

  if (kind === 'informational') {
    const isWater = /water|fresh/i.test(title + ' ' + (row.reading_passage || ''));
    const mainIdea = isWater
      ? 'The passage is mostly about why fresh water is an important resource to protect.'
      : `The passage is mostly about the important ideas in "${title}."`;
    const support = isWater
      ? 'People, animals, plants, and farms need fresh water to survive and grow food.'
      : 'A specific fact or detail from the passage supports the main idea.';
    const causeEffect = isWater
      ? 'Pollution can make water unsafe for people and animals.'
      : 'One event or fact in the text causes another result.';
    const inference = isWater
      ? 'Small daily choices can help save and protect fresh water.'
      : 'Readers can use facts in the text to understand why the topic matters.';
    return [
      q(useIds[0], 'multiple_choice', `What is the main idea of "${title}"?`, rotateOptions(mainIdea, ['The passage is mostly about going on vacation.', 'The passage is mostly about playing a sport.', 'The passage is mostly about cooking dinner.'], seed + 1), mainIdea),
      q(useIds[1], 'multiple_choice', 'Which detail from the passage best supports the main idea?', rotateOptions(support, ['The name of the street they lived on.', 'What the characters ate for breakfast.', 'The color of the sky that day.'], seed + 2), support),
      q(useIds[2], 'multiple_choice', isWater ? 'What is one cause-and-effect relationship explained in the text?' : 'Which answer shows a cause-and-effect relationship from the text?', rotateOptions(causeEffect, ['A character changes their mind for no reason.', 'The story begins with a long conversation.', 'The passage lists unrelated facts with no connection.'], seed + 3), causeEffect),
      q(useIds[3], 'multiple_choice', isWater ? 'What can you infer about protecting fresh water?' : 'What can you infer from the facts in the passage?', rotateOptions(inference, ['The topic does not affect people.', 'The details are included only for entertainment.', 'The reader should ignore the evidence.'], seed + 4), inference),
      q(useIds[4], 'written_response', isWater ? 'Explain why fresh water is important. Use at least one detail from the passage to support your answer.' : `Explain what the reader can learn from "${title}." Use at least one detail from the passage to support your answer.`, null, null)
    ];
  }

  if (kind === 'story') {
    const text = String(row.reading_passage || row.source_text || '');
    const isNewStudent = /sofia|liam|new student|mrs\. roberts/i.test(title + ' ' + text);
    const isGarden = /maya|garden|tomatoes|sunflowers|ms\. chen/i.test(title + ' ' + text);

    if (isNewStudent) {
      return [
        q(useIds[0], 'multiple_choice', 'What problem does Sofia face at the beginning of "The New Student"?', rotateOptions('She is new at school and feels nervous because she does not know anyone.', ['She forgets to bring her lunch to school.', 'She cannot find her homework.', 'She wants to leave school early.'], seed + 1), 'She is new at school and feels nervous because she does not know anyone.'),
        q(useIds[1], 'multiple_choice', 'Which detail shows that Liam understands how Sofia feels?', rotateOptions('He remembers his own first day at a new school.', ['He talks loudly in the cafeteria.', 'He finishes his lunch quickly.', 'He asks the teacher for another assignment.'], seed + 2), 'He remembers his own first day at a new school.'),
        q(useIds[2], 'multiple_choice', 'How does Liam help Sofia feel more comfortable?', rotateOptions('He sits with her, introduces her to his friends, and shows her places in the school.', ['He tells her to ignore everyone.', 'He asks her to sit alone.', 'He gives her a test to complete.'], seed + 3), 'He sits with her, introduces her to his friends, and shows her places in the school.'),
        q(useIds[3], 'multiple_choice', 'What can you infer about Liam after he sits with Sofia at lunch?', rotateOptions('Liam remembers what it felt like to be new and wants to help Sofia feel welcome.', ['Liam wants Sofia to do his schoolwork for him.', 'Liam does not notice that Sofia feels nervous.', 'Liam only talks to Sofia because the teacher tells him to.'], seed + 4), 'Liam remembers what it felt like to be new and wants to help Sofia feel welcome.'),
        q(useIds[4], 'written_response', 'What lesson can the reader learn from "The New Student"? Use at least one story detail to support your answer.', null, null)
      ];
    }

    if (isGarden) {
      return [
        q(useIds[0], 'multiple_choice', 'What is Maya trying to accomplish in "The Garden Project"?', rotateOptions('She wants to turn an empty patch of dirt into a garden.', ['She wants to win a cooking contest.', 'She wants to avoid working with her class.', 'She wants to paint the school walls.'], seed + 1), 'She wants to turn an empty patch of dirt into a garden.'),
        q(useIds[1], 'multiple_choice', 'Which detail shows that the garden takes patience?', rotateOptions('Maya waters the seeds every day and waits before the green sprouts appear.', ['Maya picks a tomato before planting seeds.', 'The teacher tells Maya the work is finished right away.', 'The garden grows overnight without any care.'], seed + 2), 'Maya waters the seeds every day and waits before the green sprouts appear.'),
        q(useIds[2], 'multiple_choice', 'How does the garden change from the beginning to the end of the story?', rotateOptions('It changes from an empty patch of dirt into a colorful garden.', ['It changes from a playground into a cafeteria.', 'It changes from a library into an art room.', 'It does not change at all.'], seed + 3), 'It changes from an empty patch of dirt into a colorful garden.'),
        q(useIds[3], 'multiple_choice', 'What can you infer about Maya from her work in the garden?', rotateOptions('Maya is patient and willing to keep working even when the job takes time.', ['Maya gives up as soon as the work gets difficult.', 'Maya only likes projects that are already finished.', 'Maya does not care what happens to the garden.'], seed + 4), 'Maya is patient and willing to keep working even when the job takes time.'),
        q(useIds[4], 'written_response', 'What lesson does Maya learn in "The Garden Project"? Use at least one detail from the story to support your answer.', null, null)
      ];
    }

    const nameMatches = [...text.matchAll(/\b([A-Z][a-z]{2,})\b/g)]
      .map(m => m[1])
      .filter(n => !['The','A','An','Mrs','Mr','Miss','One','When','After','Before','Then','Later','Monday','Tuesday','Wednesday','Thursday','Friday'].includes(n));
    const seenNames = [];
    nameMatches.forEach(n => { if (!seenNames.includes(n)) seenNames.push(n); });
    const leadName = seenNames[0] || 'the main character';
    const otherName = seenNames[1] || 'another character';
    const problem = `${leadName} faces a specific problem, goal, or change in "${title}".`;
    const detail = `A detail about ${leadName}${seenNames[1] ? ' or ' + otherName : ''} supports the story’s main idea.`;
    const sequence = `The events show how ${leadName}'s situation changes from the beginning to the end.`;
    const character = `${leadName}'s actions reveal what ${leadName} thinks, feels, or values.`;
    return [
      q(useIds[0], 'multiple_choice', `What problem, goal, or change does ${leadName} face in "${title}"?`, rotateOptions(problem, ['The story is mainly a list of unrelated facts.', `${leadName} has no goal, problem, or change.`, 'The story is mostly about a random recipe.'], seed + 1), problem),
      q(useIds[1], 'multiple_choice', `Which kind of detail about ${leadName}${seenNames[1] ? ' or ' + otherName : ''} best supports the main idea of "${title}"?`, rotateOptions(detail, ['A detail that does not affect any character.', 'A random color mentioned once with no connection.', 'Only the page number of the story.'], seed + 2), detail),
      q(useIds[2], 'multiple_choice', `Why is the order of events important in "${title}"?`, rotateOptions(sequence, ['The events can be placed in any order and mean the same thing.', 'The events are unrelated to the characters.', 'The sequence only tells the reader the date.'], seed + 3), sequence),
      q(useIds[3], 'multiple_choice', `What can the reader infer from ${leadName}'s actions?`, rotateOptions(character, [`${leadName} has no thoughts or feelings.`, `${leadName}'s actions do not matter.`, `${leadName} is exactly the same as every other character.`], seed + 4), character),
      q(useIds[4], 'written_response', `What lesson or message can the reader learn from "${title}"? Use at least one detail about ${leadName}${seenNames[1] ? ' or ' + otherName : ''} to support your answer.`, null, null)
    ];
  }

  return original;
}



function isQuizOrTestAssignment(row) {
  const t = String(row.assignment_type || '').toLowerCase();
  const w = String(row.weight_category || '').toLowerCase();
  const title = String(row.title || row.assignment_title || '').toLowerCase();
  return t.includes('quiz') || t.includes('test') || w.includes('quiz') || w.includes('test') || title.includes('quiz') || title.includes('test');
}


function readingLeadNames(text) {
  const skip = new Set(['The','A','An','Mrs','Mr','Miss','Ms','One','When','After','Before','Then','Later','Monday','Tuesday','Wednesday','Thursday','Friday','Dear','Sincerely','First','Second','Third','Fourth','Finally','Unfortunately']);
  const names = [];
  String(text || '').replace(/\b([A-Z][a-z]{2,})\b/g, (_, n) => {
    if (!skip.has(n) && !names.includes(n)) names.push(n);
    return n;
  });
  return names;
}

function practicePromptLooksGeneric(item, row) {
  const qText = String(item?.question || item?.prompt || '').toLowerCase();
  const title = String(row?.passage_title || row?.source_title || '').toLowerCase();
  const passage = String(row?.reading_passage || row?.source_text || '').toLowerCase();
  if (!qText.trim()) return true;
  if (/based on your study of|name one important thing|main focus of this lesson|write a response that includes a claim|in this lesson/i.test(qText)) return true;
  if (title && title.length > 3 && qText.includes(title)) return false;
  const names = readingLeadNames(row?.reading_passage || row?.source_text || '').map(n => n.toLowerCase());
  if (names.some(n => qText.includes(n))) return false;
  if (/passage|story|article|text/.test(qText) && passage) return false;
  return false;
}

function buildReadingAlignedPractice(row) {
  const text = String(row?.reading_passage || row?.source_text || '');
  if (!text.trim()) return parseJsonList(row?.guided_practice, []);
  const title = row?.passage_title || row?.source_title || row?.lesson_title || row?.title || 'the passage';
  const lower = (title + ' ' + text).toLowerCase();
  const names = readingLeadNames(text);
  const leadName = names[0] || 'the main person or topic';
  const kind = passageKind(row);

  if (lower.includes('butterfl')) {
    return [
      { question:'Using “Amazing Butterflies,” write a research claim about why butterflies are important to plants. Include the word pollination or explain how butterflies help flowers.', hint:'Use the passage detail about pollen sticking to a butterfly and rubbing off on another flower.', answer:'Butterflies are important to plants because they help with pollination, which allows plants to grow new seeds.' },
      { question:'Write one C-E-E response about why people should protect butterfly habitats. Use one fact from “Amazing Butterflies,” such as forests being cut down, chemicals harming fields, or monarch butterflies migrating over 3,000 miles.', hint:'C-E-E means claim, evidence, and explanation. Start with your point, add one fact from the passage, then explain why it matters.', answer:'People should protect butterfly habitats because many butterflies are in danger. The passage says butterflies lose homes and food when forests are cut down or fields are sprayed with chemicals. This matters because protecting natural areas helps butterflies survive.' }
    ];
  }

  if (kind === 'story') {
    return [
      { question:`Using “${title},” explain what ${leadName} wants, learns, or changes. Use one detail from the story.`, hint:`Look back at what ${leadName} says, does, or notices in the story.`, answer:`A strong response names ${leadName}, explains the change or goal, and uses a story detail as evidence.` },
      { question:`Write a short response that connects the lesson skill to “${title}.” Include ${leadName}${names[1] ? ' or ' + names[1] : ''} and one event from the story.`, hint:'Use a character name and a real event from the passage so your answer stays connected to the reading.', answer:'Answers will vary, but should include a character name and a detail from the story.' }
    ];
  }

  return [
    { question:`Using “${title},” write one claim or main idea that can be supported by facts from the passage.`, hint:'Use the title and one fact from the article, not a random topic.', answer:`A strong response states a claim about “${title}” and supports it with a fact from the passage.` },
    { question:`Choose one important fact from “${title}.” Explain why that fact matters to the topic of the passage.`, hint:'Look for a number, process, cause, effect, or important detail from the passage.', answer:`A strong response names a fact from “${title}” and explains how it helps the reader understand the topic.` }
  ];
}

function applyReadingAlignedPracticeFallback(row) {
  if (!row || !isGradeFiveLanguageArtsRow(row)) return row;
  if (!(row.reading_passage || row.source_text)) return row;
  const current = parseJsonList(row.guided_practice, []);
  const needsAligned = current.length === 0 || current.some(item => practicePromptLooksGeneric(item, row));
  if (needsAligned) row.guided_practice = buildReadingAlignedPractice(row);
  return row;
}

function enrichLessonDetail(row) {
  if (!row) return row;
  normalizeAssignmentTitleAndTopic(row);
  const subject = String(row.subject || '').toLowerCase();
  const titleText = String([row.title, row.lesson_title, row.unit_title, row.source_title, row.concept_title].filter(Boolean).join(' ')).toLowerCase();
  const setJson = (key, value) => { row[key] = Array.isArray(value) || typeof value === 'object' ? value : value; };

  applyGradeFiveLanguageArtsStoryContent(row);

  if (subject.includes('math') && titleText.includes('decimal place value')) {
    row.instructional_content = 'Decimal place value tells what each digit is worth based on where it is located. Digits to the right of the decimal point show parts of one whole. The first place after the decimal is the tenths place, the second place is the hundredths place, and the third place is the thousandths place.\n\nFor example, in 4.732, the 7 is in the tenths place, so it means 7 tenths. The 3 is in the hundredths place, so it means 3 hundredths. The 2 is in the thousandths place, so it means 2 thousandths. Reading decimals carefully helps you compare, order, and round numbers.';
    row.concept_title = 'Decimal Place Value';
    row.concept_explanation = row.instructional_content;
    row.vocabulary = [
      { term:'decimal point', definition:'The dot that separates whole numbers from parts of a whole.' },
      { term:'tenths', definition:'The first place to the right of the decimal point.' },
      { term:'hundredths', definition:'The second place to the right of the decimal point.' },
      { term:'thousandths', definition:'The third place to the right of the decimal point.' },
      { term:'digit value', definition:'How much a digit is worth because of its place.' }
    ];
    row.examples = [
      { title:'Place Value Example', content:'In 6.48, the 4 is in the tenths place and the 8 is in the hundredths place.', solution:'6.48 means 6 wholes, 4 tenths, and 8 hundredths.' },
      { title:'Comparing Decimals', content:'Compare 3.45 and 3.54 by looking at tenths first.', solution:'3.54 is greater because 5 tenths is greater than 4 tenths.' }
    ];
    row.guided_practice = [
      { question:'What is the value of the 5 in 2.58?', hint:'The 5 is the first digit after the decimal.', answer:'5 tenths' },
      { question:'Which is greater: 4.36 or 4.63?', hint:'Compare the tenths place first.', answer:'4.63' }
    ];
    row.activity_data = { visual_cards: [
      { title:'Tenths', text:'First digit after the decimal. 0.4 means four tenths.', icon:'①' },
      { title:'Hundredths', text:'Second digit after the decimal. 0.04 means four hundredths.', icon:'②' },
      { title:'Thousandths', text:'Third digit after the decimal. 0.004 means four thousandths.', icon:'③' }
    ], compare_table: [
      { object:'4.7', properties:'7 is in the tenths place', useful_for:'reading 4 and 7 tenths' },
      { object:'4.07', properties:'7 is in the hundredths place', useful_for:'reading 4 and 7 hundredths' },
      { object:'4.007', properties:'7 is in the thousandths place', useful_for:'reading 4 and 7 thousandths' }
    ] };
  }

  if ((subject.includes('language') || subject.includes('ela') || subject.includes('reading') || subject.includes('english')) && titleText.includes('figurative language')) {
    row.instructional_content = 'Figurative language helps writers create a picture or feeling that is stronger than plain words. A simile compares two things using like or as. A metaphor compares two things by saying one thing is another. Personification gives human actions or feelings to something that is not human. Hyperbole is an exaggeration used to make a point. Writers use figurative language to make descriptions more interesting and easier to imagine.';
    row.vocabulary = [
      { term:'simile', definition:'A comparison using like or as.' },
      { term:'metaphor', definition:'A comparison that says one thing is another.' },
      { term:'personification', definition:'Giving human actions or feelings to something that is not human.' },
      { term:'hyperbole', definition:'An exaggeration used to make a point.' }
    ];
    row.examples = [
      { title:'Simile', content:'The classroom was as quiet as a library.', solution:'This compares the classroom to a library using as.' },
      { title:'Metaphor', content:'Her smile was sunshine.', solution:'This means her smile felt bright or cheerful.' },
      { title:'Personification', content:'The wind whispered through the trees.', solution:'The wind is given the human action of whispering.' }
    ];
    row.guided_practice = [{ question:'Which kind of figurative language uses like or as?', hint:'Look for comparison words.', answer:'A simile.' }];
    row.activity_data = { visual_cards: [
      { title:'Simile', text:'Uses like or as: as quiet as a library.', icon:'↔️' },
      { title:'Metaphor', text:'Says one thing is another: Her smile was sunshine.', icon:'☀️' },
      { title:'Personification', text:'Gives human action: The wind whispered.', icon:'💬' }
    ] };
  }

  if (subject.includes('science') && (titleText.includes('properties of matter') || titleText.includes('matter'))) {
    row.instructional_content = 'Matter is anything that has mass and takes up space. A property of matter is something we can observe or measure about an object or material. Scientists use properties to describe, compare, sort, and identify matter.\n\nPhysical properties include color, size, shape, texture, hardness, magnetism, solubility, conductivity, mass, volume, and density. These properties help explain why different materials are useful for different jobs. For example, metal is often used in cooking pans because it conducts heat, while plastic is often used for containers because it is lightweight and does not break as easily as glass.';
    row.vocabulary = [
      { term:'matter', definition:'Anything that has mass and takes up space.' },
      { term:'property', definition:'A feature or characteristic that can be observed or measured.' },
      { term:'mass', definition:'The amount of matter in an object.' },
      { term:'volume', definition:'The amount of space something takes up.' },
      { term:'magnetism', definition:'A property that makes some materials attracted to magnets.' },
      { term:'solubility', definition:'How well a substance dissolves in a liquid.' },
      { term:'conductivity', definition:'How well a material lets heat or electricity move through it.' },
      { term:'density', definition:'How tightly matter is packed in a space.' }
    ];
    row.examples = [
      { title:'Paper Clip', content:'A paper clip is small, solid, metal, shiny, and magnetic. A magnet can pick it up because magnetism is one of its properties.', solution:'The useful property is magnetism.' },
      { title:'Sponge', content:'A sponge is soft, flexible, full of tiny holes, and absorbs water. Those properties make it useful for cleaning.', solution:'The useful properties are flexibility and absorbency.' },
      { title:'Salt', content:'Salt is white, grainy, solid, and dissolves in water. Dissolving in water is called solubility.', solution:'The useful property is solubility.' }
    ];
    row.guided_practice = [
      { question:'Look at a pencil. Name three physical properties you can observe.', hint:'Think about color, shape, texture, and material.', answer:'A pencil may be yellow, long, solid, smooth, and made of wood and graphite.' },
      { question:'Which property explains why a metal spoon can get hot in soup?', hint:'Think about how heat moves through material.', answer:'Conductivity helps explain why metal can transfer heat.' }
    ];
    row.activity_data = {
      visual_cards: [
        { title:'Observe', text:'Use your senses safely. What color, shape, size, or texture do you notice?', icon:'👀' },
        { title:'Measure', text:'Mass and volume can be measured with tools.', icon:'⚖️' },
        { title:'Test', text:'A magnet, water, or heat can help reveal properties.', icon:'🧲' },
        { title:'Use', text:'Properties help us choose the best material for a job.', icon:'🧰' }
      ],
      compare_table: [
        { object:'Paper clip', properties:'solid, metal, shiny, magnetic', useful_for:'holding papers together' },
        { object:'Sponge', properties:'soft, flexible, absorbs water', useful_for:'cleaning spills' },
        { object:'Plastic cup', properties:'lightweight, waterproof, not magnetic', useful_for:'holding drinks' }
      ],
      project_prompt:'Choose one object near you. Describe at least four physical properties and explain one way those properties make the object useful.'
    };
    if (isQuizOrTestAssignment(row)) {
      row.questions = [
        mc(1, 'Which statement best explains a property of matter?', 'A property is a feature of matter that can be observed or measured.', ['A property is always invisible.', 'A property is only the name of an object.', 'A property is something that never changes how matter is used.']),
        mc(2, 'A raincoat is useful because water does not soak through it easily. Which property is being used?', 'Water resistance', ['Magnetism', 'Color only', 'Smell']),
        mc(3, 'Which property explains why a magnet can pick up a paper clip?', 'Magnetism', ['Solubility', 'Color', 'Volume']),
        mc(4, 'Why is metal often used for cooking pans?', 'Metal conducts heat well.', ['Metal always floats in water.', 'Metal dissolves quickly.', 'Metal is always soft.']),
        wr(5, 'Choose an object near you. Name at least three properties of the object and explain how one property makes it useful.')
      ];
    } else {
      row.questions = [
        mc(1, 'Which object is likely magnetic?', 'A metal paper clip', ['A plastic cup', 'A cotton ball', 'A wooden pencil']),
        wr(2, 'Based on what you learned about properties of matter, describe one real-world application. Explain the science behind it in at least 3 to 4 sentences.'),
        mc(3, 'Salt dissolves in water. Which property does this show?', 'Solubility', ['Magnetism', 'Shape', 'Color']),
        mc(4, 'Which property would help you decide if a material is good for a window?', 'Transparency', ['Flavor', 'Sound volume', 'Age']),
        wr(5, 'Look at an object near you. Describe its color, texture, and one other property.')
      ];
    }
  }

  if (subject.includes('social') && (titleText.includes('13 colonies') || titleText.includes('thirteen colonies'))) {
    row.historical_context = 'The 13 Colonies were British settlements along the east coast of North America. They later became the first states of the United States. The colonies are usually grouped into three regions: New England Colonies, Middle Colonies, and Southern Colonies.\n\nThe regions were different because geography affected daily life. New England had rocky soil, forests, shipbuilding, fishing, and trade. The Middle Colonies had fertile farmland, busy ports, and a mix of cultures. The Southern Colonies had warmer weather, large farms, and crops such as tobacco, rice, and indigo. Studying the regions helps us understand how geography, work, and government shaped early American history.';
    row.instructional_content = row.historical_context;
    row.source_title = 'The 13 Colonies Regions';
    row.source_type = 'map_lesson';
    row.source_text = 'The 13 Colonies were located on the eastern side of North America near the Atlantic Ocean. New England Colonies included Massachusetts, New Hampshire, Rhode Island, and Connecticut. Middle Colonies included New York, New Jersey, Pennsylvania, and Delaware. Southern Colonies included Maryland, Virginia, North Carolina, South Carolina, and Georgia.';
    row.vocabulary = [
      { term:'colony', definition:'A place controlled by another country.' },
      { term:'region', definition:'An area with features that make it different from other areas.' },
      { term:'settlement', definition:'A place where people build a community.' },
      { term:'economy', definition:'The way people make, use, and trade goods and services.' },
      { term:'geography', definition:'The land, water, climate, and location of a place.' }
    ];
    row.examples = [
      { title:'Using a Region Chart', content:'Massachusetts is in New England. Pennsylvania is in the Middle Colonies. Georgia is in the Southern Colonies.', solution:'Grouping the colonies helps us compare climate, jobs, and daily life.' },
      { title:'Geography Affects Work', content:'Rocky soil made farming difficult in parts of New England, so fishing, shipbuilding, and trade became important.', solution:'The land and climate affected how people worked.' }
    ];
    row.guided_practice = [
      { question:'Which region included Georgia, South Carolina, and North Carolina?', hint:'Think about the warmer region farther south.', answer:'The Southern Colonies.' },
      { question:'Why did people in different colonial regions have different jobs?', hint:'Think about land, water, weather, and resources.', answer:'Geography and climate affected the resources available in each region.' }
    ];
    row.activity_data = {
      map_regions: [
        { region:'New England', colonies:['Massachusetts','New Hampshire','Rhode Island','Connecticut'], facts:'rocky soil, fishing, forests, shipbuilding, trade', icon:'🟦' },
        { region:'Middle', colonies:['New York','New Jersey','Pennsylvania','Delaware'], facts:'fertile farmland, ports, trade, diverse communities', icon:'🟨' },
        { region:'Southern', colonies:['Maryland','Virginia','North Carolina','South Carolina','Georgia'], facts:'warm climate, large farms, tobacco, rice, indigo', icon:'🟩' }
      ],
      visual_cards: [
        { title:'Where?', text:'The colonies were along the Atlantic coast of North America.', icon:'🗺️' },
        { title:'Who controlled them?', text:'Great Britain controlled the colonies before independence.', icon:'🇬🇧' },
        { title:'Why group them?', text:'Regions help us compare land, climate, jobs, and daily life.', icon:'📊' }
      ],
      map_prompt:'Look at the three regions. Which region was farthest south? Which region had Massachusetts?'
    };
    if (isQuizOrTestAssignment(row)) {
      row.questions = [
        mc(1, 'What were the 13 Colonies?', 'British settlements along the east coast of North America that later became the first states.', ['Countries in Europe that became part of Great Britain.', 'Cities on the west coast of North America.', 'A group of islands in the Pacific Ocean.']),
        mc(2, 'Which answer correctly names the three colonial regions?', 'New England, Middle, and Southern Colonies', ['Northern, Western, and Island Colonies', 'Mountain, Desert, and River Colonies', 'Eastern, Central, and Pacific Colonies']),
        mc(3, 'A colony has rocky soil, forests, fishing, and shipbuilding. Which region is most likely being described?', 'New England Colonies', ['Southern Colonies', 'Pacific Colonies', 'Desert Colonies']),
        mc(4, 'Which colony was part of the Southern Colonies?', 'Georgia', ['Massachusetts', 'Rhode Island', 'New Hampshire']),
        wr(5, 'Choose one colonial region and explain how geography affected life or work there. Use at least one specific detail.')
      ];
    } else {
      row.questions = [
        mc(1, 'Where were the 13 Colonies located?', 'Along the east coast of North America near the Atlantic Ocean.', ['In the middle of the Pacific Ocean.', 'Only on the west coast.', 'In South America.']),
        mc(2, 'Which region included New York, New Jersey, Pennsylvania, and Delaware?', 'Middle Colonies', ['New England Colonies', 'Southern Colonies', 'Island Colonies']),
        mc(3, 'Why did geography matter in the 13 Colonies?', 'Land, climate, and resources affected jobs and daily life.', ['Geography made every colony exactly the same.', 'Geography only affected the color of houses.', 'Geography had nothing to do with colonial life.']),
        mc(4, 'Which region had a warmer climate and large farms growing crops like tobacco, rice, and indigo?', 'Southern Colonies', ['New England Colonies', 'Middle Colonies', 'Arctic Colonies']),
        wr(5, 'Explain one difference between two colonial regions using details from the lesson.')
      ];
    }
  }

  if (subject.includes('social') && (titleText.includes('colonial life') || titleText.includes('colonial times'))) {
    row.historical_context = 'Colonial life means how people lived in the American colonies before the United States became an independent country. Daily life was different depending on where a family lived, what work they did, and whether they lived in a town, on a farm, or near the coast.\n\nMany colonial families had to make or grow much of what they needed. Children often helped with chores, such as carrying water, feeding animals, gathering wood, helping in gardens, or learning a trade. Colonists worked as farmers, blacksmiths, printers, carpenters, merchants, sailors, and many other jobs. Life could be difficult because there were no modern stores, electricity, cars, or phones. Studying colonial life helps us compare past and present communities.';
    row.instructional_content = row.historical_context;
    row.source_title = 'Colonial Life and Daily Work';
    row.source_type = 'social_studies_lesson';
    row.source_text = 'Colonial communities depended on work, family responsibilities, local resources, and trade. Farms produced food and raw materials. Towns had shops and tradespeople. Coastal areas often depended on fishing, shipbuilding, shipping, and trade.';
    row.vocabulary = [
      { term:'trade', definition:'A skilled job, such as blacksmithing, printing, carpentry, or shoemaking.' },
      { term:'apprentice', definition:'A young person who learns a skill by working with an experienced worker.' },
      { term:'goods', definition:'Items people make, grow, buy, sell, or trade.' },
      { term:'community', definition:'A group of people living and working together in one area.' }
    ];
    row.examples = [
      { title:'Farm Life', content:'A colonial farm family might grow corn, raise animals, make soap, and repair tools at home.', solution:'Many families had to produce what they needed instead of buying everything from a store.' },
      { title:'Town Life', content:'A colonial town might have a printer, blacksmith, teacher, tailor, and merchants.', solution:'Towns often had more specialized jobs and trade.' }
    ];
    row.guided_practice = [
      { question:'Name one colonial job or chore and explain why it mattered.', hint:'Think about food, clothing, tools, homes, or transportation.', answer:'A blacksmith made and repaired tools, which helped families and workers complete daily tasks.' },
      { question:'How was colonial life different from life today?', hint:'Think about electricity, stores, transportation, and chores.', answer:'Colonial families had to make or grow more things themselves and did not have modern electricity, cars, or phones.' }
    ];
    row.activity_data = {
      visual_cards: [
        { title:'Home and Chores', text:'Children and adults helped with work that supported the whole household.', icon:'🏠' },
        { title:'Trades', text:'Skilled workers made and repaired tools, shoes, clothes, barrels, and printed materials.', icon:'🛠️' },
        { title:'Farming', text:'Many families grew crops and raised animals for food and trade.', icon:'🌽' },
        { title:'Compare', text:'Ask: What is the same and different about life then and life now?', icon:'🔎' }
      ],
      project_prompt:'Choose one colonial job or chore. Draw or describe what tools or supplies that person might need.'
    };
    if (isQuizOrTestAssignment(row)) {
      row.questions = [
        mc(1, 'What does colonial life mean?', 'How people lived and worked in the American colonies.', ['A list of modern video games.', 'Only the names of oceans.', 'A type of weather forecast.']),
        mc(2, 'Which job would most likely be a colonial trade?', 'Blacksmith', ['Astronaut', 'Video game designer', 'Airplane pilot']),
        mc(3, 'Why did many colonial children help with chores?', 'Families needed everyone to help with work at home or on farms.', ['Children never had responsibilities.', 'Chores were only pretend games.', 'There were no families in the colonies.']),
        mc(4, 'Which detail best shows how colonial life was different from life today?', 'People did not have modern electricity, cars, or phones.', ['People used smartphones every day.', 'Families shopped online.', 'Children rode school buses.']),
        wr(5, 'Choose one colonial job or chore and explain how it helped a family or community.')
      ];
    } else {
      row.questions = [
        mc(1, 'Which statement best describes colonial life?', 'People worked hard at home, on farms, in towns, and in trades.', ['People lived exactly like families today.', 'Everyone had electricity and cars.', 'No one needed food, tools, or homes.']),
        wr(2, 'Describe one colonial chore or job and explain why it was important.'),
        mc(3, 'What is an apprentice?', 'A young person learning a skill from an experienced worker.', ['A type of ship.', 'A modern computer.', 'A colony region.']),
        mc(4, 'Why did location affect colonial jobs?', 'Land, water, and resources influenced the work people did.', ['Location never mattered.', 'Every colony had the same exact jobs.', 'Only the weather mattered, not resources.']),
        wr(5, 'Compare colonial life with life today. Give one similarity or difference.')
      ];
    }
  }

  if (subject.includes('social') && (titleText.includes('road to revolution') || titleText.includes('revolutionary war') || titleText.includes('american revolution'))) {
    row.historical_context = 'The Road to Revolution was the period when tensions grew between Great Britain and the American colonies. Many colonists became upset about taxes, laws, and limits on self-government. Events such as the Stamp Act, Boston Massacre, Boston Tea Party, and Intolerable Acts made the conflict worse.\n\nThe disagreement was not just about one tax. It was about whether the colonies had a voice in the laws and taxes that affected them. Some colonists wanted independence, some wanted to stay loyal to Britain, and some were unsure. Studying different viewpoints helps us understand why the American Revolution happened.';
    row.instructional_content = row.historical_context;
    row.source_title = 'Events Leading to the American Revolution';
    row.source_type = 'timeline_lesson';
    row.source_text = 'Several events increased tension between Great Britain and the colonies: new taxes after the French and Indian War, the Stamp Act, protests, the Boston Massacre, the Boston Tea Party, and the Intolerable Acts. These events helped lead to open conflict and eventually independence.';
    row.vocabulary = [
      { term:'revolution', definition:'A major change in government or society, often involving conflict.' },
      { term:'tax', definition:'Money people pay to a government.' },
      { term:'boycott', definition:'Refusing to buy or use something as a form of protest.' },
      { term:'loyalist', definition:'A colonist who wanted to remain loyal to Great Britain.' },
      { term:'patriot', definition:'A colonist who supported independence from Great Britain.' }
    ];
    row.examples = [
      { title:'Cause and Effect', content:'Great Britain passed new taxes. Many colonists protested because they believed they did not have proper representation.', solution:'The tax laws helped increase tension.' },
      { title:'Different Viewpoints', content:'A Patriot might support independence, while a Loyalist might believe staying with Britain was safer.', solution:'People can experience the same event from different perspectives.' }
    ];
    row.guided_practice = [
      { question:'Name one event that helped lead to the American Revolution.', hint:'Think about taxes, protests, or laws.', answer:'The Boston Tea Party was one event that increased tension.' },
      { question:'Why is it important to study both Patriots and Loyalists?', hint:'Think about different viewpoints.', answer:'Studying both sides helps us understand why people made different choices.' }
    ];
    row.activity_data = {
      visual_cards: [
        { title:'Taxes', text:'New taxes caused many colonists to protest.', icon:'💰' },
        { title:'Protests', text:'Boycotts and public protests showed growing disagreement.', icon:'📣' },
        { title:'Boston Tea Party', text:'Colonists protested tea taxes by dumping tea into Boston Harbor.', icon:'🫖' },
        { title:'Revolution', text:'The conflict eventually led to war and independence.', icon:'⭐' }
      ],
      project_prompt:'Create a simple timeline with three events that helped lead to the American Revolution.'
    };
    if (isQuizOrTestAssignment(row)) {
      row.questions = [
        mc(1, 'What was the Road to Revolution?', 'The period when tensions grew between Great Britain and the colonies before the American Revolution.', ['A road built across the Pacific Ocean.', 'A single peaceful meeting with no conflict.', 'A music lesson about rhythm.']),
        mc(2, 'Why were many colonists upset about British taxes?', 'They believed they were being taxed without proper representation.', ['They wanted more homework.', 'They wanted fewer farms.', 'They wanted colder weather.']),
        mc(3, 'What was a boycott?', 'Refusing to buy or use something as a protest.', ['A type of colonial house.', 'A person who repairs shoes.', 'A kind of ship.']),
        mc(4, 'Which event involved colonists dumping tea into Boston Harbor?', 'The Boston Tea Party', ['The Stamp Act', 'The First Thanksgiving', 'The Louisiana Purchase']),
        wr(5, 'Explain one cause of the American Revolution and one effect it had on the colonies.')
      ];
    } else {
      row.questions = [
        mc(1, 'Which issue helped cause tension before the American Revolution?', 'Taxes and laws made by Great Britain without colonial representation.', ['A shortage of video games.', 'A disagreement about modern highways.', 'A debate about airplanes.']),
        wr(2, 'Identify two causes and two effects of a major conflict related to the Road to Revolution.'),
        mc(3, 'A colonist who supported independence was called a...', 'Patriot', ['Loyalist', 'Explorer', 'Merchant only']),
        mc(4, 'Why should historians examine more than one viewpoint?', 'Different groups experienced events differently.', ['Only one person ever matters.', 'Facts are not useful.', 'Every event has the exact same meaning to everyone.']),
        wr(5, 'Choose one Road to Revolution event and explain why it mattered.')
      ];
    }
  }

  applyReadingAlignedPracticeFallback(row);
  return row;
}

function assignmentStatusForRow({ grade, submission, assignment }) {
  const stepType = getStepType(assignment || {});
  const countsGrade = countsTowardGrade(stepType);
  const passing = parseFloat(assignment?.passing_grade || 70);
  if (!countsGrade && submission && ['completed','submitted','graded','partially_graded'].includes(String(submission.status || '').toLowerCase())) return 'practice_complete';
  if (grade) {
    const pct = parseFloat(grade.percentage || 0);
    if (pct < passing) return countsGrade ? 'retake_needed' : 'practice_again';
    if (!countsGrade) return 'practice_complete';
    return 'completed';
  }
  if (submission) {
    if (submission.status === 'in_progress') return 'in_progress';
    if (submission.status === 'partially_graded') return 'pending_review';
    if (submission.status === 'completed') return countsGrade ? 'completed' : 'practice_complete';
    if (submission.status === 'submitted') return countsGrade ? 'submitted' : 'practice_complete';
    return submission.status || 'submitted';
  }
  return 'not_started';
}


function assignmentHubSection(status, isFirstOpen) {
  if (['retake_practice_available','lesson_review_required','practice_required','retake_needed','practice_again','recovery_needed'].includes(status)) return 'needs_attention';
  if (['in_progress','submitted','pending_review','partially_graded'].includes(status)) return 'in_progress';
  if (['completed','work_completed','practice_complete','graded','auto_graded'].includes(status)) return 'completed';
  return isFirstOpen ? 'continue' : 'available';
}

// Weighted grade calculator
function calculateWeightedGrade(homeworkAvg, quizAvg, testAvg) {
  const hw = homeworkAvg || 0;
  const qz = quizAvg || 0;
  const ts = testAvg || 0;
  return (hw * 0.40) + (qz * 0.30) + (ts * 0.30);
}


function subjectHourBuckets(subject) {
  const s = (subject || '').toLowerCase();
  if (s.includes('math')) return { math: 1 };
  if (s.includes('language') || s.includes('reading') || s.includes('phonics') || s.includes('spelling') || s.includes('ela')) return { ela: 1 };
  if (s.includes('science')) return { science: 1 };
  if (s.includes('social')) return { social: 1 };
  return { elective: 1 };
}

// =============================================
// STEP TYPE SYSTEM + COURSE PROGRESS HELPERS
// =============================================

function getStepType(assignment) {
  const t = String(assignment.assignment_type || '').toLowerCase();
  const w = String(assignment.weight_category || '').toLowerCase();
  const title = String(assignment.title || assignment.assignment_title || '').toLowerCase();

  // Spelling flow
  if (t === 'spelling_list' || title.includes('spelling list') || title.includes('study words') || (/week\s+\d+\s+spelling\s+(practice|words)/i.test(title) && !title.includes('sentence') && !title.includes('missing') && !title.includes('audio') && !title.includes('test'))) return 'study_lesson';
  if (t === 'audio_spelling' || title.includes('audio spelling')) return 'practice_drill';
  if (title.includes('spelling test')) return 'quiz';
  if (title.includes('missing letter')) return 'practice_drill';
  if (title.includes('words in sentence') || title.includes('use words')) return 'practice_drill';

  // Exams
  if (title.includes('final exam') || t === 'final_exam') return 'final_exam';
  if (title.includes('final review') || t === 'final_review') return 'final_review';
  if (title.includes('midterm exam') || t === 'midterm_exam') return 'midterm_exam';
  if (title.includes('midterm review') || t === 'midterm_review') return 'midterm_review';

  // Tests
  if (t === 'unit_test' || title.includes('unit test')) return 'unit_test';
  if (t === 'test' || w === 'test') return 'unit_test';

  // Quizzes
  if (t === 'quiz' || w === 'quiz') return 'quiz';

  // Reviews
  if (t === 'review' || title.includes('review practice') || title.includes('unit review')) return 'review';

  // Study / Video / Vocabulary
  if (t === 'lesson' || t === 'study_lesson' || title.includes('study lesson') || title.includes('learn the topic') || title.includes('teaching lesson')) return 'study_lesson';
  if (t === 'video_lesson' || t === 'video' || title.includes('video lesson')) return 'video_lesson';
  if (t === 'vocabulary' || t === 'vocabulary_drill' || title.includes('vocabulary')) return 'vocabulary_drill';

  // Practice / Drill
  if (['guided_practice','independent_practice','classwork','practice','drill','practice_drill'].includes(t)) return 'practice_drill';
  if (w === 'homework' && !title.includes('homework')) return 'practice_drill';
  if (title.includes('drill')) return 'practice_drill';

  // Activity / Worksheet / Lab
  if (t === 'lab' || title.includes('lab')) return 'lab';
  if (t === 'activity' || t === 'worksheet' || title.includes('worksheet') || title.includes('activity')) return 'activity';

  // Recovery
  if (t === 'recovery' || title.includes('recovery')) return 'recovery';

  // Weekly homework
  if (w === 'homework' || title.includes('homework')) return 'activity';

  // Default
  return 'activity';
}

function countsTowardGrade(stepType) {
  const gradedTypes = ['quiz', 'unit_test', 'midterm_exam', 'final_exam', 'review'];
  return gradedTypes.includes(stepType);
}

function assignmentCountsTowardGrade(assignment) {
  return countsTowardGrade(getStepType(assignment || {}));
}

function stepTypeLabel(stepType) {
  const labels = {
    study_lesson: 'Study Lesson',
    video_lesson: 'Video Lesson',
    vocabulary_drill: 'Vocabulary Drill',
    practice_drill: 'Practice Drill',
    activity: 'Activity',
    lab: 'Lab',
    review: 'Review',
    quiz: 'Quiz',
    unit_test: 'Unit Test',
    midterm_review: 'Midterm Review',
    midterm_exam: 'Midterm Exam',
    final_review: 'Final Review',
    final_exam: 'Final Exam',
    recovery: 'Recovery Work'
  };
  return labels[stepType] || 'Activity';
}

function stepTypeIcon(stepType) {
  const icons = {
    study_lesson: '📖', video_lesson: '🎬', vocabulary_drill: '📝',
    practice_drill: '✏️', activity: '📋', lab: '🔬', review: '🔄',
    quiz: '📊', unit_test: '📑', midterm_review: '🔄', midterm_exam: '📝',
    final_review: '🔄', final_exam: '🏆', recovery: '🔁'
  };
  return icons[stepType] || '📋';
}

function readinessLabel(score, gradeLevel, stepType, options = {}) {
  const g = parseInt(gradeLevel || 5);
  const isYoung = g <= 2;
  if (score === null || score === undefined) return null;
  const pct = parseFloat(score);
  const minWritingScore = options.minWritingScore === undefined || options.minWritingScore === null ? null : parseFloat(options.minWritingScore);
  const hasCopiedPrompt = !!options.hasCopiedPrompt;
  const hasWeakWriting = minWritingScore !== null && minWritingScore < 80;
  const hasVeryWeakWriting = minWritingScore !== null && minWritingScore < 60;

  const practiceStep = ['practice_drill','activity','vocabulary_drill','review'].includes(String(stepType || '').toLowerCase());
  if (hasCopiedPrompt || hasVeryWeakWriting) return practiceStep ? 'Needs More Practice' : (isYoung ? 'Try Again' : 'Needs Attention');
  if (pct >= 90 && !hasWeakWriting) return practiceStep ? 'Completed' : 'Strong Progress';
  if (pct >= 80) return isYoung ? 'Good Progress' : 'Ready to Continue';
  if (pct >= 70) return isYoung ? 'Needs More Practice' : 'Passing';
  return practiceStep ? 'Needs More Practice' : (isYoung ? 'Try Again' : 'Needs Attention');
}

function paceStatus(completedSteps, totalSteps, schoolDaysElapsed, totalSchoolDays) {
  if (totalSteps <= 0 || totalSchoolDays <= 0) return 'On Track';
  const expectedProgress = (schoolDaysElapsed / totalSchoolDays) * totalSteps;
  const ratio = completedSteps / Math.max(1, expectedProgress);
  if (ratio >= 1.15) return 'Ahead';
  if (ratio >= 0.95) return 'On Track';
  if (ratio >= 0.80) return 'Slightly Behind';
  if (ratio >= 0.60) return 'Behind';
  return 'Needs Catch-Up';
}

function calculateSchoolDays(fromDate, toDate) {
  let count = 0;
  const d = new Date(fromDate);
  const end = new Date(toDate);
  while (d <= end) {
    const day = d.getDay();
    if (day !== 0 && day !== 6) count++;
    d.setDate(d.getDate() + 1);
  }
  return count;
}

// Auto-check course completion after grading
async function checkCourseCompletion(studentId, courseId) {
  try {
    // Count total active assignments in the course
    const totalResult = await pool.query(
      `SELECT COUNT(*)::int AS total FROM assignments a
       JOIN lessons l ON a.lesson_id = l.id JOIN units u ON l.unit_id = u.id
       WHERE u.course_id = $1 AND a.is_active = true`, [courseId]);
    const totalSteps = totalResult.rows[0].total;
    if (totalSteps === 0) return null;

    // Count completed assignments for this student
    const completedResult = await pool.query(
      `SELECT COUNT(DISTINCT a.id)::int AS completed FROM assignments a
       JOIN lessons l ON a.lesson_id = l.id JOIN units u ON l.unit_id = u.id
       LEFT JOIN grades g ON g.assignment_id = a.id AND g.student_id = $1
       LEFT JOIN submissions s ON s.assignment_id = a.id AND s.student_id = $1 AND s.status IN ('submitted','graded')
       WHERE u.course_id = $2 AND a.is_active = true AND (g.id IS NOT NULL OR s.id IS NOT NULL)`,
      [studentId, courseId]);
    const completedSteps = completedResult.rows[0].completed;

    if (completedSteps >= totalSteps) {
      // Calculate final grade (graded work only)
      const gradeResult = await pool.query(
        `SELECT AVG(g.percentage) as avg_pct FROM grades g
         JOIN assignments a ON g.assignment_id = a.id
         JOIN lessons l ON a.lesson_id = l.id JOIN units u ON l.unit_id = u.id
         WHERE g.student_id = $1 AND u.course_id = $2
           AND (LOWER(COALESCE(a.weight_category,'')) IN ('quiz','test','exam','project') OR LOWER(COALESCE(a.assignment_type,'')) IN ('quiz','test','unit_test','exam','final_exam','midterm_exam','project','review'))`, [studentId, courseId]);
      const finalGrade = gradeResult.rows[0]?.avg_pct ? parseFloat(gradeResult.rows[0].avg_pct).toFixed(1) : null;
      const finalLetter = finalGrade ? getLetterGrade(parseFloat(finalGrade)) : null;

      // Get course and student info
      const courseInfo = await pool.query('SELECT name, subject, grade_level FROM courses WHERE id = $1', [courseId]);
      const studentInfo = await pool.query('SELECT first_name, last_name, grade_level FROM students WHERE id = $1', [studentId]);

      // Update enrollment to completed
      await pool.query(
        `UPDATE enrollments SET status = 'completed', updated_at = CURRENT_TIMESTAMP WHERE student_id = $1 AND course_id = $2`,
        [studentId, courseId]);

      // Save certificate record (upsert)
      await pool.query(
        `INSERT INTO certificates (student_id, course_id, course_name, student_name, grade_level,
         final_grade_pct, final_grade_letter, completed_date, total_steps, program_name, status)
         VALUES ($1, $2, $3, $4, $5, $6, $7, CURRENT_DATE, $8, 'JNM Home Education Program', 'earned')
         ON CONFLICT (student_id, course_id) DO UPDATE SET
           final_grade_pct = EXCLUDED.final_grade_pct,
           final_grade_letter = EXCLUDED.final_grade_letter,
           completed_date = COALESCE(certificates.completed_date, CURRENT_DATE),
           total_steps = EXCLUDED.total_steps,
           status = 'earned',
           updated_at = CURRENT_TIMESTAMP`,
        [studentId, courseId,
         courseInfo.rows[0]?.name || 'Course',
         (studentInfo.rows[0]?.first_name || '') + ' ' + (studentInfo.rows[0]?.last_name || ''),
         studentInfo.rows[0]?.grade_level || courseInfo.rows[0]?.grade_level || '5',
         finalGrade, finalLetter, totalSteps]);

      return { completed: true, finalGrade, finalLetter, totalSteps, completedSteps };
    }
    return { completed: false, totalSteps, completedSteps };
  } catch (err) {
    console.error('Course completion check error:', err.message);
    return null;
  }
}

// Create certificates table if it doesn't exist (safe to run multiple times)
pool.query(`CREATE TABLE IF NOT EXISTS certificates (
  id SERIAL PRIMARY KEY,
  student_id INTEGER NOT NULL,
  course_id INTEGER NOT NULL,
  course_name VARCHAR(255),
  student_name VARCHAR(255),
  grade_level VARCHAR(10),
  final_grade_pct NUMERIC(5,2),
  final_grade_letter VARCHAR(5),
  completed_date DATE,
  total_steps INTEGER,
  program_name VARCHAR(255) DEFAULT 'JNM Home Education Program',
  status VARCHAR(50) DEFAULT 'earned',
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  UNIQUE(student_id, course_id)
)`).catch(err => console.log('certificates table:', err.message));

// Tracks each student login/logout period separately so attendance can show
// real session time instead of only assignment activity time.
pool.query(`CREATE TABLE IF NOT EXISTS student_sessions (
  id SERIAL PRIMARY KEY,
  student_id INTEGER NOT NULL,
  login_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  logout_at TIMESTAMP NULL,
  last_seen_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  login_date DATE DEFAULT CURRENT_DATE,
  duration_seconds INTEGER DEFAULT 0,
  status VARCHAR(20) DEFAULT 'active',
  ip_address VARCHAR(80),
  user_agent TEXT,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)`).catch(err => console.log('student_sessions table:', err.message));
pool.query(`CREATE INDEX IF NOT EXISTS idx_student_sessions_student_date ON student_sessions(student_id, login_date)`).catch(err => console.log('student_sessions index:', err.message));

async function refreshAttendanceFromSessions(studentId, sessionDate = null) {
  if (!studentId) return null;
  const dateExpr = sessionDate || new Date().toISOString().split('T')[0];
  const owner = await pool.query('SELECT user_id FROM students WHERE id = $1', [studentId]);
  const ownerId = owner.rows[0]?.user_id || null;
  const sessionSummary = await pool.query(`
    SELECT
      COUNT(*)::int AS session_count,
      MIN(login_at)::time AS first_login,
      MAX(COALESCE(logout_at, last_seen_at, login_at))::time AS last_logout,
      COALESCE(SUM(
        CASE
          WHEN logout_at IS NOT NULL THEN GREATEST(0, EXTRACT(EPOCH FROM (logout_at - login_at)))
          ELSE GREATEST(0, EXTRACT(EPOCH FROM (COALESCE(last_seen_at, login_at) - login_at)))
        END
      ), 0)::int AS total_seconds
    FROM student_sessions
    WHERE student_id = $1 AND login_date = $2::date
  `, [studentId, dateExpr]);
  const row = sessionSummary.rows[0] || {};
  if (!row.session_count || row.session_count <= 0) return null;
  const totalHours = parseFloat(((parseInt(row.total_seconds || 0, 10) || 0) / 3600).toFixed(2));
  const result = await pool.query(
    `INSERT INTO attendance (student_id, attendance_date, status, check_in_time, check_out_time, total_hours, notes, recorded_by)
     VALUES ($1, $2::date, 'present', $3, $4, $5, $6, $7)
     ON CONFLICT (student_id, attendance_date) DO UPDATE SET
       status = 'present',
       check_in_time = COALESCE(attendance.check_in_time, EXCLUDED.check_in_time),
       check_out_time = EXCLUDED.check_out_time,
       total_hours = EXCLUDED.total_hours,
       notes = EXCLUDED.notes
     RETURNING *`,
    [studentId, dateExpr, row.first_login, row.last_logout, totalHours, `Auto-recorded from ${row.session_count} student login session${row.session_count === 1 ? '' : 's'}`, ownerId]
  );
  return { attendance: result.rows[0], session_count: row.session_count, total_seconds: row.total_seconds };
}

async function startStudentSession(req, studentId) {
  const result = await pool.query(
    `INSERT INTO student_sessions (student_id, ip_address, user_agent)
     VALUES ($1, $2, $3)
     RETURNING id, student_id, login_date`,
    [studentId, req.ip || null, req.headers['user-agent'] || null]
  );
  await refreshAttendanceFromSessions(studentId, result.rows[0].login_date);
  return result.rows[0];
}

async function endStudentSession(req, explicitSessionId = null) {
  if (!req.user || req.user.role !== 'student') return null;
  const studentId = req.user.id;
  const sessionId = explicitSessionId || req.user.session_id || null;
  let result;
  if (sessionId) {
    result = await pool.query(
      `UPDATE student_sessions
       SET logout_at = COALESCE(logout_at, CURRENT_TIMESTAMP),
           last_seen_at = CURRENT_TIMESTAMP,
           duration_seconds = GREATEST(0, EXTRACT(EPOCH FROM (COALESCE(logout_at, CURRENT_TIMESTAMP) - login_at)))::int,
           status = 'closed',
           updated_at = CURRENT_TIMESTAMP
       WHERE id = $1 AND student_id = $2
       RETURNING id, student_id, login_date, duration_seconds`,
      [sessionId, studentId]
    );
  }
  if (!result || result.rows.length === 0) {
    result = await pool.query(
      `UPDATE student_sessions
       SET logout_at = COALESCE(logout_at, CURRENT_TIMESTAMP),
           last_seen_at = CURRENT_TIMESTAMP,
           duration_seconds = GREATEST(0, EXTRACT(EPOCH FROM (COALESCE(logout_at, CURRENT_TIMESTAMP) - login_at)))::int,
           status = 'closed',
           updated_at = CURRENT_TIMESTAMP
       WHERE id = (
         SELECT id FROM student_sessions
         WHERE student_id = $1 AND status = 'active'
         ORDER BY login_at DESC
         LIMIT 1
       )
       RETURNING id, student_id, login_date, duration_seconds`,
      [studentId]
    );
  }
  if (result.rows.length > 0) {
    await refreshAttendanceFromSessions(studentId, result.rows[0].login_date);
    return result.rows[0];
  }
  return null;
}

async function recordLearningActivity({ studentId, courseId = null, lessonId = null, assignmentId = null, activeSeconds = 0, status = 'completed' }) {
  const seconds = Math.max(0, parseInt(activeSeconds || 0));
  if (!studentId || seconds <= 0) return;
  const trackedSeconds = Math.max(60, seconds);
  const hours = parseFloat((trackedSeconds / 3600).toFixed(2));
  try {
    let courseSubject = null;
    if (courseId) {
      const c = await pool.query('SELECT subject FROM courses WHERE id = $1', [courseId]);
      courseSubject = c.rows[0]?.subject || null;
    }
    if (!courseSubject && lessonId) {
      const c = await pool.query('SELECT c.id, c.subject FROM lessons l JOIN units u ON l.unit_id = u.id JOIN courses c ON u.course_id = c.id WHERE l.id = $1', [lessonId]);
      if (c.rows[0]) { courseId = courseId || c.rows[0].id; courseSubject = c.rows[0].subject; }
    }
    if (!courseSubject && assignmentId) {
      const c = await pool.query('SELECT c.id, c.subject, l.id as lesson_id FROM assignments a LEFT JOIN lessons l ON a.lesson_id = l.id LEFT JOIN units u ON l.unit_id = u.id LEFT JOIN courses c ON u.course_id = c.id WHERE a.id = $1', [assignmentId]);
      if (c.rows[0]) { courseId = courseId || c.rows[0].id; lessonId = lessonId || c.rows[0].lesson_id; courseSubject = c.rows[0].subject; }
    }

    await pool.query(
      `INSERT INTO time_logs (student_id, course_id, lesson_id, assignment_id, active_seconds, end_time, status)
       VALUES ($1,$2,$3,$4,$5, CURRENT_TIMESTAMP, $6)`,
      [studentId, courseId || null, lessonId || null, assignmentId || null, trackedSeconds, status || 'completed']
    );

    const owner = await pool.query('SELECT user_id FROM students WHERE id = $1', [studentId]);
    const ownerId = owner.rows[0]?.user_id || null;
    const b = subjectHourBuckets(courseSubject);
    const math = b.math ? hours : 0;
    const ela = b.ela ? hours : 0;
    const science = b.science ? hours : 0;
    const social = b.social ? hours : 0;
    const elective = b.elective ? hours : 0;
    const subjectText = courseSubject || 'Learning Activity';

    await pool.query(
      `INSERT INTO daily_logs (student_id, log_date, total_hours, math_hours, ela_hours, science_hours, social_studies_hours, elective_hours, subjects_covered, notes, day_status, logged_by)
       VALUES ($1, CURRENT_DATE, $2, $3, $4, $5, $6, $7, $8, $9, 'school_day', $10)
       ON CONFLICT (student_id, log_date) DO UPDATE SET
         total_hours = COALESCE(daily_logs.total_hours,0) + EXCLUDED.total_hours,
         math_hours = COALESCE(daily_logs.math_hours,0) + EXCLUDED.math_hours,
         ela_hours = COALESCE(daily_logs.ela_hours,0) + EXCLUDED.ela_hours,
         science_hours = COALESCE(daily_logs.science_hours,0) + EXCLUDED.science_hours,
         social_studies_hours = COALESCE(daily_logs.social_studies_hours,0) + EXCLUDED.social_studies_hours,
         elective_hours = COALESCE(daily_logs.elective_hours,0) + EXCLUDED.elective_hours,
         subjects_covered = EXCLUDED.subjects_covered,
         notes = EXCLUDED.notes,
         day_status = 'school_day',
         updated_at = CURRENT_TIMESTAMP`,
      [studentId, hours, math, ela, science, social, elective, subjectText, 'Auto-recorded from student learning activity', ownerId]
    );

    await pool.query(
      `INSERT INTO attendance (student_id, attendance_date, status, check_in_time, check_out_time, total_hours, notes, recorded_by)
       VALUES ($1, CURRENT_DATE, 'present', CURRENT_TIME, CURRENT_TIME, 0, $2, $3)
       ON CONFLICT (student_id, attendance_date) DO UPDATE SET
         status = 'present',
         check_in_time = COALESCE(attendance.check_in_time, EXCLUDED.check_in_time),
         check_out_time = EXCLUDED.check_out_time,
         total_hours = COALESCE(attendance.total_hours,0),
         notes = CASE WHEN attendance.notes LIKE 'Auto-recorded from % student login session%' THEN attendance.notes ELSE EXCLUDED.notes END`,
      [studentId, 'Auto-marked present from student learning activity', ownerId]
    );
  } catch (e) {
    console.error('recordLearningActivity error:', e.message);
  }
}


async function resolveActivityStudentId(req, requestedStudentId) {
  const requested = parseInt(requestedStudentId, 10);
  if (req.user.role === 'student') return parseInt(req.user.id, 10);
  if (!requested) throw new Error('student_id required');
  const owned = await pool.query('SELECT id FROM students WHERE id = $1 AND user_id = $2', [requested, req.user.id]);
  if (owned.rows.length === 0) throw new Error('Student not found or not authorized');
  return requested;
}

// Test database connection
pool.query('SELECT NOW()', (err, res) => {
  if (err) console.error('Database connection error:', err.message);
  else console.log('Database connected:', res.rows[0].now);
});

// =============================================
// OPTIONAL BOOTSTRAP PARENT ACCOUNT
// =============================================
// Production/staging never creates a public test login automatically. An
// operator may provide both bootstrap variables for the very first account,
// or the parent can use the private registration screen.
async function ensureParentAccount() {
  try {
    const email = String(process.env.JNM_BOOTSTRAP_PARENT_EMAIL || '').trim().toLowerCase();
    const password = String(process.env.JNM_BOOTSTRAP_PARENT_PASSWORD || '');
    if (!email || !password) return;
    if (password.length < 8) throw new Error('JNM_BOOTSTRAP_PARENT_PASSWORD must be at least 8 characters.');
    const hash = await bcrypt.hash(password, 10);
    const existing = await pool.query('SELECT id, password_hash FROM users WHERE email = $1', [email]);
    if (existing.rows.length === 0) {
      await pool.query(
        "INSERT INTO users (email, password_hash, first_name, last_name, role) VALUES ($1, $2, 'JNM', 'Parent', 'parent')",
        [email, hash]
      );
      console.log(`Created bootstrap parent account: ${email}`);
    } else if (existing.rows[0].password_hash === 'PENDING_BACKEND_HASH' || !existing.rows[0].password_hash) {
      await pool.query('UPDATE users SET password_hash = $1, role = $2 WHERE email = $3', [hash, 'parent', email]);
      console.log(`Set password for bootstrap parent account: ${email}`);
    }
  } catch (e) {
    console.error('ensureParentAccount error:', e.message);
  }
}

// Preview students were useful while building Grades K-5, but they must never
// be counted as real family students. Preserve their history and archive them.
async function archiveLegacyPreviewStudents() {
  const previewIds = ['KDG-001','G1-001','G2-001','G3-001','G4-001','G5-001'];
  const result = await pool.query(
    `UPDATE students SET status='inactive', updated_at=CURRENT_TIMESTAMP
     WHERE school_id = ANY($1::text[]) AND lower(COALESCE(status,'active')) <> 'inactive'
     RETURNING id, school_id, first_name, last_name`,
    [previewIds]
  );
  if (result.rowCount) console.log(`Archived ${result.rowCount} legacy preview student record(s).`);
}

async function ensureSharedFamilyDatabase() {
  const v34 = fs.readFileSync(path.join(__dirname, 'v34-launch-sync.sql'), 'utf8');
  const v35 = fs.readFileSync(path.join(__dirname, 'v35-backend-test-gate.sql'), 'utf8')
    .replace(/^\\i\s+v34-launch-sync\.sql\s*$/gmi, '');
  const sql = `${v34}\n${v35}`.replace(/^\s*BEGIN;\s*$/gmi, '').replace(/^\s*COMMIT;\s*$/gmi, '');
  await pool.query(sql);
  console.log('Shared parent/student database tables are ready.');
}

const startupReady = ensureParentAccount()
  .then(ensureCurriculumFoundation)
  .then(archiveLegacyPreviewStudents)
  .then(cleanupKindergartenArtMusicCategory)
  .then(resetKindergartenProgressForCurrentBuildOnce)
  .then(ensureSharedFamilyDatabase);



// =============================================
// DEVELOPMENT RESET: CLEAR OLD KINDERGARTEN TEST PROGRESS ONCE
// =============================================
// This is for the current local build process. It clears old Kindergarten test/demo
// records so the Kindergarten student starts clean after the preview-mode fix.
// It runs only once per database using a maintenance flag. Real student work done
// after this reset will stay saved normally.
async function resetKindergartenProgressForCurrentBuildOnce() {
  const flagKey = 'kindergarten_progress_reset_v7_category_attendance_clean_start';
  try {
    await pool.query(`CREATE TABLE IF NOT EXISTS maintenance_flags (
      flag_key TEXT PRIMARY KEY,
      flag_value TEXT,
      created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
    )`);
    const flag = await pool.query('SELECT flag_key FROM maintenance_flags WHERE flag_key = $1', [flagKey]);
    if (flag.rows.length > 0) return;
    const result = await resetKindergartenProgressRecords();
    await pool.query('INSERT INTO maintenance_flags (flag_key, flag_value) VALUES ($1, $2) ON CONFLICT (flag_key) DO NOTHING', [flagKey, JSON.stringify(result)]);
    console.log(`Kindergarten progress reset complete: ${JSON.stringify(result)}`);
  } catch (e) {
    console.error('resetKindergartenProgressForCurrentBuildOnce error:', e.message);
  }
}

async function resetKindergartenProgressRecords() {
  const kgStudents = await pool.query('SELECT id FROM students WHERE grade_level = 0');
  const studentIds = kgStudents.rows.map(r => r.id);
  if (studentIds.length === 0) return { kindergarten_students: 0, cleared_tables: [] };
  const cleared = [];
  async function clearTable(tableName, sql) {
    try {
      const r = await pool.query(sql);
      cleared.push({ table: tableName, rows: r.rowCount || 0 });
    } catch (e) {
      // Some local databases may not have every table yet. Skip missing optional tables.
      if (!/does not exist/i.test(e.message || '')) console.warn(`Skipping ${tableName}: ${e.message}`);
    }
  }
  const whereKgStudent = 'student_id IN (SELECT id FROM students WHERE grade_level = 0)';
  await clearTable('grades', `DELETE FROM grades WHERE ${whereKgStudent}`);
  await clearTable('submissions', `DELETE FROM submissions WHERE ${whereKgStudent}`);
  await clearTable('skill_progress', `DELETE FROM skill_progress WHERE ${whereKgStudent}`);
  await clearTable('time_logs', `DELETE FROM time_logs WHERE ${whereKgStudent}`);
  await clearTable('daily_logs', `DELETE FROM daily_logs WHERE ${whereKgStudent}`);
  await clearTable('attendance', `DELETE FROM attendance WHERE ${whereKgStudent}`);
  await clearTable('student_sessions', `DELETE FROM student_sessions WHERE ${whereKgStudent}`);
  await clearTable('certificates', `DELETE FROM certificates WHERE ${whereKgStudent}`);
  await clearTable('enrollments', `UPDATE enrollments SET status = 'active', updated_at = CURRENT_TIMESTAMP WHERE ${whereKgStudent} AND status = 'completed'`);
  return { kindergarten_students: studentIds.length, cleared_tables: cleared };
}


async function cleanupKindergartenArtMusicCategory() {
  try {
    // The combined Kindergarten Art & Music course has been retired.
    // Its art lessons now live under Art Fundamentals and its rhythm/sound lessons under Music Fundamentals.
    // Keep the old rows for safety, but hide them from active course lists and remove active enrollments.
    const oldCourses = await pool.query(`
      SELECT id FROM courses
      WHERE grade_level = 0
        AND (LOWER(name) LIKE 'art & music%' OR LOWER(subject) = 'art & music')
    `).catch(() => ({ rows: [] }));
    if (!oldCourses.rows.length) return { old_courses: 0 };

    const artCourse = await pool.query(`SELECT id FROM courses WHERE grade_level = 0 AND LOWER(name) LIKE 'art fundamentals%' AND is_active = true LIMIT 1`).catch(() => ({ rows: [] }));
    const musicCourse = await pool.query(`SELECT id FROM courses WHERE grade_level = 0 AND LOWER(name) LIKE 'music fundamentals%' AND is_active = true LIMIT 1`).catch(() => ({ rows: [] }));

    for (const old of oldCourses.rows) {
      const oldId = old.id;
      const enrolledStudents = await pool.query(`SELECT student_id FROM enrollments WHERE course_id = $1 AND status = 'active'`, [oldId]).catch(() => ({ rows: [] }));
      await pool.query(`UPDATE enrollments SET status = 'inactive', updated_at = CURRENT_TIMESTAMP WHERE course_id = $1`, [oldId]).catch(() => {});
      await updateFlexible('courses', oldId, { is_active: false, description: 'Merged into Art Fundamentals and Music Fundamentals for Kindergarten.' }).catch(() => {});
      for (const row of enrolledStudents.rows) {
        if (artCourse.rows[0]?.id) await safeEnrollStudentInCourse(row.student_id, artCourse.rows[0].id).catch(() => {});
        if (musicCourse.rows[0]?.id) await safeEnrollStudentInCourse(row.student_id, musicCourse.rows[0].id).catch(() => {});
      }
    }
    return { old_courses: oldCourses.rows.length };
  } catch (e) {
    console.error('cleanupKindergartenArtMusicCategory error:', e.message);
    return { error: e.message };
  }
}



function earlyElementaryCanonicalCoursePlansForGrade(grade) {
  const g = parseInt(grade, 10);
  if (![1, 2].includes(g)) return [];
  return earlyElementaryPlans(g).map(plan => ({
    name: plan.name,
    subject: plan.subject,
    course_type: plan.course_type,
    description: plan.description,
    credit_hours: plan.course_type === 'core' ? 1.0 : 0.5,
    key: plan.key
  }));
}

function earlyElementaryCanonicalCourseNames(grade) {
  return new Set(earlyElementaryCanonicalCoursePlansForGrade(grade).map(p => String(p.name || '').toLowerCase()));
}

function filterEarlyElementaryCourseRows(rows, grade) {
  const g = parseInt(grade, 10);
  if (![1, 2].includes(g)) return rows;
  const allowed = earlyElementaryCanonicalCourseNames(g);
  const seen = new Set();
  return (rows || []).filter(row => {
    const name = String(row?.name || '').toLowerCase();
    if (!allowed.has(name)) return false;
    if (seen.has(name)) return false;
    seen.add(name);
    return true;
  });
}

async function cleanupEarlyElementaryDuplicateCourses() {
  try {
    const summary = [];
    for (const grade of [1, 2]) {
      const canonicalPlans = earlyElementaryCanonicalCoursePlansForGrade(grade);
      const canonicalNames = new Set(canonicalPlans.map(p => p.name.toLowerCase()));
      const keepByName = new Map();

      for (const plan of canonicalPlans) {
        const existing = await pool.query(
          `SELECT c.*,
                  (SELECT COUNT(*) FROM units u WHERE u.course_id = c.id AND COALESCE(u.is_active, true) = true) AS unit_count,
                  (SELECT COUNT(*) FROM lessons l JOIN units u ON l.unit_id = u.id WHERE u.course_id = c.id AND COALESCE(l.is_active, true) = true) AS lesson_count,
                  (SELECT COUNT(*) FROM enrollments e WHERE e.course_id = c.id AND e.status = 'active') AS active_enrollments
             FROM courses c
            WHERE c.grade_level = $1 AND LOWER(c.name) = LOWER($2)
            ORDER BY
              CASE WHEN COALESCE(c.description,'') = $3 THEN 0 ELSE 1 END,
              CASE WHEN COALESCE(c.subject,'') = $4 THEN 0 ELSE 1 END,
              COALESCE(c.is_active, true) DESC,
              lesson_count DESC,
              unit_count DESC,
              active_enrollments DESC,
              c.id ASC`,
          [grade, plan.name, plan.description, plan.subject]
        ).catch(() => ({ rows: [] }));

        if (!existing.rows.length) continue;
        const keep = existing.rows[0];
        keepByName.set(plan.name.toLowerCase(), keep.id);
        await updateFlexible('courses', keep.id, {
          name: plan.name,
          subject: plan.subject,
          course_type: plan.course_type,
          description: plan.description,
          credit_hours: plan.credit_hours,
          is_active: true
        }).catch(() => {});

        for (const duplicate of existing.rows.slice(1)) {
          await pool.query(`UPDATE enrollments SET status = 'inactive', updated_at = CURRENT_TIMESTAMP WHERE course_id = $1`, [duplicate.id]).catch(() => {});
          await updateFlexible('courses', duplicate.id, { is_active: false, description: `Retired duplicate. Use ${plan.name}.` }).catch(() => {});
          summary.push({ grade, retired_duplicate_course: duplicate.name, kept: plan.name });
        }
      }

      const activeCourses = await pool.query(`SELECT * FROM courses WHERE grade_level = $1 AND COALESCE(is_active, true) = true`, [grade]).catch(() => ({ rows: [] }));
      for (const course of activeCourses.rows) {
        const lowerName = String(course.name || '').toLowerCase();
        if (canonicalNames.has(lowerName) && keepByName.get(lowerName) === course.id) continue;
        // Grades 1 and 2 currently use the locked 8-course K-2 structure.
        // Retire older seeded shells such as Language Arts, Mathematics, Science, Spelling, or duplicate elective shells.
        await pool.query(`UPDATE enrollments SET status = 'inactive', updated_at = CURRENT_TIMESTAMP WHERE course_id = $1`, [course.id]).catch(() => {});
        await updateFlexible('courses', course.id, { is_active: false, description: 'Retired from Grade 1-2 active course list after K-2 structure cleanup.' }).catch(() => {});
        summary.push({ grade, retired_legacy_course: course.name });
      }

      await pool.query(`UPDATE enrollments e SET status = 'inactive', updated_at = CURRENT_TIMESTAMP
        FROM courses c, students st
        WHERE e.course_id = c.id AND e.student_id = st.id
          AND st.grade_level = $1
          AND (COALESCE(c.is_active, true) = false OR LOWER(c.name) <> ALL($2::text[]))`,
        [grade, Array.from(canonicalNames)]).catch(() => {});
      const students = await pool.query('SELECT id FROM students WHERE grade_level = $1 AND status = $2', [grade, 'active']).catch(() => ({ rows: [] }));
      for (const st of students.rows) {
        for (const keepId of keepByName.values()) {
          await safeEnrollStudentInCourse(st.id, keepId).catch(() => {});
        }
      }
    }
    return { retired: summary.length, details: summary.slice(0, 50) };
  } catch (e) {
    console.error('cleanupEarlyElementaryDuplicateCourses error:', e.message);
    return { error: e.message };
  }
}

// =============================================
// CURRICULUM FOUNDATION SEED + RESOURCE HELPERS
// =============================================
// This is a safe, idempotent seed layer for visible curriculum prototypes.
// It fills empty elective/health courses with grade-appropriate guided lessons
// without rebuilding the app or changing existing working features.
let __columnCache = {};
let __columnTypeCache = {};
async function getColumnInfo(tableName) {
  if (__columnCache[tableName] && __columnTypeCache[tableName]) return { cols: __columnCache[tableName], types: __columnTypeCache[tableName] };
  const result = await pool.query(
    `SELECT column_name, data_type, udt_name FROM information_schema.columns WHERE table_schema = 'public' AND table_name = $1`,
    [tableName]
  );
  const cols = new Set(result.rows.map(r => r.column_name));
  const types = {};
  result.rows.forEach(r => { types[r.column_name] = (r.udt_name || r.data_type || '').toLowerCase(); });
  __columnCache[tableName] = cols;
  __columnTypeCache[tableName] = types;
  return { cols, types };
}
async function getColumns(tableName) {
  const info = await getColumnInfo(tableName);
  return info.cols;
}
function isJsonColumn(types, key) {
  return ['json', 'jsonb'].includes((types[key] || '').toLowerCase());
}
function valueForColumn(v, key, types) {
  if (v === undefined) return v;
  if (v === null) return null;

  // PostgreSQL json/jsonb columns require valid JSON text. In some local
  // databases, fields like learning_objectives or activity_data may be jsonb,
  // while in others they are text. This protects both schemas.
  if (isJsonColumn(types, key)) {
    if (typeof v === 'string') {
      try {
        JSON.parse(v);
        return v;
      } catch {
        return JSON.stringify(v);
      }
    }
    return JSON.stringify(v);
  }

  if (Array.isArray(v) || (v && typeof v === 'object' && !(v instanceof Date))) return JSON.stringify(v);
  return v;
}
async function insertFlexible(tableName, data) {
  const { cols, types } = await getColumnInfo(tableName);
  const keys = Object.keys(data).filter(k => cols.has(k) && data[k] !== undefined);
  if (keys.length === 0) throw new Error(`No insertable columns for ${tableName}`);
  const values = keys.map(k => valueForColumn(data[k], k, types));
  const placeholders = keys.map((_, i) => `$${i + 1}`).join(', ');
  const query = `INSERT INTO ${tableName} (${keys.join(', ')}) VALUES (${placeholders}) RETURNING *`;
  const result = await pool.query(query, values);
  return result.rows[0];
}
async function updateFlexible(tableName, id, data) {
  const { cols, types } = await getColumnInfo(tableName);
  const keys = Object.keys(data).filter(k => cols.has(k) && data[k] !== undefined);
  if (keys.length === 0) return null;
  const values = keys.map(k => valueForColumn(data[k], k, types));
  const assignments = keys.map((k, i) => `${k} = $${i + 1}`).join(', ');
  const result = await pool.query(`UPDATE ${tableName} SET ${assignments}${cols.has('updated_at') ? ', updated_at = CURRENT_TIMESTAMP' : ''} WHERE id = $${keys.length + 1} RETURNING *`, [...values, id]);
  return result.rows[0] || null;
}
async function ensureCourse({ name, grade_level, subject, course_type = 'elective', description, credit_hours = 1.0 }) {
  const existing = await pool.query(
    `SELECT * FROM courses WHERE LOWER(name) = LOWER($1) AND grade_level = $2 LIMIT 1`,
    [name, grade_level]
  );
  if (existing.rows[0]) {
    await updateFlexible('courses', existing.rows[0].id, { description, subject, course_type, credit_hours, is_active: true });
    return { ...existing.rows[0], description, subject, course_type };
  }
  return await insertFlexible('courses', { name, grade_level, subject, course_type, description, credit_hours, is_active: true });
}
async function ensureUnit(courseId, unitNumber, title, description) {
  const existing = await pool.query(`SELECT * FROM units WHERE course_id = $1 AND unit_number = $2 LIMIT 1`, [courseId, unitNumber]);
  if (existing.rows[0]) {
    await updateFlexible('units', existing.rows[0].id, { title, description, is_active: true });
    return { ...existing.rows[0], title, description };
  }
  return await insertFlexible('units', { course_id: courseId, unit_number: unitNumber, title, description, is_active: true });
}
async function ensureLesson(unitId, lessonNumber, data) {
  const existing = await pool.query(`SELECT * FROM lessons WHERE unit_id = $1 AND lesson_number = $2 LIMIT 1`, [unitId, lessonNumber]);
  const base = { ...data, unit_id: unitId, lesson_number: lessonNumber, is_active: true };
  if (existing.rows[0]) {
    await updateFlexible('lessons', existing.rows[0].id, base);
    return { ...existing.rows[0], ...base };
  }
  return await insertFlexible('lessons', base);
}
async function ensureAssignment(lessonId, data) {
  const existing = await pool.query(`SELECT * FROM assignments WHERE lesson_id = $1 AND LOWER(title) = LOWER($2) LIMIT 1`, [lessonId, data.title]);
  const base = { lesson_id: lessonId, is_active: true, passing_grade: 70, ...data };
  if (existing.rows[0]) {
    await updateFlexible('assignments', existing.rows[0].id, base);
    return { ...existing.rows[0], ...base };
  }
  return await insertFlexible('assignments', base);
}

// Safer assignment seed helper. Some local databases may be missing optional
// curriculum columns, so this tries the rich version first and then falls back
// to the minimum fields needed for a visible guided-player assignment.
async function safeEnsureAssignment(lessonId, data, label = 'assignment') {
  try {
    return await ensureAssignment(lessonId, data);
  } catch (err) {
    console.error(`safeEnsureAssignment rich insert failed for ${label}:`, err.message);
    const fallback = {
      title: data.title,
      description: data.description || data.instructions || data.title,
      instructions: data.instructions || data.description || 'Complete this guided activity.',
      assignment_type: data.assignment_type || 'activity',
      weight_category: data.weight_category || 'homework',
      point_value: data.point_value === undefined ? 0 : data.point_value,
      questions: data.questions || []
    };
    try {
      return await ensureAssignment(lessonId, fallback);
    } catch (fallbackErr) {
      console.error(`safeEnsureAssignment fallback failed for ${label}:`, fallbackErr.message);
      return null;
    }
  }
}

async function safeEnrollStudentInCourse(studentId, courseId) {
  try {
    await pool.query("INSERT INTO enrollments (student_id, course_id, status) VALUES ($1, $2, 'active') ON CONFLICT (student_id, course_id) DO UPDATE SET status = 'active'", [studentId, courseId]);
  } catch (err) {
    // Some older databases may not have the unique constraint needed for ON CONFLICT.
    // Fall back to a manual upsert so seeding units/assignments never fails here.
    try {
      const existing = await pool.query('SELECT id FROM enrollments WHERE student_id = $1 AND course_id = $2 LIMIT 1', [studentId, courseId]);
      if (existing.rows[0]) await pool.query("UPDATE enrollments SET status = 'active' WHERE id = $1", [existing.rows[0].id]);
      else await pool.query("INSERT INTO enrollments (student_id, course_id, status) VALUES ($1, $2, 'active')", [studentId, courseId]);
    } catch (fallbackErr) {
      console.error('safeEnrollStudentInCourse failed:', fallbackErr.message);
    }
  }
}
function mc(id, question, correct, distractors) {
  return q(id, 'multiple_choice', question, rotateOptions(correct, distractors, id), correct);
}
function wr(id, question) { return q(id, 'written_response', question, null, null); }


function gradeFiveElectiveKind(row) {
  const text = String([row?.subject, row?.course_subject, row?.course_name, row?.name].filter(Boolean).join(' ')).toLowerCase();
  if (text.includes('art')) return 'art';
  if (text.includes('music')) return 'music';
  if (text.includes('physical') || text === 'pe' || text.includes('fitness')) return 'pe';
  if (text.includes('health') || text.includes('nutrition')) return 'health';
  return null;
}

function gradeFiveElectiveQuestions(kind, lessonTitle, assignmentTitle, isQuiz) {
  const lesson = String(lessonTitle || '').toLowerCase();
  const title = String(assignmentTitle || '').toLowerCase();
  const text = `${lesson} ${title}`;

  if (kind === 'art') {
    if (text.includes('line') || text.includes('shape') || text.includes('pattern')) {
      return isQuiz ? [
        mc(1, 'Which answer best describes a line in art?', 'A mark that moves from one point to another.', ['A closed space only.', 'Only the color blue.', 'A finished frame.']),
        mc(2, 'Which answer best describes a shape?', 'A closed space such as a circle, square, or triangle.', ['A mark that never closes.', 'Only a musical sound.', 'A type of exercise.']),
        mc(3, 'Why do artists repeat lines, shapes, or colors?', 'To create a pattern and help the viewer’s eye move through the artwork.', ['To remove all design from the page.', 'To make the artwork impossible to see.', 'To avoid using any choices.']),
        wr(4, 'Explain how a student could use lines and shapes to create a pattern in an artwork.')
      ] : [
        wr(1, 'Describe the line-and-shape artwork you created. Include at least two types of lines or shapes you used.'),
        mc(2, 'Which choice would create a pattern?', 'Repeating triangles and zigzag lines across the page.', ['Drawing one dot and stopping.', 'Erasing every shape.', 'Writing only your name.']),
        wr(3, 'What is one design choice you made, and why did you choose it?')
      ];
    }
    if (text.includes('color wheel') || text.includes('color')) {
      return isQuiz ? [
        mc(1, 'What does a color wheel help artists understand?', 'How colors are organized and how they can work together.', ['How to measure running speed.', 'How to sort instrument families.', 'How to count money.']),
        mc(2, 'Which set includes primary colors?', 'Red, yellow, and blue.', ['Green, orange, and purple.', 'Black, gray, and white.', 'Pink, brown, and gold.']),
        mc(3, 'What happens when two primary colors are mixed?', 'They can make a secondary color.', ['They always disappear.', 'They turn into a shape.', 'They become a rhythm.']),
        wr(4, 'Explain one color choice you would make for an artwork and why it fits your idea.')
      ] : [
        wr(1, 'Describe the colors you used in your color wheel or color activity.'),
        mc(2, 'Which pair can make orange?', 'Red and yellow.', ['Blue and green.', 'Purple and black.', 'White and gray.']),
        wr(3, 'Choose two colors from your activity and explain how they look together.')
      ];
    }
    if (text.includes('observation')) {
      return isQuiz ? [
        mc(1, 'What is observational drawing?', 'Drawing by carefully looking at a real object.', ['Drawing without looking at anything.', 'Only coloring a printed page.', 'Writing a music pattern.']),
        mc(2, 'Why should an artist look back and forth between the object and the paper?', 'To notice shapes, lines, and details more accurately.', ['To avoid seeing details.', 'To make every object imaginary.', 'To finish without thinking.']),
        mc(3, 'Which detail would help an observational drawing?', 'The object’s outline, size, texture, and shadows.', ['Only the object’s name.', 'The weather next week.', 'A random quiz score.']),
        wr(4, 'Explain one strategy that can help you draw an object from observation.')
      ] : [
        wr(1, 'Describe the object you observed and drew. Include at least two details you noticed.'),
        mc(2, 'Which is the best first step for observational drawing?', 'Look closely at the object’s big shapes.', ['Close your eyes the whole time.', 'Draw a different object instead.', 'Erase all guide lines immediately.']),
        wr(3, 'What detail was easiest or hardest to include in your drawing?')
      ];
    }
    if (text.includes('texture')) {
      return isQuiz ? [
        mc(1, 'What is texture in art?', 'How a surface looks or feels, such as rough, smooth, bumpy, or soft.', ['How loud a song is.', 'How fast someone runs.', 'Only the title of a book.']),
        mc(2, 'What is a texture rubbing?', 'Placing paper over a surface and rubbing with crayon or pencil to capture the texture.', ['Pouring water on a drawing.', 'Cutting every paper into tiny pieces.', 'Watching a video without creating anything.']),
        mc(3, 'Which surface would probably make an interesting rubbing?', 'A leaf, coin, brick, or textured fabric.', ['A blank screen only.', 'Empty air.', 'A sound recording.']),
        wr(4, 'Explain how texture can make artwork more interesting.')
      ] : [
        wr(1, 'Describe two textures you found during your texture rubbing project.'),
        mc(2, 'Which word describes texture?', 'Rough', ['Silent', 'Tomorrow', 'Multiply']),
        wr(3, 'How did your rubbing change when you used a different surface?')
      ];
    }
    if (text.includes('abstract') || text.includes('realistic')) {
      return isQuiz ? [
        mc(1, 'What is realistic art?', 'Art that tries to show people, places, or objects as they look in real life.', ['Art that only uses numbers.', 'Art that cannot include details.', 'Art that is always invisible.']),
        mc(2, 'What is abstract art?', 'Art that uses shapes, colors, lines, or forms without needing to look exactly real.', ['A perfect photograph only.', 'A list of spelling words.', 'A fitness exercise.']),
        mc(3, 'How can shape and color help abstract art?', 'They can show movement, mood, and design choices.', ['They remove all meaning.', 'They make art impossible.', 'They only count as science facts.']),
        wr(4, 'Compare realistic and abstract art using one example of each.')
      ] : [
        wr(1, 'Describe your abstract shape artwork. Include the shapes and colors you used.'),
        mc(2, 'Which choice sounds most like abstract art?', 'A design made with bold shapes, lines, and colors.', ['A map copied exactly from a textbook.', 'A blank math worksheet.', 'A list of chores only.']),
        wr(3, 'What mood or idea did you want your abstract artwork to show?')
      ];
    }
    if (text.includes('portfolio') || text.includes('reflection')) {
      return isQuiz ? [
        mc(1, 'What is an art portfolio?', 'A collection of artwork that shows growth, practice, and choices over time.', ['A single erased paper.', 'A list of lunch choices.', 'A type of weather report.']),
        mc(2, 'Why do artists reflect on their work?', 'To think about what worked well and what they can improve next time.', ['To pretend they never made choices.', 'To avoid learning from practice.', 'To remove every artwork.']),
        mc(3, 'Which reflection comment is the strongest?', 'I used overlapping shapes to create depth, and next time I would add more contrast.', ['It is good.', 'I did it.', 'Art exists.']),
        wr(4, 'Write a portfolio reflection that names one strength and one thing you would improve.')
      ] : [
        wr(1, 'Choose one artwork from this unit and describe what you are proud of.'),
        mc(2, 'Which detail belongs in an art reflection?', 'A choice you made and why you made it.', ['Only today’s date.', 'A random unrelated fact.', 'A math answer with no art detail.']),
        wr(3, 'What would you change or try next time in your artwork?')
      ];
    }
  }

  if (kind === 'music') {
    if (text.includes('instrument')) {
      return isQuiz ? [
        mc(1, 'Which instrument family makes sound when strings vibrate?', 'Strings', ['Brass', 'Percussion', 'Woodwinds']),
        mc(2, 'Which family includes drums, cymbals, and tambourines?', 'Percussion', ['Strings', 'Brass', 'Woodwinds']),
        mc(3, 'A trumpet makes sound when a player buzzes their lips and blows air. Which family is it in?', 'Brass', ['Strings', 'Percussion', 'Woodwinds']),
        wr(4, 'Pick one instrument family and describe how it makes sound.')
      ] : [
        mc(1, 'Which instrument family uses vibrating strings?', 'Strings', ['Brass', 'Percussion', 'Woodwinds']),
        mc(2, 'Which family often makes sound when instruments are hit, shaken, or scraped?', 'Percussion', ['Strings', 'Brass', 'Woodwinds']),
        wr(3, 'Choose one instrument family and explain one clue that helps you identify it by sound.')
      ];
    }
    if (text.includes('beat') || text.includes('clap')) {
      return isQuiz ? [
        mc(1, 'What is a steady beat?', 'The regular pulse that stays even in music.', ['A random sound with no pattern.', 'Only the loudest note.', 'The name of an instrument family.']),
        mc(2, 'How is rhythm different from beat?', 'Rhythm is the pattern of sounds and silences that can change while the beat stays steady.', ['Rhythm never changes.', 'Beat is always a drawing.', 'Rhythm means running outside.']),
        mc(3, 'Which pattern shows rhythm?', 'Clap, clap, rest, clap.', ['Stand still forever.', 'Erase every sound.', 'Color a circle blue.']),
        wr(4, 'Write or describe a four-count clap rhythm pattern.')
      ] : [
        mc(1, 'What should stay steady while you clap a rhythm?', 'The beat', ['The page color', 'The art texture', 'The water cycle']),
        wr(2, 'Describe the clap rhythm pattern you practiced.'),
        mc(3, 'Which word means a pause with no sound?', 'Rest', ['Shape', 'Bone', 'Planet'])
      ];
    }
    if (text.includes('tempo') || text.includes('dynamics')) {
      return isQuiz ? [
        mc(1, 'What does tempo describe?', 'How fast or slow music is.', ['How rough a texture feels.', 'How tall a building is.', 'How many bones are in the body.']),
        mc(2, 'What do dynamics describe?', 'How loud or soft music is.', ['How food groups are sorted.', 'How fractions are divided.', 'How planets move.']),
        mc(3, 'Which direction tells a musician to play softly?', 'Piano', ['Forte', 'Presto', 'Allegro only']),
        wr(4, 'Explain how changing tempo or dynamics can change the mood of music.')
      ] : [
        wr(1, 'Describe one fast sound and one slow sound you tried or heard.'),
        mc(2, 'If music gets louder, which musical idea changed?', 'Dynamics', ['Texture rubbing', 'Hydration', 'Coordinate plane']),
        wr(3, 'How did you show loud/soft or fast/slow in your activity?')
      ];
    }
    if (text.includes('pitch') || text.includes('high and low') || text.includes('melody')) {
      return isQuiz ? [
        mc(1, 'What does pitch describe?', 'How high or low a sound is.', ['How long a school break is.', 'How rough a surface feels.', 'How many food groups there are.']),
        mc(2, 'Which sound is usually higher in pitch?', 'A small bird chirping.', ['A deep drum boom.', 'Thunder rumbling.', 'A low tuba note.']),
        mc(3, 'What is a melody?', 'A pattern of pitches that can go up, down, or repeat.', ['A list of chores.', 'A kind of running drill.', 'A type of map.']),
        wr(4, 'Describe a sound that has a high pitch and a sound that has a low pitch.')
      ] : [
        mc(1, 'Which word best matches pitch?', 'High or low sound', ['Rough or smooth', 'Present or absent', 'Hot or cold lunch']),
        wr(2, 'Describe how you practiced high and low sounds.'),
        mc(3, 'A melody can move...', 'Up, down, or repeat.', ['Only backward in time.', 'Only inside a math table.', 'Only silently.'])
      ];
    }
    if (text.includes('music styles') || text.includes('genres')) {
      return isQuiz ? [
        mc(1, 'What is a music style or genre?', 'A category of music with shared sounds, instruments, rhythms, or traditions.', ['A type of art paper.', 'A PE safety rule.', 'A math operation.']),
        mc(2, 'Which detail can help you compare two music styles?', 'Instruments, rhythm, tempo, mood, and vocals.', ['Only the video length.', 'The color of the computer screen.', 'A random spelling word.']),
        mc(3, 'Why can two styles sound different?', 'They may use different rhythms, instruments, or performance traditions.', ['All music must sound exactly the same.', 'Different styles never use rhythm.', 'Genres are not connected to sound.']),
        wr(4, 'Compare two music styles using at least two details you noticed.')
      ] : [
        wr(1, 'Name two music styles and describe one difference you noticed between them.'),
        mc(2, 'Which detail would help compare music styles?', 'The instruments used in each style.', ['The number of pages in a book.', 'The shape of a leaf.', 'A fraction answer.']),
        wr(3, 'Which style did you like better and why?')
      ];
    }
    if (text.includes('rhythm pattern') || text.includes('create your rhythm') || text.includes('rhythm creation')) {
      return isQuiz ? [
        mc(1, 'What is a rhythm pattern?', 'An organized pattern of sounds and silences.', ['Only a color wheel.', 'A list of bones.', 'A planet orbit.']),
        mc(2, 'Which symbol could represent a rest in a simple rhythm?', 'A pause or silent count.', ['A louder sound only.', 'A food group.', 'A texture rubbing.']),
        mc(3, 'Why should a rhythm pattern fit a steady beat?', 'So the pattern can be performed evenly and repeated.', ['So the beat disappears.', 'So no one can follow it.', 'So it becomes a science experiment.']),
        wr(4, 'Create a four-count rhythm pattern and explain how you would perform it.')
      ] : [
        wr(1, 'Write your rhythm pattern using words like clap, tap, rest, or snap.'),
        mc(2, 'Which pattern uses four counts?', 'Clap, rest, clap, tap.', ['Clap forever with no count.', 'Draw one triangle.', 'Drink water.']),
        wr(3, 'How would you teach your rhythm pattern to someone else?')
      ];
    }
  }

  if (kind === 'pe') {
    if (text.includes('fitness circuit') || text.includes('backyard')) {
      return isQuiz ? [
        mc(1, 'What is a fitness circuit?', 'A set of exercises done one after another.', ['A kind of painting.', 'A list of instrument families.', 'A food group.']),
        mc(2, 'Which choice is a safe way to prepare for movement?', 'Clear the space and wear safe shoes.', ['Run near sharp furniture.', 'Ignore pain.', 'Exercise without water all day.']),
        mc(3, 'Why should you warm up before a circuit?', 'To get the heart, lungs, muscles, and joints ready to move.', ['To make movement more dangerous.', 'To avoid using muscles.', 'To stop breathing deeply.']),
        wr(4, 'Describe one movement from your circuit and how it helped your body.')
      ] : [
        wr(1, 'List the movements you completed in your backyard or indoor fitness circuit.'),
        mc(2, 'Which action helps keep a fitness circuit safe?', 'Moving in a clear space with parent permission.', ['Jumping on slippery objects.', 'Ignoring safety rules.', 'Holding your breath the whole time.']),
        wr(3, 'How did your body feel before and after the circuit?')
      ];
    }
    if (text.includes('warm-up') || text.includes('warm up') || text.includes('cool down')) {
      return isQuiz ? [
        mc(1, 'What is the purpose of a warm-up?', 'To prepare the body for more active movement.', ['To end activity suddenly.', 'To make muscles stiff.', 'To replace drinking water.']),
        mc(2, 'What is the purpose of a cool down?', 'To help the body slow down and recover after activity.', ['To start running faster immediately.', 'To skip stretching forever.', 'To stop learning.']),
        mc(3, 'Which movement could be part of a warm-up?', 'Marching in place or gentle arm circles.', ['Sitting still for a full day.', 'Throwing supplies across the room.', 'Ignoring space around you.']),
        wr(4, 'Explain why warm-ups and cool downs are both important.')
      ] : [
        wr(1, 'Describe the warm-up and cool-down movements you practiced.'),
        mc(2, 'Which activity belongs in a cool down?', 'Slow walking and gentle stretching.', ['Starting with the hardest sprint.', 'Holding your breath.', 'Running into furniture.']),
        wr(3, 'How did the cool down help your body recover?')
      ];
    }
    if (text.includes('balance') || text.includes('coordination')) {
      return isQuiz ? [
        mc(1, 'What is balance?', 'Controlling your body so you can stay steady.', ['Only moving as fast as possible.', 'A kind of color mixing.', 'A music family.']),
        mc(2, 'What is coordination?', 'Using body parts together in a controlled way.', ['A type of water cycle.', 'Only standing still forever.', 'A reading passage.']),
        mc(3, 'Which activity can practice balance?', 'Standing on one foot safely.', ['Closing your eyes near stairs.', 'Running into objects.', 'Skipping all safety checks.']),
        wr(4, 'Describe one balance or coordination challenge and how you did it safely.')
      ] : [
        wr(1, 'Describe one balance challenge you tried and what helped you stay steady.'),
        mc(2, 'Which skill helps your hands and eyes work together?', 'Coordination', ['Texture', 'Genre', 'Hydration only']),
        wr(3, 'What could you practice to improve your balance or coordination?')
      ];
    }
    if (text.includes('agility')) {
      return isQuiz ? [
        mc(1, 'What is agility?', 'Moving quickly and safely while changing direction.', ['Only sitting still.', 'A food group.', 'A type of drawing.']),
        mc(2, 'Why should an agility course have clear space?', 'To help prevent tripping, bumping, or injuries.', ['To make it harder to see obstacles.', 'To encourage unsafe running.', 'To avoid following directions.']),
        mc(3, 'Which movement could be part of an agility course?', 'Side steps, quick turns, or cone zigzags.', ['Eating lunch.', 'Reading silently only.', 'Mixing paint.']),
        wr(4, 'Explain how to set up a safe backyard agility activity.')
      ] : [
        wr(1, 'Describe your agility course or movement path.'),
        mc(2, 'Which rule helps keep agility games safe?', 'Look where you are going and keep space clear.', ['Run with eyes closed.', 'Ignore other people.', 'Use slippery floors.']),
        wr(3, 'How did you change direction safely during the activity?')
      ];
    }
    if (text.includes('heart') || text.includes('lungs') || text.includes('muscles')) {
      return isQuiz ? [
        mc(1, 'What does the heart do during exercise?', 'It pumps blood faster to carry oxygen and energy around the body.', ['It stops working.', 'It turns into a bone.', 'It only helps with drawing.']),
        mc(2, 'What do the lungs help your body do?', 'Bring in oxygen and remove carbon dioxide.', ['Create music notes.', 'Mix colors.', 'Measure fractions.']),
        mc(3, 'How can muscles get stronger?', 'By moving safely and practicing over time.', ['By never moving.', 'By skipping rest forever.', 'By ignoring pain.']),
        wr(4, 'Explain what you noticed about your heart rate or breathing during movement.')
      ] : [
        wr(1, 'Describe what happened to your heart rate or breathing after movement.'),
        mc(2, 'Which body parts help you breathe during exercise?', 'Lungs', ['Crayons', 'Drums', 'Triangles']),
        wr(3, 'How does exercise help the heart, lungs, or muscles?')
      ];
    }
    if (text.includes('fitness habits') || text.includes('healthy fitness')) {
      return isQuiz ? [
        mc(1, 'Which habit supports safe fitness?', 'Drinking water, resting, warming up, and listening to your body.', ['Ignoring pain.', 'Never sleeping.', 'Exercising in unsafe spaces.']),
        mc(2, 'Why is rest important?', 'It helps the body recover and stay ready for more activity.', ['It makes exercise useless.', 'It replaces all movement forever.', 'It removes the need for water.']),
        mc(3, 'What should you do if something hurts during exercise?', 'Stop and tell a parent or trusted adult.', ['Keep going no matter what.', 'Hide it.', 'Try a more dangerous movement.']),
        wr(4, 'Write one weekly fitness habit you can practice safely.')
      ] : [
        wr(1, 'Describe one healthy fitness habit you practiced or plan to practice.'),
        mc(2, 'Which choice is a healthy fitness habit?', 'Drinking water after activity.', ['Skipping sleep.', 'Ignoring soreness.', 'Running in unsafe places.']),
        wr(3, 'How can you listen to your body during exercise?')
      ];
    }
  }

  if (kind === 'health') {
    if (text.includes('balanced plate') || text.includes('food group')) {
      return isQuiz ? [
        mc(1, 'What is a balanced plate?', 'A meal with a variety of helpful food groups.', ['A plate with only candy.', 'A plate used for painting only.', 'A type of music pattern.']),
        mc(2, 'Which group gives many vitamins and minerals?', 'Fruits and vegetables.', ['Only soda.', 'Only paper plates.', 'Only air.']),
        mc(3, 'Why does the body need different food groups?', 'Different foods help the body grow, move, repair, and stay healthy.', ['Every food does the exact same job.', 'The body never needs nutrients.', 'Food groups are only for decoration.']),
        wr(4, 'Describe a balanced meal and name at least two food groups included.')
      ] : [
        wr(1, 'Build or describe a balanced plate. Include at least three food groups.'),
        mc(2, 'Which choice adds a healthy variety to a meal?', 'Adding fruit, vegetables, grains, and protein.', ['Only eating one candy.', 'Skipping every meal.', 'Only drinking soda.']),
        wr(3, 'Why is variety helpful when choosing foods?')
      ];
    }
    if (text.includes('hydration') || text.includes('water')) {
      return isQuiz ? [
        mc(1, 'Why does the body need water?', 'Water helps the body stay hydrated and helps the brain and muscles work.', ['Water replaces all food forever.', 'Water makes sleep unnecessary.', 'Water is only for plants.']),
        mc(2, 'Which sign may mean your body needs more water?', 'Feeling very thirsty or tired after activity.', ['Being able to read.', 'Seeing a color wheel.', 'Hearing a rhythm.']),
        mc(3, 'When is drinking water especially important?', 'Before, during, or after activity and on warm days.', ['Only once a year.', 'Never during exercise.', 'Only after skipping sleep.']),
        wr(4, 'Explain one way you can build a healthy hydration habit.')
      ] : [
        wr(1, 'Describe one hydration habit you can practice during school or activity.'),
        mc(2, 'Which drink is usually best for hydration?', 'Water', ['Glue', 'Paint', 'Air only']),
        wr(3, 'How can drinking water help your body during learning or movement?')
      ];
    }
    if (text.includes('body systems') || text.includes('muscles') || text.includes('bones') || text.includes('heart') || text.includes('lungs')) {
      return isQuiz ? [
        mc(1, 'What do bones do for the body?', 'Support and protect the body.', ['Make music styles.', 'Create texture rubbings.', 'Sort fractions.']),
        mc(2, 'What do muscles help the body do?', 'Move.', ['Erase sleep.', 'Become a color wheel.', 'Turn into a video.']),
        mc(3, 'What do the heart and lungs help move through the body?', 'Blood and oxygen.', ['Crayons and paper.', 'Only music notes.', 'Random shapes.']),
        wr(4, 'Explain how muscles, bones, heart, or lungs help your body during activity.')
      ] : [
        wr(1, 'Match or describe two body systems and what each one does.'),
        mc(2, 'Which body part pumps blood?', 'Heart', ['Pencil', 'Triangle', 'Tambourine']),
        wr(3, 'How do bones and muscles work together when you move?')
      ];
    }
    if (text.includes('sleep') || text.includes('rest') || text.includes('focus')) {
      return isQuiz ? [
        mc(1, 'Why is sleep important for students?', 'Sleep helps the brain learn, the body grow, and emotions reset.', ['Sleep replaces food and water.', 'Sleep makes learning impossible.', 'Sleep is only for weekends.']),
        mc(2, 'Which habit can support better sleep?', 'Following a calming bedtime routine.', ['Playing loud games all night.', 'Skipping rest every day.', 'Eating only candy before bed.']),
        mc(3, 'How can rest help focus?', 'A rested brain can pay attention and remember better.', ['Rest makes focus impossible.', 'Rest only changes shoe size.', 'Rest removes the need to learn.']),
        wr(4, 'Describe one sleep routine habit that could help you focus during school.')
      ] : [
        wr(1, 'Describe your sleep routine or one routine habit you want to practice.'),
        mc(2, 'Which choice is part of a calming sleep routine?', 'Turning off screens and doing a quiet activity before bed.', ['Running loudly in the room.', 'Drinking lots of soda late at night.', 'Skipping bedtime.']),
        wr(3, 'How can sleep or rest help your learning the next day?')
      ];
    }
    if (text.includes('hygiene') || text.includes('germs')) {
      return isQuiz ? [
        mc(1, 'What are germs?', 'Tiny living things or particles that can spread sickness.', ['A kind of art line.', 'A musical instrument family.', 'A coordinate plane.']),
        mc(2, 'Which habit helps reduce the spread of germs?', 'Washing hands with soap and water.', ['Sharing dirty tissues.', 'Never brushing teeth.', 'Coughing into someone’s face.']),
        mc(3, 'When should you wash your hands?', 'Before eating and after using the bathroom, coughing, sneezing, or playing outside.', ['Only once a month.', 'Only after drawing shapes.', 'Never during the day.']),
        wr(4, 'Explain one hygiene habit and how it helps protect health.')
      ] : [
        wr(1, 'Describe one hygiene routine you practiced or learned about.'),
        mc(2, 'Which routine helps remove germs from hands?', 'Using soap and water.', ['Using crayons.', 'Clapping a rhythm.', 'Skipping water.']),
        wr(3, 'Why is it important to cover a cough or sneeze?')
      ];
    }
    if (text.includes('safety') || text.includes('safe choices')) {
      return isQuiz ? [
        mc(1, 'What is a safe choice?', 'A choice that helps prevent injuries and protects yourself or others.', ['A choice that ignores danger.', 'A choice that always breaks rules.', 'A choice that hides problems.']),
        mc(2, 'What should you do in an unsafe situation?', 'Move away if possible and tell a trusted adult.', ['Keep it secret forever.', 'Make it more dangerous.', 'Ignore safety rules.']),
        mc(3, 'Which item belongs in a safety plan?', 'Trusted adults to contact and safe places to go.', ['Only favorite colors.', 'Only music styles.', 'Only art textures.']),
        wr(4, 'Describe one safety rule for home, outside, or online and explain why it matters.')
      ] : [
        wr(1, 'Write one safety plan step for home or outside.'),
        mc(2, 'Who can help when something feels unsafe?', 'A trusted adult.', ['A random stranger only.', 'No one ever.', 'A drawing tool.']),
        wr(3, 'Why is it important to think about safety before there is a problem?')
      ];
    }
  }
  return [];
}


function gradeFiveElectiveVocabulary(kind, lessonTitle, assignmentTitle) {
  const text = String([lessonTitle, assignmentTitle].filter(Boolean).join(' ')).toLowerCase();
  if (kind === 'art') {
    if (text.includes('texture')) return [
      { term:'texture', definition:'How a surface looks or feels, such as rough, smooth, bumpy, or soft.' },
      { term:'rubbing', definition:'A way to capture texture by placing paper over a surface and rubbing with crayon or pencil.' },
      { term:'surface', definition:'The outside part of an object that you can touch or see.' }
    ];
    if (text.includes('abstract')) return [
      { term:'abstract art', definition:'Art that uses lines, shapes, and colors without needing to look exactly like real life.' },
      { term:'shape', definition:'A closed space such as a circle, square, triangle, or free-form shape.' },
      { term:'composition', definition:'How the parts of an artwork are arranged.' }
    ];
    if (text.includes('observation')) return [
      { term:'observation', definition:'Looking closely to notice shapes, lines, sizes, textures, and details.' },
      { term:'outline', definition:'The outside edge or shape of an object.' },
      { term:'detail', definition:'A small part that helps make a drawing more accurate or interesting.' }
    ];
    if (text.includes('color')) return [
      { term:'color wheel', definition:'A circle chart that shows how colors are organized and related.' },
      { term:'primary colors', definition:'Red, yellow, and blue. These colors can mix to make other colors.' },
      { term:'secondary colors', definition:'Colors made by mixing two primary colors, such as orange, green, or purple.' }
    ];
    if (text.includes('portfolio') || text.includes('reflection')) return [
      { term:'portfolio', definition:'A collection of artwork that shows practice, growth, and choices over time.' },
      { term:'reflection', definition:'Thinking about what you created, what worked well, and what you want to improve.' },
      { term:'artist statement', definition:'A short explanation of the choices and ideas behind an artwork.' }
    ];
    return [
      { term:'line', definition:'A mark that moves from one point to another.' },
      { term:'shape', definition:'A closed space such as a circle, square, or triangle.' },
      { term:'pattern', definition:'Lines, shapes, colors, or designs repeated in an organized way.' }
    ];
  }
  if (kind === 'music') {
    if (text.includes('pitch') || text.includes('high and low')) return [
      { term:'pitch', definition:'How high or low a sound is.' },
      { term:'melody', definition:'A pattern of pitches that can move up, down, or repeat.' },
      { term:'sound', definition:'Something you hear when vibrations travel to your ears.' }
    ];
    if (text.includes('tempo') || text.includes('dynamics')) return [
      { term:'tempo', definition:'How fast or slow music is.' },
      { term:'dynamics', definition:'How loud or soft music is.' },
      { term:'mood', definition:'The feeling music can create for a listener.' }
    ];
    if (text.includes('rhythm') || text.includes('clap') || text.includes('beat')) return [
      { term:'beat', definition:'The steady pulse in music.' },
      { term:'rhythm', definition:'A pattern of sounds and silences.' },
      { term:'rest', definition:'A silent count in a rhythm pattern.' }
    ];
    if (text.includes('style') || text.includes('genre')) return [
      { term:'music style', definition:'A category of music with shared sounds, rhythms, instruments, or traditions.' },
      { term:'genre', definition:'Another word for a style or category of music.' },
      { term:'compare', definition:'To tell how two things are alike and different.' }
    ];
    return [
      { term:'instrument family', definition:'A group of instruments that make sound in a similar way.' },
      { term:'strings', definition:'Instruments that use vibrating strings to make sound.' },
      { term:'percussion', definition:'Instruments that are hit, shaken, or scraped.' }
    ];
  }
  if (kind === 'pe') {
    if (text.includes('warm') || text.includes('cool')) return [
      { term:'warm-up', definition:'Gentle movement that gets the body ready for more active exercise.' },
      { term:'cool down', definition:'Slower movement and stretching that helps the body recover after activity.' },
      { term:'stretch', definition:'A safe movement that lengthens muscles gently.' }
    ];
    if (text.includes('balance') || text.includes('coordination')) return [
      { term:'balance', definition:'Controlling your body so you can stay steady.' },
      { term:'coordination', definition:'Using body parts together in a controlled way.' },
      { term:'control', definition:'Moving safely and with purpose.' }
    ];
    if (text.includes('agility')) return [
      { term:'agility', definition:'Moving quickly and safely while changing direction.' },
      { term:'direction', definition:'The way your body is moving, such as forward, backward, or sideways.' },
      { term:'safe space', definition:'An open area clear of objects that could cause tripping or injury.' }
    ];
    if (text.includes('heart') || text.includes('lungs') || text.includes('muscles')) return [
      { term:'heart rate', definition:'How fast your heart is beating.' },
      { term:'lungs', definition:'Body parts that help you breathe in oxygen and breathe out carbon dioxide.' },
      { term:'muscles', definition:'Body tissues that help you move.' }
    ];
    return [
      { term:'fitness', definition:'Keeping the body active, strong, and healthy through safe movement.' },
      { term:'circuit', definition:'A set of exercises done one after another.' },
      { term:'hydration', definition:'Having enough water in the body.' }
    ];
  }
  if (kind === 'health') {
    if (text.includes('hygiene') || text.includes('germs')) return [
      { term:'hygiene', definition:'Clean habits that help protect your health, such as washing hands and brushing teeth.' },
      { term:'germs', definition:'Tiny living things or particles that can spread sickness.' },
      { term:'routine', definition:'A set of steps you do regularly, such as washing hands before eating.' }
    ];
    if (text.includes('sleep') || text.includes('rest') || text.includes('focus')) return [
      { term:'sleep routine', definition:'Calm steps before bedtime that help the body and brain get ready to rest.' },
      { term:'rest', definition:'Time for the body and brain to recover.' },
      { term:'focus', definition:'Paying attention to learning or a task.' }
    ];
    if (text.includes('safety') || text.includes('safe')) return [
      { term:'safe choice', definition:'A choice that helps prevent injuries and protects yourself or others.' },
      { term:'trusted adult', definition:'A parent, teacher, or responsible adult you can ask for help.' },
      { term:'safety plan', definition:'Steps to follow so you know what to do if something feels unsafe.' }
    ];
    if (text.includes('hydration') || text.includes('water')) return [
      { term:'hydration', definition:'Having enough water in the body.' },
      { term:'energy', definition:'The strength your body uses to move, think, and learn.' },
      { term:'thirst', definition:'A body signal that you may need water.' }
    ];
    if (text.includes('body systems') || text.includes('muscles') || text.includes('bones') || text.includes('heart') || text.includes('lungs')) return [
      { term:'body system', definition:'A group of body parts that work together.' },
      { term:'heart', definition:'The organ that pumps blood through the body.' },
      { term:'lungs', definition:'Organs that help the body breathe.' }
    ];
    return [
      { term:'balanced meal', definition:'A meal with different food groups that help the body in different ways.' },
      { term:'nutrient', definition:'A substance in food that helps the body work and grow.' },
      { term:'food group', definition:'A category of foods, such as fruits, vegetables, grains, protein, or dairy.' }
    ];
  }
  return [];
}

function gradeFiveElectiveExamples(kind, lessonTitle, assignmentTitle) {
  const vocab = gradeFiveElectiveVocabulary(kind, lessonTitle, assignmentTitle);
  const main = vocab[0]?.term || lessonTitle || 'this topic';
  const lesson = String(lessonTitle || assignmentTitle || 'this lesson');
  if (kind === 'health' && /hygiene|germs/i.test(lesson + ' ' + assignmentTitle)) {
    return [{ title:'Hygiene Example', content:'Washing hands with soap and water before eating can remove germs that may cause sickness.', solution:'The routine is specific, clean, and connected to health.' }];
  }
  if (kind === 'music' && /rhythm|clap|beat/i.test(lesson + ' ' + assignmentTitle)) {
    return [{ title:'Rhythm Example', content:'Clap, clap, rest, clap is a four-count rhythm pattern because it has sounds and one silent count.', solution:'The beat stays steady while the rhythm pattern changes.' }];
  }
  if (kind === 'art' && /line|shape/i.test(lesson + ' ' + assignmentTitle)) {
    return [{ title:'Line and Shape Example', content:'A student might repeat zigzag lines and circles to create a border pattern.', solution:'The repeated lines and shapes make the design feel organized.' }];
  }
  if (kind === 'pe') {
    return [{ title:'Safe Movement Example', content:'Before starting, clear the area, wear safe shoes, and choose movements that fit your space.', solution:'Safety comes before speed or difficulty.' }];
  }
  return [{ title: lesson + ' Example', content: 'Use the lesson details about ' + main + ' to complete the activity.', solution: 'Your answer should name a specific detail from this lesson.' }];
}

function gradeFiveElectivePractice(kind, lessonTitle, assignmentTitle) {
  const text = String([lessonTitle, assignmentTitle].filter(Boolean).join(' ')).toLowerCase();
  if (kind === 'health' && (text.includes('hygiene') || text.includes('germs'))) return [
    { question:'Name one hygiene habit from this lesson and explain how it helps reduce germs.', hint:'Think about handwashing, covering coughs, brushing teeth, or clean routines.' }
  ];
  if (kind === 'health' && text.includes('sleep')) return [
    { question:'Name one sleep routine habit and explain how it could help focus the next day.', hint:'Think about quiet activities, turning off screens, and getting enough rest.' }
  ];
  if (kind === 'health' && text.includes('safety')) return [
    { question:'Write one safety plan step and name the trusted adult or safe place connected to it.', hint:'A safety plan should tell what to do and who can help.' }
  ];
  if (kind === 'art' && text.includes('texture')) return [{ question:'Name two textures you found and describe how each rubbing looked.', hint:'Use texture words such as rough, smooth, bumpy, soft, or ridged.' }];
  if (kind === 'art' && text.includes('abstract')) return [{ question:'Describe two shapes or colors you used and what mood or idea they show.', hint:'Abstract art can show mood through shape, color, and arrangement.' }];
  if (kind === 'music' && text.includes('high and low')) return [{ question:'Name one high-pitch sound and one low-pitch sound you heard or practiced.', hint:'A bird chirp is often high; a drum boom is often low.' }];
  if (kind === 'music' && text.includes('rhythm')) return [{ question:'Write a four-count rhythm using clap, tap, rest, or snap.', hint:'Example format: clap, rest, tap, clap.' }];
  if (kind === 'pe') return [{ question:'Describe one movement from this lesson and one safety rule you followed.', hint:'Include the movement name and how you kept your space or body safe.' }];
  return [{ question:'Name one specific detail from ' + (lessonTitle || 'this lesson') + ' and explain how it connects to the activity.', hint:'Use the vocabulary, video, or visual cards from this lesson.' }];
}

const PUBLIC_RESOURCE_LIBRARY = [
  { title: 'What\'s My Property?', source: 'Crash Course Kids', subject: 'Science', grade_band: '3-5', topic: 'Properties of Matter', resource_type: 'video', url: 'https://www.youtube.com/watch?v=nlSemv2fLN8', embed_url: 'https://www.youtube-nocookie.com/embed/nlSemv2fLN8?rel=0&modestbranding=1', requires_login: false, embeddable: true, verified: true, notes: 'Direct video about physical properties such as hardness, magnetism, malleability, and grouping materials.' },
  { title: 'What\'s Matter?', source: 'Crash Course Kids', subject: 'Science', grade_band: '3-5', topic: 'Matter', resource_type: 'video', url: 'https://www.youtube.com/watch?v=ELchwUIlWa8', embed_url: 'https://www.youtube-nocookie.com/embed/ELchwUIlWa8?rel=0&modestbranding=1', requires_login: false, embeddable: true, verified: true, notes: 'Direct video introducing matter and states of matter.' },
  { title: 'The 13 Original Colonies for Kids', source: 'Learn Bright', subject: 'Social Studies', grade_band: '3-5', topic: '13 Colonies', resource_type: 'video', url: 'https://www.youtube.com/watch?v=UUmi3iNMawo', embed_url: 'https://www.youtube-nocookie.com/embed/UUmi3iNMawo?rel=0&modestbranding=1', requires_login: false, embeddable: true, verified: true, notes: 'Direct topic video introducing the 13 Colonies and their regions.' },
  { title: 'Colonial Times for Kids | Learn about colonial America', source: 'Learn Bright', subject: 'Social Studies', grade_band: '4-6', topic: 'Colonial Life', resource_type: 'video', url: 'https://www.youtube.com/watch?v=4f3LsMKzxdg', embed_url: 'https://www.youtube-nocookie.com/embed/4f3LsMKzxdg?rel=0&modestbranding=1', requires_login: false, embeddable: true, verified: true, notes: 'Direct topic video about daily life in Colonial America.' },
  { title: 'The American Revolutionary War for Kids', source: 'Learn Bright', subject: 'Social Studies', grade_band: '4-6', topic: 'Road to Revolution', resource_type: 'video', url: 'https://www.youtube.com/watch?v=BWtW0gmh3kg', embed_url: 'https://www.youtube-nocookie.com/embed/BWtW0gmh3kg?rel=0&modestbranding=1', requires_login: false, embeddable: true, verified: true, notes: 'Direct topic video explaining events leading to the American Revolution and the Revolutionary War.' },
  { title: 'Constitution for Kids | What It Is and Why It Matters', source: 'Learn Bright', subject: 'Social Studies', grade_band: '4-6', topic: 'Constitution', resource_type: 'video', url: 'https://www.youtube.com/watch?v=jsTB7gSfDPI', embed_url: 'https://www.youtube-nocookie.com/embed/jsTB7gSfDPI?rel=0&modestbranding=1', requires_login: false, embeddable: true, verified: true, notes: 'Direct topic video about the United States Constitution.' },
  { title: 'Bill of Rights for Kids', source: 'Learn Bright', subject: 'Social Studies', grade_band: '4-6', topic: 'Bill of Rights', resource_type: 'video', url: 'https://www.youtube.com/watch?v=skWNC3a8NkM', embed_url: 'https://www.youtube-nocookie.com/embed/skWNC3a8NkM?rel=0&modestbranding=1', requires_login: false, embeddable: true, verified: true, notes: 'Direct topic video about the first 10 amendments.' },
  { title: 'Westward Expansion Explained For Kids', source: 'Learn Bright', subject: 'Social Studies', grade_band: '4-6', topic: 'Westward Expansion', resource_type: 'video', url: 'https://www.youtube.com/watch?v=_9FLJEvDniU', embed_url: 'https://www.youtube-nocookie.com/embed/_9FLJEvDniU?rel=0&modestbranding=1', requires_login: false, embeddable: true, verified: true, notes: 'Direct topic video about westward expansion.' },
  { title: 'Declaration of Independence for Kids', source: 'Learn Bright', subject: 'Social Studies', grade_band: '4-6', topic: 'Declaration of Independence', resource_type: 'video', url: 'https://www.youtube.com/watch?v=TiduzZTWFOU', embed_url: 'https://www.youtube-nocookie.com/embed/TiduzZTWFOU?rel=0&modestbranding=1', requires_login: false, embeddable: true, verified: true, notes: 'Direct topic video about the Declaration of Independence.' },
  { title: 'Battles of the American Revolution', source: 'Early US History for Kids', subject: 'Social Studies', grade_band: '4-6', topic: 'Key Battles', resource_type: 'video', url: 'https://www.youtube.com/watch?v=V9SQ4HxnWnM', embed_url: 'https://www.youtube-nocookie.com/embed/V9SQ4HxnWnM?rel=0&modestbranding=1', requires_login: false, embeddable: true, verified: true, notes: 'Direct topic video focused on Revolutionary War battles.' },
  { title: 'The Solar System Planets | Educational Video for Kids', source: 'Educational YouTube Video', subject: 'Science', grade_band: '3-5', topic: 'Solar System', resource_type: 'video', url: 'https://www.youtube.com/watch?v=ASQkz4XaphU', embed_url: 'https://www.youtube-nocookie.com/embed/ASQkz4XaphU?rel=0&modestbranding=1', requires_login: false, embeddable: true, verified: true, notes: 'Replacement solar system video after the prior Learn Bright embed produced a redirect error in testing.' },

  { title: 'Reading Comprehension for Kids | Practice Reading Comprehension Skills', source: 'Educational YouTube Video', subject: 'Language Arts', grade_band: '3-5', topic: 'Reading Comprehension', resource_type: 'video', url: 'https://www.youtube.com/watch?v=NqpbTN3diUc', embed_url: 'https://www.youtube-nocookie.com/embed/NqpbTN3diUc?rel=0&modestbranding=1', requires_login: false, embeddable: true, verified: true, notes: 'Reading-comprehension strategy video kept available for new/reset students.' },
  { title: '5th Grade Math Unit Conversions', source: 'Aaron Daffern', subject: 'Math', grade_band: '5', topic: 'Converting Units', resource_type: 'video', url: 'https://www.youtube.com/watch?v=fdyFeEc8Wss', embed_url: 'https://www.youtube-nocookie.com/embed/fdyFeEc8Wss?rel=0&modestbranding=1', requires_login: false, embeddable: true, verified: true, notes: 'Grade 5 video about converting customary and metric units.' },
  { title: 'Healthy Hydration for Kids', source: 'Hospital for Special Surgery', subject: 'Health and Nutrition', grade_band: '3-5', topic: 'Hydration', resource_type: 'video', url: 'https://www.youtube.com/watch?v=U-NNTDpHwzQ', embed_url: 'https://www.youtube-nocookie.com/embed/U-NNTDpHwzQ?rel=0&modestbranding=1', requires_login: false, embeddable: true, verified: true, notes: 'Hydration lesson video; do not reuse MyPlate for hydration lessons.' },
  { title: 'All About Sleep for Kids | Why Is Sleep Important?', source: 'Twinkl USA', subject: 'Health and Nutrition', grade_band: '3-5', topic: 'Sleep Routine', resource_type: 'video', url: 'https://www.youtube.com/watch?v=xXGnjtLyUiI', embed_url: 'https://www.youtube-nocookie.com/embed/xXGnjtLyUiI?rel=0&modestbranding=1', requires_login: false, embeddable: true, verified: true, notes: 'Sleep and rest video for health lessons.' },
  { title: 'Music Genres for Kids! Learn Music Genres', source: 'Name That Tune', subject: 'Music', grade_band: '3-5', topic: 'Music Styles', resource_type: 'video', url: 'https://www.youtube.com/watch?v=jD79wiJ_02k', embed_url: 'https://www.youtube-nocookie.com/embed/jD79wiJ_02k?rel=0&modestbranding=1', requires_login: false, embeddable: true, verified: true, notes: 'Music genres video for comparing styles.' },
  { title: 'The Milky Way for Children, Galaxies and Space', source: 'FreeSchool', subject: 'Science', grade_band: '3-5', topic: 'Stars and Galaxies', resource_type: 'video', url: 'https://www.youtube.com/watch?v=RubnGwhcT6E', embed_url: 'https://www.youtube-nocookie.com/embed/RubnGwhcT6E?rel=0&modestbranding=1', requires_login: false, embeddable: true, verified: true, notes: 'Student-facing astronomy lesson focused on the Milky Way and galaxies.' },
  { title: '5th Grade - Science - Earth, Sun, and Moon - Topic Overview', source: 'Miacademy & MiaPrep', subject: 'Science', grade_band: '5', topic: 'Earth Moon System', resource_type: 'video', url: 'https://www.youtube.com/watch?v=shN68vNkZHk', embed_url: 'https://www.youtube-nocookie.com/embed/shN68vNkZHk?rel=0&modestbranding=1', requires_login: false, embeddable: true, verified: true, notes: 'Student-facing Grade 5 topic overview about the Earth, Sun, and Moon relationship.' },
  { title: 'The Water Cycle for Kids', source: 'Learn Bright', subject: 'Science', grade_band: '3-5', topic: 'Water Cycle', resource_type: 'video', url: 'https://www.youtube.com/watch?v=TD3XSIE4ymo', embed_url: 'https://www.youtube-nocookie.com/embed/TD3XSIE4ymo?rel=0&modestbranding=1', requires_login: false, embeddable: true, verified: true, notes: 'Direct topic video about the water cycle.' },
  { title: 'How do ocean currents work?', source: 'TED-Ed', subject: 'Science', grade_band: '3-5', topic: 'Ocean Currents', resource_type: 'video', url: 'https://www.youtube.com/watch?v=p4pWafuvdrY', embed_url: 'https://www.youtube-nocookie.com/embed/p4pWafuvdrY?rel=0&modestbranding=1', requires_login: false, embeddable: true, verified: true, notes: 'Direct topic video about ocean currents.' },
  { title: 'The Atmosphere for Kids', source: 'Smile and Learn', subject: 'Science', grade_band: '3-5', topic: 'Atmosphere', resource_type: 'video', url: 'https://www.youtube.com/watch?v=G4ZIa3qkFkI', embed_url: 'https://www.youtube-nocookie.com/embed/G4ZIa3qkFkI?rel=0&modestbranding=1', requires_login: false, embeddable: true, verified: true, notes: 'Direct topic video about Earth\'s atmosphere.' },
  { title: 'Math Antics - Multiplying Fractions', source: 'Math Antics', subject: 'Math', grade_band: '3-5', topic: 'Multiplying Fractions', resource_type: 'video', url: 'https://www.youtube.com/watch?v=qmfXyR7Z6Lk', embed_url: 'https://www.youtube-nocookie.com/embed/qmfXyR7Z6Lk?rel=0&modestbranding=1', requires_login: false, embeddable: true, verified: true, notes: 'Direct topic video about multiplying fractions.' },
  { title: 'Math Antics - Dividing Fractions', source: 'Math Antics', subject: 'Math', grade_band: '3-5', topic: 'Dividing Fractions', resource_type: 'video', url: 'https://www.youtube.com/watch?v=4lkq3DgvmJo', embed_url: 'https://www.youtube-nocookie.com/embed/4lkq3DgvmJo?rel=0&modestbranding=1', requires_login: false, embeddable: true, verified: true, notes: 'Direct topic video about dividing fractions.' },
  { title: 'Math Antics - Mixed Numbers', source: 'Math Antics', subject: 'Math', grade_band: '3-5', topic: 'Mixed Numbers', resource_type: 'video', url: 'https://www.youtube.com/watch?v=qk2oP6FZ6HA', embed_url: 'https://www.youtube-nocookie.com/embed/qk2oP6FZ6HA?rel=0&modestbranding=1', requires_login: false, embeddable: true, verified: true, notes: 'Direct topic video about mixed numbers.' },
  { title: 'Math Antics - Volume', source: 'Math Antics', subject: 'Math', grade_band: '3-5', topic: 'Volume and Measurement', resource_type: 'video', url: 'https://www.youtube.com/watch?v=qJwecTgce6c', embed_url: 'https://www.youtube-nocookie.com/embed/qJwecTgce6c?rel=0&modestbranding=1', requires_login: false, embeddable: true, verified: true, notes: 'Direct topic video about volume.' },
  { title: 'Algebra Basics: Graphing On The Coordinate Plane', source: 'Math Antics', subject: 'Math', grade_band: '3-5', topic: 'Coordinate Planes', resource_type: 'video', url: 'https://www.youtube.com/watch?v=9Uc62CuQjc4', embed_url: 'https://www.youtube-nocookie.com/embed/9Uc62CuQjc4?rel=0&modestbranding=1', requires_login: false, embeddable: true, verified: true, notes: 'Direct topic video about graphing on the coordinate plane.' },
  { title: 'Physical and Chemical Changes for Kids', source: 'Homeschool Pop', subject: 'Science', grade_band: '3-5', topic: 'Physical and Chemical Changes', resource_type: 'video', url: 'https://www.youtube.com/watch?v=BOr76Zx48QM', embed_url: 'https://www.youtube-nocookie.com/embed/BOr76Zx48QM?rel=0&modestbranding=1', requires_login: false, embeddable: true, verified: true, notes: 'Direct topic video comparing physical changes and chemical changes.' },
  { title: 'Chrome Music Lab', source: 'Google Creative Lab', subject: 'Music', grade_band: 'K-12', topic: 'Music Exploration', resource_type: 'interactive', url: 'https://musiclab.chromeexperiments.com/', embed_url: null, requires_login: false, embeddable: false, verified: true, notes: 'No account needed; use as optional parent-approved exploration, not as the main student flow.' },
  { title: 'Serving Up MyPlate: A Yummy Curriculum', source: 'USDA Team Nutrition', subject: 'Health and Nutrition', grade_band: 'K-5', topic: 'Food Groups', resource_type: 'curriculum', url: 'https://www.fns.usda.gov/tn/serving-myplate-yummy-curriculum', embed_url: null, requires_login: false, embeddable: false, verified: true, notes: 'USDA classroom nutrition curriculum about the five food groups and physical activity.' },
  { title: 'The Elements of Art', source: 'Scratch Garden', subject: 'Art', grade_band: '3-5', topic: 'Elements of Art', resource_type: 'video', url: 'https://www.youtube.com/watch?v=IkvH7HaqCYk', embed_url: 'https://www.youtube-nocookie.com/embed/IkvH7HaqCYk?rel=0&modestbranding=1', requires_login: false, embeddable: true, verified: true, notes: 'Public video covering line, shape, color, texture, form, and space.' },
  { title: 'The Instruments of the Orchestra Song', source: 'Hopscotch', subject: 'Music', grade_band: '3-5', topic: 'Instrument Families', resource_type: 'video', url: 'https://www.youtube.com/watch?v=JVY4tj-JTBc', embed_url: 'https://www.youtube-nocookie.com/embed/JVY4tj-JTBc?rel=0&modestbranding=1', requires_login: false, embeddable: true, verified: true, notes: 'Public video/song introducing orchestra instruments and families.' },
  { title: 'Kids Workout and Warm Up', source: 'Prime Coaching Sport', subject: 'Physical Education', grade_band: '3-5', topic: 'Warm-Up and Fitness', resource_type: 'video', url: 'https://www.youtube.com/watch?v=ZPLHTRCmKNU', embed_url: 'https://www.youtube-nocookie.com/embed/ZPLHTRCmKNU?rel=0&modestbranding=1', requires_login: false, embeddable: true, verified: true, notes: 'Public warm-up/movement video for kids.' },
  { title: 'Food Groups and MyPlate', source: 'Educational YouTube Video', subject: 'Health and Nutrition', grade_band: '3-5', topic: 'Food Groups', resource_type: 'video', url: 'https://www.youtube.com/watch?v=L7QOUiQCb5E', embed_url: 'https://www.youtube-nocookie.com/embed/L7QOUiQCb5E?rel=0&modestbranding=1', requires_login: false, embeddable: true, verified: true, notes: 'Public video introducing five food groups and MyPlate.' }
];

function visualActivity(cards, extras = {}) {
  return { visual_cards: cards, ...extras };
}

async function addGradeFiveElectivesAndHealth() {
  const grade = 5;
  const courses = [
    { key: 'art', name: 'Art Fundamentals (K-5)', subject: 'Art', description: 'Grade 5 visual art lessons with elements of art, creative projects, examples, and reflections.' },
    { key: 'music', name: 'Music Fundamentals (K-5)', subject: 'Music', description: 'Grade 5 music lessons with rhythm, instrument families, listening activities, and simple sound practice.' },
    { key: 'pe', name: 'Physical Education (K-5)', subject: 'Physical Education', description: 'Grade 5 homeschool PE with safe movement, fitness, health awareness, and backyard-friendly activities.' },
    { key: 'health', name: 'Health and Nutrition (K-5)', subject: 'Health and Nutrition', description: 'Grade 5 health lessons about food groups, hydration, sleep, muscles, heart health, and healthy choices.' }
  ];
  const created = {};
  for (const c of courses) created[c.key] = await ensureCourse({ ...c, grade_level: grade, course_type: 'elective', credit_hours: 0.5 });

  // ART GRADE 5
  let unit = await ensureUnit(created.art.id, 1, 'Elements of Art and Creative Projects', 'Learn line, shape, color, texture, and space through guided art projects.');
  let lesson = await ensureLesson(unit.id, 1, {
    title: 'Line, Shape, and Pattern Project', description: 'Use lines and shapes to create an original patterned artwork.', lesson_type: 'guided_project', interactive_type: 'visual_project', skill_name: 'Elements of Art', video_title: 'Easy Art Activity to Teach Lines & Shapes', video_source: 'Miriam Cutelis', video_url: 'https://www.youtube.com/watch?v=dI9KvApZHzM', video_verified: true,
    learning_objectives: 'Identify line and shape. Use repeating patterns. Create an original artwork. Reflect on design choices.',
    instructional_content: 'Artists use lines and shapes to build pictures, patterns, and designs. A line can be straight, curved, zigzag, wavy, thick, or thin. A shape is a closed space, such as a circle, square, triangle, rectangle, or an organic free-form shape.\n\nA pattern repeats a line, shape, or color. Artists use patterns to create movement and interest. In this project, you will plan a design using at least three types of lines and three shapes.',
    vocabulary: [{term:'line',definition:'A mark that moves from one point to another.'},{term:'shape',definition:'A closed space such as a circle, square, or triangle.'},{term:'pattern',definition:'A repeated line, shape, color, or design.'},{term:'composition',definition:'How the parts of an artwork are arranged.'}],
    examples: [{title:'Pattern Example', content:'A student draws a row of triangles, then repeats curved lines inside each triangle. The repeated design creates a pattern.', solution:'The artwork uses line, shape, and repetition.'}],
    guided_practice: [{question:'Look around the room. Name one object that has a shape and one object that has a pattern.', hint:'A window may be a rectangle. A blanket may have a pattern.'}],
    materials_needed: 'Paper, pencil, crayons, colored pencils, or markers.',
    activity_data: visualActivity([
      { title:'Lines', text:'Straight, curved, zigzag, wavy, thick, and thin lines can create movement.', icon:'〰️' },
      { title:'Shapes', text:'Geometric shapes are clear and regular. Organic shapes look more natural or free-form.', icon:'🔺' },
      { title:'Pattern', text:'Repeat a line, shape, or color to make the viewer’s eye move across the page.', icon:'🔁' }
    ], { supplies:['Paper','Pencil','Crayons or markers'], activity_steps:['Draw a border around your page.','Add at least 3 kinds of lines.','Add at least 3 shapes.','Repeat one design to create a pattern.','Add color and give your artwork a title.'], project_prompt:'Create a patterned artwork using line and shape. Then write one sentence explaining your favorite part.' })
  });
  await safeEnsureAssignment(lesson.id, { title: 'Create a Line and Shape Artwork', description: 'Create a patterned artwork and reflect on your design.', instructions: 'Review the art examples, gather supplies, create your artwork, then answer the reflection question.', assignment_type: 'activity', weight_category: 'homework', point_value: 100, questions: [wr(1, 'What did you create? Describe at least two lines or shapes you used in your artwork.')] }, 'grade5-art-activity');
  await safeEnsureAssignment(lesson.id, { title: 'Elements of Art Quick Check', description: 'Check your understanding of line, shape, pattern, and texture.', instructions: 'Answer the art questions after reviewing the lesson and visual examples.', assignment_type: 'quiz', weight_category: 'quiz', point_value: 100, questions: [mc(1, 'Which answer best describes a line in art?', 'A mark that moves from one point to another.', ['A closed shape with no sides.', 'Only a color on a page.', 'A finished painting only.']), mc(2, 'Why do artists repeat shapes or colors?', 'To create a pattern or rhythm in the artwork.', ['To erase the artwork.', 'To hide every line.', 'To make art impossible to see.']), wr(3, 'Name two elements of art you used or saw in the lesson and explain how they can help create artwork.')] }, 'grade5-art-quiz');

  // MUSIC GRADE 5
  unit = await ensureUnit(created.music.id, 1, 'Beat, Rhythm, and Instrument Families', 'Explore rhythm, tempo, dynamics, and instrument sounds.');
  lesson = await ensureLesson(unit.id, 1, {
    title: 'Instrument Families and Sounds', description: 'Learn how instruments are grouped and practice identifying sounds.', lesson_type: 'guided_audio', interactive_type: 'sound_cards', skill_name: 'Instrument Families', video_title: 'The Instruments of the Orchestra Song', video_source: 'Hopscotch', video_url: 'https://www.youtube.com/watch?v=JVY4tj-JTBc', video_verified: true,
    learning_objectives: 'Identify the four main instrument families. Compare sounds. Listen for pitch, volume, and tone color.',
    instructional_content: 'Musicians group instruments into families based on how the sound is made. String instruments make sound when strings vibrate. Woodwind instruments use air and reeds or openings. Brass instruments use buzzing lips and air. Percussion instruments make sound when they are hit, shaken, or scraped.\n\nListen carefully for the sound quality. A violin may sound smooth and high. A drum may sound short and strong. A trumpet may sound bright and bold.',
    vocabulary: [{term:'instrument family',definition:'A group of instruments that make sound in a similar way.'},{term:'strings',definition:'Instruments that use vibrating strings.'},{term:'brass',definition:'Instruments played by buzzing lips into a mouthpiece.'},{term:'percussion',definition:'Instruments that are hit, shaken, or scraped.'},{term:'woodwind',definition:'Instruments that use air to make sound.'}],
    examples: [{title:'How to Listen', content:'If you hear a steady boom, it might be percussion. If you hear a bright, bold sound made with air, it might be brass.', solution:'Think about how the sound is made, not just whether it is loud or soft.'}],
    guided_practice: [{question:'Tap a steady beat four times. Then clap a short-short-long rhythm.', hint:'Beat stays steady. Rhythm can change.'}],
    activity_data: visualActivity([
      { title:'Strings', text:'Violin, cello, guitar, and harp use vibrating strings.', icon:'🎻', sound:'string' },
      { title:'Brass', text:'Trumpet, trombone, and tuba use buzzing lips and air.', icon:'🎺', sound:'brass' },
      { title:'Percussion', text:'Drums, tambourine, and cymbals are hit, shaken, or scraped.', icon:'🥁', sound:'percussion' },
      { title:'Woodwinds', text:'Flute, clarinet, and saxophone use air to make sound.', icon:'🎷', sound:'woodwind' }
    ], { sound_cards:true, rhythm_steps:['Count 1-2-3-4 out loud.','Clap on each count.','Now clap: short, short, long, rest.'], project_prompt:'Choose an instrument family. Explain how that family makes sound.' })
  });
  await safeEnsureAssignment(lesson.id, { title: 'Instrument Families Listening Activity', description: 'Learn instrument families and answer listening questions.', instructions: 'Study the sound cards. Play the sample sounds if available, then answer each question.', assignment_type: 'activity', weight_category: 'homework', point_value: 100, questions: [mc(1, 'Which instrument family makes sound when strings vibrate?', 'Strings', ['Brass','Percussion','Woodwinds']), mc(2, 'Which family includes drums and cymbals?', 'Percussion', ['Strings','Brass','Woodwinds']), wr(3, 'Choose one instrument family and explain how it makes sound.')] }, 'grade5-music-activity');
  await safeEnsureAssignment(lesson.id, { title: 'Instrument Families Sound Quiz', description: 'Show what you learned about instrument families and how they make sound.', instructions: 'Use what you learned from the listening cards and video to answer the quiz questions.', assignment_type: 'quiz', weight_category: 'quiz', point_value: 100, questions: [mc(1, 'A trumpet makes sound when a player buzzes their lips and blows air. Which family is it in?', 'Brass', ['Strings','Percussion','Woodwinds']), mc(2, 'A flute uses air to make sound. Which family does it belong to?', 'Woodwinds', ['Brass','Percussion','Strings']), mc(3, 'Which instrument family includes instruments that are hit, shaken, or scraped?', 'Percussion', ['Strings','Woodwinds','Brass']), wr(4, 'Pick one instrument family and describe one clue that can help you identify it by sound.')] }, 'grade5-music-quiz');

  // PE GRADE 5
  unit = await ensureUnit(created.pe.id, 1, 'Fitness at Home', 'Safe homeschool movement, backyard games, warm-ups, and body awareness.');
  lesson = await ensureLesson(unit.id, 1, {
    title: 'Backyard Fitness Circuit', description: 'Complete a safe movement circuit at home or outside with parent permission.', lesson_type: 'movement_activity', interactive_type: 'fitness_circuit', skill_name: 'Fitness and Endurance', video_title: 'Kids Workout and Warm Up', video_source: 'Prime Coaching Sport', video_url: 'https://www.youtube.com/watch?v=ZPLHTRCmKNU', video_verified: true,
    learning_objectives: 'Warm up safely. Complete a simple fitness circuit. Explain how movement helps the heart, lungs, and muscles.',
    instructional_content: 'Physical education helps your body grow stronger and healthier. A warm-up gets your heart, lungs, muscles, and joints ready to move. A circuit is a set of exercises completed one after another.\n\nToday’s circuit can be done in a living room, hallway, or backyard with parent permission. Move safely, drink water, and stop if anything hurts.',
    safety_notes: 'Clear the space before moving. Wear safe shoes. Drink water. Stop if something hurts. Ask a parent before outdoor activity.',
    examples: [{title:'Circuit Example', content:'30 seconds marching in place, 10 jumping jacks, 20-second wall sit, 10 arm circles, then a slow walk to cool down.', solution:'A circuit uses different movements to build strength, balance, and endurance.'}],
    guided_practice: [{question:'Before starting, name one thing you should do to make your exercise space safe.', hint:'Think about shoes, furniture, water, or parent permission.'}],
    activity_data: visualActivity([
      { title:'Warm Up', text:'March in place and roll your shoulders to get ready.', icon:'🚶' },
      { title:'Cardio', text:'Jumping jacks or fast steps help your heart and lungs work harder.', icon:'❤️' },
      { title:'Strength', text:'Wall sits and planks help muscles work against resistance.', icon:'💪' },
      { title:'Cool Down', text:'Slow walking and stretching help your body recover.', icon:'🧘' }
    ], { activity_steps:['Warm up for 2 minutes.','Do 10 jumping jacks or 20 fast steps.','Hold a wall sit for 20 seconds.','Do 10 arm circles forward and 10 backward.','Cool down with slow walking and gentle stretching.'], parent_check:true })
  });
  await safeEnsureAssignment(lesson.id, { title: 'Complete a Backyard Fitness Circuit', description: 'Do a safe homeschool fitness circuit and reflect on how it felt.', instructions: 'Read the safety notes, complete the movement activity, then answer the reflection question.', assignment_type: 'activity', weight_category: 'homework', point_value: 100, questions: [wr(1, 'Which movement did you complete, and how did it help your body? Write 2 to 3 sentences.')] }, 'grade5-pe-activity');
  await safeEnsureAssignment(lesson.id, { title: 'Fitness and Safety Check', description: 'Check your understanding of warm-ups, safety, and how movement helps your body.', instructions: 'Answer these questions after the PE lesson and movement activity.', assignment_type: 'quiz', weight_category: 'quiz', point_value: 100, questions: [mc(1, 'Why should you warm up before exercise?', 'To get your muscles, heart, and lungs ready to move.', ['To make your body more tired before starting.', 'To skip the exercise.', 'To make shoes unnecessary.']), mc(2, 'Which is a safe choice before doing PE at home?', 'Clear space, wear safe shoes, and ask a parent before outdoor activity.', ['Run through a crowded room.', 'Exercise if something hurts badly.', 'Ignore water and safety.']), wr(3, 'Explain how one movement from the circuit can help your heart, lungs, or muscles.')] }, 'grade5-pe-quiz');

  // HEALTH GRADE 5
  unit = await ensureUnit(created.health.id, 1, 'Food Groups and Healthy Choices', 'Learn how food groups, water, sleep, and movement support the body.');
  lesson = await ensureLesson(unit.id, 1, {
    title: 'Build a Balanced Plate', description: 'Learn food groups and how balanced meals support energy and health.', lesson_type: 'health_lesson', interactive_type: 'plate_builder', skill_name: 'Nutrition Basics', video_title: 'Food Groups and MyPlate', video_source: 'Educational YouTube Video', video_url: 'https://www.youtube.com/watch?v=L7QOUiQCb5E', video_verified: true,
    learning_objectives: 'Name the five MyPlate food groups. Explain how different foods help the body. Build a balanced plate idea.',
    instructional_content: 'A balanced meal includes different food groups that help your body in different ways. Fruits and vegetables give vitamins, minerals, and fiber. Grains give energy, especially whole grains. Protein foods help build and repair the body. Dairy or fortified alternatives can support bones with calcium and vitamin D. Water helps your body stay hydrated.\n\nHealthy choices are not about one perfect food. They are about learning how different foods help your body and building habits that support energy, growth, movement, and focus.',
    vocabulary: [{term:'nutrient',definition:'A substance in food that helps the body work and grow.'},{term:'protein',definition:'A nutrient that helps build and repair the body.'},{term:'fiber',definition:'A plant nutrient that helps digestion.'},{term:'hydration',definition:'Having enough water in the body.'},{term:'balanced meal',definition:'A meal with different food groups that work together.'}],
    examples: [{title:'Balanced Plate Example', content:'A plate with chicken, brown rice, carrots, strawberries, and milk includes protein, grains, vegetables, fruit, and dairy.', solution:'Each food group supports the body in a different way.'}],
    guided_practice: [{question:'Name one fruit or vegetable you like and one way it helps your body.', hint:'Fruits and vegetables give vitamins, minerals, and fiber.'}],
    activity_data: visualActivity([
      { title:'Fruits', text:'Often give vitamins, minerals, water, and fiber.', icon:'🍎' },
      { title:'Vegetables', text:'Help the body with vitamins, minerals, and fiber.', icon:'🥦' },
      { title:'Grains', text:'Give energy. Whole grains usually give more fiber.', icon:'🍞' },
      { title:'Protein', text:'Helps build and repair muscles and body tissues.', icon:'🥚' },
      { title:'Dairy', text:'Milk, yogurt, and fortified alternatives can support bones.', icon:'🥛' }
    ], { plate_builder:true, resource_note:'USDA MyPlate is a public nutrition education resource used for general food group learning.' })
  });
  await safeEnsureAssignment(lesson.id, { title: 'Build a Balanced Plate Activity', description: 'Use food groups to design a balanced meal idea.', instructions: 'Study the food group cards, build a balanced plate idea, then answer the questions.', assignment_type: 'activity', weight_category: 'homework', point_value: 100, questions: [mc(1, 'Which food group helps build and repair the body?', 'Protein foods', ['Fruit','Vegetables','Water']), mc(2, 'Which choice best describes a balanced meal?', 'A meal with several food groups that help the body in different ways.', ['A meal with only candy.', 'A meal with only one food every day.', 'A meal with no water or drinks.']), wr(3, 'Create one balanced plate idea and explain what each food group does for the body.')] }, 'grade5-health-activity');
  await safeEnsureAssignment(lesson.id, { title: 'Food Groups and Healthy Choices Quiz', description: 'Show what you learned about food groups, hydration, and balanced choices.', instructions: 'Use the food group cards and lesson to answer these questions.', assignment_type: 'quiz', weight_category: 'quiz', point_value: 100, questions: [mc(1, 'Which food group often gives the body vitamins, minerals, and fiber?', 'Fruits and vegetables', ['Only candy', 'Only water', 'Only empty plates']), mc(2, 'Why does the body need water?', 'Water helps the body stay hydrated and work well.', ['Water replaces all food groups.', 'Water makes sleep unnecessary.', 'Water is only for plants.']), mc(3, 'Which plate is the best balanced choice?', 'A plate with protein, grains, vegetables, fruit, and dairy or a fortified alternative.', ['A plate with only cookies.', 'A plate with only one food every day.', 'A plate with no drinks or food groups.']), wr(4, 'Describe one balanced meal idea and explain how two food groups help the body.')] }, 'grade5-health-quiz');

  // enroll Grade 5 students into these courses
  const students = await pool.query('SELECT id FROM students WHERE grade_level = $1', [grade]);
  for (const st of students.rows) {
    for (const c of Object.values(created)) {
      await safeEnrollStudentInCourse(st.id, c.id);
    }
  }
}

async function addKindergartenElectivesAndHealth() {
  const grade = 0;
  const courses = [
    { key: 'art', name: 'Art Fundamentals (Kindergarten)', subject: 'Art', description: 'Kindergarten visual art lessons with colors, shapes, lines, and simple create-at-home activities.' },
    { key: 'music', name: 'Music Fundamentals (Kindergarten)', subject: 'Music', description: 'Kindergarten music lessons with loud/soft, fast/slow, high/low, and clapping beats.' },
    { key: 'pe', name: 'Physical Education (Kindergarten)', subject: 'Physical Education', description: 'Kindergarten movement lessons for stretching, animal walks, jumping, balance, and rest.' },
    { key: 'health', name: 'Health and Nutrition (Kindergarten)', subject: 'Health and Nutrition', description: 'Kindergarten health lessons about healthy foods, water, handwashing, rest, movement, and safety.' }
  ];
  const created = {};
  for (const c of courses) created[c.key] = await ensureCourse({ ...c, grade_level: grade, course_type: 'elective', credit_hours: 0.5 });

  let unit = await ensureUnit(created.art.id, 1, 'Colors, Shapes, and Lines', 'Kindergarten-friendly art activities.');
  let lesson = await ensureLesson(unit.id, 1, {
    title: 'Make a Picture with Shapes', description: 'Use circles, squares, and triangles to make a simple picture.', lesson_type: 'early_art', interactive_type: 'create_with_shapes', skill_name: 'Colors and Shapes',
    learning_objectives: 'Name simple shapes. Choose colors. Create a picture using shapes.',
    instructional_content: 'Artists can make pictures with shapes. A circle can become a sun. A square can become a house. A triangle can become a roof. Choose colors you like and make a simple picture.',
    vocabulary: [{term:'circle',definition:'A round shape.'},{term:'square',definition:'A shape with four equal sides.'},{term:'triangle',definition:'A shape with three sides.'}],
    materials_needed: 'Paper and crayons or markers.',
    activity_data: visualActivity([{title:'Circle',text:'Round like a ball or sun.',icon:'⭕'},{title:'Square',text:'Four equal sides.',icon:'⬛'},{title:'Triangle',text:'Three sides.',icon:'🔺'}], { activity_steps:['Draw one circle.','Draw one square.','Draw one triangle.','Turn the shapes into a picture.','Color your picture.'] })
  });
  await safeEnsureAssignment(lesson.id, { title: 'Shape Picture Art Activity', description: 'Create a simple picture using shapes.', instructions: 'Look at the shape cards, create your picture, then click complete with parent help if needed.', assignment_type: 'activity', weight_category: 'homework', point_value: 0, questions: [wr(1, 'Tell about your picture. What shapes did you use?')] });

  unit = await ensureUnit(created.music.id, 1, 'Listen and Move', 'Kindergarten music listening and movement.');
  lesson = await ensureLesson(unit.id, 1, { title: 'Loud and Soft Sounds', description: 'Listen for loud and soft sounds.', lesson_type: 'early_music', interactive_type: 'sound_cards', skill_name: 'Loud and Soft', learning_objectives: 'Tell whether a sound is loud or soft. Move safely with music.', instructional_content: 'Sounds can be loud or soft. A drum can sound loud. A whisper can sound soft. Musicians use loud and soft sounds to make music interesting.', vocabulary: [{term:'loud',definition:'A big sound.'},{term:'soft',definition:'A quiet sound.'}], activity_data: visualActivity([{title:'Loud',text:'Big sound. Clap strong one time.',icon:'📣',sound:'loud'},{title:'Soft',text:'Quiet sound. Tap gently two times.',icon:'🤫',sound:'soft'}], { sound_cards:true, activity_steps:['Listen to a loud sound.','Listen to a soft sound.','Clap a loud beat.','Tap a soft beat.'] }) });
  await safeEnsureAssignment(lesson.id, { title: 'Loud and Soft Listening Activity', description: 'Practice loud and soft sounds.', instructions: 'Listen and move with parent help if needed.', assignment_type: 'activity', weight_category: 'homework', point_value: 0, questions: [mc(1, 'Which word means a quiet sound?', 'Soft', ['Loud','Fast','Jump'])] });

  unit = await ensureUnit(created.pe.id, 1, 'Stretch and Move', 'Kindergarten homeschool PE movement.');
  lesson = await ensureLesson(unit.id, 1, { title: 'Stretch Up High', description: 'Stretch, balance, and move safely.', lesson_type: 'movement_activity', interactive_type: 'early_movement', skill_name: 'Stretching and Balance', learning_objectives: 'Stretch safely. Balance on one foot. Move and rest.', instructional_content: 'Your body needs movement and rest. Stretching helps your muscles get ready. Balance helps your body stay steady.', safety_notes: 'Clear space. Move slowly. Stop if anything hurts. Ask a grown-up for help.', activity_data: visualActivity([{title:'Stand Tall',text:'Stand with your feet on the floor.',icon:'🧍'},{title:'Stretch Up',text:'Reach your arms up high.',icon:'🙆'},{title:'Balance',text:'Try standing on one foot for 3 seconds.',icon:'⚖️'}], { activity_steps:['Stand tall.','Reach up high.','Touch your toes if you can.','Balance on one foot.','Take a slow breath.'] }) });
  await safeEnsureAssignment(lesson.id, { title: 'Stretch and Balance Activity', description: 'Complete a simple movement activity.', instructions: 'Move safely with parent help.', assignment_type: 'activity', weight_category: 'homework', point_value: 0, questions: [wr(1, 'What movement did you do today?')] });

  unit = await ensureUnit(created.health.id, 1, 'Healthy Habits', 'Kindergarten healthy choices.');
  lesson = await ensureLesson(unit.id, 1, { title: 'Healthy Foods and Water', description: 'Learn about foods and water that help the body.', lesson_type: 'early_health', interactive_type: 'food_cards', skill_name: 'Healthy Habits', learning_objectives: 'Name healthy foods. Tell why water helps the body.', instructional_content: 'Food gives your body energy to play and learn. Fruits and vegetables help your body grow. Water helps your body stay ready to move.', vocabulary: [{term:'energy',definition:'What helps your body move and play.'},{term:'water',definition:'A drink your body needs.'}], activity_data: visualActivity([{title:'Fruit',text:'Fruit helps your body with vitamins.',icon:'🍎'},{title:'Vegetables',text:'Vegetables help your body grow strong.',icon:'🥕'},{title:'Water',text:'Water helps your body stay hydrated.',icon:'💧'}], { activity_steps:['Point to a fruit.','Point to a vegetable.','Take a drink of water if you need one.'] }) });
  await safeEnsureAssignment(lesson.id, { title: 'Healthy Foods and Water Activity', description: 'Learn simple healthy habits.', instructions: 'Review the cards and answer with parent help if needed.', assignment_type: 'activity', weight_category: 'homework', point_value: 0, questions: [mc(1, 'Which drink helps your body stay hydrated?', 'Water', ['Paint','Glue','Sand'])] });

  const students = await pool.query('SELECT id FROM students WHERE grade_level = $1', [grade]);
  for (const st of students.rows) {
    for (const c of Object.values(created)) {
      await safeEnrollStudentInCourse(st.id, c.id);
    }
  }
}


// Complete Kindergarten visual foundation: 9 categories/courses with at least 5 activity assignments each.
// These are parent-guided, child-facing, one-question-per-page activities.
function kTtsPlaceholder(text) {
  return 'tts-placeholder:' + encodeURIComponent(String(text || ''));
}
function kSvgGraphic(icon, label) {
  const text = String(label || '').toLowerCase();
  const fallback = `<text x="160" y="116" text-anchor="middle" dominant-baseline="middle" font-size="78" filter="url(#s)">${icon || '⭐'}</text>`;
  const iconText = String(icon || '');
  const repeatedStarCount = (iconText.match(/⭐/g) || []).length;
  const includesAny = (...parts) => parts.some(part => text.includes(part));
  const structured = iconText.match(/^([a-z-]+):(\d+)$/i);
  const itemKind = structured ? structured[1].toLowerCase() : '';
  const itemCount = structured ? Math.max(1, Math.min(12, parseInt(structured[2], 10) || 1)) : 0;
  const repeatPositions = (n) => {
    const cols = n > 8 ? 5 : n > 4 ? 4 : n;
    const rows = Math.ceil(n / cols);
    const gapX = n > 8 ? 38 : n > 4 ? 46 : 54;
    const gapY = 46;
    const startX = 160 - ((Math.min(cols, n) - 1) * gapX) / 2;
    const startY = rows === 1 ? 118 : (118 - ((rows - 1) * gapY) / 2);
    const pos = [];
    for (let i = 0; i < n; i++) pos.push([startX + (i % cols) * gapX, startY + Math.floor(i / cols) * gapY]);
    return pos;
  };
  const appleAt = (x,y,s=1) => `<g transform="translate(${x} ${y}) scale(${s})"><circle cx="-10" cy="6" r="18" fill="#ef4f5f" filter="url(#s)"/><circle cx="8" cy="6" r="18" fill="#e93d4d" filter="url(#s)"/><path d="M0 -14 C4 -28,18 -30,24 -20 C14 -18,7 -17,0 -14 Z" fill="#56c271"/><path d="M0 -16 L5 -32" stroke="#7a4a22" stroke-width="5" stroke-linecap="round"/></g>`;
  const crabAt = (x,y,s=1) => `<g transform="translate(${x} ${y}) scale(${s})"><ellipse cx="0" cy="8" rx="24" ry="18" fill="#ff7043" filter="url(#s)"/><circle cx="-10" cy="-12" r="7" fill="#fff"/><circle cx="10" cy="-12" r="7" fill="#fff"/><circle cx="-10" cy="-12" r="3" fill="#30304d"/><circle cx="10" cy="-12" r="3" fill="#30304d"/><path d="M-22 4 L-38 -8 M22 4 L38 -8" stroke="#ff7043" stroke-width="7" stroke-linecap="round"/><circle cx="-43" cy="-10" r="8" fill="#ff7043"/><circle cx="43" cy="-10" r="8" fill="#ff7043"/><path d="M-10 16 Q0 22 10 16" fill="none" stroke="#7a2d1d" stroke-width="3" stroke-linecap="round"/></g>`;
  const fishAt = (x,y,s=1) => `<g transform="translate(${x} ${y}) scale(${s})"><ellipse cx="0" cy="0" rx="28" ry="18" fill="#ff9f43" filter="url(#s)"/><polygon points="-28,0 -52,-18 -52,18" fill="#ff7f2a"/><circle cx="10" cy="-5" r="5" fill="#fff"/><circle cx="12" cy="-5" r="2" fill="#30304d"/><path d="M8 8 Q18 14 26 8" fill="none" stroke="#7a3d13" stroke-width="3" stroke-linecap="round"/></g>`;
  const blockAt = (x,y,s=1) => `<g transform="translate(${x} ${y}) scale(${s})"><rect x="-24" y="-24" width="48" height="48" rx="10" fill="#60a5fa" filter="url(#s)"/><rect x="-18" y="-18" width="36" height="36" rx="7" fill="#93c5fd"/><circle cx="0" cy="0" r="7" fill="#fff" opacity=".9"/></g>`;
  const starAt = (x,y,s=1) => `<g transform="translate(${x} ${y}) scale(${s})"><polygon points="0,-28 8,-8 30,-8 12,5 18,28 0,14 -18,28 -12,5 -30,-8 -8,-8" fill="#facc15" stroke="#f59e0b" stroke-width="4" filter="url(#s)"/></g>`;
  if (structured) {
    const pos = repeatPositions(itemCount);
    if (itemKind === 'apple') return pos.map(([x,y]) => appleAt(x,y,itemCount > 8 ? .62 : itemCount > 5 ? .72 : .85)).join('');
    if (itemKind === 'crab') return pos.map(([x,y]) => crabAt(x,y,itemCount > 8 ? .55 : itemCount > 5 ? .62 : .75)).join('');
    if (itemKind === 'fish') return pos.map(([x,y]) => fishAt(x,y,itemCount > 8 ? .55 : itemCount > 5 ? .62 : .75)).join('');
    if (itemKind === 'block') return pos.map(([x,y]) => blockAt(x,y,itemCount > 8 ? .55 : itemCount > 5 ? .62 : .75)).join('');
    if (itemKind === 'star') return pos.map(([x,y]) => starAt(x,y,itemCount > 8 ? .45 : itemCount > 5 ? .55 : .65)).join('');
  }
  if (iconText === 'apple' || text.includes('apple')) return appleAt(160,112,1.4);
  if (iconText === 'book' || text.includes('book')) return '<g filter="url(#s)"><rect x="92" y="64" width="68" height="112" rx="10" fill="#5b7bea"/><rect x="160" y="64" width="68" height="112" rx="10" fill="#7dd3fc"/><path d="M160 64 V176" stroke="#fff" stroke-width="6"/><path d="M108 92 H144 M108 114 H144 M176 92 H212 M176 114 H212" stroke="#fff" stroke-width="6" stroke-linecap="round"/></g>';
  if (iconText === 'flag' || includesAny('flag', 'united states symbol', 'country symbol')) return '<g filter="url(#s)"><line x1="84" y1="54" x2="84" y2="188" stroke="#9aa4b2" stroke-width="8" stroke-linecap="round"/><path d="M90 58 C128 42,168 78,226 58 V142 C168 162,128 126,90 142 Z" fill="#fff"/><path d="M90 58 C128 42,168 78,226 58 V70 C168 90,128 54,90 70 Z M90 82 C128 66,168 102,226 82 V94 C168 114,128 78,90 94 Z M90 106 C128 90,168 126,226 106 V118 C168 138,128 102,90 118 Z M90 130 C128 114,168 150,226 130 V142 C168 162,128 126,90 142 Z" fill="#ef4444"/><path d="M90 58 C112 50,132 56,152 64 V102 C132 94,112 88,90 94 Z" fill="#1d4ed8"/><circle cx="104" cy="68" r="2" fill="#fff"/><circle cx="118" cy="70" r="2" fill="#fff"/><circle cx="132" cy="74" r="2" fill="#fff"/><circle cx="104" cy="82" r="2" fill="#fff"/><circle cx="120" cy="84" r="2" fill="#fff"/><circle cx="136" cy="88" r="2" fill="#fff"/></g>';
  if (iconText === 'mail-carrier' || includesAny('mail carrier', 'postal worker', 'brings mail')) return '<g filter="url(#s)"><circle cx="118" cy="74" r="22" fill="#ffd7b0"/><path d="M92 98 H144 L152 176 H84 Z" fill="#2563eb"/><rect x="94" y="44" width="48" height="20" rx="10" fill="#1d4ed8"/><path d="M88 54 H148" stroke="#1d4ed8" stroke-width="10" stroke-linecap="round"/><rect x="142" y="104" width="54" height="34" rx="5" fill="#fff" stroke="#cbd5e1" stroke-width="3"/><path d="M142 104 L169 124 L196 104" fill="none" stroke="#cbd5e1" stroke-width="3"/><rect x="202" y="110" width="58" height="44" rx="10" fill="#3b82f6"/><path d="M202 132 H260" stroke="#1e3a8a" stroke-width="5"/><line x1="226" y1="154" x2="226" y2="184" stroke="#64748b" stroke-width="8" stroke-linecap="round"/><path d="M250 104 V78 H272" stroke="#ef4444" stroke-width="7" stroke-linecap="round"/></g>';
  if (iconText === 'sunny' || includesAny('sunny weather', 'sunny')) return '<g filter="url(#s)"><circle cx="160" cy="112" r="38" fill="#facc15"/><g stroke="#f59e0b" stroke-width="9" stroke-linecap="round"><line x1="160" y1="52" x2="160" y2="30"/><line x1="160" y1="194" x2="160" y2="172"/><line x1="100" y1="112" x2="78" y2="112"/><line x1="242" y1="112" x2="220" y2="112"/><line x1="118" y1="70" x2="102" y2="54"/><line x1="218" y1="170" x2="202" y2="154"/><line x1="118" y1="154" x2="102" y2="170"/><line x1="218" y1="54" x2="202" y2="70"/></g></g>';
  if (iconText === 'rainy' || includesAny('rainy weather', 'rainy', 'rain')) return '<g filter="url(#s)"><ellipse cx="150" cy="86" rx="50" ry="28" fill="#c7d2fe"/><ellipse cx="190" cy="92" rx="46" ry="30" fill="#a5b4fc"/><ellipse cx="120" cy="96" rx="38" ry="24" fill="#bfdbfe"/><g stroke="#3b82f6" stroke-width="7" stroke-linecap="round"><line x1="116" y1="138" x2="106" y2="164"/><line x1="156" y1="138" x2="146" y2="164"/><line x1="196" y1="138" x2="186" y2="164"/></g></g>';
  if (iconText === 'cloudy' || includesAny('cloudy', 'cloud')) return '<g filter="url(#s)"><ellipse cx="138" cy="108" rx="58" ry="32" fill="#dbeafe"/><ellipse cx="184" cy="112" rx="54" ry="34" fill="#bfdbfe"/><ellipse cx="114" cy="122" rx="42" ry="24" fill="#eff6ff"/><rect x="94" y="112" width="136" height="44" rx="22" fill="#dbeafe"/></g>';
  if (iconText === 'cat' || text.includes('cat')) return '<g filter="url(#s)"><circle cx="160" cy="112" r="42" fill="#f8fafc" stroke="#475569" stroke-width="5"/><polygon points="128,78 104,48 116,90" fill="#f8fafc" stroke="#475569" stroke-width="5"/><polygon points="192,78 216,48 204,90" fill="#f8fafc" stroke="#475569" stroke-width="5"/><circle cx="146" cy="108" r="5" fill="#111827"/><circle cx="174" cy="108" r="5" fill="#111827"/><path d="M160 118 Q156 126 148 126 M160 118 Q164 126 172 126" fill="none" stroke="#111827" stroke-width="4" stroke-linecap="round"/><path d="M110 118 H140 M180 118 H210 M112 134 H142 M178 134 H208" stroke="#475569" stroke-width="3" stroke-linecap="round"/></g>';
  if (iconText === 'dog' || text.includes('dog')) return '<g filter="url(#s)"><ellipse cx="160" cy="122" rx="52" ry="42" fill="#c47a3c"/><circle cx="142" cy="104" r="10" fill="#111827"/><circle cx="178" cy="104" r="10" fill="#111827"/><ellipse cx="160" cy="124" rx="14" ry="10" fill="#3f1f0f"/><path d="M132 84 C98 56,86 116,118 128" fill="#8b4513"/><path d="M188 84 C222 56,234 116,202 128" fill="#8b4513"/><path d="M148 142 Q160 154 174 142" fill="none" stroke="#3f1f0f" stroke-width="5" stroke-linecap="round"/></g>';
  if (iconText === 'bear' || text.includes('bear')) return '<g filter="url(#s)"><circle cx="160" cy="116" r="46" fill="#a16207"/><circle cx="118" cy="76" r="20" fill="#a16207"/><circle cx="202" cy="76" r="20" fill="#a16207"/><circle cx="144" cy="108" r="5" fill="#111827"/><circle cx="176" cy="108" r="5" fill="#111827"/><ellipse cx="160" cy="130" rx="22" ry="16" fill="#f0c48a"/><circle cx="160" cy="124" r="6" fill="#111827"/><path d="M150 138 Q160 148 170 138" fill="none" stroke="#111827" stroke-width="4" stroke-linecap="round"/></g>';
  if (repeatedStarCount >= 4) {
    const cols = repeatedStarCount > 10 ? 6 : 5;
    const gapX = repeatedStarCount > 10 ? 34 : 40;
    const gapY = 42;
    const rows = Math.ceil(repeatedStarCount / cols);
    const startX = 160 - ((Math.min(cols, repeatedStarCount) - 1) * gapX) / 2;
    const startY = rows === 1 ? 112 : (112 - ((rows - 1) * gapY) / 2);
    let stars = '';
    for (let i = 0; i < repeatedStarCount; i++) {
      const x = startX + (i % cols) * gapX;
      const y = startY + Math.floor(i / cols) * gapY;
      stars += `<text x="${x}" y="${y}" text-anchor="middle" dominant-baseline="middle" font-size="34" filter="url(#s)">⭐</text>`;
    }
    return stars;
  }
  if (includesAny('open floor', 'safe space')) {
    return '<rect x="74" y="74" width="172" height="116" rx="18" fill="#9be7a4" filter="url(#s)"/><path d="M92 104 H228 M92 134 H228 M92 164 H228" stroke="#ffffff" stroke-width="8" stroke-linecap="round" opacity=".85"/><circle cx="160" cy="132" r="22" fill="#6f5de7" filter="url(#s)"/><path d="M144 132 L122 152 M176 132 L198 152" stroke="#6f5de7" stroke-width="8" stroke-linecap="round"/>';
  }
  if (includesAny('grown-up nearby', 'trusted grown-up', 'ask a grown-up')) {
    return '<circle cx="126" cy="70" r="22" fill="#ffd7b0" filter="url(#s)"/><path d="M104 96 H148 L158 172 H94 Z" fill="#6f5de7" filter="url(#s)"/><circle cx="198" cy="90" r="16" fill="#ffd7b0" filter="url(#s)"/><path d="M182 112 H214 L222 172 H174 Z" fill="#56c271" filter="url(#s)"/><path d="M146 118 C164 132,176 132,190 118" fill="none" stroke="#ff9f43" stroke-width="8" stroke-linecap="round"/>';
  }
  if (text.includes('school')) {
    return '<rect x="86" y="82" width="148" height="92" rx="10" fill="#7bb6ff" filter="url(#s)"/><rect x="142" y="122" width="36" height="52" fill="#fff"/><polygon points="76,84 160,40 244,84" fill="#ef6f6c" filter="url(#s)"/><text x="160" y="112" text-anchor="middle" font-family="Arial" font-size="24" font-weight="900" fill="#fff">ABC</text>';
  }
  if (text.includes('plant')) {
    return '<path d="M160 174 C160 136,160 105,160 74" fill="none" stroke="#2f9e55" stroke-width="10" stroke-linecap="round"/><ellipse cx="130" cy="116" rx="34" ry="18" fill="#50c878" transform="rotate(-28 130 116)" filter="url(#s)"/><ellipse cx="190" cy="100" rx="34" ry="18" fill="#37b26c" transform="rotate(28 190 100)" filter="url(#s)"/><circle cx="160" cy="70" r="28" fill="#ffcc4d" filter="url(#s)"/>';
  }
  if (includesAny('jump', 'jumping', 'jumping jack', 'land', 'hop', 'hops', 'frog hop')) {
    return '<line x1="92" y1="176" x2="228" y2="176" stroke="#c8d8f6" stroke-width="8" stroke-linecap="round"/><circle cx="160" cy="62" r="20" fill="#ffd7b0" filter="url(#s)"/><path d="M148 86 L172 86 L178 128 L142 128 Z" fill="#6f5de7" filter="url(#s)"/><path d="M148 96 L118 120" stroke="#6f5de7" stroke-width="10" stroke-linecap="round"/><path d="M172 96 L204 116" stroke="#6f5de7" stroke-width="10" stroke-linecap="round"/><path d="M155 126 L128 164" stroke="#ff9f43" stroke-width="10" stroke-linecap="round"/><path d="M166 126 L197 154" stroke="#ff9f43" stroke-width="10" stroke-linecap="round"/><path d="M118 118 L106 104 M204 114 L216 100 M126 164 L112 170 M196 154 L210 158" stroke="#8f7cff" stroke-width="6" stroke-linecap="round"/>';
  }
  if (includesAny('balance', 'one foot', 'steady')) {
    return '<line x1="106" y1="180" x2="214" y2="180" stroke="#c8d8f6" stroke-width="8" stroke-linecap="round"/><circle cx="160" cy="62" r="20" fill="#ffd7b0" filter="url(#s)"/><path d="M148 86 L172 86 L178 132 L142 132 Z" fill="#56c271" filter="url(#s)"/><path d="M146 98 L112 112" stroke="#56c271" stroke-width="10" stroke-linecap="round"/><path d="M174 98 L208 112" stroke="#56c271" stroke-width="10" stroke-linecap="round"/><path d="M156 130 L152 180" stroke="#f59f00" stroke-width="10" stroke-linecap="round"/><path d="M166 130 L194 156" stroke="#f59f00" stroke-width="10" stroke-linecap="round"/><circle cx="198" cy="156" r="6" fill="#f59f00"/><path d="M104 112 L98 104 M216 112 L222 104" stroke="#8f7cff" stroke-width="6" stroke-linecap="round"/>';
  }
  if (includesAny('stretch', 'reach up', 'reach your arms', 'arms up', 'touch toes', 'touch your toes')) {
    return '<circle cx="160" cy="60" r="20" fill="#ffd7b0" filter="url(#s)"/><path d="M148 84 L172 84 L176 134 L144 134 Z" fill="#5b7bea" filter="url(#s)"/><path d="M148 94 L126 56" stroke="#5b7bea" stroke-width="10" stroke-linecap="round"/><path d="M172 94 L194 56" stroke="#5b7bea" stroke-width="10" stroke-linecap="round"/><path d="M156 132 L144 176" stroke="#f59f00" stroke-width="10" stroke-linecap="round"/><path d="M166 132 L176 176" stroke="#f59f00" stroke-width="10" stroke-linecap="round"/><line x1="120" y1="182" x2="200" y2="182" stroke="#c8d8f6" stroke-width="8" stroke-linecap="round"/>';
  }
  if (includesAny('clap', 'tap two', 'tap your knees', 'beat')) {
    return '<path d="M108 132 C96 102,112 82,134 86 C148 88,154 104,150 118 L146 156 C144 166,136 174,124 172 C112 170,104 162,102 150 Z" fill="#ffd7b0" filter="url(#s)"/><path d="M212 132 C224 102,208 82,186 86 C172 88,166 104,170 118 L174 156 C176 166,184 174,196 172 C208 170,216 162,218 150 Z" fill="#ffd7b0" filter="url(#s)"/><path d="M160 72 Q176 52 192 72" fill="none" stroke="#8f7cff" stroke-width="8" stroke-linecap="round"/><path d="M128 72 Q144 52 160 72" fill="none" stroke="#8f7cff" stroke-width="8" stroke-linecap="round"/><circle cx="160" cy="104" r="12" fill="#6f5de7" filter="url(#s)"/>';
  }
  if (includesAny('march in place', 'marching', 'march')) {
    return '<line x1="96" y1="182" x2="224" y2="182" stroke="#c8d8f6" stroke-width="8" stroke-linecap="round"/><circle cx="160" cy="62" r="20" fill="#ffd7b0" filter="url(#s)"/><path d="M148 86 L172 86 L178 132 L142 132 Z" fill="#22c55e" filter="url(#s)"/><path d="M148 96 L126 122" stroke="#22c55e" stroke-width="10" stroke-linecap="round"/><path d="M172 96 L196 80" stroke="#22c55e" stroke-width="10" stroke-linecap="round"/><path d="M154 130 L136 174" stroke="#2563eb" stroke-width="10" stroke-linecap="round"/><path d="M168 130 L198 150" stroke="#2563eb" stroke-width="10" stroke-linecap="round"/><path d="M126 174 Q138 168 152 176 M190 150 Q204 150 212 158" fill="none" stroke="#1d4ed8" stroke-width="7" stroke-linecap="round"/><path d="M112 96 Q94 122 112 148 M208 84 Q226 108 210 132" fill="none" stroke="#8f7cff" stroke-width="5" stroke-linecap="round" opacity=".8"/>';
  }
  if (includesAny('tiptoe', 'walk softly', 'softly')) {
    return '<line x1="90" y1="180" x2="230" y2="180" stroke="#c8d8f6" stroke-width="8" stroke-linecap="round"/><circle cx="160" cy="62" r="20" fill="#ffd7b0" filter="url(#s)"/><path d="M148 86 L172 86 L176 130 L144 130 Z" fill="#6f5de7" filter="url(#s)"/><path d="M148 98 L124 118" stroke="#6f5de7" stroke-width="9" stroke-linecap="round"/><path d="M172 98 L196 116" stroke="#6f5de7" stroke-width="9" stroke-linecap="round"/><path d="M155 128 L140 172" stroke="#ff9f43" stroke-width="9" stroke-linecap="round"/><path d="M168 128 L190 166" stroke="#ff9f43" stroke-width="9" stroke-linecap="round"/><path d="M132 172 Q144 164 156 172" fill="none" stroke="#ff9f43" stroke-width="7" stroke-linecap="round"/><path d="M182 166 Q194 160 204 168" fill="none" stroke="#ff9f43" stroke-width="7" stroke-linecap="round"/><text x="160" y="42" text-anchor="middle" font-family="Arial" font-size="18" font-weight="800" fill="#6f5de7">quiet steps</text>';
  }
  if (includesAny('fast steps', 'running shoes', 'fast shoes', 'step', 'steps', 'walk', 'march', 'running')) {
    return '<path d="M102 148 C122 148,132 136,146 136 L182 136 C192 136,198 140,202 148 C206 156,202 166,192 166 L146 166 C134 166,124 160,116 156 C106 152,98 154,94 150 C90 146,94 148,102 148 Z" fill="#5b7bea" filter="url(#s)"/><path d="M146 164 C154 176,164 182,182 182 L224 182" fill="none" stroke="#f59f00" stroke-width="10" stroke-linecap="round"/><path d="M94 106 C112 106,122 94,136 94 L174 94 C184 94,190 98,194 106 C198 114,194 124,184 124 L138 124 C126 124,116 118,108 114 C98 110,90 112,86 108 C82 104,86 106,94 106 Z" fill="#56c271" filter="url(#s)"/><path d="M138 122 C146 134,156 140,174 140 L214 140" fill="none" stroke="#ff9f43" stroke-width="10" stroke-linecap="round"/>';
  }
  if (includesAny('shoe', 'shoes')) {
    return '<path d="M88 146 C110 146,120 132,134 132 L190 132 C204 132,214 140,218 152 C222 164,214 176,198 176 L110 176 C98 176,84 168,80 158 C76 150,80 146,88 146 Z" fill="#5b7bea" filter="url(#s)"/><path d="M144 132 L156 106 L176 110 L180 132" fill="#56c271" filter="url(#s)"/><path d="M104 160 L194 160" stroke="#ffffff" stroke-width="8" stroke-linecap="round"/>';
  }
  if (includesAny('wash', 'soap and water', 'clean hands')) {
    return '<path d="M98 148 C116 136,124 112,140 104 C150 100,160 108,160 120 L160 166 C160 174,154 180,146 180 C128 180,110 170,98 156 Z" fill="#ffd7b0" filter="url(#s)"/><path d="M222 148 C204 136,196 112,180 104 C170 100,160 108,160 120 L160 166 C160 174,166 180,174 180 C192 180,210 170,222 156 Z" fill="#ffd7b0" filter="url(#s)"/><circle cx="144" cy="78" r="18" fill="#ffffff" filter="url(#s)"/><circle cx="172" cy="64" r="14" fill="#ffffff" filter="url(#s)"/><circle cx="188" cy="88" r="10" fill="#ffffff" filter="url(#s)"/><rect x="132" y="58" width="44" height="18" rx="9" fill="#7dd3fc"/>';
  }
  if (includesAny('sleep', 'rest', 'bed', 'calm', 'slow breath', 'cool down')) {
    return '<path d="M88 152 H222" stroke="#c8d8f6" stroke-width="8" stroke-linecap="round"/><rect x="104" y="122" width="112" height="36" rx="18" fill="#dbeafe" filter="url(#s)"/><circle cx="130" cy="106" r="16" fill="#ffd7b0" filter="url(#s)"/><path d="M152 110 C166 124,188 128,210 124" fill="none" stroke="#8f7cff" stroke-width="8" stroke-linecap="round"/><path d="M212 66 C228 66,236 82,228 94 C224 100,218 104,212 104 C218 94,216 78,206 70 C208 68,210 66,212 66 Z" fill="#f6c945" filter="url(#s)"/>';
  }
  const visualChars = Array.from(iconText).filter(ch => ch.trim() && ch !== '\ufe0f');
  const countableChars = visualChars.filter(ch => !['+','-','=','–'].includes(ch));
  if (visualChars.length >= 4 && visualChars.length <= 18) {
    const cols = visualChars.length > 10 ? 6 : Math.min(5, visualChars.length);
    const gapX = visualChars.length > 10 ? 32 : 42;
    const gapY = 40;
    const rows = Math.ceil(visualChars.length / cols);
    const startX = 160 - ((Math.min(cols, visualChars.length) - 1) * gapX) / 2;
    const startY = rows === 1 ? 112 : (112 - ((rows - 1) * gapY) / 2);
    let marks = '';
    visualChars.forEach((ch, i) => {
      const x = startX + (i % cols) * gapX;
      const y = startY + Math.floor(i / cols) * gapY;
      const size = ['+','-','=','–'].includes(ch) ? 36 : (countableChars.length > 10 ? 28 : 34);
      marks += `<text x="${x}" y="${y}" text-anchor="middle" dominant-baseline="middle" font-size="${size}" filter="url(#s)">${ch}</text>`;
    });
    return marks;
  }
  if (text.includes('red')) return '<circle cx="160" cy="112" r="42" fill="#ef3b4d" filter="url(#s)"/><circle cx="145" cy="96" r="10" fill="rgba(255,255,255,.45)"/>';
  if (text.includes('blue')) return '<circle cx="160" cy="112" r="42" fill="#3182e6" filter="url(#s)"/><circle cx="145" cy="96" r="10" fill="rgba(255,255,255,.45)"/>';
  if (text.includes('yellow')) return '<circle cx="160" cy="112" r="42" fill="#f6c945" filter="url(#s)"/><circle cx="145" cy="96" r="10" fill="rgba(255,255,255,.45)"/>';
  if (text.includes('green')) return '<circle cx="160" cy="112" r="42" fill="#46c96f" filter="url(#s)"/><circle cx="145" cy="96" r="10" fill="rgba(255,255,255,.45)"/>';
  if (text.includes('orange')) return '<circle cx="160" cy="112" r="42" fill="#f59e38" filter="url(#s)"/><circle cx="145" cy="96" r="10" fill="rgba(255,255,255,.45)"/>';
  if (text.includes('circle')) return '<circle cx="160" cy="112" r="44" fill="none" stroke="#ef3b6d" stroke-width="16" filter="url(#s)"/>';
  if (text.includes('triangle')) return '<polygon points="160,64 105,158 215,158" fill="#f05283" filter="url(#s)"/>';
  if (text.includes('square')) return '<rect x="116" y="68" width="88" height="88" rx="10" fill="#4a4a58" filter="url(#s)"/>';
  if (text.includes('wavy')) return '<path d="M70 115 C100 75, 130 155, 160 115 S220 75, 250 115" fill="none" stroke="#5b7bea" stroke-width="18" stroke-linecap="round" filter="url(#s)"/>';
  if (text.includes('straight')) return '<path d="M72 116 L248 116" fill="none" stroke="#5b7bea" stroke-width="18" stroke-linecap="round" filter="url(#s)"/>';
  if (text === 'dot' || text.includes(' dot')) return '<circle cx="160" cy="112" r="34" fill="#2e2e38" filter="url(#s)"/>';
  if (text.includes('ball') || text.includes('basketball')) return '<circle cx="160" cy="112" r="48" fill="#f28c28" filter="url(#s)"/><path d="M112 112 H208 M160 64 V160 M127 78 C145 100,145 124,127 146 M193 78 C175 100,175 124,193 146" fill="none" stroke="#6b3e21" stroke-width="6" stroke-linecap="round"/>';
  return fallback;
}
function kSvgImage(icon, label, bg1 = '#fff7d6', bg2 = '#dff7ff', showLabel = true) {
  const safeLabel = String(label || '').replace(/[<>&]/g, '');
  const graphic = kSvgGraphic(icon, label);
  const labelMarkup = showLabel ? `<rect x="44" y="166" width="232" height="42" rx="21" fill="rgba(255,255,255,0.78)"/><text x="160" y="193" text-anchor="middle" font-family="Arial, sans-serif" font-size="19" font-weight="700" fill="#30304d">${safeLabel}</text>` : '';
  const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="320" height="240" viewBox="0 0 320 240">
    <defs><linearGradient id="g" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="${bg1}"/><stop offset="1" stop-color="${bg2}"/></linearGradient><filter id="s" x="-20%" y="-20%" width="140%" height="140%"><feDropShadow dx="0" dy="8" stdDeviation="8" flood-color="#6a5acd" flood-opacity="0.18"/></filter></defs>
    <rect width="320" height="240" rx="34" fill="url(#g)"/>
    <circle cx="252" cy="50" r="34" fill="rgba(255,255,255,0.45)"/>
    <circle cx="64" cy="190" r="44" fill="rgba(255,255,255,0.35)"/>
    ${graphic}
    ${labelMarkup}
  </svg>`;
  return 'data:image/svg+xml;charset=UTF-8,' + encodeURIComponent(svg);
}
function kRotateKindergartenChoices(choices, seedText = '') {
  if (!Array.isArray(choices) || choices.length <= 1) return choices || [];
  const correctIndex = choices.findIndex(c => Array.isArray(c) ? !!c[2] : !!c?.correct);
  let seed = 0;
  String(seedText || '').split('').forEach(ch => { seed += ch.charCodeAt(0); });
  let offset = seed % choices.length;
  // If the correct answer was entered first, avoid leaving it first.
  if (correctIndex === 0 && offset === 0) offset = 1;
  if (offset === 0) return choices;
  return choices.slice(offset).concat(choices.slice(0, offset));
}

function kChoiceVisual(choice, paletteIndex = 0, hideLabelUntilSelected = false, choiceMode = null) {
  const palettes = [ ['#fff2cc','#d9f7ff'], ['#ffe1ec','#e7dcff'], ['#e4ffe8','#fff2d5'], ['#e4f0ff','#fff3d9'] ];
  const asObj = Array.isArray(choice)
    ? { label: choice[0], icon: choice[1], correct: choice[2], ...(choice[3] || {}) }
    : (choice || {});
  const label = asObj.label;
  const icon = asObj.icon;
  const correct = !!asObj.correct;
  const mode = asObj.choice_mode || asObj.mode || choiceMode || 'picture';
  const textOnly = !icon || ['word','letter','sentence','number','text'].includes(String(mode).toLowerCase());
  const colors = palettes[paletteIndex % palettes.length];
  return {
    label,
    correct,
    display_mode: mode,
    force_show_label: textOnly || asObj.force_show_label === true,
    image: textOnly ? null : kSvgImage(icon, label, colors[0], colors[1], true),
    preview_image: (!textOnly && hideLabelUntilSelected) ? kSvgImage(icon, label, colors[0], colors[1], false) : null
  };
}

function kIsKindergartenFinishStep(step) {
  const title = String(step?.title || '').toLowerCase().trim();
  const instruction = String(step?.instruction || '').toLowerCase().trim();
  return title === 'finish' || title === 'finished' || instruction.includes('tap i did this page');
}

function kCloneKindergartenPracticeStep(sourceStep, lesson, copyIndex) {
  const base = { ...(sourceStep || {}) };
  const titleOptions = ['Practice Again', 'Try Another One', 'Show What You Know', 'Keep Practicing', 'Last Practice'];
  const intro = titleOptions[(copyIndex - 1) % titleOptions.length];
  const choices = Array.isArray(base.choices) ? base.choices.map(c => Array.isArray(c) ? [...c] : { ...c }) : null;
  const correct = choices ? choices.find(c => Array.isArray(c) ? c[2] : c?.correct) : null;
  const correctLabel = correct ? (Array.isArray(correct) ? correct[0] : correct.label) : '';
  return {
    ...base,
    title: `${intro} ${copyIndex}`,
    instruction: base.instruction || lesson.instruction || `Practice ${lesson.title}.`,
    skill: base.skill || lesson.skill,
    prompt_icon: base.prompt_icon || base.icon || lesson.icon,
    prompt_label: base.prompt_label || base.image_label || correctLabel || lesson.title,
    choices: choices || undefined,
    done: choices ? false : true
  };
}

function kEnsureMinimumKindergartenSteps(lesson) {
  const raw = Array.isArray(lesson.steps) ? lesson.steps.filter(st => !kIsKindergartenFinishStep(st)) : [];
  const minTasks = (lesson.review || /review|skill check|show what you know/i.test(String(lesson.title || ''))) ? 8 : 5;
  const taskCount = raw.filter(st => !kIsKindergartenFinishStep(st)).length;
  if (taskCount >= minTasks) return raw;

  const choiceStep = raw.find(st => Array.isArray(st.choices) && st.choices.length > 0);
  const doneStep = raw.find(st => st.done) || raw[0] || { title: lesson.title, instruction: lesson.instruction, skill: lesson.skill, done: true };
  const source = choiceStep || doneStep;
  const filled = [...raw];
  let copyIndex = 1;
  while (filled.filter(st => !kIsKindergartenFinishStep(st)).length < minTasks) {
    filled.push(kCloneKindergartenPracticeStep(source, lesson, copyIndex));
    copyIndex++;
  }
  return filled;
}

function kBuildActivityData(lesson) {
  const hideLabels = lesson.hide_choice_labels_preselect !== false;
  const hidePromptLabel = lesson.hide_prompt_label !== false;
  return {
    kind: 'activity_flow',
    mode: 'one_question_per_page',
    title: lesson.title,
    instruction: lesson.instruction,
    hide_choice_labels_preselect: hideLabels,
    lesson_text: lesson.lesson_text || null,
    lesson_audio: lesson.lesson_audio_text ? kTtsPlaceholder(lesson.lesson_audio_text) : null,
    steps: kEnsureMinimumKindergartenSteps(lesson).map((step, idx) => {
      const hasChoices = Array.isArray(step.choices) && step.choices.length > 0;
      const correctChoice = hasChoices ? step.choices.find(c => c && c[2]) : null;
      const stepImageIcon = step.prompt_icon || step.icon || (correctChoice ? correctChoice[1] : null) || lesson.icon || '⭐';
      const stepImageLabel = step.prompt_label || step.image_label || step.title || (correctChoice ? correctChoice[0] : null) || lesson.title;
      const displayTitle = step.prompt_title || (hideLabels && hasChoices ? 'Look closely' : step.title);
      const showImageLabel = step.show_image_label !== undefined ? !!step.show_image_label : (!hidePromptLabel && (!hideLabels || !hasChoices));
      const choiceMode = step.choice_label_mode || step.choice_mode || null;
      return {
        title: displayTitle,
        instruction: step.instruction,
        skill_name: step.skill || lesson.skill,
        audio: kTtsPlaceholder(step.instruction || step.title || lesson.title),
        image: step.no_prompt_image ? null : kSvgImage(stepImageIcon, stepImageLabel, idx % 2 ? '#e7dcff' : '#fff4d8', idx % 2 ? '#d9f7ff' : '#e8ffe6', showImageLabel),
        choices: hasChoices ? kRotateKindergartenChoices(step.choices, `${lesson.title}-${step.title}-${idx}`).map((c, i) => kChoiceVisual(c, i, hideLabels, choiceMode)) : null,
        parent_check: !!step.done
      };
    })
  };
}

async function ensureCompleteKindergartenFoundation() {
  const grade = 0;
  const plans = [
  {
    "key": "math",
    "name": "Kindergarten Math",
    "subject": "Math",
    "course_type": "core",
    "description": "Counting, number recognition, shapes, and simple patterns.",
    "unit_title": "Numbers, Shapes, and Patterns",
    "unit_description": "Kindergarten math activities with one clear question on each page.",
    "lessons": [
      {
        "title": "Count 1 to 5",
        "description": "Count small groups of objects.",
        "skill": "Counts objects to 5",
        "icon": "🔢",
        "instruction": "Count the objects. Choose the matching number.",
        "steps": [
          {
            "title": "Count Apples",
            "instruction": "How many apples do you see?",
            "skill": "Counts three objects",
            "choices": [
              [
                "3",
                "🍎🍎🍎",
                true
              ],
              [
                "1",
                "🍎",
                false
              ],
              [
                "5",
                "🍎🍎🍎🍎🍎",
                false
              ]
            ]
          },
          {
            "title": "Count Stars",
            "instruction": "How many stars do you see?",
            "skill": "Counts four objects",
            "choices": [
              [
                "4",
                "⭐⭐⭐⭐",
                true
              ],
              [
                "2",
                "⭐⭐",
                false
              ],
              [
                "5",
                "⭐⭐⭐⭐⭐",
                false
              ]
            ]
          },
          {
            "title": "Say It",
            "instruction": "Count from 1 to 5 out loud with your grown-up.",
            "skill": "Counts aloud to 5",
            "done": true
          }
        ]
      },
      {
        "title": "Number 1 and Number 2",
        "description": "Recognize and choose numbers 1 and 2.",
        "skill": "Recognizes numbers 1 and 2",
        "icon": "1️⃣",
        "instruction": "Look at the number. Tap the match.",
        "steps": [
          {
            "title": "Find 1",
            "instruction": "Which card shows number 1?",
            "skill": "Recognizes number 1",
            "choices": [
              [
                "1",
                "1️⃣",
                true
              ],
              [
                "3",
                "3️⃣",
                false
              ],
              [
                "5",
                "5️⃣",
                false
              ]
            ]
          },
          {
            "title": "Find 2",
            "instruction": "Which card shows number 2?",
            "skill": "Recognizes number 2",
            "choices": [
              [
                "2",
                "2️⃣",
                true
              ],
              [
                "4",
                "4️⃣",
                false
              ],
              [
                "0",
                "0️⃣",
                false
              ]
            ]
          },
          {
            "title": "Show One",
            "instruction": "Hold up one finger with your grown-up.",
            "skill": "Represents one",
            "done": true
          }
        ]
      },
      {
        "title": "More or Less",
        "description": "Compare two small groups.",
        "skill": "Compares more and less",
        "icon": "⚖️",
        "instruction": "Look at the groups. Choose more or less.",
        "steps": [
          {
            "title": "Which Has More?",
            "instruction": "Which group has more balls?",
            "skill": "Identifies more",
            "choices": [
              [
                "Three Balls",
                "⚽⚽⚽",
                true
              ],
              [
                "One Ball",
                "⚽",
                false
              ]
            ]
          },
          {
            "title": "Which Has Less?",
            "instruction": "Which group has less flowers?",
            "skill": "Identifies less",
            "choices": [
              [
                "One Flower",
                "🌼",
                true
              ],
              [
                "Four Flowers",
                "🌼🌼🌼🌼",
                false
              ]
            ]
          },
          {
            "title": "Point and Tell",
            "instruction": "Point to a group near you that has more than one thing.",
            "skill": "Compares real objects",
            "done": true
          }
        ]
      },
      {
        "title": "Shapes Around Us",
        "description": "Find basic shapes in pictures and the room.",
        "skill": "Identifies basic shapes",
        "icon": "🔺",
        "instruction": "Choose the shape that matches the prompt.",
        "steps": [
          {
            "title": "Find the Square",
            "instruction": "Which shape is a square?",
            "skill": "Identifies square",
            "choices": [
              [
                "Square",
                "◼️",
                true
              ],
              [
                "Circle",
                "⭕",
                false
              ],
              [
                "Triangle",
                "🔺",
                false
              ]
            ]
          },
          {
            "title": "Find the Triangle",
            "instruction": "Which shape has three sides?",
            "skill": "Identifies triangle",
            "choices": [
              [
                "Triangle",
                "🔺",
                true
              ],
              [
                "Circle",
                "⭕",
                false
              ],
              [
                "Rectangle",
                "▭",
                false
              ]
            ]
          },
          {
            "title": "Find One Nearby",
            "instruction": "Find one shape in the room and tell your grown-up.",
            "skill": "Finds shapes in environment",
            "done": true
          }
        ]
      },
      {
        "title": "Simple Patterns",
        "description": "Continue a simple color or object pattern.",
        "skill": "Completes simple patterns",
        "icon": "🔁",
        "instruction": "Look at the pattern. Choose what comes next.",
        "steps": [
          {
            "title": "Color Pattern",
            "instruction": "Red, blue, red, blue. What comes next?",
            "skill": "Completes AB color pattern",
            "choices": [
              [
                "Red",
                "🔴",
                true
              ],
              [
                "Green",
                "🟢",
                false
              ],
              [
                "Yellow",
                "🟡",
                false
              ]
            ]
          },
          {
            "title": "Shape Pattern",
            "instruction": "Circle, square, circle, square. What comes next?",
            "skill": "Completes AB shape pattern",
            "choices": [
              [
                "Circle",
                "⭕",
                true
              ],
              [
                "Triangle",
                "🔺",
                false
              ],
              [
                "Star",
                "⭐",
                false
              ]
            ]
          },
          {
            "title": "Make a Pattern",
            "instruction": "Make a clap, tap, clap, tap pattern.",
            "skill": "Creates simple pattern",
            "done": true
          }
        ]
      }
    ]
  },
  {
    "key": "movement_play",
    "name": "Movement & Play (Kindergarten)",
    "subject": "PE",
    "course_type": "core",
    "description": "Simple movement, body awareness, and active play.",
    "unit_title": "Move Safely",
    "unit_description": "Parent-guided movement and play activities for Kindergarten.",
    "lessons": [
      {
        "title": "Safe Space Check",
        "description": "Learn how to check your space before movement.",
        "skill": "Checks movement space",
        "icon": "🏠",
        "instruction": "Look around before you move. Choose the safe answer.",
        "steps": [
          {
            "title": "Safe Space",
            "instruction": "Which place is safe for movement?",
            "skill": "Chooses safe space",
            "choices": [
              [
                "Open Floor",
                "🟩",
                true
              ],
              [
                "Toys Everywhere",
                "🧸",
                false
              ],
              [
                "Wet Floor",
                "💧",
                false
              ]
            ]
          },
          {
            "title": "Grown-Up Check",
            "instruction": "Ask your grown-up if your space is safe.",
            "skill": "Uses parent safety check",
            "done": true
          },
          {
            "title": "Ready Body",
            "instruction": "Stand tall and take one slow breath.",
            "skill": "Prepares body for movement",
            "done": true
          }
        ]
      },
      {
        "title": "Animal Walks",
        "description": "Move like animals while staying safe.",
        "skill": "Completes animal movements",
        "icon": "🐻",
        "instruction": "Try one movement, then answer one page at a time.",
        "steps": [
          {
            "title": "Bear Walk",
            "instruction": "Try a slow bear walk for three steps.",
            "skill": "Practices bear walk",
            "done": true
          },
          {
            "title": "Which Animal Hops?",
            "instruction": "Which animal can hop?",
            "skill": "Identifies hopping movement",
            "choices": [
              [
                "Frog",
                "🐸",
                true
              ],
              [
                "Fish",
                "🐟",
                false
              ],
              [
                "Snail",
                "🐌",
                false
              ]
            ]
          },
          {
            "title": "Move Safely",
            "instruction": "Show your grown-up your favorite animal move.",
            "skill": "Demonstrates safe movement",
            "done": true
          }
        ]
      },
      {
        "title": "Jump and Land",
        "description": "Practice safe jumping and landing.",
        "skill": "Jumps and lands safely",
        "icon": "🤸",
        "instruction": "Jump with control. Land with soft knees.",
        "steps": [
          {
            "title": "Jump One Time",
            "instruction": "Do one small jump and land softly.",
            "skill": "Completes small jump",
            "done": true
          },
          {
            "title": "Safe Landing",
            "instruction": "Which landing is safer?",
            "skill": "Identifies safe landing",
            "choices": [
              [
                "Soft Knees",
                "🦵",
                true
              ],
              [
                "Fall Down",
                "⬇️",
                false
              ],
              [
                "Jump on Toys",
                "🧸",
                false
              ]
            ]
          },
          {
            "title": "Rest",
            "instruction": "Take one slow breath after jumping.",
            "skill": "Uses rest after movement",
            "done": true
          }
        ]
      },
      {
        "title": "Balance Challenge",
        "description": "Try simple balance activities.",
        "skill": "Practices balance",
        "icon": "🧍",
        "instruction": "Balance helps your body stay steady.",
        "steps": [
          {
            "title": "One-Foot Balance",
            "instruction": "Try standing on one foot for three seconds.",
            "skill": "Balances on one foot",
            "done": true
          },
          {
            "title": "Which Helps Balance?",
            "instruction": "Which choice helps you balance?",
            "skill": "Identifies balance support",
            "choices": [
              [
                "Arms Out",
                "🙆",
                true
              ],
              [
                "Eyes Closed and Run",
                "🙈",
                false
              ],
              [
                "Spin Fast",
                "🌀",
                false
              ]
            ]
          },
          {
            "title": "Try Again",
            "instruction": "Try your balance one more time with help nearby.",
            "skill": "Repeats balance practice",
            "done": true
          }
        ]
      },
      {
        "title": "Rest and Breathe",
        "description": "Learn that rest is part of movement.",
        "skill": "Uses breathing and rest",
        "icon": "🌬️",
        "instruction": "Your body needs movement and rest.",
        "steps": [
          {
            "title": "Slow Breath",
            "instruction": "Take one slow breath in and out.",
            "skill": "Practices slow breathing",
            "done": true
          },
          {
            "title": "When Do We Rest?",
            "instruction": "When should you rest?",
            "skill": "Knows when to rest",
            "choices": [
              [
                "When my body feels tired",
                "😌",
                true
              ],
              [
                "Never",
                "🚫",
                false
              ],
              [
                "Only when running faster",
                "🏃",
                false
              ]
            ]
          },
          {
            "title": "Cool Down",
            "instruction": "Stretch your arms up and relax.",
            "skill": "Completes cool down",
            "done": true
          }
        ]
      }
    ]
  },
  {
    "key": "reading",
    "name": "Reading & Phonics (Kindergarten)",
    "subject": "Reading",
    "course_type": "core",
    "description": "Letters, sounds, sight words, and read-along stories for early readers.",
    "unit_title": "Letters, Sounds, and Little Stories",
    "unit_description": "Short Kindergarten phonics and read-along activities.",
    "lessons": [
      {
        "title": "Letter B Sound",
        "description": "Hear and recognize the beginning sound B.",
        "skill": "Identifies beginning sound B",
        "icon": "🐻",
        "instruction": "Listen for the B sound. Choose the matching picture.",
        "steps": [
          {
            "title": "B Sound",
            "instruction": "Which picture starts with the B sound?",
            "skill": "Identifies B beginning sound",
            "choices": [
              [
                "Ball",
                "🏀",
                true
              ],
              [
                "Cat",
                "🐱",
                false
              ],
              [
                "Sun",
                "☀️",
                false
              ]
            ]
          },
          {
            "title": "Say It",
            "instruction": "Say ball. Listen for the B sound at the beginning.",
            "skill": "Pronounces B sound word",
            "prompt_icon": "🏀",
            "prompt_label": "Ball",
            "done": true
          },
          {
            "title": "Find B",
            "instruction": "Which letter is B?",
            "skill": "Recognizes letter B",
            "choices": [
              [
                "B",
                "🅱️",
                true
              ],
              [
                "M",
                "Ⓜ️",
                false
              ],
              [
                "O",
                "⭕",
                false
              ]
            ]
          }
        ]
      },
      {
        "title": "Rhyming Words",
        "description": "Listen for words that sound alike at the end.",
        "skill": "Recognizes simple rhymes",
        "icon": "🎶",
        "instruction": "Rhyming words sound alike at the end.",
        "steps": [
          {
            "title": "Find a Rhyme",
            "instruction": "Which word rhymes with cat?",
            "skill": "Identifies rhyme with cat",
            "choices": [
              [
                "hat",
                "🎩",
                true
              ],
              [
                "dog",
                "🐶",
                false
              ],
              [
                "sun",
                "☀️",
                false
              ]
            ]
          },
          {
            "title": "Another Rhyme",
            "instruction": "Which word rhymes with ball?",
            "skill": "Identifies rhyme with ball",
            "choices": [
              [
                "tall",
                "📏",
                true
              ],
              [
                "fish",
                "🐟",
                false
              ],
              [
                "book",
                "📘",
                false
              ]
            ]
          },
          {
            "title": "Say It",
            "instruction": "Say cat and hat with your grown-up.",
            "skill": "Says rhyming pair",
            "done": true
          }
        ]
      },
      {
        "title": "Story: Sam and the Ball",
        "description": "Read a very short story and answer one picture question.",
        "skill": "Understands a short story",
        "icon": "📖",
        "instruction": "Listen to the little story. Then answer the question.",
        "steps": [
          {
            "title": "Read Together",
            "instruction": "Sam has a red ball. Sam rolls the ball. The ball goes fast.",
            "skill": "Listens to short story",
            "done": true
          },
          {
            "title": "Story Question",
            "instruction": "What does Sam have?",
            "skill": "Answers story question",
            "choices": [
              [
                "Ball",
                "🏀",
                true
              ],
              [
                "Book",
                "📘",
                false
              ],
              [
                "Hat",
                "🎩",
                false
              ]
            ]
          },
          {
            "title": "Tell It Back",
            "instruction": "Tell your grown-up one thing Sam did.",
            "skill": "Retells story detail",
            "done": true
          }
        ]
      },
      {
        "title": "Sight Word: the",
        "description": "Recognize the sight word the.",
        "skill": "Recognizes sight word the",
        "icon": "👁️",
        "instruction": "Look for the word the.",
        "steps": [
          {
            "title": "Find the Word",
            "instruction": "Which card says the?",
            "skill": "Identifies sight word the",
            "choices": [
              [
                "the",
                "📄",
                true
              ],
              [
                "and",
                "📄",
                false
              ],
              [
                "big",
                "📄",
                false
              ]
            ]
          },
          {
            "title": "Read It",
            "instruction": "Point to the word the and say it.",
            "skill": "Reads sight word the",
            "done": true
          },
          {
            "title": "Use It",
            "instruction": "Listen: the ball. Which word did you hear first?",
            "skill": "Hears sight word in phrase",
            "choices": [
              [
                "the",
                "📄",
                true
              ],
              [
                "ball",
                "🏀",
                false
              ]
            ]
          }
        ]
      },
      {
        "title": "Picture and Sound Match",
        "description": "Match pictures to beginning sounds.",
        "skill": "Matches picture to beginning sound",
        "icon": "🔤",
        "instruction": "Listen for the first sound in the word.",
        "steps": [
          {
            "title": "/m/ Sound",
            "instruction": "Which picture starts with /m/?",
            "skill": "Identifies /m/ beginning sound",
            "choices": [
              [
                "Moon",
                "🌙",
                true
              ],
              [
                "Dog",
                "🐶",
                false
              ],
              [
                "Ball",
                "🏀",
                false
              ]
            ]
          },
          {
            "title": "/s/ Sound",
            "instruction": "Which picture starts with /s/?",
            "skill": "Identifies /s/ beginning sound",
            "choices": [
              [
                "Sun",
                "☀️",
                true
              ],
              [
                "Fish",
                "🐟",
                false
              ],
              [
                "Bear",
                "🐻",
                false
              ]
            ]
          },
          {
            "title": "Say a Sound",
            "instruction": "Say one word that starts like your name or favorite toy.",
            "skill": "Connects sound to real word",
            "done": true
          }
        ]
      }
    ]
  },
  {
    "key": "science",
    "name": "Science & Discovery (Kindergarten)",
    "subject": "Science",
    "course_type": "core",
    "description": "Animals, weather, plants, and simple observations of the world.",
    "unit_title": "Observe the World",
    "unit_description": "Kindergarten science discovery activities with simple observation and sorting.",
    "lessons": [
      {
        "title": "Weather Today",
        "description": "Look outside and name the weather.",
        "skill": "Observes weather",
        "icon": "☀️",
        "instruction": "Look at the weather. Choose what you see.",
        "steps": [
          {
            "title": "Sunny Weather",
            "instruction": "Which picture shows sunny weather?",
            "skill": "Identifies sunny weather",
            "choices": [
              [
                "Sun",
                "☀️",
                true
              ],
              [
                "Rain",
                "🌧️",
                false
              ],
              [
                "Snow",
                "❄️",
                false
              ]
            ]
          },
          {
            "title": "Weather Check",
            "instruction": "Look outside with a grown-up. What do you see?",
            "skill": "Observes real weather",
            "done": true
          },
          {
            "title": "Dress for Weather",
            "instruction": "What helps on a rainy day?",
            "skill": "Matches clothing to weather",
            "choices": [
              [
                "Umbrella",
                "☂️",
                true
              ],
              [
                "Beach Ball",
                "🏖️",
                false
              ],
              [
                "Ice Cream",
                "🍦",
                false
              ]
            ]
          }
        ]
      },
      {
        "title": "Living and Nonliving",
        "description": "Sort things that grow and things that do not.",
        "skill": "Sorts living and nonliving",
        "icon": "🌱",
        "instruction": "Living things grow and need care.",
        "steps": [
          {
            "title": "Living Thing",
            "instruction": "Which one is living?",
            "skill": "Identifies living thing",
            "choices": [
              [
                "Plant",
                "🌱",
                true
              ],
              [
                "Rock",
                "🪨",
                false
              ],
              [
                "Chair",
                "🪑",
                false
              ]
            ]
          },
          {
            "title": "Nonliving Thing",
            "instruction": "Which one is nonliving?",
            "skill": "Identifies nonliving thing",
            "choices": [
              [
                "Rock",
                "🪨",
                true
              ],
              [
                "Dog",
                "🐶",
                false
              ],
              [
                "Flower",
                "🌼",
                false
              ]
            ]
          },
          {
            "title": "Find One",
            "instruction": "Point to one living thing or picture near you.",
            "skill": "Connects living thing to environment",
            "done": true
          }
        ]
      },
      {
        "title": "Plant Needs",
        "description": "Learn what plants need to grow.",
        "skill": "Names basic plant needs",
        "icon": "🌻",
        "instruction": "Plants need helpful things to grow.",
        "steps": [
          {
            "title": "Plant Need",
            "instruction": "What does a plant need?",
            "skill": "Identifies water as plant need",
            "choices": [
              [
                "Water",
                "💧",
                true
              ],
              [
                "Shoes",
                "🏀",
                false
              ],
              [
                "Crayons",
                "🖍️",
                false
              ]
            ]
          },
          {
            "title": "Another Need",
            "instruction": "What else helps many plants grow?",
            "skill": "Identifies sunlight as plant need",
            "choices": [
              [
                "Sunlight",
                "☀️",
                true
              ],
              [
                "Pillow",
                "🛏️",
                false
              ],
              [
                "Drum",
                "🥁",
                false
              ]
            ]
          },
          {
            "title": "Observe",
            "instruction": "Look at a plant or plant picture and tell one thing you notice.",
            "skill": "Makes plant observation",
            "done": true
          }
        ]
      },
      {
        "title": "Animal Homes",
        "description": "Match animals to simple homes.",
        "skill": "Matches animals to homes",
        "icon": "🐦",
        "instruction": "Animals live in different places.",
        "steps": [
          {
            "title": "Bird Home",
            "instruction": "Where can a bird live?",
            "skill": "Matches bird to nest",
            "choices": [
              [
                "Nest",
                "🪺",
                true
              ],
              [
                "Backpack",
                "🎒",
                false
              ],
              [
                "Cup",
                "🥤",
                false
              ]
            ]
          },
          {
            "title": "Fish Home",
            "instruction": "Where does a fish live?",
            "skill": "Matches fish to water",
            "choices": [
              [
                "Water",
                "🌊",
                true
              ],
              [
                "Tree",
                "🌳",
                false
              ],
              [
                "Shoe",
                "👟",
                false
              ]
            ]
          },
          {
            "title": "Tell One",
            "instruction": "Name one animal and where it lives.",
            "skill": "Names animal home",
            "done": true
          }
        ]
      },
      {
        "title": "Sink or Float",
        "description": "Predict whether objects might sink or float.",
        "skill": "Predicts sink or float",
        "icon": "🛟",
        "instruction": "Some things stay on top of water. Some things sink down.",
        "steps": [
          {
            "title": "Float Guess",
            "instruction": "Which object often floats?",
            "skill": "Identifies floating object",
            "choices": [
              [
                "Beach Ball",
                "🏖️",
                true
              ],
              [
                "Rock",
                "🪨",
                false
              ],
              [
                "Coin",
                "🪙",
                false
              ]
            ]
          },
          {
            "title": "Sink Guess",
            "instruction": "Which object often sinks?",
            "skill": "Identifies sinking object",
            "choices": [
              [
                "Rock",
                "🪨",
                true
              ],
              [
                "Leaf",
                "🍃",
                false
              ],
              [
                "Beach Ball",
                "🏖️",
                false
              ]
            ]
          },
          {
            "title": "Parent Demo",
            "instruction": "With a grown-up, choose one safe object and guess sink or float.",
            "skill": "Makes simple science prediction",
            "done": true
          }
        ]
      }
    ]
  },
  {
    "key": "art",
    "name": "Art Fundamentals (Kindergarten)",
    "subject": "Art",
    "course_type": "elective",
    "description": "Kindergarten visual art lessons with colors, shapes, lines, and simple create-at-home activities.",
    "unit_title": "Colors, Shapes, and Lines",
    "unit_description": "Hands-on visual art activities for early learners.",
    "lessons": [
      {
        "title": "Primary Colors",
        "description": "Learn red, yellow, and blue.",
        "skill": "Identifies primary colors",
        "icon": "🎨",
        "instruction": "Primary colors are red, yellow, and blue.",
        "steps": [
          {
            "title": "Find Red",
            "instruction": "Which one is red?",
            "skill": "Identifies red",
            "choices": [
              [
                "Red Circle",
                "🔴",
                true
              ],
              [
                "Blue Circle",
                "🔵",
                false
              ],
              [
                "Yellow Circle",
                "🟡",
                false
              ]
            ]
          },
          {
            "title": "Find Yellow",
            "instruction": "Which one is yellow?",
            "skill": "Identifies yellow",
            "choices": [
              [
                "Yellow Circle",
                "🟡",
                true
              ],
              [
                "Green Circle",
                "🟢",
                false
              ],
              [
                "Purple Circle",
                "🟣",
                false
              ]
            ]
          },
          {
            "title": "Color Point",
            "instruction": "Point to something blue near you if you can.",
            "skill": "Finds blue in environment",
            "done": true
          }
        ]
      },
      {
        "title": "Color Mixing",
        "description": "Notice how two colors can make a new color.",
        "skill": "Explores color mixing",
        "icon": "🌈",
        "instruction": "Colors can mix and make new colors.",
        "steps": [
          {
            "title": "Red and Yellow",
            "instruction": "Red and yellow can make which color?",
            "skill": "Knows red plus yellow makes orange",
            "choices": [
              [
                "Orange",
                "🟠",
                true
              ],
              [
                "Blue",
                "🔵",
                false
              ],
              [
                "White",
                "⚪",
                false
              ]
            ]
          },
          {
            "title": "Blue and Yellow",
            "instruction": "Blue and yellow can make which color?",
            "skill": "Knows blue plus yellow makes green",
            "choices": [
              [
                "Green",
                "🟢",
                true
              ],
              [
                "Red",
                "🔴",
                false
              ],
              [
                "Black",
                "⚫",
                false
              ]
            ]
          },
          {
            "title": "Try It Later",
            "instruction": "Ask a grown-up if you can try mixing colors with crayons or paint later.",
            "skill": "Connects color mixing to art making",
            "done": true
          }
        ]
      },
      {
        "title": "Shape Picture",
        "description": "Use shapes to build a picture.",
        "skill": "Creates picture with shapes",
        "icon": "🏠",
        "instruction": "Shapes can make pictures.",
        "steps": [
          {
            "title": "House Shape",
            "instruction": "Which shape could be a roof?",
            "skill": "Uses triangle as roof",
            "choices": [
              [
                "Triangle",
                "🔺",
                true
              ],
              [
                "Circle",
                "⭕",
                false
              ],
              [
                "Line",
                "➖",
                false
              ]
            ]
          },
          {
            "title": "Sun Shape",
            "instruction": "Which shape could be the sun?",
            "skill": "Uses circle as sun",
            "choices": [
              [
                "Circle",
                "⭕",
                true
              ],
              [
                "Square",
                "◼️",
                false
              ],
              [
                "Star",
                "⭐",
                false
              ]
            ]
          },
          {
            "title": "Draw It",
            "instruction": "Draw one circle, one square, or one triangle.",
            "skill": "Draws basic shape",
            "done": true
          }
        ]
      },
      {
        "title": "Texture Touch",
        "description": "Explore rough, smooth, and soft textures.",
        "skill": "Identifies texture",
        "icon": "🧸",
        "instruction": "Texture is how something feels.",
        "steps": [
          {
            "title": "Soft Texture",
            "instruction": "Which picture can feel soft?",
            "skill": "Identifies soft texture",
            "choices": [
              [
                "Teddy Bear",
                "🧸",
                true
              ],
              [
                "Rock",
                "🪨",
                false
              ],
              [
                "Fork",
                "🍴",
                false
              ]
            ]
          },
          {
            "title": "Rough Texture",
            "instruction": "Which picture can feel rough?",
            "skill": "Identifies rough texture",
            "choices": [
              [
                "Rock",
                "🪨",
                true
              ],
              [
                "Pillow",
                "🛏️",
                false
              ],
              [
                "Cloud",
                "☁️",
                false
              ]
            ]
          },
          {
            "title": "Find Texture",
            "instruction": "Touch a safe object and tell if it feels smooth, rough, or soft.",
            "skill": "Describes texture",
            "done": true
          }
        ]
      },
      {
        "title": "Pattern Art",
        "description": "Make a simple repeating art pattern.",
        "skill": "Creates simple art pattern",
        "icon": "🔁",
        "instruction": "A pattern repeats.",
        "steps": [
          {
            "title": "Find the Pattern",
            "instruction": "Which pattern repeats red, blue?",
            "skill": "Identifies repeating color pattern",
            "choices": [
              [
                "Red Blue Red Blue",
                "🔴🔵🔴🔵",
                true
              ],
              [
                "Red Red Blue Green",
                "🔴🔴🔵🟢",
                false
              ]
            ]
          },
          {
            "title": "Shape Pattern",
            "instruction": "Circle, star, circle, star. What comes next?",
            "skill": "Completes shape pattern",
            "choices": [
              [
                "Circle",
                "⭕",
                true
              ],
              [
                "Square",
                "◼️",
                false
              ],
              [
                "Heart",
                "❤️",
                false
              ]
            ]
          },
          {
            "title": "Make Pattern",
            "instruction": "Draw or tap a two-part pattern with your grown-up.",
            "skill": "Creates pattern",
            "done": true
          }
        ]
      },
      {
        "title": "Color Explorer",
        "description": "Look at bright objects and choose the named color.",
        "skill": "Identifies colors in creative work",
        "icon": "🎨",
        "instruction": "Look closely. Tap the object that matches the color word.",
        "steps": [
          {
            "title": "Find Red",
            "instruction": "Which picture shows red?",
            "skill": "Identifies color red",
            "choices": [
              [
                "Red Apple",
                "🍎",
                true
              ],
              [
                "Blue Ball",
                "🔵",
                false
              ],
              [
                "Yellow Star",
                "⭐",
                false
              ]
            ]
          },
          {
            "title": "Find Blue",
            "instruction": "Which picture shows blue?",
            "skill": "Identifies color blue",
            "choices": [
              [
                "Blue Ball",
                "🔵",
                true
              ],
              [
                "Green Leaf",
                "🍃",
                false
              ],
              [
                "Orange",
                "🍊",
                false
              ]
            ]
          },
          {
            "title": "Parent Share",
            "instruction": "Tell your grown-up one color you found today.",
            "skill": "Talks about colors",
            "done": true
          }
        ]
      },
      {
        "title": "Lines and Shapes",
        "description": "Notice lines and shapes in simple pictures.",
        "skill": "Recognizes line and shape",
        "icon": "🔷",
        "instruction": "Look at each picture. Choose the shape or line that matches.",
        "steps": [
          {
            "title": "Find the Circle",
            "instruction": "Which shape is a circle?",
            "skill": "Identifies circle",
            "choices": [
              [
                "Circle",
                "⭕",
                true
              ],
              [
                "Triangle",
                "🔺",
                false
              ],
              [
                "Square",
                "⬛",
                false
              ]
            ]
          },
          {
            "title": "Find the Wavy Line",
            "instruction": "Which line looks wavy?",
            "skill": "Identifies line type",
            "choices": [
              [
                "Wavy Line",
                "〰️",
                true
              ],
              [
                "Straight Line",
                "➖",
                false
              ],
              [
                "Dot",
                "⚫",
                false
              ]
            ]
          },
          {
            "title": "Make It",
            "instruction": "Draw one shape with your finger in the air.",
            "skill": "Creates simple shape",
            "done": true
          }
        ]
      },
      {
        "title": "Create and Share",
        "description": "Make a simple picture or sound pattern and tell about it.",
        "skill": "Completes simple creative activity",
        "icon": "🌈",
        "instruction": "Create one small thing. Then tell your grown-up about it.",
        "steps": [
          {
            "title": "Pick One",
            "instruction": "What will you make today?",
            "skill": "Chooses creative activity",
            "choices": [
              [
                "Picture",
                "🖼️",
                true
              ],
              [
                "Sound Pattern",
                "🎶",
                true
              ],
              [
                "Color Design",
                "🌈",
                true
              ]
            ]
          },
          {
            "title": "Create It",
            "instruction": "Draw, clap, tap, or color your idea.",
            "skill": "Creates simple activity",
            "done": true
          },
          {
            "title": "Tell About It",
            "instruction": "Tell your grown-up what you made.",
            "skill": "Describes creative work",
            "done": true
          }
        ]
      }
    ]
  },
  {
    "key": "health",
    "name": "Health and Nutrition (Kindergarten)",
    "subject": "Health and Nutrition",
    "course_type": "elective",
    "description": "Kindergarten health lessons about healthy foods, water, handwashing, rest, movement, and safety.",
    "unit_title": "Healthy Habits",
    "unit_description": "Simple health and nutrition activities for Kindergarten.",
    "lessons": [
      {
        "title": "Healthy Foods",
        "description": "Name fruits and vegetables that help the body.",
        "skill": "Identifies healthy foods",
        "icon": "🍎",
        "instruction": "Foods help your body learn, play, and grow.",
        "steps": [
          {
            "title": "Fruit Choice",
            "instruction": "Which one is a fruit?",
            "skill": "Identifies fruit",
            "choices": [
              [
                "Apple",
                "🍎",
                true
              ],
              [
                "Crayon",
                "🖍️",
                false
              ],
              [
                "Shoe",
                "👟",
                false
              ]
            ]
          },
          {
            "title": "Vegetable Choice",
            "instruction": "Which one is a vegetable?",
            "skill": "Identifies vegetable",
            "choices": [
              [
                "Carrot",
                "🥕",
                true
              ],
              [
                "Ball",
                "🏀",
                false
              ],
              [
                "Book",
                "📘",
                false
              ]
            ]
          },
          {
            "title": "Tell One",
            "instruction": "Tell your grown-up one healthy food you like.",
            "skill": "Names healthy food",
            "done": true
          }
        ]
      },
      {
        "title": "Water Helps",
        "description": "Learn why water helps the body.",
        "skill": "Knows water helps body",
        "icon": "💧",
        "instruction": "Water helps your body stay ready.",
        "steps": [
          {
            "title": "Healthy Drink",
            "instruction": "Which drink helps your body stay hydrated?",
            "skill": "Identifies water",
            "choices": [
              [
                "Water",
                "💧",
                true
              ],
              [
                "Paint",
                "🎨",
                false
              ],
              [
                "Glue",
                "🧴",
                false
              ]
            ]
          },
          {
            "title": "When to Drink",
            "instruction": "When might your body need water?",
            "skill": "Connects water to activity",
            "choices": [
              [
                "After playing",
                "🏃",
                true
              ],
              [
                "Only while sleeping",
                "🛌",
                false
              ],
              [
                "Never",
                "🚫",
                false
              ]
            ]
          },
          {
            "title": "Check In",
            "instruction": "Ask your grown-up if you need a drink of water.",
            "skill": "Responds to hydration need",
            "done": true
          }
        ]
      },
      {
        "title": "Handwashing Steps",
        "description": "Practice simple handwashing steps.",
        "skill": "Knows handwashing routine",
        "icon": "🧼",
        "instruction": "Washing hands helps keep hands clean.",
        "steps": [
          {
            "title": "Clean Hands",
            "instruction": "What helps clean your hands?",
            "skill": "Identifies soap and water",
            "choices": [
              [
                "Soap and Water",
                "🧼",
                true
              ],
              [
                "Sand",
                "🏖️",
                false
              ],
              [
                "Markers",
                "🖍️",
                false
              ]
            ]
          },
          {
            "title": "When to Wash",
            "instruction": "When should you wash hands?",
            "skill": "Knows handwashing timing",
            "choices": [
              [
                "Before eating",
                "🍽️",
                true
              ],
              [
                "Only on birthdays",
                "🎂",
                false
              ],
              [
                "Never",
                "🚫",
                false
              ]
            ]
          },
          {
            "title": "Pretend Wash",
            "instruction": "Pretend to wash your hands while counting slowly.",
            "skill": "Practices handwashing routine",
            "done": true
          }
        ]
      },
      {
        "title": "Rest and Sleep",
        "description": "Learn that bodies need rest.",
        "skill": "Knows rest helps body",
        "icon": "🌙",
        "instruction": "Rest helps your body and brain.",
        "steps": [
          {
            "title": "Rest Time",
            "instruction": "Which picture shows rest?",
            "skill": "Identifies rest",
            "choices": [
              [
                "Bed",
                "🛏️",
                true
              ],
              [
                "Drum",
                "🥁",
                false
              ],
              [
                "Jump Rope",
                "🪢",
                false
              ]
            ]
          },
          {
            "title": "Calm Choice",
            "instruction": "Which choice can help before sleep?",
            "skill": "Chooses calm bedtime habit",
            "choices": [
              [
                "Read quietly",
                "📖",
                true
              ],
              [
                "Run fast",
                "🏃",
                false
              ],
              [
                "Yell loudly",
                "📣",
                false
              ]
            ]
          },
          {
            "title": "Slow Breath",
            "instruction": "Take one slow breath with your grown-up.",
            "skill": "Uses calm-down strategy",
            "done": true
          }
        ]
      },
      {
        "title": "Safety Helpers",
        "description": "Know that trusted adults can help.",
        "skill": "Identifies trusted adult for safety",
        "icon": "🛡️",
        "instruction": "A trusted grown-up can help you stay safe.",
        "steps": [
          {
            "title": "Who Can Help?",
            "instruction": "Who can help if you feel unsafe?",
            "skill": "Identifies trusted adult",
            "choices": [
              [
                "Trusted grown-up",
                "🧑‍🏫",
                true
              ],
              [
                "A stranger online",
                "❓",
                false
              ],
              [
                "No one",
                "🚫",
                false
              ]
            ]
          },
          {
            "title": "Safe Choice",
            "instruction": "Which is a safe choice?",
            "skill": "Chooses safe action",
            "choices": [
              [
                "Ask a grown-up",
                "🙋",
                true
              ],
              [
                "Hide a problem",
                "🙈",
                false
              ],
              [
                "Run into the street",
                "🚗",
                false
              ]
            ]
          },
          {
            "title": "Practice Words",
            "instruction": "Say: I need help, please.",
            "skill": "Uses help-seeking words",
            "done": true
          }
        ]
      }
    ]
  },
  {
    "key": "music",
    "name": "Music Fundamentals (Kindergarten)",
    "subject": "Music",
    "course_type": "elective",
    "description": "Kindergarten music lessons with loud/soft, fast/slow, high/low, and clapping beats.",
    "unit_title": "Listen and Move",
    "unit_description": "Kindergarten music listening, rhythm, and movement.",
    "lessons": [
      {
        "title": "Loud and Soft",
        "description": "Tell loud and soft sounds apart.",
        "skill": "Recognizes loud and soft",
        "icon": "📣",
        "instruction": "Sounds can be loud or soft.",
        "steps": [
          {
            "title": "Loud Sound",
            "instruction": "Which picture can be loud?",
            "skill": "Identifies loud sound",
            "choices": [
              [
                "Drum",
                "🥁",
                true
              ],
              [
                "Whisper",
                "🤫",
                false
              ],
              [
                "Pillow",
                "🛏️",
                false
              ]
            ]
          },
          {
            "title": "Soft Sound",
            "instruction": "Which picture can be soft?",
            "skill": "Identifies soft sound",
            "choices": [
              [
                "Whisper",
                "🤫",
                true
              ],
              [
                "Fire Truck",
                "🚒",
                false
              ],
              [
                "Drum",
                "🥁",
                false
              ]
            ]
          },
          {
            "title": "Try It",
            "instruction": "Clap once loudly, then tap softly.",
            "skill": "Makes loud and soft sounds",
            "done": true
          }
        ]
      },
      {
        "title": "Fast and Slow",
        "description": "Hear and move to fast and slow music.",
        "skill": "Recognizes fast and slow",
        "icon": "🏃",
        "instruction": "Music can move fast or slow.",
        "steps": [
          {
            "title": "Fast Movement",
            "instruction": "Which picture shows fast?",
            "skill": "Identifies fast",
            "choices": [
              [
                "Running Shoes",
                "👟",
                true
              ],
              [
                "Sleeping Moon",
                "🌙",
                false
              ],
              [
                "Turtle Resting",
                "🐢",
                false
              ]
            ]
          },
          {
            "title": "Slow Movement",
            "instruction": "Which picture shows slow?",
            "skill": "Identifies slow",
            "choices": [
              [
                "Turtle",
                "🐢",
                true
              ],
              [
                "Rocket",
                "🚀",
                false
              ],
              [
                "Race Car",
                "🏎️",
                false
              ]
            ]
          },
          {
            "title": "Move It",
            "instruction": "March slowly for three steps.",
            "skill": "Moves to slow beat",
            "done": true
          }
        ]
      },
      {
        "title": "High and Low Sounds",
        "description": "Listen for high and low sounds.",
        "skill": "Recognizes high and low sounds",
        "icon": "🎹",
        "instruction": "Some sounds are high. Some sounds are low.",
        "steps": [
          {
            "title": "High Sound",
            "instruction": "Which picture can make a high sound?",
            "skill": "Identifies high sound",
            "choices": [
              [
                "Bird Chirp",
                "🐦",
                true
              ],
              [
                "Big Drum",
                "🥁",
                false
              ],
              [
                "Thunder",
                "⛈️",
                false
              ]
            ]
          },
          {
            "title": "Low Sound",
            "instruction": "Which picture can make a low sound?",
            "skill": "Identifies low sound",
            "choices": [
              [
                "Big Drum",
                "🥁",
                true
              ],
              [
                "Tiny Bell",
                "🔔",
                false
              ],
              [
                "Bird",
                "🐦",
                false
              ]
            ]
          },
          {
            "title": "Voice Try",
            "instruction": "Use a high voice, then a low voice with your grown-up.",
            "skill": "Experiments with pitch",
            "done": true
          }
        ]
      },
      {
        "title": "Steady Beat",
        "description": "Keep a simple steady beat.",
        "skill": "Keeps steady beat",
        "icon": "🥁",
        "instruction": "A beat stays steady like a heartbeat.",
        "steps": [
          {
            "title": "Tap Beat",
            "instruction": "Tap your knees four times.",
            "skill": "Taps steady beat",
            "done": true
          },
          {
            "title": "Which Keeps Beat?",
            "instruction": "Which picture helps keep a beat?",
            "skill": "Identifies beat instrument",
            "choices": [
              [
                "Drum",
                "🥁",
                true
              ],
              [
                "Banana",
                "🍌",
                false
              ],
              [
                "Cloud",
                "☁️",
                false
              ]
            ]
          },
          {
            "title": "Clap Beat",
            "instruction": "Clap four steady claps.",
            "skill": "Claps steady beat",
            "done": true
          }
        ]
      },
      {
        "title": "Instrument Sounds",
        "description": "Match simple instruments to music-making.",
        "skill": "Identifies simple instruments",
        "icon": "🎸",
        "instruction": "Instruments make music sounds.",
        "steps": [
          {
            "title": "Music Maker",
            "instruction": "Which one is an instrument?",
            "skill": "Identifies instrument",
            "choices": [
              [
                "Guitar",
                "🎸",
                true
              ],
              [
                "Shoe",
                "👟",
                false
              ],
              [
                "Apple",
                "🍎",
                false
              ]
            ]
          },
          {
            "title": "Drum Sound",
            "instruction": "Which instrument can you tap?",
            "skill": "Identifies drum",
            "choices": [
              [
                "Drum",
                "🥁",
                true
              ],
              [
                "Book",
                "📘",
                false
              ],
              [
                "Moon",
                "🌙",
                false
              ]
            ]
          },
          {
            "title": "Pretend Play",
            "instruction": "Pretend to play an instrument for three seconds.",
            "skill": "Pretends instrument play",
            "done": true
          }
        ]
      },
      {
        "title": "Clap the Beat",
        "description": "Practice steady beat with claps and taps.",
        "skill": "Recognizes steady beat",
        "icon": "👏",
        "instruction": "Listen to the rhythm. Do one action before moving on.",
        "steps": [
          {
            "title": "Clap Once",
            "instruction": "Clap one time with your grown-up.",
            "skill": "Claps one beat",
            "done": true
          },
          {
            "title": "Tap Two Times",
            "instruction": "Tap your knees two times.",
            "skill": "Taps a simple beat",
            "done": true
          },
          {
            "title": "Which Is Music?",
            "instruction": "Which picture makes music?",
            "skill": "Connects sound to music",
            "choices": [
              [
                "Drum",
                "🥁",
                true
              ],
              [
                "Sock",
                "🧦",
                false
              ],
              [
                "Chair",
                "🪑",
                false
              ]
            ]
          }
        ]
      },
      {
        "title": "Sound Feelings",
        "description": "Match sounds with feelings and movement.",
        "skill": "Connects music and feeling",
        "icon": "🎵",
        "instruction": "Music can feel happy, calm, fast, or slow. Choose one answer on each page.",
        "steps": [
          {
            "title": "Happy Sound",
            "instruction": "Which picture can show happy music?",
            "skill": "Identifies happy music feeling",
            "choices": [
              [
                "Smiling Face",
                "😊",
                true
              ],
              [
                "Sleeping Moon",
                "🌙",
                false
              ],
              [
                "Rain Cloud",
                "🌧️",
                false
              ]
            ]
          },
          {
            "title": "Calm Sound",
            "instruction": "Which picture can show calm music?",
            "skill": "Identifies calm music feeling",
            "choices": [
              [
                "Quiet Moon",
                "🌙",
                true
              ],
              [
                "Loud Drum",
                "🥁",
                false
              ],
              [
                "Fast Shoes",
                "👟",
                false
              ]
            ]
          },
          {
            "title": "Move With It",
            "instruction": "Move slowly like calm music for three seconds.",
            "skill": "Moves to music feeling",
            "done": true
          }
        ]
      }
    ]
  },
  {
    "key": "pe",
    "name": "Physical Education (Kindergarten)",
    "subject": "Physical Education",
    "course_type": "elective",
    "description": "Kindergarten movement lessons for stretching, animal walks, jumping, balance, and rest.",
    "unit_title": "Stretch and Move",
    "unit_description": "Simple PE activities for safe movement at home.",
    "lessons": [
      {
        "title": "Stretch Up High",
        "description": "Reach, stretch, and move slowly.",
        "skill": "Completes simple stretch",
        "icon": "🙆",
        "instruction": "Stretch slowly and safely.",
        "steps": [
          {
            "title": "Reach Up",
            "instruction": "Reach your arms up high.",
            "skill": "Reaches overhead",
            "done": true
          },
          {
            "title": "Touch Toes",
            "instruction": "Try touching your toes or your knees.",
            "skill": "Practices gentle stretch",
            "done": true
          },
          {
            "title": "Which Is Stretching?",
            "instruction": "Which picture shows stretching?",
            "skill": "Identifies stretching",
            "choices": [
              [
                "Arms Up",
                "🙆",
                true
              ],
              [
                "Sleeping",
                "🛌",
                false
              ],
              [
                "Eating",
                "🍽️",
                false
              ]
            ]
          }
        ]
      },
      {
        "title": "Balance on One Foot",
        "description": "Practice safe balance.",
        "skill": "Balances safely",
        "icon": "🧍",
        "instruction": "Balance near a grown-up or a safe wall.",
        "steps": [
          {
            "title": "Balance Try",
            "instruction": "Stand on one foot for three seconds.",
            "skill": "Balances on one foot",
            "done": true
          },
          {
            "title": "Safe Balance",
            "instruction": "What helps you balance safely?",
            "skill": "Chooses safe balance support",
            "choices": [
              [
                "Grown-up nearby",
                "🧑‍🏫",
                true
              ],
              [
                "Slippery floor",
                "💧",
                false
              ],
              [
                "Toys under feet",
                "🧸",
                false
              ]
            ]
          },
          {
            "title": "Switch Feet",
            "instruction": "Try the other foot if you can.",
            "skill": "Repeats balance on other foot",
            "done": true
          }
        ]
      },
      {
        "title": "Jumping Jacks or Fast Steps",
        "description": "Move your body and notice your heart working.",
        "skill": "Completes active movement",
        "icon": "🤸",
        "instruction": "Choose the safe movement for your space.",
        "steps": [
          {
            "title": "Pick Movement",
            "instruction": "Which movement can be done in one safe spot?",
            "skill": "Chooses safe cardio movement",
            "choices": [
              [
                "Fast Steps",
                "👟",
                true
              ],
              [
                "Climb Table",
                "🪑",
                false
              ],
              [
                "Run Outside Alone",
                "🚫",
                false
              ]
            ]
          },
          {
            "title": "Move",
            "instruction": "Do ten fast steps or five jumping jacks.",
            "skill": "Completes active movement",
            "done": true
          },
          {
            "title": "Body Check",
            "instruction": "Put your hand near your heart. Is it beating faster?",
            "skill": "Notices heart rate",
            "done": true
          }
        ]
      },
      {
        "title": "Animal Movements",
        "description": "Move like animals with control.",
        "skill": "Performs animal movements",
        "icon": "🐸",
        "instruction": "Try safe animal movements.",
        "steps": [
          {
            "title": "Frog Hop",
            "instruction": "Try one small frog hop.",
            "skill": "Completes frog hop",
            "done": true
          },
          {
            "title": "Crab Walk",
            "instruction": "Try a crab walk or pretend crab movement.",
            "skill": "Practices crab movement",
            "done": true
          },
          {
            "title": "Which Animal Hops?",
            "instruction": "Which animal hops?",
            "skill": "Matches animal to movement",
            "choices": [
              [
                "Frog",
                "🐸",
                true
              ],
              [
                "Fish",
                "🐟",
                false
              ],
              [
                "Bird",
                "🐦",
                false
              ]
            ]
          }
        ]
      },
      {
        "title": "Cool Down",
        "description": "Slow down after movement.",
        "skill": "Completes cool down",
        "icon": "🧘",
        "instruction": "Cool down helps your body rest.",
        "steps": [
          {
            "title": "Slow Walk",
            "instruction": "Walk slowly in place for ten seconds.",
            "skill": "Completes slow walk",
            "done": true
          },
          {
            "title": "Slow Breath",
            "instruction": "Take one slow breath in and out.",
            "skill": "Practices breathing",
            "done": true
          },
          {
            "title": "Cool Down Choice",
            "instruction": "Which is a cool down activity?",
            "skill": "Identifies cool down",
            "choices": [
              [
                "Slow Stretch",
                "🧘",
                true
              ],
              [
                "Run Faster",
                "🏃",
                false
              ],
              [
                "Jump on Bed",
                "🛏️",
                false
              ]
            ]
          }
        ]
      }
    ]
  }
];


  // Kindergarten full-year preview expansion v19.
  // Targets are intentionally activity-based, not heavy graded assignments.
  // Course totals: Reading 45, Math 45, Science 25, Social Studies 20,
  // Art 15, Music 15, Health 10, Physical Education 15, Movement & Play 10 = 200.
  const kVid = {
    phonics: { title: 'Alphabet and Phonics Practice', source: 'Child-focused phonics video', url: 'https://www.youtube.com/watch?v=pU8IWLmRdNM' },
    counting: { title: 'Number Song 1-20 for Children', source: 'The Singing Walrus', url: 'https://www.youtube.com/watch?v=D0Ajq682yrA' },
    addition: { title: 'Addition Using Pictures and Objects', source: 'Kids Academy / early math video', url: 'https://www.youtube.com/watch?v=2lrUvrG7UPI' },
    shapes: { title: 'Shapes for Kids', source: 'Child-focused early math video', url: 'https://www.youtube.com/watch?v=OEbRDtCAFdU' },
    colors: { title: 'Colors Song for Children', source: 'Child-focused color video', url: 'https://www.youtube.com/watch?v=qhOTU8_1Af4' },
    weather: { title: 'Weather for Kids', source: 'Child-focused science video', url: 'https://www.youtube.com/watch?v=tfAB4BXSHOA' },
    plants: { title: 'Plant Life Cycle for Kids', source: 'Child-focused science video', url: 'https://www.youtube.com/watch?v=TE6xptjgNR0' },
    community: { title: 'Community Helpers for Kids', source: 'Child-focused social studies video', url: 'https://www.youtube.com/watch?v=5uTOOQDEMLI' },
    music: { title: 'Musical Instruments for Kids', source: 'Child-focused music video', url: 'https://www.youtube.com/watch?v=7_1_TrP9jwA' },
    exercise: { title: 'Exercise for Kids', source: 'Child-focused movement video', url: 'https://www.youtube.com/watch?v=oc4QS2USKmk' },
    hygiene: { title: 'Wash Your Hands Song', source: 'Child-focused health video', url: 'https://www.youtube.com/watch?v=dDHJW4r3elE' }
  };

  const kLesson = (title, description, skill, icon, instruction, choices, opts = {}) => ({
    title,
    description,
    skill,
    icon,
    instruction,
    video: opts.video || null,
    lesson_text: opts.lesson_text || null,
    lesson_audio_text: opts.lesson_audio_text || opts.lesson_text || null,
    hide_prompt_label: opts.hide_prompt_label !== undefined ? opts.hide_prompt_label : true,
    // Kindergarten picture choices should not give away the answer with labels.
    // Word, letter, number, and sentence tasks still force labels because reading is the skill.
    hide_choice_labels_preselect: opts.hide_choice_labels_preselect !== undefined ? opts.hide_choice_labels_preselect : true,
    review: !!opts.review,
    steps: [
      ...(opts.lesson_text ? [{ title: 'Read or Listen', instruction: opts.lesson_text, skill, prompt_icon: icon, prompt_label: title, done: true }] : []),
      { title: opts.questionTitle || 'Try It', instruction, skill, prompt_icon: opts.prompt_icon || icon, prompt_label: opts.prompt_label || title, show_image_label: opts.show_image_label !== false, choice_label_mode: opts.choice_label_mode || 'picture', choices },
      ...(Array.isArray(opts.extra_steps) ? opts.extra_steps : [])
    ]
  });
  const kLetterSoundLesson = (title, description, skill, icon, instruction, choices, opts = {}) => {
    const correct = (choices || []).find(c => Array.isArray(c) ? c[2] : c?.correct) || choices?.[0];
    const correctLabel = Array.isArray(correct) ? correct[0] : correct?.label;
    const correctIcon = Array.isArray(correct) ? correct[1] : correct?.icon;
    const wrong = (choices || []).filter(c => !(Array.isArray(c) ? c[2] : c?.correct));
    const w1 = wrong[0] || ['Dog', '🐶', false, { choice_mode: 'word' }];
    const w2 = wrong[1] || ['Sun', '☀️', false, { choice_mode: 'word' }];
    return {
      title,
      description,
      skill,
      icon,
      instruction,
      video: opts.video || null,
      lesson_text: opts.lesson_text || null,
      lesson_audio_text: opts.lesson_audio_text || opts.lesson_text || null,
      hide_prompt_label: false,
      hide_choice_labels_preselect: true,
      steps: [
        { title: 'Listen for the Sound', instruction: `${title.replace(' Sound','')} makes the sound at the beginning of ${correctLabel}. Say ${correctLabel}.`, skill, prompt_icon: correctIcon || icon, prompt_label: correctLabel, done: true },
        { title: 'Find the Beginning Sound', instruction, skill, prompt_icon: icon, prompt_label: title, show_image_label: true, choice_label_mode: opts.choice_label_mode || 'word', choices },
        { title: 'Find Another Match', instruction, skill, prompt_icon: correctIcon || icon, prompt_label: correctLabel, show_image_label: true, choice_label_mode: opts.choice_label_mode || 'word', choices: [w1, correct, w2] },
        { title: 'Sound Check', instruction: `Which word starts with the same sound as ${correctLabel}?`, skill, prompt_icon: correctIcon || icon, prompt_label: correctLabel, show_image_label: true, choice_label_mode: opts.choice_label_mode || 'word', choices: [w2, w1, correct] },
        { title: 'Final Sound Practice', instruction, skill, prompt_icon: icon, prompt_label: title, show_image_label: true, choice_label_mode: opts.choice_label_mode || 'word', choices: [correct, w2, w1] }
      ]
    };
  };
  const C = (label, icon, correct, extra = {}) => [label, icon, correct, extra];
  const word = (label, correct) => [label, null, correct, { choice_mode: 'word' }];
  const num = (label, correct) => [label, null, correct, { choice_mode: 'number' }];
  const sentence = (label, correct) => [label, null, correct, { choice_mode: 'sentence' }];

  function pushKLessons(key, lessons) {
    const plan = plans.find(p => p.key === key);
    if (plan) plan.lessons.push(...lessons);
  }
  function trimKLessons(key, target) {
    const plan = plans.find(p => p.key === key);
    if (plan && plan.lessons.length > target) plan.lessons = plan.lessons.slice(0, target);
  }

  // Kindergarten unit organization v20.
  // This keeps the full-year preview clean by grouping activities inside each course
  // instead of showing one long mixed list.
  const kUnitMeta = {
    reading: {
      'Letter Names and Sounds': 'Recognize letters and hear beginning sounds.',
      'Rhyming and Word Play': 'Listen for rhymes and playful word patterns.',
      'Sight Words and Early Words': 'Recognize simple sight words and early print.',
      'Read-Aloud Stories': 'Listen to short stories and answer simple questions.'
    },
    math: {
      'Counting and Numbers': 'Count objects and recognize numbers.',
      'Comparing Numbers': 'Compare groups using more, less, same, one more, and one less.',
      'Shapes and Colors': 'Recognize shapes and colors in pictures.',
      'Addition and Subtraction with Pictures': 'Use objects and pictures to add and take away.',
      'Patterns, Sorting, and Measurement': 'Sort, make patterns, and compare size or length.'
    },
    science: {
      'Weather and Seasons': 'Observe weather, seasons, and the sky.',
      'Plants, Animals, and Living Things': 'Learn about living things, plants, animals, and homes.',
      'Simple Science Discovery': 'Use observation, senses, sink/float, and simple investigations.'
    },
    social: {
      'Community Helpers and Places': 'Learn about helpers, places, and people in a community.',
      'Maps and Directions': 'Practice simple map ideas and direction words.',
      'Good Citizenship and Safety': 'Learn rules, kindness, safety, and responsibility.',
      'Needs, Wants, Goods, and Services': 'Sort needs and wants and learn simple economics.'
    },
    art: {
      'Colors and Color Mixing': 'Identify colors and explore color mixing.',
      'Lines, Shapes, and Drawing': 'Use lines and shapes to create simple pictures.',
      'Texture, Pattern, and Create': 'Explore texture, patterns, and simple art projects.'
    },
    music: {
      'Listening Skills': 'Hear loud/soft, fast/slow, and high/low sounds.',
      'Beat and Rhythm': 'Clap, tap, and move to a steady beat.',
      'Instruments and Music Makers': 'Recognize instruments and music-making sounds.'
    },
    health: {
      'Healthy Food and Water': 'Choose healthy foods and know why water helps.',
      'Hygiene and Clean Habits': 'Practice handwashing, brushing teeth, and clean routines.',
      'Rest, Feelings, and Safety': 'Learn rest, calm-down choices, and trusted adults.'
    },
    pe: {
      'Warm Up and Body Control': 'Warm up, stretch, balance, and move safely.',
      'Movement Skills': 'Practice jumping, hopping, throwing, catching, and cool down.',
      'Sportsmanship and Safe Play': 'Practice safe play, kindness, and good game habits.'
    },
    movement_play: {
      'Movement Games': 'Move, freeze, march, tiptoe, and follow directions.',
      'Animal and Creative Movement': 'Use animal walks and big/small creative movements.'
    }
  };

  function kUnitForLesson(planKey, item) {
    const t = String(item?.title || '').toLowerCase();
    const sk = String(item?.skill || '').toLowerCase();
    const txt = `${t} ${sk}`;
    if (planKey === 'reading') {
      if (/story|read|retell|sam/.test(txt)) return 'Read-Aloud Stories';
      if (/rhyme|rhym/.test(txt)) return 'Rhyming and Word Play';
      if (/sight|word|the|and|i|see|early/.test(txt)) return 'Sight Words and Early Words';
      return 'Letter Names and Sounds';
    }
    if (planKey === 'math') {
      if (/add|plus|take away|subtract|minus/.test(txt)) return 'Addition and Subtraction with Pictures';
      if (/shape|color|circle|square|triangle|rectangle/.test(txt)) return 'Shapes and Colors';
      if (/pattern|sort|measure|length|size|tall|short|heavy|light/.test(txt)) return 'Patterns, Sorting, and Measurement';
      if (/more|less|same|compare|one more|one less/.test(txt)) return 'Comparing Numbers';
      return 'Counting and Numbers';
    }
    if (planKey === 'science') {
      if (/weather|season|sky|sun|rain|snow|cloud/.test(txt)) return 'Weather and Seasons';
      if (/plant|animal|living|nonliving|home|habitat|grow/.test(txt)) return 'Plants, Animals, and Living Things';
      return 'Simple Science Discovery';
    }
    if (planKey === 'social') {
      if (/map|direction|left|right|near|far|place/.test(txt)) return 'Maps and Directions';
      if (/need|want|goods|service|buy|sell/.test(txt)) return 'Needs, Wants, Goods, and Services';
      if (/rule|citizen|safe|kind|responsib|share|helper words/.test(txt)) return 'Good Citizenship and Safety';
      return 'Community Helpers and Places';
    }
    if (planKey === 'art') {
      if (/color|red|blue|yellow|mix|primary/.test(txt)) return 'Colors and Color Mixing';
      if (/line|shape|draw|picture|circle|triangle|square/.test(txt)) return 'Lines, Shapes, and Drawing';
      return 'Texture, Pattern, and Create';
    }
    if (planKey === 'music') {
      if (/instrument|drum|guitar|bell|music maker/.test(txt)) return 'Instruments and Music Makers';
      if (/beat|rhythm|clap|tap|steady/.test(txt)) return 'Beat and Rhythm';
      return 'Listening Skills';
    }
    if (planKey === 'health') {
      if (/food|water|snack|fruit|vegetable|healthy/.test(txt)) return 'Healthy Food and Water';
      if (/wash|hand|brush|teeth|clean|hygiene/.test(txt)) return 'Hygiene and Clean Habits';
      return 'Rest, Feelings, and Safety';
    }
    if (planKey === 'pe') {
      if (/throw|catch|jump|hop|cool|movement|animal/.test(txt)) return 'Movement Skills';
      if (/sport|game|kind|safe play|sportsmanship/.test(txt)) return 'Sportsmanship and Safe Play';
      return 'Warm Up and Body Control';
    }
    if (planKey === 'movement_play') {
      if (/animal|bear|frog|big|small|creative/.test(txt)) return 'Animal and Creative Movement';
      return 'Movement Games';
    }
    return plan.unit_title || 'Kindergarten Activities';
  }

  const mathMore = [
    ['Count to 6', 'Counts six objects.', 'Counts to 6', '⭐⭐⭐⭐⭐⭐', 'How many stars do you see?', [num('6', true), num('5', false), num('4', false)], { video: kVid.counting, prompt_label:'Six stars', choice_label_mode:'number' }],
    ['Count to 7', 'Counts seven objects.', 'Counts to 7', '⭐⭐⭐⭐⭐⭐⭐', 'How many stars do you see?', [num('7', true), num('6', false), num('8', false)], { prompt_label:'Seven stars', choice_label_mode:'number' }],
    ['Count to 8', 'Counts eight objects.', 'Counts to 8', '⭐⭐⭐⭐⭐⭐⭐⭐', 'How many stars do you see?', [num('8', true), num('7', false), num('9', false)], { prompt_label:'Eight stars', choice_label_mode:'number' }],
    ['Count to 9', 'Counts nine objects.', 'Counts to 9', '⭐⭐⭐⭐⭐⭐⭐⭐⭐', 'How many stars do you see?', [num('9', true), num('8', false), num('10', false)], { prompt_label:'Nine stars', choice_label_mode:'number' }],
    ['Count to 10', 'Counts ten objects.', 'Counts to 10', '⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐', 'How many stars do you see?', [num('10', true), num('9', false), num('11', false)], { prompt_label:'Ten stars', choice_label_mode:'number' }],
    ['Number 0', 'Recognize zero as none.', 'Recognizes 0', '○', 'Which number means none?', [num('0', true), num('1', false), num('2', false)], { choice_label_mode:'number' }],
    ['Number 1', 'Recognize number 1.', 'Recognizes 1', '1', 'Which number is one?', [num('1', true), num('3', false), num('5', false)], { choice_label_mode:'number' }],
    ['Number 2', 'Recognize number 2.', 'Recognizes 2', '2', 'Which number is two?', [num('2', true), num('4', false), num('6', false)], { choice_label_mode:'number' }],
    ['Number 3', 'Recognize number 3.', 'Recognizes 3', '3', 'Which number is three?', [num('3', true), num('5', false), num('8', false)], { choice_label_mode:'number' }],
    ['Number 4', 'Recognize number 4.', 'Recognizes 4', '4', 'Which number is four?', [num('4', true), num('2', false), num('7', false)], { choice_label_mode:'number' }],
    ['Number 5', 'Recognize number 5.', 'Recognizes 5', '5', 'Which number is five?', [num('5', true), num('3', false), num('9', false)], { choice_label_mode:'number' }],
    ['More or Less', 'Compare small groups.', 'Compares more and less', '🍎🍎🍎', 'Which group has more?', [C('Three apples','🍎🍎🍎',true), C('One apple','🍎',false), C('Two apples','🍎🍎',false)]],
    ['Same Amount', 'Find equal groups.', 'Identifies same amount', '🍌🍌', 'Which group has the same amount as two bananas?', [C('Two dots','⚫⚫',true), C('One dot','⚫',false), C('Three dots','⚫⚫⚫',false)]],
    ['One More', 'Find one more.', 'Finds one more', '⭐', 'What is one more than 4?', [num('5', true), num('3', false), num('4', false)], { choice_label_mode:'number' }],
    ['One Less', 'Find one less.', 'Finds one less', '⭐', 'What is one less than 5?', [num('4', true), num('5', false), num('6', false)], { choice_label_mode:'number' }],
    ['Add 1', 'Add one object.', 'Adds one', '🍎+🍎', 'What is 1 apple plus 1 apple?', [num('2', true), num('1', false), num('3', false)], { video:kVid.addition, choice_label_mode:'number' }],
    ['Add 2', 'Add small groups.', 'Adds small groups', '🍎🍎+🍎', 'What is 2 plus 1?', [num('3', true), num('2', false), num('4', false)], { choice_label_mode:'number' }],
    ['Take Away 1', 'Take one away.', 'Subtracts one', '🍪🍪', 'If you have 2 cookies and take away 1, how many are left?', [num('1', true), num('2', false), num('3', false)], { choice_label_mode:'number' }],
    ['Take Away 2', 'Take away from a group.', 'Subtracts small groups', '🍪🍪🍪', 'If you have 3 cookies and take away 1, how many are left?', [num('2', true), num('1', false), num('4', false)], { choice_label_mode:'number' }],
    ['Circle Review', 'Recognize circles.', 'Identifies circle', '⭕', 'Which shape is a circle?', [C('Circle','⭕',true), C('Square','◼️',false), C('Triangle','🔺',false)], { video:kVid.shapes }],
    ['Square Review', 'Recognize squares.', 'Identifies square', '◼️', 'Which shape is a square?', [C('Square','◼️',true), C('Circle','⭕',false), C('Triangle','🔺',false)]],
    ['Triangle Review', 'Recognize triangles.', 'Identifies triangle', '🔺', 'Which shape is a triangle?', [C('Triangle','🔺',true), C('Circle','⭕',false), C('Rectangle','▭',false)]],
    ['Rectangle Review', 'Recognize rectangles.', 'Identifies rectangle', '▭', 'Which shape is a rectangle?', [C('Rectangle','▭',true), C('Circle','⭕',false), C('Triangle','🔺',false)]],
    ['Shape in the World', 'Match shapes to objects.', 'Finds shapes in objects', '🚪', 'A door looks most like which shape?', [C('Rectangle','▭',true), C('Triangle','🔺',false), C('Circle','⭕',false)]],
    ['Pattern AB', 'Complete a simple pattern.', 'Completes AB pattern', '🔴🔵🔴', 'What comes next: red, blue, red, ___?', [C('Blue','🔵',true), C('Red','🔴',false), C('Yellow','🟡',false)]],
    ['Pattern AAB', 'Complete a repeating pattern.', 'Completes AAB pattern', '🟢🟢🟡', 'What comes next: green, green, yellow, green, green, ___?', [C('Yellow','🟡',true), C('Green','🟢',false), C('Blue','🔵',false)]],
    ['Sort by Color', 'Sort objects by color.', 'Sorts by color', '🔴', 'Which item belongs with red things?', [C('Red ball','🔴',true), C('Blue ball','🔵',false), C('Yellow ball','🟡',false)], { video:kVid.colors }],
    ['Sort by Size', 'Sort by big and small.', 'Sorts by size', '🐘', 'Which animal is bigger?', [C('Elephant','🐘',true), C('Mouse','🐭',false), C('Bug','🐞',false)]],
    ['Before and After', 'Tell what comes next.', 'Orders numbers', '1 2 3', 'What number comes after 3?', [num('4', true), num('2', false), num('1', false)], { choice_label_mode:'number' }],
    ['First and Last', 'Use position words.', 'Identifies position', '🚗🚙🚌', 'Which vehicle is first?', [C('Car','🚗',true), C('Bus','🚌',false), C('Truck','🚚',false)]],
    ['Above and Below', 'Use location words.', 'Uses above and below', '☁️', 'The cloud is above. Which word means over?', [word('Above', true), word('Under', false), word('Beside', false)], { choice_label_mode:'word' }],
    ['Near and Far', 'Use distance words.', 'Uses near and far', '🏠', 'Which word means close by?', [word('Near', true), word('Far', false), word('Away', false)], { choice_label_mode:'word' }],
    ['Measure Long', 'Compare length.', 'Compares length', '✏️', 'Which item is longer?', [C('Long pencil','✏️',true), C('Short crayon','🖍️',false), C('Dot','⚫',false)]],
    ['Measure Tall', 'Compare height.', 'Compares height', '🌳', 'Which is taller?', [C('Tree','🌳',true), C('Flower','🌷',false), C('Rock','🪨',false)]],
    ['Heavy and Light', 'Compare weight.', 'Compares weight', '🎒', 'Which is heavier?', [C('Backpack','🎒',true), C('Feather','🪶',false), C('Leaf','🍃',false)]],
    ['Time of Day', 'Tell daily time.', 'Identifies day routine', '☀️', 'When do we usually eat breakfast?', [word('Morning', true), word('Night', false), word('Midnight', false)], { choice_label_mode:'word' }],
    ['Money: Penny', 'Recognize a penny.', 'Identifies penny', '🪙', 'Which coin is a penny?', [word('Penny', true), word('Quarter', false), word('Nickel', false)], { choice_label_mode:'word' }],
    ['Graph Favorite Fruit', 'Read a simple graph.', 'Reads simple graph', '🍎🍌', 'If apples have the most marks, which fruit is the favorite?', [C('Apple','🍎',true), C('Banana','🍌',false), C('Grape','🍇',false)]],
    ['Review Numbers', 'Review counting and numbers.', 'Reviews numbers', '123', 'Which number shows ten?', [num('10', true), num('1', false), num('0', false)], { choice_label_mode:'number' }]
  ].map(x => kLesson(...x));
  pushKLessons('math', mathMore);
  trimKLessons('math', 45);

  const letters = [
    ['Letter A Sound','Recognize A sound.','Recognizes A sound','🍎','Which word starts with A?', [word('Apple',true), word('Ball',false), word('Sun',false)], { video:kVid.phonics, choice_label_mode:'word' }],
    ['Letter B Sound','Recognize B sound.','Recognizes B sound','⚽','Which word starts with B?', [word('Ball',true), word('Cat',false), word('Dog',false)], { choice_label_mode:'word' }],
    ['Letter C Sound','Recognize C sound.','Recognizes C sound','🐱','Which word starts with C?', [word('Cat',true), word('Fish',false), word('Moon',false)], { choice_label_mode:'word' }],
    ['Letter D Sound','Recognize D sound.','Recognizes D sound','🐶','Which word starts with D?', [word('Dog',true), word('Sun',false), word('Apple',false)], { choice_label_mode:'word' }],
    ['Letter E Sound','Recognize E sound.','Recognizes E sound','🐘','Which word starts with E?', [word('Elephant',true), word('Fish',false), word('Cat',false)], { choice_label_mode:'word' }],
    ['Letter F Sound','Recognize F sound.','Recognizes F sound','🐟','Which word starts with F?', [word('Fish',true), word('Dog',false), word('Ball',false)], { choice_label_mode:'word' }],
    ['Letter G Sound','Recognize G sound.','Recognizes G sound','🍇','Which word starts with G?', [word('Grape',true), word('Apple',false), word('Moon',false)], { choice_label_mode:'word' }],
    ['Letter H Sound','Recognize H sound.','Recognizes H sound','🏠','Which word starts with H?', [word('House',true), word('Cat',false), word('Sun',false)], { choice_label_mode:'word' }],
    ['Letter I Sound','Recognize I sound.','Recognizes I sound','🧊','Which word starts with I?', [word('Ice',true), word('Ball',false), word('Dog',false)], { choice_label_mode:'word' }],
    ['Letter J Sound','Recognize J sound.','Recognizes J sound','🥤','Which word starts with J?', [word('Juice',true), word('Fish',false), word('Apple',false)], { choice_label_mode:'word' }],
    ['Letter K Sound','Recognize K sound.','Recognizes K sound','🪁','Which word starts with K?', [word('Kite',true), word('Sun',false), word('Moon',false)], { choice_label_mode:'word' }],
    ['Letter L Sound','Recognize L sound.','Recognizes L sound','🦁','Which word starts with L?', [word('Lion',true), word('Cat',false), word('Dog',false)], { choice_label_mode:'word' }],
    ['Letter M Sound','Recognize M sound.','Recognizes M sound','🌙','Which word starts with M?', [word('Moon',true), word('Apple',false), word('Fish',false)], { choice_label_mode:'word' }],
    ['Letter N Sound','Recognize N sound.','Recognizes N sound','🪺','Which word starts with N?', [word('Nest',true), word('Ball',false), word('Cat',false)], { choice_label_mode:'word' }],
    ['Letter O Sound','Recognize O sound.','Recognizes O sound','🍊','Which word starts with O?', [word('Orange',true), word('Dog',false), word('Sun',false)], { choice_label_mode:'word' }],
    ['Letter P Sound','Recognize P sound.','Recognizes P sound','🐷','Which word starts with P?', [word('Pig',true), word('Apple',false), word('Fish',false)], { choice_label_mode:'word' }],
    ['Letter Q Sound','Recognize Q sound.','Recognizes Q sound','👑','Which word starts with Q?', [word('Queen',true), word('Moon',false), word('Dog',false)], { choice_label_mode:'word' }],
    ['Letter R Sound','Recognize R sound.','Recognizes R sound','🤖','Which word starts with R?', [word('Robot',true), word('Cat',false), word('Sun',false)], { choice_label_mode:'word' }],
    ['Letter S Sound','Recognize S sound.','Recognizes S sound','☀️','Which word starts with S?', [word('Sun',true), word('Fish',false), word('Dog',false)], { choice_label_mode:'word' }],
    ['Letter T Sound','Recognize T sound.','Recognizes T sound','🐢','Which word starts with T?', [word('Turtle',true), word('Apple',false), word('Moon',false)], { choice_label_mode:'word' }],
    ['Letter U Sound','Recognize U sound.','Recognizes U sound','☂️','Which word starts with U?', [word('Umbrella',true), word('Cat',false), word('Dog',false)], { choice_label_mode:'word' }],
    ['Letter V Sound','Recognize V sound.','Recognizes V sound','🎻','Which word starts with V?', [word('Violin',true), word('Sun',false), word('Ball',false)], { choice_label_mode:'word' }],
    ['Letter W Sound','Recognize W sound.','Recognizes W sound','💧','Which word starts with W?', [word('Water',true), word('Fish',false), word('Cat',false)], { choice_label_mode:'word' }],
    ['Letter X Sound','Recognize X sound.','Recognizes X sound','📦','Which word has X?', [word('Box',true), word('Dog',false), word('Sun',false)], { choice_label_mode:'word' }],
    ['Letter Y Sound','Recognize Y sound.','Recognizes Y sound','🪀','Which word starts with Y?', [word('Yo-yo',true), word('Apple',false), word('Fish',false)], { choice_label_mode:'word' }],
    ['Letter Z Sound','Recognize Z sound.','Recognizes Z sound','🦓','Which word starts with Z?', [word('Zebra',true), word('Moon',false), word('Cat',false)], { choice_label_mode:'word' }]
  ].map(x => kLesson(...x));
  const readingMore = [
    ...letters,
    kLesson('Rhyming Cat and Hat','Listen for rhyming words.','Recognizes rhyme','🐱🎩','Which word rhymes with cat?', [word('Hat',true), word('Dog',false), word('Sun',false)], {choice_label_mode:'word'}),
    kLesson('Rhyming Sun and Run','Listen for rhyming words.','Recognizes rhyme','☀️🏃','Which word rhymes with sun?', [word('Run',true), word('Fish',false), word('Book',false)], {choice_label_mode:'word'}),
    kLesson('Syllable Clap','Clap word parts.','Claps syllables','👏','How many claps are in baby?', [num('2',true), num('1',false), num('3',false)], {choice_label_mode:'number'}),
    kLesson('First Sound','Hear the beginning sound.','Identifies beginning sound','🐶','What sound starts dog?', [word('/d/',true), word('/m/',false), word('/s/',false)], {choice_label_mode:'word'}),
    kLesson('Last Sound','Hear the ending sound.','Identifies ending sound','🐱','What sound ends cat?', [word('/t/',true), word('/m/',false), word('/s/',false)], {choice_label_mode:'word'}),
    kLesson('Sight Word I','Read the word I.','Reads sight word I','I','Which word is I?', [word('I',true), word('see',false), word('a',false)], {choice_label_mode:'word'}),
    kLesson('Sight Word See','Read the word see.','Reads sight word see','👀','Which word says see?', [word('see',true), word('the',false), word('and',false)], {choice_label_mode:'word'}),
    kLesson('Sight Word A','Read the word a.','Reads sight word a','a','Which word is a?', [word('a',true), word('I',false), word('see',false)], {choice_label_mode:'word'}),
    kLesson('Sight Word The','Read the word the.','Reads sight word the','the','Which word says the?', [word('the',true), word('and',false), word('to',false)], {choice_label_mode:'word'}),
    kLesson('Picture Sentence','Match a picture to a sentence.','Matches sentence to picture','🐶','Which sentence matches the dog?', [sentence('I see a dog.',true), sentence('I see a fish.',false), sentence('I see a sun.',false)], {choice_label_mode:'sentence'}),
    kLesson('Read a Simple Sentence','Read a short sentence.','Reads simple sentence','🐱','Which sentence is complete?', [sentence('The cat sits.',true), sentence('cat sits',false), sentence('The cat',false)], {choice_label_mode:'sentence'}),
    kLesson('Story: The Red Kite','Listen to a short story.','Answers story detail','🪁','Who had the red kite?', [word('Mia',true), word('Sam',false), word('Dad',false)], {choice_label_mode:'word', lesson_text:'Mia had a red kite. The wind blew softly. Mia ran in the grass, and the kite went up, up, up.'}),
    kLesson('Story: The Little Seed','Listen to a short story.','Answers story detail','🌱','What did the seed need?', [word('Water',true), word('Shoes',false), word('A hat',false)], {choice_label_mode:'word', lesson_text:'A little seed rested in the dirt. Rain fell. The sun shined. Soon the seed began to grow.'}),
    kLesson('Book Parts','Learn parts of a book.','Identifies book cover','📘','Which part is on the front of a book?', [word('Cover',true), word('Backpack',false), word('Shoe',false)], {choice_label_mode:'word'})
  ];
  pushKLessons('reading', readingMore);
  trimKLessons('reading', 45);

  const scienceMore = [
    kLesson('Weather: Sunny','Observe sunny weather.','Identifies sunny weather','☀️','Which picture shows sunny weather?', [C('Sunny','☀️',true), C('Rainy','🌧️',false), C('Snowy','❄️',false)], {video:kVid.weather}),
    kLesson('Weather: Rainy','Observe rainy weather.','Identifies rainy weather','🌧️','What do we use in the rain?', [C('Umbrella','☂️',true), C('Sunglasses','🕶️',false), C('Flip flops','🩴',false)]),
    kLesson('Day Sky','Observe the day sky.','Identifies day sky','☀️','What do you often see in the day sky?', [C('Sun','☀️',true), C('Stars','⭐',false), C('Moon only','🌙',false)]),
    kLesson('Night Sky','Observe the night sky.','Identifies night sky','🌙','What can you see in the night sky?', [C('Moon','🌙',true), C('Lunch box','🍱',false), C('Rainbow only','🌈',false)]),
    kLesson('Living Thing','Sort living things.','Identifies living things','🐶','Which one is a living thing?', [C('Dog','🐶',true), C('Chair','🪑',false), C('Rock','🪨',false)]),
    kLesson('Nonliving Thing','Sort nonliving things.','Identifies nonliving things','🪨','Which one is nonliving?', [C('Rock','🪨',true), C('Flower','🌷',false), C('Bird','🐦',false)]),
    kLesson('Plant Needs','Know what plants need.','Identifies plant needs','🌱','What does a plant need to grow?', [C('Water','💧',true), C('Candy','🍬',false), C('Shoes','👟',false)], {video:kVid.plants}),
    kLesson('Plant Parts','Name plant parts.','Identifies plant parts','🌻','Which part can be underground?', [word('Roots',true), word('Petals',false), word('Clouds',false)], {choice_label_mode:'word'}),
    kLesson('Animal Homes','Match animals to homes.','Matches animal habitats','🐟','Where does a fish live?', [C('Water','💧',true), C('Tree','🌳',false), C('Desert','🏜️',false)]),
    kLesson('Five Senses: See','Use senses.','Identifies sense of sight','👀','Which body part helps you see?', [C('Eyes','👀',true), C('Feet','🦶',false), C('Knees','🦵',false)]),
    kLesson('Five Senses: Hear','Use senses.','Identifies sense of hearing','👂','Which body part helps you hear?', [C('Ears','👂',true), C('Hands','✋',false), C('Elbows','💪',false)]),
    kLesson('Magnets Pull','Observe magnets.','Understands magnets pull','🧲','What can a magnet pull?', [C('Metal clip','📎',true), C('Banana','🍌',false), C('Cloud','☁️',false)]),
    kLesson('Sink or Float','Observe sink and float.','Predicts sink or float','🛟','Which one can float?', [C('Beach ball','⚽',true), C('Rock','🪨',false), C('Key','🔑',false)]),
    kLesson('Hot and Cold','Compare temperature.','Sorts hot and cold','🔥❄️','Which one is cold?', [C('Ice','🧊',true), C('Fire','🔥',false), C('Soup','🍲',false)]),
    kLesson('Seasons','Notice seasons.','Recognizes seasons','🍂','When leaves fall, it may be ___?', [word('Fall',true), word('Lunch',false), word('Bedtime',false)], {choice_label_mode:'word'}),
    kLesson('Earth Care','Care for Earth.','Identifies Earth care','🌎','Which choice helps Earth?', [C('Pick up trash','🗑️',true), C('Throw trash outside','🚯',false), C('Waste water','💧',false)]),
    kLesson('Observe with a Magnifier','Use observation tools.','Uses observation tools','🔍','What helps you look closely?', [C('Magnifier','🔍',true), C('Pillow','🛏️',false), C('Spoon','🥄',false)]),
    kLesson('Push and Pull','Understand movement.','Identifies push and pull','🚪','To open some doors, you may need to ___?', [word('Pull',true), word('Sleep',false), word('Eat',false)], {choice_label_mode:'word'}),
    kLesson('Sound Around Us','Notice sound.','Identifies sound source','🔔','Which one makes a ringing sound?', [C('Bell','🔔',true), C('Pillow','🛏️',false), C('Cloud','☁️',false)]),
    kLesson('Light Helps Us See','Understand light.','Identifies light source','💡','Which one gives light?', [C('Lamp','💡',true), C('Rock','🪨',false), C('Shoe','👟',false)])
  ];
  pushKLessons('science', scienceMore);
  trimKLessons('science', 25);

  plans.push({ key:'social', name:'Social Studies / Community Basics (Kindergarten)', subject:'Social Studies', course_type:'core', description:'Kindergarten community, maps, rules, helpers, family, and basic citizenship.', unit_title:'My Family, School, and Community', unit_description:'Simple Kindergarten social studies activities with pictures, words, and audio.', lessons:[
    kLesson('My Family','Talk about families.','Recognizes family roles','👨‍👩‍👧','Who can be part of a family?', [C('Parent','👩',true), C('Desk','🪑',false), C('Cloud','☁️',false)]),
    kLesson('My School','Recognize school places.','Identifies school','🏫','Where do students learn?', [C('School','🏫',true), C('Cave','🕳️',false), C('Ocean','🌊',false)]),
    kLesson('Classroom Rules','Know classroom rules.','Identifies rule following','✋','Which choice follows a rule?', [C('Raise hand','✋',true), C('Run inside','🏃',false), C('Push a friend','🙅',false)]),
    kLesson('Community Helpers','Know community helpers.','Identifies helper','👩‍🚒','Who helps put out fires?', [C('Firefighter','👩‍🚒',true), C('Chef','👩‍🍳',false), C('Singer','🎤',false)], {video:kVid.community}),
    kLesson('Doctor Helper','Know helpers.','Identifies doctor','👩‍⚕️','Who helps people feel better?', [C('Doctor','👩‍⚕️',true), C('Painter','🎨',false), C('Farmer','👩‍🌾',false)]),
    kLesson('Mail Carrier','Know helpers.','Identifies mail carrier','📬','Who brings mail?', [C('Mail carrier','📬',true), C('Lifeguard','🛟',false), C('Dancer','💃',false)]),
    kLesson('Needs and Wants','Tell needs from wants.','Sorts needs and wants','🥛','Which one is a need?', [C('Water','💧',true), C('Toy','🧸',false), C('Candy','🍬',false)]),
    kLesson('Goods and Services','Know basic economics.','Identifies goods','🍎','Which one is something you can buy and hold?', [C('Apple','🍎',true), C('Haircut','💇',false), C('Song','🎵',false)]),
    kLesson('Map Symbol: School','Read a simple map symbol.','Identifies map symbol','🏫','Which symbol could mean school?', [C('School','🏫',true), C('Tree','🌳',false), C('Fish','🐟',false)]),
    kLesson('Map Symbol: Park','Read a simple map symbol.','Identifies park symbol','🌳','Which symbol could mean park?', [C('Tree','🌳',true), C('Fork','🍴',false), C('Bed','🛏️',false)]),
    kLesson('Directions: Left and Right','Use direction words.','Uses directions','⬅️➡️','Which arrow points right?', [C('Right','➡️',true), C('Left','⬅️',false), C('Up','⬆️',false)]),
    kLesson('Safety Sign: Stop','Know safety signs.','Identifies stop sign','🛑','What does this sign mean?', [word('Stop',true), word('Jump',false), word('Sing',false)], {choice_label_mode:'word'}),
    kLesson('Good Citizen','Practice citizenship.','Identifies kindness','🤝','Which choice shows kindness?', [C('Help a friend','🤝',true), C('Grab a toy','🧸',false), C('Yell loudly','📣',false)]),
    kLesson('Taking Turns','Practice sharing.','Takes turns','🔄','What should you do when playing a game?', [word('Take turns',true), word('Push ahead',false), word('Hide pieces',false)], {choice_label_mode:'word'}),
    kLesson('Past and Present','Understand time.','Sorts past and present','🕰️','Which word means long ago?', [word('Past',true), word('Now',false), word('Today',false)], {choice_label_mode:'word'}),
    kLesson('Holidays and Traditions','Recognize traditions.','Recognizes traditions','🎉','What is a tradition?', [sentence('Something a family or group does again.',true), sentence('A kind of shoe.',false), sentence('A rainy cloud.',false)], {choice_label_mode:'sentence'}),
    kLesson('United States Symbol','Recognize symbols.','Identifies symbol','🇺🇸','Which picture can be a country symbol?', [C('Flag','🇺🇸',true), C('Cookie','🍪',false), C('Shoe','👟',false)]),
    kLesson('Community Places','Know community places.','Identifies library','📚','Where can people borrow books?', [C('Library','📚',true), C('Pool','🏊',false), C('Farm','🚜',false)]),
    kLesson('Jobs Around Us','Recognize jobs.','Identifies job','👷','Who builds things?', [C('Builder','👷',true), C('Baby','👶',false), C('Fish','🐟',false)]),
    kLesson('Review My Community','Review community basics.','Reviews community','🏘️','Which place is in a community?', [C('School','🏫',true), C('Moon','🌙',false), C('Cloud only','☁️',false)])
  ]});

  const artMore = [
    kLesson('Red Color','Recognize red.','Identifies red','🔴','Which color is red?', [C('Red','🔴',true), C('Blue','🔵',false), C('Yellow','🟡',false)], {video:kVid.colors}),
    kLesson('Blue Color','Recognize blue.','Identifies blue','🔵','Which color is blue?', [C('Blue','🔵',true), C('Red','🔴',false), C('Green','🟢',false)]),
    kLesson('Yellow Color','Recognize yellow.','Identifies yellow','🟡','Which color is yellow?', [C('Yellow','🟡',true), C('Blue','🔵',false), C('Red','🔴',false)]),
    kLesson('Green Color','Recognize green.','Identifies green','🟢','Which color is green?', [C('Green','🟢',true), C('Orange','🟠',false), C('Purple','🟣',false)]),
    kLesson('Straight Line','Recognize line types.','Identifies straight line','━','Which line is straight?', [C('Straight line','━',true), C('Wavy line','〰️',false), C('Dot','⚫',false)]),
    kLesson('Wavy Line','Recognize line types.','Identifies wavy line','〰️','Which line is wavy?', [C('Wavy line','〰️',true), C('Straight line','━',false), C('Circle','⭕',false)]),
    kLesson('Texture Smooth','Explore texture.','Identifies smooth','🧼','Which item may feel smooth?', [C('Soap','🧼',true), C('Pinecone','🌲',false), C('Rocky path','🪨',false)]),
    kLesson('Texture Rough','Explore texture.','Identifies rough','🪨','Which item may feel rough?', [C('Rock','🪨',true), C('Bubble','🫧',false), C('Silk','🧣',false)]),
    kLesson('Color Pattern Art','Make art patterns.','Recognizes art pattern','🔴🔵🔴','What color comes next?', [C('Blue','🔵',true), C('Red','🔴',false), C('Green','🟢',false)]),
    kLesson('Draw From Nature','Look and draw.','Observes nature','🍃','Which item comes from nature?', [C('Leaf','🍃',true), C('Crayon','🖍️',false), C('Chair','🪑',false)])
  ];
  pushKLessons('art', artMore);
  trimKLessons('art', 15);

  const musicMore = [
    kLesson('Loud Sound','Identify loud and soft.','Identifies loud','📣','Which sound is loud?', [C('Drum','🥁',true), C('Whisper','🤫',false), C('Feather','🪶',false)], {video:kVid.music}),
    kLesson('Soft Sound','Identify loud and soft.','Identifies soft','🤫','Which sound is soft?', [C('Whisper','🤫',true), C('Fire truck','🚒',false), C('Drum','🥁',false)]),
    kLesson('Fast Tempo','Identify fast music.','Identifies fast tempo','🏃','Which word means quick music?', [word('Fast',true), word('Slow',false), word('Quiet',false)], {choice_label_mode:'word'}),
    kLesson('Slow Tempo','Identify slow music.','Identifies slow tempo','🐢','Which word means not fast?', [word('Slow',true), word('Fast',false), word('Loud',false)], {choice_label_mode:'word'}),
    kLesson('High Sound','Hear high and low.','Identifies high sound','🐦','Which animal often makes a high sound?', [C('Bird','🐦',true), C('Cow','🐄',false), C('Lion','🦁',false)]),
    kLesson('Low Sound','Hear high and low.','Identifies low sound','🐄','Which animal often makes a low sound?', [C('Cow','🐄',true), C('Bird','🐦',false), C('Mouse','🐭',false)]),
    kLesson('Beat Clap','Keep a beat.','Claps beat','👏','What can you use to clap a beat?', [C('Hands','👏',true), C('Shoes only','👟',false), C('Pillow','🛏️',false)]),
    kLesson('Instrument: Drum','Know instruments.','Identifies drum','🥁','Which instrument can you tap?', [C('Drum','🥁',true), C('Book','📘',false), C('Apple','🍎',false)]),
    kLesson('Instrument: Guitar','Know instruments.','Identifies guitar','🎸','Which picture is a guitar?', [C('Guitar','🎸',true), C('Drum','🥁',false), C('Bell','🔔',false)]),
    kLesson('Music Feelings','Respond to music.','Connects music and feeling','😊','Which music might feel calm?', [word('Soft and slow',true), word('Very loud and fast',false), word('No sound',false)], {choice_label_mode:'word'})
  ];
  pushKLessons('music', musicMore);
  trimKLessons('music', 15);

  const healthMore = [
    kLesson('Drink Water','Choose water.','Identifies hydration','💧','What helps your body stay hydrated?', [C('Water','💧',true), C('Candy','🍬',false), C('Paint','🎨',false)]),
    kLesson('Wash Hands','Practice hygiene.','Identifies handwashing','🧼','What should you use to wash hands?', [C('Soap and water','🧼',true), C('Glue','🧴',false), C('Dirt','🪨',false)], {video:kVid.hygiene}),
    kLesson('Brush Teeth','Practice dental health.','Identifies brushing teeth','🪥','What helps clean teeth?', [C('Toothbrush','🪥',true), C('Crayon','🖍️',false), C('Shoe','👟',false)]),
    kLesson('Healthy Snack','Choose healthy foods.','Chooses healthy snack','🍎','Which is a healthy snack?', [C('Apple','🍎',true), C('Candy only','🍬',false), C('Soda','🥤',false)]),
    kLesson('Rest Time','Know rest.','Identifies rest','😴','What helps your body rest?', [C('Sleep','😴',true), C('Run all night','🏃',false), C('Yell loudly','📣',false)])
  ];
  pushKLessons('health', healthMore);
  trimKLessons('health', 10);

  const peMore = [
    kLesson('Warm Up','Practice warm up.','Identifies warm up','🙆','What should you do before exercise?', [C('Warm up','🙆',true), C('Sit forever','🪑',false), C('Skip water all day','🚫',false)]),
    kLesson('Stretch Arms','Move safely.','Stretches arms','💪','Which body part are you stretching?', [C('Arms','💪',true), C('Ears','👂',false), C('Nose','👃',false)]),
    kLesson('Jump Safely','Practice jumping.','Jumps safely','🦘','Where should you land after a jump?', [C('On your feet','🦶',true), C('On your head','🙃',false), C('On a table','🪑',false)]),
    kLesson('Balance Pose','Practice balance.','Balances body','⚖️','What helps you balance?', [C('Stand still','🧍',true), C('Spin fast','🌪️',false), C('Close eyes and run','🏃',false)]),
    kLesson('Throw and Catch','Practice coordination.','Throws and catches','⚽','What can you gently throw and catch?', [C('Ball','⚽',true), C('Glass cup','🥛',false), C('Sharp pencil','✏️',false)]),
    kLesson('Hop One Foot','Practice hopping.','Hops on one foot','🦶','How many feet do you use to hop on one foot?', [num('1',true), num('2',false), num('4',false)], {choice_label_mode:'number'}),
    kLesson('Cool Down','Practice cool down.','Identifies cool down','🧘','What should you do after exercise?', [C('Slow stretch','🧘',true), C('Run faster','🏃',false), C('Jump on bed','🛏️',false)]),
    kLesson('Move Like Animals','Move creatively.','Uses creative movement','🐸','Which animal hops?', [C('Frog','🐸',true), C('Fish','🐟',false), C('Snake','🐍',false)]),
    kLesson('Dance Freeze','Follow movement directions.','Follows movement directions','💃','When the music stops, what should you do?', [word('Freeze',true), word('Keep running',false), word('Shout',false)], {choice_label_mode:'word'}),
    kLesson('Sportsmanship','Practice kindness in games.','Shows sportsmanship','🤝','What should you say after a game?', [sentence('Good game.',true), sentence('I will not share.',false), sentence('That was bad.',false)], {choice_label_mode:'sentence'})
  ];
  pushKLessons('pe', peMore);
  trimKLessons('pe', 15);

  const moveMore = [
    kLesson('March in Place','Practice movement.','Marches in place','🚶','Which movement is marching?', [C('Step in place','🚶',true), C('Sleep','😴',false), C('Paint','🎨',false)]),
    kLesson('Animal Walk: Bear','Move like a bear.','Bear walk','🐻','Which animal walks on four paws?', [C('Bear','🐻',true), C('Bird','🐦',false), C('Fish','🐟',false)]),
    kLesson('Tiptoe Walk','Control movement.','Tiptoes','🦶','How do you tiptoe?', [C('Walk softly','🦶',true), C('Stomp loudly','👟',false), C('Sit down','🪑',false)]),
    kLesson('Freeze Game','Follow stop and go.','Stops and starts','🛑','When you hear freeze, what do you do?', [word('Stop',true), word('Run',false), word('Jump',false)], {choice_label_mode:'word'}),
    kLesson('Big and Small Movements','Compare movement size.','Uses big and small movement','🙆','Which is a big movement?', [C('Big arm circle','🙆',true), C('Tiny finger tap','☝️',false), C('Blink','👁️',false)])
  ];
  pushKLessons('movement_play', moveMore);
  trimKLessons('movement_play', 10);

  // Kindergarten blueprint expansion v24: deeper units, more choices, reusable content bank.
  // Each assignment is expanded to at least 5 tasks by kEnsureMinimumKindergartenSteps().
  const blueprintSightUnits = [
    ['Sight Words Unit 1: I, see, the, a, my', ['I','see','the','a','my']],
    ['Sight Words Unit 2: like, can, we, go, to', ['like','can','we','go','to']],
    ['Sight Words Unit 3: is, it, and, me, you', ['is','it','and','me','you']],
    ['Sight Words Unit 4: he, she, look, at, up', ['he','she','look','at','up']],
    ['Sight Words Unit 5: down, in, on, here, come', ['down','in','on','here','come']],
    ['Sight Words Unit 6: color words', ['red','blue','yellow','green','orange']]
  ];
  for (const [title, words] of blueprintSightUnits) {
    pushKLessons('reading', [kLesson(title, `Practice sight words: ${words.join(', ')}.`, `Reads ${title}`, '📘', `Which word says ${words[0]}?`, words.slice(0,3).map((w,i) => word(w, i === 0)), { choice_label_mode:'word', review:true, lesson_text:`This unit practices the sight words ${words.join(', ')}. Read each word. Then use the words in simple sentences.` })]);
  }
  pushKLessons('reading', [
    kLesson('Uppercase Lowercase Review A-F','Match uppercase and lowercase letters.','Matches A-F letter pairs','Aa','Which letter matches A?', [word('a',true), word('m',false), word('t',false)], { choice_label_mode:'letter', review:true }),
    kLesson('Beginning Sounds Mixed Review','Listen for different beginning sounds.','Reviews beginning sounds','🔤','Which picture starts with B?', [C('Ball','🏀',true), C('Sun','☀️',false), C('Fish','🐟',false)], { review:true }),
    kLesson('Rhyming Mixed Review','Find words that rhyme.','Reviews rhyming words','🎵','Which word rhymes with cat?', [word('hat',true), word('sun',false), word('dog',false)], { choice_label_mode:'word', review:true }),
    kLesson('CVC Picture Word Review','Read simple CVC words with pictures.','Reviews CVC words','🐱','Which word matches the cat picture?', [word('cat',true), word('cap',false), word('cup',false)], { choice_label_mode:'word', review:true })
  ]);

  pushKLessons('math', [
    kLesson('Count and Choose: Fruit 1-10','Count fruit and choose the number.','Counts fruit to 10','🍎🍎🍎🍎🍎','How many apples do you see?', [num('5',true), num('3',false), num('7',false)], { choice_label_mode:'number', review:true }),
    kLesson('Count and Choose: Animals 1-10','Count animals and choose the number.','Counts animals to 10','🐟🐟🐟🐟','How many fish do you see?', [num('4',true), num('2',false), num('6',false)], { choice_label_mode:'number', review:true }),
    kLesson('Numbers 11 to 20','Recognize teen numbers.','Recognizes numbers 11-20','12','Which number is twelve?', [num('12',true), num('15',false), num('18',false)], { choice_label_mode:'number', review:true }),
    kLesson('Ten Frame Practice','Use ten frames to count.','Counts with ten frames','🔟','A full ten frame shows how many?', [num('10',true), num('5',false), num('20',false)], { choice_label_mode:'number' }),
    kLesson('Shape Sorting Review','Sort shapes by name.','Sorts common shapes','🔺◼️⭕','Which shape has three sides?', [C('Triangle','🔺',true), C('Circle','⭕',false), C('Square','◼️',false)], { review:true }),
    kLesson('Pattern Review: Shapes','Complete shape patterns.','Completes shape patterns','⭕◼️⭕','What comes next: circle, square, circle, ___?', [C('Square','◼️',true), C('Circle','⭕',false), C('Triangle','🔺',false)], { review:true }),
    kLesson('Compare Groups Review','Compare more, fewer, and same.','Compares groups','🍓🍓🍓','Which group has more?', [C('Three strawberries','🍓🍓🍓',true), C('One strawberry','🍓',false), C('Two strawberries','🍓🍓',false)], { review:true }),
    kLesson('Add with Picture Stories','Add small groups with pictures.','Adds with pictures','🍎+🍎','2 apples plus 2 apples equals what?', [num('4',true), num('3',false), num('5',false)], { choice_label_mode:'number' }),
    kLesson('Take Away Picture Stories','Take away objects with pictures.','Subtracts with pictures','🍪🍪🍪','3 cookies take away 1 cookie leaves how many?', [num('2',true), num('3',false), num('1',false)], { choice_label_mode:'number' }),
    kLesson('Position Words Review','Use position words.','Recognizes position words','⬆️⬇️','Which word means over?', [word('above',true), word('under',false), word('beside',false)], { choice_label_mode:'word', review:true })
  ]);

  pushKLessons('science', [
    kLesson('Animal Habitats Review','Match animals to homes.','Reviews animal habitats','🐟🌊','Where does a fish live?', [C('Water','🌊',true), C('Tree','🌳',false), C('Nest','🪺',false)], { review:true }),
    kLesson('Five Senses Review','Match senses to body parts.','Reviews five senses','👁️👂👃','What do you use to hear?', [C('Ears','👂',true), C('Eyes','👁️',false), C('Hands','✋',false)], { review:true }),
    kLesson('Weather Choices Review','Choose weather pictures.','Reviews weather','☀️🌧️','What do you use on a rainy day?', [C('Umbrella','☂️',true), C('Sunglasses','🕶️',false), C('Sandals','🩴',false)], { review:true }),
    kLesson('Plant Growth Order','Put plant growth in order.','Recognizes plant growth','🌱','What comes from a seed?', [C('Sprout','🌱',true), C('Car','🚗',false), C('Rock','🪨',false)]),
    kLesson('Living and Nonliving Review','Sort living and nonliving things.','Reviews living and nonliving','🐶🪨','Which one is living?', [C('Dog','🐶',true), C('Rock','🪨',false), C('Chair','🪑',false)], { review:true })
  ]);

  pushKLessons('social', [
    kLesson('United States Flag','Identify a common country symbol.','Identifies flag symbol','🇺🇸','Which picture is the flag?', [C('Flag','🇺🇸',true), C('Book','📘',false), C('Ball','🏀',false)], { hide_choice_labels_preselect:true }),
    kLesson('Mail Carrier Helper','Identify a mail carrier.','Identifies mail carrier','🧑‍✈️✉️','Who delivers mail?', [C('Mail carrier','🧑‍✈️✉️',true), C('Chef','👩‍🍳',false), C('Doctor','👩‍⚕️',false)], { hide_choice_labels_preselect:true }),
    kLesson('What Is a Tradition?','Understand family and community traditions.','Explains tradition','🎂','A tradition is something a family, group, or community does again and again in a special way. Which picture can show a tradition?', [C('Birthday meal','🎂',true), C('Lost shoe','👟',false), C('Broken pencil','✏️',false)], { lesson_text:'A tradition is something a family, group, or community does again and again in a special way. Some families have birthday traditions, holiday traditions, church traditions, meal traditions, or bedtime traditions.' }),
    kLesson('Community Places Review','Match helpers and places.','Reviews community places','🏫','Where can you borrow books?', [C('Library','📚',true), C('Fire station','🚒',false), C('Grocery store','🛒',false)], { review:true }),
    kLesson('Safe Choices Review','Choose safe and kind actions.','Reviews safe choices','🛟','Who can help if you are lost?', [C('Trusted adult','👩‍🏫',true), C('A stranger online','💻',false), C('No one','🚫',false)], { choice_label_mode:'picture', review:true })
  ]);

  const artBlueprint = [
    ['Straight Lines','Recognize straight lines.','Identifies straight lines','━','Which line is straight?', [C('Straight line','━',true), C('Wavy line','〰️',false), C('Zigzag line','〽️',false)]],
    ['Curved Lines','Recognize curved lines.','Identifies curved lines','🌙','Which line is curved?', [C('Curved line','🌙',true), C('Straight line','━',false), C('Square','◼️',false)]],
    ['Wavy and Squiggly Lines','Recognize wavy lines.','Identifies wavy lines','〰️','Which line looks wavy?', [C('Wavy line','〰️',true), C('Straight line','━',false), C('Dot','⚫',false)]],
    ['Zigzag Lines','Recognize zigzag lines.','Identifies zigzag lines','〽️','Which line is zigzag?', [C('Zigzag line','〽️',true), C('Circle','⭕',false), C('Oval','🥚',false)]],
    ['Up and Down Lines','Recognize vertical lines.','Identifies vertical lines','↕️','Which line goes up and down?', [C('Up-down line','↕️',true), C('Left-right line','↔️',false), C('Circle','⭕',false)]],
    ['Left to Right Lines','Recognize horizontal lines.','Identifies horizontal lines','↔️','Which line goes left to right?', [C('Left-right line','↔️',true), C('Up-down line','↕️',false), C('Triangle','🔺',false)]],
    ['Diagonal Lines','Recognize diagonal lines.','Identifies diagonal lines','↗️','Which line is slanted?', [C('Diagonal line','↗️',true), C('Straight across','↔️',false), C('Circle','⭕',false)]],
    ['Thick and Thin Lines','Compare line thickness.','Compares thick and thin lines','▬','Which line is thick?', [C('Thick line','▬',true), C('Thin line','━',false), C('Dot','⚫',false)]],
    ['Line Pattern Review','Make line patterns.','Reviews line patterns','━〰️━','What comes next: straight, wavy, straight, ___?', [C('Wavy','〰️',true), C('Straight','━',false), C('Dot','⚫',false)]],
    ['Circle Shape Art','Recognize circles in art.','Identifies circles','⭕','Which object is round like a circle?', [C('Ball','⚽',true), C('Door','🚪',false), C('Slice of pizza','🍕',false)]],
    ['Square Shape Art','Recognize squares in art.','Identifies squares','◼️','Which object can look like a square?', [C('Block','🟦',true), C('Wheel','⭕',false), C('Banana','🍌',false)]],
    ['Triangle Shape Art','Recognize triangles in art.','Identifies triangles','🔺','Which object can look like a triangle?', [C('Party hat','🥳',true), C('Ball','⚽',false), C('Book','📘',false)]],
    ['Shape Sorting Art','Sort shapes in art.','Sorts shapes','⭕◼️🔺','Which is not a circle?', [C('Triangle','🔺',true), C('Ball','⚽',false), C('Button','🔘',false)]],
    ['Warm Colors','Know warm colors.','Identifies warm colors','🔴🟠🟡','Which color can feel warm?', [C('Red','🔴',true), C('Blue','🔵',false), C('Purple','🟣',false)]],
    ['Cool Colors','Know cool colors.','Identifies cool colors','🔵🟢🟣','Which color can feel cool?', [C('Blue','🔵',true), C('Red','🔴',false), C('Orange','🟠',false)]],
    ['Color and Shape Pattern','Make art patterns.','Creates color/shape patterns','🔴◼️🔴','What comes next: red circle, blue square, red circle, ___?', [C('Blue square','🟦',true), C('Yellow triangle','🔺',false), C('Red circle','🔴',false)]],
    ['Art Tools Review','Identify art tools.','Reviews art tools','🖍️','Which tool can color a picture?', [C('Crayon','🖍️',true), C('Spoon','🥄',false), C('Shoe','👟',false)], { review:true }],
    ['Texture in Art','Recognize texture words.','Identifies texture','🪨','Which might feel rough?', [C('Rock','🪨',true), C('Bubble','🫧',false), C('Silk','🧣',false)]],
    ['Draw With Shapes','Build pictures from shapes.','Creates pictures with shapes','🏠','Which shape can be a roof?', [C('Triangle','🔺',true), C('Circle','⭕',false), C('Oval','🥚',false)]],
    ['Art Review: Lines and Shapes','Review lines and shapes.','Reviews art lines and shapes','🎨','Which picture shows a zigzag line?', [C('Zigzag','〽️',true), C('Circle','⭕',false), C('Square','◼️',false)], { review:true }]
  ];
  pushKLessons('art', artBlueprint.map(x => kLesson(...x)));

  pushKLessons('music', [
    kLesson('Steady Beat Practice','Hear and keep a steady beat.','Keeps steady beat','👏','What can you clap to keep a beat?', [C('Hands','👏',true), C('Hat','🎩',false), C('Pillow','🛏️',false)]),
    kLesson('Rhythm Pattern Clap','Clap rhythm patterns.','Claps rhythm patterns','👏👏','Which pattern has two claps?', [C('Clap clap','👏👏',true), C('One clap','👏',false), C('No claps','🤫',false)]),
    kLesson('Fast and Slow Review','Review tempo.','Reviews fast and slow','🐇🐢','Which animal can show slow?', [C('Turtle','🐢',true), C('Rabbit','🐇',false), C('Cheetah','🐆',false)], { review:true }),
    kLesson('High and Low Review','Review pitch.','Reviews high and low sounds','🐦🐄','Which animal can make a low sound?', [C('Cow','🐄',true), C('Bird','🐦',false), C('Mouse','🐭',false)], { review:true }),
    kLesson('Instrument: Piano','Identify piano.','Identifies piano','🎹','Which instrument has keys?', [C('Piano','🎹',true), C('Drum','🥁',false), C('Guitar','🎸',false)]),
    kLesson('Instrument: Trumpet','Identify trumpet.','Identifies trumpet','🎺','Which instrument do you blow into?', [C('Trumpet','🎺',true), C('Piano','🎹',false), C('Drum','🥁',false)]),
    kLesson('Instrument Sorting','Sort instruments by how they play.','Sorts instruments','🎸🥁🎺','Which instrument can be tapped?', [C('Drum','🥁',true), C('Trumpet','🎺',false), C('Flute','🪈',false)]),
    kLesson('Music Feelings Review','Connect music to feelings.','Reviews music feelings','😊','Which music can feel calm?', [word('Soft and slow',true), word('Very loud and fast',false), word('Silly and wild',false)], { choice_label_mode:'word', review:true }),
    kLesson('Body Percussion','Use body to make rhythm.','Uses body percussion','👏','Which body part can clap?', [C('Hands','👏',true), C('Ears','👂',false), C('Nose','👃',false)]),
    kLesson('Music Review','Review beat, sounds, and instruments.','Reviews music skills','🎵','Which instrument is a drum?', [C('Drum','🥁',true), C('Book','📘',false), C('Apple','🍎',false)], { review:true })
  ]);

  pushKLessons('health', [
    kLesson('Fruit Choices','Identify fruit.','Identifies fruit','🍎','Which one is a fruit?', [C('Apple','🍎',true), C('Carrot','🥕',false), C('Bread','🍞',false)]),
    kLesson('Vegetable Choices','Identify vegetables.','Identifies vegetables','🥦','Which one is a vegetable?', [C('Broccoli','🥦',true), C('Cookie','🍪',false), C('Ice cream','🍦',false)]),
    kLesson('Healthy Plate','Choose balanced foods.','Chooses healthy foods','🍽️','Which food helps your body grow?', [C('Vegetables','🥦',true), C('Candy only','🍬',false), C('Soda','🥤',false)]),
    kLesson('Handwashing Steps','Know handwashing steps.','Reviews handwashing','🧼','What do you use with water to wash hands?', [C('Soap','🧼',true), C('Paint','🎨',false), C('Glue','🧴',false)], { review:true }),
    kLesson('Feelings Check','Identify feelings.','Identifies feelings','😊','Which face looks happy?', [C('Happy face','😊',true), C('Sad face','😢',false), C('Mad face','😠',false)]),
    kLesson('Calm Down Choice','Choose calming strategies.','Chooses calm strategy','🧘','What can help you calm down?', [C('Take slow breaths','🧘',true), C('Yell louder','📣',false), C('Throw toys','🧸',false)]),
    kLesson('Safety Helpers','Know trusted helpers.','Identifies safety helpers','👩‍🏫','Who can help you stay safe?', [C('Trusted adult','👩‍🏫',true), C('Broken chair','🪑',false), C('Candy','🍬',false)]),
    kLesson('Health Review','Review healthy habits.','Reviews healthy habits','🍎🧼💧','Which habit helps keep hands clean?', [C('Washing hands','🧼',true), C('Drawing lines','🖍️',false), C('Jumping','🦘',false)], { review:true })
  ]);

  pushKLessons('pe', [
    kLesson('Safe Space Check','Check space before moving.','Checks safe space','🟩','What do you need before moving?', [C('Open safe space','🟩',true), C('Toys everywhere','🧸',false), C('A slippery floor','💧',false)]),
    kLesson('Marching Knees','Practice marching knees.','Marches with knees','🧍','Which action shows marching?', [C('Lift knees','🧍',true), C('Sleep','😴',false), C('Sit still','🪑',false)]),
    kLesson('Tiptoe Control','Move softly with control.','Tiptoes with control','🦶','How do you tiptoe?', [C('Walk softly','🦶',true), C('Stomp loudly','👟',false), C('Run fast','🏃',false)]),
    kLesson('Balance on One Foot','Practice balance.','Balances on one foot','🧍','What helps you balance on one foot?', [C('Arms out and still','🧍',true), C('Spin fast','🌪️',false), C('Close eyes and run','🏃',false)]),
    kLesson('Jump and Land','Practice safe jumping.','Jumps and lands safely','🤸','Where should you land after jumping?', [C('On your feet','🦶',true), C('On your head','🙃',false), C('On a table','🪑',false)]),
    kLesson('Hop Practice','Practice hopping.','Hops safely','🦶','How many feet do you use for one-foot hop?', [num('1',true), num('2',false), num('4',false)], { choice_label_mode:'number' }),
    kLesson('Animal Move: Frog','Move like a frog.','Moves like a frog','🐸','Which animal hops?', [C('Frog','🐸',true), C('Fish','🐟',false), C('Snake','🐍',false)]),
    kLesson('Throw Soft Ball','Practice gentle throwing.','Throws safely','⚽','What can you gently throw?', [C('Soft ball','⚽',true), C('Glass cup','🥛',false), C('Sharp pencil','✏️',false)]),
    kLesson('Catch With Two Hands','Practice catching.','Catches safely','🙌','What helps you catch safely?', [C('Use two hands','🙌',true), C('Close eyes','🙈',false), C('Turn away','↩️',false)]),
    kLesson('Movement Review','Review safe movement skills.','Reviews movement skills','🤸','Which action shows balance?', [C('Stand still on one foot','🧍',true), C('Run into a wall','🏃',false), C('Throw a pencil','✏️',false)], { review:true })
  ]);

  pushKLessons('movement_play', [
    kLesson('Freeze and Go Review','Follow freeze/go directions.','Reviews freeze and go','🛑','When you hear freeze, what do you do?', [word('Stop',true), word('Run',false), word('Shout',false)], { choice_label_mode:'word', review:true }),
    kLesson('Big Movement Small Movement','Compare movement sizes.','Compares movement size','🙆','Which is a big movement?', [C('Big arm circle','🙆',true), C('Tiny finger tap','☝️',false), C('Blink','👁️',false)]),
    kLesson('Creative Movement Review','Review creative movement.','Reviews creative movement','🐻','Which animal walks on four paws?', [C('Bear','🐻',true), C('Bird','🐦',false), C('Fish','🐟',false)], { review:true })
  ]);

  const created = {};
  for (const plan of plans) {
    created[plan.key] = await ensureCourse({
      name: plan.name,
      grade_level: grade,
      subject: plan.subject,
      course_type: plan.course_type || 'core',
      description: plan.description,
      credit_hours: plan.course_type === 'core' ? 1.0 : 0.5
    });
  }

  for (const plan of plans) {
    const course = created[plan.key];

    // Clean v20 grouping: deactivate previous Kindergarten lesson rows for this course
    // before rebuilding them into organized unit sections. This prevents the older
    // one-long-list structure from continuing to show after an update.
    await pool.query(`UPDATE assignments a SET is_active = false FROM lessons l JOIN units u ON l.unit_id = u.id WHERE a.lesson_id = l.id AND u.course_id = $1`, [course.id]).catch(() => {});
    await pool.query(`UPDATE lessons l SET is_active = false FROM units u WHERE l.unit_id = u.id AND u.course_id = $1`, [course.id]).catch(() => {});
    await pool.query(`UPDATE units SET is_active = false WHERE course_id = $1`, [course.id]).catch(() => {});

    const unitOrder = [];
    const unitItems = {};
    for (const item of plan.lessons) {
      const unitTitle = kUnitForLesson(plan.key, item);
      if (!unitItems[unitTitle]) { unitItems[unitTitle] = []; unitOrder.push(unitTitle); }
      unitItems[unitTitle].push(item);
    }

    for (let uIndex = 0; uIndex < unitOrder.length; uIndex++) {
      const unitTitle = unitOrder[uIndex];
      const unitDescription = kUnitMeta[plan.key]?.[unitTitle] || plan.unit_description || 'Kindergarten activities in a clean learning order.';
      const unit = await ensureUnit(course.id, uIndex + 1, unitTitle, unitDescription);
      const items = unitItems[unitTitle] || [];
      for (let i = 0; i < items.length; i++) {
        const item = items[i];
        const globalIndex = plan.lessons.indexOf(item) + 1;
        const lesson = await ensureLesson(unit.id, i + 1, {
          title: item.title,
          description: item.description,
          lesson_type: 'kindergarten_activity',
          interactive_type: 'activity_flow',
          skill_name: item.skill,
          learning_objectives: item.skill,
          instructional_content: item.instruction,
          audio_url: kTtsPlaceholder(item.instruction),
          image_url: kSvgImage(item.icon || '⭐', item.title, '#fff4d8', '#e7f8ff'),
          activity_data: kBuildActivityData(item),
          // Kindergarten videos stay in Video Fun / Learning Videos. Assignments link there for optional help.
          video_url: null,
          video_title: null,
          video_source: null,
          video_verified: false
        });

        const firstChoiceStep = (item.steps || []).find(st => Array.isArray(st.choices) && st.choices.length);
        const correctChoice = firstChoiceStep?.choices?.find(c => c[2]);
        const questions = correctChoice ? [mc(1, firstChoiceStep.instruction, String(correctChoice[0]), firstChoiceStep.choices.filter(c => !c[2]).map(c => String(c[0])))] : [wr(1, `Parent note: What did the student practice in "${item.title}"?`)];
        await safeEnsureAssignment(lesson.id, {
          title: item.title + ' Activity',
          description: item.description,
          instructions: 'Complete the Kindergarten one-page-at-a-time activity. Parent help is allowed for this grade level.',
          assignment_type: 'activity',
          weight_category: 'practice',
          point_value: 0,
          questions
        }, `kindergarten-${plan.key}-${globalIndex}`);
      }
    }
  }

  const students = await pool.query('SELECT id FROM students WHERE grade_level = $1', [grade]);
  for (const st of students.rows) {
    for (const c of Object.values(created)) {
      await safeEnrollStudentInCourse(st.id, c.id);
    }
  }
}


// Grades 1 and 2 early-elementary foundation.
// These grades follow the K-2 visual/audio-supported structure, but with more reading,
// thinking, and practice than Kindergarten. Each course has at least 5 guided activities.
function eeStep(title, instruction, skill, choices, options = {}) {
  return { title, instruction, skill, choices, ...options };
}
function eeDone(title, instruction, skill) {
  return { title, instruction, skill, done: true };
}
function eeLesson(title, description, skill, icon, instruction, steps, video, options = {}) {
  const hasVideo = !!(video && video.url) && !options.force_no_video;
  const autoLessonText = !hasVideo && options.no_auto_lesson_text !== true
    ? `${title}. ${description || ''} ${instruction || ''}`.replace(/\s+/g, ' ').trim()
    : null;
  const lessonText = options.lesson_text !== undefined ? options.lesson_text : autoLessonText;
  return {
    title,
    description,
    skill,
    icon,
    instruction,
    steps,
    video_url: options.force_no_video ? null : (video?.url || null),
    video_title: options.force_no_video ? null : (video?.title || null),
    video_source: options.force_no_video ? null : (video?.source || null),
    hide_choice_labels_preselect: !!options.hide_choice_labels_preselect,
    hide_prompt_label: options.hide_prompt_label !== false,
    lesson_text: lessonText || null,
    lesson_audio_text: options.lesson_audio_text || lessonText || null
  };
}
function eeVideo(title, source, url) {
  return { title, source, url };
}

function earlyElementaryPlans(grade) {
  const g = parseInt(grade, 10);
  const label = `Grade ${g}`;
  const v = {
    phonics: eeVideo('Alphabet and Phonics Practice', 'Child-focused phonics video', 'https://www.youtube.com/watch?v=pU8IWLmRdNM'),
    reading: eeVideo('Reading Comprehension for Kids', 'Educational YouTube Video', 'https://www.youtube.com/watch?v=NqpbTN3diUc'),
    sentences: eeVideo('Sentence Fragments for Kids', 'Homeschool Pop', 'https://www.youtube.com/watch?v=ds0JHxYvkvQ'),
    addition: eeVideo('Addition - Learn How To Add Using Pictures and Objects', 'Kids Academy / early math video', 'https://www.youtube.com/watch?v=2lrUvrG7UPI'),
    count20: eeVideo('Number song 1-20 for children', 'The Singing Walrus', 'https://www.youtube.com/watch?v=D0Ajq682yrA'),
    subtraction1: eeVideo('Basic Picture Subtraction for Kids', 'Early math video', 'https://www.youtube.com/watch?v=keH5Ok36GE8'),
    timeHour1: eeVideo('Telling Time to the Hour', 'Elementary time video', 'https://www.youtube.com/watch?v=hqWlzZ9s1-g'),
    numberline: eeVideo('Math Antics - The Number Line', 'Math Antics', 'https://www.youtube.com/watch?v=RSJOTBJlKNA'),
    placeValue2: eeVideo('Place Value: Ones and Tens | Math for Grade 2', 'Kids Academy', 'https://www.youtube.com/watch?v=_dHu5TFxPtk'),
    addWithin100: eeVideo('Add Ones or Tens - Place Value | Math for 2nd Grade', 'Kids Academy', 'https://www.youtube.com/watch?v=oGpFG5QRFZA'),
    multidigit: eeVideo('Math Antics - Multi-Digit Addition', 'Math Antics', 'https://www.youtube.com/watch?v=mAvuom42NyY'),
    subtraction: eeVideo('Math Antics - Multi-Digit Subtraction', 'Math Antics', 'https://www.youtube.com/watch?v=Y6M89-6106I'),
    subtractWithin100: eeVideo('2-Digit Subtraction Without Regrouping', 'Math with Mr. J', 'https://www.youtube.com/watch?v=n0HDh8PP8sQ'),
    arrays2: eeVideo('Arrays for Kids | Math for 2nd Grade', 'Kids Academy', 'https://www.youtube.com/watch?v=00m3YhLT6fA'),
    measureLength2: eeVideo('Measuring! | Mini Math Movies', 'Scratch Garden', 'https://www.youtube.com/watch?v=2wUsdsae0ro'),
    shapes: eeVideo('Shapes Song for Kids', 'Kids learning song', 'https://www.youtube.com/watch?v=6_NZqbnuw-I'),
    plants: eeVideo('How Plants Grow for Kids', 'Learn Bright', 'https://www.youtube.com/watch?v=u46A0WKp2nk'),
    seasons: eeVideo('The Four Seasons for Kids', 'Learn Bright', 'https://www.youtube.com/watch?v=4HeeCYVqf3U'),
    living: eeVideo('Living and Non-Living Things', 'Kids science lesson', 'https://www.youtube.com/watch?v=yW5VbMEDRJk'),
    water: eeVideo('The Water Cycle for Kids', 'Learn Bright', 'https://www.youtube.com/watch?v=TD3XSIE4ymo'),
    matter: eeVideo('What\'s Matter?', 'Crash Course Kids', 'https://www.youtube.com/watch?v=ELchwUIlWa8'),
    community: eeVideo('Our Community Helpers', 'Homeschool Pop', 'https://www.youtube.com/watch?v=WLi0EHHQeKA'),
    needs: eeVideo('Needs and Wants Explained - Facts for Kids', 'Kids Academy / elementary social studies', 'https://www.youtube.com/watch?v=TP1gPF6e3UM'),
    communities: eeVideo('Types of Communities for Kids', 'Elementary social studies video', 'https://www.youtube.com/watch?v=04lO0ISBT40'),
    artLines: eeVideo('Lines and Colors for Kindergarten', 'Kindergarten art lesson', 'https://www.youtube.com/watch?v=MyECmrrakt8'),
    artShapes: eeVideo('Easy Art Activity to Teach Lines & Shapes', 'Miriam Cutelis', 'https://www.youtube.com/watch?v=dI9KvApZHzM'),
    patterns: eeVideo('Patterns! Mini Math Movies', 'Scratch Garden', 'https://www.youtube.com/watch?v=Js45cR_7wFE'),
    rhythm: eeVideo('Rhythm Clap Along', 'Kids music lesson', 'https://www.youtube.com/watch?v=jYkT0hGTYJM'),
    instruments: eeVideo('Musical Instruments Names and Sounds for Kids', 'Kiddopedia', 'https://www.youtube.com/watch?v=a8lZQnQZg_A'),
    highlow: eeVideo('High or Low? Learn and Practice High and Low Sounds', 'Kids music lesson', 'https://www.youtube.com/watch?v=jcAa9G64HdA'),
    tempo: eeVideo('CLIBURN KIDS Musical Opposites: Tempo & Dynamics', 'Cliburn Kids', 'https://www.youtube.com/watch?v=XlszJiJsz4w'),
    healthFoods: eeVideo('Food Groups and MyPlate', 'Kids nutrition video', 'https://www.youtube.com/watch?v=L7QOUiQCb5E'),
    handwash: eeVideo('How to Wash Your Hands Song', 'NHS', 'https://www.youtube.com/watch?v=S9VjeIWLnEg'),
    germs: eeVideo('Germs for Kids', 'Learn Bright', 'https://www.youtube.com/watch?v=z6n-37RTKRo'),
    peWarm: eeVideo('Quick Warm Up Routine for Kids', 'Prime Coaching Sport', 'https://www.youtube.com/watch?v=_yanQAS79Co'),
    balance: eeVideo('Balance - Stand Still, Jump, and Try Tree Pose!', 'Miacademy Fit and Active K-2', 'https://www.youtube.com/watch?v=-C9_fbheAaE'),
    jumpLand: eeVideo('How to Jump and Land Safely', 'Early PE movement video', 'https://www.youtube.com/watch?v=dnUzoihXMrw'),
    throwCatch: eeVideo('Throwing and Catching Challenges for PE Grades K-3', 'Prime Coaching Sport', 'https://www.youtube.com/watch?v=FYS9o-__MwI'),
    coolDown: eeVideo('5 Minute Cool Down Stretch for Kids', 'PE cool down video', 'https://www.youtube.com/watch?v=P5hedhqyVGA'),
    exercise: eeVideo('Physical Exercise for Children', 'Smile and Learn', 'https://www.youtube.com/watch?v=lSuekPtI_Kc')
  };

  const readingLessons1 = [
    eeLesson('Short A Words', 'Practice short a words with pictures and simple sentences.', 'Reads short a words', '🍎', 'Listen for the short a sound. Choose the word that matches the picture.', [
      eeStep('Find the Word', 'Which word matches the apple picture?', 'Matches short a word', [['apple','',true],['igloo','',false],['sun','',false]], { prompt_icon: '🍎', prompt_label: 'apple picture', show_image_label: false, choice_label_mode: 'word' }),
      eeStep('Short A Sound', 'Which word has the short a sound?', 'Identifies short a sound', [['cat','',true],['bike','',false],['moon','',false]], { choice_label_mode: 'word', no_prompt_image: true, prompt_title: 'Listen for short A' }),
      eeDone('Read Aloud', 'Read this sentence with your grown-up: The cat sat.', 'Reads short sentence')
    ], null),
    eeLesson('Sight Words: I, See, A', 'Read common sight words in a tiny story.', 'Reads early sight words', '👁️', 'Tap the sight word that completes the sentence.', [
      eeStep('I ___ a dog.', 'Which word completes the sentence: I ___ a dog?', 'Uses sight word see', [['see','',true],['run','',false],['blue','',false]], { choice_label_mode: 'word', no_prompt_image: true, prompt_title: 'I ___ a dog.' }),
      eeStep('Find I', 'Which card shows the word I?', 'Recognizes sight word I', [['I','',true],['me','',false],['we','',false]], { choice_label_mode: 'word', no_prompt_image: true, prompt_title: 'Find the word I' }),
      eeDone('Read Together', 'Read: I see a cat. I see a dog.', 'Reads sight word sentence')
    ], null),
    eeLesson('Story Details: The Red Kite', 'Listen to a short story about Mia and her red kite, then answer story-detail questions.', 'Identifies story details', '🪁', 'Listen to the story, then choose the answers that match the story.', [
      { title: 'Who had the kite?', prompt_title: 'Think about the story', icon: '🪁', image_label: 'The Red Kite', show_image_label: true, instruction: 'In the story, who had the red kite?', skill: 'Identifies character', choices: [['Mia','👧',true],['Sam','🧒',false],['Ben','👦',false]] },
      { title: 'Where did it fly?', prompt_title: 'Think about the setting', icon: '🌳', image_label: 'Park', show_image_label: true, instruction: 'Where did Mia fly the kite?', skill: 'Identifies setting', choices: [['Park','🌳',true],['Kitchen','🍽️',false],['Bedroom','🛏️',false]] },
      eeDone('Tell It Back', 'Tell your grown-up one thing that happened in the red kite story.', 'Retells one detail')
    ], null, { lesson_text: 'Mia had a red kite. She took the kite to the park. The wind lifted the kite high above the grass. Mia smiled and held the string tight.' }),
    eeLesson('Beginning Sounds', 'Match pictures to the first sound you hear.', 'Identifies beginning sounds', '🔤', 'Say the picture name. Choose the beginning sound.', [
      eeStep('Ball', 'What sound does ball start with?', 'Identifies B sound', [['B','',true],['M','',false],['S','',false]], { prompt_icon: '🏀', prompt_label: 'ball', show_image_label: false, choice_label_mode: 'letter' }),
      eeStep('Sun', 'What sound does sun start with?', 'Identifies S sound', [['S','',true],['T','',false],['B','',false]], { prompt_icon: '☀️', prompt_label: 'sun', show_image_label: false, choice_label_mode: 'letter' }),
      eeDone('Sound Hunt', 'Find one thing near you that starts with the M sound.', 'Connects sounds to objects')
    ], null),
    eeLesson('Complete Sentences', 'Learn that a sentence tells a complete idea.', 'Recognizes complete sentence', '✏️', 'Choose the group of words that sounds complete.', [
      eeStep('Complete Idea', 'Which one is a complete sentence?', 'Chooses complete sentence', [['The dog runs.','',true],['The dog','',false],['Runs fast','',false]], { choice_label_mode: 'sentence', no_prompt_image: true, prompt_title: 'Choose a complete sentence' }),
      eeStep('Capital Letter', 'What should a sentence start with?', 'Identifies sentence start', [['Capital letter','',true],['Tiny picture','',false],['No spaces','',false]], { choice_label_mode: 'word', no_prompt_image: true, prompt_title: 'Sentence rule' }),
      eeDone('Say a Sentence', 'Say one complete sentence about an animal.', 'Creates oral sentence')
    ], v.sentences)
  ];

  const readingLessons2 = [
    eeLesson('Long A Words', 'Practice words where the vowel says its name.', 'Reads long a words', '🌧️', 'Look for the long A sound.', [
      eeStep('Find Long A', 'Which word has the long A sound?', 'Identifies long A', [['cake','',true],['cat','',false],['sun','',false]], { choice_label_mode: 'word', no_prompt_image: true, prompt_title: 'Listen for the long A sound' }),
      eeStep('Silent E', 'Which word uses silent e to make long A?', 'Recognizes silent e', [['lake','',true],['lap','',false],['log','',false]], { choice_label_mode: 'word', no_prompt_image: true, prompt_title: 'Silent e can help a vowel say its name' }),
      eeDone('Read Aloud', 'Read this sentence with your grown-up: Jake made a cake.', 'Reads long A sentence')
    ], null, { lesson_text: 'Long A can sound like the letter name A. In some words, silent e helps the vowel say its name. Cake and lake have the long A sound.' }),
    eeLesson('Story Retell: The Garden Plan', 'Read a short story and retell the beginning, middle, and end.', 'Retells a short story', '🌱', 'Think about what happened first, next, and last.', [
      eeStep('First', 'What did the children do first in the story?', 'Identifies beginning', [['They made a garden plan.','',true],['They went swimming.','',false],['They baked a cake.','',false]], { choice_label_mode: 'sentence', no_prompt_image: true, prompt_title: 'Think about the beginning' }),
      eeStep('Last', 'What happened at the end of the story?', 'Identifies ending', [['Tiny sprouts came up.','',true],['Snow covered the garden.','',false],['The kite flew away.','',false]], { choice_label_mode: 'sentence', no_prompt_image: true, prompt_title: 'Think about the ending' }),
      eeDone('Retell It', 'Tell the beginning, middle, and end to your grown-up.', 'Retells sequence')
    ], null, { lesson_text: 'Lena and Max wanted to grow a small garden. First, they drew a garden plan. Next, they planted seeds and watered the soil. A few days later, tiny green sprouts came up. Lena and Max smiled because their plan was working.' }),
    eeLesson('Main Idea', 'Find what a short passage is mostly about.', 'Identifies main idea', '💡', 'Choose what the passage is mostly about.', [
      eeStep('Passage Topic', 'A passage tells about dogs, leashes, food, and walks. What is it mostly about?', 'Identifies main idea', [['Taking care of dogs','',true],['Building a boat','',false],['Making music','',false]], { choice_label_mode: 'word', no_prompt_image: true, prompt_title: 'Find the main idea' }),
      eeStep('Detail Match', 'Which detail fits a passage about plants?', 'Selects matching detail', [['Plants need water.','',true],['Shoes have laces.','',false],['Trucks are loud.','',false]], { choice_label_mode: 'sentence', no_prompt_image: true, prompt_title: 'Choose the matching detail' }),
      eeDone('Say the Main Idea', 'Tell your grown-up what your favorite story is mostly about.', 'States main idea')
    ], null, { lesson_text: 'The main idea tells what a passage is mostly about. Details tell more about the main idea. If many details tell about dogs, the main idea is probably about dogs.' }),
    eeLesson('Prefixes and Suffixes', 'Notice small word parts that change meaning.', 'Recognizes simple word parts', '🧩', 'Look for the word part at the beginning or end.', [
      eeStep('Prefix un-', 'What does unhappy mean?', 'Understands un prefix', [['not happy','',true],['very happy','',false],['happy again','',false]], { choice_label_mode: 'word', no_prompt_image: true, prompt_title: 'Prefix un-' }),
      eeStep('Suffix -ful', 'What does helpful mean?', 'Understands ful suffix', [['full of help','',true],['without help','',false],['helped yesterday','',false]], { choice_label_mode: 'word', no_prompt_image: true, prompt_title: 'Suffix -ful' }),
      eeDone('Word Part Talk', 'Think of one word with un- or -ful.', 'Applies word part')
    ], null, { lesson_text: 'A prefix is a word part at the beginning of a word. A suffix is a word part at the end of a word. The prefix un- can mean not. The suffix -ful can mean full of.' }),
    eeLesson('Write a Small Paragraph', 'Plan three connected sentences about one topic.', 'Writes connected sentences', '📝', 'Choose the sentence that stays on topic.', [
      eeStep('Topic Match', 'The topic is pets. Which sentence belongs?', 'Chooses on-topic sentence', [['My dog likes walks.','',true],['The sun is hot.','',false],['I have blue shoes.','',false]], { choice_label_mode: 'sentence', no_prompt_image: true, prompt_title: 'Choose the on-topic sentence' }),
      eeStep('Closing Sentence', 'Which sentence can end a paragraph about apples?', 'Chooses closing sentence', [['Apples are a healthy snack.','',true],['I saw a train.','',false],['The moon is bright.','',false]], { choice_label_mode: 'sentence', no_prompt_image: true, prompt_title: 'Choose the closing sentence' }),
      eeDone('Say Three Sentences', 'Say three sentences about one topic to your grown-up.', 'Plans paragraph orally')
    ], null, { lesson_text: 'A small paragraph has sentences about one topic. The sentences should connect. A closing sentence can finish the idea.' })
  ];

  const mathLessons1 = [
    eeLesson('Numbers to 20', 'Count, read, and compare numbers up to 20.', 'Counts to 20', '🔢', 'Look at the number or group. Choose the answer.', [
      eeStep('How many stars are shown?', 'Choose the number that matches the stars.', 'Counts to 10', [['10','',true],['6','',false],['12','',false]], { choice_label_mode: 'number', prompt_icon: '⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐', prompt_label: '10 stars', show_image_label: false, prompt_title: 'How many stars are shown?' }),
      eeStep('Which card shows 15?', 'Choose the number 15.', 'Recognizes 15', [['15','',true],['51','',false],['5','',false]], { choice_label_mode: 'number', prompt_icon: '15', prompt_label: 'number 15', show_image_label: false, prompt_title: 'Which card shows 15?' }),
      eeDone('Count Aloud', 'Count from 1 to 20 with your grown-up.', 'Counts aloud to 20')
    ], v.count20, { lesson_text: 'Numbers help us count objects. Count each object one time. The last number you say tells how many there are.' }),
    eeLesson('Addition to 10', 'Put two groups together and count the total.', 'Adds within 10', '➕', 'Add the groups. Choose the sum.', [
      eeStep('What is 2 + 3?', 'Choose the sum.', 'Adds 2 and 3', [['5','',true],['4','',false],['6','',false]], { choice_label_mode: 'number', prompt_icon: '🍎🍎 + 🍎🍎🍎', prompt_label: '2 + 3 apples', show_image_label: false, prompt_title: 'What is 2 + 3?' }),
      eeStep('What is 4 + 1?', 'Choose the sum.', 'Adds 4 and 1', [['5','',true],['3','',false],['7','',false]], { choice_label_mode: 'number', prompt_icon: '⭐⭐⭐⭐ + ⭐', prompt_label: '4 + 1 stars', show_image_label: false, prompt_title: 'What is 4 + 1?' }),
      eeDone('Make an Addition', 'Use fingers or objects to show 1 + 2.', 'Models addition')
    ], v.addition, { lesson_text: 'Addition means putting groups together. Count the first group, count the second group, then count all of them to find the sum.' }),
    eeLesson('Subtraction to 10', 'Take away and count what is left.', 'Subtracts within 10', '➖', 'Look at what is taken away. Choose what is left.', [
      eeStep('Five apples. Two are eaten.', 'How many apples are left?', 'Subtracts 5 minus 2', [['3','',true],['2','',false],['5','',false]], { choice_label_mode: 'number', prompt_icon: '🍎🍎🍎🍎🍎 − 🍎🍎', prompt_label: '5 minus 2 apples', show_image_label: false, prompt_title: 'Five apples. Two are eaten.' }),
      eeStep('Four balls. One rolls away.', 'How many balls are left?', 'Subtracts 4 minus 1', [['3','',true],['1','',false],['4','',false]], { choice_label_mode: 'number', prompt_icon: '⚽⚽⚽⚽ − ⚽', prompt_label: '4 minus 1 ball', show_image_label: false, prompt_title: 'Four balls. One rolls away.' }),
      eeDone('Take Away', 'Show 3 fingers, put 1 down, and tell how many are left.', 'Models subtraction')
    ], v.subtraction1, { lesson_text: 'Subtraction means taking away. Count how many you start with, take some away, then count what is left.' }),
    eeLesson('2D Shapes', 'Name circles, squares, triangles, and rectangles.', 'Identifies 2D shapes', '🔷', 'Choose the shape that matches.', [
      eeStep('Which shape is a rectangle?', 'Choose the rectangle.', 'Identifies rectangle', [['Rectangle','▭',true],['Circle','⭕',false],['Triangle','🔺',false]], { choice_label_mode: 'word', no_prompt_image: true, prompt_title: 'Which shape is a rectangle?' }),
      eeStep('Which shape has no corners?', 'Choose the shape with no corners.', 'Identifies circle', [['Circle','⭕',true],['Square','◼️',false],['Triangle','🔺',false]], { choice_label_mode: 'word', no_prompt_image: true, prompt_title: 'Which shape has no corners?' }),
      eeDone('Shape Hunt', 'Find one rectangle near you.', 'Finds shape nearby')
    ], v.shapes, { lesson_text: 'Two-dimensional shapes are flat. A rectangle has four sides and four corners. A circle is round and has no corners.' }),
    eeLesson('Time to the Hour', 'Read simple clock times to the hour.', 'Tells time to hour', '🕒', 'Look at the clock time. Choose the hour.', [
      eeStep('Which time says 3 o\'clock?', 'Choose the matching time.', 'Reads 3 o clock', [['3:00','',true],['6:00','',false],['9:00','',false]], { choice_label_mode: 'word', prompt_icon: '🕒', prompt_label: 'clock showing 3 o clock', show_image_label: false, prompt_title: 'Which time says 3 o\'clock?' }),
      eeStep('Which time says 8 o\'clock?', 'Choose the matching time.', 'Reads 8 o clock', [['8:00','',true],['2:00','',false],['10:00','',false]], { choice_label_mode: 'word', prompt_icon: '🕗', prompt_label: 'clock showing 8 o clock', show_image_label: false, prompt_title: 'Which time says 8 o\'clock?' }),
      eeDone('Daily Time', 'Tell your grown-up one thing you do in the morning.', 'Connects time to routine')
    ], v.timeHour1, { lesson_text: 'When the big hand points to 12, we read the time to the hour. Look at the short hand to find the hour.' })
  ];

  const mathLessons2 = [
    eeLesson('Place Value to 100', 'Understand tens and ones.', 'Uses tens and ones', '🔟', 'Look at tens and ones. Choose the number.', [
      eeStep('What number is 2 tens and 3 ones?', 'Choose the number that matches 2 tens and 3 ones.', 'Identifies 23', [['23','',true],['32','',false],['5','',false]], { choice_label_mode: 'number', prompt_icon: '🔟', prompt_label: '2 tens + 3 ones', image_label: '2 tens + 3 ones', show_image_label: true, prompt_title: 'What number is 2 tens and 3 ones?' }),
      eeStep('What number is 4 tens?', 'Choose the number that matches 4 tens.', 'Identifies 40', [['40','',true],['14','',false],['4','',false]], { choice_label_mode: 'number', prompt_icon: '🔟', prompt_label: '4 tens', image_label: '4 tens', show_image_label: true, prompt_title: 'What number is 4 tens?' }),
      eeDone('Build a Number', 'Use objects to show one ten and five ones.', 'Models place value')
    ], v.placeValue2, { lesson_text: 'A ten is a group of 10 ones. Place value helps you see tens and ones. Two tens and three ones make 23.' }),
    eeLesson('Add Within 100', 'Add two-digit numbers using tens and ones.', 'Adds within 100', '➕', 'Add tens and ones carefully.', [
      eeStep('What is 23 + 10?', 'Choose the sum.', 'Adds ten to two-digit number', [['33','',true],['24','',false],['13','',false]], { choice_label_mode: 'number', prompt_icon: '➕', prompt_label: '23 + 10', image_label: '23 + 10', show_image_label: true, prompt_title: 'What is 23 + 10?' }),
      eeStep('What is 34 + 5?', 'Choose the sum.', 'Adds ones', [['39','',true],['83','',false],['35','',false]], { choice_label_mode: 'number', prompt_icon: '➕', prompt_label: '34 + 5', image_label: '34 + 5', show_image_label: true, prompt_title: 'What is 34 + 5?' }),
      eeDone('Explain Addition', 'Tell your grown-up how adding 10 changes a number.', 'Explains adding tens')
    ], v.addWithin100, { lesson_text: 'When you add 10 to a number, the tens grow by one ten. When you add ones, count forward by ones.' }),
    eeLesson('Subtract Within 100', 'Subtract tens or ones from two-digit numbers.', 'Subtracts within 100', '➖', 'Take away tens or ones.', [
      eeStep('What is 45 - 10?', 'Choose the difference.', 'Subtracts ten', [['35','',true],['44','',false],['55','',false]], { choice_label_mode: 'number', prompt_icon: '➖', prompt_label: '45 - 10', image_label: '45 - 10', show_image_label: true, prompt_title: 'What is 45 - 10?' }),
      eeStep('What is 28 - 4?', 'Choose the difference.', 'Subtracts ones', [['24','',true],['32','',false],['20','',false]], { choice_label_mode: 'number', prompt_icon: '➖', prompt_label: '28 - 4', image_label: '28 - 4', show_image_label: true, prompt_title: 'What is 28 - 4?' }),
      eeDone('Explain Subtraction', 'Tell what changes when you subtract 10.', 'Explains subtracting tens')
    ], v.subtractWithin100, { lesson_text: 'Subtraction means taking away. When you subtract 10, the tens go down by one ten. When you subtract ones, count back by ones.' }),
    eeLesson('Arrays and Equal Groups', 'Use rows and columns to count groups.', 'Recognizes arrays', '🔳', 'Look at equal groups and count.', [
      eeStep('How many stars are in 2 rows of 3?', 'Choose the total number of stars.', 'Counts array', [['6','',true],['5','',false],['8','',false]], { choice_label_mode: 'number', prompt_icon: '⭐⭐⭐\n⭐⭐⭐', prompt_label: '2 rows of 3 stars', image_label: '2 rows of 3 stars', show_image_label: false, prompt_title: 'How many stars are in 2 rows of 3?' }),
      eeStep('Which shows equal groups?', 'Choose the matching equal groups.', 'Identifies equal groups', [['2 and 2','🍎🍎  🍎🍎',true],['1 and 3','🍎  🍎🍎🍎',false],['4 and 1','🍎🍎🍎🍎 🍎',false]], { choice_label_mode: 'word', prompt_icon: '🍎🍎  🍎🍎', prompt_label: 'equal groups', show_image_label: false, prompt_title: 'Which shows equal groups?' }),
      eeDone('Make Groups', 'Make two equal groups using small objects.', 'Models equal groups')
    ], v.arrays2, { lesson_text: 'An array has objects in rows and columns. Equal groups have the same number in each group.' }),
    eeLesson('Measure Length', 'Compare and measure objects with nonstandard units.', 'Compares length', '📏', 'Choose longer, shorter, or the measurement.', [
      eeStep('Which object is longer?', 'Choose the longer object.', 'Identifies longer', [['Pencil','✏️✏️✏️',true],['Eraser','▭',false]], { choice_label_mode: 'word', prompt_icon: '📏', prompt_label: 'compare lengths', show_image_label: false, prompt_title: 'Which object is longer?' }),
      eeStep('A crayon is 5 cubes long. How long is it?', 'Choose the measurement.', 'Reads measurement', [['5 cubes','',true],['2 cubes','',false],['8 cubes','',false]], { choice_label_mode: 'word', prompt_icon: '🧱🧱🧱🧱🧱', prompt_label: '5 cubes long', show_image_label: false, prompt_title: 'A crayon is 5 cubes long. How long is it?' }),
      eeDone('Measure Nearby', 'Use paperclips or blocks to measure one object.', 'Measures object')
    ], v.measureLength2, { lesson_text: 'You can measure length with same-size objects such as cubes, blocks, or paper clips. Line them up from one end to the other.' })
  ];

  const scienceLessons1 = [
    eeLesson('Plant Parts', 'Learn roots, stems, leaves, and flowers.', 'Identifies plant parts', '🌱', 'Look at the plant. Choose the part.', [
      eeStep('Which part helps a plant use sunlight?', 'Choose the plant part.', 'Identifies leaves', [['Leaves','🍃',true],['Roots','🌱',false],['Stem','🌿',false]], { choice_label_mode: 'word', prompt_icon: '🪴', prompt_label: 'whole plant', show_image_label: false, prompt_title: 'Which part helps a plant use sunlight?' }),
      eeStep('Which plant part is usually under the soil?', 'Choose the plant part.', 'Identifies roots', [['Roots','🌱',true],['Flower','🌸',false],['Leaves','🍃',false]], { choice_label_mode: 'word', prompt_icon: '🪴', prompt_label: 'whole plant', show_image_label: false, prompt_title: 'Which plant part is usually under the soil?' }),
      eeDone('Plant Look', 'Look at a plant or picture and point to a leaf.', 'Observes plant part')
    ], null, { lesson_text: 'Plants have parts. Roots help hold the plant in the soil. A stem helps hold the plant up. Leaves help the plant use sunlight.' }),
    eeLesson('Weather Watch', 'Observe sunny, rainy, cloudy, and windy weather.', 'Identifies weather', '☀️', 'Look at the weather symbol.', [
      eeStep('Which picture shows rainy weather?', 'Choose the weather picture.', 'Identifies rain', [['Rain','🌧️',true],['Sun','☀️',false],['Snowman','⛄',false]], { choice_label_mode: 'word', no_prompt_image: true, prompt_title: 'Which picture shows rainy weather?' }),
      eeStep('Which picture shows wind?', 'Choose the weather picture.', 'Identifies wind', [['Wind','🌬️',true],['Apple','🍎',false],['Book','📖',false]], { choice_label_mode: 'word', no_prompt_image: true, prompt_title: 'Which picture shows wind?' }),
      eeDone('Look Outside', 'Tell your grown-up what the weather is today.', 'Observes weather')
    ], null, { lesson_text: 'Weather tells what the sky and air are like. It can be sunny, rainy, cloudy, snowy, or windy.' }),
    eeLesson('Animal Groups', 'Notice animals with fur, feathers, scales, or shells.', 'Sorts animals', '🐾', 'Choose the animal that matches.', [
      eeStep('Which animal has feathers?', 'Choose the animal.', 'Identifies bird trait', [['Bird','🐦',true],['Fish','🐟',false],['Cat','🐱',false]], { choice_label_mode: 'word', no_prompt_image: true, prompt_title: 'Which animal has feathers?' }),
      eeStep('Which animal may have scales?', 'Choose the animal.', 'Identifies fish trait', [['Fish','🐟',true],['Dog','🐶',false],['Horse','🐴',false]], { choice_label_mode: 'word', no_prompt_image: true, prompt_title: 'Which animal may have scales?' }),
      eeDone('Animal Tell', 'Name one animal and one body covering it has.', 'Describes animal')
    ], null, { lesson_text: 'Animals can be grouped by body coverings. Some have feathers, fur, scales, shells, or smooth skin.' }),
    eeLesson('Five Senses', 'Use sight, hearing, smell, taste, and touch.', 'Names five senses', '👂', 'Choose the sense that helps.', [
      eeStep('Which body part helps you hear?', 'Choose the body part.', 'Identifies hearing', [['Ears','👂',true],['Feet','🦶',false],['Elbow','💪',false]], { choice_label_mode: 'word', no_prompt_image: true, prompt_title: 'Which body part helps you hear?' }),
      eeStep('Which body part helps you smell flowers?', 'Choose the body part.', 'Identifies smell', [['Nose','👃',true],['Hand','✋',false],['Knee','🦵',false]], { choice_label_mode: 'word', no_prompt_image: true, prompt_title: 'Which body part helps you smell flowers?' }),
      eeDone('Sense Hunt', 'Use one sense to describe something safe nearby.', 'Uses senses')
    ], null, { lesson_text: 'Your five senses help you learn about the world. You can see, hear, smell, taste, and touch.' }),
    eeLesson('Day and Night Sky', 'Compare what we see in the day and night sky.', 'Compares day and night', '🌙', 'Choose day or night sky objects.', [
      eeStep('What do we usually see in the day sky?', 'Choose the sky object.', 'Identifies sun', [['Sun','☀️',true],['Stars','✨',false],['Owl','🦉',false]], { choice_label_mode: 'word', no_prompt_image: true, prompt_title: 'What do we usually see in the day sky?' }),
      eeStep('What can we see in the night sky?', 'Choose the sky object.', 'Identifies moon', [['Moon','🌙',true],['Rainbow','🌈',false],['Lunchbox','🍱',false]], { choice_label_mode: 'word', no_prompt_image: true, prompt_title: 'What can we see in the night sky?' }),
      eeDone('Sky Talk', 'Tell your grown-up one thing you see in the sky.', 'Observes sky')
    ], null, { lesson_text: 'The day sky is bright because the sun is up. The night sky is darker and may show the moon and stars.' })
  ];

  const scienceLessons2 = [
    eeLesson('Plant Life Cycle', 'Learn how seeds grow into plants.', 'Explains plant life cycle', '🌱', 'Put plant growth in order.', [
      eeStep('First Step', 'What comes first in a plant life cycle?', 'Identifies seed', [['Seed','🌰',true],['Flower','🌸',false],['Fruit','🍎',false]], { choice_label_mode: 'word' }),
      eeStep('Plant Need', 'What does a plant need to grow?', 'Names plant need', [['Water','💧',true],['Shoes','👟',false],['A backpack','🎒',false]], { choice_label_mode: 'word' }),
      eeDone('Sequence It', 'Tell the order: seed, sprout, plant.', 'Sequences life cycle')
    ], v.plants, { lesson_text: 'A plant life cycle shows how a plant grows and changes. A seed can sprout, grow leaves, become a plant, and make new seeds.' }),
    eeLesson('Habitats', 'Match animals to where they live.', 'Matches animals to habitats', '🏞️', 'Choose the home that fits the animal.', [
      eeStep('Where does a fish live?', 'Choose the habitat that fits the fish.', 'Matches fish habitat', [['Water','🌊',true],['Tree top','🌳',false],['Desert only','🏜️',false]], { choice_label_mode: 'word', prompt_icon: '🐟', prompt_label: 'fish', show_image_label: false, prompt_title: 'Where does a fish live?' }),
      eeStep('Where might a bird build a nest?', 'Choose the habitat that fits the bird.', 'Matches bird habitat', [['Tree','🌳',true],['Deep ocean','🌊',false],['Inside a shoe','👟',false]], { choice_label_mode: 'word', prompt_icon: '🐦', prompt_label: 'bird', show_image_label: false, prompt_title: 'Where might a bird build a nest?' }),
      eeDone('Habitat Talk', 'Name one animal and where it lives.', 'Describes habitat')
    ], null, { lesson_text: 'A habitat is a place where an animal lives. A fish lives in water. Many birds build nests in trees.' }),
    eeLesson('States of Matter', 'Observe solids, liquids, and gases.', 'Identifies states of matter', '🧊', 'Choose solid, liquid, or gas.', [
      eeStep('Solid', 'Which item is a solid?', 'Identifies solid', [['Ice cube','🧊',true],['Steam','💨',false],['Juice','🧃',false]], { choice_label_mode: 'word' }),
      eeStep('Liquid', 'Which item is a liquid?', 'Identifies liquid', [['Water','💧',true],['Rock','🪨',false],['Air','💨',false]], { choice_label_mode: 'word' }),
      eeDone('Matter Hunt', 'Find one solid object nearby.', 'Observes matter')
    ], v.matter, { lesson_text: 'Matter can be a solid, liquid, or gas. A solid keeps its shape. A liquid can pour. A gas can spread out.' }),
    eeLesson('Water Cycle Basics', 'Learn evaporation, clouds, rain, and collection.', 'Describes water cycle', '🌧️', 'Think about how water moves.', [
      eeStep('Rain', 'What falls from clouds during precipitation?', 'Identifies rain', [['Rain','🌧️',true],['Leaves','🍃',false],['Sand','🏖️',false]], { choice_label_mode: 'word' }),
      eeStep('Sun Heat', 'What helps water evaporate?', 'Identifies sun heat', [['Sun','☀️',true],['A spoon','🥄',false],['A hat','🧢',false]], { choice_label_mode: 'word' }),
      eeDone('Cycle Talk', 'Tell one step of the water cycle.', 'Explains water cycle step')
    ], v.water, { lesson_text: 'The water cycle is how water moves. The sun warms water. Water can evaporate, form clouds, and fall as rain.' }),
    eeLesson('Pushes and Pulls', 'Explore forces that move objects.', 'Identifies push and pull', '🧲', 'Decide if the movement is a push or a pull.', [
      eeStep('Open Door', 'Pulling a door handle is a...', 'Identifies pull', [['Pull','',true],['Push','',false]], { choice_label_mode: 'word', no_prompt_image: true, prompt_title: 'Door handle' }),
      eeStep('Kick Ball', 'Kicking a ball is a...', 'Identifies push', [['Push','',true],['Pull','',false]], { choice_label_mode: 'word', no_prompt_image: true, prompt_title: 'Kick a ball' }),
      eeDone('Safe Force', 'Safely push a small object and tell what happened.', 'Observes force')
    ], null, { lesson_text: 'A force is a push or a pull. A push moves something away from you. A pull moves something toward you.' })
  ];

  const socialLessons1 = [
    eeLesson('Classroom and Home Rules', 'Learn why rules help keep people safe and kind.', 'Explains rules', '🏫', 'Choose the helpful rule.', [
      eeStep('Which choice is kind?', 'Choose the helpful action.', 'Chooses kind rule', [['Take turns','🤝',true],['Push in line','✋',false],['Yell at others','📣',false]], { choice_label_mode: 'sentence', no_prompt_image: true, prompt_title: 'Which choice is kind?' }),
      eeStep('Which rule helps safety?', 'Choose the safe rule.', 'Chooses safety rule', [['Walk indoors','🚶',true],['Run with scissors','✂️',false],['Climb on tables','🪑',false]], { choice_label_mode: 'sentence', no_prompt_image: true, prompt_title: 'Which rule helps safety?' }),
      eeDone('Rule Talk', 'Tell one rule you follow at home or school.', 'Names rule')
    ], null, { lesson_text: 'Rules help people stay safe, kind, and ready to learn. Rules can help at home, at school, and in the community.' }),
    eeLesson('Community Helpers', 'Learn about workers who help the community.', 'Identifies community helpers', '👩‍🚒', 'Match the helper to the job.', [
      eeStep('Who helps put out fires?', 'Choose the community helper.', 'Identifies firefighter', [['Firefighter','🚒',true],['Baker','🍞',false],['Painter','🎨',false]], { choice_label_mode: 'word', no_prompt_image: true, prompt_title: 'Who helps put out fires?' }),
      eeStep('Who helps people feel better?', 'Choose the community helper.', 'Identifies doctor', [['Doctor','🩺',true],['Farmer','🌾',false],['Musician','🎵',false]], { choice_label_mode: 'word', no_prompt_image: true, prompt_title: 'Who helps people feel better?' }),
      eeDone('Helper Talk', 'Name one community helper and how they help.', 'Describes helper')
    ], null, { lesson_text: 'Community helpers are people who do work that helps others. Firefighters, doctors, teachers, farmers, and mail carriers are community helpers.' }),
    eeLesson('Map Symbols', 'Use simple symbols to read a map.', 'Reads simple map symbols', '🗺️', 'Match the map symbol to the place.', [
      eeStep('Which symbol could show a park?', 'Choose the map symbol.', 'Identifies park symbol', [['Park','🌳',true],['Restaurant','🍴',false],['Hotel','🛏️',false]], { choice_label_mode: 'word', no_prompt_image: true, prompt_title: 'Which symbol could show a park?' }),
      eeStep('Which symbol could show a school?', 'Choose the map symbol.', 'Identifies school symbol', [['School','🏫',true],['Pond','🐟',false],['Shoe store','👟',false]], { choice_label_mode: 'word', no_prompt_image: true, prompt_title: 'Which symbol could show a school?' }),
      eeDone('Map Talk', 'Draw or point to one place in your home.', 'Connects map to place')
    ], null, { lesson_text: 'A map can use small pictures called symbols. A tree can show a park. A school building can show a school. Symbols help us find places.' }),
    eeLesson('Needs and Wants', 'Sort things people need and things people want.', 'Distinguishes needs and wants', '🍎', 'Choose need or want.', [
      eeStep('Which item is a need?', 'Choose the need.', 'Identifies need', [['Water','💧',true],['Toy robot','🤖',false],['Sticker','⭐',false]], { choice_label_mode: 'word', no_prompt_image: true, prompt_title: 'Which item is a need?' }),
      eeStep('Which item is a want?', 'Choose the want.', 'Identifies want', [['Toy','🧸',true],['Food','🍎',false],['Safe home','🏠',false]], { choice_label_mode: 'word', no_prompt_image: true, prompt_title: 'Which item is a want?' }),
      eeDone('Sort It', 'Tell one need and one want.', 'Sorts need/want')
    ], null, { lesson_text: 'A need is something people must have to live and be safe. A want is something people would like to have.' }),
    eeLesson('Holidays and Traditions', 'Notice ways families and communities celebrate.', 'Recognizes traditions', '🎉', 'Choose the tradition or celebration.', [
      eeStep('What is a tradition?', 'Choose the best answer.', 'Defines tradition', [['Something people do again in a special way','🎉',true],['Only a math problem','➕',false],['A type of weather','🌧️',false]], { choice_label_mode: 'sentence', no_prompt_image: true, prompt_title: 'What is a tradition?' }),
      eeStep('What should we do when someone celebrates differently?', 'Choose the respectful action.', 'Chooses respect', [['Be respectful','🤝',true],['Laugh at them','😬',false],['Ignore kindness','🚫',false]], { choice_label_mode: 'sentence', no_prompt_image: true, prompt_title: 'What should we do?' }),
      eeDone('Family Talk', 'Tell your grown-up one family tradition.', 'Shares tradition')
    ], null, { lesson_text: 'A tradition is something people do again in a special way. Families and communities may have different traditions, and we can show respect.' })
  ];

  const socialLessons2 = [
    eeLesson('Types of Communities', 'Compare urban, suburban, and rural communities.', 'Compares communities', '🏙️', 'Choose the community type.', [
      eeStep('Rural', 'Which place has farms and open land?', 'Identifies rural', [['Rural','',true],['Urban','',false],['Suburban','',false]], { choice_label_mode: 'word', no_prompt_image: true, prompt_title: 'Farms and open land' }),
      eeStep('Urban', 'Which place has many tall buildings?', 'Identifies urban', [['Urban','',true],['Rural','',false],['Forest only','',false]], { choice_label_mode: 'word', no_prompt_image: true, prompt_title: 'Many tall buildings' }),
      eeDone('Community Talk', 'Tell what you notice about your community.', 'Describes community')
    ], null, { lesson_text: 'A community is a place where people live, work, and help each other. Urban communities often have many buildings. Rural communities often have farms and open land. Suburban communities may have homes, schools, and stores.' }),
    eeLesson('Goods and Services', 'Tell the difference between things people buy and work people do.', 'Distinguishes goods and services', '🛍️', 'Choose good or service.', [
      eeStep('Good', 'Which one is a good?', 'Identifies good', [['Apple','🍎',true],['Haircut','💇',false],['Bus ride','🚌',false]], { choice_label_mode: 'word' }),
      eeStep('Service', 'Which one is a service?', 'Identifies service', [['Doctor visit','🩺',true],['Book','📖',false],['Shoes','👟',false]], { choice_label_mode: 'word' }),
      eeDone('Name Examples', 'Name one good and one service.', 'Gives examples')
    ], null, { lesson_text: 'A good is something people can buy or use, like food or a book. A service is work that helps people, like a doctor visit or haircut.' }),
    eeLesson('Citizenship', 'Practice being responsible, respectful, and helpful.', 'Explains citizenship', '🤝', 'Choose the responsible action.', [
      eeStep('Responsible Choice', 'Which choice helps the group?', 'Chooses responsibility', [['Clean up your area','',true],['Leave a mess','',false],['Hide supplies','',false]], { choice_label_mode: 'sentence', no_prompt_image: true, prompt_title: 'Responsible choice' }),
      eeStep('Respectful Choice', 'Which choice shows respect?', 'Chooses respect', [['Listen while others speak','',true],['Interrupt loudly','',false],['Take every turn','',false]], { choice_label_mode: 'sentence', no_prompt_image: true, prompt_title: 'Respectful choice' }),
      eeDone('Citizen Talk', 'Tell one way to help at home or in your community.', 'Names helpful action')
    ], null, { lesson_text: 'A good citizen is responsible, respectful, and helpful. Good citizens help make a home, school, or community better.' }),
    eeLesson('Maps and Directions', 'Use north, south, east, west and simple direction words.', 'Uses map directions', '🧭', 'Follow a simple direction.', [
      eeStep('North', 'Which symbol means north on a compass?', 'Identifies north', [['N','',true],['S','',false],['W','',false]], { choice_label_mode: 'letter', no_prompt_image: true, prompt_title: 'Compass direction' }),
      eeStep('Left or Right', 'Which arrow points right?', 'Identifies right', [['Right →','',true],['Left ←','',false],['Up ↑','',false]], { choice_label_mode: 'word', no_prompt_image: true, prompt_title: 'Choose the arrow' }),
      eeDone('Direction Practice', 'Take two safe steps forward and one step back.', 'Follows directions')
    ], null, { lesson_text: 'Maps help people find places. A compass can show directions like north, south, east, and west. Direction words like left, right, forward, and back help you know where to go.' }),
    eeLesson('Past and Present', 'Compare long ago and today.', 'Compares past and present', '⏳', 'Choose past or present.', [
      eeStep('Long Ago', 'Which item was used more often long ago?', 'Identifies past object', [['Horse carriage','🐴',true],['Tablet','📱',false],['Robot vacuum','🤖',false]], { choice_label_mode: 'word' }),
      eeStep('Today', 'Which item is common today?', 'Identifies present object', [['Computer','💻',true],['Candle-only light','🕯️',false],['Covered wagon','🛞',false]], { choice_label_mode: 'word' }),
      eeDone('Compare It', 'Tell one thing that is different now than long ago.', 'Compares time')
    ], null, { lesson_text: 'Past means long ago. Present means now. We can compare tools, transportation, schools, and homes from the past and present.' })
  ];

  const artLessons = g === 1 ? [
    eeLesson('Primary Colors', 'Identify red, yellow, and blue.', 'Identifies primary colors', '🎨', 'Choose the color that matches.', [
      eeStep('Which object shows red?', 'Choose the red object.', 'Identifies red', [['Red apple','🍎',true],['Blue ball','🔵',false],['Yellow star','🟡',false]], { choice_label_mode: 'word', no_prompt_image: true, prompt_title: 'Which object shows red?' }),
      eeStep('Which object shows yellow?', 'Choose the yellow object.', 'Identifies yellow', [['Yellow sun','☀️',true],['Green leaf','🍃',false],['Blue book','📘',false]], { choice_label_mode: 'word', no_prompt_image: true, prompt_title: 'Which object shows yellow?' }),
      eeDone('Color Hunt', 'Find one red thing near you.', 'Finds color nearby')
    ], null, { lesson_text: 'The primary colors are red, yellow, and blue. Artists can use colors to make pictures and patterns.' }),
    eeLesson('Line Types', 'Recognize straight, curved, zigzag, and wavy lines.', 'Identifies line types', '〰️', 'Choose the line type.', [
      eeStep('Which line is wavy?', 'Choose the line type.', 'Identifies wavy line', [['Wavy','〰️',true],['Straight','—',false],['Zigzag','⚡',false]], { choice_label_mode: 'word', no_prompt_image: true, prompt_title: 'Which line is wavy?' }),
      eeStep('Which line is zigzag?', 'Choose the line type.', 'Identifies zigzag line', [['Zigzag','⚡',true],['Circle','⭕',false],['Dot','•',false]], { choice_label_mode: 'word', no_prompt_image: true, prompt_title: 'Which line is zigzag?' }),
      eeDone('Draw Lines', 'Draw a straight line and a wavy line.', 'Draws line types')
    ], null, { lesson_text: 'Artists use many kinds of lines. Lines can be straight, wavy, curved, or zigzag.' }),
    eeLesson('Shape Picture', 'Use shapes to make a picture.', 'Creates shape picture', '🔺', 'Choose shapes that can build pictures.', [
      eeStep('Which shape can be a roof?', 'Choose the shape.', 'Chooses triangle roof', [['Triangle','🔺',true],['Circle','⭕',false],['Star','⭐',false]], { choice_label_mode: 'word', no_prompt_image: true, prompt_title: 'Which shape can be a roof?' }),
      eeStep('Which shape can be a window?', 'Choose the shape.', 'Chooses square window', [['Square','◼️',true],['Wavy line','〰️',false],['Raindrop','💧',false]], { choice_label_mode: 'word', no_prompt_image: true, prompt_title: 'Which shape can be a window?' }),
      eeDone('Create', 'Draw a simple picture using two shapes.', 'Creates with shapes')
    ], v.artShapes),
    eeLesson('Pattern Art', 'Create repeated color or shape patterns.', 'Creates pattern art', '🔁', 'Find what comes next in the pattern.', [
      eeStep('Red, blue, red, blue. What comes next?', 'Choose what comes next in the pattern.', 'Completes pattern', [['Red','🔴',true],['Green','🟢',false],['Yellow','🟡',false]], { choice_label_mode: 'word', prompt_icon: '🔴 🔵 🔴 🔵 ❓', prompt_label: 'red blue red blue pattern', show_image_label: false, prompt_title: 'Red, blue, red, blue. What comes next?' }),
      eeStep('Circle, square, circle, square. What comes next?', 'Choose what comes next in the pattern.', 'Completes shape pattern', [['Circle','⭕',true],['Triangle','🔺',false],['Star','⭐',false]], { choice_label_mode: 'word', prompt_icon: '⭕ ◼️ ⭕ ◼️ ❓', prompt_label: 'circle square pattern', show_image_label: false, prompt_title: 'Circle, square, circle, square. What comes next?' }),
      eeDone('Make Art', 'Create a two-color pattern on paper.', 'Creates art pattern')
    ], v.patterns, { lesson_text: 'A pattern repeats. Look at what repeats, then choose what should come next.' }),
    eeLesson('Texture Rubbing', 'Explore bumpy, smooth, and rough textures.', 'Explores texture', '🧱', 'Choose the texture word.', [
      eeStep('Which word describes a brick wall?', 'Choose the texture word.', 'Identifies bumpy', [['Bumpy','🧱',true],['Silent','🤫',false],['Round','⭕',false]], { choice_label_mode: 'word', prompt_icon: '🧱', prompt_label: 'brick wall', show_image_label: false, prompt_title: 'Which word describes a brick wall?' }),
      eeStep('Which object may feel smooth?', 'Choose the smooth object.', 'Identifies smooth', [['Glass','🪟',true],['Rocky path','🪨',false],['Tree bark','🌳',false]], { choice_label_mode: 'word', no_prompt_image: true, prompt_title: 'Which object may feel smooth?' }),
      eeDone('Texture Hunt', 'Find one safe texture and describe it.', 'Describes texture')
    ], null, { lesson_text: 'Texture is how something feels or looks like it might feel. A brick wall can look bumpy. Glass can look smooth.' })

  ] : [
    eeLesson('Warm and Cool Colors', 'Sort colors by warm and cool families.', 'Sorts warm and cool colors', '🎨', 'Choose warm or cool.', [
      eeStep('Warm Color', 'Which color is warm?', 'Identifies warm color', [['Red','🔴',true],['Blue','🔵',false],['Purple','🟣',false]], { choice_label_mode: 'word' }),
      eeStep('Cool Color', 'Which color is cool?', 'Identifies cool color', [['Blue','🔵',true],['Orange','🟠',false],['Yellow','🟡',false]], { choice_label_mode: 'word' }),
      eeDone('Color Choice', 'Pick one warm and one cool color.', 'Chooses colors')
    ], null, { lesson_text: 'Warm colors can remind us of sunlight or fire. Red, orange, and yellow are warm colors. Cool colors can remind us of water or shade. Blue, green, and purple are cool colors.' }),
    eeLesson('Foreground and Background', 'Notice what is in front and what is behind.', 'Identifies foreground and background', '🖼️', 'Choose front or back.', [
      eeStep('Foreground', 'What is the foreground?', 'Identifies foreground', [['Things in front','',true],['Things far behind','',false],['Only the title','',false]], { choice_label_mode: 'word', no_prompt_image: true, prompt_title: 'Picture space' }),
      eeStep('Background', 'What is the background?', 'Identifies background', [['Things behind','',true],['The closest object','',false],['A pencil','',false]], { choice_label_mode: 'word', no_prompt_image: true, prompt_title: 'Picture space' }),
      eeDone('Picture Talk', 'Look at a picture and point to the background.', 'Finds background')
    ], null, { lesson_text: 'The foreground is the part of a picture that looks closest to you. The background is the part that looks farther away or behind the main objects.' }),
    eeLesson('Texture in Art', 'Use texture words to describe art.', 'Describes visual texture', '🧱', 'Choose the texture word.', [
      eeStep('Rough Texture', 'Which word means not smooth?', 'Identifies rough', [['Rough','',true],['Soft sound','',false],['Tiny number','',false]], { choice_label_mode: 'word', no_prompt_image: true, prompt_title: 'Texture word' }),
      eeStep('Soft Texture', 'Which object can feel soft?', 'Identifies soft', [['Cotton','☁️',true],['Brick','🧱',false],['Metal','🔩',false]], { choice_label_mode: 'word' }),
      eeDone('Texture Sketch', 'Draw an object and add a texture pattern.', 'Creates texture')
    ], null, { lesson_text: 'Texture is how something feels or how it looks like it might feel. Art can show rough, smooth, soft, bumpy, or fuzzy textures.' }),
    eeLesson('Symmetry', 'Find matching sides in a design.', 'Recognizes symmetry', '🦋', 'Look for matching sides.', [
      eeStep('Symmetrical Picture', 'Which picture can have matching sides?', 'Identifies symmetry', [['Butterfly','🦋',true],['Wavy string','〰️',false],['Pile of rocks','🪨',false]], { choice_label_mode: 'word' }),
      eeStep('Line of Symmetry', 'A line of symmetry splits a shape into...', 'Explains symmetry', [['Matching parts','',true],['Random parts','',false],['No parts','',false]], { choice_label_mode: 'word', no_prompt_image: true, prompt_title: 'Line of symmetry' }),
      eeDone('Fold Test', 'Fold paper and draw a simple matching design.', 'Creates symmetry')
    ], null, { lesson_text: 'Symmetry means two sides match. A line of symmetry can split a shape or picture into matching parts.' }),
    eeLesson('Art Reflection', 'Talk about choices in your artwork.', 'Reflects on artwork', '💬', 'Choose a helpful art reflection.', [
      eeStep('Reflection', 'Which sentence tells about art choices?', 'Identifies reflection', [['I used blue lines to show water.','',true],['The dog ran outside.','',false],['I ate lunch.','',false]], { choice_label_mode: 'sentence', no_prompt_image: true, prompt_title: 'Art reflection' }),
      eeStep('Improve', 'What can an artist do after looking at their work?', 'Identifies revision', [['Add or change details','',true],['Hide it forever','',false],['Never think about it','',false]], { choice_label_mode: 'word', no_prompt_image: true, prompt_title: 'Improve art' }),
      eeDone('Tell About Art', 'Tell your grown-up one choice you made in your art.', 'Reflects orally')
    ], null, { lesson_text: 'Artists can talk about the choices they made. They can say what colors, lines, shapes, or textures they used and what they might change next time.' })
  ];

  const musicLessons = [
    eeLesson('Steady Beat', 'Feel and keep a steady beat.', 'Keeps steady beat', '🎵', 'Listen and move with the beat.', [eeStep('Beat Choice', 'Which action can keep a steady beat?', 'Identifies beat action', [['Clap evenly','👏',true],['Run randomly','🏃',false],['Hide quietly','🙈',false]]), eeStep('Beat or No Beat', 'Which sound could have a beat?', 'Identifies beat', [['Drum tap','🥁',true],['Silent room','🤫',false],['A still picture','🖼️',false]]), eeDone('Clap Beat', 'Clap a steady beat with your grown-up.', 'Performs beat')], v.rhythm),
    eeLesson('Rhythm Patterns', 'Clap short and long sound patterns.', 'Claps rhythm pattern', '👏', 'Choose the rhythm pattern.', [eeStep('Clap Pattern', 'Which pattern is clap, rest, clap?', 'Recognizes rhythm', [['👏 🤫 👏','👏',true],['👏 👏 👏','👏',false],['🤫 🤫 👏','🤫',false]]), eeStep('Repeat Rhythm', 'Which means repeat?', 'Understands repeat', [['Do it again','🔁',true],['Stop forever','🛑',false],['Go to sleep','😴',false]]), eeDone('Make Rhythm', 'Make a clap-tap-clap pattern.', 'Creates rhythm')], v.rhythm),
    eeLesson('High and Low Sounds', 'Tell whether a sound is high or low.', 'Identifies high and low sounds', '🎶', 'Choose high or low.', [eeStep('Bird Sound', 'A tiny bird chirp may sound...', 'Identifies high sound', [['High','🐦',true],['Low','🐘',false]]), eeStep('Drum Sound', 'A big drum may sound...', 'Identifies low sound', [['Low','🥁',true],['High','🐭',false]]), eeDone('Voice Play', 'Make a high voice and a low voice safely.', 'Demonstrates pitch')], v.highlow),
    eeLesson('Fast and Slow Tempo', 'Move to fast and slow music.', 'Identifies tempo', '🏃', 'Choose fast or slow.', [eeStep('Fast Tempo', 'Which movement fits fast music?', 'Identifies fast tempo', [['Quick steps','🏃',true],['Slow stretch','🧘',false]]), eeStep('Slow Tempo', 'Which movement fits slow music?', 'Identifies slow tempo', [['Slow sway','🌊',true],['Speed run','⚡',false]]), eeDone('Tempo Move', 'Move your hands fast, then slow.', 'Demonstrates tempo')], v.tempo),
    eeLesson('Instrument Sounds', 'Listen for drums, guitars, piano, and bells.', 'Identifies instrument sounds', '🎸', 'Choose the instrument.', [eeStep('Drum', 'Which instrument is tapped or hit?', 'Identifies drum', [['Drum','🥁',true],['Book','📖',false],['Apple','🍎',false]]), eeStep('Guitar', 'Which instrument has strings?', 'Identifies guitar', [['Guitar','🎸',true],['Spoon','🥄',false],['Cloud','☁️',false]]), eeDone('Sound Talk', 'Name one instrument you know.', 'Names instrument')], v.instruments)
  ];

  const healthLessons = [
    eeLesson('Healthy Plate', 'Recognize fruits, vegetables, grains, protein, and dairy.', 'Identifies food groups', '🍎', 'Choose the healthy food match.', [eeStep('Fruit', 'Which one is a fruit?', 'Identifies fruit', [['Apple','🍎',true],['Sock','🧦',false],['Crayon','🖍️',false]]), eeStep('Vegetable', 'Which one is a vegetable?', 'Identifies vegetable', [['Carrot','🥕',true],['Cookie','🍪',false],['Ball','⚽',false]]), eeDone('Plate Talk', 'Name one healthy food you like.', 'Names healthy food')], v.healthFoods),
    eeLesson('Drink Water', 'Learn why water helps the body.', 'Explains hydration', '💧', 'Choose the water habit.', [eeStep('Best Drink', 'What drink helps your body stay hydrated?', 'Identifies water', [['Water','💧',true],['Paint','🎨',false],['Glue','🧴',false]], { prompt_icon: '💧', prompt_label: 'water drop', show_image_label: false }), eeStep('Thirst', 'When you are thirsty, what should you ask for?', 'Chooses water', [['Water','🥤',true],['Sand','🏖️',false],['A toy','🧸',false]]), eeDone('Water Check', 'Ask your grown-up when your next water break is.', 'Plans hydration')], null, { lesson_text: 'Water helps your body stay hydrated. It helps you feel ready to learn, play, and move.' }),
    eeLesson('Handwashing', 'Practice steps for clean hands.', 'Explains handwashing', '🧼', 'Choose the healthy handwashing step.', [eeStep('Soap', 'What helps clean your hands?', 'Identifies soap', [['Soap and water','🧼',true],['Mud','🟤',false],['Paint only','🎨',false]]), eeStep('When to Wash', 'When should you wash hands?', 'Identifies wash time', [['Before eating','🍽️',true],['Only on birthdays','🎂',false],['Never','🚫',false]]), eeDone('Wash Practice', 'Practice handwashing steps with your grown-up.', 'Practices handwashing')], v.handwash),
    eeLesson('Rest and Sleep', 'Learn why the body needs rest.', 'Explains rest', '😴', 'Choose the bedtime habit.', [eeStep('Rest Habit', 'Which helps your body rest?', 'Identifies rest habit', [['Calm bedtime routine','🛏️',true],['Jumping all night','🤸',false],['Loud screen time','📱',false]], { prompt_icon: '🛏️', prompt_label: 'bedtime', show_image_label: false }), eeStep('Sleep Helps', 'Sleep helps your body...', 'Explains sleep', [['Grow and learn','🌙',true],['Forget everything','❓',false],['Never move','🛑',false]]), eeDone('Routine Talk', 'Tell one calm thing you can do before bed.', 'Names routine')], null, { lesson_text: 'Rest and sleep help your body grow, learn, and feel ready for a new day.' }),
    eeLesson('Safe Choices', 'Make safe choices at home, outside, and online.', 'Identifies safe choices', '🛡️', 'Choose the safe action.', [eeStep('Trusted Adult', 'Who can help if something feels unsafe?', 'Identifies trusted adult', [['Trusted grown-up','👨‍👩‍👧',true],['A stranger only','❓',false],['No one','🚫',false]]), eeStep('Safe Play', 'Which is a safe play choice?', 'Chooses safe play', [['Use open space','🟩',true],['Run on wet floor','💧',false],['Throw toys at people','🧸',false]]), eeDone('Safety Talk', 'Tell one safety rule you follow.', 'Names safety rule')], null, { lesson_text: 'Safe choices protect your body. If something feels unsafe, ask a trusted grown-up for help.' })
  ];

  const peLessons = [
    eeLesson('Warm Up', 'Get the body ready before active movement.', 'Completes warm up', '🏃', 'Choose safe warm-up actions.', [eeStep('Warm-up Move', 'Which is a good warm-up?', 'Identifies warm-up', [['March in place','🚶',true],['Sit forever','🪑',false],['Jump on furniture','🛏️',false]]), eeStep('Start Slow', 'A warm-up should usually start...', 'Understands warm-up', [['Slow and safe','🐢',true],['Wild and unsafe','⚡',false]]), eeDone('Try It', 'March in place for ten seconds.', 'Performs warm-up')], v.peWarm),
    eeLesson('Balance Practice', 'Practice standing and moving with control.', 'Practices balance', '⚖️', 'Choose the balance action.', [eeStep('Balance', 'Which action practices balance?', 'Identifies balance', [['Stand on one foot safely','🦶',true],['Close eyes and run','🙈',false],['Spin near a table','🌀',false]]), eeStep('Safe Balance', 'What should be nearby when practicing balance?', 'Chooses safety', [['Open safe space','🟩',true],['Wet floor','💧',false],['Sharp toys','⚠️',false]]), eeDone('Try Balance', 'Balance safely for three seconds with a grown-up nearby.', 'Performs balance')], v.balance),
    eeLesson('Jump and Land', 'Jump safely and land with bent knees.', 'Jumps and lands safely', '🦘', 'Choose safe jumping.', [eeStep('Safe Landing', 'How should you land?', 'Identifies landing', [['Soft knees','🦵',true],['Locked knees','🚫',false],['On furniture','🛏️',false]]), eeStep('Jump Spot', 'Where is a safer place to jump?', 'Chooses safe place', [['Open floor','🟩',true],['Stairs','🪜',false],['Crowded room','🧸',false]]), eeDone('Small Jump', 'Do three small safe jumps.', 'Performs jumps')], v.jumpLand),
    eeLesson('Throw and Catch', 'Practice gentle throwing and catching with a soft ball.', 'Practices throw and catch', '🏀', 'Choose safe ball actions.', [eeStep('Safe Ball', 'What ball is best for indoor practice?', 'Chooses soft ball', [['Soft ball','🟠',true],['Heavy rock','🪨',false],['Glass cup','🥛',false]]), eeStep('Catch Ready', 'What helps you catch?', 'Identifies ready hands', [['Hands ready','🤲',true],['Eyes closed','🙈',false],['Back turned','↩️',false]]), eeDone('Practice', 'Toss a soft ball gently with your grown-up.', 'Practices throwing')], v.throwCatch),
    eeLesson('Cool Down', 'Slow the body down after movement.', 'Completes cool down', '🧘', 'Choose a cool-down action.', [eeStep('Cool Down Move', 'Which is a cool down?', 'Identifies cool down', [['Slow stretch','🧘',true],['Race faster','🏁',false],['Jump on couch','🛋️',false]]), eeStep('Breathing', 'What helps your body calm down?', 'Identifies breathing', [['Slow breath','🌬️',true],['Yelling','📣',false],['Running more','🏃',false]]), eeDone('Try Cool Down', 'Take one slow breath and stretch gently.', 'Performs cool down')], v.coolDown)
  ];

  return [
    { key:'reading', name:`${label} Reading & Phonics`, subject:'Reading & Phonics', course_type:'core', description: g === 1 ? 'Short vowels, sight words, beginning sounds, story details, and sentences.' : 'Long vowels, story retell, main idea, word parts, and paragraph readiness.', unit_title: g === 1 ? 'Foundations of Reading' : 'Growing Readers and Writers', unit_description:`${label} early reading activities with audio-supported prompts and one page at a time practice.`, lessons: g === 1 ? readingLessons1 : readingLessons2 },
    { key:'math', name:`${label} Math`, subject:'Math', course_type:'core', description: g === 1 ? 'Numbers to 20, addition, subtraction, shapes, and time.' : 'Place value, two-digit addition and subtraction, equal groups, and measurement.', unit_title: g === 1 ? 'Numbers and Early Operations' : 'Tens, Ones, and Problem Solving', unit_description:`${label} guided math activities with visuals and practice.`, lessons: g === 1 ? mathLessons1 : mathLessons2 },
    { key:'science', name:`${label} Science & Discovery`, subject:'Science', course_type:'core', description: g === 1 ? 'Plants, weather, animals, senses, and day/night sky.' : 'Life cycles, habitats, matter, water cycle, and pushes/pulls.', unit_title: g === 1 ? 'Observe the World' : 'How the World Works', unit_description:`${label} science discovery with videos, observations, and simple checks.`, lessons: g === 1 ? scienceLessons1 : scienceLessons2 },
    { key:'social', name:`${label} Social Studies`, subject:'Social Studies', course_type:'core', description: g === 1 ? 'Rules, community helpers, maps, needs and wants, and traditions.' : 'Communities, goods and services, citizenship, maps, and past/present.', unit_title: g === 1 ? 'My Home, School, and Community' : 'Communities and Citizenship', unit_description:`${label} social studies with real-world examples and simple reflection.`, lessons: g === 1 ? socialLessons1 : socialLessons2 },
    { key:'art', name:`${label} Art Fundamentals`, subject:'Art', course_type:'elective', description: g === 1 ? 'Colors, lines, shapes, patterns, and textures.' : 'Warm/cool colors, foreground/background, texture, symmetry, and reflection.', unit_title: g === 1 ? 'Create with Color and Shape' : 'Art Choices and Design', unit_description:`${label} visual art activities with clear pictures and simple creating.`, lessons: artLessons },
    { key:'music', name:`${label} Music Fundamentals`, subject:'Music', course_type:'elective', description:'Beat, rhythm, pitch, tempo, and instrument listening.', unit_title:'Listen, Clap, and Create', unit_description:`${label} music activities with rhythm, sound, and listening.`, lessons: musicLessons },
    { key:'health', name:`${label} Health and Nutrition`, subject:'Health and Nutrition', course_type:'elective', description:'Healthy foods, water, handwashing, rest, and safety.', unit_title:'Healthy Habits', unit_description:`${label} health activities with practical habits.`, lessons: healthLessons },
    { key:'pe', name:`${label} Physical Education`, subject:'Physical Education', course_type:'elective', description:'Warm-up, balance, jumping, throwing, catching, and cool down.', unit_title:'Move Safely and Stay Active', unit_description:`${label} physical education activities with movement and safety.`, lessons: peLessons }
  ];
}


async function ensureKindergartenBlueprintSampleLessons() {
  try {
    const C = (label, icon, correct, extra = {}) => [label, icon, correct, extra];
    const N = (label, correct) => [label, null, correct, { choice_mode: 'number' }];
    const W = (label, correct) => [label, null, correct, { choice_mode: 'word' }];
    const L = (label, correct) => [label, null, correct, { choice_mode: 'letter' }];
    const lessonObj = (title, description, skill, icon, instruction, steps, opts = {}) => ({
      title, description, skill, icon, instruction,
      lesson_text: opts.lesson_text || null,
      lesson_audio_text: opts.lesson_text || instruction,
      hide_prompt_label: opts.hide_prompt_label !== undefined ? opts.hide_prompt_label : true,
      hide_choice_labels_preselect: opts.hide_choice_labels_preselect !== undefined ? opts.hide_choice_labels_preselect : true,
      review: !!opts.review,
      steps
    });
    async function updateByTitle(title, courseLike, data) {
      const row = (await pool.query(`
        SELECT l.id
          FROM lessons l
          JOIN units u ON l.unit_id = u.id
          JOIN courses c ON u.course_id = c.id
         WHERE c.grade_level = 0
           AND COALESCE(l.is_active, true) = true
           AND LOWER(l.title) = LOWER($1)
           AND LOWER(c.name) LIKE LOWER($2)
         ORDER BY l.id DESC
         LIMIT 1`, [title, courseLike])).rows[0];
      if (!row) return false;
      await updateFlexible('lessons', row.id, {
        title: data.title,
        description: data.description,
        skill_name: data.skill,
        learning_objectives: data.skill,
        instructional_content: data.instruction,
        audio_url: kTtsPlaceholder(data.instruction),
        image_url: kSvgImage(data.icon || '⭐', data.title, '#fff4d8', '#e7f8ff', false),
        activity_data: kBuildActivityData(data),
        video_url: null,
        video_title: null,
        video_source: null,
        video_verified: false,
        interactive_type: 'activity_flow',
        lesson_type: 'kindergarten_activity'
      });
      return true;
    }

    const countOneToFive = lessonObj('Count 1 to 5', 'Count clear groups of apples from one to five.', 'Counts objects to 5', 'apple:5', 'Count the apples. Choose the number that matches.', [
      { title:'Count 1 apple', instruction:'How many apples do you see?', skill:'Counts 1 object', prompt_icon:'apple:1', prompt_label:'1 apple', show_image_label:false, choice_label_mode:'number', choices:[N('1',true),N('2',false),N('3',false)] },
      { title:'Count 2 apples', instruction:'How many apples do you see?', skill:'Counts 2 objects', prompt_icon:'apple:2', prompt_label:'2 apples', show_image_label:false, choice_label_mode:'number', choices:[N('2',true),N('1',false),N('4',false)] },
      { title:'Count 3 apples', instruction:'How many apples do you see?', skill:'Counts 3 objects', prompt_icon:'apple:3', prompt_label:'3 apples', show_image_label:false, choice_label_mode:'number', choices:[N('3',true),N('5',false),N('2',false)] },
      { title:'Count 4 apples', instruction:'How many apples do you see?', skill:'Counts 4 objects', prompt_icon:'apple:4', prompt_label:'4 apples', show_image_label:false, choice_label_mode:'number', choices:[N('4',true),N('3',false),N('5',false)] },
      { title:'Count 5 apples', instruction:'How many apples do you see?', skill:'Counts 5 objects', prompt_icon:'apple:5', prompt_label:'5 apples', show_image_label:false, choice_label_mode:'number', choices:[N('5',true),N('4',false),N('2',false)] }
    ], { lesson_text:'Look at the group of apples. Count each apple one time. Then choose the number that tells how many apples there are.' });
    await updateByTitle('Count 1 to 5', '%math%', countOneToFive);

    const weatherToday = lessonObj('Weather Today', 'Choose clear weather pictures without answer labels.', 'Identifies common weather', 'sunny', 'Look at the answer pictures. Choose the weather you hear.', [
      { title:'Find sunny', instruction:'Which picture shows sunny weather?', skill:'Identifies sunny weather', no_prompt_image:true, choices:[C('Sunny','sunny',true),C('Rainy','rainy',false),C('Cloudy','cloudy',false)] },
      { title:'Find rainy', instruction:'Which picture shows rainy weather?', skill:'Identifies rainy weather', no_prompt_image:true, choices:[C('Rainy','rainy',true),C('Sunny','sunny',false),C('Cloudy','cloudy',false)] },
      { title:'Find cloudy', instruction:'Which picture shows cloudy weather?', skill:'Identifies cloudy weather', no_prompt_image:true, choices:[C('Cloudy','cloudy',true),C('Sunny','sunny',false),C('Rainy','rainy',false)] },
      { title:'Weather tool', instruction:'Which picture would help on a rainy day?', skill:'Matches rain tool', no_prompt_image:true, choices:[C('Umbrella','☂️',true),C('Sunglasses','🕶️',false),C('Beach ball','⚽',false)] },
      { title:'Weather review', instruction:'Which picture shows bright sun?', skill:'Reviews sunny weather', no_prompt_image:true, choices:[C('Sun','sunny',true),C('Cloud','cloudy',false),C('Rain','rainy',false)] }
    ], { lesson_text:'Weather tells what the sky and air are like. It can be sunny, rainy, cloudy, windy, snowy, hot, or cold.' });
    await updateByTitle('Weather Today', '%science%', weatherToday);

    const beginningSounds = lessonObj('Beginning Sounds A to D', 'Listen for beginning sounds and choose matching pictures.', 'Matches beginning sounds A-D', 'B', 'Listen to the beginning sound. Choose the picture that starts with that sound.', [
      { title:'Sound /b/', instruction:'Which picture starts with B?', skill:'Identifies B sound', no_prompt_image:true, choices:[C('Ball','ball',true),C('Dog','dog',false),C('Cat','cat',false)] },
      { title:'Sound /c/', instruction:'Which picture starts with C?', skill:'Identifies C sound', no_prompt_image:true, choices:[C('Cat','cat',true),C('Ball','ball',false),C('Dog','dog',false)] },
      { title:'Sound /d/', instruction:'Which picture starts with D?', skill:'Identifies D sound', no_prompt_image:true, choices:[C('Dog','dog',true),C('Cat','cat',false),C('Book','book',false)] },
      { title:'Letter starts ball', instruction:'Which letter starts ball?', skill:'Chooses beginning letter B', prompt_icon:'ball', prompt_label:'Ball', show_image_label:false, choices:[L('B',true),L('M',false),L('S',false)] },
      { title:'Letter starts cat', instruction:'Which letter starts cat?', skill:'Chooses beginning letter C', prompt_icon:'cat', prompt_label:'Cat', show_image_label:false, choices:[L('C',true),L('T',false),L('P',false)] }
    ], { lesson_text:'The beginning sound is the first sound you hear in a word. Ball starts with B. Cat starts with C. Dog starts with D.' });
    await updateByTitle('Letter B Sound', '%reading%', beginningSounds).catch(() => {});

    const sightOne = lessonObj('Sight Words Unit 1', 'Practice the first five sight words as a unit.', 'Reads sight words I, see, the, a, my', 'book', 'Find and use each sight word from this unit.', [
      { title:'Find I', instruction:'Which word says I?', skill:'Reads sight word I', no_prompt_image:true, choice_label_mode:'word', choices:[W('I',true),W('see',false),W('my',false)] },
      { title:'Find see', instruction:'Which word says see?', skill:'Reads sight word see', no_prompt_image:true, choice_label_mode:'word', choices:[W('see',true),W('the',false),W('a',false)] },
      { title:'Find the', instruction:'Which word says the?', skill:'Reads sight word the', no_prompt_image:true, choice_label_mode:'word', choices:[W('the',true),W('my',false),W('I',false)] },
      { title:'Find a', instruction:'Which word says a?', skill:'Reads sight word a', no_prompt_image:true, choice_label_mode:'word', choices:[W('a',true),W('see',false),W('the',false)] },
      { title:'Find my', instruction:'Which word says my?', skill:'Reads sight word my', no_prompt_image:true, choice_label_mode:'word', choices:[W('my',true),W('I',false),W('see',false)] }
    ], { lesson_text:'This unit practices five sight words: I, see, the, a, and my. Read the word you hear and choose the match.', hide_choice_labels_preselect:false });
    await updateByTitle('Sight Word: I', '%reading%', sightOne).catch(() => {});

    const mailCarrier = lessonObj('Mail Carrier', 'Identify community helpers with real helper pictures.', 'Identifies mail carrier', 'mail-carrier', 'Choose the community helper that matches the job.', [
      { title:'Brings mail', instruction:'Who brings mail?', skill:'Identifies mail carrier', no_prompt_image:true, choices:[C('Mail carrier','mail-carrier',true),C('Doctor','👩‍⚕️',false),C('Chef','👩‍🍳',false)] },
      { title:'Carries letters', instruction:'Who carries letters to homes?', skill:'Matches mail carrier job', no_prompt_image:true, choices:[C('Mail carrier','mail-carrier',true),C('Firefighter','👩‍🚒',false),C('Teacher','👩‍🏫',false)] },
      { title:'Post office helper', instruction:'Who works with mail and packages?', skill:'Identifies postal worker', no_prompt_image:true, choices:[C('Postal worker','mail-carrier',true),C('Dancer','💃',false),C('Lifeguard','🛟',false)] },
      { title:'Helper tool', instruction:'Which picture shows something a mail carrier delivers?', skill:'Identifies mail item', no_prompt_image:true, choices:[C('Letter','✉️',true),C('Soup','🍲',false),C('Paint','🎨',false)] },
      { title:'Community helper review', instruction:'Which helper brings mail to people?', skill:'Reviews mail carrier', no_prompt_image:true, choices:[C('Mail carrier','mail-carrier',true),C('Builder','👷',false),C('Farmer','👩‍🌾',false)] }
    ], { lesson_text:'A mail carrier is a community helper. Mail carriers bring letters and packages to homes, schools, and businesses.' });
    await updateByTitle('Mail Carrier', '%social studies%', mailCarrier);

    const usSymbol = lessonObj('United States Symbol', 'Identify a real flag as a country symbol.', 'Identifies flag as symbol', 'flag', 'Look at the pictures. Choose the country symbol.', [
      { title:'Find the flag', instruction:'Which picture can be a country symbol?', skill:'Identifies flag', no_prompt_image:true, choices:[C('Flag','flag',true),C('Cookie','🍪',false),C('Shoe','shoe',false)] },
      { title:'Symbol review', instruction:'Which picture shows a flag?', skill:'Finds flag picture', no_prompt_image:true, choices:[C('Flag','flag',true),C('Book','book',false),C('Apple','apple',false)] },
      { title:'Not a symbol', instruction:'Which picture is not a country symbol?', skill:'Sorts symbol and object', no_prompt_image:true, choices:[C('Shoe','shoe',true),C('Flag','flag',false),C('Flag on a pole','flag',false)] },
      { title:'Flag match', instruction:'Choose the flag.', skill:'Matches flag visual', no_prompt_image:true, choices:[C('Flag','flag',true),C('Dog','dog',false),C('Cat','cat',false)] },
      { title:'Symbol check', instruction:'What picture can stand for a country?', skill:'Reviews country symbol', no_prompt_image:true, choices:[C('Flag','flag',true),C('Ball','ball',false),C('Bear','bear',false)] }
    ], { lesson_text:'A symbol is a picture or object that stands for something. A flag can be a symbol for a country.' });
    await updateByTitle('United States Symbol', '%social studies%', usSymbol);

    const march = lessonObj('March in Place', 'Practice marching with real movement prompts.', 'Marches in place', 'march', 'Follow five safe marching prompts.', [
      { title:'Marching legs', instruction:'Lift one knee, then put it down.', skill:'Marches one step', prompt_icon:'march', prompt_label:'March in place', show_image_label:false, done:true },
      { title:'March arms', instruction:'Move your arms while you march.', skill:'Coordinates arms and legs', prompt_icon:'march', prompt_label:'March arms', show_image_label:false, done:true },
      { title:'Slow march', instruction:'March slowly for five counts.', skill:'Marches slowly', prompt_icon:'march', prompt_label:'Slow march', show_image_label:false, done:true },
      { title:'Freeze', instruction:'Stop your body when a grown-up says freeze.', skill:'Follows freeze direction', prompt_icon:'march', prompt_label:'Freeze after marching', show_image_label:false, done:true },
      { title:'March review', instruction:'Which picture shows marching?', skill:'Identifies marching', no_prompt_image:true, choices:[C('Marching','march',true),C('Sleeping','bed',false),C('Painting','🎨',false)] }
    ], { lesson_text:'Marching means stepping in place. Lift one knee, put it down, then lift the other knee. Move safely in open space.' });
    await updateByTitle('March in Place', '%physical education%', march).catch(() => updateByTitle('March in Place', '%movement%', march));

    const balance = lessonObj('Balance on One Foot', 'Practice balance with child movement visuals.', 'Balances on one foot', 'balance', 'Try balance steps safely with a grown-up nearby.', [
      { title:'Stand tall', instruction:'Stand tall with your feet on the floor.', skill:'Prepares to balance', prompt_icon:'balance', prompt_label:'Stand tall', show_image_label:false, done:true },
      { title:'Lift one foot', instruction:'Lift one foot a little off the floor.', skill:'Starts one-foot balance', prompt_icon:'balance', prompt_label:'Lift one foot', show_image_label:false, done:true },
      { title:'Arms out', instruction:'Hold your arms out to help your body stay steady.', skill:'Uses arms for balance', prompt_icon:'balance', prompt_label:'Arms out', show_image_label:false, done:true },
      { title:'Count to three', instruction:'Try to balance while you count to three.', skill:'Balances for three counts', prompt_icon:'balance', prompt_label:'Balance count', show_image_label:false, done:true },
      { title:'Balance picture', instruction:'Which picture shows balancing on one foot?', skill:'Identifies balance', no_prompt_image:true, choices:[C('Balance','balance',true),C('Sleep','bed',false),C('Run','shoe',false)] }
    ], { lesson_text:'Balance means keeping your body steady. A child can balance by standing on one foot with arms out.' });
    await updateByTitle('Balance on One Foot', '%movement%', balance).catch(() => updateByTitle('Balance Challenge', '%physical education%', balance));

    // Retire the old combined Kindergarten Art & Music course whenever it still appears.
    await cleanupKindergartenArtMusicCategory().catch(() => {});
    console.log('Kindergarten blueprint sample lessons refreshed: count, weather, phonics, sight words, social studies, movement.');
  } catch (e) {
    console.error('ensureKindergartenBlueprintSampleLessons error:', e.message);
  }
}


async function ensureEarlyElementaryGradesOneTwoFoundation() {
  for (const grade of [1, 2]) {
    const plans = earlyElementaryPlans(grade);
    const created = {};
    for (const plan of plans) {
      created[plan.key] = await ensureCourse({
        name: plan.name,
        grade_level: grade,
        subject: plan.subject,
        course_type: plan.course_type,
        description: plan.description,
        credit_hours: plan.course_type === 'core' ? 1.0 : 0.5
      });
    }
    for (const plan of plans) {
      const course = created[plan.key];
      const unit = await ensureUnit(course.id, 1, plan.unit_title, plan.unit_description);
      for (let i = 0; i < plan.lessons.length; i++) {
        const item = plan.lessons[i];
        const lesson = await ensureLesson(unit.id, i + 1, {
          title: item.title,
          description: item.description,
          lesson_type: 'early_elementary_activity',
          interactive_type: 'activity_flow',
          skill_name: item.skill,
          learning_objectives: item.skill,
          instructional_content: item.instruction,
          audio_url: kTtsPlaceholder(item.instruction),
          image_url: kSvgImage(item.icon || '⭐', item.title, '#fff4d8', '#e7f8ff'),
          activity_data: kBuildActivityData(item),
          video_url: item.video_url,
          video_title: item.video_title,
          video_source: item.video_source,
          video_verified: !!item.video_url
        });
        const firstChoiceStep = (item.steps || []).find(st => Array.isArray(st.choices) && st.choices.length);
        const correctChoice = firstChoiceStep?.choices?.find(c => c[2]);
        const questions = correctChoice
          ? [mc(1, kFriendlyPromptForSeed(firstChoiceStep.instruction), String(correctChoice[0]), firstChoiceStep.choices.filter(c => !c[2]).map(c => String(c[0])))]
          : [wr(1, `Parent note: What did the student practice in "${item.title}"?`)];
        await safeEnsureAssignment(lesson.id, {
          title: item.title + ' Activity',
          description: item.description,
          instructions: 'Complete this early elementary one-page-at-a-time activity. Parent help is allowed when needed.',
          assignment_type: 'activity',
          weight_category: 'practice',
          point_value: 0,
          questions
        }, `grade-${grade}-${plan.key}-${i + 1}`);
      }
    }
    const students = await pool.query('SELECT id FROM students WHERE grade_level = $1', [grade]);
    for (const st of students.rows) {
      for (const c of Object.values(created)) {
        await safeEnrollStudentInCourse(st.id, c.id);
      }
    }
  }
}

function kFriendlyPromptForSeed(text) {
  return String(text || '').replace(/\/[A-Za-z]\//g, (m) => m.replace(/\//g, '').toUpperCase() + ' sound');
}


async function ensureVisibleGradeFiveElectiveContentForCourse(courseRow) {
  if (!courseRow) return false;
  const gradeLevel = parseInt(courseRow.grade_level || 5);
  if (gradeLevel !== 5) return false;
  const text = String([courseRow.subject, courseRow.name].filter(Boolean).join(' ')).toLowerCase();
  let unitTitle, unitDescription, lessonData, assignments;

  if (text.includes('art')) {
    unitTitle = 'Elements of Art and Creative Projects';
    unitDescription = 'Learn line, shape, color, texture, and space through guided art projects.';
    lessonData = {
      title: 'Line, Shape, and Pattern Project', description: 'Use lines and shapes to create an original patterned artwork.', lesson_type: 'guided_project', interactive_type: 'visual_project', skill_name: 'Elements of Art', video_title: 'Easy Art Activity to Teach Lines & Shapes', video_source: 'Miriam Cutelis', video_url: 'https://www.youtube.com/watch?v=dI9KvApZHzM', video_verified: true,
      learning_objectives: 'Identify line and shape. Use repeating patterns. Create an original artwork. Reflect on design choices.',
      instructional_content: 'Artists use lines and shapes to build pictures, patterns, and designs. A line can be straight, curved, zigzag, wavy, thick, or thin. A shape is a closed space, such as a circle, square, triangle, rectangle, or an organic free-form shape. A pattern repeats a line, shape, or color. Artists use patterns to create movement and interest.',
      vocabulary: [{term:'line',definition:'A mark that moves from one point to another.'},{term:'shape',definition:'A closed space such as a circle, square, or triangle.'},{term:'pattern',definition:'A repeated line, shape, color, or design.'},{term:'composition',definition:'How the parts of an artwork are arranged.'}],
      examples: [{title:'Pattern Example', content:'A student draws triangles, then repeats curved lines inside each triangle.', solution:'The artwork uses line, shape, and repetition.'}],
      guided_practice: [{question:'Look around the room. Name one object that has a shape and one object that has a pattern.', hint:'A window may be a rectangle. A blanket may have a pattern.'}],
      materials_needed: 'Paper, pencil, crayons, colored pencils, or markers.',
      activity_data: visualActivity([{title:'Lines', text:'Straight, curved, zigzag, wavy, thick, and thin lines can create movement.', icon:'〰️'},{title:'Shapes', text:'Geometric shapes are clear and regular. Organic shapes look more natural or free-form.', icon:'🔺'},{title:'Pattern', text:'Repeat a line, shape, or color to make the viewer’s eye move across the page.', icon:'🔁'}], {supplies:['Paper','Pencil','Crayons or markers'], activity_steps:['Draw a border around your page.','Add at least 3 kinds of lines.','Add at least 3 shapes.','Repeat one design to create a pattern.','Add color and give your artwork a title.'], project_prompt:'Create a patterned artwork using line and shape. Then write one sentence explaining your favorite part.'})
    };
    assignments = [
      { title: 'Create a Line and Shape Artwork', description: 'Create a patterned artwork and reflect on your design.', instructions: 'Review the art examples, gather supplies, create your artwork, then answer the reflection question.', assignment_type: 'activity', weight_category: 'homework', point_value: 100, questions: [wr(1, 'What did you create? Describe at least two lines or shapes you used in your artwork.')] },
      { title: 'Elements of Art Quick Check', description: 'Check your understanding of line, shape, pattern, and texture.', instructions: 'Answer the art questions after reviewing the lesson and visual examples.', assignment_type: 'quiz', weight_category: 'quiz', point_value: 100, questions: [mc(1, 'Which answer best describes a line in art?', 'A mark that moves from one point to another.', ['A closed shape with no sides.', 'Only a color on a page.', 'A finished painting only.']), mc(2, 'Why do artists repeat shapes or colors?', 'To create a pattern or rhythm in the artwork.', ['To erase the artwork.', 'To hide every line.', 'To make art impossible to see.']), wr(3, 'Name two elements of art you used or saw in the lesson and explain how they can help create artwork.')] }
    ];
  } else if (text.includes('music')) {
    unitTitle = 'Beat, Rhythm, and Instrument Families';
    unitDescription = 'Explore rhythm, tempo, dynamics, and instrument sounds.';
    lessonData = { title: 'Instrument Families and Sounds', description: 'Learn how instruments are grouped and practice identifying sounds.', lesson_type: 'guided_audio', interactive_type: 'sound_cards', skill_name: 'Instrument Families', video_title: 'The Instruments of the Orchestra Song', video_source: 'Hopscotch', video_url: 'https://www.youtube.com/watch?v=JVY4tj-JTBc', video_verified: true, learning_objectives: 'Identify the four main instrument families. Compare sounds. Listen for pitch, volume, and tone color.', instructional_content: 'Musicians group instruments into families based on how the sound is made. String instruments make sound when strings vibrate. Woodwind instruments use air and reeds or openings. Brass instruments use buzzing lips and air. Percussion instruments make sound when they are hit, shaken, or scraped.', vocabulary: [{term:'instrument family',definition:'A group of instruments that make sound in a similar way.'},{term:'strings',definition:'Instruments that use vibrating strings.'},{term:'brass',definition:'Instruments played by buzzing lips into a mouthpiece.'},{term:'percussion',definition:'Instruments that are hit, shaken, or scraped.'},{term:'woodwind',definition:'Instruments that use air to make sound.'}], examples: [{title:'How to Listen', content:'If you hear a steady boom, it might be percussion. If you hear a bright, bold sound made with air, it might be brass.', solution:'Think about how the sound is made, not just whether it is loud or soft.'}], guided_practice: [{question:'Tap a steady beat four times. Then clap a short-short-long rhythm.', hint:'Beat stays steady. Rhythm can change.'}], activity_data: visualActivity([{title:'Strings', text:'Violin, cello, guitar, and harp use vibrating strings.', icon:'🎻', sound:'string'},{title:'Brass', text:'Trumpet, trombone, and tuba use buzzing lips and air.', icon:'🎺', sound:'brass'},{title:'Percussion', text:'Drums, tambourine, and cymbals are hit, shaken, or scraped.', icon:'🥁', sound:'percussion'},{title:'Woodwinds', text:'Flute, clarinet, and saxophone use air to make sound.', icon:'🎷', sound:'woodwind'}], {sound_cards:true, rhythm_steps:['Count 1-2-3-4 out loud.','Clap on each count.','Now clap: short, short, long, rest.'], project_prompt:'Choose an instrument family. Explain how that family makes sound.'}) };
    assignments = [
      { title: 'Instrument Families Listening Activity', description: 'Learn instrument families and answer listening questions.', instructions: 'Study the sound cards. Play the sample sounds if available, then answer each question.', assignment_type: 'activity', weight_category: 'homework', point_value: 100, questions: [mc(1, 'Which instrument family makes sound when strings vibrate?', 'Strings', ['Brass','Percussion','Woodwinds']), mc(2, 'Which family includes drums and cymbals?', 'Percussion', ['Strings','Brass','Woodwinds']), wr(3, 'Choose one instrument family and explain how it makes sound.')] },
      { title: 'Instrument Families Sound Quiz', description: 'Show what you learned about instrument families and how they make sound.', instructions: 'Use what you learned from the listening cards and video to answer the quiz questions.', assignment_type: 'quiz', weight_category: 'quiz', point_value: 100, questions: [mc(1, 'A trumpet makes sound when a player buzzes their lips and blows air. Which family is it in?', 'Brass', ['Strings','Percussion','Woodwinds']), mc(2, 'A flute uses air to make sound. Which family does it belong to?', 'Woodwinds', ['Brass','Percussion','Strings']), mc(3, 'Which instrument family includes instruments that are hit, shaken, or scraped?', 'Percussion', ['Strings','Woodwinds','Brass']), wr(4, 'Pick one instrument family and describe one clue that can help you identify it by sound.')] }
    ];
  } else if (text.includes('physical') || text.includes('pe')) {
    unitTitle = 'Fitness at Home';
    unitDescription = 'Safe homeschool movement, backyard games, warm-ups, and body awareness.';
    lessonData = { title: 'Backyard Fitness Circuit', description: 'Complete a safe movement circuit at home or outside with parent permission.', lesson_type: 'movement_activity', interactive_type: 'fitness_circuit', skill_name: 'Fitness and Endurance', video_title: 'Kids Workout and Warm Up', video_source: 'Prime Coaching Sport', video_url: 'https://www.youtube.com/watch?v=ZPLHTRCmKNU', video_verified: true, learning_objectives: 'Warm up safely. Complete a simple fitness circuit. Explain how movement helps the heart, lungs, and muscles.', instructional_content: 'Physical education helps your body grow stronger and healthier. A warm-up gets your heart, lungs, muscles, and joints ready to move. A circuit is a set of exercises completed one after another. Today’s circuit can be done in a living room, hallway, or backyard with parent permission.', safety_notes: 'Clear the space before moving. Wear safe shoes. Drink water. Stop if something hurts. Ask a parent before outdoor activity.', examples: [{title:'Circuit Example', content:'30 seconds marching in place, 10 jumping jacks, 20-second wall sit, 10 arm circles, then a slow walk to cool down.', solution:'A circuit uses different movements to build strength, balance, and endurance.'}], guided_practice: [{question:'Before starting, name one thing you should do to make your exercise space safe.', hint:'Think about shoes, furniture, water, or parent permission.'}], activity_data: visualActivity([{title:'Warm Up', text:'March in place and roll your shoulders to get ready.', icon:'🚶'},{title:'Cardio', text:'Jumping jacks or fast steps help your heart and lungs work harder.', icon:'❤️'},{title:'Strength', text:'Wall sits and planks help muscles work against resistance.', icon:'💪'},{title:'Cool Down', text:'Slow walking and stretching help your body recover.', icon:'🧘'}], {activity_steps:['Warm up for 2 minutes.','Do 10 jumping jacks or 20 fast steps.','Hold a wall sit for 20 seconds.','Do 10 arm circles forward and 10 backward.','Cool down with slow walking and gentle stretching.'], parent_check:true}) };
    assignments = [
      { title: 'Complete a Backyard Fitness Circuit', description: 'Do a safe homeschool fitness circuit and reflect on how it felt.', instructions: 'Read the safety notes, complete the movement activity, then answer the reflection question.', assignment_type: 'activity', weight_category: 'homework', point_value: 100, questions: [wr(1, 'Which movement did you complete, and how did it help your body? Write 2 to 3 sentences.')] },
      { title: 'Fitness and Safety Check', description: 'Check your understanding of warm-ups, safety, and how movement helps your body.', instructions: 'Answer these questions after the PE lesson and movement activity.', assignment_type: 'quiz', weight_category: 'quiz', point_value: 100, questions: [mc(1, 'Why should you warm up before exercise?', 'To get your muscles, heart, and lungs ready to move.', ['To make your body more tired before starting.', 'To skip the exercise.', 'To make shoes unnecessary.']), mc(2, 'Which is a safe choice before doing PE at home?', 'Clear space, wear safe shoes, and ask a parent before outdoor activity.', ['Run through a crowded room.', 'Exercise if something hurts badly.', 'Ignore water and safety.']), wr(3, 'Explain how one movement from the circuit can help your heart, lungs, or muscles.')] }
    ];
  } else if (text.includes('health') || text.includes('nutrition')) {
    unitTitle = 'Food Groups and Healthy Choices';
    unitDescription = 'Learn how food groups, water, sleep, and movement support the body.';
    lessonData = { title: 'Build a Balanced Plate', description: 'Learn food groups and how balanced meals support energy and health.', lesson_type: 'health_lesson', interactive_type: 'plate_builder', skill_name: 'Nutrition Basics', video_title: 'Food Groups and MyPlate', video_source: 'Educational YouTube Video', video_url: 'https://www.youtube.com/watch?v=L7QOUiQCb5E', video_verified: true, learning_objectives: 'Name the five MyPlate food groups. Explain how different foods help the body. Build a balanced plate idea.', instructional_content: 'A balanced meal includes different food groups that help your body in different ways. Fruits and vegetables give vitamins, minerals, and fiber. Grains give energy, especially whole grains. Protein foods help build and repair the body. Dairy or fortified alternatives can support bones with calcium and vitamin D. Water helps your body stay hydrated. Healthy choices are about learning how different foods help your body and building habits that support energy, growth, movement, and focus.', vocabulary: [{term:'nutrient',definition:'A substance in food that helps the body work and grow.'},{term:'protein',definition:'A nutrient that helps build and repair the body.'},{term:'fiber',definition:'A plant nutrient that helps digestion.'},{term:'hydration',definition:'Having enough water in the body.'},{term:'balanced meal',definition:'A meal with different food groups that work together.'}], examples: [{title:'Balanced Plate Example', content:'A plate with chicken, brown rice, carrots, strawberries, and milk includes protein, grains, vegetables, fruit, and dairy.', solution:'Each food group supports the body in a different way.'}], guided_practice: [{question:'Name one fruit or vegetable you like and one way it helps your body.', hint:'Fruits and vegetables give vitamins, minerals, and fiber.'}], activity_data: visualActivity([{title:'Fruits', text:'Often give vitamins, minerals, water, and fiber.', icon:'🍎'},{title:'Vegetables', text:'Help the body with vitamins, minerals, and fiber.', icon:'🥦'},{title:'Grains', text:'Give energy. Whole grains usually give more fiber.', icon:'🍞'},{title:'Protein', text:'Helps build and repair muscles and body tissues.', icon:'🥚'},{title:'Dairy', text:'Milk, yogurt, and fortified alternatives can support bones.', icon:'🥛'}], {plate_builder:true, resource_note:'USDA MyPlate is a public nutrition education resource used for general food group learning.'}) };
    assignments = [
      { title: 'Build a Balanced Plate Activity', description: 'Use food groups to design a balanced meal idea.', instructions: 'Study the food group cards, build a balanced plate idea, then answer the questions.', assignment_type: 'activity', weight_category: 'homework', point_value: 100, questions: [mc(1, 'Which food group helps build and repair the body?', 'Protein foods', ['Fruit','Vegetables','Water']), mc(2, 'Which choice best describes a balanced meal?', 'A meal with several food groups that help the body in different ways.', ['A meal with only candy.', 'A meal with only one food every day.', 'A meal with no water or drinks.']), wr(3, 'Create one balanced plate idea and explain what each food group does for the body.')] },
      { title: 'Food Groups and Healthy Choices Quiz', description: 'Show what you learned about food groups, hydration, and balanced choices.', instructions: 'Use the food group cards and lesson to answer these questions.', assignment_type: 'quiz', weight_category: 'quiz', point_value: 100, questions: [mc(1, 'Which food group often gives the body vitamins, minerals, and fiber?', 'Fruits and vegetables', ['Only candy', 'Only water', 'Only empty plates']), mc(2, 'Why does the body need water?', 'Water helps the body stay hydrated and work well.', ['Water replaces all food groups.', 'Water makes sleep unnecessary.', 'Water is only for plants.']), mc(3, 'Which plate is the best balanced choice?', 'A plate with protein, grains, vegetables, fruit, and dairy or a fortified alternative.', ['A plate with only cookies.', 'A plate with only one food every day.', 'A plate with no drinks or food groups.']), wr(4, 'Describe one balanced meal idea and explain how two food groups help the body.')] }
    ];
  } else {
    return false;
  }

  const unit = await ensureUnit(courseRow.id, 1, unitTitle, unitDescription);
  const lesson = await ensureLesson(unit.id, 1, lessonData);
  // Repair older databases where the first activity was accidentally seeded as 100 points.
  assignments = assignments.map(a => {
    const isQuiz = String(a.assignment_type || '').toLowerCase() === 'quiz' || String(a.weight_category || '').toLowerCase() === 'quiz';
    return { ...a, point_value: isQuiz ? 100 : 0 };
  });
  for (const a of assignments) await safeEnsureAssignment(lesson.id, a, `repair-${courseRow.subject || courseRow.name}-${a.title}`);
  await ensureExpandedGradeFiveElectivePath(courseRow);
  await pool.query(`UPDATE assignments a SET point_value = 0
    FROM lessons l JOIN units u ON l.unit_id = u.id
    WHERE a.lesson_id = l.id AND u.course_id = $1
      AND NOT (LOWER(COALESCE(a.assignment_type,'')) = 'quiz' OR LOWER(COALESCE(a.weight_category,'')) = 'quiz' OR LOWER(COALESCE(a.assignment_type,'')) LIKE '%test%' OR LOWER(COALESCE(a.weight_category,'')) LIKE '%test%')`, [courseRow.id]).catch(()=>{});
  await pool.query(`UPDATE assignments a SET point_value = 100
    FROM lessons l JOIN units u ON l.unit_id = u.id
    WHERE a.lesson_id = l.id AND u.course_id = $1
      AND (LOWER(COALESCE(a.assignment_type,'')) = 'quiz' OR LOWER(COALESCE(a.weight_category,'')) = 'quiz')`, [courseRow.id]).catch(()=>{});
  return true;
}



async function ensureExpandedGradeFiveElectivePath(courseRow) {
  if (!courseRow || parseInt(courseRow.grade_level || 5) !== 5) return false;
  const text = String([courseRow.subject, courseRow.name].filter(Boolean).join(' ')).toLowerCase();
  const lessonSetsByKind = {
    art: [
      ['Elements of Art and Creative Projects', 'Line, shape, color, texture, and creative projects.', [
        ['Lines, Shapes, and Patterns', 'Artists use lines, shapes, and patterns to organize artwork and guide the viewer’s eye.', 'Create a Line and Shape Artwork', 'Elements of Art Quick Check'],
        ['Color Wheel and Mood', 'Artists use color families, contrast, and mood to communicate feeling.', 'Create a Color Mood Sketch', 'Color and Mood Quiz']
      ]],
      ['Drawing and Observation', 'Practice looking closely and drawing what you see.', [
        ['Drawing from Observation', 'Observation drawing means slowing down, noticing details, and drawing what is actually in front of you.', 'Observation Drawing Activity', 'Observation Drawing Quiz'],
        ['Texture and Pattern in Art', 'Texture shows how something might feel, and pattern repeats elements to create rhythm.', 'Texture Rubbing Project', 'Texture and Pattern Quiz']
      ]],
      ['Art Styles and Reflection', 'Learn styles and reflect on creative choices.', [
        ['Realistic and Abstract Art', 'Realistic art looks close to real life, while abstract art uses lines, colors, and shapes in a different way.', 'Abstract Shape Artwork', 'Art Styles Quiz'],
        ['Portfolio Reflection', 'Artists reflect on their work to explain choices and plan improvements.', 'Art Portfolio Reflection', 'Art Reflection Quiz']
      ]]
    ],
    music: [
      ['Beat, Rhythm, and Instrument Families', 'Explore beat, rhythm, tempo, and sound.', [
        ['Instrument Families and Sounds', 'Instruments are grouped by how they make sound: strings, woodwinds, brass, and percussion.', 'Instrument Families Listening Activity', 'Instrument Families Sound Quiz'],
        ['Beat vs. Rhythm', 'The beat stays steady, while rhythm is the pattern of long and short sounds.', 'Clap a Rhythm Pattern', 'Beat and Rhythm Quiz']
      ]],
      ['Tempo, Dynamics, and Melody', 'Listen for speed, volume, and musical direction.', [
        ['Tempo and Dynamics', 'Tempo tells how fast or slow music is. Dynamics tell how loud or soft it is.', 'Tempo and Dynamics Listening Activity', 'Tempo and Dynamics Quiz'],
        ['Melody and Pitch', 'A melody is a tune. Pitch tells how high or low a sound is.', 'High and Low Sound Activity', 'Melody and Pitch Quiz']
      ]],
      ['Music Styles and Creating Patterns', 'Compare styles and create rhythm patterns.', [
        ['Music Styles', 'Different music styles can use different instruments, rhythms, and moods.', 'Compare Two Music Styles', 'Music Styles Quiz'],
        ['Create a Rhythm Pattern', 'Musicians can write and perform patterns using claps, taps, or simple instruments.', 'Create Your Rhythm Pattern', 'Rhythm Creation Quiz']
      ]]
    ],
    pe: [
      ['Fitness at Home', 'Safe homeschool movement, warm-ups, and fitness circuits.', [
        ['Backyard Fitness Circuit', 'A circuit is a set of exercises done one after another to build strength and endurance.', 'Complete a Backyard Fitness Circuit', 'Fitness and Safety Check'],
        ['Warm-Up and Cool Down', 'Warming up prepares the body. Cooling down helps the body recover.', 'Warm-Up and Cool Down Activity', 'Warm-Up Quiz']
      ]],
      ['Movement Skills', 'Balance, coordination, throwing, catching, and agility.', [
        ['Balance and Coordination', 'Balance helps you control your body. Coordination helps body parts work together.', 'Balance Challenge Activity', 'Balance and Coordination Quiz'],
        ['Backyard Agility Games', 'Agility means moving quickly and safely while changing direction.', 'Backyard Agility Course', 'Agility Safety Quiz']
      ]],
      ['Body, Health, and Fitness Habits', 'Learn how movement supports the heart, lungs, and muscles.', [
        ['Heart, Lungs, and Muscles', 'Exercise makes your heart and lungs work harder and helps muscles grow stronger.', 'Heart Rate Observation Activity', 'Heart and Lungs Quiz'],
        ['Healthy Fitness Habits', 'Safe fitness includes water, rest, stretching, and listening to your body.', 'Weekly Fitness Reflection', 'Fitness Habits Quiz']
      ]]
    ],
    health: [
      ['Food Groups and Healthy Choices', 'Learn food groups, hydration, and balanced meals.', [
        ['Build a Balanced Plate', 'Food groups help your body in different ways. Balanced meals include several helpful food groups.', 'Build a Balanced Plate Activity', 'Food Groups and Healthy Choices Quiz'],
        ['Hydration and Energy', 'Water helps the body stay hydrated and helps the brain and muscles work well.', 'Hydration Habit Activity', 'Hydration and Energy Quiz']
      ]],
      ['Body Systems and Wellness', 'Learn how the body works and what habits support it.', [
        ['Muscles, Bones, Heart, and Lungs', 'Bones support the body, muscles help it move, the heart pumps blood, and lungs help you breathe.', 'Body Systems Match Activity', 'Body Systems Quiz'],
        ['Sleep, Rest, and Focus', 'Sleep and rest help the brain learn, the body grow, and emotions reset.', 'Sleep Routine Reflection', 'Sleep and Focus Quiz']
      ]],
      ['Safety, Hygiene, and Choices', 'Practice healthy and safe routines.', [
        ['Hygiene and Germs', 'Handwashing, brushing teeth, and clean routines help protect health.', 'Healthy Hygiene Routine Activity', 'Hygiene Quiz'],
        ['Safe Choices at Home and Outside', 'Safe choices help prevent injuries and protect others.', 'Safety Plan Activity', 'Safe Choices Quiz']
      ]]
    ]
  };
  let kind = text.includes('art') ? 'art' : text.includes('music') ? 'music' : (text.includes('physical') || text.includes('pe')) ? 'pe' : (text.includes('health') || text.includes('nutrition')) ? 'health' : null;
  if (!kind) return false;
  const iconByKind = { art:'🎨', music:'🎵', pe:'🏃', health:'🥗' };
  const fallbackVideoByKind = {
    art: { title:'The Elements of Art', source:'Scratch Garden', url:'https://www.youtube.com/watch?v=IkvH7HaqCYk', notes:'Fallback art fundamentals video.' },
    music: { title:'The Instruments of the Orchestra Song', source:'Hopscotch', url:'https://www.youtube.com/watch?v=JVY4tj-JTBc', notes:'Fallback music fundamentals video.' },
    pe: { title:'Kids Workout and Warm Up', source:'Prime Coaching Sport', url:'https://www.youtube.com/watch?v=ZPLHTRCmKNU', notes:'Fallback PE movement video.' },
    health: { title:'Food Groups and MyPlate', source:'Educational YouTube Video', url:'https://www.youtube.com/watch?v=L7QOUiQCb5E', notes:'Fallback health and nutrition video.' }
  };
  const units = lessonSetsByKind[kind];
  for (let uIdx=0; uIdx<units.length; uIdx++) {
    const [unitTitle, unitDesc, lessons] = units[uIdx];
    const unit = await ensureUnit(courseRow.id, uIdx + 1, unitTitle, unitDesc);
    for (let lIdx=0; lIdx<lessons.length; lIdx++) {
      const [lessonTitle, content, activityTitle, quizTitle] = lessons[lIdx];
      const v = getGradeFiveElectiveTopicVideo({
        subject: courseRow.subject || courseRow.name,
        grade_level: courseRow.grade_level,
        unit_title: unitTitle,
        lesson_title: lessonTitle,
        title: activityTitle,
        description: content,
        instructions: quizTitle
      }) || fallbackVideoByKind[kind];
      const lesson = await ensureLesson(unit.id, lIdx + 1, {
        title: lessonTitle,
        description: content,
        lesson_type: kind + '_guided_lesson',
        interactive_type: kind + '_activity',
        skill_name: lessonTitle,
        video_title: v.title,
        video_source: v.source,
        video_url: v.url,
        video_verified: true,
        learning_objectives: `Understand ${lessonTitle}. Complete a guided activity. Answer questions that match this lesson.`,
        instructional_content: content + '\n\nThis lesson is designed for Grade 5 homeschool learning. Study the examples, use the visual cards, complete the activity, and then answer the quiz questions in your own words.',
        vocabulary: gradeFiveElectiveVocabulary(kind, lessonTitle, activityTitle),
        examples: gradeFiveElectiveExamples(kind, lessonTitle, activityTitle),
        guided_practice: gradeFiveElectivePractice(kind, lessonTitle, activityTitle),
        materials_needed: kind === 'art' ? 'Paper, pencil, crayons, colored pencils, or markers.' : (kind === 'pe' ? 'Open safe space, water, safe shoes, and parent permission for outdoor movement.' : ''),
        safety_notes: kind === 'pe' ? 'Clear space before moving. Drink water. Stop if something hurts. Ask a parent before outdoor activity.' : null,
        activity_data: visualActivity([
          { title: 'Watch', text: 'Use the video or visual to understand the topic.', icon: iconByKind[kind] },
          { title: 'Try', text: 'Complete the activity or practice step.', icon: '✅' },
          { title: 'Reflect', text: 'Explain what you learned in your own words.', icon: '💬' }
        ], { supplies: kind === 'art' ? ['Paper','Pencil','Crayons or markers'] : [], activity_steps: ['Review the lesson screen.','Watch the video if it is available.','Complete the activity.','Answer the reflection or quiz.'], project_prompt: activityTitle })
      });
      await safeEnsureAssignment(lesson.id, {
        title: activityTitle,
        description: 'Guided activity for ' + lessonTitle + '.',
        instructions: 'Use the lesson, visual cards, and video to complete this activity.',
        assignment_type: 'activity',
        weight_category: 'homework',
        point_value: 0,
        questions: gradeFiveElectiveQuestions(kind, lessonTitle, activityTitle, false)
      }, `expanded-${kind}-${activityTitle}`);
      await safeEnsureAssignment(lesson.id, {
        title: quizTitle,
        description: 'Check your understanding of ' + lessonTitle + '.',
        instructions: 'Answer these questions using what you learned in this lesson.',
        assignment_type: 'quiz',
        weight_category: 'quiz',
        point_value: 100,
        questions: gradeFiveElectiveQuestions(kind, lessonTitle, quizTitle, true)
      }, `expanded-${kind}-${quizTitle}`);
    }
  }
  await pool.query(`UPDATE assignments a SET point_value = 0
    FROM lessons l JOIN units u ON l.unit_id = u.id
    WHERE a.lesson_id = l.id AND u.course_id = $1
      AND NOT (LOWER(COALESCE(a.assignment_type,'')) = 'quiz' OR LOWER(COALESCE(a.weight_category,'')) = 'quiz' OR LOWER(COALESCE(a.assignment_type,'')) LIKE '%test%' OR LOWER(COALESCE(a.weight_category,'')) LIKE '%test%')`, [courseRow.id]).catch(()=>{});
  await pool.query(`UPDATE assignments a SET point_value = 100
    FROM lessons l JOIN units u ON l.unit_id = u.id
    WHERE a.lesson_id = l.id AND u.course_id = $1
      AND (LOWER(COALESCE(a.assignment_type,'')) = 'quiz' OR LOWER(COALESCE(a.weight_category,'')) = 'quiz')`, [courseRow.id]).catch(()=>{});
  return true;
}
async function ensureVisibleGradeFiveElectivesForAllExistingCourses() {
  const rows = await pool.query(`SELECT * FROM courses WHERE grade_level = 5 AND is_active = true AND (LOWER(subject) IN ('art','music','physical education','health and nutrition') OR LOWER(name) LIKE '%art fundamentals%' OR LOWER(name) LIKE '%music fundamentals%' OR LOWER(name) LIKE '%physical education%' OR LOWER(name) LIKE '%health and nutrition%')`);
  for (const course of rows.rows) await ensureVisibleGradeFiveElectiveContentForCourse(course);
}


async function ensureGradeFiveSocialStudiesEnrollment() {
  try {
    const students = await pool.query('SELECT id FROM students WHERE grade_level = 5 AND status = $1', ['active']);
    const courses = await pool.query("SELECT id FROM courses WHERE grade_level = 5 AND is_active = true AND (LOWER(subject) LIKE '%social%' OR LOWER(name) LIKE '%social studies%')");
    for (const st of students.rows) for (const c of courses.rows) await safeEnrollStudentInCourse(st.id, c.id);
  } catch (e) { console.error('ensureGradeFiveSocialStudiesEnrollment error:', e.message); }
}




async function repairGradeFiveVocabGrammarArtVideoCoverage() {
  try {
    const rows = await pool.query(`
      SELECT l.*, u.title AS unit_title, c.name AS course_name, c.subject, c.grade_level
      FROM lessons l
      JOIN units u ON l.unit_id = u.id
      JOIN courses c ON u.course_id = c.id
      WHERE c.grade_level = 5
        AND c.is_active = true
        AND (
          LOWER(COALESCE(c.subject,'')) LIKE '%language%' OR
          LOWER(COALESCE(c.name,'')) LIKE '%language%' OR
          LOWER(COALESCE(c.subject,'')) LIKE '%art%' OR
          LOWER(COALESCE(c.name,'')) LIKE '%art%'
        )
        AND (
          LOWER(COALESCE(u.title,'')) LIKE '%vocabulary%' OR
          LOWER(COALESCE(u.title,'')) LIKE '%grammar%' OR
          LOWER(COALESCE(c.name,'')) LIKE '%art%' OR
          LOWER(COALESCE(c.subject,'')) LIKE '%art%' OR
          LOWER(COALESCE(l.title,'')) LIKE '%greek%' OR
          LOWER(COALESCE(l.title,'')) LIKE '%latin%' OR
          LOWER(COALESCE(l.title,'')) LIKE '%context clues%' OR
          LOWER(COALESCE(l.title,'')) LIKE '%academic vocabulary%' OR
          LOWER(COALESCE(l.title,'')) LIKE '%abstract%' OR
          LOWER(COALESCE(l.title,'')) LIKE '%portfolio%' OR
          LOWER(COALESCE(l.title,'')) LIKE '%reflection%'
        )
    `);
    for (const lesson of rows.rows) {
      const videoRow = applyTopicVideo({
        ...lesson,
        lesson_title: lesson.title,
        course_name: lesson.course_name,
        subject: lesson.subject,
        unit_title: lesson.unit_title,
        grade_level: lesson.grade_level
      });
      await updateFlexible('lessons', lesson.id, {
        video_title: videoRow.video_title || null,
        video_source: videoRow.video_source || null,
        video_url: videoRow.video_url || null,
        video_verified: !!(videoRow.video_url && videoRow.video_verified)
      });
    }
  } catch (e) {
    console.error('repairGradeFiveVocabGrammarArtVideoCoverage error:', e.message);
  }
}

async function repairGradeFiveSpellingVideoCoverage() {
  try {
    const rows = await pool.query(`
      SELECT l.*, u.title AS unit_title, c.name AS course_name, c.subject, c.grade_level
      FROM lessons l
      JOIN units u ON l.unit_id = u.id
      JOIN courses c ON u.course_id = c.id
      WHERE c.grade_level = 5
        AND c.is_active = true
        AND (
          LOWER(COALESCE(c.subject,'')) LIKE '%spelling%' OR
          LOWER(COALESCE(c.name,'')) LIKE '%spelling%' OR
          LOWER(COALESCE(u.title,'')) LIKE '%spelling%' OR
          LOWER(COALESCE(l.title,'')) LIKE '%spelling%'
        )
    `);
    for (const lesson of rows.rows) {
      const videoRow = applyTopicVideo({
        ...lesson,
        lesson_title: lesson.title,
        course_name: lesson.course_name,
        subject: lesson.subject,
        unit_title: lesson.unit_title,
        grade_level: lesson.grade_level
      });
      if (videoRow.video_url && videoRow.video_verified) {
        await updateFlexible('lessons', lesson.id, {
          video_title: videoRow.video_title,
          video_source: videoRow.video_source,
          video_url: videoRow.video_url,
          video_verified: true
        });
      }
    }
  } catch (e) {
    console.error('repairGradeFiveSpellingVideoCoverage error:', e.message);
  }
}

async function repairGradeFiveCoreVideoCoverage() {
  const videos = [
    { course:'social', topic:'13 colonies', title:'The 13 Original Colonies for Kids', source:'Learn Bright', url:'https://www.youtube.com/watch?v=UUmi3iNMawo' },
    { course:'social', topic:'colonial life', title:'Colonial Times for Kids | Learn about colonial America', source:'Learn Bright', url:'https://www.youtube.com/watch?v=4f3LsMKzxdg' },
    { course:'social', topic:'road to revolution', title:'The American Revolutionary War for Kids', source:'FreeSchool', url:'https://www.youtube.com/watch?v=BWtW0gmh3kg' },
    { course:'science', topic:'solar system', title:'The Solar System Planets | Educational Video for Kids', source:'Educational YouTube Video', url:'https://www.youtube.com/watch?v=ASQkz4XaphU' },
    { course:'science', topic:'properties of matter', title:"What's My Property?", source:'Crash Course Kids', url:'https://www.youtube.com/watch?v=nlSemv2fLN8' },
    { course:'science', topic:'chemical', title:'Physical and Chemical Changes for Kids', source:'Homeschool Pop', url:'https://www.youtube.com/watch?v=BOr76Zx48QM' },
    { course:'math', topic:'decimal place value', title:'Decimal Place Value', source:'Math Antics', url:'https://www.youtube.com/watch?v=KG6ILNOiMgM' },
    { course:'math', topic:'adding and subtracting decimals', title:'Decimal Arithmetic', source:'Math Antics', url:'https://www.youtube.com/watch?v=kwh4SD1ToFc' },
    { course:'math', topic:'multiplying fractions', title:'Math Antics - Multiplying Fractions', source:'Math Antics', url:'https://www.youtube.com/watch?v=qmfXyR7Z6Lk' },
    { course:'math', topic:'dividing fractions', title:'Math Antics - Dividing Fractions', source:'Math Antics', url:'https://www.youtube.com/watch?v=4lkq3DgvmJo' },
    { course:'math', topic:'mixed numbers', title:'Math Antics - Mixed Numbers', source:'Math Antics', url:'https://www.youtube.com/watch?v=qk2oP6FZ6HA' },
    { course:'math', topic:'volume', title:'Math Antics - Volume', source:'Math Antics', url:'https://www.youtube.com/watch?v=qJwecTgce6c' },
    { course:'math', topic:'metric', title:'Math Antics - Intro to the Metric System', source:'Math Antics', url:'https://www.youtube.com/watch?v=BTbTQgq6Mt0' },
    { course:'math', topic:'customary measurement', title:'Customary Measurement Conversions', source:'Math with Mr. J', url:'https://www.youtube.com/watch?v=-lNebjbHwtc' },
    { course:'math', topic:'converting units', title:'5th Grade Math Unit Conversions', source:'Aaron Daffern', url:'https://www.youtube.com/watch?v=fdyFeEc8Wss' },
    { course:'math', topic:'coordinate plane', title:'Algebra Basics: Graphing On The Coordinate Plane', source:'Math Antics', url:'https://www.youtube.com/watch?v=9Uc62CuQjc4' },
    { course:'language', topic:'figurative language', title:'Figurative Language for Kids | Similes, Metaphors & Idioms', source:'Homeschool Pop', url:'https://www.youtube.com/watch?v=35PzoZWJpIM' },
    { course:'language', topic:'point of view', title:'Point of View for Kids: First and Third Person', source:'Educational YouTube Video', url:'https://www.youtube.com/watch?v=R9-ONtB1GBY' },
    { course:'language', topic:'persuasive writing', title:'Persuasive Writing - Using Techniques To Convince Others', source:'Miacademy & MiaPrep', url:'https://www.youtube.com/watch?v=evIlRRK_rOE' },
    { course:'language', topic:'narrative', title:'Writing a Personal Narrative - Brainstorming a Story for Kids', source:'Teaching Without Frills', url:'https://www.youtube.com/watch?v=AxInWagSaA0' },
    { course:'language', topic:'research paper', title:'Intro to Research Writing', source:'Educational YouTube Video', url:'https://www.youtube.com/watch?v=1N0WB4gf3co' },
    { course:'language', topic:'grammar', title:'Parts of Speech | English Grammar Grade 5', source:'Periwinkle', url:'https://www.youtube.com/watch?v=40Gv90LigKY' },
    { course:'language', topic:'context clues', title:'Context Clues - How to Figure Out Unfamiliar Words in a Story', source:'Miacademy & MiaPrep', url:'https://www.youtube.com/watch?v=wDJZOBwtGMY' },
    { course:'language', topic:'greek and latin roots', title:'Latin and Greek Root Words - Language Skills for Kids', source:'Miacademy & MiaPrep', url:'https://www.youtube.com/watch?v=jWyX8vl6kMs' },
    { course:'language', topic:'academic vocabulary', title:'5th Grade - Reading - Academic Words - Topic Overview', source:'Education Galaxy', url:'https://www.youtube.com/watch?v=fR5DGIpZ5H4' },
    { course:'language', topic:'prepositions', title:'Prepositions', source:'Miacademy & MiaPrep', url:'https://www.youtube.com/watch?v=QO-1vz7Tp-4' },
    { course:'spelling', topic:'spelling', title:'Look-Say-Cover-Write-Check Strategy', source:'Educational YouTube Video', url:'https://www.youtube.com/watch?v=P8cYKLu7IM8' }
  ];
  try {
    for (const v of videos) {
      const topic = `%${v.topic.toLowerCase()}%`;
      const course = `%${v.course.toLowerCase()}%`;
      const rows = await pool.query(`
        SELECT l.*
        FROM lessons l
        JOIN units u ON l.unit_id = u.id
        JOIN courses c ON u.course_id = c.id
        WHERE c.grade_level = 5
          AND c.is_active = true
          AND (LOWER(COALESCE(c.subject,'')) LIKE $2 OR LOWER(COALESCE(c.name,'')) LIKE $2)
          AND (
            LOWER(COALESCE(l.title,'')) LIKE $1 OR
            LOWER(COALESCE(u.title,'')) LIKE $1 OR
            LOWER(COALESCE(l.skill_name,'')) LIKE $1 OR
            LOWER(COALESCE(l.description,'')) LIKE $1
          )
      `, [topic, course]);
      for (const row of rows.rows) {
        await updateFlexible('lessons', row.id, {
          video_title: v.title,
          video_source: v.source,
          video_url: v.url,
          video_verified: true
        });
      }
    }
  } catch (e) {
    console.error('repairGradeFiveCoreVideoCoverage error:', e.message);
  }
}

async function repairGradeFiveElectiveVideoCoverage() {
  try {
    const rows = await pool.query(`
      SELECT l.*, u.title AS unit_title, c.subject, c.name AS course_name, c.grade_level
      FROM lessons l
      JOIN units u ON l.unit_id = u.id
      JOIN courses c ON u.course_id = c.id
      WHERE c.grade_level = 5
        AND c.is_active = true
        AND (
          LOWER(COALESCE(c.subject,'')) IN ('art','music','physical education','health and nutrition') OR
          LOWER(COALESCE(c.name,'')) LIKE '%art fundamentals%' OR
          LOWER(COALESCE(c.name,'')) LIKE '%music fundamentals%' OR
          LOWER(COALESCE(c.name,'')) LIKE '%physical education%' OR
          LOWER(COALESCE(c.name,'')) LIKE '%health and nutrition%'
        )
    `);
    for (const lesson of rows.rows) {
      const v = getGradeFiveElectiveTopicVideo(lesson);
      if (!v) continue;
      await updateFlexible('lessons', lesson.id, {
        video_title: v.title,
        video_source: v.source,
        video_url: v.url,
        video_verified: true
      });
    }
  } catch (e) {
    console.error('repairGradeFiveElectiveVideoCoverage error:', e.message);
  }
}


async function repairGradeFiveElectiveQuestionCoverage() {
  try {
    const rows = await pool.query(`
      SELECT a.*, l.title AS lesson_title, u.title AS unit_title, c.subject, c.name AS course_name, c.grade_level
      FROM assignments a
      JOIN lessons l ON a.lesson_id = l.id
      JOIN units u ON l.unit_id = u.id
      JOIN courses c ON u.course_id = c.id
      WHERE c.grade_level = 5
        AND c.is_active = true
        AND (
          LOWER(COALESCE(c.subject,'')) IN ('art','music','physical education','health and nutrition') OR
          LOWER(COALESCE(c.name,'')) LIKE '%art fundamentals%' OR
          LOWER(COALESCE(c.name,'')) LIKE '%music fundamentals%' OR
          LOWER(COALESCE(c.name,'')) LIKE '%physical education%' OR
          LOWER(COALESCE(c.name,'')) LIKE '%health and nutrition%'
        )
    `);
    for (const row of rows.rows) {
      const kind = gradeFiveElectiveKind(row);
      if (!kind) continue;
      const questions = gradeFiveElectiveQuestions(kind, row.lesson_title || row.unit_title, row.title || row.assignment_title, isQuizOrTestAssignment(row));
      if (questions.length === 0) continue;
      await updateFlexible('assignments', row.id, { questions });
    }
  } catch (e) {
    console.error('repairGradeFiveElectiveQuestionCoverage error:', e.message);
  }
}

async function repairGradeFiveElectiveLessonContentCoverage() {
  try {
    const rows = await pool.query(`
      SELECT l.*, u.title AS unit_title, c.subject, c.name AS course_name, c.grade_level
      FROM lessons l
      JOIN units u ON l.unit_id = u.id
      JOIN courses c ON u.course_id = c.id
      WHERE c.grade_level = 5
        AND c.is_active = true
        AND (
          LOWER(COALESCE(c.subject,'')) IN ('art','music','physical education','health and nutrition') OR
          LOWER(COALESCE(c.name,'')) LIKE '%art fundamentals%' OR
          LOWER(COALESCE(c.name,'')) LIKE '%music fundamentals%' OR
          LOWER(COALESCE(c.name,'')) LIKE '%physical education%' OR
          LOWER(COALESCE(c.name,'')) LIKE '%health and nutrition%'
        )
    `);
    for (const row of rows.rows) {
      const kind = gradeFiveElectiveKind(row);
      if (!kind) continue;
      const title = row.title || row.lesson_title || row.skill_name || row.unit_title;
      await updateFlexible('lessons', row.id, {
        vocabulary: gradeFiveElectiveVocabulary(kind, title, title),
        examples: gradeFiveElectiveExamples(kind, title, title),
        guided_practice: gradeFiveElectivePractice(kind, title, title)
      });
    }
  } catch (e) {
    console.error('repairGradeFiveElectiveLessonContentCoverage error:', e.message);
  }
}


async function repairGradeFiveLanguageArtsContentCoverage() {
  try {
    const lessons = await pool.query(`
      SELECT l.*, u.title AS unit_title, c.subject, c.name AS course_name, c.grade_level
      FROM lessons l
      JOIN units u ON l.unit_id = u.id
      JOIN courses c ON u.course_id = c.id
      WHERE c.grade_level = 5
        AND c.is_active = true
        AND (LOWER(COALESCE(c.subject,'')) LIKE '%language%' OR LOWER(COALESCE(c.subject,'')) LIKE '%ela%' OR LOWER(COALESCE(c.name,'')) LIKE '%language arts%' OR LOWER(COALESCE(c.name,'')) LIKE '%english%')
        AND (
          LOWER(COALESCE(l.title,'')) LIKE '%persuasive writing%' OR
          LOWER(COALESCE(l.title,'')) LIKE '%narrative essays%' OR
          LOWER(COALESCE(l.title,'')) LIKE '%narrative writing%' OR
          LOWER(COALESCE(l.title,'')) LIKE '%research papers%' OR
          LOWER(COALESCE(l.title,'')) LIKE '%research paper%' OR
          LOWER(COALESCE(u.title,'')) LIKE '%persuasive writing%' OR
          LOWER(COALESCE(u.title,'')) LIKE '%narrative essays%' OR
          LOWER(COALESCE(u.title,'')) LIKE '%narrative writing%' OR
          LOWER(COALESCE(u.title,'')) LIKE '%research papers%' OR
          LOWER(COALESCE(u.title,'')) LIKE '%research paper%'
        )
    `);
    for (const lesson of lessons.rows) {
      const repaired = applyGradeFiveLanguageArtsStoryContent({ ...lesson });
      await updateFlexible('lessons', lesson.id, {
        title: repaired.lesson_title || repaired.title,
        passage_title: repaired.passage_title,
        passage_type: repaired.passage_type,
        passage_source: repaired.passage_source,
        reading_passage: repaired.reading_passage,
        instructional_content: repaired.instructional_content,
        vocabulary: repaired.vocabulary,
        examples: repaired.examples,
        guided_practice: repaired.guided_practice
      });
    }

    const assignments = await pool.query(`
      SELECT a.*, l.title AS lesson_title, l.reading_passage, l.source_text, l.passage_title, l.passage_type,
             u.title AS unit_title, c.subject, c.name AS course_name, c.grade_level
      FROM assignments a
      JOIN lessons l ON a.lesson_id = l.id
      JOIN units u ON l.unit_id = u.id
      JOIN courses c ON u.course_id = c.id
      WHERE c.grade_level = 5
        AND c.is_active = true
        AND (LOWER(COALESCE(c.subject,'')) LIKE '%language%' OR LOWER(COALESCE(c.subject,'')) LIKE '%ela%' OR LOWER(COALESCE(c.name,'')) LIKE '%language arts%' OR LOWER(COALESCE(c.name,'')) LIKE '%english%')
        AND (
          LOWER(COALESCE(a.title,'')) LIKE '%persuasive writing%' OR
          LOWER(COALESCE(a.title,'')) LIKE '%narrative essays%' OR
          LOWER(COALESCE(a.title,'')) LIKE '%narrative writing%' OR
          LOWER(COALESCE(a.title,'')) LIKE '%research papers%' OR
          LOWER(COALESCE(a.title,'')) LIKE '%research paper%' OR
          LOWER(COALESCE(l.title,'')) LIKE '%persuasive writing%' OR
          LOWER(COALESCE(l.title,'')) LIKE '%narrative essays%' OR
          LOWER(COALESCE(l.title,'')) LIKE '%narrative writing%' OR
          LOWER(COALESCE(l.title,'')) LIKE '%research papers%' OR
          LOWER(COALESCE(l.title,'')) LIKE '%research paper%'
        )
    `);
    for (const assignment of assignments.rows) {
      const row = applyGradeFiveLanguageArtsStoryContent({ ...assignment });
      const questions = gradeFiveLanguageArtsQuestions(row);
      const newTitle = row.title || assignment.title;
      const patch = { title: newTitle };
      if (questions) patch.questions = questions;
      await updateFlexible('assignments', assignment.id, patch);
    }
  } catch (e) {
    console.error('repairGradeFiveLanguageArtsContentCoverage error:', e.message);
  }
}


// Grades 3 and 4 bridge-level foundation.
// These grades use a simplified Grade 5 style: lesson/video or read-listen passage,
// guided practice, short response, and a quick check. Grade 3 is more guided;
// Grade 4 is closer to the Grade 5 baseline.
function bridgeVideo(title, source, url) { return { title, source, url }; }
function bridgeLesson(title, description, skill, content, questions, video = null, options = {}) {
  return {
    title,
    description,
    skill,
    content,
    questions,
    video_url: video?.url || null,
    video_title: video?.title || null,
    video_source: video?.source || null,
    activity_data: {
      mode: 'bridge_grade_guided_lesson',
      grade_band: '3-4',
      lesson_text: content,
      lesson_audio_text: options.audio_text || content,
      answer_style_rule: options.answer_style_rule || 'Use words, numbers, sentences, diagrams, or pictures based on the exact question type.',
      student_flow: ['Watch, read, or listen', 'Practice the skill', 'Answer the check questions', 'Write a short explanation when asked']
    }
  };
}
function bridgePlans(grade) {
  const g = parseInt(grade, 10);
  const label = `Grade ${g}`;
  const v = {
    mainIdea: bridgeVideo('Main Idea and Details for Kids', 'Teaching Without Frills / elementary ELA', 'https://www.youtube.com/watch?v=mkZo2zVKJR4'),
    inference: bridgeVideo('Making Inferences for Kids', 'Teaching Without Frills / elementary ELA', 'https://www.youtube.com/watch?v=to30AJm2epQ'),
    textEvidence: bridgeVideo('How to Find Text Evidence', 'Elementary reading lesson', 'https://www.youtube.com/watch?v=oq2y_QfR6sA'),
    multiplication: bridgeVideo('Math Antics - Multiplication', 'Math Antics', 'https://www.youtube.com/watch?v=FJ5qLWP3Fqo'),
    division: bridgeVideo('Math Antics - Basic Division', 'Math Antics', 'https://www.youtube.com/watch?v=KGMf314LUc0'),
    fractions: bridgeVideo('Math Antics - Fractions Are Parts', 'Math Antics', 'https://www.youtube.com/watch?v=CA9XLJpQp3c'),
    placeValue: bridgeVideo('Math Antics - Place Value', 'Math Antics', 'https://www.youtube.com/watch?v=T5Qf0qSSJFI'),
    perimeter: bridgeVideo('Perimeter for Kids', 'Math learning video', 'https://www.youtube.com/watch?v=AAY1bsazcgM'),
    plantLife: bridgeVideo('Plant Life Cycle for Kids', 'Learn Bright', 'https://www.youtube.com/watch?v=Gf6QM1UyN7k'),
    foodChain: bridgeVideo('Food Chains for Kids', 'Learn Bright', 'https://www.youtube.com/watch?v=7sKktv-OpSA'),
    waterCycle: bridgeVideo('The Water Cycle for Kids', 'Learn Bright', 'https://www.youtube.com/watch?v=TD3XSIE4ymo'),
    matter: bridgeVideo('What\'s Matter?', 'Crash Course Kids', 'https://www.youtube.com/watch?v=ELchwUIlWa8'),
    electricity: bridgeVideo('Electricity for Kids', 'Learn Bright', 'https://www.youtube.com/watch?v=Uf76pThNXZc'),
    maps: bridgeVideo('Maps and Directions for Kids', 'Kids Academy / geography', 'https://www.youtube.com/watch?v=Uq2sQy8Tf7g'),
    communities: bridgeVideo('Types of Communities for Kids', 'Learn Bright', 'https://www.youtube.com/watch?v=04lO0ISBT40'),
    government: bridgeVideo('Government for Kids', 'Homeschool Pop', 'https://www.youtube.com/watch?v=LRmTFjHToLk'),
    artElements: bridgeVideo('Elements of Art for Kids', 'Elementary art lesson', 'https://www.youtube.com/watch?v=QZQyV9BB50E'),
    colorTheory: bridgeVideo('Color Theory for Kids', 'Art for kids', 'https://www.youtube.com/watch?v=YeI6Wqn4I78'),
    instruments: bridgeVideo('Musical Instruments Names and Sounds for Kids', 'Kiddopedia', 'https://www.youtube.com/watch?v=a8lZQnQZg_A'),
    genres: bridgeVideo('Music Genres for Kids', 'Name That Tune', 'https://www.youtube.com/watch?v=jD79wiJ_02k'),
    nutrition: bridgeVideo('Food Groups and MyPlate', 'Kids nutrition video', 'https://www.youtube.com/watch?v=L7QOUiQCb5E'),
    safety: bridgeVideo('Internet Safety for Kids', 'Elementary safety lesson', 'https://www.youtube.com/watch?v=yrln8nyVBLU'),
    fitness: bridgeVideo('Physical Exercise for Children', 'Smile and Learn', 'https://www.youtube.com/watch?v=lSuekPtI_Kc')
  };

  const reading3 = [
    bridgeLesson('Main Idea and Details', 'Find what a passage is mostly about and choose details that support it.', 'Identifies main idea and details', 'A main idea tells what a paragraph or passage is mostly about. Details are facts, examples, or events that support the main idea. When you read, ask: What is this mostly about? Which sentences prove it?', [mc(1,'What does the main idea tell?', 'What the passage is mostly about', ['One tiny detail only','The page number','The author\'s name']), mc(2,'Which is a supporting detail?', 'A fact or example that supports the main idea', ['A random sentence about another topic','Only the title','A blank answer']), wr(3,'Read the passage your parent gives you and write the main idea in one sentence.')], v.mainIdea),
    bridgeLesson('Character and Setting', 'Identify who is in the story and where it happens.', 'Identifies character and setting', 'Characters are the people, animals, or creatures in a story. The setting is where and when the story happens. Good readers notice both because they help explain the plot.', [mc(1,'Who is a character?', 'A person or animal in the story', ['The page margin','A math symbol','The table of contents']), mc(2,'What is the setting?', 'Where and when the story happens', ['Only the book cover','A spelling list','The answer choices']), wr(3,'Name one character and one setting from a story you read.')]),
    bridgeLesson('Cause and Effect', 'Tell why something happened and what happened next.', 'Identifies cause and effect', 'A cause is why something happens. An effect is what happens because of the cause. Signal words like because, so, since, and as a result can help.', [mc(1,'What is a cause?', 'Why something happened', ['The last word in a sentence','A drawing tool','A type of map']), mc(2,'What is an effect?', 'What happened because of the cause', ['A random title','Only a vocabulary word','A page number']), wr(3,'Write one cause and effect sentence using because.')]),
    bridgeLesson('Context Clues', 'Use nearby words to figure out word meaning.', 'Uses context clues', 'Context clues are words or sentences around an unknown word that help you understand its meaning. Look for examples, definitions, and clues in the sentence.', [mc(1,'What are context clues?', 'Words nearby that help explain meaning', ['Only capital letters','Random pictures','The answer key']), mc(2,'What should you do when you meet a hard word?', 'Reread nearby words for clues', ['Skip every sentence','Close the lesson','Guess without reading']), wr(3,'Choose one hard word from a passage and write what clue helped you.')], v.mainIdea),
    bridgeLesson('Summarize the Story', 'Retell the important events in order.', 'Summarizes a story', 'A summary tells the most important parts of a story in your own words. It should be shorter than the story and include the beginning, middle, and end.', [mc(1,'What should a summary include?', 'The most important parts', ['Every single word','Only one random detail','Only the author\'s picture']), mc(2,'What order should story events follow?', 'Beginning, middle, and end', ['End, end, end','Alphabetical order only','Random order']), wr(3,'Write a 2 sentence summary of a story you read.')])
  ];
  const reading4 = [
    bridgeLesson('Theme with Evidence', 'Find the lesson or message in a story and support it with evidence.', 'Identifies theme with evidence', 'A theme is a message or lesson a reader can learn from a story. Readers use events, character choices, and text evidence to support the theme.', [mc(1,'What is theme?', 'A message or lesson in a story', ['The number of pages','Only the setting','A punctuation mark']), mc(2,'What should support a theme?', 'Text evidence from the story', ['A random guess','A math equation only','A drawing with no connection']), wr(3,'Write one possible theme from a story and one piece of evidence.')], v.textEvidence),
    bridgeLesson('Character Traits', 'Use actions and words to describe a character.', 'Explains character traits', 'A character trait describes what a character is like on the inside, such as brave, curious, honest, or selfish. Traits should be supported by what the character says, thinks, or does.', [mc(1,'Which is a character trait?', 'Brave', ['Park','Blue','Chapter']), mc(2,'How can you prove a trait?', 'Use the character\'s actions or words', ['Only pick your favorite color','Ignore the story','Use a random number']), wr(3,'Name one trait for a character and give evidence.')]),
    bridgeLesson('Point of View', 'Tell who is telling the story.', 'Identifies point of view', 'Point of view tells who is speaking or telling the story. First person uses words like I, me, and my. Third person uses names or words like he, she, or they.', [mc(1,'Which word can show first person?', 'I', ['They','The title','Chapter']), mc(2,'Which point of view uses he, she, or they?', 'Third person', ['First person','Second person only','No point of view']), wr(3,'Write one clue that shows the point of view in a passage.')]),
    bridgeLesson('Make an Inference', 'Use clues and what you know to figure out what the text means.', 'Makes inferences', 'An inference is a smart idea based on text clues and your own knowledge. Good readers do not just guess; they support their thinking with evidence.', [mc(1,'What does an inference use?', 'Text clues and what you know', ['Only a guess','Only the page color','Only the answer label']), mc(2,'Why is evidence important?', 'It supports your inference', ['It hides the answer','It removes reading','It makes text disappear']), wr(3,'Make one inference from a passage and tell what clue helped you.')], v.inference),
    bridgeLesson('Author\'s Purpose', 'Decide if the author wants to inform, entertain, persuade, or explain.', 'Identifies author purpose', 'Authors write for a reason. They may inform, entertain, persuade, or explain how something works. Look at the text type and details to decide the purpose.', [mc(1,'What does persuade mean?', 'Try to convince the reader', ['Tell a joke only','Measure a line','Name a fraction']), mc(2,'A science article usually tries to...', 'Inform or explain', ['Hide facts','Only entertain with fantasy','Ask no questions']), wr(3,'What is the author\'s purpose of a text you read? Explain why.')])
  ];

  const writing3 = [
    bridgeLesson('Complete Sentences', 'Write complete sentences with a subject and predicate.', 'Writes complete sentences', 'A complete sentence tells a full thought. It usually has a subject and a predicate, starts with a capital letter, and ends with punctuation.', [mc(1,'Which is a complete sentence?', 'The dog ran home.', ['Ran home.','The dog.','Because the dog']), mc(2,'What punctuation can end a statement?', 'Period', ['Comma only','A lowercase letter','A blank space']), wr(3,'Write one complete sentence about school.')], v.sentences),
    bridgeLesson('Nouns, Verbs, and Adjectives', 'Identify basic parts of speech.', 'Identifies parts of speech', 'A noun names a person, place, or thing. A verb shows action or being. An adjective describes a noun.', [mc(1,'Which word is a noun?', 'teacher', ['quickly','jump','blueish']), mc(2,'Which word is a verb?', 'run', ['desk','green','happy']), wr(3,'Write a sentence with one noun and one verb.')]),
    bridgeLesson('Topic Sentence', 'Choose and write a strong topic sentence.', 'Writes topic sentences', 'A topic sentence tells what a paragraph will mostly be about. It should be clear and focused so the reader knows the main idea.', [mc(1,'What does a topic sentence do?', 'Tells the main idea of a paragraph', ['Ends every story','Lists only punctuation','Names every color']), wr(2,'Write a topic sentence about your favorite animal.'), wr(3,'Add one detail sentence that supports your topic sentence.')]),
    bridgeLesson('Opinion Writing', 'State an opinion and support it with reasons.', 'Writes opinion with reasons', 'An opinion tells what someone thinks or believes. A strong opinion paragraph gives reasons and examples to support the opinion.', [mc(1,'Which sentence is an opinion?', 'Pizza is the best lunch.', ['A triangle has three sides.','Water can freeze.','A week has seven days.']), wr(2,'Write one opinion sentence.'), wr(3,'Write one reason that supports your opinion.')]),
    bridgeLesson('Revise and Edit', 'Improve writing by checking meaning and mechanics.', 'Revises and edits writing', 'Writers revise to improve ideas and edit to fix capitalization, punctuation, spelling, and grammar. Good writing usually improves after checking it.', [mc(1,'What does edit mean?', 'Fix grammar, punctuation, spelling, or capitalization', ['Erase all ideas','Never reread','Only add pictures']), wr(2,'Fix this sentence: the cat ran home'), wr(3,'Write one thing you check when editing.')])
  ];
  const writing4 = [
    bridgeLesson('Sentence Types', 'Identify statements, questions, commands, and exclamations.', 'Identifies sentence types', 'Sentences can tell, ask, command, or show strong feeling. The punctuation often gives a clue about the sentence type.', [mc(1,'Which sentence asks something?', 'Where is my notebook?', ['Close the door.','The bird sings.','What a surprise!']), mc(2,'Which mark ends most questions?', 'Question mark', ['Comma','Period only','Quotation mark only']), wr(3,'Write one question and one statement.')], v.sentences),
    bridgeLesson('Compound Sentences', 'Join related ideas with a conjunction.', 'Writes compound sentences', 'A compound sentence joins two complete thoughts. Words like and, but, and so can connect ideas when used correctly.', [mc(1,'Which word can join two complete thoughts?', 'and', ['the','because only','blue']), mc(2,'Which is compound?', 'I practiced, and I improved.', ['Because I practiced.','The big dog.','Running fast.']), wr(3,'Write a compound sentence using but.')]),
    bridgeLesson('Quotation Marks', 'Use quotation marks to show exact spoken words.', 'Uses quotation marks', 'Quotation marks go around the exact words a person says. Commas and end punctuation help show where the speech begins and ends.', [mc(1,'What do quotation marks show?', 'Exact spoken words', ['Only math answers','Silent reading only','A map scale']), mc(2,'Which sentence uses quotation marks?', 'Mia said, "I found it."', ['Mia said I found it.','Mia said I found it','Mia said, I found it.']), wr(3,'Write a sentence where someone speaks.')]),
    bridgeLesson('Informative Paragraph', 'Write a paragraph that teaches facts about a topic.', 'Writes informative paragraph', 'Informative writing teaches the reader about a topic. It uses facts, definitions, details, and examples instead of only opinions.', [mc(1,'What does informative writing do?', 'Teaches facts about a topic', ['Only tells a joke','Only gives an opinion','Only lists random words']), wr(2,'Write a topic sentence for an informative paragraph about plants.'), wr(3,'Write two facts that could support your topic sentence.')]),
    bridgeLesson('Narrative Writing', 'Plan a story with characters, setting, events, and ending.', 'Plans a narrative', 'A narrative tells a story. It should have characters, a setting, events in order, and an ending. Details help the reader picture what happened.', [mc(1,'What does a narrative do?', 'Tells a story', ['Only explains a graph','Only lists chores','Only defines a fraction']), mc(2,'What is setting?', 'Where and when the story happens', ['Only the author name','The answer key','A line plot']), wr(3,'Plan a short story idea with a character, setting, and problem.')])
  ];

  const math3 = [
    bridgeLesson('Place Value to 1,000', 'Read and build three-digit numbers.', 'Uses place value to 1,000', 'Place value tells the value of each digit. In 347, the 3 means 3 hundreds, the 4 means 4 tens, and the 7 means 7 ones.', [mc(1,'What is the value of 5 in 582?', '500', ['50','5','58']), mc(2,'Which number is 6 hundreds, 2 tens, and 4 ones?', '624', ['642','264','426']), wr(3,'Write 739 in expanded form.')], v.placeValue),
    bridgeLesson('Multiplication Equal Groups', 'Understand multiplication as equal groups.', 'Uses multiplication with equal groups', 'Multiplication can show equal groups. If there are 4 groups of 3, that means 4 x 3. Arrays and repeated addition can help.', [mc(1,'What does 4 x 3 mean?', '4 groups of 3', ['4 plus nothing','3 groups of 4 only','A fraction only']), mc(2,'What is 5 x 2?', '10', ['7','12','3']), wr(3,'Draw or describe 3 groups of 4.')], v.multiplication),
    bridgeLesson('Division as Sharing', 'Use division to share equally.', 'Uses division as sharing', 'Division can mean sharing into equal groups. If 12 apples are shared by 3 people, each person gets 4 apples.', [mc(1,'What is 12 ÷ 3?', '4', ['3','9','15']), mc(2,'Division groups should be...', 'equal', ['random','hidden','always bigger']), wr(3,'Write a sharing story for 10 ÷ 2.')], v.division),
    bridgeLesson('Fractions of a Whole', 'Name equal parts of a whole.', 'Identifies simple fractions', 'A fraction shows equal parts of a whole. The bottom number tells how many equal parts are in all. The top number tells how many parts are selected.', [mc(1,'If a shape has 4 equal parts and 1 is shaded, the fraction is...', '1/4', ['4/1','1/2','3/4']), mc(2,'Fractions must use parts that are...', 'equal', ['different sizes','invisible','always circles']), wr(3,'Describe a real-life example of one half.')], v.fractions),
    bridgeLesson('Area and Perimeter', 'Find space inside and distance around.', 'Explains area and perimeter', 'Area is the space inside a shape. Perimeter is the distance around a shape. Count square units for area and side lengths for perimeter.', [mc(1,'What is perimeter?', 'Distance around a shape', ['Space inside','A story detail','A music beat']), mc(2,'What is area?', 'Space inside a shape', ['Only the corners','A spelling word','A type of weather']), wr(3,'Tell how area and perimeter are different.')], v.perimeter)
  ];
  const math4 = [
    bridgeLesson('Place Value to 1,000,000', 'Read large numbers by place value.', 'Uses place value to 1,000,000', 'Large numbers have ones, tens, hundreds, thousands, ten thousands, hundred thousands, and millions places. Place value helps you read, compare, and round numbers.', [mc(1,'What is the value of 7 in 732,000?', '700,000', ['70,000','7,000','700']), mc(2,'Which number is greater?', '452,100', ['425,100','245,100','45,210']), wr(3,'Write 306,418 in expanded form.')], v.placeValue),
    bridgeLesson('Multi-Digit Multiplication', 'Multiply larger numbers using place value.', 'Solves multi-digit multiplication', 'Multi-digit multiplication uses place value and partial products. Break numbers apart, multiply each part, then add the parts together.', [mc(1,'What is 23 x 4?', '92', ['27','82','102']), mc(2,'What strategy uses tens and ones?', 'Partial products', ['Alphabetical order','Setting only','Cause and effect']), wr(3,'Explain how to solve 32 x 3.')], v.multiplication),
    bridgeLesson('Division with Remainders', 'Divide and understand what is left over.', 'Solves division with remainders', 'Sometimes division leaves a remainder. A remainder is the amount left after making equal groups.', [mc(1,'What is 17 ÷ 5?', '3 remainder 2', ['5 remainder 3','2 remainder 3','15']), mc(2,'What is a remainder?', 'What is left after equal groups are made', ['The biggest factor','The title','A fraction line only']), wr(3,'Write a division story with a remainder.')], v.division),
    bridgeLesson('Equivalent Fractions', 'Find fractions that name the same amount.', 'Identifies equivalent fractions', 'Equivalent fractions look different but name the same amount. One half is equivalent to two fourths because they cover the same size of the whole.', [mc(1,'Which is equivalent to 1/2?', '2/4', ['1/3','3/5','4/5']), mc(2,'Equivalent means...', 'equal in value', ['always bigger','always smaller','unrelated']), wr(3,'Draw or describe two equivalent fractions.')], v.fractions),
    bridgeLesson('Angles and Lines', 'Identify basic angles and lines.', 'Identifies lines and angles', 'Geometry uses points, lines, rays, and angles. A right angle forms a square corner. Parallel lines stay the same distance apart and do not meet.', [mc(1,'A right angle measures...', '90 degrees', ['45 degrees','180 degrees','10 degrees']), mc(2,'Parallel lines...', 'do not meet and stay the same distance apart', ['cross once','make every shape curved','are always vertical only']), wr(3,'Find one right angle in the room and describe it.')])
  ];

  const science3 = [
    bridgeLesson('Plant Life Cycle', 'Understand how plants grow and change.', 'Explains plant life cycle', 'Many plants begin as seeds. A seed can sprout, grow roots and a stem, make leaves, flower, and produce new seeds.', [mc(1,'What often comes first in a plant life cycle?', 'Seed', ['Flower only','Adult plant only','Rain cloud']), mc(2,'What do roots help a plant do?', 'Take in water and hold the plant in place', ['Fly away','Make music','Read maps']), wr(3,'Describe one stage in a plant life cycle.')], v.plantLife),
    bridgeLesson('Animal Life Cycles', 'Compare how animals grow and change.', 'Explains animal life cycles', 'Animals have life cycles too. Some animals hatch from eggs, some are born live, and many grow from young animals into adults.', [mc(1,'What is a life cycle?', 'The stages of life and growth', ['Only one day','A map symbol','A type of punctuation']), mc(2,'Which animal can hatch from an egg?', 'Bird', ['Rock','Desk','Cloud']), wr(3,'Name an animal and one stage of its life cycle.')]),
    bridgeLesson('Food Chains', 'Follow energy from one living thing to another.', 'Explains food chains', 'A food chain shows how energy moves. Plants make food from sunlight. Animals eat plants or other animals. Arrows show the direction energy moves.', [mc(1,'What usually starts many food chains?', 'Plants', ['Pencils','Shoes','Desks']), mc(2,'What does a food chain show?', 'How energy moves between living things', ['How to spell words','How to draw angles','How to wash hands']), wr(3,'Write a simple food chain with three parts.')], v.foodChain),
    bridgeLesson('Water Cycle', 'Describe evaporation, condensation, and precipitation.', 'Explains water cycle', 'The water cycle moves water through Earth. Water evaporates into vapor, condenses into clouds, and falls as precipitation.', [mc(1,'What is precipitation?', 'Water falling from clouds', ['Water changing to vapor','A plant root','A magnet']), mc(2,'What is condensation?', 'Water vapor cooling into droplets', ['A kind of sentence','A food group','A map key']), wr(3,'Explain one part of the water cycle.')], v.waterCycle),
    bridgeLesson('Forces and Motion', 'Learn how pushes and pulls change motion.', 'Explains force and motion', 'A force is a push or a pull. Forces can make objects start moving, stop moving, speed up, slow down, or change direction.', [mc(1,'What is a force?', 'A push or pull', ['A color only','A story setting','A fraction']), mc(2,'What can a force change?', 'Motion', ['The alphabet order','The sun into a plant','Every answer']), wr(3,'Describe a push or pull you used today.')])
  ];
  const science4 = [
    bridgeLesson('Energy and Motion', 'Understand how energy can cause change.', 'Explains energy and motion', 'Energy is the ability to do work or cause change. Moving objects have motion energy. Energy can transfer when objects interact.', [mc(1,'Energy can...', 'cause change or motion', ['erase all matter','replace reading','only make maps']), mc(2,'A moving ball has...', 'motion energy', ['no energy ever','only spelling','a government branch']), wr(3,'Give an example of energy causing change.')]),
    bridgeLesson('Electricity and Circuits', 'Learn how simple circuits work.', 'Explains simple circuits', 'Electricity can flow through a closed circuit. A simple circuit may include a battery, wires, and a light bulb. If the path is open, electricity cannot flow all the way around.', [mc(1,'What does a closed circuit allow?', 'Electricity to flow', ['A story to end','A plant to become a rock','A sentence to disappear']), mc(2,'What can open a circuit?', 'A break in the path', ['A closed switch only','A full loop','A connected wire']), wr(3,'Describe what a battery, wire, and bulb do in a circuit.')], v.electricity),
    bridgeLesson('Food Webs', 'See how food chains connect.', 'Explains food webs', 'A food web is made of connected food chains. It shows how several living things in an ecosystem get energy.', [mc(1,'What is a food web?', 'Connected food chains', ['A grammar rule','A single page number','Only one animal']), mc(2,'What gives energy to many food chains?', 'The Sun', ['A ruler','A comma','A desk']), wr(3,'Explain why removing one animal could affect a food web.')], v.foodChain),
    bridgeLesson('Weathering and Erosion', 'Explain how Earth materials break down and move.', 'Explains weathering and erosion', 'Weathering breaks rock into smaller pieces. Erosion moves rock, soil, or sand by wind, water, ice, or gravity.', [mc(1,'What is weathering?', 'Breaking rock into smaller pieces', ['Moving to a new house','Writing a summary','Counting beats']), mc(2,'What is erosion?', 'Moving Earth materials', ['A punctuation mark','A food group','A color mix']), wr(3,'Describe one example of erosion.')]),
    bridgeLesson('Natural Resources', 'Identify resources people use from Earth.', 'Identifies natural resources', 'Natural resources come from Earth. Water, soil, trees, minerals, sunlight, and air are resources people, plants, and animals use.', [mc(1,'Which is a natural resource?', 'Water', ['Plastic toy only','Computer game only','A made-up word']), mc(2,'Why should people conserve resources?', 'So they can be used wisely and protected', ['To waste them faster','To hide all water','To stop plants growing']), wr(3,'Name one natural resource and how people use it.')])
  ];

  const social3 = [
    bridgeLesson('Types of Communities', 'Compare urban, suburban, and rural communities.', 'Compares communities', 'Communities are places where people live and work. Urban areas are cities, suburban areas are near cities, and rural areas have more open land.', [mc(1,'Which community is usually a city?', 'Urban', ['Rural','Only ocean','Only forest']), mc(2,'Which community often has farms or open land?', 'Rural', ['Urban only','Suburban only','No community']), wr(3,'Compare two types of communities.')], v.communities),
    bridgeLesson('Map Skills and Directions', 'Use maps, symbols, and directions.', 'Uses map skills', 'Maps show places. A compass rose shows directions. A map key or legend explains symbols. Good map readers use all of these tools.', [mc(1,'What shows directions on a map?', 'Compass rose', ['Title only','Paragraph','Question mark']), mc(2,'What explains map symbols?', 'Map key or legend', ['Index card','Only the border','A food chain']), wr(3,'Name one map symbol and what it could mean.')], v.maps),
    bridgeLesson('Good Citizenship', 'Learn ways people help their community.', 'Explains citizenship', 'A good citizen follows rules, helps others, tells the truth, and takes responsibility. Citizenship is about how people act in a community.', [mc(1,'Which is a good citizen choice?', 'Helping someone safely', ['Breaking rules for fun','Littering','Taking things that are not yours']), mc(2,'Why do communities have rules?', 'To help people stay safe and respectful', ['To confuse everyone','To stop kindness','To hide maps']), wr(3,'Write one way to be a good citizen.')]),
    bridgeLesson('Producers and Consumers', 'Identify people who make, sell, buy, or use goods and services.', 'Explains producers and consumers', 'A producer makes or grows goods or provides services. A consumer buys or uses goods and services.', [mc(1,'Who is a producer?', 'A person who makes or grows something', ['A person who only buys','A map symbol','A weather event']), mc(2,'Who is a consumer?', 'A person who buys or uses goods/services', ['Only a factory','Only a plant root','A paragraph']), wr(3,'Give one example of a producer and one consumer.')]),
    bridgeLesson('Timelines and History', 'Use timelines to understand events in order.', 'Uses timelines', 'A timeline shows events in the order they happened. Timelines help readers understand history and sequence.', [mc(1,'What does a timeline show?', 'Events in order', ['Only map symbols','Only fractions','Only food groups']), mc(2,'Which event comes first on a timeline?', 'The earliest event', ['The latest event','A random event','The event with the longest word']), wr(3,'Make a timeline with three events from your day.')])
  ];
  const social4 = [
    bridgeLesson('U.S. Regions', 'Learn how the United States can be divided into regions.', 'Identifies U.S. regions', 'A region is an area with features in common. Regions may share landforms, climate, resources, culture, or location.', [mc(1,'What is a region?', 'An area with features in common', ['One random street only','A sentence type','A fraction part']), mc(2,'Which could be used to describe a region?', 'Climate or landforms', ['Only punctuation','Only a spelling word','Only a desk']), wr(3,'Describe one region feature such as climate, land, or resources.')], v.maps),
    bridgeLesson('Native American Communities', 'Learn that Native American communities had different cultures and ways of life.', 'Recognizes Native American communities', 'Native American communities lived in many regions and developed different homes, tools, foods, and traditions based on their environment and culture.', [mc(1,'Why did communities have different homes or tools?', 'They used resources from their environment', ['They all lived the exact same way','They had no traditions','They never used land']), wr(2,'Name one way environment can affect a community.'), wr(3,'Write one respectful question you could ask about a historical community.')]),
    bridgeLesson('Branches of Government', 'Identify the three branches of U.S. government.', 'Identifies branches of government', 'The U.S. government has three branches. The legislative branch makes laws, the executive branch carries out laws, and the judicial branch explains laws.', [mc(1,'Which branch makes laws?', 'Legislative', ['Executive','Judicial','Compass']), mc(2,'Which branch includes courts?', 'Judicial', ['Legislative','Executive','Rural']), wr(3,'Explain why having branches can help government share power.')], v.government),
    bridgeLesson('Rights and Responsibilities', 'Understand that citizens have rights and responsibilities.', 'Explains rights and responsibilities', 'A right is a freedom or protection people have. A responsibility is something people should do to help keep a community fair and safe.', [mc(1,'What is a responsibility?', 'Something people should do', ['A random number','A plant part','Only a color']), mc(2,'Which is a responsibility?', 'Following fair rules', ['Damaging property','Ignoring safety','Taking from others']), wr(3,'Name one right and one responsibility.')]),
    bridgeLesson('Resources and Trade', 'Learn why communities trade goods and resources.', 'Explains resources and trade', 'Communities may not have every resource they need. Trade lets people exchange goods and services with other people or places.', [mc(1,'Why do communities trade?', 'To get goods or resources they need', ['To stop all businesses','To hide every map','To erase history']), mc(2,'Which is a resource?', 'Wood', ['A question mark','Only a story title','A verb tense']), wr(3,'Give an example of something people trade.')])
  ];

  const art = g === 3 ? [
    bridgeLesson('Elements of Art', 'Review line, shape, color, texture, and space.', 'Identifies elements of art', 'Artists use elements such as line, shape, color, texture, form, value, and space to make artwork. These elements help organize what viewers see.', [mc(1,'Which is an element of art?', 'Line', ['Paragraph','Division only','Citizenship']), mc(2,'What is texture?', 'How something looks or feels on the surface', ['A map title','A number fact','A verb']), wr(3,'Name two art elements you could use in a drawing.')], v.artElements),
    bridgeLesson('Warm and Cool Colors', 'Sort warm and cool colors.', 'Sorts colors', 'Warm colors such as red, orange, and yellow can feel energetic. Cool colors such as blue, green, and purple can feel calm.', [mc(1,'Which is a warm color?', 'Red', ['Blue','Green','Purple']), mc(2,'Which is a cool color?', 'Blue', ['Red','Orange','Yellow']), wr(3,'Describe a picture you could make with warm colors.')], v.colorTheory),
    bridgeLesson('Patterns and Symmetry', 'Use repeated designs and balanced sides.', 'Creates patterns and symmetry', 'A pattern repeats. Symmetry means one side matches another side. Artists use patterns and symmetry to create balance and rhythm.', [mc(1,'What is a pattern?', 'A repeated design', ['A random mark','A spelling word','A community rule']), mc(2,'What is symmetry?', 'Matching or balanced sides', ['Only rough texture','A food chain','A sentence fragment']), wr(3,'Plan a symmetrical design.')]),
    bridgeLesson('Landscape Drawing', 'Create a simple landscape with foreground, middle ground, and background.', 'Plans landscape art', 'A landscape shows an outdoor place. Artists can show depth by using foreground, middle ground, and background.', [mc(1,'What is a landscape?', 'Artwork showing an outdoor place', ['Only a portrait','A division problem','A sound pattern']), wr(2,'Name three things you could include in a landscape.'), wr(3,'Explain what would be in the foreground.')]),
    bridgeLesson('Art Reflection', 'Describe choices you made in your artwork.', 'Reflects on art', 'Artists reflect by thinking about what they created, what choices they made, and what they could improve next time.', [mc(1,'What is an art reflection?', 'Thinking and writing about your artwork', ['Throwing the artwork away','Only naming colors','Skipping every choice']), wr(2,'Describe one art choice you made.'), wr(3,'What would you improve next time?')])
  ] : [
    bridgeLesson('Color Theory', 'Use primary, secondary, complementary, and analogous colors.', 'Applies color theory', 'Color theory helps artists choose colors. Primary colors mix to make secondary colors. Complementary colors are across from each other on the color wheel.', [mc(1,'Which are primary colors?', 'Red, yellow, and blue', ['Green, orange, purple','Black, white, gray','Pink, gold, brown']), mc(2,'Complementary colors are...', 'across from each other on the color wheel', ['always the same color','always invisible','only warm colors']), wr(3,'Choose two colors and explain why they work together.')], v.colorTheory),
    bridgeLesson('Perspective Introduction', 'Show depth in a drawing.', 'Uses perspective basics', 'Perspective helps drawings look like they have depth. Objects farther away may look smaller and higher on the page.', [mc(1,'What does perspective help show?', 'Depth or distance', ['Only spelling','A food web','A sentence type']), mc(2,'Far objects may look...', 'smaller', ['larger every time','invisible only','like punctuation']), wr(3,'Describe how to make a road look like it goes far away.')]),
    bridgeLesson('Symmetry and Balance', 'Use balance in a design.', 'Explains symmetry and balance', 'Balance is how visual weight is arranged in artwork. Symmetrical balance has matching sides. Asymmetrical balance feels balanced without matching exactly.', [mc(1,'What does symmetrical mean?', 'Both sides match or nearly match', ['No sides match','Only uses red','Only has words']), wr(2,'Describe a symmetrical design.'), wr(3,'Describe an artwork that could be balanced without matching sides.')]),
    bridgeLesson('Art from Cultures', 'Learn how art can show culture and history.', 'Connects art and culture', 'Art can show stories, traditions, materials, beliefs, and history from different cultures. We study art respectfully and look for meaning and purpose.', [mc(1,'Art can show...', 'Culture, history, or traditions', ['Only math facts','Only exercise','Nothing about people']), wr(2,'What is one respectful thing to notice when studying art from a culture?'), wr(3,'Describe how materials can affect art.')]),
    bridgeLesson('Project Reflection', 'Explain your choices in a project.', 'Writes art reflection', 'A strong reflection explains what you made, what choices you used, what worked well, and what you might change next time.', [mc(1,'What should a project reflection include?', 'Choices and what you learned', ['Only the date','Only one color word','No explanation']), wr(2,'Write two sentences about your project.'), wr(3,'What would you try differently next time?')])
  ];
  const music = [
    bridgeLesson('Rhythm and Beat', 'Clap and describe rhythm patterns.', 'Identifies rhythm and beat', 'Beat is the steady pulse in music. Rhythm is the pattern of long and short sounds over the beat.', [mc(1,'What is beat?', 'The steady pulse', ['A color','A plant part','A map symbol']), mc(2,'What is rhythm?', 'A pattern of sounds', ['Only silence','A paragraph','A fraction']), wr(3,'Clap a rhythm and describe it.')]),
    bridgeLesson('Tempo and Dynamics', 'Listen for speed and loudness.', 'Identifies tempo and dynamics', 'Tempo tells how fast or slow music is. Dynamics tell how loud or soft music is.', [mc(1,'What does tempo mean?', 'Speed of music', ['Loudness only','Instrument family only','Story theme']), mc(2,'What do dynamics describe?', 'Loud and soft', ['Map direction','Fraction parts','Character setting']), wr(3,'Name a song or sound that is fast or slow.')], v.tempo),
    bridgeLesson('Instrument Families', 'Sort instruments by how they make sound.', 'Identifies instrument families', 'Instruments can be grouped into families such as strings, woodwinds, brass, and percussion. Instruments make sound in different ways.', [mc(1,'Which family includes drums?', 'Percussion', ['Strings','Brass','Woodwinds']), mc(2,'Which instrument has strings?', 'Guitar', ['Drum','Trumpet','Flute only']), wr(3,'Choose one instrument and describe its sound.')], v.instruments),
    bridgeLesson('Melody Listening', 'Listen for the tune in music.', 'Identifies melody', 'Melody is the tune you can sing or hum. It can move higher, lower, step by step, or by leaps.', [mc(1,'What is melody?', 'The tune', ['Only the steady beat','A map key','A plant root']), mc(2,'Melody can move...', 'higher and lower', ['only sideways on paper','only as a sentence','only by states']), wr(3,'Hum a short melody and describe if it moves higher or lower.')]),
    bridgeLesson('Music Styles', 'Compare two styles of music.', 'Compares music styles', 'Music styles can sound different because of instruments, rhythm, tempo, voices, and purpose. Listeners can compare what they hear.', [mc(1,'What can make music styles different?', 'Instruments, rhythm, tempo, or voices', ['Only page number','Only punctuation','Only a fraction']), wr(2,'Name one music style or song type you know.'), wr(3,'Compare two sounds or songs.')], v.genres)
  ];
  const health = [
    bridgeLesson('Balanced Meals', 'Choose foods from helpful food groups.', 'Explains balanced meals', 'A balanced meal includes a variety of foods that help the body grow, think, move, and stay healthy.', [mc(1,'Which helps make a balanced meal?', 'A variety of food groups', ['Only candy','Only soda','Only one snack forever']), wr(2,'Name three foods that could be part of a balanced meal.'), wr(3,'Explain why variety is helpful.')], v.nutrition),
    bridgeLesson('Hydration', 'Explain why the body needs water.', 'Explains hydration', 'Water helps the body stay hydrated. It helps the brain, muscles, and organs work well, especially during activity or warm weather.', [mc(1,'Which drink usually helps hydration best?', 'Water', ['Glue','Paint','Only air']), wr(2,'When should you drink water?'), wr(3,'Describe one hydration habit.')]),
    bridgeLesson('Hygiene and Germs', 'Practice habits that reduce germs.', 'Explains hygiene', 'Hygiene means habits that help keep the body clean and reduce germs. Washing hands is one important hygiene habit.', [mc(1,'When should you wash hands?', 'Before eating and after using the bathroom', ['Only once a year','Never after playing','Only when watching TV']), wr(2,'List two handwashing steps.'), wr(3,'Explain why hygiene matters.')], v.germs),
    bridgeLesson('Sleep and Emotions', 'Learn how rest helps the brain and body.', 'Explains rest and emotions', 'Sleep helps the body rest and the brain learn. Rest can also help feelings and focus during the day.', [mc(1,'What can sleep help?', 'Learning, focus, and body rest', ['Never feeling emotions','Skipping food forever','Only map reading']), wr(2,'Name one bedtime habit.'), wr(3,'How can rest help schoolwork?')]),
    bridgeLesson('Digital Safety', 'Make safe choices online.', 'Explains digital safety', 'Digital safety means using devices and the internet carefully. Students should ask a trusted adult before sharing personal information or clicking unknown links.', [mc(1,'What should you do before sharing personal information online?', 'Ask a trusted adult', ['Share it quickly','Post everything','Ignore safety']), wr(2,'Name one thing you should not share online.'), wr(3,'Write one safe internet rule.')], v.safety)
  ];
  const pe = [
    bridgeLesson('Warm Up and Cool Down', 'Prepare and recover safely during exercise.', 'Explains warm-up and cool-down', 'A warm-up prepares the body for activity. A cool down helps the body slow down and recover.', [mc(1,'What does a warm-up do?', 'Prepares the body for activity', ['Ends every lesson','Makes muscles stiff','Replaces water']), mc(2,'What does a cool down do?', 'Helps the body recover', ['Starts the hardest sprint','Skips movement','Stops breathing']), wr(3,'Name one warm-up and one cool-down movement.')], v.fitness),
    bridgeLesson('Balance and Coordination', 'Practice controlled movement.', 'Practices balance and coordination', 'Balance helps you stay steady. Coordination helps different body parts work together smoothly.', [mc(1,'What is balance?', 'Staying steady and controlled', ['Only running fast','Only singing','Only reading']), wr(2,'Describe a balance activity.'), wr(3,'How can you practice coordination safely?')]),
    bridgeLesson('Strength and Endurance', 'Learn how exercise helps the body.', 'Explains strength and endurance', 'Strength helps muscles work against force. Endurance helps the body keep moving for a longer time.', [mc(1,'What is endurance?', 'Keeping activity going over time', ['Only one jump','Only a color','A story setting']), wr(2,'Name one strength activity.'), wr(3,'Name one endurance activity.')], v.fitness),
    bridgeLesson('Throwing and Catching', 'Practice object control safely.', 'Practices throwing and catching', 'Throwing and catching use coordination, space awareness, and control. Practice with a soft ball and safe space.', [mc(1,'What helps safe throwing and catching?', 'Clear space and a soft object', ['Sharp objects','Crowded stairs','Closed eyes']), wr(2,'Describe how to catch safely.'), wr(3,'What should you check before throwing?')]),
    bridgeLesson('Fitness Goals and Sportsmanship', 'Set goals and show respect during movement.', 'Explains fitness goals and sportsmanship', 'A fitness goal helps you improve safely. Sportsmanship means being fair, respectful, and encouraging during games or movement.', [mc(1,'What is sportsmanship?', 'Being fair and respectful', ['Only winning','Ignoring others','Breaking rules']), wr(2,'Write one fitness goal.'), wr(3,'How can you show good sportsmanship?')])
  ];

  return [
    { key:'reading', name:`${label} Reading & Language Arts`, subject:'Language Arts', course_type:'core', description: g===3 ? 'Main idea, story elements, context clues, cause/effect, and summaries.' : 'Theme, traits, point of view, inference, author purpose, and evidence.', unit_title: g===3 ? 'Reading to Understand' : 'Reading with Evidence', unit_description:`${label} reading with passages, audio support, and short responses.`, lessons: g===3 ? reading3 : reading4 },
    { key:'writing', name:`${label} Writing & Grammar`, subject:'Writing & Grammar', course_type:'core', description: g===3 ? 'Sentences, parts of speech, topic sentences, opinion writing, and editing.' : 'Sentence types, compound sentences, quotations, paragraphs, and narrative planning.', unit_title: g===3 ? 'Sentences and Paragraphs' : 'Paragraphs and Writing Craft', unit_description:`${label} writing and grammar practice with short responses.`, lessons: g===3 ? writing3 : writing4 },
    { key:'math', name:`${label} Math`, subject:'Math', course_type:'core', description: g===3 ? 'Place value, multiplication, division, fractions, area, and perimeter.' : 'Large place value, multi-digit operations, fractions, geometry, and word problems.', unit_title: g===3 ? 'Operations and Problem Solving' : 'Numbers, Fractions, and Geometry', unit_description:`${label} math with videos, examples, and guided checks.`, lessons: g===3 ? math3 : math4 },
    { key:'science', name:`${label} Science`, subject:'Science', course_type:'core', description: g===3 ? 'Life cycles, food chains, water cycle, and forces.' : 'Energy, circuits, food webs, Earth processes, and resources.', unit_title: g===3 ? 'Life and Earth Systems' : 'Energy, Earth, and Living Systems', unit_description:`${label} science with diagrams, audio, and short explanations.`, lessons: g===3 ? science3 : science4 },
    { key:'social', name:`${label} Social Studies`, subject:'Social Studies', course_type:'core', description: g===3 ? 'Communities, maps, citizenship, economics, and timelines.' : 'Regions, history, government, rights, responsibilities, and trade.', unit_title: g===3 ? 'Communities and Maps' : 'Regions, History, and Government', unit_description:`${label} social studies with maps, passages, and evidence.`, lessons: g===3 ? social3 : social4 },
    { key:'art', name:`${label} Art Fundamentals`, subject:'Art', course_type:'elective', description:'Elements of art, color, design, culture, and project reflection.', unit_title:'Art Elements and Design', unit_description:`${label} art lessons with project prompts and reflection.`, lessons: art },
    { key:'music', name:`${label} Music Fundamentals`, subject:'Music', course_type:'elective', description:'Rhythm, tempo, dynamics, instruments, melody, and music styles.', unit_title:'Listening and Music Skills', unit_description:`${label} music lessons with listening and reflection.`, lessons: music },
    { key:'health', name:`${label} Health and Nutrition`, subject:'Health and Nutrition', course_type:'elective', description:'Nutrition, hydration, hygiene, rest, emotions, and digital safety.', unit_title:'Healthy Choices', unit_description:`${label} health lessons with practical habits.`, lessons: health },
    { key:'pe', name:`${label} Physical Education`, subject:'Physical Education', course_type:'elective', description:'Warm-up, balance, strength, endurance, coordination, and sportsmanship.', unit_title:'Fitness, Safety, and Movement', unit_description:`${label} physical education with safe movement and reflection.`, lessons: pe }
  ];
}

async function ensureBridgeGradesThreeFourFoundation() {
  for (const grade of [3, 4]) {
    const plans = bridgePlans(grade);
    const created = {};
    for (const plan of plans) {
      created[plan.key] = await ensureCourse({
        name: plan.name,
        grade_level: grade,
        subject: plan.subject,
        course_type: plan.course_type,
        description: plan.description,
        credit_hours: plan.course_type === 'core' ? 1.0 : 0.5
      });
    }
    for (const plan of plans) {
      const course = created[plan.key];
      const unit = await ensureUnit(course.id, 1, plan.unit_title, plan.unit_description);
      for (let i = 0; i < plan.lessons.length; i++) {
        const item = plan.lessons[i];
        const lesson = await ensureLesson(unit.id, i + 1, {
          title: item.title,
          description: item.description,
          lesson_type: 'bridge_grade_guided_lesson',
          interactive_type: 'guided_lesson_with_practice',
          skill_name: item.skill,
          learning_objectives: item.skill,
          instructional_content: item.content,
          audio_url: kTtsPlaceholder(item.content),
          activity_data: item.activity_data,
          video_url: item.video_url,
          video_title: item.video_title,
          video_source: item.video_source,
          video_verified: !!item.video_url
        });
        await safeEnsureAssignment(lesson.id, {
          title: `${item.title} Practice`,
          description: item.description,
          instructions: `Read, listen, or watch the lesson. Then answer the practice questions for ${item.title}.`,
          assignment_type: 'practice',
          weight_category: 'homework',
          point_value: 0,
          questions: item.questions
        }, `grade-${grade}-${plan.key}-${i+1}-practice`);
        await safeEnsureAssignment(lesson.id, {
          title: `${item.title} Quick Check`,
          description: `Check understanding for ${item.title}.`,
          instructions: `Complete this short Grade ${grade} check. Use the lesson text or video if needed.`,
          assignment_type: 'quiz',
          weight_category: 'quiz',
          point_value: 100,
          questions: item.questions.slice(0, 2)
        }, `grade-${grade}-${plan.key}-${i+1}-quiz`);
      }
    }
    const students = await pool.query('SELECT id FROM students WHERE grade_level = $1', [grade]).catch(() => ({ rows: [] }));
    for (const st of students.rows) {
      for (const c of Object.values(created)) await safeEnrollStudentInCourse(st.id, c.id);
    }
  }
}


async function ensureCurriculumFoundation() {
  try {
    await addGradeFiveElectivesAndHealth();
    await ensureVisibleGradeFiveElectivesForAllExistingCourses();
    await ensureGradeFiveSocialStudiesEnrollment();
    await repairGradeFiveCoreVideoCoverage();
    await repairGradeFiveSpellingVideoCoverage();
    await repairGradeFiveVocabGrammarArtVideoCoverage();
    await repairGradeFiveElectiveVideoCoverage();
    await repairGradeFiveElectiveQuestionCoverage();
    await repairGradeFiveElectiveLessonContentCoverage();
    await repairGradeFiveLanguageArtsContentCoverage();
    await addKindergartenElectivesAndHealth();
    await ensureCompleteKindergartenFoundation();
    await ensureKindergartenBlueprintSampleLessons();
    await ensureEarlyElementaryGradesOneTwoFoundation();
    await cleanupEarlyElementaryDuplicateCourses();
    await ensureBridgeGradesThreeFourFoundation();
    console.log('Curriculum foundation ensured: Grade 5 foundation and complete Kindergarten visual activity path.');
  } catch (e) {
    console.error('ensureCurriculumFoundation error:', e.message);
  }
}

// =============================================
// AUTHENTICATION ROUTES
// =============================================

// Parent/Teacher Login
app.post('/api/auth/login', async (req, res) => {
  try {
    const { email, password } = req.body;
    const result = await pool.query('SELECT * FROM users WHERE email = $1', [email]);
    if (result.rows.length === 0) return res.status(401).json({ error: 'Invalid credentials' });
    const user = result.rows[0];
    const validPassword = await bcrypt.compare(password, user.password_hash);
    if (!validPassword) return res.status(401).json({ error: 'Invalid credentials' });
    const token = jwt.sign({ id: user.id, role: user.role, email: user.email }, process.env.JWT_SECRET || 'jnm_secret_key', { expiresIn: '24h' });
    res.json({ token, user: { id: user.id, email: user.email, first_name: user.first_name, last_name: user.last_name, role: user.role } });
  } catch (error) { res.status(500).json({ error: error.message }); }
});

// Student Login
app.post('/api/auth/student-login', async (req, res) => {
  try {
    const { school_id, password } = req.body;
    const normalizedSchoolId = String(school_id || '').trim().toUpperCase();
    const result = await pool.query('SELECT * FROM students WHERE upper(school_id) = $1', [normalizedSchoolId]);
    if (result.rows.length === 0) return res.status(401).json({ error: 'Invalid School ID' });
    const student = result.rows[0];
    if (student.status && String(student.status).toLowerCase() !== 'active') {
      return res.status(403).json({ error: 'This student is not active. Ask the parent or teacher to reactivate the student before logging in.' });
    }
    if (!student.password_hash) return res.status(403).json({ error: 'A student password has not been set. Ask the parent or teacher to set one.' });
    if (!password) return res.status(401).json({ error: 'Password is required' });
    const validPassword = await bcrypt.compare(password, student.password_hash);
    if (!validPassword) return res.status(401).json({ error: 'Invalid password' });
    const session = await startStudentSession(req, student.id);
    const token = jwt.sign({ id: student.id, role: 'student', school_id: student.school_id, session_id: session.id }, process.env.JWT_SECRET || 'jnm_secret_key', { expiresIn: '24h' });
    res.json({ token, session_id: session.id, student: { id: student.id, school_id: student.school_id, first_name: student.first_name, last_name: student.last_name, grade_level: student.grade_level } });
  } catch (error) { res.status(500).json({ error: error.message }); }
});

// Student logout closes the current login session for attendance tracking.
app.post('/api/auth/logout', authenticateToken, async (req, res) => {
  try {
    if (req.user.role !== 'student') return res.json({ success: true, tracked: false });
    const closed = await endStudentSession(req, req.body?.session_id || null);
    res.json({ success: true, tracked: !!closed, session: closed });
  } catch (error) { res.status(500).json({ error: error.message }); }
});

// Restore session from token (used on app startup / refresh)
app.get('/api/auth/me', authenticateToken, async (req, res) => {
  try {
    if (req.user.role === 'student') {
      const r = await pool.query('SELECT id, school_id, first_name, last_name, grade_level, status FROM students WHERE id = $1', [req.user.id]);
      if (r.rows.length === 0) return res.status(404).json({ error: 'Student not found' });
      return res.json({ type: 'student', student: r.rows[0] });
    }
    const r = await pool.query('SELECT id, email, first_name, last_name, role FROM users WHERE id = $1', [req.user.id]);
    if (r.rows.length === 0) return res.status(404).json({ error: 'User not found' });
    return res.json({ type: 'parent', user: r.rows[0] });
  } catch (error) { res.status(500).json({ error: error.message }); }
});

// Any signed-in parent or student can change their own password after
// confirming the current one. This avoids support-dependent password changes.
app.post('/api/auth/change-password', authenticateToken, async (req, res) => {
  try {
    const currentPassword = String(req.body?.current_password || '');
    const newPassword = String(req.body?.new_password || '');
    if (newPassword.length < 8) return res.status(400).json({ error: 'The new password must be at least 8 characters.' });
    const table = req.user.role === 'student' ? 'students' : 'users';
    const result = await pool.query(`SELECT id, password_hash FROM ${table} WHERE id=$1`, [req.user.id]);
    if (!result.rows.length) return res.status(404).json({ error: 'Account not found' });
    const existingHash = result.rows[0].password_hash;
    if (!existingHash || !(await bcrypt.compare(currentPassword, existingHash))) {
      return res.status(401).json({ error: 'Current password is incorrect.' });
    }
    const passwordHash = await bcrypt.hash(newPassword, 12);
    await pool.query(`UPDATE ${table} SET password_hash=$1, updated_at=CURRENT_TIMESTAMP WHERE id=$2`, [passwordHash, req.user.id]);
    res.json({ success: true, message: 'Password changed successfully.' });
  } catch (error) { res.status(500).json({ error: error.message }); }
});

// Register Parent
app.post('/api/auth/register', async (req, res) => {
  try {
    const { email, password, first_name, last_name, phone } = req.body;
    if (!email || !first_name || !last_name) return res.status(400).json({ error: 'Email, first name, and last name are required.' });
    if (String(password || '').length < 8) return res.status(400).json({ error: 'Password must be at least 8 characters.' });
    const hashedPassword = await bcrypt.hash(password, 12);
    const result = await pool.query(
      'INSERT INTO users (email, password_hash, first_name, last_name, phone) VALUES ($1, $2, $3, $4, $5) RETURNING id, email, first_name, last_name, role',
      [email, hashedPassword, first_name, last_name, phone]
    );
    const user = result.rows[0];
    const token = jwt.sign({ id: user.id, role: user.role, email: user.email }, process.env.JWT_SECRET || 'jnm_secret_key', { expiresIn: '24h' });
    res.status(201).json({ token, user });
  } catch (error) {
    if (error.code === '23505') return res.status(400).json({ error: 'Email already exists' });
    res.status(500).json({ error: error.message });
  }
});

// =============================================

const CURRENT_GRADE_TIME_JOIN = `
  JOIN students tl_student ON tl_student.id = tl.student_id
  LEFT JOIN courses tl_direct_course ON tl.course_id = tl_direct_course.id
  LEFT JOIN assignments tl_assignment ON tl.assignment_id = tl_assignment.id
  LEFT JOIN lessons tl_assignment_lesson ON tl_assignment.lesson_id = tl_assignment_lesson.id
  LEFT JOIN units tl_assignment_unit ON tl_assignment_lesson.unit_id = tl_assignment_unit.id
  LEFT JOIN courses tl_assignment_course ON tl_assignment_unit.course_id = tl_assignment_course.id
  LEFT JOIN lessons tl_lesson ON tl.lesson_id = tl_lesson.id
  LEFT JOIN units tl_lesson_unit ON tl_lesson.unit_id = tl_lesson_unit.id
  LEFT JOIN courses tl_lesson_course ON tl_lesson_unit.course_id = tl_lesson_course.id
`;
const CURRENT_GRADE_TIME_FILTER = `COALESCE(tl_direct_course.grade_level, tl_assignment_course.grade_level, tl_lesson_course.grade_level) = tl_student.grade_level`;
const CURRENT_GRADE_TIME_SUBJECT = `COALESCE(tl_direct_course.subject, tl_assignment_course.subject, tl_lesson_course.subject, 'Learning Activity')`;

// STUDENT MANAGEMENT ROUTES
// =============================================

// Get all students for a parent (active by default)
app.get('/api/students', authenticateToken, async (req, res) => {
  try {
    const { includeInactive } = req.query;
    const base = `SELECT s.*,
      (SELECT COUNT(*)::integer FROM enrollments e JOIN courses c ON c.id=e.course_id
       WHERE e.student_id=s.id AND e.status='active' AND c.is_active=true AND c.grade_level=s.grade_level) AS course_count,
      (SELECT MAX(a.last_activity_at) FROM student_activity_sessions a WHERE a.student_id=s.id) AS last_active,
      (SELECT COUNT(*)::integer FROM curriculum_submissions cs WHERE cs.student_id=s.id) AS completed_count
      FROM students s WHERE s.user_id=$1`;
    const query = includeInactive === 'true'
      ? `${base} ORDER BY s.first_name, s.last_name, s.id`
      : `${base} AND lower(COALESCE(s.status,'active'))='active' ORDER BY s.first_name, s.last_name, s.id`;
    const result = await pool.query(query, [req.user.id]);
    res.json(result.rows);
  } catch (error) { res.status(500).json({ error: error.message }); }
});

// Add a student
app.post('/api/students', authenticateToken, async (req, res) => {
  try {
    if (req.user.role === 'student') return res.status(403).json({ error: 'Parent or teacher access required.' });
    const { school_id, first_name, last_name, email, password, date_of_birth, grade_level } = req.body;
    const normalizedSchoolId = String(school_id || '').trim().toUpperCase();
    if (!/^JNM-\d{3,}$/.test(normalizedSchoolId)) return res.status(400).json({ error: 'Student ID must use the format JNM-005.' });
    if (String(password || '').length < 8) return res.status(400).json({ error: 'Create a student password with at least 8 characters.' });
    const password_hash = await bcrypt.hash(password, 12);
    const result = await pool.query(
      'INSERT INTO students (user_id, school_id, first_name, last_name, email, password_hash, date_of_birth, grade_level) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING *',
      [req.user.id, normalizedSchoolId, first_name, last_name, email, password_hash, date_of_birth, parseInt(grade_level, 10)]
    );
    const newStudent = result.rows[0];
    const gradeCourses = await pool.query('SELECT id FROM courses WHERE grade_level = $1 AND is_active = true', [parseInt(newStudent.grade_level, 10)]).catch(() => ({ rows: [] }));
    for (const c of gradeCourses.rows) {
      await safeEnrollStudentInCourse(newStudent.id, c.id);
    }
    res.status(201).json(newStudent);
  } catch (error) {
    if (error.code === '23505') return res.status(400).json({ error: 'School ID already exists' });
    res.status(500).json({ error: error.message });
  }
});

// A parent/teacher can issue a new student password without knowing the old
// password. Ownership is always checked before the update.
app.put('/api/students/:id/password', authenticateToken, async (req, res) => {
  try {
    if (req.user.role === 'student') return res.status(403).json({ error: 'Parent or teacher access required.' });
    const newPassword = String(req.body?.new_password || '');
    if (newPassword.length < 8) return res.status(400).json({ error: 'Student password must be at least 8 characters.' });
    const hash = await bcrypt.hash(newPassword, 12);
    const result = await pool.query(
      'UPDATE students SET password_hash=$1, updated_at=CURRENT_TIMESTAMP WHERE id=$2 AND user_id=$3 RETURNING id, school_id',
      [hash, req.params.id, req.user.id]
    );
    if (!result.rows.length) return res.status(404).json({ error: 'Student not found.' });
    res.json({ success: true, student: result.rows[0] });
  } catch (error) { res.status(500).json({ error: error.message }); }
});

// Update a student
app.put('/api/students/:id', authenticateToken, async (req, res) => {
  try {
    const { id } = req.params;
    const { first_name, last_name, email, grade_level, school_id, status } = req.body;
    const gradeNum = parseInt(grade_level, 10);
    if (Number.isNaN(gradeNum) || gradeNum < 0 || gradeNum > 12) {
      return res.status(400).json({ error: 'Grade level must be Kindergarten through Grade 12.' });
    }

    const before = await pool.query('SELECT grade_level, status FROM students WHERE id = $1 AND user_id = $2', [id, req.user.id]);
    if (before.rows.length === 0) return res.status(404).json({ error: 'Student not found' });
    const rawStatus = String(status || before.rows[0].status || 'active').toLowerCase().replace(/[\s_]+/g, '-');
    const statusValue = rawStatus === 'inactive' || rawStatus === 'not-active' || rawStatus === 'non-active'
      ? 'inactive'
      : (rawStatus === 'graduated' || rawStatus === 'completed' || rawStatus === 'graduated-completed' ? 'graduated' : 'active');

    const result = await pool.query(
      'UPDATE students SET first_name=$1, last_name=$2, email=$3, grade_level=$4, school_id=$5, status=$6, updated_at=CURRENT_TIMESTAMP WHERE id=$7 AND user_id=$8 RETURNING *',
      [first_name, last_name, email || null, gradeNum, school_id, statusValue, id, req.user.id]
    );

    // When a parent changes a student grade, make the active curriculum a clean
    // grade window. Old work is preserved in submissions/grades, but every old
    // active enrollment is dropped so old courses cannot continue showing in
    // Assignments, Dashboard, Courses, or pacing views. Then the student is
    // enrolled into all active courses for the new grade.
    let active_curriculum_reset = false;
    let active_course_count = null;
    if (String(before.rows[0].grade_level) !== String(gradeNum)) {
      active_curriculum_reset = true;
      await pool.query('BEGIN');
      try {
        await pool.query(
          `UPDATE enrollments
           SET status = 'dropped', updated_at = CURRENT_TIMESTAMP
           WHERE student_id = $1 AND status = 'active'`,
          [id]
        );

        const gradeCourses = await pool.query(
          'SELECT id FROM courses WHERE grade_level = $1 AND is_active = true ORDER BY course_type, subject, name',
          [gradeNum]
        );
        active_course_count = gradeCourses.rows.length;
        for (const c of gradeCourses.rows) {
          await pool.query(
            `INSERT INTO enrollments (student_id, course_id, status)
             VALUES ($1, $2, 'active')
             ON CONFLICT (student_id, course_id)
             DO UPDATE SET status = 'active', updated_at = CURRENT_TIMESTAMP`,
            [id, c.id]
          ).catch(async () => {
            const existing = await pool.query(
              'SELECT id FROM enrollments WHERE student_id = $1 AND course_id = $2 LIMIT 1',
              [id, c.id]
            );
            if (existing.rows[0]) {
              await pool.query(
                "UPDATE enrollments SET status = 'active', updated_at = CURRENT_TIMESTAMP WHERE id = $1",
                [existing.rows[0].id]
              );
            } else {
              await pool.query(
                "INSERT INTO enrollments (student_id, course_id, status) VALUES ($1, $2, 'active')",
                [id, c.id]
              );
            }
          });
        }
        await pool.query('COMMIT');
      } catch (switchErr) {
        await pool.query('ROLLBACK').catch(() => {});
        throw switchErr;
      }
    }

    res.json({
      ...result.rows[0],
      active_curriculum_reset,
      active_course_count,
      curriculum_message: active_curriculum_reset
        ? (active_course_count > 0
            ? `Grade changed. Active curriculum was reset to Grade ${gradeNum === 0 ? 'Kindergarten' : gradeNum}.`
            : `Grade changed. No active curriculum courses are built for Grade ${gradeNum === 0 ? 'Kindergarten' : gradeNum} yet.`)
        : null
    });
  } catch (error) { res.status(500).json({ error: error.message }); }
});

// Delete a student (soft delete - preserves grades/submissions/enrollments)
app.delete('/api/students/:id', authenticateToken, async (req, res) => {
  try {
    const { hard } = req.query;
    if (hard === 'true') {
      await pool.query('DELETE FROM students WHERE id = $1 AND user_id = $2', [req.params.id, req.user.id]);
      return res.json({ message: 'Student permanently deleted' });
    }
    await pool.query(
      "UPDATE students SET status = 'inactive', updated_at = CURRENT_TIMESTAMP WHERE id = $1 AND user_id = $2",
      [req.params.id, req.user.id]
    );
    res.json({ message: 'Student deactivated' });
  } catch (error) { res.status(500).json({ error: error.message }); }
});

// Get student dashboard
app.get('/api/students/:id/dashboard', authenticateToken, async (req, res) => {
  try {
    const { id } = req.params;
    const student = await pool.query('SELECT * FROM students WHERE id = $1', [id]);
    const courses = await pool.query(
      `SELECT c.*, e.enrolled_date, e.status as enrollment_status
       FROM courses c
       JOIN enrollments e ON c.id = e.course_id
       JOIN students st ON st.id = e.student_id
       WHERE e.student_id = $1 AND e.status = $2 AND c.grade_level = st.grade_level AND c.is_active = true`,
      [id, 'active']
    );
    const recentGrades = await pool.query(
      `SELECT g.*, a.title as assignment_title, c.name as course_name, c.subject
       FROM grades g
       JOIN assignments a ON g.assignment_id = a.id
       JOIN lessons l ON a.lesson_id = l.id
       JOIN units u ON l.unit_id = u.id
       JOIN courses c ON u.course_id = c.id
       JOIN students st ON st.id = g.student_id
       WHERE g.student_id = $1 AND c.grade_level = st.grade_level AND c.is_active = true
       ORDER BY g.graded_date DESC LIMIT 20`,
      [id]
    );
    const pendingAssignments = await pool.query(
      `SELECT a.*, c.name as course_name, c.subject
       FROM assignments a
       JOIN lessons l ON a.lesson_id = l.id
       JOIN units u ON l.unit_id = u.id
       JOIN courses c ON u.course_id = c.id
       JOIN enrollments e ON c.id = e.course_id
       JOIN students st ON st.id = e.student_id
       WHERE e.student_id = $1 AND e.status = 'active' AND c.grade_level = st.grade_level AND c.is_active = true
         AND a.id NOT IN (SELECT assignment_id FROM grades WHERE student_id = $1)
         AND a.is_active = true
       ORDER BY a.due_date`,
      [id]
    );
    const notifications = await pool.query(
      'SELECT * FROM notifications WHERE student_id = $1 AND is_read = false ORDER BY created_at DESC LIMIT 10',
      [id]
    );
    res.json({
      student: student.rows[0],
      courses: courses.rows,
      recentGrades: recentGrades.rows,
      pendingAssignments: pendingAssignments.rows,
      notifications: notifications.rows
    });
  } catch (error) { res.status(500).json({ error: error.message }); }
});

// =============================================
// COURSE ROUTES
// =============================================

// Get courses by grade level
app.get('/api/courses/grade/:gradeLevel', async (req, res) => {
  try {
    const result = await pool.query(
      'SELECT * FROM courses WHERE grade_level = $1 AND is_active = true ORDER BY course_type, subject, name',
      [req.params.gradeLevel]
    );
    res.json(filterEarlyElementaryCourseRows(result.rows, req.params.gradeLevel));
  } catch (error) { res.status(500).json({ error: error.message }); }
});

// Get assignments by grade level (no auth required - bridges demo to live data)
app.get('/api/assignments/grade/:gradeLevel', async (req, res) => {
  try {
    const { gradeLevel } = req.params;
    const { studentId } = req.query;

    const result = await pool.query(
      `SELECT a.id, a.title, a.description, a.due_date, a.assignment_type,
              a.point_value, a.weight_category, a.passing_grade,
              c.name as course_name, c.subject, c.grade_level,
              l.title as lesson_title, l.video_url, l.video_source, l.video_verified, l.video_title,
              u.title as unit_title, u.unit_number, l.lesson_number
       FROM assignments a
       JOIN lessons l ON a.lesson_id = l.id
       JOIN units u ON l.unit_id = u.id
       JOIN courses c ON u.course_id = c.id
       WHERE c.grade_level = $1 AND c.is_active = true AND a.is_active = true
       ORDER BY c.subject, u.unit_number, l.lesson_number`,
      [gradeLevel]
    );

    let grades = [];
    let submissions = [];

    if (studentId) {
      try {
        const gradesResult = await pool.query(
          'SELECT * FROM grades WHERE student_id = $1', [studentId]
        );
        grades = gradesResult.rows;
      } catch (e) { /* no grades yet */ }

      try {
        const submissionsResult = await pool.query(
          "SELECT * FROM submissions WHERE student_id = $1 AND status IN ('submitted','graded','completed','partially_graded')", [studentId]
        );
        submissions = submissionsResult.rows;
      } catch (e) { /* no submissions yet */ }
    }

    const assignments = result.rows.map(a => {
      applyTopicVideo(a);
      const grade = grades.find(g => g.assignment_id === a.id);
      const submission = submissions.find(s => s.assignment_id === a.id);

      normalizeAssignmentTitleAndTopic(a);
      const step_type = getStepType(a);
      const counts_toward_grade = countsTowardGrade(step_type);
      let status = assignmentStatusForRow({ grade, submission, assignment: a });
      if (status === 'not_started') status = 'pending';
      return {
        ...a,
        status,
        step_type,
        counts_toward_grade,
        step_type_label: stepTypeLabel(step_type),
        percentage: grade ? parseFloat(grade.percentage) : null,
        letter_grade: grade ? grade.letter_grade : null,
        points_earned: grade ? parseInt(grade.points_earned) : null,
        feedback: grade ? grade.feedback : null
      };
    });

    res.json(assignments);
  } catch (error) { res.status(500).json({ error: error.message }); }
});


// Public verified resource library used by curriculum seed/prototype lessons.
app.get('/api/resources/public', authenticateToken, async (req, res) => {
  res.json(PUBLIC_RESOURCE_LIBRARY);
});

// Get single lesson with full content (no auth - bridges demo to live data)
app.get('/api/lessons/:id', async (req, res) => {
  try {
    const result = await pool.query(
      `SELECT l.*, u.title as unit_title, u.unit_number,
              c.name as course_name, c.subject, c.grade_level, c.id as course_id
       FROM lessons l
       JOIN units u ON l.unit_id = u.id
       JOIN courses c ON u.course_id = c.id
       WHERE l.id = $1`, [req.params.id]
    );
    if (result.rows.length === 0) return res.status(404).json({ error: 'Lesson not found' });
    const row = applyTopicVideo(result.rows[0]);
    res.json(row);
  } catch (error) { res.status(500).json({ error: error.message }); }
});

// Get all lessons for a course with content (no auth)
app.get('/api/courses/:courseId/lessons', async (req, res) => {
  try {
    const result = await pool.query(
      `SELECT l.id, l.title, l.lesson_number, l.learning_objectives, l.video_url, l.video_source,
              l.video_verified, l.video_title,
              u.title as unit_title, u.unit_number, u.id as unit_id,
              c.name as course_name, c.subject, c.grade_level
       FROM lessons l
       JOIN units u ON l.unit_id = u.id
       JOIN courses c ON u.course_id = c.id
       WHERE u.course_id = $1 AND COALESCE(c.is_active, true) = true AND COALESCE(u.is_active, true) = true AND COALESCE(l.is_active, true) = true
       ORDER BY u.unit_number, l.lesson_number`, [req.params.courseId]
    );
    res.json(result.rows.map(row => applyTopicVideo(row)));
  } catch (error) { res.status(500).json({ error: error.message }); }
});

// Get assignment detail with questions (no auth - bridges demo to live data)
app.get('/api/assignments/:id/detail', async (req, res) => {
  try {
    const { id } = req.params;
    const result = await pool.query(
      `SELECT a.id, a.title, a.description, a.instructions, a.questions,
              a.due_date, a.assignment_type, a.point_value, a.weight_category, a.passing_grade,
              c.name as course_name, c.subject, c.grade_level,
              l.id as lesson_id, l.title as lesson_title, l.description as lesson_description,
              l.video_url, l.video_source, l.video_verified, l.video_title,
              l.learning_objectives, l.instructional_content, l.vocabulary, l.examples, l.guided_practice,
              l.passage_title, l.passage_type, l.reading_passage, l.passage_source,
              l.reading_level, l.grade_band, l.audio_url, l.audio_title, l.read_aloud_available,
              l.concept_title, l.concept_explanation, l.formula_text,
              l.step_by_step_examples, l.common_mistakes,
              l.materials_needed, l.safety_notes, l.experiment_steps,
              l.observation_questions, l.conclusion_prompt,
              l.source_title, l.source_type, l.source_text,
              l.historical_context, l.timeline_points, l.map_activity_prompt,
              l.real_world_application, l.word_list,
              l.image_url, l.lesson_type, l.interactive_type, l.activity_data, l.skill_name,
              u.title as unit_title, u.unit_number
       FROM assignments a
       JOIN lessons l ON a.lesson_id = l.id
       JOIN units u ON l.unit_id = u.id
       JOIN courses c ON u.course_id = c.id
       WHERE a.id = $1 AND COALESCE(a.is_active, true) = true AND COALESCE(l.is_active, true) = true AND COALESCE(u.is_active, true) = true AND COALESCE(c.is_active, true) = true`,
      [id]
    );
    if (result.rows.length === 0) return res.status(404).json({ error: 'Assignment not found' });
    const row = applyTopicVideo(result.rows[0]);
    enrichLessonDetail(row);
    applyTopicVideo(row);
    row.questions = improveQuestionsForContext(row);
    row.step_type = getStepType(row);
    row.counts_toward_grade = countsTowardGrade(row.step_type);
    row.step_type_label = stepTypeLabel(row.step_type);
    if ((!row.questions || row.questions.length === 0) && Array.isArray(row.questions)) row.questions = row.questions;
    res.json(row);
  } catch (error) { res.status(500).json({ error: error.message }); }
});

// Submit assignment answers and auto-grade (no auth - bridges demo to live data)
app.post('/api/assignments/:id/submit', optionalAuthenticateToken, async (req, res) => {
  try {
    const { id } = req.params;
    const { student_id, answers, practice_responses, written_response, time_spent_seconds, is_retake, progress_complete, preview_mode } = req.body;

    // Fetch assignment with questions + lesson passage text (for writing evidence detection)
    const assignResult = await pool.query(
      `SELECT a.*,
              l.title as lesson_title, l.reading_passage, l.source_text, l.passage_title, l.passage_type, l.source_title, l.source_type,
              c.subject, c.grade_level, c.name as course_name
       FROM assignments a
       LEFT JOIN lessons l ON a.lesson_id = l.id
       LEFT JOIN units u ON l.unit_id = u.id
       LEFT JOIN courses c ON u.course_id = c.id
       WHERE a.id = $1 AND COALESCE(a.is_active, true) = true AND COALESCE(l.is_active, true) = true AND COALESCE(u.is_active, true) = true AND COALESCE(c.is_active, true) = true`, [id]);
    if (assignResult.rows.length === 0) return res.status(404).json({ error: 'Assignment not found' });

    const assignment = applyTopicVideo(assignResult.rows[0]);
    enrichLessonDetail(assignment);
    let questions = improveQuestionsForContext(assignment) || [];
    const passageText = assignment.reading_passage || assignment.source_text || '';
    const step_type = getStepType(assignment);
    const countsGradeStep = countsTowardGrade(step_type);
    const forceProgressComplete = !!progress_complete && !countsGradeStep;
    if (forceProgressComplete) questions = [];

    const rawPractice = parseJsonList(assignment.guided_practice, []);
    const requiredPractice = (!forceProgressComplete && !isQuizOrTestAssignment(assignment) && !isSpellingAssignmentRow(assignment)) ? rawPractice : [];
    const requiredWorkErrors = validateRequiredStudentWork({
      questions,
      answers: Array.isArray(answers) ? answers : [],
      written_response,
      practice: requiredPractice,
      practice_responses: practice_responses || {},
      skipPractice: requiredPractice.length === 0
    });
    if (requiredWorkErrors.length > 0) {
      return res.status(400).json({ error: requiredWorkErrors[0], validation_errors: requiredWorkErrors });
    }

    // Auto-grade supported question types, including short written responses.
    let autoGradeableCount = 0;
    let correctCount = 0;
    let earnedUnits = 0;
    let totalUnits = 0;
    const questionResults = questions.map((q, idx) => {
      const studentAnswer = answers ? answers[idx] : null;
      const qType = String(q.type || '').toLowerCase();

      if (qType === 'multiple_choice' || qType === 'true_false') {
        autoGradeableCount++; totalUnits++;
        const isCorrect = studentAnswer && studentAnswer.toString().trim().toLowerCase() === q.correct_answer.toString().trim().toLowerCase();
        if (isCorrect) { correctCount++; earnedUnits += 1; }
        return { question_id: q.id, type: q.type, student_answer: studentAnswer, correct_answer: q.correct_answer, is_correct: isCorrect };
      } else if (qType === 'short_answer' && q.correct_answer) {
        autoGradeableCount++; totalUnits++;
        const isCorrect = studentAnswer && studentAnswer.toString().trim().toLowerCase() === q.correct_answer.toString().trim().toLowerCase();
        if (isCorrect) { correctCount++; earnedUnits += 1; }
        return { question_id: q.id, type: q.type, student_answer: studentAnswer, correct_answer: q.correct_answer, is_correct: isCorrect };
      } else if (qType === 'written_response') {
        autoGradeableCount++; totalUnits++;
        const responseText = studentAnswer || written_response || '';
        const writing = autoGradeShortWriting(responseText, q.question || assignment.instructions || '', passageText);
        earnedUnits += (writing.score / 100);
        if (writing.is_correct) correctCount++;
        return { question_id: q.id, type: q.type, student_answer: responseText, is_correct: writing.is_correct, needs_review: false, auto_score: writing.score, writing_label: writing.label, writing_feedback: writing.feedback, writing_details: writing.details, writing_next_step: writingNextStepFromResult(writing), writing_homework_recommended: writing.score < 70 || writing.details?.copied_prompt === true };
      } else {
        return { question_id: q.id, type: q.type, student_answer: studentAnswer || written_response, is_correct: null, needs_review: true };
      }
    });

    // Calculate score
    const hasManualGrading = questionResults.some(r => r.is_correct === null || r.needs_review === true);
    let percentage, pointsEarned, letterGrade, status;

    if (autoGradeableCount > 0 && totalUnits > 0) {
      percentage = parseFloat(((earnedUnits / totalUnits) * 100).toFixed(2));
      pointsEarned = Math.round((earnedUnits / totalUnits) * assignment.point_value);
      letterGrade = getLetterGrade(percentage);
      status = countsGradeStep ? (hasManualGrading ? 'partially_graded' : 'graded') : 'completed';
    } else {
      percentage = null;
      pointsEarned = null;
      letterGrade = null;
      status = countsGradeStep ? 'submitted' : 'completed';
    }

    const isPreviewSubmission = !!preview_mode || !isStudentLearningSession(req, student_id);
    if (isPreviewSubmission) {
      const passingGrade = parseFloat(assignment.passing_grade || 70);
      const isPassing = percentage === null || percentage === undefined ? null : percentage >= passingGrade;
      const writingScores = questionResults
        .filter(r => String(r.type || '').toLowerCase() === 'written_response' && r.auto_score !== undefined && r.auto_score !== null)
        .map(r => parseFloat(r.auto_score));
      const minWritingScore = writingScores.length ? Math.min(...writingScores) : null;
      const hasCopiedPrompt = questionResults.some(r => r.writing_details && r.writing_details.copied_prompt === true);
      const hasWritingFeedback = writingScores.length > 0;
      const progressOnlyWriting = hasWritingFeedback && !countsTowardGrade(step_type);
      const writingPracticeRecommended = progressOnlyWriting && (minWritingScore !== null && minWritingScore < 70 || hasCopiedPrompt);
      const extraPracticeRecommended = !countsTowardGrade(step_type) && isPassing === false;
      const finalReadiness = forceProgressComplete ? 'Completed' : readinessLabel(percentage, assignment.grade_level || 5, step_type, { minWritingScore, hasCopiedPrompt });
      return res.json({
        preview_mode: true,
        saved: false,
        status: 'preview_complete',
        progress_complete: forceProgressComplete || (!countsGradeStep && percentage === null),
        score: { correct: correctCount, total: autoGradeableCount, percentage, points_earned: pointsEarned, point_value: assignment.point_value, letter_grade: letterGrade },
        results: questionResults,
        has_manual_grading: hasManualGrading,
        step_type,
        counts_toward_grade: countsTowardGrade(step_type),
        readiness: finalReadiness,
        writing_summary: { min_writing_score: minWritingScore, has_copied_prompt: hasCopiedPrompt },
        writing_feedback_mode: progressOnlyWriting,
        writing_practice_recommended: writingPracticeRecommended,
        writing_homework_message: writingPracticeRecommended ? 'This response needs more writing practice. The student can continue, but the parent may want to assign a short writing homework practice before the next writing lesson.' : null,
        extra_practice_recommended: extraPracticeRecommended,
        is_passing: isPassing,
        retake_required: false,
        practice_again: false,
        next_action: 'Preview complete. No student record was changed.',
        recovery_message: null,
        course_completion: null,
        message: 'Preview complete. No student progress, grade work, daily hours, attendance, reports, or transcript records were saved.'
      });
    }

    // Save submission (upsert on the unique (assignment_id, student_id) constraint)
    const submissionResult = await pool.query(
      `INSERT INTO submissions (assignment_id, student_id, submission_text, status, hours_spent, time_spent_seconds, is_retake, retake_number)
       VALUES ($1, $2, $3, $4, 0, $5, $6, CASE WHEN $6 THEN 1 ELSE 0 END)
       ON CONFLICT (assignment_id, student_id) DO UPDATE SET
         submission_text = EXCLUDED.submission_text,
         status = EXCLUDED.status,
         is_retake = submissions.is_retake OR EXCLUDED.is_retake,
         retake_number = COALESCE(submissions.retake_number, 0) + CASE WHEN EXCLUDED.is_retake THEN 1 ELSE 0 END,
         time_spent_seconds = COALESCE(submissions.time_spent_seconds,0) + EXCLUDED.time_spent_seconds,
         submission_date = CURRENT_TIMESTAMP
       RETURNING id, retake_number`,
      [id, student_id, JSON.stringify({ answers, practice_responses, written_response, results: questionResults }), status, time_spent_seconds || 0, !!is_retake]
    );
    const submissionId = submissionResult.rows[0].id;

    // Record learning time, daily hours, and attendance only for a real student login.
    // Parent/admin browsing or testing should never add Daily Hours, Attendance, Analytics time, or report hours.
    if (time_spent_seconds && time_spent_seconds > 0 && isStudentLearningSession(req, student_id)) {
      try {
        await recordLearningActivity({ studentId: student_id, assignmentId: id, activeSeconds: time_spent_seconds, status: 'completed' });
      } catch (tlErr) { /* time/attendance is best-effort, never block submission */ }
    }

    // Save grade only for work that truly counts toward the grade.
    // Practice drills and study/activity steps may still be auto-scored for feedback,
    // but they must not create or keep gradebook rows that affect the main average.
    if (autoGradeableCount > 0 && percentage !== null && countsGradeStep) {
      await pool.query(
        `INSERT INTO grades (submission_id, assignment_id, student_id, points_earned, point_value, percentage, letter_grade, weight_category, feedback, graded_by)
         VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, NULL)
         ON CONFLICT (assignment_id, student_id) DO UPDATE SET
           submission_id = EXCLUDED.submission_id,
           points_earned = EXCLUDED.points_earned,
           percentage = EXCLUDED.percentage,
           letter_grade = EXCLUDED.letter_grade,
           feedback = EXCLUDED.feedback,
           graded_date = CURRENT_TIMESTAMP`,
        [submissionId, id, student_id, pointsEarned, assignment.point_value, percentage, letterGrade, assignment.weight_category,
         hasManualGrading ? 'This assignment was checked automatically where possible. Any items needing review are shown below.' : 'This assignment was checked automatically. Writing feedback is shown below where needed.']
      );
    }

    // Progress-only work should never leave an old F/0 grade behind.
    if (!countsGradeStep) {
      await pool.query('DELETE FROM grades WHERE assignment_id = $1 AND student_id = $2', [id, student_id]).catch(() => {});
    }

    // Check course completion after grading
    let courseCompletion = null;
    if (status === 'graded' || status === 'partially_graded' || status === 'completed') {
      try {
        const courseQuery = await pool.query(
          `SELECT c.id FROM assignments a JOIN lessons l ON a.lesson_id = l.id
           JOIN units u ON l.unit_id = u.id JOIN courses c ON u.course_id = c.id
           WHERE a.id = $1 AND COALESCE(a.is_active, true) = true AND COALESCE(l.is_active, true) = true AND COALESCE(u.is_active, true) = true AND COALESCE(c.is_active, true) = true`, [id]);
        if (courseQuery.rows[0]) {
          courseCompletion = await checkCourseCompletion(student_id, courseQuery.rows[0].id);
        }
      } catch (e) { /* non-fatal */ }
    }

    const passingGrade = parseFloat(assignment.passing_grade || 70);
    const isPassing = percentage === null || percentage === undefined ? null : percentage >= passingGrade;
    const writingScores = questionResults
      .filter(r => String(r.type || '').toLowerCase() === 'written_response' && r.auto_score !== undefined && r.auto_score !== null)
      .map(r => parseFloat(r.auto_score));
    const minWritingScore = writingScores.length ? Math.min(...writingScores) : null;
    const hasCopiedPrompt = questionResults.some(r => r.writing_details && r.writing_details.copied_prompt === true);
    const hasWritingFeedback = writingScores.length > 0;
    const progressOnlyWriting = hasWritingFeedback && !countsTowardGrade(step_type);
    const retakeRequired = isPassing === false && countsTowardGrade(step_type);
    const practiceAgain = false;
    const writingPracticeRecommended = progressOnlyWriting && (minWritingScore !== null && minWritingScore < 70 || hasCopiedPrompt);
    const extraPracticeRecommended = !countsTowardGrade(step_type) && isPassing === false;
    const finalReadiness = forceProgressComplete ? 'Completed' : readinessLabel(percentage, assignment.grade_level || 5, step_type, { minWritingScore, hasCopiedPrompt });
    res.json({
      status,
      progress_complete: forceProgressComplete || (!countsGradeStep && percentage === null),
      score: { correct: correctCount, total: autoGradeableCount, percentage, points_earned: pointsEarned, point_value: assignment.point_value, letter_grade: letterGrade },
      results: questionResults,
      has_manual_grading: hasManualGrading,
      step_type,
      counts_toward_grade: countsTowardGrade(step_type),
      readiness: finalReadiness,
      writing_summary: { min_writing_score: minWritingScore, has_copied_prompt: hasCopiedPrompt },
      writing_feedback_mode: progressOnlyWriting,
      writing_practice_recommended: writingPracticeRecommended,
      writing_homework_message: writingPracticeRecommended ? 'This response needs more writing practice. The student can continue, but the parent may want to assign a short writing homework practice before the next writing lesson.' : null,
      extra_practice_recommended: extraPracticeRecommended,
      is_passing: isPassing,
      retake_required: retakeRequired,
      practice_again: practiceAgain,
      next_action: retakeRequired ? 'Review the lesson, then retake this graded step.' : writingPracticeRecommended ? 'Continue to the next assignment. Extra writing practice is recommended.' : extraPracticeRecommended ? 'Continue to the next assignment. Extra practice may be helpful.' : 'Continue to the next course item.',
      recovery_message: retakeRequired ? 'This scored work is below the passing score. The student should review the lesson and retake it before continuing.' : null,
      course_completion: courseCompletion,
      message: forceProgressComplete ? 'Study step complete. Progress has been recorded.' :
               status === 'graded' ? `Score: ${percentage}% (${letterGrade})` :
               status === 'partially_graded' ? `Auto-graded score: ${percentage}% - some answers need review` :
               'Submitted for review'
    });
  } catch (error) { res.status(500).json({ error: error.message }); }
});

// Get full course with units, lessons, assignments
app.get('/api/courses/:courseId/full', authenticateToken, async (req, res) => {
  try {
    const { courseId } = req.params;
    const course = await pool.query('SELECT * FROM courses WHERE id = $1', [courseId]);
    let units = await pool.query('SELECT * FROM units WHERE course_id = $1 ORDER BY unit_number', [courseId]);
    let lessons = await pool.query(
      'SELECT l.* FROM lessons l JOIN units u ON l.unit_id = u.id WHERE u.course_id = $1 ORDER BY u.unit_number, l.lesson_number',
      [courseId]
    );
    let assignments = await pool.query(
      'SELECT a.*, l.lesson_number, u.unit_number FROM assignments a JOIN lessons l ON a.lesson_id = l.id JOIN units u ON l.unit_id = u.id WHERE u.course_id = $1 ORDER BY u.unit_number, l.lesson_number',
      [courseId]
    );

    // Repair visible Grade 5 elective/health courses on open. This fixes existing local DBs
    // that already had empty elective shells before the curriculum seed was added.
    if (course.rows[0] && (units.rows.length === 0 || assignments.rows.length === 0)) {
      try {
        const repaired = await ensureVisibleGradeFiveElectiveContentForCourse(course.rows[0]);
        if (repaired) {
          units = await pool.query('SELECT * FROM units WHERE course_id = $1 ORDER BY unit_number', [courseId]);
          lessons = await pool.query(
            'SELECT l.* FROM lessons l JOIN units u ON l.unit_id = u.id WHERE u.course_id = $1 ORDER BY u.unit_number, l.lesson_number',
            [courseId]
          );
          assignments = await pool.query(
            'SELECT a.*, l.lesson_number, u.unit_number FROM assignments a JOIN lessons l ON a.lesson_id = l.id JOIN units u ON l.unit_id = u.id WHERE u.course_id = $1 ORDER BY u.unit_number, l.lesson_number',
            [courseId]
          );
        }
      } catch (repairErr) { console.error('Course elective repair failed:', repairErr.message); }
    }
    res.json({ course: course.rows[0], units: units.rows, lessons: lessons.rows, assignments: assignments.rows });
  } catch (error) { res.status(500).json({ error: error.message }); }
});

// Enroll student in course
app.post('/api/enrollments', authenticateToken, async (req, res) => {
  try {
    const { student_id, course_id } = req.body;
    const result = await pool.query(
      'INSERT INTO enrollments (student_id, course_id) VALUES ($1, $2) ON CONFLICT (student_id, course_id) DO UPDATE SET status = $3 RETURNING *',
      [student_id, course_id, 'active']
    );
    res.status(201).json(result.rows[0]);
  } catch (error) { res.status(500).json({ error: error.message }); }
});

// =============================================
// COURSE/UNIT/LESSON NAVIGATION ROUTES
// =============================================

// Get all courses (optionally filtered by grade via query)
app.get('/api/courses', authenticateToken, async (req, res) => {
  try {
    const { grade } = req.query;
    let result;
    if (grade) {
      result = await pool.query(
        'SELECT * FROM courses WHERE grade_level = $1 AND is_active = true ORDER BY course_type, subject, name',
        [grade]
      );
      return res.json(filterEarlyElementaryCourseRows(result.rows, grade));
    } else {
      result = await pool.query('SELECT * FROM courses WHERE is_active = true ORDER BY grade_level, course_type, subject, name');
    }
    const cleanedRows = [];
    const seenEarly = new Set();
    for (const row of result.rows) {
      const g = parseInt(row.grade_level, 10);
      if ([1, 2].includes(g)) {
        const allowed = earlyElementaryCanonicalCourseNames(g);
        const name = String(row.name || '').toLowerCase();
        const key = `${g}:${name}`;
        if (!allowed.has(name) || seenEarly.has(key)) continue;
        seenEarly.add(key);
      }
      cleanedRows.push(row);
    }
    res.json(cleanedRows);
  } catch (error) { res.status(500).json({ error: error.message }); }
});

// Get units for a course, each with assignment counts
app.get('/api/courses/:courseId/units', authenticateToken, async (req, res) => {
  try {
    const { courseId } = req.params;
    const { studentId } = req.query;

    // Repair visible Grade 5 elective/health courses here, because the UI uses
    // this units endpoint when a parent clicks View Units. Earlier repairs only
    // happened on /full, so empty elective shells could still show "No units".
    const courseLookup = await pool.query('SELECT * FROM courses WHERE id = $1 LIMIT 1', [courseId]);
    if (courseLookup.rows[0]) {
      const cRow = courseLookup.rows[0];
      const gLevel = parseInt(cRow.grade_level || 0, 10);
      if (cRow.is_active === false || cRow.is_active === 'false' || ([1,2].includes(gLevel) && !earlyElementaryCanonicalCourseNames(gLevel).has(String(cRow.name || '').toLowerCase()))) {
        return res.json([]);
      }
      const quickUnits = await pool.query('SELECT COUNT(*)::int AS total FROM units WHERE course_id = $1 AND is_active = true', [courseId]);
      const quickAssignments = await pool.query(`SELECT COUNT(*)::int AS total
        FROM assignments a
        JOIN lessons l ON a.lesson_id = l.id
        JOIN units u ON l.unit_id = u.id
        WHERE u.course_id = $1 AND a.is_active = true`, [courseId]);
      const courseText = String([courseLookup.rows[0].subject, courseLookup.rows[0].name].filter(Boolean).join(' ')).toLowerCase();
      const isGradeFiveElective = parseInt(courseLookup.rows[0].grade_level || 0) === 5 && (courseText.includes('art') || courseText.includes('music') || courseText.includes('physical') || courseText.includes('pe') || courseText.includes('health') || courseText.includes('nutrition'));
      if ((quickUnits.rows[0]?.total || 0) === 0 || (quickAssignments.rows[0]?.total || 0) === 0 || (isGradeFiveElective && (quickUnits.rows[0]?.total || 0) < 3) || (isGradeFiveElective && (quickAssignments.rows[0]?.total || 0) < 12)) {
        try { await ensureVisibleGradeFiveElectiveContentForCourse(courseLookup.rows[0]); }
        catch (repairErr) { console.error('Units endpoint elective repair failed:', repairErr.message); }
      }
    }

    const units = await pool.query(
      'SELECT * FROM units WHERE course_id = $1 AND is_active = true ORDER BY unit_number',
      [courseId]
    );
    // Attach assignment counts per unit (and graded count if studentId provided)
    for (const unit of units.rows) {
      const counts = await pool.query(
        `SELECT COUNT(*)::int AS total
         FROM assignments a JOIN lessons l ON a.lesson_id = l.id
         WHERE l.unit_id = $1 AND a.is_active = true`,
        [unit.id]
      );
      unit.assignment_count = counts.rows[0].total;
      const lessonCounts = await pool.query(
        `SELECT COUNT(*)::int AS total
         FROM lessons
         WHERE unit_id = $1 AND is_active = true`,
        [unit.id]
      );
      unit.lesson_count = lessonCounts.rows[0].total;
      // Kindergarten lessons are activity-based and usually do not have rows in assignments.
      // Show lesson count as activity count so unit cards do not incorrectly show 0 activities.
      unit.activity_count = unit.assignment_count > 0 ? unit.assignment_count : unit.lesson_count;
      if (studentId) {
        const graded = await pool.query(
          `SELECT COUNT(*)::int AS graded
           FROM grades g JOIN assignments a ON g.assignment_id = a.id JOIN lessons l ON a.lesson_id = l.id
           WHERE l.unit_id = $1 AND g.student_id = $2`,
          [unit.id, studentId]
        );
        unit.graded_count = graded.rows[0].graded;
      }
    }
    res.json(units.rows);
  } catch (error) { res.status(500).json({ error: error.message }); }
});

// Get lessons for a unit, each with its assignments (and student status)
app.get('/api/units/:unitId/lessons', authenticateToken, async (req, res) => {
  try {
    const { unitId } = req.params;
    const { studentId } = req.query;
    const unitContext = await pool.query(
      `SELECT u.title as unit_title, u.unit_number, c.name as course_name, c.subject, c.grade_level, c.is_active as course_is_active
       FROM units u JOIN courses c ON u.course_id = c.id
       WHERE u.id = $1 AND COALESCE(u.is_active, true) = true AND COALESCE(c.is_active, true) = true`,
      [unitId]
    );
    const ctx = unitContext.rows[0] || {};
    const gCtx = parseInt(ctx.grade_level || 0, 10);
    if ([1,2].includes(gCtx) && !earlyElementaryCanonicalCourseNames(gCtx).has(String(ctx.course_name || '').toLowerCase())) return res.json([]);
    const lessons = await pool.query(
      'SELECT id, title, description, lesson_number, video_url, video_source, video_title, video_verified, image_url, audio_url, lesson_type, interactive_type, activity_data, instructional_content, learning_objectives, safety_notes, skill_name, word_list FROM lessons WHERE unit_id = $1 AND is_active = true ORDER BY lesson_number',
      [unitId]
    );
    let grades = [], submissions = [];
    if (studentId) {
      const g = await pool.query('SELECT assignment_id, percentage, letter_grade FROM grades WHERE student_id = $1', [studentId]);
      grades = g.rows;
      const s = await pool.query("SELECT assignment_id, status FROM submissions WHERE student_id = $1", [studentId]);
      submissions = s.rows;
    }
    for (const lesson of lessons.rows) {
      const videoRow = applyTopicVideo({ ...lesson, lesson_title: lesson.title, unit_title: ctx.unit_title, course_name: ctx.course_name, subject: ctx.subject, grade_level: ctx.grade_level });
      lesson.video_url = videoRow.video_url;
      lesson.video_source = videoRow.video_source;
      lesson.video_title = videoRow.video_title;
      lesson.video_verified = !!videoRow.video_verified;
      lesson.video_status = videoRow.video_status;
      lesson.video_notes = videoRow.video_notes;
      const a = await pool.query(
        'SELECT id, title, assignment_type, weight_category, point_value, due_date FROM assignments WHERE lesson_id = $1 AND is_active = true ORDER BY id',
        [lesson.id]
      );
      lesson.assignments = a.rows.map(assignment => {
        const assignmentWithContext = applyTopicVideo({
          ...assignment,
          lesson_title: lesson.title,
          unit_title: ctx.unit_title,
          course_name: ctx.course_name,
          subject: ctx.subject,
          grade_level: ctx.grade_level
        });
        const grade = grades.find(x => x.assignment_id === assignment.id);
        const sub = submissions.find(x => x.assignment_id === assignment.id);
        const step_type = getStepType(assignmentWithContext);
        const counts_toward_grade = countsTowardGrade(step_type);
        let status = assignmentStatusForRow({ grade, submission: sub, assignment: assignmentWithContext });
        if (status === 'not_started') status = 'pending';
        return {
          ...assignmentWithContext,
          status,
          step_type,
          counts_toward_grade,
          step_type_label: stepTypeLabel(step_type),
          percentage: grade ? parseFloat(grade.percentage) : null,
          letter_grade: grade ? grade.letter_grade : null
        };
      });
    }
    res.json(lessons.rows);
  } catch (error) { res.status(500).json({ error: error.message }); }
});

// Get assignments for a lesson
app.get('/api/lessons/:lessonId/assignments', authenticateToken, async (req, res) => {
  try {
    const { lessonId } = req.params;
    const { studentId } = req.query;
    const result = await pool.query(
      `SELECT a.*, l.title as lesson_title, l.video_url, l.video_source, l.video_verified, l.video_title,
              u.title as unit_title, u.unit_number, c.name as course_name, c.subject, c.grade_level
       FROM assignments a
       JOIN lessons l ON a.lesson_id = l.id
       JOIN units u ON l.unit_id = u.id
       JOIN courses c ON u.course_id = c.id
       WHERE a.lesson_id = $1 AND a.is_active = true AND COALESCE(c.is_active, true) = true AND COALESCE(u.is_active, true) = true AND COALESCE(l.is_active, true) = true ORDER BY a.id`,
      [lessonId]
    );
    let grades = [], submissions = [];
    if (studentId) {
      const g = await pool.query('SELECT assignment_id, percentage, letter_grade FROM grades WHERE student_id = $1', [studentId]);
      grades = g.rows;
      const s = await pool.query("SELECT assignment_id, status FROM submissions WHERE student_id = $1", [studentId]);
      submissions = s.rows;
    }
    const assignments = result.rows.map(a => {
      applyTopicVideo(a);
      normalizeAssignmentTitleAndTopic(a);
      const grade = grades.find(x => x.assignment_id === a.id);
      const sub = submissions.find(x => x.assignment_id === a.id);
      const step_type = getStepType(a);
      const counts_toward_grade = countsTowardGrade(step_type);
      let status = assignmentStatusForRow({ grade, submission: sub, assignment: a });
      if (status === 'not_started') status = 'pending';
      return { ...a, status, step_type, counts_toward_grade, step_type_label: stepTypeLabel(step_type), percentage: grade ? parseFloat(grade.percentage) : null, letter_grade: grade ? grade.letter_grade : null };
    });
    res.json(assignments);
  } catch (error) { res.status(500).json({ error: error.message }); }
});

// Get assignments grouped for a whole course (course -> unit -> lesson -> assignment)
app.get('/api/courses/:courseId/assignments', authenticateToken, async (req, res) => {
  try {
    const { courseId } = req.params;
    const { studentId } = req.query;
    const result = await pool.query(
      `SELECT a.id as assignment_id, a.title as assignment_title, a.assignment_type, a.weight_category,
              a.point_value, a.due_date,
              c.id as course_id, c.name as course_name, c.subject, c.grade_level,
              u.id as unit_id, u.unit_number, u.title as unit_title,
              l.id as lesson_id, l.lesson_number, l.title as lesson_title,
              l.video_url, l.video_source, l.video_verified, l.video_title
       FROM assignments a
       JOIN lessons l ON a.lesson_id = l.id
       JOIN units u ON l.unit_id = u.id
       JOIN courses c ON u.course_id = c.id
       WHERE c.id = $1 AND a.is_active = true AND COALESCE(c.is_active, true) = true AND COALESCE(u.is_active, true) = true AND COALESCE(l.is_active, true) = true
       ORDER BY u.unit_number, l.lesson_number, a.id`,
      [courseId]
    );
    let grades = [];
    if (studentId) {
      const g = await pool.query('SELECT assignment_id, percentage, letter_grade FROM grades WHERE student_id = $1', [studentId]);
      grades = g.rows;
    }
    const rows = result.rows.map(r => {
      applyTopicVideo(r);
      normalizeAssignmentTitleAndTopic(r);
      const grade = grades.find(x => x.assignment_id === r.assignment_id);
      const step_type = getStepType(r);
      const counts_toward_grade = countsTowardGrade(step_type);
      return { ...r, status: grade ? (counts_toward_grade && parseFloat(grade.percentage) < 70 ? 'retake_needed' : 'graded') : 'pending', step_type, counts_toward_grade, step_type_label: stepTypeLabel(step_type), percentage: grade ? parseFloat(grade.percentage) : null, letter_grade: grade ? grade.letter_grade : null };
    });
    res.json(rows);
  } catch (error) { res.status(500).json({ error: error.message }); }
});


// Assignment hub for the student's Assignments page.
// Keeps students from seeing one confusing long list and separates work by purpose.
app.get('/api/students/:studentId/assignment-hub', authenticateToken, async (req, res) => {
  try {
    const { studentId } = req.params;
    const result = await pool.query(
      `SELECT a.id as assignment_id, a.title as assignment_title, a.assignment_type, a.weight_category,
              a.point_value, a.due_date, a.passing_grade,
              c.id as course_id, c.name as course_name, c.subject, c.grade_level,
              u.id as unit_id, u.unit_number, u.title as unit_title,
              l.id as lesson_id, l.lesson_number, l.title as lesson_title,
              g.id as grade_id, g.percentage, g.letter_grade, g.points_earned, g.graded_date,
              s.id as submission_id, s.status as submission_status, s.submission_date, s.time_spent_seconds, s.is_retake, s.retake_number
       FROM enrollments e
       JOIN courses c ON e.course_id = c.id
       JOIN units u ON u.course_id = c.id AND u.is_active = true
       JOIN lessons l ON l.unit_id = u.id AND l.is_active = true
       JOIN assignments a ON a.lesson_id = l.id AND a.is_active = true
       LEFT JOIN grades g ON g.assignment_id = a.id AND g.student_id = e.student_id
       LEFT JOIN submissions s ON s.assignment_id = a.id AND s.student_id = e.student_id
       JOIN students st ON st.id = e.student_id
       WHERE e.student_id = $1 AND e.status = 'active' AND c.grade_level = st.grade_level AND COALESCE(c.is_active, true) = true
       ORDER BY c.course_type, c.subject, u.unit_number, l.lesson_number, a.id`,
      [studentId]
    );

    const sections = { continue_learning: [], needs_attention: [], in_progress: [], available: [], completed: [] };
    let firstOpenUsed = false;
    const rows = result.rows.map(r => {
      const assignment = {
        id: r.assignment_id,
        assignment_id: r.assignment_id,
        title: r.assignment_title,
        assignment_title: r.assignment_title,
        assignment_type: r.assignment_type,
        weight_category: r.weight_category,
        point_value: r.point_value,
        due_date: r.due_date,
        passing_grade: r.passing_grade,
        course_id: r.course_id,
        course_name: r.course_name,
        subject: r.subject,
        grade_level: r.grade_level,
        unit_id: r.unit_id,
        unit_number: r.unit_number,
        unit_title: r.unit_title,
        lesson_id: r.lesson_id,
        lesson_number: r.lesson_number,
        lesson_title: r.lesson_title,
        percentage: r.percentage === null ? null : parseFloat(r.percentage),
        letter_grade: r.letter_grade,
        submission_id: r.submission_id,
        submission_status: r.submission_status,
        submitted_date: r.submission_date,
        time_spent_seconds: r.time_spent_seconds || 0,
        is_retake: r.is_retake || false,
        retake_number: r.retake_number || 0,
      };
      normalizeAssignmentTitleAndTopic(assignment);
      const step_type = getStepType(assignment);
      assignment.step_type = step_type;
      assignment.counts_toward_grade = countsTowardGrade(step_type);
      assignment.step_type_label = stepTypeLabel(step_type);
      assignment.step_type_icon = stepTypeIcon(step_type);
      const status = assignmentStatusForRow({ grade: r.grade_id ? r : null, submission: r.submission_id ? { status: r.submission_status } : null, assignment });
      const isOpen = status === 'not_started';
      const section = assignmentHubSection(status, isOpen && !firstOpenUsed);
      if (isOpen && !firstOpenUsed) firstOpenUsed = true;
      assignment.status = status;
      assignment.ui_section = section;
      assignment.readiness = r.percentage !== null ? readinessLabel(r.percentage, r.grade_level, step_type) : null;
      assignment.retake_allowed = ['retake_practice_available','lesson_review_required','practice_required'].includes(status);
      if (section === 'continue') sections.continue_learning.push(assignment);
      else if (sections[section]) sections[section].push(assignment);
      return assignment;
    });

    const visibleAvailable = sections.available.slice(0, 5);
    res.json({
      all: rows,
      continue_learning: sections.continue_learning,
      needs_attention: sections.needs_attention,
      in_progress: sections.in_progress,
      available: visibleAvailable,
      available_total: sections.available.length,
      available_hidden_count: Math.max(0, sections.available.length - visibleAvailable.length),
      completed: sections.completed.slice(0, 20),
      completed_total: sections.completed.length,
      flow: ['Lesson', 'Practice', 'Review Practice', 'Quiz', 'Unit Test']
    });
  } catch (error) { res.status(500).json({ error: error.message }); }
});

// Get a student's enrolled (active) courses
app.get('/api/students/:studentId/courses', authenticateToken, async (req, res) => {
  try {
    await ensureGradeFiveSocialStudiesEnrollment();
    const result = await pool.query(
      `SELECT c.*, e.status as enrollment_status, e.enrolled_date
       FROM courses c
       JOIN enrollments e ON c.id = e.course_id
       JOIN students st ON st.id = e.student_id
       WHERE e.student_id = $1 AND e.status = 'active' AND c.grade_level = st.grade_level AND COALESCE(c.is_active, true) = true
       ORDER BY c.course_type, c.subject`,
      [req.params.studentId]
    );
    const grade = result.rows[0]?.grade_level;
    res.json(filterEarlyElementaryCourseRows(result.rows, grade));
  } catch (error) { res.status(500).json({ error: error.message }); }
});

// Assign a course to a student
app.post('/api/students/:studentId/courses/:courseId', authenticateToken, async (req, res) => {
  try {
    const { studentId, courseId } = req.params;
    const result = await pool.query(
      "INSERT INTO enrollments (student_id, course_id, status) VALUES ($1, $2, 'active') ON CONFLICT (student_id, course_id) DO UPDATE SET status = 'active' RETURNING *",
      [studentId, courseId]
    );
    res.status(201).json(result.rows[0]);
  } catch (error) { res.status(500).json({ error: error.message }); }
});

// Remove a course from a student (soft: set enrollment inactive)
app.delete('/api/students/:studentId/courses/:courseId', authenticateToken, async (req, res) => {
  try {
    const { studentId, courseId } = req.params;
    await pool.query(
      "UPDATE enrollments SET status = 'dropped' WHERE student_id = $1 AND course_id = $2",
      [studentId, courseId]
    );
    res.json({ message: 'Course removed from student' });
  } catch (error) { res.status(500).json({ error: error.message }); }
});

// Add custom course
app.post('/api/courses', authenticateToken, async (req, res) => {
  try {
    const { name, grade_level, subject, course_type, description, credit_hours } = req.body;
    const result = await pool.query(
      'INSERT INTO courses (name, grade_level, subject, course_type, description, credit_hours, created_by) VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING *',
      [name, grade_level, subject, course_type || 'elective', description, credit_hours || 1.0, req.user.id]
    );
    res.status(201).json(result.rows[0]);
  } catch (error) { res.status(500).json({ error: error.message }); }
});

// Edit course
app.put('/api/courses/:id', authenticateToken, async (req, res) => {
  try {
    const { name, description, credit_hours } = req.body;
    const result = await pool.query(
      'UPDATE courses SET name=$1, description=$2, credit_hours=$3, updated_at=CURRENT_TIMESTAMP WHERE id=$4 RETURNING *',
      [name, description, credit_hours, req.params.id]
    );
    res.json(result.rows[0]);
  } catch (error) { res.status(500).json({ error: error.message }); }
});

// =============================================
// ASSIGNMENT ROUTES
// =============================================

// Get assignments for student
app.get('/api/assignments/student/:studentId', authenticateToken, async (req, res) => {
  try {
    const { studentId } = req.params;
    const { courseId, status } = req.query;
    let query = `SELECT a.*, c.name as course_name, c.subject, c.grade_level, l.title as lesson_title,
                 l.video_url, l.video_source, l.video_verified, l.video_title,
                 u.title as unit_title, u.unit_number, l.lesson_number
                 FROM assignments a
                 JOIN lessons l ON a.lesson_id = l.id
                 JOIN units u ON l.unit_id = u.id
                 JOIN courses c ON u.course_id = c.id
                 JOIN enrollments e ON c.id = e.course_id
                 JOIN students st ON st.id = e.student_id
                 WHERE e.student_id = $1 AND e.status = 'active' AND c.grade_level = st.grade_level AND a.is_active = true`;
    const params = [studentId];
    if (courseId) { query += ' AND c.id = $' + (params.length + 1); params.push(courseId); }
    query += ' ORDER BY a.due_date, u.unit_number, l.lesson_number';
    const result = await pool.query(query, params);
    res.json(result.rows.map(row => applyTopicVideo(row)));
  } catch (error) { res.status(500).json({ error: error.message }); }
});

// Create assignment
app.post('/api/assignments', authenticateToken, async (req, res) => {
  try {
    const { lesson_id, title, description, instructions, due_date, assignment_type, point_value, weight_category, passing_grade } = req.body;
    const result = await pool.query(
      'INSERT INTO assignments (lesson_id, title, description, instructions, due_date, assignment_type, point_value, weight_category, passing_grade, created_by) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10) RETURNING *',
      [lesson_id, title, description, instructions, due_date, assignment_type || 'practice', point_value || 100, weight_category || 'homework', passing_grade || 70, req.user.id]
    );
    res.status(201).json(result.rows[0]);
  } catch (error) { res.status(500).json({ error: error.message }); }
});

// Edit assignment
app.put('/api/assignments/:id', authenticateToken, async (req, res) => {
  try {
    const { title, description, instructions, due_date, point_value, weight_category } = req.body;
    const result = await pool.query(
      'UPDATE assignments SET title=$1, description=$2, instructions=$3, due_date=$4, point_value=$5, weight_category=$6, updated_at=CURRENT_TIMESTAMP WHERE id=$7 RETURNING *',
      [title, description, instructions, due_date, point_value, weight_category, req.params.id]
    );
    res.json(result.rows[0]);
  } catch (error) { res.status(500).json({ error: error.message }); }
});

// =============================================
// SUBMISSION ROUTES
// =============================================

// Submit assignment
app.post('/api/submissions', authenticateToken, upload.single('file'), async (req, res) => {
  try {
    const { assignment_id, student_id, submission_text, hours_spent, is_retake, retake_number } = req.body;
    const file_url = req.file ? '/uploads/' + req.file.filename : null;
    const file_name = req.file ? req.file.originalname : null;
    const result = await pool.query(
      'INSERT INTO submissions (assignment_id, student_id, submission_text, submission_file_url, submission_file_name, hours_spent, is_retake, retake_number, status) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9) RETURNING *',
      [assignment_id, student_id, submission_text, file_url, file_name, hours_spent || 0, is_retake || false, retake_number || 0, 'submitted']
    );
    res.status(201).json(result.rows[0]);
  } catch (error) { res.status(500).json({ error: error.message }); }
});

// Get pending submissions
app.get('/api/submissions/pending', authenticateToken, async (req, res) => {
  try {
    const result = await pool.query(
      "SELECT s.*, a.title as assignment_title, a.point_value, st.first_name, st.last_name, st.school_id, c.name as course_name, c.subject FROM submissions s JOIN assignments a ON s.assignment_id = a.id JOIN students st ON s.student_id = st.id JOIN lessons l ON a.lesson_id = l.id JOIN units u ON l.unit_id = u.id JOIN courses c ON u.course_id = c.id WHERE s.status = 'submitted' AND st.user_id = $1 ORDER BY s.submission_date DESC",
      [req.user.id]
    );
    res.json(result.rows);
  } catch (error) { res.status(500).json({ error: error.message }); }
});

// Get ALL submitted/graded work for the parent's students (Grade Work page)
app.get('/api/gradework', authenticateToken, async (req, res) => {
  try {
    const { studentId } = req.query;
    let scopedStudentId = null;
    if (req.user.role === 'student') {
      scopedStudentId = parseInt(req.user.id, 10);
    } else if (studentId) {
      scopedStudentId = await resolveActivityStudentId(req, studentId);
    }

    const params = req.user.role === 'student'
      ? [scopedStudentId]
      : (scopedStudentId ? [req.user.id, scopedStudentId] : [req.user.id]);

    const filter = req.user.role === 'student'
      ? 's.student_id = $1'
      : (scopedStudentId ? 'st.user_id = $1 AND s.student_id = $2' : 'st.user_id = $1');

    const result = await pool.query(
      `SELECT s.id as submission_id, s.assignment_id, s.student_id, s.submission_date, s.status, s.submission_text,
              s.time_spent_seconds, s.retake_number,
              a.title as assignment_title, a.point_value, a.weight_category, a.assignment_type, a.passing_grade,
              st.first_name, st.last_name, st.school_id,
              c.name as course_name, c.subject, c.grade_level,
              g.percentage, g.letter_grade, g.points_earned, g.feedback, g.graded_date
       FROM submissions s
       JOIN assignments a ON s.assignment_id = a.id
       JOIN students st ON s.student_id = st.id
       JOIN lessons l ON a.lesson_id = l.id
       JOIN units u ON l.unit_id = u.id
       JOIN courses c ON u.course_id = c.id
       LEFT JOIN grades g ON g.assignment_id = s.assignment_id AND g.student_id = s.student_id
       WHERE ${filter}
       ORDER BY s.submission_date DESC`,
      params
    );
    const rows = result.rows.map(r => {
      let displayStatus = r.status;
      let needsReview = false;
      if (r.percentage !== null && r.status !== 'partially_graded') displayStatus = 'graded';
      if (r.status === 'submitted' && r.percentage === null) { displayStatus = 'needs_review'; needsReview = true; }
      if (r.status === 'partially_graded') { displayStatus = 'partially_graded'; needsReview = true; }
      const step_type = getStepType(r);
      const counts_toward_grade = countsTowardGrade(step_type);
      const pct = r.percentage === null ? null : parseFloat(r.percentage);
      const retake_needed = counts_toward_grade && pct !== null && pct < parseFloat(r.passing_grade || 70);
      return { ...r, display_status: displayStatus, needs_review: needsReview, step_type, step_type_label: stepTypeLabel(step_type), counts_toward_grade, progress_only: !counts_toward_grade, retake_needed };
    });
    res.json(rows);
  } catch (error) { res.status(error.message.includes('authorized') ? 403 : 500).json({ error: error.message }); }
});

// Get the full submission detail (answers + results) for review
app.get('/api/submissions/:id/detail', authenticateToken, async (req, res) => {
  try {
    const result = await pool.query(
      `SELECT s.*, a.title as assignment_title, a.questions, a.point_value, a.weight_category, a.assignment_type, a.passing_grade,
              st.first_name, st.last_name, st.school_id,
              c.name as course_name, c.subject, c.grade_level,
              u.title as unit_title, u.unit_number,
              l.title as lesson_title, l.lesson_number,
              l.reading_passage, l.source_text, l.passage_title, l.passage_type, l.source_title, l.source_type,
              g.percentage, g.letter_grade, g.points_earned, g.feedback, g.graded_date
       FROM submissions s
       JOIN assignments a ON s.assignment_id = a.id
       JOIN students st ON s.student_id = st.id
       JOIN lessons l ON a.lesson_id = l.id
       JOIN units u ON l.unit_id = u.id
       JOIN courses c ON u.course_id = c.id
       LEFT JOIN grades g ON g.assignment_id = s.assignment_id AND g.student_id = s.student_id
       WHERE s.id = $1
         AND (($3 = 'student' AND s.student_id = $2) OR st.user_id = $2)`,
      [req.params.id, req.user.id, req.user.role]
    );
    if (result.rows.length === 0) return res.status(404).json({ error: 'Submission not found' });
    const row = result.rows[0];
    let parsed = null;
    try { parsed = typeof row.submission_text === 'string' ? JSON.parse(row.submission_text) : row.submission_text; } catch { parsed = null; }
    applyTopicVideo(row);
    row.questions = improveQuestionsForContext(row);
    res.json({ ...row, parsed_submission: parsed });
  } catch (error) { res.status(500).json({ error: error.message }); }
});

// Get submission detail by assignment + student (for "View Submission" on completed work)
app.get('/api/assignments/:assignmentId/submission/:studentId', authenticateToken, async (req, res) => {
  try {
    const { assignmentId, studentId } = req.params;
    const authOk = (req.user.role === 'student' && parseInt(req.user.id) === parseInt(studentId));

    const result = await pool.query(
      `SELECT s.*, a.title as assignment_title, a.questions, a.point_value, a.weight_category,
              st.first_name, st.last_name,
              c.name as course_name, c.subject, c.grade_level,
              u.title as unit_title, u.unit_number,
              l.title as lesson_title, l.lesson_number,
              l.reading_passage, l.source_text, l.passage_title, l.passage_type, l.source_title, l.source_type,
              g.percentage, g.letter_grade, g.points_earned, g.feedback, g.graded_date
       FROM submissions s
       JOIN assignments a ON s.assignment_id = a.id
       JOIN students st ON s.student_id = st.id
       JOIN lessons l ON a.lesson_id = l.id
       JOIN units u ON l.unit_id = u.id
       JOIN courses c ON u.course_id = c.id
       LEFT JOIN grades g ON g.assignment_id = s.assignment_id AND g.student_id = s.student_id
       WHERE s.assignment_id = $1 AND s.student_id = $2
         AND (($4 = 'student' AND s.student_id = $3) OR st.user_id = $3)`,
      [assignmentId, studentId, req.user.id, req.user.role]
    );
    if (result.rows.length > 0) {
      const row = result.rows[0];
      let parsed = null;
      try { parsed = typeof row.submission_text === 'string' ? JSON.parse(row.submission_text) : row.submission_text; } catch { parsed = null; }
      applyTopicVideo(row);
    row.questions = improveQuestionsForContext(row);
      return res.json({ ...row, parsed_submission: parsed });
    }

    // Fallback for older/seeded records that have a grade but no saved submission row.
    const gradeResult = await pool.query(
      `SELECT g.id as grade_id, g.percentage, g.letter_grade, g.points_earned, g.feedback, g.graded_date,
              a.id as assignment_id, a.title as assignment_title, a.questions, a.point_value, a.weight_category, a.assignment_type, a.passing_grade,
              st.first_name, st.last_name,
              c.name as course_name, c.subject, c.grade_level,
              u.title as unit_title, u.unit_number,
              l.title as lesson_title, l.lesson_number,
              l.reading_passage, l.source_text, l.passage_title, l.passage_type, l.source_title, l.source_type
       FROM grades g
       JOIN assignments a ON g.assignment_id = a.id
       JOIN students st ON g.student_id = st.id
       JOIN lessons l ON a.lesson_id = l.id
       JOIN units u ON l.unit_id = u.id
       JOIN courses c ON u.course_id = c.id
       WHERE g.assignment_id = $1 AND g.student_id = $2
         AND (($4 = 'student' AND g.student_id = $3) OR st.user_id = $3)`,
      [assignmentId, studentId, req.user.id, req.user.role]
    );

    if (gradeResult.rows.length === 0) return res.status(404).json({ error: 'No submission found' });
    const row = gradeResult.rows[0];
    applyTopicVideo(row);
    row.questions = improveQuestionsForContext(row);
    return res.json({
      ...row,
      id: null,
      status: 'graded',
      submission_date: row.graded_date,
      submission_text: null,
      time_spent_seconds: null,
      parsed_submission: null,
      grade_only: true
    });
  } catch (error) { res.status(500).json({ error: error.message }); }
});

// =============================================
// KINDERGARTEN SKILL PROGRESS ROUTES
// =============================================

// Get all skill progress for a student
app.get('/api/students/:studentId/skills', authenticateToken, async (req, res) => {
  try {
    const result = await pool.query(
      `SELECT sp.*, c.name as course_name, c.subject, l.title as lesson_title
       FROM skill_progress sp
       LEFT JOIN courses c ON sp.course_id = c.id
       LEFT JOIN lessons l ON sp.lesson_id = l.id
       WHERE sp.student_id = $1 AND (l.id IS NULL OR l.is_active = true)
       ORDER BY c.subject, sp.updated_at DESC`,
      [req.params.studentId]
    );
    res.json(result.rows);
  } catch (error) { res.status(500).json({ error: error.message }); }
});

// Record / update a skill progress entry (used by Kindergarten activities)
app.post('/api/skills', authenticateToken, async (req, res) => {
  try {
    const { student_id, course_id, lesson_id, skill_name, status, notes, activity_completed, time_spent_seconds, preview_mode } = req.body;
    if (!student_id || !skill_name) return res.status(400).json({ error: 'student_id and skill_name required' });
    const normalizedStatus = String(status || 'introduced').toLowerCase() === 'mastered' ? 'completed' : (status || 'introduced');
    if (preview_mode || !isStudentLearningSession(req, student_id)) {
      return res.status(200).json({
        preview_mode: true,
        saved: false,
        student_id,
        course_id: course_id || null,
        lesson_id: lesson_id || null,
        skill_name: skill_name,
        status: normalizedStatus,
        activity_completed: !!activity_completed,
        time_spent_seconds: 0,
        message: 'Preview complete. Skill progress was not saved to the student record.'
      });
    }
    const studentSeconds = time_spent_seconds || 0;
    const completedAt = (normalizedStatus === 'completed' || activity_completed) ? new Date() : null;
    const result = await pool.query(
      `INSERT INTO skill_progress (student_id, course_id, lesson_id, skill_name, status, notes, activity_completed, time_spent_seconds, completed_at, updated_at)
       VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9, CURRENT_TIMESTAMP)
       ON CONFLICT (student_id, lesson_id, skill_name) DO UPDATE SET
         status = EXCLUDED.status,
         notes = COALESCE(EXCLUDED.notes, skill_progress.notes),
         activity_completed = EXCLUDED.activity_completed OR skill_progress.activity_completed,
         time_spent_seconds = skill_progress.time_spent_seconds + COALESCE(EXCLUDED.time_spent_seconds, 0),
         completed_at = COALESCE(EXCLUDED.completed_at, skill_progress.completed_at),
         updated_at = CURRENT_TIMESTAMP
       RETURNING *`,
      [student_id, course_id || null, lesson_id || null, skill_name, normalizedStatus, notes || null, activity_completed || false, studentSeconds, completedAt]
    );
    if (studentSeconds && studentSeconds > 0) {
      await recordLearningActivity({ studentId: student_id, courseId: course_id || null, lessonId: lesson_id || null, activeSeconds: studentSeconds, status: 'completed' });
    }
    res.status(201).json(result.rows[0]);
  } catch (error) { res.status(500).json({ error: error.message }); }
});

// Record learning activity and update time logs, daily logs, and attendance.
app.post('/api/learning-activity', authenticateToken, async (req, res) => {
  try {
    const { student_id, course_id, lesson_id, assignment_id, active_seconds, status } = req.body;
    if (req.user.role !== 'student') {
      return res.status(200).json({ success: true, tracked: false, reason: 'Parent/admin viewing is not counted as student learning time.' });
    }
    const scopedStudentId = await resolveActivityStudentId(req, student_id);
    await recordLearningActivity({
      studentId: scopedStudentId,
      courseId: course_id || null,
      lessonId: lesson_id || null,
      assignmentId: assignment_id || null,
      activeSeconds: active_seconds || 60,
      status: status || 'in_progress'
    });
    res.status(201).json({ success: true, tracked: true, student_id: scopedStudentId });
  } catch (error) { res.status(error.message.includes('authorized') ? 403 : 500).json({ error: error.message }); }
});

// =============================================
// TIME LOG ROUTES (assignment-level time foundation)
// =============================================

// Record a time log entry and keep daily logs / attendance in sync
app.post('/api/time-logs', authenticateToken, async (req, res) => {
  try {
    const { student_id, course_id, lesson_id, assignment_id, active_seconds, status } = req.body;
    if (req.user.role !== 'student') {
      return res.status(200).json({ success: true, tracked: false, reason: 'Parent/admin time logs are ignored for student attendance and learning hours.' });
    }
    const scopedStudentId = await resolveActivityStudentId(req, student_id);
    await recordLearningActivity({
      studentId: scopedStudentId,
      courseId: course_id || null,
      lessonId: lesson_id || null,
      assignmentId: assignment_id || null,
      activeSeconds: active_seconds || 60,
      status: status || 'completed'
    });
    res.status(201).json({ success: true, tracked: true, student_id: scopedStudentId });
  } catch (error) { res.status(error.message.includes('authorized') ? 403 : 500).json({ error: error.message }); }
});

// Get time summary for a student (today + this week + by assignment)
app.get('/api/students/:studentId/time-summary', authenticateToken, async (req, res) => {
  try {
    const { studentId } = req.params;
    const today = await pool.query(
      `SELECT COALESCE(SUM(tl.active_seconds),0)::int AS seconds
       FROM time_logs tl
       ${CURRENT_GRADE_TIME_JOIN}
       WHERE tl.student_id = $1 AND tl.log_date = CURRENT_DATE AND ${CURRENT_GRADE_TIME_FILTER}`,
      [studentId]
    );
    const week = await pool.query(
      `SELECT COALESCE(SUM(tl.active_seconds),0)::int AS seconds
       FROM time_logs tl
       ${CURRENT_GRADE_TIME_JOIN}
       WHERE tl.student_id = $1 AND tl.log_date >= CURRENT_DATE - INTERVAL '7 days' AND ${CURRENT_GRADE_TIME_FILTER}`,
      [studentId]
    );
    const byAssignment = await pool.query(
      `SELECT a.title as assignment_title, COALESCE(SUM(tl.active_seconds),0)::int AS seconds
       FROM time_logs tl
       JOIN assignments a ON tl.assignment_id = a.id
       ${CURRENT_GRADE_TIME_JOIN}
       WHERE tl.student_id = $1 AND ${CURRENT_GRADE_TIME_FILTER}
       GROUP BY a.title ORDER BY seconds DESC LIMIT 10`,
      [studentId]
    );
    res.json({
      today_seconds: today.rows[0].seconds,
      week_seconds: week.rows[0].seconds,
      by_assignment: byAssignment.rows
    });
  } catch (error) { res.status(500).json({ error: error.message }); }
});

// =============================================
// PARENT VIEW: full student summary (for Manage Students > Student)
// =============================================
app.get('/api/students/:studentId/parent-view', authenticateToken, async (req, res) => {
  try {
    const { studentId } = req.params;
    // Verify ownership
    const stRes = await pool.query('SELECT * FROM students WHERE id = $1 AND user_id = $2', [studentId, req.user.id]);
    if (stRes.rows.length === 0) return res.status(404).json({ error: 'Student not found' });
    const student = stRes.rows[0];
    const isKindergarten = parseInt(student.grade_level) === 0;

    // Enrolled courses
    const courses = await pool.query(
      `SELECT c.* FROM courses c JOIN enrollments e ON c.id = e.course_id
       WHERE e.student_id = $1 AND e.status = 'active' ORDER BY c.course_type, c.subject`,
      [studentId]
    );

    // Recent grades
    const grades = await pool.query(
      `SELECT g.percentage, g.letter_grade, g.graded_date, a.title as assignment_title, a.id as assignment_id,
              c.name as course_name, c.subject
       FROM grades g JOIN assignments a ON g.assignment_id = a.id
       JOIN lessons l ON a.lesson_id = l.id JOIN units u ON l.unit_id = u.id JOIN courses c ON u.course_id = c.id
       WHERE g.student_id = $1 ORDER BY g.graded_date DESC LIMIT 10`,
      [studentId]
    );

    // Recent submissions
    const submissions = await pool.query(
      `SELECT s.id as submission_id, s.assignment_id, s.submission_date, s.status,
              a.title as assignment_title, c.name as course_name
       FROM submissions s JOIN assignments a ON s.assignment_id = a.id
       JOIN lessons l ON a.lesson_id = l.id JOIN units u ON l.unit_id = u.id JOIN courses c ON u.course_id = c.id
       WHERE s.student_id = $1 ORDER BY s.submission_date DESC LIMIT 10`,
      [studentId]
    );

    // Status counts
    const counts = await pool.query(
      `SELECT
         COUNT(*) FILTER (WHERE s.status = 'graded' OR g.id IS NOT NULL)::int AS completed,
         COUNT(*) FILTER (WHERE s.status = 'in_progress')::int AS in_progress
       FROM submissions s LEFT JOIN grades g ON g.assignment_id = s.assignment_id AND g.student_id = s.student_id
       WHERE s.student_id = $1`,
      [studentId]
    );

    // Time summary
    const timeToday = await pool.query(
      "SELECT COALESCE(SUM(active_seconds),0)::int AS s FROM time_logs WHERE student_id = $1 AND log_date = CURRENT_DATE", [studentId]
    );
    const timeWeek = await pool.query(
      "SELECT COALESCE(SUM(active_seconds),0)::int AS s FROM time_logs WHERE student_id = $1 AND log_date >= CURRENT_DATE - INTERVAL '7 days'", [studentId]
    );

    // Kindergarten skills (only if K)
    let skills = [];
    if (isKindergarten) {
      const sk = await pool.query(
        `SELECT sp.skill_name, sp.status, sp.activity_completed, sp.updated_at,
                c.name as course_name, c.subject, l.title as lesson_title
         FROM skill_progress sp LEFT JOIN courses c ON sp.course_id = c.id
         LEFT JOIN lessons l ON sp.lesson_id = l.id
         WHERE sp.student_id = $1 AND (l.id IS NULL OR l.is_active = true)
         ORDER BY c.subject, sp.updated_at DESC`,
        [studentId]
      );
      skills = sk.rows;
    }

    res.json({
      student: { ...student, is_kindergarten: isKindergarten },
      courses: courses.rows,
      recent_grades: grades.rows,
      recent_submissions: submissions.rows,
      completed_count: counts.rows[0].completed,
      in_progress_count: counts.rows[0].in_progress,
      time_today_seconds: timeToday.rows[0].s,
      time_week_seconds: timeWeek.rows[0].s,
      skills
    });
  } catch (error) { res.status(500).json({ error: error.message }); }
});



// =============================================
// GRADING ROUTES
// =============================================

// Grade a submission
app.post('/api/grades', authenticateToken, async (req, res) => {
  try {
    const { submission_id, assignment_id, student_id, points_earned, feedback } = req.body;
    const assignment = await pool.query('SELECT point_value, weight_category, passing_grade FROM assignments WHERE id = $1', [assignment_id]);
    const pointValue = assignment.rows[0].point_value;
    const percentage = (points_earned / pointValue) * 100;
    const letterGrade = getLetterGrade(percentage);
    const weightCategory = assignment.rows[0].weight_category;
    const result = await pool.query(
      `INSERT INTO grades (submission_id, assignment_id, student_id, points_earned, point_value, percentage, letter_grade, weight_category, feedback, graded_by)
       VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10)
       ON CONFLICT (assignment_id, student_id) DO UPDATE SET
         submission_id = EXCLUDED.submission_id,
         points_earned = EXCLUDED.points_earned,
         percentage = EXCLUDED.percentage,
         letter_grade = EXCLUDED.letter_grade,
         feedback = EXCLUDED.feedback,
         graded_by = EXCLUDED.graded_by,
         graded_date = CURRENT_TIMESTAMP
       RETURNING *`,
      [submission_id, assignment_id, student_id, points_earned, pointValue, percentage.toFixed(2), letterGrade, weightCategory, feedback, req.user.id]
    );
    if (submission_id) await pool.query("UPDATE submissions SET status = 'graded' WHERE id = $1", [submission_id]);
    if (percentage < assignment.rows[0].passing_grade) {
      await pool.query(
        "INSERT INTO notifications (user_id, student_id, notification_type, title, message, priority) VALUES ($1, $2, 'failed_assignment', $3, $4, 'high')",
        [req.user.id, student_id, 'Assignment Failed - Retake Available', 'Grade: ' + letterGrade + ' (' + percentage.toFixed(1) + '%). Retake is available.']
      );
    }
    res.status(201).json(result.rows[0]);
  } catch (error) { res.status(500).json({ error: error.message }); }
});

// Get grades for student
app.get('/api/grades/student/:studentId', authenticateToken, async (req, res) => {
  try {
    const { studentId } = req.params;
    const { courseId } = req.query;
    let query = `SELECT g.*, a.title as assignment_title, a.assignment_type, a.weight_category as assignment_weight_category,
                        c.name as course_name, c.subject, c.grade_level,
                        s.submission_date, s.retake_number, s.status as submission_status, s.time_spent_seconds
                 FROM grades g
                 JOIN assignments a ON g.assignment_id = a.id
                 JOIN lessons l ON a.lesson_id = l.id
                 JOIN units u ON l.unit_id = u.id
                 JOIN courses c ON u.course_id = c.id
                 JOIN students st ON st.id = g.student_id
                 LEFT JOIN submissions s ON s.assignment_id = g.assignment_id AND s.student_id = g.student_id
                 WHERE g.student_id = $1 AND c.grade_level = st.grade_level AND c.is_active = true`;
    const params = [studentId];
    if (courseId) { query += ' AND c.id = $' + (params.length + 1); params.push(courseId); }
    query += ' ORDER BY g.graded_date DESC';
    const result = await pool.query(query, params);
    res.json(result.rows.map(r => {
      const step_type = getStepType(r);
      const counts_toward_grade = countsTowardGrade(step_type);
      return { ...r, step_type, step_type_label: stepTypeLabel(step_type), counts_toward_grade, progress_only: !counts_toward_grade };
    }));
  } catch (error) { res.status(500).json({ error: error.message }); }
});

// =============================================
// ANALYTICS ROUTES
// =============================================

// Get student analytics
app.get('/api/analytics/:studentId', authenticateToken, async (req, res) => {
  try {
    const { studentId } = req.params;
    const gpaBySubject = await pool.query(
      'SELECT c.name, c.subject, c.course_type, AVG(g.percentage) as avg_percentage, COUNT(g.id) as total_graded, COUNT(CASE WHEN g.letter_grade = $2 THEN 1 END) as failed_count FROM grades g JOIN assignments a ON g.assignment_id = a.id JOIN lessons l ON a.lesson_id = l.id JOIN units u ON l.unit_id = u.id JOIN courses c ON u.course_id = c.id JOIN students st ON st.id = g.student_id WHERE g.student_id = $1 AND c.grade_level = st.grade_level AND c.is_active = true GROUP BY c.id, c.name, c.subject, c.course_type ORDER BY c.subject',
      [studentId, 'F']
    );
    const assignmentStats = await pool.query(
      "SELECT COUNT(DISTINCT a.id) as total_assignments, COUNT(DISTINCT CASE WHEN g.id IS NOT NULL THEN a.id END) as completed, COUNT(DISTINCT CASE WHEN g.letter_grade = 'F' THEN a.id END) as failed, COUNT(DISTINCT CASE WHEN g.id IS NULL THEN a.id END) as pending FROM assignments a JOIN lessons l ON a.lesson_id = l.id JOIN units u ON l.unit_id = u.id JOIN courses c ON u.course_id = c.id JOIN enrollments e ON c.id = e.course_id JOIN students st ON st.id = e.student_id LEFT JOIN grades g ON a.id = g.assignment_id AND g.student_id = $1 WHERE e.student_id = $1 AND e.status = 'active' AND c.grade_level = st.grade_level AND a.is_active = true",
      [studentId]
    );
    const weekStart = new Date();
    weekStart.setDate(weekStart.getDate() - weekStart.getDay());
    const hoursThisWeek = await pool.query(
      `SELECT COALESCE(ROUND(SUM(tl.active_seconds)::numeric / 3600, 2), 0) as total_hours
       FROM time_logs tl
       ${CURRENT_GRADE_TIME_JOIN}
       WHERE tl.student_id = $1 AND tl.log_date >= $2 AND ${CURRENT_GRADE_TIME_FILTER}`,
      [studentId, weekStart.toISOString().split('T')[0]]
    );
    const weightedGrades = await pool.query(
      "SELECT c.name, c.subject, AVG(CASE WHEN g.weight_category = 'homework' THEN g.percentage END) as homework_avg, AVG(CASE WHEN g.weight_category = 'quiz' THEN g.percentage END) as quiz_avg, AVG(CASE WHEN g.weight_category = 'test' THEN g.percentage END) as test_avg FROM grades g JOIN assignments a ON g.assignment_id = a.id JOIN lessons l ON a.lesson_id = l.id JOIN units u ON l.unit_id = u.id JOIN courses c ON u.course_id = c.id JOIN students st ON st.id = g.student_id WHERE g.student_id = $1 AND c.grade_level = st.grade_level AND c.is_active = true GROUP BY c.id, c.name, c.subject",
      [studentId]
    );
    const attendanceSummary = await pool.query(
      `SELECT COUNT(*) as present_days, 0::int as absent_days, 0::int as late_days, COUNT(*) as total_days
       FROM (
         SELECT tl.log_date
         FROM time_logs tl
         ${CURRENT_GRADE_TIME_JOIN}
         WHERE tl.student_id = $1 AND ${CURRENT_GRADE_TIME_FILTER}
         GROUP BY tl.log_date
       ) days`,
      [studentId]
    );
    res.json({
      gpaBySubject: gpaBySubject.rows,
      assignmentStats: assignmentStats.rows[0],
      hoursThisWeek: hoursThisWeek.rows[0],
      weightedGrades: weightedGrades.rows,
      attendanceSummary: attendanceSummary.rows[0]
    });
  } catch (error) { res.status(500).json({ error: error.message }); }
});

// =============================================
// DAILY LOG & TIME TRACKING ROUTES
// =============================================

// Get recent daily logs for dashboard (no auth - bridges demo to live data)
app.get('/api/daily-logs/student/:studentId/week', async (req, res) => {
  try {
    const { studentId } = req.params;
    const today = new Date();
    const weekAgo = new Date(today);
    weekAgo.setDate(today.getDate() - 7);
    const result = await pool.query(
      `SELECT tl.log_date,
              ROUND(SUM(tl.active_seconds)::numeric / 3600, 2) as total_hours,
              ROUND(SUM(CASE WHEN LOWER(${CURRENT_GRADE_TIME_SUBJECT}) LIKE '%math%' THEN tl.active_seconds ELSE 0 END)::numeric / 3600, 2) as math_hours,
              ROUND(SUM(CASE WHEN LOWER(${CURRENT_GRADE_TIME_SUBJECT}) LIKE '%language%' OR LOWER(${CURRENT_GRADE_TIME_SUBJECT}) LIKE '%reading%' OR LOWER(${CURRENT_GRADE_TIME_SUBJECT}) LIKE '%spelling%' THEN tl.active_seconds ELSE 0 END)::numeric / 3600, 2) as ela_hours,
              ROUND(SUM(CASE WHEN LOWER(${CURRENT_GRADE_TIME_SUBJECT}) LIKE '%science%' THEN tl.active_seconds ELSE 0 END)::numeric / 3600, 2) as science_hours,
              ROUND(SUM(CASE WHEN LOWER(${CURRENT_GRADE_TIME_SUBJECT}) LIKE '%social%' OR LOWER(${CURRENT_GRADE_TIME_SUBJECT}) LIKE '%history%' THEN tl.active_seconds ELSE 0 END)::numeric / 3600, 2) as social_studies_hours,
              ROUND(SUM(CASE WHEN NOT (LOWER(${CURRENT_GRADE_TIME_SUBJECT}) LIKE '%math%' OR LOWER(${CURRENT_GRADE_TIME_SUBJECT}) LIKE '%language%' OR LOWER(${CURRENT_GRADE_TIME_SUBJECT}) LIKE '%reading%' OR LOWER(${CURRENT_GRADE_TIME_SUBJECT}) LIKE '%spelling%' OR LOWER(${CURRENT_GRADE_TIME_SUBJECT}) LIKE '%science%' OR LOWER(${CURRENT_GRADE_TIME_SUBJECT}) LIKE '%social%' OR LOWER(${CURRENT_GRADE_TIME_SUBJECT}) LIKE '%history%') THEN tl.active_seconds ELSE 0 END)::numeric / 3600, 2) as elective_hours,
              STRING_AGG(DISTINCT ${CURRENT_GRADE_TIME_SUBJECT}, ', ') as subjects_covered,
              'Auto-recorded from student learning activity' as notes,
              'school_day' as day_status
       FROM time_logs tl
       ${CURRENT_GRADE_TIME_JOIN}
       WHERE tl.student_id = $1 AND tl.log_date >= $2 AND ${CURRENT_GRADE_TIME_FILTER}
       GROUP BY tl.log_date
       ORDER BY tl.log_date DESC
       LIMIT 7`,
      [studentId, weekAgo.toISOString().split('T')[0]]
    );
    res.json(result.rows);
  } catch (error) {
    res.json([]);
  }
});

app.post('/api/daily-logs', authenticateToken, async (req, res) => {
  try {
    const { student_id, log_date, total_hours, math_hours, ela_hours, science_hours, social_studies_hours, elective_hours, subjects_covered, notes, day_status } = req.body;
    const result = await pool.query(
      'INSERT INTO daily_logs (student_id, log_date, total_hours, math_hours, ela_hours, science_hours, social_studies_hours, elective_hours, subjects_covered, notes, day_status, logged_by) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12) ON CONFLICT (student_id, log_date) DO UPDATE SET total_hours=$3, math_hours=$4, ela_hours=$5, science_hours=$6, social_studies_hours=$7, elective_hours=$8, subjects_covered=$9, notes=$10, day_status=$11, updated_at=CURRENT_TIMESTAMP RETURNING *',
      [student_id, log_date, total_hours, math_hours || 0, ela_hours || 0, science_hours || 0, social_studies_hours || 0, elective_hours || 0, subjects_covered, notes, day_status || 'school_day', req.user.id]
    );
    res.json(result.rows[0]);
  } catch (error) { res.status(500).json({ error: error.message }); }
});

app.get('/api/daily-logs/:studentId', authenticateToken, async (req, res) => {
  try {
    const { startDate, endDate } = req.query;
    const params = [req.params.studentId];
    let dateFilter = '';
    if (startDate) { params.push(startDate); dateFilter += ` AND tl.log_date >= $${params.length}`; }
    if (endDate) { params.push(endDate); dateFilter += ` AND tl.log_date <= $${params.length}`; }
    const result = await pool.query(
      `SELECT tl.log_date,
              ROUND(SUM(tl.active_seconds)::numeric / 3600, 2) as total_hours,
              ROUND(SUM(CASE WHEN LOWER(${CURRENT_GRADE_TIME_SUBJECT}) LIKE '%math%' THEN tl.active_seconds ELSE 0 END)::numeric / 3600, 2) as math_hours,
              ROUND(SUM(CASE WHEN LOWER(${CURRENT_GRADE_TIME_SUBJECT}) LIKE '%language%' OR LOWER(${CURRENT_GRADE_TIME_SUBJECT}) LIKE '%reading%' OR LOWER(${CURRENT_GRADE_TIME_SUBJECT}) LIKE '%spelling%' THEN tl.active_seconds ELSE 0 END)::numeric / 3600, 2) as ela_hours,
              ROUND(SUM(CASE WHEN LOWER(${CURRENT_GRADE_TIME_SUBJECT}) LIKE '%science%' THEN tl.active_seconds ELSE 0 END)::numeric / 3600, 2) as science_hours,
              ROUND(SUM(CASE WHEN LOWER(${CURRENT_GRADE_TIME_SUBJECT}) LIKE '%social%' OR LOWER(${CURRENT_GRADE_TIME_SUBJECT}) LIKE '%history%' THEN tl.active_seconds ELSE 0 END)::numeric / 3600, 2) as social_studies_hours,
              ROUND(SUM(CASE WHEN NOT (LOWER(${CURRENT_GRADE_TIME_SUBJECT}) LIKE '%math%' OR LOWER(${CURRENT_GRADE_TIME_SUBJECT}) LIKE '%language%' OR LOWER(${CURRENT_GRADE_TIME_SUBJECT}) LIKE '%reading%' OR LOWER(${CURRENT_GRADE_TIME_SUBJECT}) LIKE '%spelling%' OR LOWER(${CURRENT_GRADE_TIME_SUBJECT}) LIKE '%science%' OR LOWER(${CURRENT_GRADE_TIME_SUBJECT}) LIKE '%social%' OR LOWER(${CURRENT_GRADE_TIME_SUBJECT}) LIKE '%history%') THEN tl.active_seconds ELSE 0 END)::numeric / 3600, 2) as elective_hours,
              STRING_AGG(DISTINCT ${CURRENT_GRADE_TIME_SUBJECT}, ', ') as subjects_covered,
              'Auto-recorded from student learning activity' as notes,
              'school_day' as day_status
       FROM time_logs tl
       ${CURRENT_GRADE_TIME_JOIN}
       WHERE tl.student_id = $1 AND ${CURRENT_GRADE_TIME_FILTER}${dateFilter}
       GROUP BY tl.log_date
       ORDER BY tl.log_date DESC`,
      params
    );
    res.json(result.rows);
  } catch (error) { res.status(500).json({ error: error.message }); }
});

function attendanceDateKey(value) {
  if (!value) return null;
  if (value instanceof Date) return value.toISOString().slice(0, 10);
  const raw = String(value);
  return raw.includes('T') ? raw.split('T')[0] : raw.slice(0, 10);
}
function parseLocalDateOnly(value) {
  const key = attendanceDateKey(value);
  if (!key) return null;
  const [y, m, d] = key.split('-').map(n => parseInt(n, 10));
  if (!y || !m || !d) return null;
  return new Date(Date.UTC(y, m - 1, d, 12, 0, 0));
}
function addDaysUtc(date, days) {
  const d = new Date(date.getTime());
  d.setUTCDate(d.getUTCDate() + days);
  return d;
}
function isWeekendDate(date) {
  const day = date.getUTCDay();
  return day === 0 || day === 6;
}
function parseExcludedDateSet(raw) {
  return new Set(String(raw || '').split(',').map(v => v.trim()).filter(Boolean));
}
function parseBreakRanges(raw) {
  return String(raw || '').split(',').map(part => {
    const [start, end] = part.split(':').map(v => v && v.trim());
    if (!start || !end) return null;
    return { start, end };
  }).filter(Boolean);
}
function dateInBreakRanges(key, ranges) {
  return ranges.some(r => key >= r.start && key <= r.end);
}
function buildAttendanceCalendarRows(existingRows, { startDate, endDate, holidays, breaks }) {
  const byDate = new Map();
  (existingRows || []).forEach(row => {
    const key = attendanceDateKey(row.attendance_date);
    if (key) byDate.set(key, { ...row, attendance_date: key, attendance_required: row.attendance_required !== false });
  });

  const end = parseLocalDateOnly(endDate) || parseLocalDateOnly(new Date());
  let start = parseLocalDateOnly(startDate);
  if (!start) {
    const keys = [...byDate.keys()].sort();
    start = keys.length ? parseLocalDateOnly(keys[0]) : end;
  }
  if (start > end) start = end;

  const holidaySet = holidays || new Set();
  const breakRanges = breaks || [];
  const generated = [];
  for (let d = end; d >= start; d = addDaysUtc(d, -1)) {
    const key = d.toISOString().slice(0, 10);
    const existing = byDate.get(key);
    const weekend = isWeekendDate(d);
    const holiday = holidaySet.has(key);
    const onBreak = dateInBreakRanges(key, breakRanges);

    if (weekend || holiday || onBreak) {
      if (existing) {
        generated.push({
          ...existing,
          day_status: holiday ? 'holiday' : onBreak ? 'break' : 'weekend',
          attendance_required: false
        });
      }
      continue;
    }

    if (existing) {
      generated.push({ ...existing, day_status: existing.day_status || 'school_day', attendance_required: true });
    } else {
      generated.push({
        id: `auto-absent-${key}`,
        attendance_date: key,
        status: 'absent',
        session_count: 0,
        check_in_time: null,
        check_out_time: null,
        total_hours: 0,
        notes: 'Auto-generated absent for required Monday-Friday school day with no student login or learning activity.',
        day_status: 'school_day',
        attendance_required: true,
        generated: true
      });
    }
  }
  return generated;
}


async function getStudentAttendanceStartDate(studentId) {
  const r = await pool.query(`
    SELECT MIN(start_date) AS start_date
    FROM (
      SELECT MIN(login_date)::date AS start_date FROM student_sessions WHERE student_id = $1
      UNION ALL
      SELECT MIN(attendance_date)::date AS start_date FROM attendance WHERE student_id = $1 AND status IN ('present','late')
    ) starts
    WHERE start_date IS NOT NULL
  `, [studentId]).catch(() => ({ rows: [] }));
  return attendanceDateKey(r.rows[0]?.start_date);
}

// =============================================
// ATTENDANCE ROUTES
// =============================================

app.post('/api/attendance', authenticateToken, async (req, res) => {
  try {
    const { student_id, attendance_date, status, check_in_time, check_out_time, total_hours, notes } = req.body;
    const result = await pool.query(
      'INSERT INTO attendance (student_id, attendance_date, status, check_in_time, check_out_time, total_hours, notes, recorded_by) VALUES ($1,$2,$3,$4,$5,$6,$7,$8) ON CONFLICT (student_id, attendance_date) DO UPDATE SET status=$3, check_in_time=$4, check_out_time=$5, total_hours=$6, notes=$7 RETURNING *',
      [student_id, attendance_date, status, check_in_time, check_out_time, total_hours, notes, req.user.id]
    );
    res.json(result.rows[0]);
  } catch (error) { res.status(500).json({ error: error.message }); }
});

app.get('/api/attendance/:studentId', authenticateToken, async (req, res) => {
  try {
    const studentId = parseInt(req.params.studentId, 10);
    const authOk = req.user.role === 'student'
      ? parseInt(req.user.id, 10) === studentId
      : (await pool.query('SELECT id FROM students WHERE id = $1 AND user_id = $2', [studentId, req.user.id])).rows.length > 0;
    if (!authOk) return res.status(403).json({ error: 'Not authorized for this student attendance.' });

    // Attendance starts from the student's first real login/learning session.
    // Do not generate past absences before the student has actually started using the program.
    const firstStartDate = await getStudentAttendanceStartDate(studentId);
    if (!firstStartDate && !req.query.start) return res.json([]);

    const result = await pool.query(`
      SELECT a.attendance_date,
             a.status,
             a.check_in_time,
             a.check_out_time,
             COALESCE(a.total_hours, 0) AS total_hours,
             a.notes,
             COALESCE(ss.session_count, 0)::int AS session_count
      FROM attendance a
      LEFT JOIN (
        SELECT student_id, login_date, COUNT(*)::int AS session_count
        FROM student_sessions
        WHERE student_id = $1
        GROUP BY student_id, login_date
      ) ss ON ss.student_id = a.student_id AND ss.login_date = a.attendance_date
      WHERE a.student_id = $1
      ORDER BY a.attendance_date DESC
    `, [studentId]);

    const todayKey = new Date().toISOString().slice(0, 10);
    const startDate = req.query.start || firstStartDate || todayKey;
    const endDate = req.query.end || todayKey;
    const holidays = parseExcludedDateSet(req.query.holidays || req.query.closed || '');
    const breaks = parseBreakRanges(req.query.breaks || '');
    res.json(buildAttendanceCalendarRows(result.rows, { startDate, endDate, holidays, breaks }));
  } catch (error) { res.status(500).json({ error: error.message }); }
});
// =============================================
// COMMENTS ROUTES
// =============================================

app.post('/api/comments', authenticateToken, async (req, res) => {
  try {
    const { student_id, assignment_id, course_id, comment_text, comment_type, is_private } = req.body;
    const result = await pool.query(
      'INSERT INTO comments (student_id, assignment_id, course_id, user_id, comment_text, comment_type, is_private) VALUES ($1,$2,$3,$4,$5,$6,$7) RETURNING *',
      [student_id, assignment_id, course_id, req.user.id, comment_text, comment_type || 'general', is_private || false]
    );
    res.status(201).json(result.rows[0]);
  } catch (error) { res.status(500).json({ error: error.message }); }
});

app.get('/api/comments/:studentId', authenticateToken, async (req, res) => {
  try {
    const result = await pool.query('SELECT c.*, u.first_name as teacher_name FROM comments c JOIN users u ON c.user_id = u.id WHERE c.student_id = $1 ORDER BY c.created_at DESC', [req.params.studentId]);
    res.json(result.rows);
  } catch (error) { res.status(500).json({ error: error.message }); }
});

// =============================================
// CALENDAR ROUTES
// =============================================

app.get('/api/calendar', async (req, res) => {
  try {
    const { schoolYear } = req.query;
    let query = 'SELECT * FROM calendar_events';
    const params = [];
    if (schoolYear) { query += ' WHERE school_year = $1'; params.push(schoolYear); }
    query += ' ORDER BY event_date';
    const result = await pool.query(query, params);
    res.json(result.rows);
  } catch (error) { res.status(500).json({ error: error.message }); }
});

// =============================================
// NOTIFICATIONS ROUTES
// =============================================

app.get('/api/notifications', authenticateToken, async (req, res) => {
  try {
    const result = await pool.query('SELECT * FROM notifications WHERE user_id = $1 ORDER BY created_at DESC LIMIT 50', [req.user.id]);
    res.json(result.rows);
  } catch (error) { res.status(500).json({ error: error.message }); }
});

app.put('/api/notifications/:id/read', authenticateToken, async (req, res) => {
  try {
    await pool.query('UPDATE notifications SET is_read = true WHERE id = $1', [req.params.id]);
    res.json({ message: 'Notification marked as read' });
  } catch (error) { res.status(500).json({ error: error.message }); }
});

// =============================================
// COURSE PROGRESS & PACING
// =============================================

app.get('/api/students/:studentId/course-progress', authenticateToken, async (req, res) => {
  try {
    const { studentId } = req.params;

    // Get all enrolled courses with their total and completed step counts
    const result = await pool.query(
      `SELECT c.id as course_id, c.name as course_name, c.subject, c.grade_level, c.course_type,
              e.enrolled_date, e.status as enrollment_status,
              COUNT(DISTINCT a.id)::int AS total_steps,
              COUNT(DISTINCT CASE WHEN g.id IS NOT NULL OR (s.id IS NOT NULL AND s.status IN ('submitted','graded')) THEN a.id END)::int AS completed_steps
       FROM enrollments e
       JOIN courses c ON e.course_id = c.id
       LEFT JOIN units u ON u.course_id = c.id AND u.is_active = true
       LEFT JOIN lessons l ON l.unit_id = u.id AND l.is_active = true
       LEFT JOIN assignments a ON a.lesson_id = l.id AND a.is_active = true
       LEFT JOIN grades g ON g.assignment_id = a.id AND g.student_id = e.student_id
       LEFT JOIN submissions s ON s.assignment_id = a.id AND s.student_id = e.student_id
       JOIN students st ON st.id = e.student_id
       WHERE e.student_id = $1 AND e.status IN ('active', 'completed') AND c.grade_level = st.grade_level
       GROUP BY c.id, c.name, c.subject, c.grade_level, c.course_type, e.enrolled_date, e.status
       ORDER BY c.course_type, c.subject`,
      [studentId]
    );

    // Get grade averages per course (only graded work that counts toward grade)
    const gradeResult = await pool.query(
      `SELECT c.id as course_id,
              AVG(g.percentage) as grade_avg,
              COUNT(g.id)::int as graded_count
       FROM grades g
       JOIN assignments a ON g.assignment_id = a.id
       JOIN lessons l ON a.lesson_id = l.id
       JOIN units u ON l.unit_id = u.id
       JOIN courses c ON u.course_id = c.id
       JOIN students st ON st.id = g.student_id
       WHERE g.student_id = $1 AND c.grade_level = st.grade_level AND c.is_active = true
       GROUP BY c.id`,
      [studentId]
    );
    const gradeMap = {};
    gradeResult.rows.forEach(r => { gradeMap[r.course_id] = r; });

    // Get certificates
    const certResult = await pool.query(
      'SELECT * FROM certificates WHERE student_id = $1', [studentId]
    );
    const certMap = {};
    certResult.rows.forEach(r => { certMap[r.course_id] = r; });

    const today = new Date();
    const defaultTargetDate = new Date(today.getFullYear() + 1, 4, 24); // May 24 next year

    const courses = result.rows.map(r => {
      const remaining = r.total_steps - r.completed_steps;
      const pctComplete = r.total_steps > 0 ? Math.round((r.completed_steps / r.total_steps) * 100) : 0;

      const enrolledDate = r.enrolled_date ? new Date(r.enrolled_date) : today;
      const targetDate = defaultTargetDate;
      const schoolDaysElapsed = calculateSchoolDays(enrolledDate, today);
      const schoolDaysRemaining = calculateSchoolDays(today, targetDate);
      const totalSchoolDays = calculateSchoolDays(enrolledDate, targetDate);

      const stepsPerDay = schoolDaysRemaining > 0 ? Math.ceil(remaining / schoolDaysRemaining) : 0;
      const stepsPerWeek = stepsPerDay * 5;

      const pace = paceStatus(r.completed_steps, r.total_steps, schoolDaysElapsed, totalSchoolDays);

      // Catch-up pace (if behind)
      let catchUpPace = null;
      if (pace === 'Behind' || pace === 'Needs Catch-Up' || pace === 'Slightly Behind') {
        catchUpPace = schoolDaysRemaining > 0 ? Math.ceil((remaining * 1.15) / schoolDaysRemaining) : stepsPerDay;
      }

      const gradeInfo = gradeMap[r.course_id];
      const gradeAvg = gradeInfo ? parseFloat(gradeInfo.grade_avg).toFixed(1) : null;
      const gradeLetter = gradeAvg ? getLetterGrade(parseFloat(gradeAvg)) : null;

      const cert = certMap[r.course_id] || null;

      return {
        course_id: r.course_id,
        course_name: r.course_name,
        subject: r.subject,
        grade_level: r.grade_level,
        course_type: r.course_type,
        enrollment_status: r.enrollment_status,

        total_steps: r.total_steps,
        completed_steps: r.completed_steps,
        remaining_steps: remaining,
        percent_complete: pctComplete,

        grade_avg: gradeAvg,
        grade_letter: gradeLetter,
        graded_count: gradeInfo?.graded_count || 0,

        target_completion_date: targetDate.toISOString().split('T')[0],
        enrolled_date: r.enrolled_date,
        school_days_remaining: schoolDaysRemaining,
        school_days_elapsed: schoolDaysElapsed,
        steps_per_day: stepsPerDay,
        steps_per_week: stepsPerWeek,
        pace_status: pace,
        catch_up_pace: catchUpPace,

        certificate: cert ? {
          id: cert.id,
          completed_date: cert.completed_date,
          final_grade_pct: cert.final_grade_pct,
          final_grade_letter: cert.final_grade_letter,
          status: cert.status
        } : null
      };
    });

    res.json(courses);
  } catch (error) { res.status(500).json({ error: error.message }); }
});

// Get certificate data for a specific course/student
app.get('/api/certificates/:courseId/:studentId', authenticateToken, async (req, res) => {
  try {
    const { courseId, studentId } = req.params;
    const result = await pool.query(
      'SELECT * FROM certificates WHERE course_id = $1 AND student_id = $2',
      [courseId, studentId]
    );
    if (result.rows.length === 0) return res.status(404).json({ error: 'Certificate not found' });
    res.json(result.rows[0]);
  } catch (error) { res.status(500).json({ error: error.message }); }
});

// Get all certificates for a student
app.get('/api/students/:studentId/certificates', authenticateToken, async (req, res) => {
  try {
    const result = await pool.query(
      'SELECT * FROM certificates WHERE student_id = $1 ORDER BY completed_date DESC',
      [req.params.studentId]
    );
    res.json(result.rows);
  } catch (error) { res.status(500).json({ error: error.message }); }
});



// Manual Kindergarten progress reset for local testing/demo cleanup.
// Parent/admin only. This does not remove courses, lessons, units, or videos.
app.post('/api/admin/reset-kindergarten-progress', authenticateToken, async (req, res) => {
  try {
    if (!['parent','admin','teacher'].includes(req.user.role)) return res.status(403).json({ error: 'Not authorized.' });
    const result = await resetKindergartenProgressRecords();
    await pool.query('DELETE FROM maintenance_flags WHERE flag_key = $1', ['kindergarten_progress_reset_v7_category_attendance_clean_start']).catch(() => {});
    await pool.query('INSERT INTO maintenance_flags (flag_key, flag_value) VALUES ($1, $2) ON CONFLICT (flag_key) DO UPDATE SET flag_value = EXCLUDED.flag_value, created_at = CURRENT_TIMESTAMP', ['kindergarten_progress_reset_v7_category_attendance_clean_start', JSON.stringify(result)]).catch(() => {});
    res.json({ ok: true, message: 'Kindergarten progress records reset. Courses, lessons, videos, and assignments were kept.', result });
  } catch (error) { res.status(500).json({ error: error.message }); }
});

// Manual reseed endpoint for local testing when a database already had empty elective shells.
// This lets the parent refresh Art, Music, PE, and Health curriculum without rebuilding the DB.
app.post('/api/admin/seed-curriculum-foundation', authenticateToken, async (req, res) => {
  try {
    await ensureCurriculumFoundation();
    res.json({ ok: true, message: 'Curriculum foundation refreshed.' });
  } catch (error) { res.status(500).json({ error: error.message }); }
});

// =============================================
// HEALTH CHECK
// =============================================

// Install the matched shared-family routes once. Existing authentication and
// student routes above remain the source of truth for login and student setup.
const installV35BackendTestRoutes = require('./v35-backend-test-routes');
installV35BackendTestRoutes(app, pool, authenticateToken);

app.get('/api/health', (req, res) => {
  res.json({ status: 'JNM Home Education Program API running', version: '1.0.0' });
});

// Start only after required local PostgreSQL tables and cleanup are complete.
startupReady.then(() => {
  app.listen(PORT, () => {
    console.log('=================================');
    console.log('JNM Home Education Program API Server');
    console.log('Running on port ' + PORT);
    console.log('Matched family synchronization: ready');
    console.log('=================================');
  });
}).catch(error => {
  console.error('Backend startup failed before listening:', error);
  process.exitCode = 1;
});

module.exports = app;
