// Degree-plan grid view (semester columns)
function PlanView({ currentUser }) {
  const [semesters, setSemesters] = React.useState([]);
  const [loading, setLoading] = React.useState(true);
  const [saveStatus, setSaveStatus] = React.useState(""); // "" | "saving" | "saved"
  const [searchOpenFor, setSearchOpenFor] = React.useState(null); // index of semester with the add-course box open
  const [editingCourse, setEditingCourse] = React.useState(null); // { semIndex, courseIndex } being swapped for a different course
  const [query, setQuery] = React.useState("");
  const [results, setResults] = React.useState([]);
  const [detailsCourse, setDetailsCourse] = React.useState(null); // the plan-course row that was clicked
  const [detailsData, setDetailsData] = React.useState(null); // matching catalog entry, if any
  const [detailsLoading, setDetailsLoading] = React.useState(false);
  const dragRef = React.useRef(null); // { semIndex, code }
  const [dragOverIndex, setDragOverIndex] = React.useState(null);
  const searchTimer = React.useRef(null);
  const saveTimer = React.useRef(null);
  const clickTimer = React.useRef(null); // distinguishes a single click (details) from a double-click (swap)
  const suppressClickUntil = React.useRef(0); // guards against the tail of a swap-selection click landing on the newly-rendered card
  const loadedRef = React.useRef(false); // guards against saving the plan back over itself right after loading it

  React.useEffect(() => () => { if (clickTimer.current) clearTimeout(clickTimer.current); }, []);

  React.useEffect(() => {
    const token = localStorage.getItem("token");
    fetch(`${window.API_BASE}/api/plan`, { headers: { "Authorization": "Bearer " + token } })
      .then(res => res.json())
      .then(data => {
        setSemesters(data.semesters || []);
        loadedRef.current = true;
      })
      .catch(() => { loadedRef.current = true; })
      .finally(() => setLoading(false));
  }, []);

  React.useEffect(() => {
    if (!loadedRef.current) return; // don't save the plan we just loaded
    setSaveStatus("saving");
    if (saveTimer.current) clearTimeout(saveTimer.current);
    saveTimer.current = setTimeout(async () => {
      try {
        const token = localStorage.getItem("token");
        await fetch(`${window.API_BASE}/api/plan`, {
          method: "PUT",
          headers: { "Authorization": "Bearer " + token, "Content-Type": "application/json" },
          body: JSON.stringify({ semesters }),
        });
        setSaveStatus("saved");
        // Let the context panel's Plan Timeline card know it's stale, so an edit here
        // shows up there without the student having to reload the page.
        if (window.__refreshPlanTimeline) window.__refreshPlanTimeline();
      } catch (e) {
        setSaveStatus("");
      }
    }, 600);
    return () => clearTimeout(saveTimer.current);
  }, [semesters]);

  // Placeholder rows from a Major Map (e.g. "Minor, Cognate, or Elective") can repeat the
  // same label within one semester, so `code` isn't a reliable identity — index is.
  function removeCourse(semIndex, courseIndex) {
    setSemesters(prev => prev.map((p, i) =>
      i === semIndex ? { ...p, courses: p.courses.filter((_, ci) => ci !== courseIndex) } : p
    ));
  }

  function addCourse(semIndex, course) {
    suppressClickUntil.current = Date.now() + 400;
    setSemesters(prev => prev.map((p, i) =>
      i === semIndex && !p.courses.some(c => c.code === course.code)
        ? { ...p, courses: [...p.courses, course] }
        : p
    ));
    setSearchOpenFor(null);
    setQuery("");
    setResults([]);
  }

  function moveCourse(fromIndex, toIndex, courseIndex) {
    if (fromIndex === toIndex) return;
    setSemesters(prev => {
      const moving = prev[fromIndex].courses[courseIndex];
      if (!moving) return prev;
      return prev.map((p, i) => {
        if (i === fromIndex) return { ...p, courses: p.courses.filter((_, ci) => ci !== courseIndex) };
        if (i === toIndex) return { ...p, courses: [...p.courses, moving] };
        return p;
      });
    });
  }

  function openSearch(semIndex) {
    setSearchOpenFor(semIndex);
    setQuery("");
    setResults([]);
  }

  function openEdit(semIndex, courseIndex) {
    setEditingCourse({ semIndex, courseIndex });
    setQuery("");
    setResults([]);
  }

  function replaceCourse(semIndex, courseIndex, course) {
    // The swapped-in course re-renders as a normal clickable card at the same on-screen
    // slot immediately, while this same mouse interaction is still finishing (mouseup/click
    // after this mousedown) — without this guard, that tail end can land on the new card
    // and trigger its single-click "open details" handler.
    suppressClickUntil.current = Date.now() + 400;
    setSemesters(prev => prev.map((p, i) =>
      i === semIndex
        ? { ...p, courses: p.courses.map((c, ci) => ci === courseIndex ? course : c) }
        : p
    ));
    setEditingCourse(null);
    setQuery("");
    setResults([]);
  }

  async function openDetails(course) {
    if (clickTimer.current) { clearTimeout(clickTimer.current); clickTimer.current = null; }
    setDetailsCourse(course);
    setDetailsData(null);
    setDetailsLoading(true);
    try {
      const token = localStorage.getItem("token");
      const res = await fetch(`${window.API_BASE}/api/courses/search?q=${encodeURIComponent(course.code)}`, {
        headers: { "Authorization": "Bearer " + token },
      });
      const data = await res.json();
      const list = Array.isArray(data) ? data : [];
      const exact = list.find(d => (d.code || "").toUpperCase() === (course.code || "").toUpperCase());
      setDetailsData(exact || list[0] || null);
    } catch (e) {
      setDetailsData(null);
    } finally {
      setDetailsLoading(false);
    }
  }

  function handleCourseClick(course) {
    if (Date.now() < suppressClickUntil.current) return;
    if (clickTimer.current) clearTimeout(clickTimer.current);
    clickTimer.current = setTimeout(() => {
      clickTimer.current = null;
      openDetails(course);
    }, 220);
  }

  function handleCourseDoubleClick(semIndex, courseIndex) {
    if (clickTimer.current) { clearTimeout(clickTimer.current); clickTimer.current = null; }
    openEdit(semIndex, courseIndex);
  }

  function runSearch(value) {
    setQuery(value);
    if (searchTimer.current) clearTimeout(searchTimer.current);
    if (!value.trim()) { setResults([]); return; }
    searchTimer.current = setTimeout(async () => {
      try {
        const token = localStorage.getItem("token");
        const res = await fetch(`${window.API_BASE}/api/courses/search?q=${encodeURIComponent(value)}`, {
          headers: { "Authorization": "Bearer " + token },
        });
        const data = await res.json();
        setResults(Array.isArray(data) ? data : []);
      } catch (e) {
        setResults([]);
      }
    }, 200);
  }

  // "Remaining (unscheduled)" is a staging bucket for not-yet-placed courses,
  // not a real term — it shouldn't count toward "N-semester plan."
  const scheduledCount = semesters.filter(s => !s.unscheduled).length;

  function handleExport() {
    const { jsPDF } = window.jspdf;
    const doc = new jsPDF({ unit: "pt", format: "letter" });
    const marginX = 48;
    const rightEdge = 564;
    const pageHeight = doc.internal.pageSize.getHeight();
    let y = 56;

    doc.setFont("helvetica", "bold");
    doc.setFontSize(18);
    doc.setTextColor(20);
    doc.text("Degree Plan", marginX, y);
    y += 22;

    doc.setFont("helvetica", "normal");
    doc.setFontSize(10);
    doc.setTextColor(90);
    const subtitle = [currentUser?.name, currentUser?.major].filter(Boolean).join("  ·  ");
    if (subtitle) { doc.text(subtitle, marginX, y); y += 14; }
    const dateStr = new Date().toLocaleDateString(undefined, { year: "numeric", month: "long", day: "numeric" });
    doc.text(`Generated ${dateStr}`, marginX, y);
    y += 28;

    semesters.forEach((sem) => {
      if (y > pageHeight - 90) { doc.addPage(); y = 56; }

      doc.setFont("helvetica", "bold");
      doc.setFontSize(13);
      doc.setTextColor(20);
      doc.text(sem.term, marginX, y);
      doc.setFont("helvetica", "normal");
      doc.setFontSize(9);
      doc.setTextColor(120);
      doc.text((sem.status || "").toUpperCase(), rightEdge, y, { align: "right" });
      y += 8;
      doc.setDrawColor(210);
      doc.line(marginX, y, rightEdge, y);
      y += 16;

      sem.courses.forEach((c) => {
        if (y > pageHeight - 70) { doc.addPage(); y = 56; }
        doc.setFont("helvetica", "bold");
        doc.setFontSize(10.5);
        doc.setTextColor(20);
        doc.text(c.code || "", marginX, y);
        doc.setFont("helvetica", "normal");
        doc.setTextColor(70);
        doc.text(c.title || "", marginX + 85, y, { maxWidth: 380 });
        doc.text(`${c.cr} cr`, rightEdge, y, { align: "right" });
        y += 16;
      });

      const total = sem.courses.reduce((s, c) => s + (c.cr || 0), 0);
      doc.setFont("helvetica", "bold");
      doc.setFontSize(9.5);
      doc.setTextColor(90);
      doc.text(`credits: ${total}`, rightEdge, y, { align: "right" });
      y += 26;
    });

    doc.setFont("helvetica", "italic");
    doc.setFontSize(8.5);
    doc.setTextColor(140);
    const footer = "Unofficial plan generated by Carolina AI — verify all course details in Self-Service Carolina before registering.";
    doc.text(footer, marginX, pageHeight - 30, { maxWidth: rightEdge - marginX });

    const nameSlug = (currentUser?.name || "student").replace(/[^a-z0-9]+/gi, "_").toLowerCase();
    doc.save(`${nameSlug}_degree_plan.pdf`);
  }

  return (
    <div className="view-wrap">
      <div className="view-head">
        <div>
          <h2>{scheduledCount > 0 ? `${scheduledCount}-semester ` : ""}<em>plan</em></h2>
          <p>
            Drag courses between terms to see how your graduation date shifts.{" "}
            {saveStatus === "saving" && "Saving…"}
            {saveStatus === "saved" && "Saved."}
            {!saveStatus && "Drafts auto-save."}
          </p>
        </div>
        <div className="view-actions">
          <button className="btn" onClick={handleExport} disabled={semesters.length === 0}>Export PDF</button>
        </div>
      </div>

      {loading && <p className="chip-empty">Loading your plan…</p>}

      {!loading && semesters.length === 0 && (
        <p className="chip-empty">
          No plan yet — set your major in your profile, or use "+ add course" once semesters appear here.
        </p>
      )}

      <div className="plan-grid">
        {semesters.map((p, i) => {
          const total = p.courses.reduce((s, c) => s + c.cr, 0);
          return (
            <div
              key={i}
              className={`sem-card ${p.status === "current" ? "current" : ""} ${dragOverIndex === i ? "drag-over" : ""}`}
              onDragOver={(e) => { e.preventDefault(); e.dataTransfer.dropEffect = "move"; if (dragOverIndex !== i) setDragOverIndex(i); }}
              onDragLeave={(e) => { if (!e.currentTarget.contains(e.relatedTarget)) setDragOverIndex(null); }}
              onDrop={(e) => {
                e.preventDefault();
                const dragged = dragRef.current;
                if (dragged) moveCourse(dragged.semIndex, i, dragged.courseIndex);
                dragRef.current = null;
                setDragOverIndex(null);
              }}
            >
              <div className="sem-head">
                <h3>{p.term}</h3>
                <span className="credits">{p.status}</span>
              </div>
              {p.courses.map((c, ci) => (
                editingCourse && editingCourse.semIndex === i && editingCourse.courseIndex === ci ? (
                  <CourseSearchBox
                    key={`edit-${ci}`}
                    query={query}
                    results={results}
                    runSearch={runSearch}
                    onSelect={(course) => replaceCourse(i, ci, course)}
                    onClose={() => setEditingCourse(null)}
                  />
                ) : (
                  <div
                    key={`${c.code}-${ci}`}
                    className="sem-course"
                    draggable
                    onDragStart={(e) => {
                      e.dataTransfer.effectAllowed = "move";
                      dragRef.current = { semIndex: i, courseIndex: ci };
                    }}
                    onDragEnd={() => { dragRef.current = null; setDragOverIndex(null); }}
                    onClick={() => handleCourseClick(c)}
                    onDoubleClick={() => handleCourseDoubleClick(i, ci)}
                    style={{ cursor: "pointer" }}
                    title="Click for details · double-click to swap"
                  >
                    <span className="code">
                      {c.code}
                      <span
                        onClick={(e) => { e.stopPropagation(); removeCourse(i, ci); }}
                        style={{ float: "right", cursor: "pointer", color: "var(--ink-4)" }}
                        title="Remove"
                      >×</span>
                    </span>
                    <span className="title">{c.title}</span>
                    <span className="cr">{c.cr} cr</span>
                  </div>
                )
              ))}

              {searchOpenFor === i ? (
                <CourseSearchBox
                  query={query}
                  results={results}
                  runSearch={runSearch}
                  onSelect={(course) => addCourse(i, course)}
                  onClose={() => setSearchOpenFor(null)}
                />
              ) : (
                <button className="sem-course ghost" onClick={() => openSearch(i)}>+ add course</button>
              )}

              <div className="sem-total">
                <span>credits</span>
                <span>{total}</span>
              </div>
            </div>
          );
        })}
      </div>

      {detailsCourse && (
        <CourseDetailsModal
          course={detailsCourse}
          data={detailsData}
          loading={detailsLoading}
          onClose={() => setDetailsCourse(null)}
        />
      )}
    </div>
  );
}

// Search-and-pick dropdown, reused for both "+ add course" (append) and
// double-click-to-swap (replace) — the caller decides what onSelect does.
function CourseSearchBox({ query, results, runSearch, onSelect, onClose }) {
  return (
    <div className="sem-course" style={{ cursor: "default" }}>
      <input
        autoFocus
        value={query}
        onChange={(e) => runSearch(e.target.value)}
        onKeyDown={(e) => { if (e.key === "Escape") onClose(); }}
        onBlur={() => setTimeout(onClose, 150)}
        placeholder="Search code or title…"
        style={{ border: "none", outline: "none", background: "transparent", font: "inherit", width: "100%" }}
      />
      {results.length > 0 && (
        <div style={{ marginTop: 6, display: "flex", flexDirection: "column", gap: 4 }}>
          {results.map(r => (
            <div
              key={r.code}
              onMouseDown={() => onSelect({ code: r.code, title: r.title, cr: r.credits })}
              style={{ cursor: "pointer", fontSize: 12, padding: "4px 6px", borderRadius: 4 }}
              onMouseEnter={(e) => e.currentTarget.style.background = "var(--card-2)"}
              onMouseLeave={(e) => e.currentTarget.style.background = "transparent"}
            >
              <span className="code">{r.code}</span> — {r.title}
            </div>
          ))}
        </div>
      )}
    </div>
  );
}

function CourseDetailsModal({ course, data, loading, onClose }) {
  const badgeStyle = { fontFamily: "var(--mono)", fontSize: 11, fontWeight: 600, color: "var(--ink-3)", background: "var(--card-2)", padding: "3px 8px", borderRadius: 999 };
  const labelStyle = { fontSize: 11, fontWeight: 700, textTransform: "uppercase", letterSpacing: "0.06em", color: "var(--ink-3)", marginBottom: 4 };

  return (
    <div
      style={{ position: "fixed", inset: 0, background: "rgba(20,10,10,0.45)", zIndex: 60, display: "flex", alignItems: "center", justifyContent: "center", padding: 20 }}
      onClick={onClose}
    >
      <div
        style={{ background: "var(--card)", borderRadius: "var(--r-lg)", maxWidth: 480, width: "100%", maxHeight: "80vh", overflowY: "auto", padding: 24, boxShadow: "0 24px 60px -20px rgba(0,0,0,0.4)" }}
        onClick={(e) => e.stopPropagation()}
      >
        <div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start", marginBottom: 14 }}>
          <div>
            <div style={{ fontFamily: "var(--mono)", fontSize: 12, color: "var(--accent)", letterSpacing: "0.06em", fontWeight: 700 }}>{course.code}</div>
            <h3 style={{ fontFamily: "var(--serif)", fontSize: 20, fontWeight: 600, marginTop: 4 }}>{data?.title || course.title}</h3>
          </div>
          <button onClick={onClose} className="icon-btn" title="Close"><I.x /></button>
        </div>

        <div style={{ display: "flex", gap: 8, marginBottom: 18 }}>
          <span style={badgeStyle}>{data?.credits ?? course.cr} cr</span>
          {data?.department && <span style={badgeStyle}>{data.department}</span>}
        </div>

        {loading && <p className="chip-empty">Loading course details…</p>}

        {!loading && data && (
          <React.Fragment>
            {data.description && (
              <div style={{ marginBottom: 16 }}>
                <div style={labelStyle}>Description</div>
                <p style={{ fontSize: 14, lineHeight: 1.6, color: "var(--ink-2)" }}>{data.description}</p>
              </div>
            )}
            <div>
              <div style={labelStyle}>Prerequisites</div>
              <p style={{ fontSize: 14, lineHeight: 1.6, color: "var(--ink-2)" }}>
                {data.prerequisites?.text || "None listed."}
              </p>
            </div>
          </React.Fragment>
        )}

        {!loading && !data && (
          <p className="chip-empty">
            No catalog entry on file for {course.code}. It may be a placeholder or program-specific
            course — check the official Bulletin or Self-Service Carolina for full details.
          </p>
        )}
      </div>
    </div>
  );
}

window.PlanView = PlanView;
