// Counselor's Roadmap — month-by-month advising playbook, sourced directly from
// the Student-Athlete Advising Roadmap (counselor edition). Each month lists the
// items a counselor sends, organized by grade level and pillar. Every item shows
// what kind of asset backs it (Doc, Video, Video + Doc, Spreadsheet) or whether
// it's simply a counselor reminder.

// ── Shared roadmap override store ──────────────────────────────────────────
// Admins can edit each task's title + description and mark its asset uploaded.
// Edits persist to localStorage and broadcast so the counselor roadmap and the
// admin console stay in sync live (across the same tab and across tabs).
const ROADMAP_OVR_KEY = "sasa_roadmap_overrides";

function loadRoadmapOverrides() {
  try { return JSON.parse(localStorage.getItem(ROADMAP_OVR_KEY) || "{}"); }
  catch { return {}; }
}
function saveRoadmapOverrides(o) {
  localStorage.setItem(ROADMAP_OVR_KEY, JSON.stringify(o));
  window.dispatchEvent(new CustomEvent("sasa-roadmap-change"));
}
window.getRoadmapOverrides = loadRoadmapOverrides;
window.setRoadmapOverride = (id, patch) => {
  const o = loadRoadmapOverrides();
  o[id] = { ...(o[id] || {}), ...patch };
  saveRoadmapOverrides(o);
};
function useRoadmapOverrides() {
  const [ovr, setOvr] = useState(loadRoadmapOverrides);
  useEffect(() => {
    const h = () => setOvr(loadRoadmapOverrides());
    window.addEventListener("sasa-roadmap-change", h);
    window.addEventListener("storage", h);
    return () => {
      window.removeEventListener("sasa-roadmap-change", h);
      window.removeEventListener("storage", h);
    };
  }, []);
  return ovr;
}
window.useRoadmapOverrides = useRoadmapOverrides;

// Merge a base task with any admin override (title / desc / uploaded).
function mergeTask(task, overrides) {
  const o = overrides?.[task.id];
  if (!o) return task;
  return {
    ...task,
    title: o.title != null && o.title !== "" ? o.title : task.title,
    desc: o.desc != null ? o.desc : task.desc,
    uploaded: o.uploaded ?? task.uploaded,
  };
}

// Grade columns: 9 = Freshman, 10 = Sophomore, 11 = Junior, 12 = Senior
const ROADMAP = [
  {
    id: "august", label: "August", semester: "fall",
    tasks: [
      { id: "aug-1", grade: "9",  pillar: "academic",   rtype: "doc",       title: "Introduction to the NCAA core-course track (16 core courses)." },
      { id: "aug-2", grade: "9",  pillar: "recruiting",  rtype: "video_doc", title: "Academics as a factor in the recruiting process.", desc: "Send early: coaches screen on GPA and core-course rigor before they ever watch film. Walk freshmen through how transcripts factor into offers and pair the video with the one-page academics checklist so families start tracking core courses now." },
      { id: "aug-3", grade: "11", pillar: "recruiting",  rtype: "doc",       title: "Phone call & visit prep (official & unofficial) + questions to ask coaches." },
      { id: "aug-4", grade: "12", pillar: "recruiting",  rtype: "video",     title: "The admissions process as a student-athlete. Misconceptions and strategy." },
    ],
  },
  {
    id: "september", label: "September", semester: "fall",
    tasks: [
      { id: "sep-1", grade: "10", pillar: "recruiting",    rtype: "video",     title: "Into the Mind of a College Coach." },
      { id: "sep-2", grade: "11", pillar: "recruiting",    rtype: "video_doc", title: "College coach outreach & communication.", desc: "For juniors actively reaching out. The video models a first-contact email and a follow-up cadence; the doc is a fill-in template. Remind athletes to personalize each message and to CC you so you can track responses." },
      { id: "sep-3", grade: "12", pillar: "recruiting",    rtype: "video_doc", title: "Fallback & alternative paths for the uncommitted: dual enroll, JUCO, D3, NAIA, preferred walk-on, prep/postgrad year. No path is a failure." },
      { id: "sep-4", grade: "9",  pillar: "mental_health", rtype: "video",     title: "Set healthy routines early — balancing school + sport with sleep, nutrition, and social life." },
      { id: "sep-5", grade: "11", pillar: "mental_health", rtype: "video_doc", title: "Imposter syndrome & fear of rejection — the wall that stops kids from hitting \u2018send.\u2019" },
    ],
  },
  {
    id: "october", label: "October", semester: "fall",
    tasks: [
      { id: "oct-1", grade: "9",  pillar: "recruiting",    rtype: "video_doc", title: "Recruiting-landscape orientation: governing bodies & divisions, levels." },
      { id: "oct-2", grade: "12", pillar: "financial_aid", rtype: "doc",       title: "Time to apply for FAFSA." },
    ],
  },
  {
    id: "november", label: "November", semester: "fall",
    tasks: [
      { id: "nov-1", grade: "9",  pillar: "academic",   rtype: "doc",   title: "Register with the NCAA Eligibility Center — free Profile Page (upgrade to a Certification Account if D1/D2 is likely)." },
      { id: "nov-2", grade: "10", pillar: "recruiting", rtype: "video", title: "Building your student-athlete story/narrative. What makes you a recruitable student-athlete?" },
      { id: "nov-3", grade: "11", pillar: "recruiting", rtype: "video", title: "Placing schools in \u201Cbuckets\u201D based on academic and athletic factors that affect recruiting timelines." },
    ],
  },
  {
    id: "december", label: "December", semester: "fall",
    tasks: [
      { id: "dec-1", grade: "10", pillar: "academic", rtype: "reminder", title: "10/7 Progress: is the student on pace to finish 10 core courses (7 in English/math/science) before senior year? These grades lock in." },
    ],
  },
  {
    id: "january", label: "January", semester: "spring",
    tasks: [
      { id: "jan-1", grade: "10", pillar: "recruiting", rtype: "video_doc", title: "Active vs. passive recruiting strategy." },
      { id: "jan-2", grade: "11", pillar: "recruiting", rtype: "video_doc", title: "The transfer portal." },
    ],
  },
  {
    id: "february", label: "February", semester: "spring",
    tasks: [
      { id: "feb-1", grade: "11", pillar: "academic", rtype: "video_doc", title: "Testing decision, SAT/ACT. Not required for NCAA, but may matter for academic & merit aid." },
    ],
  },
  {
    id: "march", label: "March", semester: "spring",
    tasks: [
      { id: "mar-1", grade: "10", pillar: "recruiting", rtype: "video_doc_sheet", title: "Target lists based on athlete-specific criteria." },
    ],
  },
  {
    id: "april", label: "April", semester: "spring",
    tasks: [
      { id: "apr-1", grade: "10", pillar: "financial_aid", rtype: "video_doc", title: "Scholarships & athletic aid basics — how offers, athletic aid, and revenue-sharing work now." },
    ],
  },
  {
    id: "may", label: "May", semester: "spring",
    tasks: [
      { id: "may-1", grade: "9",  pillar: "academic",      rtype: "reminder",  title: "Confirm the sophomore schedule keeps core courses on track. [9th grade, spring/summer]" },
      { id: "may-2", grade: "11", pillar: "academic",      rtype: "reminder",  title: "By end of junior year, 10 core courses should be complete." },
      { id: "may-3", grade: "10", pillar: "recruiting",    rtype: "video_doc", title: "How to determine what level you belong at?" },
      { id: "may-4", grade: "11", pillar: "recruiting",    rtype: "video_doc", title: "Narrow the target list to reach / match / safety based on real interest received." },
      { id: "may-5", grade: "12", pillar: "mental_health", rtype: "video",     title: "Transition to college campus." },
    ],
  },
  {
    id: "june", label: "June", semester: "spring",
    tasks: [
      { id: "jun-1", grade: "12", pillar: "academic", rtype: "reminder", title: "Upload student\u2019s final official transcript + proof of graduation to the NCAA Eligibility Center high school portal." },
    ],
  },
];

const PILLAR_META = {
  recruiting:    { label: "Recruiting",              color: "var(--accent)", soft: "var(--accent-soft)", ink: "var(--accent-ink)" },
  mental_health: { label: "Social-Emotional (SEL)",  color: "var(--teal)",   soft: "var(--teal-soft)",   ink: "var(--teal-ink)"   },
  academic:      { label: "Academic",                color: "var(--ink)",    soft: "var(--bg-2)",        ink: "var(--ink)"        },
  financial_aid: { label: "Financial Aid / FAFSA",   color: "var(--orange)", soft: "var(--orange-soft)", ink: "#8C4000"           },
};

const RES_TYPE = {
  doc:             { label: "Doc",                icon: "File",       bg: "#F5F0EE",             ink: "#5A2A00",         upload: true },
  video:           { label: "Video",              icon: "PlayCircle", bg: "var(--accent-soft)",  ink: "var(--accent-ink)", upload: true },
  video_doc:       { label: "Video + Doc",        icon: "Video",      bg: "var(--teal-soft)",    ink: "var(--teal-ink)", upload: true },
  video_doc_sheet: { label: "Video + Doc + Sheet",icon: "Video",      bg: "var(--teal-soft)",    ink: "var(--teal-ink)", upload: true },
  spreadsheet:     { label: "Spreadsheet",        icon: "Report",     bg: "var(--success-soft)", ink: "#14504F",         upload: true },
  reminder:        { label: "Counselor Reminder", icon: "Bell",       bg: "var(--bg-2)",         ink: "var(--ink-3)",    upload: false },
};

const GRADE_LABEL = { "9": "9th", "10": "10th", "11": "11th", "12": "12th" };

const SEMESTER_META = {
  fall:   { label: "Fall Semester",   sub: "Aug \u2013 Dec", tagline: "Foundation \u2192 outreach \u2192 eligibility" },
  spring: { label: "Spring Semester", sub: "Jan \u2013 Jun", tagline: "Targeting \u2192 aid \u2192 transition" },
};

// ── Downloadable assets on each task ────────────────────────────────────────
const ASSET_META = {
  video: { label: "Video", icon: "PlayCircle", open: "Play",  ext: "mp4" },
  doc:   { label: "Doc",   icon: "File",       open: "Open",  ext: "pdf" },
  sheet: { label: "Sheet", icon: "Report",     open: "Open",  ext: "xlsx" },
};
const ASSETS_BY_RTYPE = {
  doc:             ["doc"],
  video:           ["video"],
  video_doc:       ["video", "doc"],
  video_doc_sheet: ["video", "doc", "sheet"],
  spreadsheet:     ["sheet"],
  reminder:        [],
};
function assetsFor(task) { return ASSETS_BY_RTYPE[task.rtype] || []; }

function slugify(s) {
  return (s || "resource").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 60);
}
function buildAssetContent(task, kind) {
  const m = ASSET_META[kind];
  const pillar = PILLAR_META[task.pillar]?.label || task.pillar;
  return [
    `${m.label.toUpperCase()} — ${task.title}`,
    "",
    task.desc ? task.desc + "\n" : "",
    "Student Athlete Success Academy · Counselor's Roadmap",
    `Grade: ${GRADE_LABEL[task.grade]}   ·   Pillar: ${pillar}`,
    "",
    "[Sample export from the roadmap prototype — replace with the uploaded file.]",
  ].join("\n");
}
function downloadAsset(task, kind) {
  const m = ASSET_META[kind];
  const blob = new Blob([buildAssetContent(task, kind)], { type: "text/plain" });
  const url = URL.createObjectURL(blob);
  const a = document.createElement("a");
  a.href = url;
  a.download = `${slugify(task.title)}-${m.label.toLowerCase()}.txt`;
  document.body.appendChild(a);
  a.click();
  a.remove();
  setTimeout(() => URL.revokeObjectURL(url), 1500);
}

// Download + record the interaction for the Impact & Engagement reports.
function downloadAndTrack(task, kind) {
  downloadAsset(task, kind);
  window.trackDownload && window.trackDownload({
    source: "roadmap", refId: task.id, title: task.title,
    pillar: task.pillar, grade: task.grade, assetType: kind,
  });
}

// One compact control per asset: click the label to preview, the ⤓ to download.
function AssetChip({ task, kind, onOpen }) {
  const m = ASSET_META[kind];
  const Icon = window.I[m.icon];
  return (
    <div style={{
      display: "inline-flex", alignItems: "stretch", flexShrink: 0,
      border: "1px solid var(--line-2)", borderRadius: 7, overflow: "hidden",
      background: "var(--card)",
    }}>
      <button
        onClick={() => onOpen(kind)}
        title={`${m.open} ${m.label.toLowerCase()}`}
        style={{
          display: "inline-flex", alignItems: "center", gap: 6,
          padding: "5px 9px", fontSize: 11.5, fontWeight: 600,
          color: "var(--ink-2)", letterSpacing: -0.005, cursor: "pointer",
        }}
        onMouseEnter={e => e.currentTarget.style.background = "var(--bg-2)"}
        onMouseLeave={e => e.currentTarget.style.background = "transparent"}
      >
        <Icon size={13} stroke="var(--ink-2)" />
        {m.label}
      </button>
      <button
        onClick={() => downloadAndTrack(task, kind)}
        title={`Download ${m.label.toLowerCase()}`}
        style={{
          display: "inline-flex", alignItems: "center", justifyContent: "center",
          padding: "5px 8px", borderLeft: "1px solid var(--line)", cursor: "pointer",
        }}
        onMouseEnter={e => e.currentTarget.style.background = "var(--bg-2)"}
        onMouseLeave={e => e.currentTarget.style.background = "transparent"}
      >
        <window.I.Download size={12} stroke="var(--ink-3)" />
      </button>
    </div>
  );
}

// In-app preview shown when a counselor opens (not downloads) an asset.
function ResourcePreviewModal({ task, kind, onClose }) {
  const m = ASSET_META[kind];
  const meta = PILLAR_META[task.pillar];
  const isVideo = kind === "video";
  const PreviewIcon = window.I[isVideo ? "PlayCircle" : m.icon];
  return (
    <window.UI.Modal open onClose={onClose} width={640}>
      <div style={{ padding: 0 }}>
        {/* Preview surface */}
        <div style={{
          aspectRatio: isVideo ? "16/9" : "16/10",
          background: isVideo
            ? "linear-gradient(135deg, #1B3D2F 0%, #2C5C44 100%)"
            : "repeating-linear-gradient(135deg, var(--bg-2) 0 14px, var(--card) 14px 28px)",
          display: "flex", alignItems: "center", justifyContent: "center",
          borderTopLeftRadius: "var(--radius-lg)", borderTopRightRadius: "var(--radius-lg)",
          position: "relative",
        }}>
          {isVideo ? (
            <div style={{
              width: 62, height: 62, borderRadius: 999, background: "rgba(255,255,255,0.95)",
              display: "flex", alignItems: "center", justifyContent: "center", paddingLeft: 4,
              boxShadow: "0 8px 24px -6px rgba(0,0,0,0.4)",
            }}>
              <window.I.Play size={24} stroke="var(--ink)" sw={2.5} />
            </div>
          ) : (
            <div style={{ textAlign: "center", color: "var(--ink-3)" }}>
              <PreviewIcon size={34} stroke="var(--ink-3)" />
              <div className="mono" style={{ fontSize: 11, marginTop: 8, letterSpacing: 0.04 }}>
                {m.label.toUpperCase()} PREVIEW
              </div>
            </div>
          )}
        </div>

        <div style={{ padding: 24 }}>
          <div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 6 }}>
            <window.UI.Tag cat={task.pillar} />
            <span className="mono" style={{ fontSize: 11, color: "var(--ink-3)" }}>{GRADE_LABEL[task.grade]} grade</span>
          </div>
          <h2 className="serif" style={{ fontSize: 22, margin: "2px 0 0", letterSpacing: -0.015, lineHeight: 1.2 }}>
            {task.title}
          </h2>
          {task.desc && (
            <p style={{ fontSize: 13, lineHeight: 1.55, color: "var(--ink-2)", marginTop: 12 }}>{task.desc}</p>
          )}
          <div style={{ display: "flex", gap: 10, marginTop: 20, paddingTop: 18, borderTop: "1px solid var(--line)" }}>
            <window.UI.Btn onClick={() => downloadAndTrack(task, kind)} icon={<window.I.Download size={13} />}>
              Download {m.label.toLowerCase()}
            </window.UI.Btn>
            <window.UI.Btn variant="outline" onClick={onClose}>Close</window.UI.Btn>
          </div>
        </div>
      </div>
    </window.UI.Modal>
  );
}

// Prompt shown when a counselor marks a roadmap task complete: capture roughly
// how many student-athletes they shared the resource with. This reach data is
// stored against the task and rolls into the Impact & Engagement reports.
function ShareCountModal({ task, onCancel, onConfirm }) {
  const [count, setCount] = useState(3);
  const presets = [0, 1, 3, 5, 10, 20];

  const fld = {
    width: 90, padding: "10px 12px", border: "1px solid var(--line-2)",
    borderRadius: 8, fontSize: 15, fontWeight: 600, textAlign: "center",
  };

  return (
    <window.UI.Modal open onClose={onCancel} width={480}>
      <div style={{ padding: 26 }}>
        <div style={{ display: "flex", alignItems: "center", gap: 10, marginBottom: 4 }}>
          <div style={{ width: 34, height: 34, borderRadius: 9, background: "var(--teal-soft)", display: "flex", alignItems: "center", justifyContent: "center" }}>
            <window.I.Users size={17} stroke="var(--teal-ink)" />
          </div>
          <div style={{ fontSize: 11, color: "var(--ink-3)", letterSpacing: 0.06, textTransform: "uppercase", fontWeight: 600 }}>
            Marking complete
          </div>
        </div>

        <h2 className="serif" style={{ fontSize: 23, margin: "8px 0 0", letterSpacing: -0.015, lineHeight: 1.2 }}>
          About how many student-athletes did you share this with?
        </h2>

        <div style={{ fontSize: 12.5, color: "var(--ink-2)", lineHeight: 1.5, marginTop: 10, padding: "12px 14px", background: "var(--bg-2)", border: "1px solid var(--line)", borderRadius: 9 }}>
          <div style={{ fontSize: 13, fontWeight: 500, color: "var(--ink)", marginBottom: 3, display: "-webkit-box", WebkitLineClamp: 2, WebkitBoxOrient: "vertical", overflow: "hidden" }}>{task.title}</div>
          We track this to build engagement and student-success insights for your school &amp; district &mdash; it helps us show the real reach of the resources you share. An estimate is perfectly fine.
        </div>

        <div style={{ display: "flex", flexWrap: "wrap", gap: 6, marginTop: 16 }}>
          {presets.map(p => {
            const active = count === p;
            return (
              <button
                key={p}
                onClick={() => setCount(p)}
                style={{
                  padding: "7px 13px", borderRadius: 999, cursor: "pointer",
                  fontSize: 12.5, fontWeight: 500,
                  background: active ? "var(--ink)" : "var(--card)",
                  color: active ? "white" : "var(--ink-2)",
                  border: `1px solid ${active ? "var(--ink)" : "var(--line-2)"}`,
                }}
              >
                {p === 0 ? "None yet" : p === 20 ? "20+" : p}
              </button>
            );
          })}
        </div>

        <div style={{ display: "flex", alignItems: "center", gap: 10, marginTop: 14 }}>
          <div style={{ fontSize: 12.5, color: "var(--ink-3)" }}>Or enter a number:</div>
          <input
            type="number" min={0} max={999} value={count}
            onChange={e => setCount(Math.max(0, parseInt(e.target.value || "0", 10)))}
            style={fld}
          />
          <div style={{ fontSize: 12.5, color: "var(--ink-3)" }}>student-athletes</div>
        </div>

        <div style={{ display: "flex", gap: 10, justifyContent: "flex-end", marginTop: 22, paddingTop: 18, borderTop: "1px solid var(--line)" }}>
          <window.UI.Btn variant="outline" onClick={onCancel}>Cancel</window.UI.Btn>
          <window.UI.Btn onClick={() => onConfirm(task, count)} icon={<window.I.Check size={13} sw={3} />}>
            Mark complete
          </window.UI.Btn>
        </div>
      </div>
    </window.UI.Modal>
  );
}

function RoadmapView() {
  const [done, setDone] = useState(() => {
    try { return new Set(JSON.parse(localStorage.getItem("sasa_roadmap_done") || "[]")); }
    catch { return new Set(); }
  });
  const [openId, setOpenId] = useState("august");
  const [semester, setSemester] = useState("fall");
  const [gradeFilter, setGradeFilter] = useState("All");
  const [pillarFilter, setPillarFilter] = useState("all");
  const [preview, setPreview] = useState(null); // { task, kind }
  const [sharePrompt, setSharePrompt] = useState(null); // task awaiting share count
  const overrides = useRoadmapOverrides();
  const events = useEvents();

  // Latest recorded share count per task (drives the "shared with N" chip)
  const shareByTask = useMemo(() => {
    const m = {};
    events.forEach(e => {
      if (e.type === "share" && e.source === "roadmap") m[e.refId] = e.students;
    });
    return m;
  }, [events]);

  const RTYPE_ASSET = { doc: "doc", video: "video", video_doc: "video", video_doc_sheet: "video", spreadsheet: "sheet", reminder: null };

  useEffect(() => {
    localStorage.setItem("sasa_roadmap_done", JSON.stringify([...done]));
  }, [done]);

  const toggleDone = (id) => {
    setDone(prev => {
      const n = new Set(prev);
      if (n.has(id)) n.delete(id); else n.add(id);
      return n;
    });
  };

  // Completing a task opens the share-count prompt (except plain reminders,
  // which have no resource to share); unchecking is immediate.
  const requestToggle = (task) => {
    if (done.has(task.id)) { toggleDone(task.id); return; }
    if (task.rtype === "reminder") {
      toggleDone(task.id);
      window.trackComplete({ source: "roadmap", refId: task.id, title: task.title, pillar: task.pillar, grade: task.grade, assetType: null });
      return;
    }
    setSharePrompt(task);
  };

  const confirmShare = (task, students) => {
    toggleDone(task.id);
    const assetType = RTYPE_ASSET[task.rtype] || "doc";
    window.trackShare({ source: "roadmap", refId: task.id, title: task.title, pillar: task.pillar, grade: task.grade, assetType, students });
    window.trackComplete({ source: "roadmap", refId: task.id, title: task.title, pillar: task.pillar, grade: task.grade, assetType });
    setSharePrompt(null);
  };

  const filterTasks = (tasks) => tasks.filter(t => {
    if (gradeFilter !== "All" && t.grade !== gradeFilter) return false;
    if (pillarFilter !== "all" && t.pillar !== pillarFilter) return false;
    return true;
  });

  const visibleMonths = ROADMAP.filter(m => m.semester === semester);

  const allTasks = ROADMAP.flatMap(m => m.tasks);
  const totalCount = allTasks.length;
  const doneCount = allTasks.filter(t => done.has(t.id)).length;
  const pct = totalCount ? Math.round((doneCount / totalCount) * 100) : 0;

  const semesterStats = useMemo(() => {
    const stats = {};
    ["fall", "spring"].forEach(s => {
      const tasks = ROADMAP.filter(m => m.semester === s).flatMap(m => m.tasks);
      stats[s] = { total: tasks.length, done: tasks.filter(t => done.has(t.id)).length };
    });
    return stats;
  }, [done]);

  return (
    <>
      <window.TopBar
        title="Counselor's Roadmap"
        subtitle="Month-by-month advising plan — what to send each grade level, and the resource behind it."
        right={
          <div style={{ display: "flex", alignItems: "center", gap: 14 }}>
            <div style={{ fontSize: 12, color: "var(--ink-3)" }}>
              <span style={{ color: "var(--ink)", fontWeight: 600 }}>{doneCount}</span>
              <span> / {totalCount} complete</span>
            </div>
            <div style={{ width: 140 }}>
              <window.UI.Progress value={pct} color="var(--accent)" />
            </div>
          </div>
        }
      />

      <div style={{ padding: 32, display: "flex", flexDirection: "column", gap: 20 }}>

        {/* Semester toggle */}
        <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 10 }}>
          {["fall", "spring"].map(s => {
            const meta = SEMESTER_META[s];
            const stats = semesterStats[s];
            const active = semester === s;
            const semPct = stats.total ? (stats.done / stats.total) * 100 : 0;
            return (
              <button
                key={s}
                onClick={() => { setSemester(s); setOpenId(ROADMAP.find(m => m.semester === s).id); }}
                className="hover-lift"
                style={{
                  textAlign: "left", cursor: "pointer",
                  padding: "18px 22px", borderRadius: 12,
                  background: active ? "var(--ink)" : "var(--card)",
                  color: active ? "white" : "var(--ink)",
                  border: active ? "1px solid var(--ink)" : "1px solid var(--line)",
                  transition: "background 160ms, color 160ms, border-color 160ms",
                  display: "flex", alignItems: "center", gap: 18,
                }}
              >
                <div style={{
                  width: 44, height: 44, borderRadius: 10,
                  background: active ? "rgba(255,255,255,0.12)" : "var(--bg-2)",
                  border: active ? "1px solid rgba(255,255,255,0.18)" : "1px solid var(--line)",
                  display: "flex", alignItems: "center", justifyContent: "center", flexShrink: 0,
                }}>
                  {s === "fall"
                    ? <window.I.Sun size={20} stroke={active ? "var(--accent)" : "var(--ink-2)"} />
                    : <window.I.Sparkles size={20} stroke={active ? "var(--accent)" : "var(--ink-2)"} />}
                </div>
                <div style={{ flex: 1, minWidth: 0 }}>
                  <div style={{ display: "flex", alignItems: "baseline", gap: 10 }}>
                    <div style={{ fontSize: 16, fontWeight: 600, letterSpacing: -0.012 }}>{meta.label}</div>
                    <div style={{ fontSize: 12, opacity: active ? 0.7 : 0.5, fontWeight: 500 }} className="mono">{meta.sub}</div>
                  </div>
                  <div style={{ marginTop: 9, height: 3, borderRadius: 999, background: active ? "rgba(255,255,255,0.16)" : "var(--line)", overflow: "hidden" }}>
                    <div style={{ width: `${semPct}%`, height: "100%", background: "var(--accent)", transition: "width 360ms cubic-bezier(0.2,0.8,0.2,1)" }} />
                  </div>
                </div>
                <div style={{ textAlign: "right", flexShrink: 0 }}>
                  <div style={{ fontSize: 13, fontWeight: 600 }}>{stats.done}/{stats.total}</div>
                  <div style={{ fontSize: 10.5, opacity: active ? 0.65 : 0.5, marginTop: 1 }}>items done</div>
                </div>
              </button>
            );
          })}
        </div>

        {/* Filter strip */}
        <div style={{
          display: "flex", alignItems: "center", gap: 16, flexWrap: "wrap",
          padding: "12px 16px", background: "var(--card)",
          border: "1px solid var(--line)", borderRadius: 10,
        }}>
          <FilterGroup
            label="Grade" value={gradeFilter} onChange={setGradeFilter}
            options={[
              { v: "All", l: "All grades" },
              { v: "9",  l: "9th" },
              { v: "10", l: "10th" },
              { v: "11", l: "11th" },
              { v: "12", l: "12th" },
            ]}
          />
          <div style={{ width: 1, height: 22, background: "var(--line)" }} />
          <FilterGroup
            label="Pillar" value={pillarFilter} onChange={setPillarFilter}
            options={[
              { v: "all",           l: "All pillars" },
              { v: "recruiting",    l: "Recruiting" },
              { v: "mental_health", l: "SEL" },
              { v: "academic",      l: "Academic" },
              { v: "financial_aid", l: "Financial Aid" },
            ]}
          />
          <div style={{ flex: 1 }} />
          <button
            onClick={() => setOpenId(openId === "ALL" ? null : "ALL")}
            style={{
              fontSize: 12.5, color: "var(--ink-2)", fontWeight: 500,
              padding: "6px 10px", border: "1px solid var(--line)",
              borderRadius: 7, background: "var(--card)", cursor: "pointer",
            }}
          >
            {openId === "ALL" ? "Collapse all" : "Expand all"}
          </button>
        </div>

        {/* Semester section header */}
        <div style={{ display: "flex", alignItems: "baseline", gap: 12, marginTop: 4, paddingBottom: 12, borderBottom: "1px solid var(--line)" }}>
          <h2 style={{ fontSize: 22, fontWeight: 600, letterSpacing: -0.018, margin: 0 }} className="serif">
            {SEMESTER_META[semester].label}
          </h2>
          <div style={{ fontSize: 13, color: "var(--ink-3)" }}>
            {SEMESTER_META[semester].sub} · {visibleMonths.length} months
          </div>
        </div>

        {/* Month detail blocks */}
        <div style={{ display: "flex", flexDirection: "column", gap: 14 }}>
          {visibleMonths.map(m => {
            const tasks = filterTasks(m.tasks);
            const isOpen = openId === "ALL" || openId === m.id;
            const monthDone = m.tasks.filter(t => done.has(t.id)).length;
            return (
              <MonthBlock
                key={m.id}
                month={m}
                tasks={tasks}
                totalTasks={m.tasks.length}
                doneCount={monthDone}
                isOpen={isOpen}
                onToggle={() => setOpenId(openId === m.id ? null : m.id)}
                done={done}
                onTaskToggle={requestToggle}
                shareByTask={shareByTask}
                overrides={overrides}
                onOpenAsset={(task, kind) => setPreview({ task, kind })}
              />
            );
          })}
        </div>
      </div>

      {preview && (
        <ResourcePreviewModal
          task={preview.task}
          kind={preview.kind}
          onClose={() => setPreview(null)}
        />
      )}
      {sharePrompt && (
        <ShareCountModal
          task={sharePrompt}
          onCancel={() => setSharePrompt(null)}
          onConfirm={confirmShare}
        />
      )}
    </>
  );
}

function FilterGroup({ label, value, onChange, options }) {
  return (
    <div style={{ display: "flex", alignItems: "center", gap: 10 }}>
      <div style={{ fontSize: 11, letterSpacing: 0.06, textTransform: "uppercase", color: "var(--ink-3)", fontWeight: 600 }}>{label}</div>
      <div style={{ display: "flex", gap: 4, padding: 3, background: "var(--bg-2)", borderRadius: 7, border: "1px solid var(--line)" }}>
        {options.map(o => {
          const active = value === o.v;
          return (
            <button
              key={o.v}
              onClick={() => onChange(o.v)}
              style={{
                padding: "5px 10px", borderRadius: 5,
                background: active ? "var(--card)" : "transparent",
                fontSize: 12, fontWeight: 500, letterSpacing: -0.005,
                color: active ? "var(--ink)" : "var(--ink-3)",
                boxShadow: active ? "0 1px 2px rgba(0,0,0,0.06)" : "none",
                cursor: "pointer",
              }}
            >
              {o.l}
            </button>
          );
        })}
      </div>
    </div>
  );
}

function MonthBlock({ month, tasks, totalTasks, doneCount, isOpen, onToggle, done, onTaskToggle, shareByTask, overrides, onOpenAsset }) {
  // order tasks by grade so counselors read youngest -> oldest
  const ordered = [...tasks].sort((a, b) => Number(a.grade) - Number(b.grade));
  return (
    <div style={{ borderRadius: 14, background: "var(--card)", border: "1px solid var(--line)", overflow: "hidden" }}>
      <button
        onClick={onToggle}
        className="hover-lift"
        style={{
          width: "100%", padding: "20px 22px",
          display: "flex", alignItems: "center", gap: 18,
          background: "transparent", border: "none", cursor: "pointer", textAlign: "left",
        }}
      >
        <div style={{ width: 90, flexShrink: 0 }}>
          <div className="mono" style={{ fontSize: 11, color: "var(--ink-3)", letterSpacing: 0.04, textTransform: "uppercase" }}>Month</div>
          <div className="serif" style={{ fontSize: 21, fontWeight: 600, letterSpacing: -0.015, color: "var(--ink)" }}>{month.label}</div>
        </div>

        <div style={{ width: 1, height: 44, background: "var(--line)" }} />

        <div style={{ flex: 1, minWidth: 0, display: "flex", gap: 6, flexWrap: "wrap" }}>
          {totalTasks === 0
            ? <span style={{ fontSize: 12.5, color: "var(--ink-3)" }}>No scheduled items.</span>
            : Array.from(new Set(month.tasks.map(t => t.pillar))).map(p => (
                <PillarChip key={p} meta={PILLAR_META[p]} />
              ))}
        </div>

        <div style={{ display: "flex", alignItems: "center", gap: 12, flexShrink: 0 }}>
          <div style={{ textAlign: "right" }}>
            <div style={{ fontSize: 12, color: "var(--ink-2)", fontWeight: 500 }}>{doneCount}/{totalTasks}</div>
            <div style={{ fontSize: 10.5, color: "var(--ink-3)", marginTop: 1 }}>items done</div>
          </div>
          <div style={{
            width: 32, height: 32, borderRadius: 999,
            background: "var(--bg-2)", border: "1px solid var(--line)",
            display: "flex", alignItems: "center", justifyContent: "center",
            transition: "transform 0.2s", transform: isOpen ? "rotate(90deg)" : "rotate(0deg)",
          }}>
            <window.I.ChevronRight size={15} stroke="var(--ink-2)" />
          </div>
        </div>
      </button>

      {isOpen && (
        <div style={{ padding: "0 22px 22px", borderTop: "1px solid var(--line)" }}>
          {ordered.length === 0 ? (
            <div style={{ padding: "26px 0", textAlign: "center", fontSize: 12.5, color: "var(--ink-3)" }}>
              No items match the current filters for this month.
            </div>
          ) : (
            <div style={{ display: "flex", flexDirection: "column", gap: 0, marginTop: 8 }}>
              {ordered.map((t, i) => (
                <TaskRow
                  key={t.id}
                  task={t}
                  done={done.has(t.id)}
                  onToggle={onTaskToggle}
                  shareCount={shareByTask?.[t.id]}
                  isLast={i === ordered.length - 1}
                  overrides={overrides}
                  onOpenAsset={onOpenAsset}
                />
              ))}
            </div>
          )}
        </div>
      )}
    </div>
  );
}

function TaskRow({ task: baseTask, done, onToggle, shareCount, isLast, overrides, onOpenAsset }) {
  const task = mergeTask(baseTask, overrides);
  const meta = PILLAR_META[task.pillar];
  const rt = RES_TYPE[task.rtype];
  const assets = assetsFor(task);
  const hasDesc = !!(task.desc && task.desc.trim());
  const [descOpen, setDescOpen] = useState(false);

  return (
    <div style={{ borderBottom: isLast ? "none" : "1px solid var(--line)" }}>
      <div style={{
        display: "flex", alignItems: "flex-start", gap: 14,
        padding: "14px 4px",
      }}>
        <button
          onClick={() => onToggle(task)}
          aria-label={done ? "Mark incomplete" : "Mark complete"}
          style={{
            width: 20, height: 20, borderRadius: 6, marginTop: 1,
            border: done ? "1px solid var(--accent)" : "1.5px solid var(--line-2)",
            background: done ? "var(--accent)" : "var(--card)",
            display: "flex", alignItems: "center", justifyContent: "center",
            cursor: "pointer", flexShrink: 0,
            transition: "background 120ms, border-color 120ms",
          }}
        >
          {done && <window.I.Check size={11} sw={3} stroke="white" />}
        </button>

        <div style={{ fontSize: 12.5, fontWeight: 600, width: 40, flexShrink: 0, color: "var(--ink-2)", marginTop: 1 }}>
          {GRADE_LABEL[task.grade]}
        </div>

        <div style={{ flexShrink: 0, marginTop: 0.5 }}>
          <PillarChip meta={meta} />
        </div>

        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{
            fontSize: 13.5, lineHeight: 1.45,
            color: done ? "var(--ink-3)" : "var(--ink)",
            textDecoration: done ? "line-through" : "none",
            textDecorationColor: "var(--ink-4)",
          }}>
            {task.title}
          </div>

          {/* Downloadable resources — open or download right here */}
          {assets.length > 0 && (
            <div style={{ display: "flex", flexWrap: "wrap", gap: 6, marginTop: 9 }}>
              {assets.map(kind => (
                <AssetChip key={kind} task={task} kind={kind} onOpen={(k) => onOpenAsset(task, k)} />
              ))}
            </div>
          )}

          {/* Recorded reach from the completion prompt */}
          {done && shareCount != null && (
            <div style={{ display: "inline-flex", alignItems: "center", gap: 6, marginTop: 9, fontSize: 11.5, color: "var(--teal-ink)", background: "var(--teal-soft)", padding: "3px 9px", borderRadius: 999, fontWeight: 500 }}>
              <window.I.Users size={12} stroke="var(--teal-ink)" />
              {`Shared with ${shareCount} ${shareCount === 1 ? "student-athlete" : "student-athletes"}`}
            </div>
          )}
        </div>

        {/* Reminder tag (no downloadable assets) + description dropdown toggle */}
        <div style={{ display: "flex", alignItems: "center", gap: 8, flexShrink: 0 }}>
          {assets.length === 0 && <ResTypeBadge rt={rt} />}
          {hasDesc && (
            <button
              onClick={() => setDescOpen(o => !o)}
              aria-label={descOpen ? "Hide description" : "Read description"}
              title={descOpen ? "Hide description" : "Read description"}
              style={{
                width: 26, height: 26, borderRadius: 7, flexShrink: 0,
                border: "1px solid var(--line-2)",
                background: descOpen ? "var(--bg-2)" : "var(--card)",
                display: "flex", alignItems: "center", justifyContent: "center",
                cursor: "pointer", marginTop: 0.5,
                transition: "transform 0.2s, background 0.15s",
                transform: descOpen ? "rotate(90deg)" : "rotate(0deg)",
              }}
            >
              <window.I.ChevronRight size={13} stroke="var(--ink-2)" />
            </button>
          )}
        </div>
      </div>

      {/* Admin-authored description */}
      {hasDesc && descOpen && (
        <div style={{
          margin: "0 4px 14px 88px",
          padding: "12px 14px",
          background: "var(--bg-2)", border: "1px solid var(--line)",
          borderRadius: 9, animation: "slideUp 200ms ease",
        }}>
          <div style={{ display: "flex", alignItems: "center", gap: 6, marginBottom: 6 }}>
            <window.I.Info size={12} stroke="var(--ink-3)" />
            <span style={{ fontSize: 10.5, letterSpacing: 0.06, textTransform: "uppercase", color: "var(--ink-3)", fontWeight: 600 }}>
              Counselor note
            </span>
          </div>
          <div style={{ fontSize: 13, lineHeight: 1.55, color: "var(--ink-2)" }}>{task.desc}</div>
        </div>
      )}
    </div>
  );
}

function PillarChip({ meta }) {
  return (
    <div style={{
      display: "inline-flex", alignItems: "center", gap: 6,
      padding: "3px 9px", borderRadius: 999,
      background: meta.soft, color: meta.ink,
      fontSize: 11, fontWeight: 500, letterSpacing: -0.005,
      whiteSpace: "nowrap", flexShrink: 0,
    }}>
      <span style={{ width: 5, height: 5, borderRadius: 999, background: meta.color }} />
      {meta.label}
    </div>
  );
}

function ResTypeBadge({ rt }) {
  const Icon = window.I[rt.icon];
  return (
    <div style={{
      display: "inline-flex", alignItems: "center", gap: 6,
      padding: "4px 10px", borderRadius: 7,
      background: rt.bg, color: rt.ink,
      fontSize: 11, fontWeight: 600, letterSpacing: 0.01,
      whiteSpace: "nowrap", flexShrink: 0, marginTop: 0.5,
      border: rt.upload ? "none" : "1px dashed var(--line-2)",
    }}>
      <Icon size={12} stroke={rt.ink} />
      {rt.label}
    </div>
  );
}

window.ROADMAP_DATA = ROADMAP;
window.ROADMAP_PILLARS = PILLAR_META;
window.ROADMAP_RES_TYPE = RES_TYPE;
window.RoadmapView = RoadmapView;
