/* data.jsx — sample roster + derived-state helpers
   status values: 'g' | 'y' | 'r'   placement: 'tray' | 'class1' | 'class2'
*/
(function () {
  let _id = 0;
  const uid = () => 'st_' + (++_id) + '_' + Math.random().toString(36).slice(2, 6);

  function mk(name, originClass, gender, friends, math, dutch, spelling, avi, behavior, doNotPlaceWith, notes) {
    return {
      id: uid(),
      name, originClass, gender,
      friends: friends || [],
      math, dutch, spelling, avi,
      behavior,
      doNotPlaceWith: doNotPlaceWith || [],
      notes: notes || '',
      placement: 'tray',
      iac: false,
      leersteun: false,
    };
  }

  // A believable groep-3 roster from two origin classes (3A / 3B).
  // Friends reference real names so matches & lines resolve.
  const SAMPLE = [
    mk('Emma',  '3A', 'f', ['Lena', 'Sara', 'Mila'],   'g', 'g', 'y', 'E4', 'g', [],        'Helpt graag anderen.'),
    mk('Lena',  '3A', 'f', ['Emma', 'Sara', 'Tess'],   'g', 'y', 'g', 'M4', 'g', []),
    mk('Sara',  '3B', 'f', ['Emma', 'Lena', 'Noor'],   'y', 'g', 'g', 'E4', 'y', []),
    mk('Mila',  '3A', 'f', ['Emma', 'Fenna', 'Evi'],   'g', 'g', 'g', 'E5', 'g', []),
    mk('Tess',  '3B', 'f', ['Lena', 'Roos', 'Anna'],   'y', 'y', 'y', 'M4', 'y', []),
    mk('Noor',  '3B', 'f', ['Sara', 'Fenna'],          'r', 'y', 'y', 'M3', 'r', ['Tijn'], 'Heeft rustige buur nodig.'),
    mk('Fenna', '3A', 'f', ['Mila', 'Noor', 'Evi'],    'g', 'g', 'y', 'E4', 'g', []),
    mk('Anna',  '3B', 'f', ['Tess', 'Roos'],           'y', 'g', 'g', 'E4', 'y', []),
    mk('Roos',  '3A', 'f', ['Anna', 'Tess', 'Julia'],  'g', 'y', 'g', 'M4', 'g', []),
    mk('Julia', '3B', 'f', ['Roos', 'Sofie'],          'y', 'r', 'y', 'M3', 'y', []),
    mk('Sofie', '3A', 'f', ['Julia', 'Liv'],           'g', 'g', 'g', 'E5', 'g', []),
    mk('Liv',   '3B', 'f', ['Sofie', 'Evi'],           'y', 'y', 'g', 'M4', 'y', []),
    mk('Evi',   '3A', 'f', ['Mila', 'Fenna', 'Liv'],   'g', 'g', 'y', 'E4', 'g', []),
    mk('Daan',  '3A', 'm', ['Sem', 'Finn', 'Bram'],    'g', 'y', 'g', 'M4', 'g', []),
    mk('Sem',   '3B', 'm', ['Daan', 'Finn', 'Lucas'],  'y', 'y', 'y', 'M4', 'y', ['Ravi']),
    mk('Finn',  '3A', 'm', ['Daan', 'Sem', 'Tijn'],    'g', 'g', 'g', 'E5', 'g', []),
    mk('Bram',  '3B', 'm', ['Daan', 'Mees'],           'y', 'r', 'y', 'M3', 'r', [], 'Werkt het best vooraan.'),
    mk('Lucas', '3A', 'm', ['Sem', 'Jesse', 'Luuk'],   'g', 'g', 'y', 'E4', 'g', []),
    mk('Jesse', '3B', 'm', ['Lucas', 'Luuk'],          'y', 'y', 'g', 'M4', 'y', []),
    mk('Luuk',  '3A', 'm', ['Lucas', 'Jesse', 'Mees'], 'r', 'y', 'r', 'M3', 'y', []),
    mk('Mees',  '3B', 'm', ['Bram', 'Luuk', 'Noud'],   'y', 'g', 'y', 'M4', 'g', []),
    mk('Noud',  '3A', 'm', ['Mees', 'Tijn'],           'g', 'y', 'g', 'E4', 'g', []),
    mk('Tijn',  '3B', 'm', ['Finn', 'Noud'],           'y', 'y', 'y', 'M4', 'r', ['Noor'], 'Onrustig naast Noor.'),
    mk('Ravi',  '3A', 'm', ['Jesse'],                  'g', 'g', 'g', 'E5', 'y', ['Sem'], 'Nieuw op school.'),
  ];

  const DEFAULT_SETTINGS = {
    fields: {
      origin: true, gender: true, math: true, avi: true,
      dutch: true, spelling: true, behavior: true, hearts: true, notes: true,
    },
    labels: {
      math: 'Rekenen',
      dutch: 'Begrijpend lezen',
      spelling: 'Spelling',
      avi: 'AVI',
      behavior: 'Gedrag',
      origin: 'Oude klas',
    },
    classNames: { class1: 'Klas 1', class2: 'Klas 2' },
  };

  // ---- derived helpers ----
  const lc = (s) => String(s || '').trim().toLowerCase();

  function sameClassMates(student, students) {
    if (student.placement === 'tray') return [];
    return students.filter(o => o.id !== student.id && o.placement === student.placement);
  }

  // how many of a student's (≤3) chosen friends are in the same class
  function friendMatch(student, students) {
    const friends = (student.friends || []).slice(0, 3);
    if (student.placement === 'tray') return { friends, matched: [], count: 0, total: friends.length };
    const mates = new Set(sameClassMates(student, students).map(s => lc(s.name)));
    const matched = friends.filter(f => mates.has(lc(f)));
    return { friends, matched, count: matched.length, total: friends.length };
  }

  // is the student socially isolated (placed, has friends, none in class)
  function isIsolated(student, students) {
    if (student.placement === 'tray') return false;
    const fm = friendMatch(student, students);
    return fm.total > 0 && fm.count === 0;
  }

  // friend-match tier — drives the colour of the left stripe on a card
  //   g = 2+ of this kid's friends are in the same class
  //   y = exactly 1                                    r = 0
  //   n = neutral (kid in tray, or kid has no friends listed)
  function friendTier(student, students) {
    if (student.placement === 'tray') return 'n';
    const fm = friendMatch(student, students);
    if (fm.total === 0) return 'n';
    if (fm.count >= 2) return 'g';
    if (fm.count === 1) return 'y';
    return 'r';
  }

  // per-friend placement status, from this student's perspective
  //   same    = chosen friend is in the same class  (green)
  //   other   = friend is placed in the other class (muted)
  //   tray    = friend still waiting to be divided   (black)
  //   unknown = no card with that name exists         (faint)
  function friendStatuses(student, students) {
    const byName = new Map(students.map(s => [lc(s.name), s]));
    return (student.friends || []).slice(0, 3).map(f => {
      const o = byName.get(lc(f));
      if (!o) return { name: f, status: 'unknown' };
      if (o.placement === 'tray') return { name: o.name, status: 'tray' };
      if (student.placement !== 'tray' && o.placement === student.placement) return { name: o.name, status: 'same' };
      return { name: o.name, status: 'other' };
    });
  }

  // conflicting mates (do-not-place-with, either direction) in same class
  function conflictsFor(student, students) {
    if (student.placement === 'tray') return [];
    const mates = sameClassMates(student, students);
    const mine = (student.doNotPlaceWith || []).map(lc);
    return mates.filter(o =>
      mine.includes(lc(o.name)) ||
      (o.doNotPlaceWith || []).map(lc).includes(lc(student.name))
    );
  }

  // tray companion hints: when one or more placed kids are isolated (no friends
  // in their class), surface their potential matches in the tray.
  //   type 'mutual'  — both listed each other
  //   type 'oneway'  — only one direction listed
  function trayCompanionHints(students) {
    const trayList = students.filter(s => s.placement === 'tray');
    const placedIsolated = students.filter(s => s.placement !== 'tray' && isIsolated(s, students));
    const out = new Map();
    if (placedIsolated.length === 0) return out;
    const trayByName = new Map(trayList.map(t => [lc(t.name), t]));

    for (const iso of placedIsolated) {
      const isoName = lc(iso.name);
      const isoFriends = (iso.friends || []).slice(0, 3).map(lc);

      // a) tray kids the isolated kid picked
      for (const fn of isoFriends) {
        const t = trayByName.get(fn);
        if (!t) continue;
        const tFriends = (t.friends || []).slice(0, 3).map(lc);
        const mutual = tFriends.includes(isoName);
        const cur = out.get(t.id);
        const type = mutual ? 'mutual' : 'oneway';
        if (!cur || (cur.type !== 'mutual' && type === 'mutual')) {
          out.set(t.id, { type, sources: [iso.name] });
        } else if (cur && !cur.sources.includes(iso.name)) {
          cur.sources.push(iso.name);
        }
      }

      // b) tray kids who picked the isolated kid (still useful — they want him)
      for (const t of trayList) {
        if (out.get(t.id) && out.get(t.id).type === 'mutual') continue;
        const tFriends = (t.friends || []).slice(0, 3).map(lc);
        if (tFriends.includes(isoName) && !isoFriends.includes(lc(t.name))) {
          if (!out.get(t.id)) out.set(t.id, { type: 'oneway', sources: [iso.name] });
          else if (!out.get(t.id).sources.includes(iso.name)) out.get(t.id).sources.push(iso.name);
        }
      }
    }
    return out;
  }
  function classLevelGroups(students, placement, field) {
    const inClass = students.filter(s => s.placement === placement);
    const orderIdx = new Map(students.map((s, i) => [s.id, i]));
    const groups = { g: [], y: [], r: [] };
    for (const s of inClass) {
      const v = ['g', 'y', 'r'].includes(s[field]) ? s[field] : 'y';
      groups[v].push(s.id);
    }
    for (const k of Object.keys(groups)) groups[k].sort((a, b) => orderIdx.get(a) - orderIdx.get(b));
    return groups;
  }
  //   pods  = clusters of ≥2 linked kids   loners = kids with no in-class link
  function classClusters(students, placement) {
    const inClass = students.filter(s => s.placement === placement);
    const byName = new Map(inClass.map(s => [lc(s.name), s]));
    const adj = new Map(inClass.map(s => [s.id, new Set()]));
    for (const s of inClass) {
      for (const f of (s.friends || []).slice(0, 3)) {
        const o = byName.get(lc(f));
        if (o && o.id !== s.id) { adj.get(s.id).add(o.id); adj.get(o.id).add(s.id); }
      }
    }
    const orderIdx = new Map(students.map((s, i) => [s.id, i]));
    const seen = new Set();
    const comps = [];
    for (const s of inClass) {
      if (seen.has(s.id)) continue;
      const stack = [s.id], comp = [];
      seen.add(s.id);
      while (stack.length) {
        const id = stack.pop();
        comp.push(id);
        for (const n of adj.get(id)) if (!seen.has(n)) { seen.add(n); stack.push(n); }
      }
      comps.push(comp);
    }
    const pods = comps.filter(c => c.length > 1);
    const loners = comps.filter(c => c.length === 1).map(c => c[0]);
    pods.forEach(p => p.sort((a, b) => orderIdx.get(a) - orderIdx.get(b)));
    pods.sort((a, b) => (b.length - a.length) || (orderIdx.get(a[0]) - orderIdx.get(b[0])));
    loners.sort((a, b) => orderIdx.get(a) - orderIdx.get(b));
    return { pods, loners };
  }

  function friendPairs(students, placement) {
    const inClass = students.filter(s => s.placement === placement);
    const byName = new Map(inClass.map(s => [lc(s.name), s]));
    const pairs = [];
    const seen = new Set();
    for (const s of inClass) {
      for (const f of (s.friends || []).slice(0, 3)) {
        const o = byName.get(lc(f));
        if (!o || o.id === s.id) continue;
        const key = [s.id, o.id].sort().join('|');
        if (seen.has(key)) continue;
        seen.add(key);
        pairs.push({ key, a: s.id, b: o.id });
      }
    }
    return pairs;
  }

  function classSummary(students, placement) {
    const list = students.filter(s => s.placement === placement);
    const sum = { count: list.length, m: 0, f: 0, origins: {}, math: { g: 0, y: 0, r: 0 }, dutch: { g: 0, y: 0, r: 0 }, spelling: { g: 0, y: 0, r: 0 }, conflicts: 0 };
    let conflictIds = new Set();
    for (const s of list) {
      if (s.gender === 'm') sum.m++; else if (s.gender === 'f') sum.f++;
      if (s.originClass) {
        if (!sum.origins[s.originClass]) sum.origins[s.originClass] = { total: 0, m: 0, f: 0 };
        sum.origins[s.originClass].total++;
        if (s.gender === 'm') sum.origins[s.originClass].m++;
        else if (s.gender === 'f') sum.origins[s.originClass].f++;
      }
      if (sum.math[s.math] != null) sum.math[s.math]++;
      if (sum.dutch[s.dutch] != null) sum.dutch[s.dutch]++;
      if (sum.spelling[s.spelling] != null) sum.spelling[s.spelling]++;
      if (conflictsFor(s, students).length) conflictIds.add(s.id);
    }
    sum.conflicts = conflictIds.size;
    return sum;
  }

  window.AppData = {
    uid, mk, SAMPLE, DEFAULT_SETTINGS,
    friendMatch, isIsolated, conflictsFor, friendPairs, classSummary, sameClassMates, lc,
    classClusters, friendStatuses, friendTier, classLevelGroups, trayCompanionHints,
  };
})();
