/* planner-app.jsx — top-level app shell */

(function () {
  const {
    DAY_NAMES, DAY_SHORT, PALETTE, BLOCK_TYPES, DEFAULT_SETTINGS,
    startOfWeek, addDays, toISODate, parseISODate, fmtDate, fmtDur,
    normalizeBlock, loadState, saveState, normalizeState, sampleBlocks, planCompletion,
    makeId, clamp, snapMin
  } = window.PLANNER_DATA;

  const cloneBlocksWithNewIds = (blocks) => (Array.isArray(blocks) ? blocks : []).map(b => ({
    ...b,
    id: makeId(),
    activities: (b.activities || []).map(a => ({ ...a })),
    stickers: (b.stickers || []).map(s => typeof s === "string" ? s : { ...s })
  }));

  function App() {
    const [state, setState] = React.useState(loadState);
    /* Demo build: there is no account system and no server. The planner runs
       entirely in the browser and persists to localStorage. */
    const syncStatus = window.PLANNER_DEMO.SYNC_STATUS;
    const [activeId, setActiveId] = React.useState(null);
    const [editorOpen, setEditorOpen] = React.useState(false);
    const [focusTitleOnce, setFocusTitleOnce] = React.useState(false);
    const [smartAddOpen, setSmartAddOpen] = React.useState(false);
    const [editingTemplate, setEditingTemplate] = React.useState(false);
    const [toast, setToast] = React.useState(null);
    const [confetti, setConfetti] = React.useState([]);
    const [tweaks, setTweaks] = React.useState(() => window.PLANNER_TWEAK_DEFAULTS);

    const historyRef = React.useRef([]);
    const futureRef = React.useRef([]);
    const HISTORY_MAX = 20;

    /* Pick up tweak changes (since useTweaks is in PlannerTweaks; we mirror via postMessage) */
    React.useEffect(() => {
      function onMsg(e) {
        const d = e.data;
        if (d && d.type === "__edit_mode_set_keys" && d.edits) {
          setTweaks(prev => ({ ...prev, ...d.edits }));
        }
      }
      window.addEventListener("message", onMsg);
      return () => window.removeEventListener("message", onMsg);
    }, []);

    /* Apply theme to document */
    React.useEffect(() => {
      document.body.setAttribute("data-theme", tweaks.theme);
      document.body.setAttribute("data-motion", tweaks.motion < 3 ? "low" : "normal");
    }, [tweaks.theme, tweaks.motion]);

    /* Persist to this browser only. */
    React.useEffect(() => {
      saveState(state);
    }, [state]);

    const currentWeekStart = state.currentWeekStart;
    const weekStartDate = parseISODate(currentWeekStart);
    const realWeek = state.weeks[currentWeekStart] || { settings: { ...DEFAULT_SETTINGS }, blocks: [] };
    const template = state.template || { settings: { ...DEFAULT_SETTINGS }, blocks: [] };
    const hasTemplateBlocks = template.blocks && template.blocks.length > 0;
    const week = editingTemplate ? template : realWeek;
    const settings = week.settings;
    const blocks = week.blocks;

    const showToast = (msg, options = {}) => {
      const { action = null, duration = 1800 } = options;
      setToast({ msg, action, t: Date.now() });
      clearTimeout(window.__toastTimer);
      window.__toastTimer = setTimeout(() => setToast(null), duration);
    };

    const dismissToast = () => {
      clearTimeout(window.__toastTimer);
      setToast(null);
    };

    const snapshotWeek = (s) => {
      const w = s.weeks[s.currentWeekStart] || { settings: { ...DEFAULT_SETTINGS }, blocks: [] };
      return {
        weekStart: s.currentWeekStart,
        week: { settings: { ...w.settings }, blocks: w.blocks.map(b => ({ ...b, activities: (b.activities || []).map(a => ({ ...a })), stickers: (b.stickers || []).map(s => typeof s === "string" ? s : { ...s }) })) }
      };
    };

    const mutateWeek = (mut) => {
      if (editingTemplate) {
        setState(s => {
          const t = s.template || { settings: { ...DEFAULT_SETTINGS }, blocks: [] };
          const newT = typeof mut === "function" ? mut(t) : { ...t, ...mut };
          return { ...s, template: newT };
        });
        return;
      }
      setState(s => {
        const snap = snapshotWeek(s);
        historyRef.current.push(snap);
        if (historyRef.current.length > HISTORY_MAX) historyRef.current.shift();
        futureRef.current = [];
        const w = s.weeks[s.currentWeekStart] || { settings: { ...DEFAULT_SETTINGS }, blocks: [] };
        const newW = typeof mut === "function" ? mut(w) : { ...w, ...mut };
        return { ...s, weeks: { ...s.weeks, [s.currentWeekStart]: newW } };
      });
    };

    const undo = () => {
      if (editingTemplate) { showToast("Undo isn't available in schedule mode"); return; }
      const snap = historyRef.current.pop();
      if (!snap) { showToast("Nothing to undo"); return; }
      setState(s => {
        futureRef.current.push(snapshotWeek({ ...s, currentWeekStart: snap.weekStart }));
        if (futureRef.current.length > HISTORY_MAX) futureRef.current.shift();
        return { ...s, currentWeekStart: snap.weekStart, weeks: { ...s.weeks, [snap.weekStart]: snap.week } };
      });
      setActiveId(null);
      showToast("Undone");
    };

    const redo = () => {
      if (editingTemplate) return;
      const snap = futureRef.current.pop();
      if (!snap) { showToast("Nothing to redo"); return; }
      setState(s => {
        historyRef.current.push(snapshotWeek({ ...s, currentWeekStart: snap.weekStart }));
        if (historyRef.current.length > HISTORY_MAX) historyRef.current.shift();
        return { ...s, currentWeekStart: snap.weekStart, weeks: { ...s.weeks, [snap.weekStart]: snap.week } };
      });
      setActiveId(null);
      showToast("Redone");
    };

    React.useEffect(() => {
      const isEditable = (target) => {
        if (!target) return false;
        const tag = target.tagName;
        return tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT" || target.isContentEditable;
      };
      const onKey = (e) => {
        const mod = e.metaKey || e.ctrlKey;
        if (!mod) return;
        const k = e.key.toLowerCase();
        if (k === "z" && !e.shiftKey) {
          if (isEditable(e.target)) return;
          e.preventDefault();
          undo();
        } else if ((k === "z" && e.shiftKey) || k === "y") {
          if (isEditable(e.target)) return;
          e.preventDefault();
          redo();
        }
      };
      window.addEventListener("keydown", onKey);
      return () => window.removeEventListener("keydown", onKey);
    }, []);

    const fireConfetti = () => {
      if (tweaks.motion < 2) return;
      const colors = PALETTE;
      const pieces = Array.from({ length: 30 + tweaks.motion * 3 }).map((_, i) => ({
        id: makeId() + i,
        x: 30 + Math.random() * 40,
        delay: Math.random() * 0.3,
        rotate: Math.random() * 360,
        color: colors[Math.floor(Math.random() * colors.length)],
        size: 10 + Math.random() * 10
      }));
      setConfetti(pieces);
      setTimeout(() => setConfetti([]), 2000);

      if (tweaks.sound) {
        try {
          const ctx = new (window.AudioContext || window.webkitAudioContext)();
          [880, 1320, 1760].forEach((f, i) => {
            const o = ctx.createOscillator(); const g = ctx.createGain();
            o.type = "triangle"; o.frequency.value = f;
            g.gain.value = 0; o.connect(g); g.connect(ctx.destination);
            g.gain.linearRampToValueAtTime(0.08, ctx.currentTime + 0.02 + i*0.06);
            g.gain.linearRampToValueAtTime(0, ctx.currentTime + 0.3 + i*0.06);
            o.start(ctx.currentTime + i*0.06); o.stop(ctx.currentTime + 0.35 + i*0.06);
          });
        } catch (e) {}
      }
    };

    /* Mutators */
    const setWeek = (mut) => {
      setState(s => {
        if (editingTemplate) {
          const t = s.template || { settings: { ...DEFAULT_SETTINGS }, blocks: [] };
          const newT = typeof mut === "function" ? mut(t) : { ...t, ...mut };
          return { ...s, template: newT };
        }
        const w = s.weeks[s.currentWeekStart] || { settings: { ...DEFAULT_SETTINGS }, blocks: [] };
        const newW = typeof mut === "function" ? mut(w) : { ...w, ...mut };
        return { ...s, weeks: { ...s.weeks, [s.currentWeekStart]: newW } };
      });
    };

    const updateBlock = (id, patch) => {
      setWeek(w => ({ ...w, blocks: w.blocks.map(b => b.id === id ? normalizeBlock({ ...b, ...patch }) : b) }));
    };

    const moveBlock = (id, patch) => {
      mutateWeek(w => ({ ...w, blocks: w.blocks.map(b => b.id === id ? normalizeBlock({ ...b, ...patch }) : b) }));
    };

    const createBlock = (overrides = {}) => {
      const baseStart = clamp(settings.startHour * 60 + 120, settings.startHour * 60, settings.endHour * 60 - 30);
      const b = normalizeBlock({
        id: makeId(),
        dayIndex: 0, start: baseStart, end: Math.min(baseStart + 45, settings.endHour * 60),
        title: "New lesson", typeId: "math",
        color: PALETTE[blocks.length % PALETTE.length],
        activities: [{ name: "Opening", minutes: 8, detail: "" }, { name: "Core activity", minutes: 25, detail: "" }, { name: "Wrap", minutes: 7, detail: "" }],
        ...overrides
      });
      mutateWeek(w => ({ ...w, blocks: [...w.blocks, b] }));
      setActiveId(b.id);
      setEditorOpen(true);
      setFocusTitleOnce(true);
      showToast("New block added");
    };

    const addBlocks = (blockList) => {
      const incoming = (blockList || []).filter(Boolean);
      if (!incoming.length) return { total: 0, otherWeeks: [] };

      const byWeek = {};
      const otherWeeks = new Set();
      for (const raw of incoming) {
        const { date, ...rest } = raw;
        let targetWeek = currentWeekStart;
        let dayIndex = rest.dayIndex;
        if (date && /^\d{4}-\d{2}-\d{2}$/.test(date)) {
          const d = parseISODate(date);
          if (!Number.isNaN(d.getTime())) {
            targetWeek = toISODate(startOfWeek(d));
            dayIndex = (d.getDay() + 6) % 7;
          }
        }
        const normalized = normalizeBlock({ id: makeId(), ...rest, dayIndex });
        if (!normalized) continue;
        if (!byWeek[targetWeek]) byWeek[targetWeek] = [];
        byWeek[targetWeek].push(normalized);
        if (targetWeek !== currentWeekStart) otherWeeks.add(targetWeek);
      }

      const totalCount = Object.values(byWeek).reduce((n, arr) => n + arr.length, 0);
      if (!totalCount) return { total: 0, otherWeeks: [] };

      setState(s => {
        const snap = snapshotWeek(s);
        historyRef.current.push(snap);
        if (historyRef.current.length > HISTORY_MAX) historyRef.current.shift();
        futureRef.current = [];
        const newWeeks = { ...s.weeks };
        const currentSettings = (s.weeks[s.currentWeekStart] && s.weeks[s.currentWeekStart].settings) || DEFAULT_SETTINGS;
        for (const [ws, list] of Object.entries(byWeek)) {
          const existing = newWeeks[ws] || { settings: { ...currentSettings }, blocks: [] };
          newWeeks[ws] = { ...existing, settings: { ...DEFAULT_SETTINGS, ...existing.settings }, blocks: [...existing.blocks, ...list] };
        }
        return { ...s, weeks: newWeeks };
      });

      return { total: totalCount, otherWeeks: Array.from(otherWeeks) };
    };

    const placeSticker = (blockId, sticker) => {
      mutateWeek(w => ({
        ...w, blocks: w.blocks.map(b => b.id === blockId
          ? { ...b, stickers: [...(b.stickers||[]).filter(s => typeof s !== "string" || s !== sticker.kind), sticker] }
          : b)
      }));
      if (tweaks.motion >= 4) fireConfetti();
      showToast("✨ Sticker added");
    };

    const duplicateBlock = (id) => {
      const b = blocks.find(x => x.id === id);
      if (!b) return;
      const copy = normalizeBlock({ ...b, id: makeId(), start: Math.min(b.start + 15, 1425), end: Math.min(b.end + 15, 1440), title: b.title + " copy" });
      mutateWeek(w => ({ ...w, blocks: [...w.blocks, copy] }));
      setActiveId(copy.id);
      showToast("Duplicated");
    };

    const deleteBlock = (id) => {
      const removed = blocks.find(b => b.id === id);
      if (!removed) return;
      mutateWeek(w => ({ ...w, blocks: w.blocks.filter(b => b.id !== id) }));
      setActiveId(null);
      setEditorOpen(false);
      showToast(`Deleted "${(removed.title || "lesson").slice(0, 24)}"`, {
        duration: 6000,
        action: {
          label: "Undo",
          onClick: () => {
            mutateWeek(w => ({ ...w, blocks: [...w.blocks, removed] }));
            setActiveId(removed.id);
            dismissToast();
          }
        }
      });
    };

    const onSelect = (id, openEditor = false) => {
      setActiveId(id);
      if (openEditor) setEditorOpen(true);
    };

    /* Template apply */
    const applyTemplateToWeek = (targetWeekStart, opts = {}) => {
      const { silent = false } = opts;
      if (!state.template || !state.template.blocks || !state.template.blocks.length) {
        if (!silent) showToast("No schedule template yet — open Schedule to set one up");
        return false;
      }
      const previous = state.weeks[targetWeekStart];
      const previousHasBlocks = previous && Array.isArray(previous.blocks) && previous.blocks.length > 0;
      if (previousHasBlocks && !silent && !window.confirm("This week already has lessons. Replace them with your weekly schedule?")) {
        return false;
      }
      const fromWeekStart = currentWeekStart;
      setState(s => {
        const t = s.template;
        const cloned = cloneBlocksWithNewIds(t.blocks);
        return {
          ...s,
          currentWeekStart: targetWeekStart,
          weeks: {
            ...s.weeks,
            [targetWeekStart]: {
              settings: { ...t.settings },
              blocks: cloned
            }
          }
        };
      });
      setActiveId(null);
      if (!silent) {
        showToast("Schedule applied — add lesson details for the week", {
          duration: 6000,
          action: {
            label: "Undo",
            onClick: () => {
              setState(s => {
                const nextWeeks = { ...s.weeks };
                if (previous) {
                  nextWeeks[targetWeekStart] = previous;
                } else {
                  delete nextWeeks[targetWeekStart];
                }
                return { ...s, currentWeekStart: fromWeekStart, weeks: nextWeeks };
              });
              dismissToast();
            }
          }
        });
      }
      return true;
    };

    const offerApplyTemplate = (weekStart) => {
      const t = state.template;
      if (!t || !t.blocks || !t.blocks.length) return;
      const existing = state.weeks[weekStart];
      if (existing && Array.isArray(existing.blocks) && existing.blocks.length > 0) return;
      showToast("Apply your weekly schedule to this week?", {
        duration: 7000,
        action: {
          label: "Apply",
          onClick: () => {
            applyTemplateToWeek(weekStart, { silent: true });
            showToast("Schedule applied — add details for this week");
          }
        }
      });
    };

    /* Week nav */
    const shiftWeek = (days) => {
      if (editingTemplate) return;
      const next = toISODate(addDays(parseISODate(currentWeekStart), days));
      setState(s => {
        const weeks = { ...s.weeks };
        if (!weeks[next]) weeks[next] = { settings: { ...DEFAULT_SETTINGS }, blocks: [] };
        return { ...s, currentWeekStart: next, weeks };
      });
      setActiveId(null);
      const landedEmpty = !state.weeks[next] || !(state.weeks[next].blocks && state.weeks[next].blocks.length);
      if (landedEmpty && hasTemplateBlocks) {
        offerApplyTemplate(next);
      } else {
        showToast(days < 0 ? "← Previous week" : "Next week →");
      }
    };
    const jumpToday = () => {
      if (editingTemplate) return;
      const ws = toISODate(startOfWeek(new Date()));
      const existing = state.weeks[ws];
      const isEmpty = !existing || !(existing.blocks && existing.blocks.length);
      setState(s => {
        const weeks = { ...s.weeks };
        if (!weeks[ws]) {
          weeks[ws] = hasTemplateBlocks
            ? { settings: { ...DEFAULT_SETTINGS } , blocks: [] }
            : { settings: { ...DEFAULT_SETTINGS }, blocks: sampleBlocks() };
        }
        return { ...s, currentWeekStart: ws, weeks };
      });
      if (isEmpty && hasTemplateBlocks) {
        offerApplyTemplate(ws);
      } else {
        showToast("Jumped to today");
      }
    };

    const toggleTemplateEdit = () => {
      setEditorOpen(false);
      setActiveId(null);
      setEditingTemplate(prev => !prev);
    };

    const duplicateToNextWeek = () => {
      if (editingTemplate) return;
      const nextWeekStart = toISODate(addDays(parseISODate(currentWeekStart), 7));
      const previousNext = state.weeks[nextWeekStart];
      const previousNextHasBlocks = previousNext && Array.isArray(previousNext.blocks) && previousNext.blocks.length > 0;
      if (previousNextHasBlocks && !window.confirm("Next week already has lessons. Replace them with a copy of this week?")) {
        return;
      }
      setState(s => {
        const cur = s.weeks[s.currentWeekStart] || { settings: { ...DEFAULT_SETTINGS }, blocks: [] };
        return {
          ...s,
          currentWeekStart: nextWeekStart,
          weeks: {
            ...s.weeks,
            [nextWeekStart]: {
              settings: { ...cur.settings },
              blocks: cloneBlocksWithNewIds(cur.blocks)
            }
          }
        };
      });
      setActiveId(null);
      const sourceWeekStart = currentWeekStart;
      showToast("Copied to next week →", {
        duration: 6000,
        action: {
          label: "Undo",
          onClick: () => {
            setState(s => {
              const nextWeeks = { ...s.weeks };
              if (previousNext) {
                nextWeeks[nextWeekStart] = previousNext;
              } else {
                delete nextWeeks[nextWeekStart];
              }
              return { ...s, currentWeekStart: sourceWeekStart, weeks: nextWeeks };
            });
            dismissToast();
          }
        }
      });
    };

    const exportWeek = () => {
      const payload = { exportedAt: new Date().toISOString(), currentWeekStart, week };
      const blob = new Blob([JSON.stringify(payload, null, 2)], { type: "application/json" });
      const a = document.createElement("a");
      a.href = URL.createObjectURL(blob);
      a.download = "lesson-week-" + currentWeekStart + ".json";
      document.body.appendChild(a); a.click();
      URL.revokeObjectURL(a.href); a.remove();
      showToast("Exported");
      fireConfetti();
    };

    /* Stats */
    const totalMinutes = blocks.reduce((sum, b) => sum + Math.max(b.end - b.start, 0), 0);
    const planned = blocks.length ? Math.round(blocks.reduce((s,b) => s + planCompletion(b), 0) / blocks.length) : 0;

    const activeBlock = blocks.find(b => b.id === activeId) || null;

    /* Save command */
    const saveBlockAndCelebrate = () => {
      setEditorOpen(false);
      fireConfetti();
      showToast("Saved! 🎉");
    };

    /* Build week range string */
    const rangeStr = fmtDate(weekStartDate, { month: "short", day: "numeric" }) + " – " +
                     fmtDate(addDays(weekStartDate, settings.includeWeekend ? 6 : 4), { month: "short", day: "numeric", year: "numeric" });

    return (
      <div className={"app" + (editingTemplate ? " mode-template" : "")}>
        {/* Background scene */}
        <window.PlannerScene motion={tweaks.motion} showWeather={tweaks.showWeather} theme={tweaks.theme}/>

        {/* Top bar */}
        <header className="topbar">
          <div className="brand">
            <div className="brand-mark">
              <svg viewBox="0 0 40 40" fill="none" stroke="#2a2438" strokeWidth="3" strokeLinejoin="round" strokeLinecap="round">
                <rect x="6" y="9" width="28" height="24" rx="4" fill="#fff"/>
                <path d="M6 14h28"/>
                <path d="M13 5v8M27 5v8"/>
                <circle cx="14" cy="22" r="2" fill="#ff7aa9" stroke="none"/>
                <circle cx="20" cy="22" r="2" fill="#5cd6b1" stroke="none"/>
                <circle cx="26" cy="22" r="2" fill="#5ea7ff" stroke="none"/>
                <circle cx="14" cy="28" r="2" fill="#ffc24c" stroke="none"/>
                <circle cx="20" cy="28" r="2" fill="#b07cff" stroke="none"/>
                <circle cx="26" cy="28" r="2" fill="#ff945c" stroke="none"/>
              </svg>
            </div>
            <div>
              <h1>{editingTemplate ? "Weekly schedule template" : "Weekly Lesson Planner"}</h1>
              <div className="subline">
                <span className="pin"/>
                {editingTemplate ? "Set up the blocks that repeat every week — times, days, subjects" : rangeStr}
              </div>
            </div>
          </div>

          <div className="toolbar">
            {!editingTemplate && (
              <>
                <button className="tool-btn tb-prev icon-only" onClick={() => shiftWeek(-7)} title="Previous week">
                  <window.Icon.left/>
                </button>
                <button className="tool-btn tb-today" onClick={jumpToday}>Today</button>
                <button className="tool-btn tb-next icon-only" onClick={() => shiftWeek(7)} title="Next week">
                  <window.Icon.right/>
                </button>
                <button className="tool-btn tb-schedule" onClick={toggleTemplateEdit} title="Edit your weekly schedule template">
                  <window.Icon.grid/> Schedule
                </button>
                {hasTemplateBlocks && (
                  <button className="tool-btn tb-apply" onClick={() => applyTemplateToWeek(currentWeekStart)} title="Apply your weekly schedule to this week">
                    <window.Icon.check/> Use schedule
                  </button>
                )}
                <button className="tool-btn tb-duplicate" onClick={duplicateToNextWeek} title="Copy this week to next week" disabled={blocks.length === 0}>
                  <window.Icon.copy/> Copy to next
                </button>
              </>
            )}
            {editingTemplate && (
              <button className="tool-btn tb-done-template" onClick={toggleTemplateEdit} title="Finish editing the schedule template">
                <window.Icon.check/> Done editing schedule
              </button>
            )}
            <button className="tool-btn" onClick={() => createBlock()} title={editingTemplate ? "Add a block to your schedule" : "New block"}>
              <window.Icon.plus/> {editingTemplate ? "Add block" : "New"}
            </button>
            {!editingTemplate && (
              <button className="tool-btn tb-smart" onClick={() => setSmartAddOpen(true)} title="Smart add — paste a note">
                <window.Icon.sparkle/> Smart add
              </button>
            )}
            {!editingTemplate && (
              <button className="tool-btn tb-export" onClick={exportWeek} title="Export">
                <window.Icon.download/>
              </button>
            )}
            {!editingTemplate && (
              <button className="tool-btn tb-print icon-only" onClick={() => window.print()} title="Print">
                <window.Icon.print/>
              </button>
            )}
            <button
              className="tool-btn tb-style"
              onClick={() => window.postMessage({ type: "__activate_edit_mode" }, "*")}
              title="Style — themes, motion, mascots"
            >
              <window.Icon.sparkle/> Style
            </button>
          </div>
        </header>

        {editingTemplate && (
          <div className="template-banner">
            <div className="template-banner-text">
              <strong>Editing your weekly schedule.</strong> Add the blocks (subjects, days, times) that repeat every week. Lesson details you add here become defaults — you can override them per week.
            </div>
            <button className="template-banner-cta" onClick={toggleTemplateEdit}>Done</button>
          </div>
        )}

        {!editingTemplate && !hasTemplateBlocks && blocks.length === 0 && (
          <div className="template-empty">
            <div className="template-empty-text">
              <strong>Want to save time?</strong> Set up your weekly schedule once (subjects, days, times) and reuse it every week.
            </div>
            <button className="template-empty-cta" onClick={toggleTemplateEdit}>
              <window.Icon.grid/> Set up my schedule
            </button>
          </div>
        )}

        <div className="sync-line">
          {syncStatus}
        </div>

        {/* Metrics */}
        <div className="metrics">
          <div className="metric m-blocks">
            <div className="m-icon" style={{background:"var(--c-mon)"}}><window.Icon.book/></div>
            <div className="m-value">{blocks.length}</div>
            <div className="m-label">Lessons</div>
          </div>
          <div className="metric m-hours">
            <div className="m-icon" style={{background:"var(--c-thu)"}}><window.Icon.sun/></div>
            <div className="m-value">{fmtDur(totalMinutes)}</div>
            <div className="m-label">Teaching</div>
          </div>
          <div className="metric m-plan">
            <div className="m-icon" style={{background:"var(--c-wed)"}}><window.Icon.check/></div>
            <div className="m-value">{planned}%</div>
            <div className="m-label">Planned</div>
          </div>
        </div>

        {/* Board */}
        <window.PlannerBoard
          state={week}
          settings={settings}
          currentWeekStart={currentWeekStart}
          weekStartDate={weekStartDate}
          activeId={activeId}
          onSelect={onSelect}
          onCreate={(o) => createBlock(o)}
          onUpdate={moveBlock}
          onPlaceSticker={placeSticker}
          motion={tweaks.motion}
        />

        {/* Editor */}
        <window.PlannerEditor
          open={editorOpen && activeBlock}
          block={activeBlock}
          settings={settings}
          focusTitle={focusTitleOnce}
          onFocused={() => setFocusTitleOnce(false)}
          onClose={saveBlockAndCelebrate}
          onUpdate={updateBlock}
          onDuplicate={duplicateBlock}
          onDelete={deleteBlock}
        />

        {/* Smart add modal */}
        {window.PlannerSmartAdd && (
          <window.PlannerSmartAdd
            open={smartAddOpen}
            settings={settings}
            currentWeekStart={currentWeekStart}
            onClose={() => setSmartAddOpen(false)}
            onAdd={(proposed) => {
              const { total, otherWeeks } = addBlocks(proposed);
              setSmartAddOpen(false);
              if (total) {
                if (otherWeeks.length) {
                  const labels = otherWeeks.sort().map(ws => fmtDate(parseISODate(ws), { month: "short", day: "numeric" }));
                  const weekText = labels.length === 1 ? `week of ${labels[0]}` : `${otherWeeks.length + 1} weeks`;
                  showToast(`Added ${total} lesson${total === 1 ? "" : "s"} — incl. ${weekText}`, { duration: 4000 });
                } else {
                  showToast(total === 1 ? "Added 1 lesson" : `Added ${total} lessons`);
                }
                fireConfetti();
              }
            }}
          />
        )}

        {/* Toast */}
        <div className={"toast" + (toast ? " show" : "") + (toast && toast.action ? " has-action" : "")}>
          {toast && (
            <>
              <span className="toast-msg">{toast.msg}</span>
              {toast.action && (
                <button
                  type="button"
                  className="toast-action"
                  onClick={(e) => { e.stopPropagation(); toast.action.onClick(); }}
                >
                  {toast.action.label}
                </button>
              )}
            </>
          )}
        </div>

        {/* Confetti */}
        {confetti.length > 0 && (
          <div className="confetti-layer">
            {confetti.map(p => (
              <div key={p.id} className="confetti-piece" style={{
                left: p.x + "%",
                top: "-30px",
                width: p.size + "px",
                height: (p.size * 1.4) + "px",
                background: p.color,
                animationDelay: p.delay + "s",
                transform: `rotate(${p.rotate}deg)`
              }}/>
            ))}
          </div>
        )}

        {/* Sticker tray */}
        <window.StickerTray visible={tweaks.showStickers && !editorOpen && !editingTemplate}/>

        {/* Floating "open editor" handle when a block is selected */}
        {activeBlock && !editorOpen && (
          <button
            onClick={() => setEditorOpen(true)}
            style={{
              position:"fixed", right: 22, bottom: 84, zIndex: 22,
              padding:"12px 18px", borderRadius:999,
              border:"3px solid var(--ink)", background:"var(--c-tue)",
              boxShadow:"6px 8px 0 var(--ink)", fontWeight:800
            }}
          >
            ✏️ Edit "{activeBlock.title.slice(0,18)}"
          </button>
        )}

        {/* Tweaks panel */}
        <window.PlannerTweaks/>
      </div>
    );
  }

  const root = ReactDOM.createRoot(document.getElementById("root"));
  root.render(<App/>);
})();
