/* ENSSA mock cohort with task-percentile data.
   Generated deterministically so screens stay consistent across reloads. */

/* Tiny seedable PRNG */
function rng(seed) {
  let s = seed >>> 0;
  return () => { s = (s * 1664525 + 1013904223) >>> 0; return s / 0x100000000; };
}

/* Build a synthetic per-task percentile vector for a student.
   Generates percentiles for ALL possible task IDs so any battery
   can be served without re-generating student data. */
function studentTaskPercentiles(rand, bias = 0, spread = 18) {
  const out = {};
  for (const id of Object.keys(TASK_DEFS)) {
    let p = 50 + bias + (rand() - 0.5) * spread * 2;
    p = Math.max(1, Math.min(99, Math.round(p)));
    out[id] = p;
  }
  return out;
}

/* Index score derived as weighted mean of task percentiles for a given battery. */
function indexFromTasks(tp, battery) {
  const tasks = battery || TASKS;
  const vals = tasks.map(t => tp[t.id]);
  return Math.round(vals.reduce((a, b) => a + b, 0) / vals.length);
}

/* Risk band derived from index score (P-band thresholds).
   When tasks are not all complete the band is 'more_info' (more information needed)
   — the brief promotes this from a footnote to a first-class fourth band. */
function riskFromIndex(idx, complete) {
  if (!complete) return 'more_info';
  if (idx < 20) return 'high';
  if (idx < 40) return 'moderate';
  return 'low';
}

/* Status from completion %. */
function statusFromComplete(c, total) {
  if (c === 0) return 'notstarted';
  if (c < total) return 'partial';
  return 'completed';
}

const FIRST_NAMES = ['Asha','Ben','Caitlin','Daniel','Eddie','Farah','Grace','Henry','Iris','Jonas','Kalani','Lior','Mira','Nico','Omari','Priya','Quinn','Ravi','Sienna','Theo','Uma','Vince','Willa','Xan','Yara','Zane','Aiko','Bodhi','Cleo','Dante','Esme','Finn','Gus','Hana','Indi','Jett','Kira','Leo','Maeve','Noor','Otis','Pip','Rosa','Sam','Tess','Uri','Vida','Wren','Xavi','Yusuf'];
const SURNAMES = ['Patel','Cassidy','O\u2019Hara','Wu','Mitchell','Bouzid','Lin','Ngata','Mendel','Beck','Tupou','Simmons','Halevi','Esposito','Sato','Henderson','Liang','Rowe','Park','Anand','Whittaker','Tran','Okonkwo','Petrova','Daoud','Vargas','Akiyama','Halverson','Boateng','Stein','Caruso','Adebayo','Cleary','Perez','Quinn','Halloran'];

function makeClass({ rand, name, teacher, year, cohort, n, biasShift = 0, partialN = 0, notStartedN = 0 }) {
  const battery = getBattery(year, cohort, TERM_DEFAULT);
  const totalTasks = battery.length;
  const students = [];
  const transRand = rng(name.split('').reduce((a,c) => a + c.charCodeAt(0), 42));
  for (let i = 0; i < n; i++) {
    const fn = FIRST_NAMES[Math.floor(rand() * FIRST_NAMES.length)];
    const sn = SURNAMES[Math.floor(rand() * SURNAMES.length)];
    const r = rand();
    const personalBias = biasShift + (r < 0.25 ? -25 : r > 0.75 ? 18 : 0);
    const tp = studentTaskPercentiles(rand, personalBias, 16);
    const idx = indexFromTasks(tp, battery);
    const tasksDone = i < notStartedN ? 0 : i < notStartedN + partialN ? Math.floor(rand() * (totalTasks - 1)) + 1 : totalTasks;
    const complete = tasksDone === totalTasks;
    const base = {
      id: `${name}-${i}`,
      name: `${fn} ${sn}`,
      year,
      cohort,
      tp,
      tasksDone,
      tasksTotal: totalTasks,
      indexScore: idx,
      indexInterval: [Math.max(1, idx - 4), Math.min(99, idx + 4)],
      overallPercentile: idx,
      risk: riskFromIndex(idx, complete),
      status: statusFromComplete(tasksDone, totalTasks),
      _classId: name,
    };
    students.push(addMultiTermRisks(base, transRand));
  }
  return { id: name, name, teacher, year, cohort, students };
}

const DISTRICTS = (() => {
  const rand = rng(20260420);
  // Westmore — the focal district
  const westmore = {
    id: 'westmore',
    name: 'Westmore Education Region',
    schools: [
      {
        id: 'eastvale', name: 'Eastvale Primary', leader: 'Anita Krishnan', lastAdmin: '4 May 2026',
        classes: [
          makeClass({ rand, name: 'F-A',  teacher: 'Ms Sato',     year: 'F',  cohort: 'A', n: 24, biasShift: 5  }),
          makeClass({ rand, name: 'F-B',  teacher: 'Mr Halloran', year: 'F',  cohort: 'B', n: 23, biasShift: 0  }),
          makeClass({ rand, name: '1A',   teacher: 'Ms Whittaker',year: 'Y1', cohort: 'A', n: 26, biasShift: 8  }),
          makeClass({ rand, name: '1B',   teacher: 'Mr Tran',     year: 'Y1', cohort: 'B', n: 24, biasShift: -10, partialN: 2 }),
          makeClass({ rand, name: '1C',   teacher: 'Ms Okonkwo',  year: 'Y1', cohort: 'A', n: 26, biasShift: 3  }),
        ],
      },
      {
        id: 'birchgrove', name: 'Birchgrove Public School', leader: 'Dale Robertson', lastAdmin: '6 May 2026',
        classes: [
          makeClass({ rand, name: 'F-A',  teacher: 'Ms Vargas',   year: 'F',  cohort: 'A', n: 22, biasShift: 12 }),
          makeClass({ rand, name: '1A',   teacher: 'Mr Daoud',    year: 'Y1', cohort: 'A', n: 24, biasShift: 14 }),
          makeClass({ rand, name: '1B',   teacher: 'Ms Petrova',  year: 'Y1', cohort: 'B', n: 25, biasShift: 8  }),
        ],
      },
      {
        id: 'oakridge', name: 'Oakridge Primary', leader: 'Margaret Holloway', lastAdmin: '2 May 2026',
        classes: [
          makeClass({ rand, name: 'F-A',  teacher: 'Ms Stein',    year: 'F',  cohort: 'A', n: 20, biasShift: -12, partialN: 1, notStartedN: 1 }),
          makeClass({ rand, name: '1A',   teacher: 'Mr Caruso',   year: 'Y1', cohort: 'A', n: 22, biasShift: -14, partialN: 2 }),
          makeClass({ rand, name: '1B',   teacher: 'Ms Adebayo',  year: 'Y1', cohort: 'B', n: 22, biasShift: -8  }),
        ],
      },
      {
        id: 'fairwater', name: 'Fairwater Public School', leader: 'Stephen Liu', lastAdmin: '5 May 2026',
        classes: [
          makeClass({ rand, name: 'F-A',  teacher: 'Ms Quinn',    year: 'F',  cohort: 'A', n: 28, biasShift: 6 }),
          makeClass({ rand, name: '1A',   teacher: 'Mr Akiyama',  year: 'Y1', cohort: 'A', n: 27, biasShift: 4 }),
          makeClass({ rand, name: '1B',   teacher: 'Ms Halverson',year: 'Y1', cohort: 'B', n: 28, biasShift: 7 }),
          makeClass({ rand, name: '1C',   teacher: 'Mr Boateng',  year: 'Y1', cohort: 'A', n: 27, biasShift: 5 }),
        ],
      },
      {
        id: 'mirralong', name: 'Mirralong Primary', leader: 'Eve Tannous', lastAdmin: '3 May 2026',
        classes: [
          makeClass({ rand, name: 'F-A',  teacher: 'Ms Cleary',   year: 'F',  cohort: 'B', n: 26, biasShift: -4 }),
          makeClass({ rand, name: '1A',   teacher: 'Mr Perez',    year: 'Y1', cohort: 'A', n: 26, biasShift: 0 }),
        ],
      },
    ],
  };
  // Three more districts with varied profiles for the Portal Home cross-district view.
  const more = ['Northrun','Southshore','Greenford'].map((nm, i) => {
    const rd = rng(20260420 + (i+1)*9001);
    return {
      id: nm.toLowerCase(),
      name: `${nm} Education Region`,
      schools: Array.from({ length: 3 + i }, (_, j) => ({
        id: `${nm.toLowerCase()}-s${j}`,
        name: `${nm} Public School ${j+1}`,
        leader: `${SURNAMES[Math.floor(rd()*SURNAMES.length)]}`,
        lastAdmin: `${1 + Math.floor(rd()*6)} May 2026`,
        classes: [
          makeClass({ rand: rd, name: 'F-A', teacher: '—', year: 'F',  cohort: 'A', n: 22 + Math.floor(rd()*6), biasShift: -8 + i*4 }),
          makeClass({ rand: rd, name: '1A',  teacher: '—', year: 'Y1', cohort: 'A', n: 24 + Math.floor(rd()*4), biasShift: -6 + i*4 }),
          makeClass({ rand: rd, name: '1B',  teacher: '—', year: 'Y1', cohort: 'B', n: 24 + Math.floor(rd()*4), biasShift: -4 + i*4 }),
        ],
      })),
    };
  });
  // Stamp school/class context onto each student for drill-down
  for (const d of [westmore, ...more]) {
    for (const s of d.schools) {
      for (const c of s.classes) {
        for (const st of c.students) {
          st._schoolId = s.id;
          st._schoolName = s.name;
          st._districtId = d.id;
        }
      }
    }
  }
  return [westmore, ...more];
})();

/* Aggregation helpers */
function allStudents(node) {
  if (node.students) return node.students;
  if (node.classes) return node.classes.flatMap(c => c.students);
  if (node.schools) return node.schools.flatMap(s => s.classes.flatMap(c => c.students));
  return [];
}

function riskCountsOf(node, yearFilter) {
  const studs = allStudents(node).filter(s => !yearFilter || s.year === yearFilter);
  const out = { low: 0, moderate: 0, high: 0, more_info: 0, total: studs.length };
  for (const s of studs) out[s.risk]++;
  return out;
}

function statusCountsOf(node, yearFilter) {
  const studs = allStudents(node).filter(s => !yearFilter || s.year === yearFilter);
  const out = { completed: 0, partial: 0, notstarted: 0, total: studs.length };
  for (const s of studs) out[s.status]++;
  return out;
}

/* Box-plot stats per task across a node's students.
   When a yearFilter is active, only matching students are included.
   Stats are computed per task ID present in the relevant batteries. */
function taskBoxStats(node, yearFilter) {
  const studs = allStudents(node).filter(s => !yearFilter || s.year === yearFilter);
  // Collect all task IDs present across the relevant batteries
  const taskIdSet = new Set();
  for (const s of studs) {
    const battery = getBattery(s.year, s.cohort || 'A', TERM_DEFAULT);
    battery.forEach(t => taskIdSet.add(t.id));
  }
  const stats = {};
  for (const tid of taskIdSet) {
    const taskDef = TASK_DEFS[tid];
    if (!taskDef) continue;
    const vals = studs
      .filter(s => {
        const battery = getBattery(s.year, s.cohort || 'A', TERM_DEFAULT);
        return s.tasksDone === s.tasksTotal && battery.some(t => t.id === tid);
      })
      .map(s => s.tp[tid])
      .sort((a, b) => a - b);
    const insuff = studs.filter(s => {
      const battery = getBattery(s.year, s.cohort || 'A', TERM_DEFAULT);
      return battery.some(t => t.id === tid) && s.tasksDone < s.tasksTotal;
    }).length;
    if (!vals.length) {
      stats[tid] = { ...taskDef, p10: 0, p25: 0, p50: 0, p75: 0, p90: 0, n: 0, insufficient: insuff, below20: 0 };
      continue;
    }
    const q = (p) => vals[Math.max(0, Math.min(vals.length-1, Math.floor(p/100 * vals.length)))];
    stats[tid] = {
      ...taskDef,
      p10: q(10), p25: q(25), p50: q(50), p75: q(75), p90: q(90),
      n: vals.length,
      insufficient: insuff,
      below20: vals.filter(v => v < 20).length,
    };
  }
  return stats;
}

/* Synthetic risk-band Sankey across two adjacent terms. */
function sankeyTransitions(node, yearFilter) {
  const studs = allStudents(node).filter(s => !yearFilter || s.year === yearFilter);
  const bands = ['low','moderate','high','more_info'];
  const flows = {};
  const r = rng(99);
  for (const s of studs) {
    let prev = s.risk;
    const x = r();
    if (x < 0.18) {
      const i = bands.indexOf(s.risk);
      prev = bands[Math.max(0, i-1)];
    } else if (x < 0.32) {
      const i = bands.indexOf(s.risk);
      prev = bands[Math.min(2, i+1)];
    }
    const k = `${prev}>${s.risk}`;
    flows[k] = (flows[k] || 0) + 1;
  }
  return flows;
}

/* ── Multi-stage transition data ──────────────────────────────
   Each student gets t1Risk / t3Risk / t4Risk fields.
   T1 = current term result (risk).
   T3 / T4 are synthetic previous terms derived deterministically.
   'unknown' means screened that term but no classification (incomplete).
   null means not screened that term.
   ─────────────────────────────────────────────────────────── */
function addMultiTermRisks(student, rand) {
  // T1 = current risk
  const t1 = student.risk === 'more_info' ? 'unknown' : student.risk;
  // T3: perturb. ~65% stay same band, ~20% shift one band, ~10% improve, ~5% unknown
  const r3 = rand();
  let t3;
  if (r3 < 0.05) { t3 = 'unknown'; }
  else if (r3 < 0.20) { t3 = shiftRisk(t1, -1); } // worse
  else if (r3 < 0.35) { t3 = shiftRisk(t1, +1); } // better
  else { t3 = t1; }
  // T4: from T3. Similarly sticky, with slight improvement trend
  const r4 = rand();
  let t4;
  if (r4 < 0.06) { t4 = 'unknown'; }
  else if (r4 < 0.18) { t4 = shiftRisk(t3 === 'unknown' ? t1 : t3, -1); }
  else if (r4 < 0.38) { t4 = shiftRisk(t3 === 'unknown' ? t1 : t3, +1); }
  else { t4 = t3 === 'unknown' ? t1 : t3; }
  return { ...student, t1Risk: t1, t3Risk: t3, t4Risk: t4 };
}

function shiftRisk(risk, dir) {
  // dir: +1 = improve (toward low), -1 = worsen (toward high)
  const order = ['high', 'moderate', 'low'];
  const i = order.indexOf(risk);
  if (i === -1) return risk; // unknown/null
  return order[Math.max(0, Math.min(2, i + dir))];
}

/* Build transition rows for Sankey from a set of students with school/class context. */
function getMultiStageTransitions(node, yearFilter) {
  const studs = allStudents(node).filter(s => !yearFilter || s.year === yearFilter);
  return studs
    .filter(s => s.t1Risk && s.t1Risk !== 'unknown')
    .map(s => ({
      studentId:   s.id,
      studentName: s.name,
      year:        s.year,
      t1Risk:      s.t1Risk,
      t3Risk:      s.t3Risk,
      t4Risk:      s.t4Risk,
      schoolId:    s._schoolId,
      schoolName:  s._schoolName,
      classId:     s._classId,
      risk:        s.risk,
      indexScore:  s.indexScore,
      overallPercentile: s.overallPercentile,
    }));
}

Object.assign(window, {
  DISTRICTS, allStudents, riskCountsOf, statusCountsOf, taskBoxStats,
  sankeyTransitions, getMultiStageTransitions, riskFromIndex,
});
