/* app.jsx — main application: state, drag & drop, action bar, IO */
(function () {
  const { useState, useRef, useEffect, useCallback } = React;
  const LucideIcon = window.LucideIcon;
  const Card = window.Card;
  const ClassColumn = window.ClassColumn;
  const Tray = window.Tray;
  const FriendLines = window.FriendLines;
  const CardEditor = window.CardEditor;
  const SettingsOverlay = window.SettingsOverlay;
  const RosterList = window.RosterList;
  const Tutorial = window.Tutorial;
  const ShareModal = window.ShareModal;
  const D = window.AppData;

  const LS_KEY = 'klasverdeling_v1';
  const TUT_KEY = 'klasverdeling_tut_seen';

  // shared-session payload (from a #m=…&d=… link) wins over localStorage at boot
  const shared = (window.SharedSession && window.SharedSession.readShared && window.SharedSession.readShared()) || null;

  function loadState() {
    try {
      const raw = localStorage.getItem(LS_KEY);
      if (raw) {
        const obj = JSON.parse(raw);
        if (obj && Array.isArray(obj.students)) return obj;
      }
    } catch (e) {}
    return { students: D.SAMPLE, settings: D.DEFAULT_SETTINGS };
  }

  function App() {
    const init = shared ? { students: shared.data.students, settings: shared.data.settings || {} } : loadState();
    const isView = shared && shared.mode === 'view';

    const [students, setStudents] = useState(init.students);
    const [viewOnly, setViewOnly] = useState(!!isView);
    const [settings, setSettings] = useState(() => ({
      ...D.DEFAULT_SETTINGS, ...init.settings,
      fields: { ...D.DEFAULT_SETTINGS.fields, ...(init.settings && init.settings.fields) },
      labels: { ...D.DEFAULT_SETTINGS.labels, ...(init.settings && init.settings.labels) },
      classNames: { ...D.DEFAULT_SETTINGS.classNames, ...(init.settings && init.settings.classNames) },
      linesVisible: (init.settings && init.settings.linesVisible !== undefined) ? init.settings.linesVisible : true,
      compactCards: (init.settings && init.settings.compactCards) || false,
      sort: (init.settings && init.settings.sort) || { field: null, filter: null },
    }));

    const setSortField = (field) => setSettings(s => {
      const cur = s.sort || {};
      if (cur.field === field) return { ...s, sort: { field: null, filter: null } };
      return { ...s, sort: { field, filter: null } };
    });
    const setSortFilter = (field, value) => setSettings(s => {
      const cur = s.sort || {};
      if (cur.field !== field) return { ...s, sort: { field, filter: value } };
      if (cur.filter === value) return { ...s, sort: { field, filter: null } };
      return { ...s, sort: { field, filter: value } };
    });
    const [selectedId, setSelectedId] = useState(null);
    const [editingId, setEditingId] = useState(null);
    const [settingsOpen, setSettingsOpen] = useState(false);
    const [ghost, setGhost] = useState(null);
    const [dropHot, setDropHot] = useState(null);
    const [justPlacedId, setJustPlacedId] = useState(null);
    const [actionBar, setActionBar] = useState(null);
    const [version, setVersion] = useState(0);
    const [toast, setToast] = useState(null);
    const [rosterOpen, setRosterOpen] = useState(false);
    const [tutorialOpen, setTutorialOpen] = useState(() => {
      if (isView) return false; // visitors don't need the host's onboarding
      try { return !localStorage.getItem(TUT_KEY); } catch (e) { return true; }
    });
    const [shareOpen, setShareOpen] = useState(false);
    const closeTutorial = () => {
      setTutorialOpen(false);
      try { localStorage.setItem(TUT_KEY, '1'); } catch (e) {}
    };

    const boardRef = useRef(null);
    const cardEls = useRef(new Map());
    const zoneEls = useRef(new Map());
    const drag = useRef(null);
    const tapInfo = useRef({ id: null, time: 0 });
    const xlsxInput = useRef(null);
    const jsonInput = useRef(null);

    const bump = useCallback(() => setVersion(v => v + 1), []);

    // persist (but only when this isn't a read-only share — we don't want
    // to clobber the viewer's own session)
    useEffect(() => {
      if (viewOnly) return;
      try { localStorage.setItem(LS_KEY, JSON.stringify({ students, settings })); } catch (e) {}
    }, [students, settings, viewOnly]);

    // strip the share fragment after loading so a refresh starts clean
    useEffect(() => {
      if (shared && window.SharedSession && window.SharedSession.clearShared) {
        window.SharedSession.clearShared();
      }
    }, []);

    // toggle global body class for view-only styling
    useEffect(() => {
      document.body.classList.toggle('view-only', viewOnly);
      return () => document.body.classList.remove('view-only');
    }, [viewOnly]);

    // recompute lines on resize + scroll
    useEffect(() => {
      const onResize = () => bump();
      window.addEventListener('resize', onResize);
      return () => window.removeEventListener('resize', onResize);
    }, [bump]);

    const showToast = (msg, err) => { setToast({ msg, err }); setTimeout(() => setToast(null), 2600); };

    const registerZone = (which, el) => {
      if (el) zoneEls.current.set(which, el); else zoneEls.current.delete(which);
    };

    // ---------- placement ----------
    const placeStudent = useCallback((id, target) => {
      setStudents(prev => prev.map(s => s.id === id ? { ...s, placement: target } : s));
      if (target !== 'tray') { setJustPlacedId(id); setTimeout(() => setJustPlacedId(null), 420); }
      setActionBar(null);
      setSelectedId(null);
      setTimeout(bump, 30);
    }, [bump]);

    // ---------- drag system (pointer = mouse + touch) ----------
    const zoneAt = (x, y) => {
      const el = document.elementFromPoint(x, y);
      if (!el) return null;
      const z = el.closest('[data-dropzone]');
      return z ? z.dataset.dropzone : null;
    };

    const onMove = useCallback((e) => {
      const dg = drag.current;
      if (!dg) return;
      const x = e.clientX, y = e.clientY;
      const dist = Math.hypot(x - dg.startX, y - dg.startY);
      if (!dg.moved && dist > 9) {
        dg.moved = true;
        const el = cardEls.current.get(dg.id);
        if (el) el.style.opacity = '0.28';
        setGhost({ student: dg.student, w: dg.w, x: x - dg.offX, y: y - dg.offY });
        setActionBar(null);
      }
      if (dg.moved) {
        e.preventDefault();
        setGhost(g => g ? { ...g, x: x - dg.offX, y: y - dg.offY } : g);
        const z = zoneAt(x, y);
        if (z !== dg.lastZone) { dg.lastZone = z; setDropHot(z); }
      }
    }, []);

    const onUp = useCallback((e) => {
      const dg = drag.current;
      window.removeEventListener('pointermove', onMove);
      window.removeEventListener('pointerup', onUp);
      window.removeEventListener('pointercancel', onUp);
      if (!dg) return;
      drag.current = null;
      const el = cardEls.current.get(dg.id);
      if (el) el.style.opacity = '';

      if (dg.moved) {
        setGhost(null);
        setDropHot(null);
        const z = zoneAt(e.clientX, e.clientY);
        if (z && z !== dg.student.placement) placeStudent(dg.id, z);
        return;
      }
      // ---- it was a tap ----
      const now = Date.now();
      const isDouble = tapInfo.current.id === dg.id && (now - tapInfo.current.time) < 380;
      tapInfo.current = { id: dg.id, time: now };
      if (isDouble) {
        setActionBar(null);
        setEditingId(dg.id);
        return;
      }
      // single tap: select + show action bar
      setSelectedId(dg.id);
      const r = el ? el.getBoundingClientRect() : null;
      const b = boardRef.current.getBoundingClientRect();
      if (r) {
        const below = (r.top - b.top) < 80;
        setActionBar({
          id: dg.id,
          x: r.left - b.left + r.width / 2,
          y: below ? (r.bottom - b.top + 14) : (r.top - b.top - 10),
          below,
        });
      }
    }, [onMove, placeStudent]);

    const onCardPointerDown = useCallback((e, student) => {
      if (viewOnly) return;
      if (e.button != null && e.button !== 0) return;
      const el = cardEls.current.get(student.id);
      const r = el.getBoundingClientRect();
      drag.current = {
        id: student.id, student,
        startX: e.clientX, startY: e.clientY,
        offX: e.clientX - r.left, offY: e.clientY - r.top,
        w: r.width, moved: false, lastZone: null,
      };
      window.addEventListener('pointermove', onMove, { passive: false });
      window.addEventListener('pointerup', onUp);
      window.addEventListener('pointercancel', onUp);
    }, [onMove, onUp]);

    // tap empty space closes action bar / deselect
    const onBoardPointerDown = (e) => {
      if (e.target.closest('.card') || e.target.closest('.action-bar') || e.target.closest('.tray-scroll-btn') || e.target.closest('.col-name') || e.target.closest('.icon-btn')) return;
      setActionBar(null);
      setSelectedId(null);
    };

    // ---------- editor ----------
    const saveStudent = (next) => {
      setStudents(prev => prev.map(s => s.id === next.id ? next : s));
      setEditingId(null);
      setTimeout(bump, 30);
    };
    const deleteStudent = (id) => {
      setStudents(prev => prev.filter(s => s.id !== id));
      setEditingId(null);
      setActionBar(null);
      setTimeout(bump, 30);
    };
    const addStudent = () => {
      const s = D.mk('Nieuwe leerling', '', 'f', [], 'y', 'y', 'y', '', 'y', [], '');
      setStudents(prev => [...prev, s]);
      setEditingId(s.id);
    };
    // inline patch (used by the roster list)
    const updateStudent = (id, patch) => {
      setStudents(prev => prev.map(s => s.id === id ? Object.assign({}, s, patch) : s));
      setTimeout(bump, 30);
    };
    const removeStudent = (id) => {
      setStudents(prev => prev.filter(s => s.id !== id));
      setTimeout(bump, 30);
    };
    const addBlank = () => {
      const s = D.mk('Nieuwe leerling', '', '', [], 'y', 'y', 'y', '', 'y', [], '');
      setStudents(prev => [s, ...prev]);
    };

    const renameClass = (which, value) => setSettings(s => ({ ...s, classNames: { ...s.classNames, [which]: value } }));

    // ---------- click-to-place: tap a column bg with a card selected ----------
    const onColumnClick = (which, e) => {
      if (e.target.closest('.card') || e.target.closest('.col-name')) return;
      if (selectedId) {
        const s = students.find(x => x.id === selectedId);
        if (s && s.placement !== which) placeStudent(selectedId, which);
      }
    };

    // ---------- IO ----------
    const downloadBlob = (blob, name) => {
      const url = URL.createObjectURL(blob);
      const a = document.createElement('a');
      a.href = url; a.download = name; a.click();
      setTimeout(() => URL.revokeObjectURL(url), 1500);
    };

    const exportJson = () => {
      const data = { app: 'klasverdeling', version: 1, exportedAt: new Date().toISOString(), settings, students };
      downloadBlob(new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' }), 'klasverdeling-sessie.json');
      showToast('Sessie geëxporteerd');
    };

    const importJson = () => jsonInput.current && jsonInput.current.click();
    const onJsonFile = (e) => {
      const file = e.target.files[0]; e.target.value = '';
      if (!file) return;
      const fr = new FileReader();
      fr.onload = () => {
        try {
          const obj = JSON.parse(fr.result);
          if (!Array.isArray(obj.students)) throw new Error('bad');
          setStudents(obj.students);
          if (obj.settings) setSettings(s => ({ ...s, ...obj.settings, fields: { ...s.fields, ...obj.settings.fields }, labels: { ...s.labels, ...obj.settings.labels }, classNames: { ...s.classNames, ...obj.settings.classNames } }));
          setSettingsOpen(false);
          setTimeout(bump, 40);
          showToast('Sessie geladen');
        } catch (err) { showToast('Kon JSON niet lezen', true); }
      };
      fr.readAsText(file);
    };

    const importXlsx = () => xlsxInput.current && xlsxInput.current.click();
    const onXlsxFile = (e) => {
      const file = e.target.files[0]; e.target.value = '';
      if (!file) return;
      const fr = new FileReader();
      fr.onload = () => {
        try {
          const wb = XLSX.read(new Uint8Array(fr.result), { type: 'array' });
          const ws = wb.Sheets[wb.SheetNames[0]];
          const rows = XLSX.utils.sheet_to_json(ws, { header: 1, blankrows: false });
          const list = [];
          rows.forEach(row => {
            const nm = String(row[0] == null ? '' : row[0]).trim();
            if (!nm) return;
            const friends = String(row[1] == null ? '' : row[1])
              .split(/[;,]/).map(x => x.trim()).filter(Boolean).slice(0, 3);
            list.push(D.mk(nm, '', '', friends, 'y', 'y', 'y', '', 'y', [], ''));
          });
          if (!list.length) throw new Error('empty');
          setStudents(list);
          setSettingsOpen(false);
          setTimeout(bump, 40);
          showToast(list.length + ' leerlingen geïmporteerd');
        } catch (err) { showToast('Kon Excel niet lezen', true); }
      };
      fr.readAsArrayBuffer(file);
    };

    const exportXlsx = () => {
      const cn = settings.classNames;
      const L = settings.labels;
      const nameOf = (p) => p === 'class1' ? cn.class1 : p === 'class2' ? cn.class2 : 'Nog te verdelen';
      const order = { class1: 0, class2: 1, tray: 2 };
      const rows = [...students]
        .sort((a, b) => (order[a.placement] - order[b.placement]) || a.name.localeCompare(b.name))
        .map(s => ({
          Naam: s.name,
          [L.origin]: s.originClass,
          Geslacht: s.gender === 'm' ? 'Jongen' : s.gender === 'f' ? 'Meisje' : '',
          'Nieuwe klas': nameOf(s.placement),
          [L.math]: s.math, [L.dutch]: s.dutch, [L.spelling]: s.spelling,
          [L.avi]: s.avi, [L.behavior]: s.behavior,
          Vriendjes: (s.friends || []).join('; '),
          'Niet met': (s.doNotPlaceWith || []).join('; '),
          Notities: s.notes || '',
        }));
      const ws = XLSX.utils.json_to_sheet(rows);
      ws['!cols'] = Object.keys(rows[0] || { a: 1 }).map(() => ({ wch: 16 }));
      const wb = XLSX.utils.book_new();
      XLSX.utils.book_append_sheet(wb, ws, 'Verdeling');
      XLSX.writeFile(wb, 'klasverdeling-resultaat.xlsx');
      showToast('Verdeling geëxporteerd');
    };

    const resetPlacements = () => {
      setStudents(prev => prev.map(s => ({ ...s, placement: 'tray' })));
      setTimeout(bump, 30);
      showToast('Alle kaarten terug in de bak');
    };
    const clearAll = () => {
      if (!window.confirm('Alle gegevens wissen? Dit kan niet ongedaan worden gemaakt.')) return;
      setStudents([]);
      setSettingsOpen(false);
      setTimeout(bump, 30);
      showToast('Alle gegevens gewist');
    };

    const placedCount = students.filter(s => s.placement !== 'tray').length;

    // ---------- action bar ----------
    let actionBarEl = null;
    if (actionBar) {
      const s = students.find(x => x.id === actionBar.id);
      if (s) {
        const cn = settings.classNames;
        const btns = [];
        if (s.placement === 'tray') {
          btns.push(['→ ' + cn.class1, () => placeStudent(s.id, 'class1'), null]);
          btns.push(['→ ' + cn.class2, () => placeStudent(s.id, 'class2'), null]);
        } else {
          const other = s.placement === 'class1' ? 'class2' : 'class1';
          btns.push(['→ ' + (other === 'class1' ? cn.class1 : cn.class2), () => placeStudent(s.id, other), 'ArrowRightLeft']);
          btns.push(['Terug', () => placeStudent(s.id, 'tray'), 'Undo2', 'warn']);
        }
        actionBarEl = React.createElement('div', {
          className: 'action-bar',
          style: { left: actionBar.x, top: actionBar.y, transform: actionBar.below ? 'translate(-50%,0)' : 'translate(-50%,-100%)' },
        },
          btns.map((b, i) => React.createElement('button', { key: i, className: b[3] || '', onClick: b[1] },
            b[2] ? React.createElement(LucideIcon, { name: b[2], size: 16, strokeWidth: 2.4 }) : null, b[0])),
          React.createElement('button', { onClick: () => { setEditingId(s.id); setActionBar(null); } },
            React.createElement(LucideIcon, { name: 'Pencil', size: 16, strokeWidth: 2.4 }), 'Bewerken')
        );
        // hide the arrow caret tweak for below
        if (actionBar.below) actionBarEl = React.cloneElement(actionBarEl, { 'data-below': '1' });
      }
    }

    const editingStudent = editingId ? students.find(s => s.id === editingId) : null;

    return React.createElement('div', { className: 'board', ref: boardRef, onPointerDown: onBoardPointerDown },

      // columns
      React.createElement('div', { className: 'columns' },
        React.createElement('div', { onClick: (e) => onColumnClick('class1', e), style: { display: 'contents' } },
          React.createElement(ClassColumn, {
            which: 'class1', name: settings.classNames.class1, students, settings,
            onRename: renameClass, dropHot: dropHot === 'class1', registerZone, cardEls,
            selectedId, onCardPointerDown, justPlacedId,
            onScroll: bump,
            sort: settings.sort, onSortField: setSortField, onSortFilter: setSortFilter,
          })),
        React.createElement('div', { onClick: (e) => onColumnClick('class2', e), style: { display: 'contents' } },
          React.createElement(ClassColumn, {
            which: 'class2', name: settings.classNames.class2, students, settings,
            onRename: renameClass, dropHot: dropHot === 'class2', registerZone, cardEls,
            selectedId, onCardPointerDown, justPlacedId,
            onScroll: bump,
            sort: settings.sort, onSortField: setSortField, onSortFilter: setSortFilter,
          }))),

      // friend lines overlay — only when grouping by friends (not by level)
      (settings.linesVisible !== false && !(settings.sort && settings.sort.field)) ? React.createElement(FriendLines, { students, cardEls, boardRef, version }) : null,

      // tray
      React.createElement(Tray, {
        students, settings, dropHot: dropHot === 'tray', registerZone, cardEls,
        selectedId, onCardPointerDown,
        linesVisible: settings.linesVisible !== false,
        toggleLines: () => setSettings(s => ({ ...s, linesVisible: !(s.linesVisible !== false) })),
        compactCards: !!settings.compactCards,
        toggleCompact: () => setSettings(s => ({ ...s, compactCards: !s.compactCards })),
        openRoster: () => setRosterOpen(true),
        openSettings: () => setSettingsOpen(true),
        openTutorial: () => setTutorialOpen(true),
        openShare: () => setShareOpen(true),
        viewOnly,
        sort: settings.sort,
      }),

      // action bar
      actionBarEl,

      // drag ghost
      ghost ? React.createElement('div', {
        className: 'drag-ghost',
        style: { left: ghost.x, top: ghost.y, width: ghost.w },
      }, React.createElement(Card, { student: ghost.student, settings, mates: { all: students }, isGhost: true, cardRef: () => {} })) : null,

      // overlays
      settingsOpen ? React.createElement(SettingsOverlay, {
        settings, setSettings, onClose: () => setSettingsOpen(false),
        counts: { placed: placedCount },
        onAddStudent: () => { setSettingsOpen(false); addStudent(); },
        io: { importXlsx, importJson, exportJson, exportXlsx, resetPlacements, clearAll },
      }) : null,

      editingStudent ? React.createElement(CardEditor, {
        student: editingStudent, settings, allStudents: students,
        onSave: saveStudent, onDelete: deleteStudent, onClose: () => setEditingId(null),
      }) : null,

      rosterOpen ? React.createElement(RosterList, {
        students, settings, update: updateStudent, remove: removeStudent,
        addBlank, onClose: () => setRosterOpen(false),
      }) : null,

      tutorialOpen ? React.createElement(Tutorial, { onClose: closeTutorial }) : null,

      shareOpen ? React.createElement(ShareModal, {
        students, settings, onClose: () => setShareOpen(false),
      }) : null,

      // toast
      toast ? React.createElement('div', { className: 'toast' + (toast.err ? ' err' : '') },
        React.createElement(LucideIcon, { name: toast.err ? 'TriangleAlert' : 'Check', size: 18, strokeWidth: 2.6 }), toast.msg) : null,

      // hidden file inputs
      React.createElement('input', { ref: xlsxInput, type: 'file', accept: '.xlsx,.xls', className: 'file-hidden', onChange: onXlsxFile }),
      React.createElement('input', { ref: jsonInput, type: 'file', accept: '.json,application/json', className: 'file-hidden', onChange: onJsonFile })
    );
  }

  window.App = App;
  ReactDOM.createRoot(document.getElementById('root')).render(React.createElement(App));
})();
