// ScheduleBuilder — the Plan tab's registration assistant for a single
// registerable (currently-open) term. For each planned course it lets the
// student pick a real section (instructor + meeting times from the scraped
// SOCCCD class schedule), renders a weekly calendar of the picks, flags
// time conflicts, and lays out the steps to actually register in MySite.
//
// Data in:  /api/sections (one fetch per course in the term).
// Data out: profile.sectionPicks via onPick(courseKey, sectionOrNull) — the
//           picked section is stored minimally so the calendar re-renders
//           after a reload without re-fetching.
// We store NO ratings here; instructor names link out to Rate My Professors.

const { useState, useEffect, useMemo } = React;

// ── pure time/day helpers ──────────────────────────────────────────────────
// Day codes as SmartSchedule/Banner publish them: M T W R(Thu) F S(Sat) U(Sun).
const SB_DAYS = [
  { code: 'M', label: 'Mon' }, { code: 'T', label: 'Tue' }, { code: 'W', label: 'Wed' },
  { code: 'R', label: 'Thu' }, { code: 'F', label: 'Fri' }, { code: 'S', label: 'Sat' },
  { code: 'U', label: 'Sun' },
];
function sbToMin(t) {
  const m = /^(\d{1,2}):(\d{2})$/.exec((t || '').trim());
  return m ? (+m[1]) * 60 + (+m[2]) : null;
}
function sbExpandDays(s) {
  return (s || '').toUpperCase().replace(/[^MTWRFSU]/g, '').split('').filter(Boolean);
}
function sbFmtTime(min) {
  if (min == null) return '';
  let h = Math.floor(min / 60), m = min % 60;
  const ap = h >= 12 ? 'pm' : 'am';
  h = h % 12 || 12;
  return `${h}:${String(m).padStart(2, '0')}${ap}`;
}
// A one-line "MW 10:00am–11:50am · In Person" summary of a section's meetings.
function sbMeetingSummary(sec) {
  const ms = (sec && sec.meetings) || [];
  if (!ms.length) return 'No meeting times listed (online/async)';
  return ms.map(mt => {
    const s = sbToMin(mt.start), e = sbToMin(mt.end);
    const when = (mt.days || '?') + (s != null ? ` ${sbFmtTime(s)}–${sbFmtTime(e)}` : '');
    return when + (mt.modality ? ` · ${mt.modality}` : '');
  }).join(' · ');
}

// Short location summary (first non-empty room) for the dropdown line.
function sbLocSummary(sec) {
  const loc = ((sec && sec.meetings) || []).map(m => m.location).find(Boolean);
  return loc || '';
}

// Full detail for a chosen section: CRN, units, status, length, waitlist, seats,
// ZTC, every meeting (day/time/room/modality), and section notes.
function sbSectionDetail(sec) {
  const chip = (txt, tone) => (
    <span style={{
      fontSize: '0.58rem', fontWeight: 700, padding: '1px 6px', borderRadius: 999, whiteSpace: 'nowrap',
      background: tone === 'warn' ? 'rgba(200,60,60,0.12)' : tone === 'good' ? 'rgba(43,179,163,0.14)' : 'var(--accent-bg, rgba(124,92,255,0.1))',
      color: tone === 'warn' ? 'var(--red)' : tone === 'good' ? '#1f8f82' : 'var(--accent)',
    }}>{txt}</span>
  );
  const chips = [chip(`CRN ${sec.section_crn}`)];
  if (sec.units != null) chips.push(chip(`${sec.units} units`));
  if (sec.status) chips.push(chip(sec.status, sec.status === 'open' ? 'good' : sec.status === 'closed' ? 'warn' : null));
  if (sec.weeks && sec.weeks !== 16) chips.push(chip(`${sec.weeks}-week`));
  if (sec.waitlist_count) chips.push(chip(`${sec.waitlist_count} waitlisted`, 'warn'));
  if (sec.seats_total) chips.push(chip(`cap ${sec.seats_total}`));
  if (sec.zero_textbook_cost) chips.push(chip('Zero textbook cost', 'good'));
  const ms = sec.meetings || [];
  return (
    <div style={{ fontSize: '0.66rem', color: 'var(--text-2)', display: 'flex', flexDirection: 'column', gap: 4, paddingLeft: 70 }}>
      <div style={{ display: 'flex', gap: 4, flexWrap: 'wrap' }}>
        {chips.map((c, i) => <React.Fragment key={i}>{c}</React.Fragment>)}
      </div>
      {ms.map((m, i) => {
        const s = sbToMin(m.start);
        return (
          <div key={i}>
            {m.days || (m.modality === 'Online' ? 'Online / async' : 'TBA')}
            {s != null ? ` · ${sbFmtTime(s)}–${sbFmtTime(sbToMin(m.end))}` : ''}
            {m.location ? ` · ${m.location}` : ''}
            {m.modality ? ` · ${m.modality}` : ''}
          </div>
        );
      })}
      {sec.notes ? <div style={{ fontStyle: 'italic', color: 'var(--text-3)' }}>{sec.notes}</div> : null}
    </div>
  );
}

function ScheduleBuilder({ semId, termCode, courses, instId, apiBase, picks, onPick }) {
  const courseList = courses || [];
  const [byCourse, setByCourse] = useState({});   // courseKey -> [section]
  const [loading, setLoading] = useState(true);
  const [showSteps, setShowSteps] = useState(false);

  // Fetch sections for every course in this term.
  useEffect(() => {
    let cancelled = false;
    setLoading(true);
    const split = (k) => { const i = k.lastIndexOf(' '); return i < 0 ? null : [k.slice(0, i), k.slice(i + 1)]; };
    Promise.all(courseList.map(k => {
      const p = split(k);
      if (!p) return Promise.resolve([k, []]);
      const url = `${apiBase}/api/sections?institution_id=${instId}`
        + `&term=${encodeURIComponent(termCode)}`
        + `&prefix=${encodeURIComponent(p[0])}&course_number=${encodeURIComponent(p[1])}`;
      return fetch(url).then(r => r.ok ? r.json() : []).then(d => [k, Array.isArray(d) ? d : []])
        .catch(() => [k, []]);
    })).then(pairs => {
      if (cancelled) return;
      const m = {}; pairs.forEach(([k, v]) => { m[k] = v; });
      setByCourse(m); setLoading(false);
    });
    return () => { cancelled = true; };
  }, [termCode, instId, apiBase, courseList.join(',')]);

  // Picks belonging to THIS term, in course order.
  const picked = useMemo(() => courseList
    .map(k => ({ key: k, sec: (picks || {})[k] }))
    .filter(x => x.sec && x.sec.term_code === termCode), [courseList, picks, termCode]);

  // Flatten picked meetings into day-blocks; detect cross-course overlaps.
  const { blocks, conflictKeys, minStart, maxEnd, activeDays } = useMemo(() => {
    const blocks = [];
    picked.forEach(({ key, sec }) => ((sec.meetings) || []).forEach(mt => {
      const s = sbToMin(mt.start), e = sbToMin(mt.end);
      if (s == null || e == null) return;
      sbExpandDays(mt.days).forEach(day => blocks.push({
        key, day, start: s, end: e, loc: mt.location, modality: mt.modality,
      }));
    }));
    const conflictKeys = new Set();
    for (let i = 0; i < blocks.length; i++) {
      for (let j = i + 1; j < blocks.length; j++) {
        const a = blocks[i], b = blocks[j];
        if (a.key !== b.key && a.day === b.day && a.start < b.end && b.start < a.end) {
          conflictKeys.add(a.key); conflictKeys.add(b.key);
        }
      }
    }
    const starts = blocks.map(b => b.start), ends = blocks.map(b => b.end);
    const minStart = starts.length ? Math.min(...starts) : 8 * 60;
    const maxEnd = ends.length ? Math.max(...ends) : 15 * 60;
    const activeDays = SB_DAYS.filter(d =>
      d.code !== 'U' && (['M', 'T', 'W', 'R', 'F'].includes(d.code) || blocks.some(b => b.day === d.code)));
    return { blocks, conflictKeys, minStart, maxEnd, activeDays };
  }, [picked]);

  // A stable color per picked course (for the calendar blocks + legend).
  const colorOf = useMemo(() => {
    const palette = ['#7c5cff', '#2bb3a3', '#e08a2b', '#c0477c', '#3d7fd6', '#6a9a3b'];
    const m = {}; picked.forEach((p, i) => { m[p.key] = palette[i % palette.length]; });
    return m;
  }, [picked]);

  const pick = (key, sec) => {
    if (!sec) { onPick(key, null); return; }
    onPick(key, {
      term_code: termCode, section_crn: sec.section_crn,
      instructor_name: sec.instructor_name, meetings: sec.meetings || [],
      status: sec.status, units: sec.units, course_title: sec.course_title,
      rmp_search_url: sec.rmp_search_url,
    });
  };

  const noDataAtAll = !loading && courseList.every(k => !(byCourse[k] || []).length);
  if (loading) {
    return <div style={{ padding: '10px 14px', fontSize: '0.72rem', color: 'var(--text-3)' }}>Loading sections…</div>;
  }
  if (noDataAtAll) {
    // Term is "open" but we haven't scraped sections for these specific courses.
    return (
      <div style={{ padding: '8px 14px 12px', fontSize: '0.72rem', color: 'var(--text-3)' }}>
        No published sections for these courses in {window.tlTermLabel ? window.tlTermLabel(semId) : semId} yet.
      </div>
    );
  }

  // Pixel scale for the calendar (minutes → px), padded to the half hour.
  const PX = 0.8;
  const top0 = Math.floor(minStart / 30) * 30;
  const bot0 = Math.ceil(maxEnd / 30) * 30;
  const gridH = Math.max(120, (bot0 - top0) * PX);
  const hourLines = [];
  for (let t = Math.ceil(top0 / 60) * 60; t <= bot0; t += 60) hourLines.push(t);

  const pickedCrns = picked.map(p => p.sec.section_crn).filter(Boolean);

  return (
    <div className="pb-sched" style={{
      margin: '2px 10px 12px', padding: '12px 14px', borderRadius: 12,
      border: '1px solid var(--border)', background: 'var(--bg-2, rgba(124,92,255,0.03))',
    }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 10 }}>
        <span style={{ fontWeight: 700, fontSize: '0.78rem' }}>Build your schedule</span>
        <span style={{ fontSize: '0.64rem', color: 'var(--text-3)' }}>
          · {window.tlTermLabel ? window.tlTermLabel(semId) : semId} · registration open
        </span>
        {conflictKeys.size > 0 && (
          <span style={{
            marginLeft: 'auto', fontSize: '0.66rem', fontWeight: 700, color: 'var(--red)',
            display: 'inline-flex', alignItems: 'center', gap: 4,
          }}>⚠ Time conflict</span>
        )}
      </div>

      {/* ── per-course section pickers ── */}
      <div style={{ display: 'flex', flexDirection: 'column', gap: 8, marginBottom: 12 }}>
        {courseList.map(key => {
          const secs = byCourse[key] || [];
          const cur = (picks || {})[key];
          const curCrn = cur && cur.term_code === termCode ? cur.section_crn : '';
          const conflicted = conflictKeys.has(key);
          const chosen = secs.find(s => String(s.section_crn) === String(curCrn)) || null;
          return (
            <div key={key} style={{
              display: 'flex', flexDirection: 'column', gap: 6,
              padding: '6px 8px', borderRadius: 8,
              border: `1px solid ${conflicted ? 'var(--red)' : 'var(--border)'}`,
              background: 'var(--bg-1, #fff)',
            }}>
              <div style={{ display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' }}>
                <span style={{
                  fontWeight: 700, fontSize: '0.72rem', minWidth: 62,
                  borderLeft: `3px solid ${colorOf[key] || 'transparent'}`, paddingLeft: 6,
                }}>{key}</span>
                {secs.length === 0 ? (
                  <span style={{ fontSize: '0.68rem', color: 'var(--text-3)' }}>no sections listed this term</span>
                ) : (
                  <select
                    value={curCrn}
                    onChange={(e) => pick(key, secs.find(s => String(s.section_crn) === e.target.value) || null)}
                    style={{
                      flex: 1, minWidth: 180, fontSize: '0.68rem', padding: '4px 6px',
                      borderRadius: 6, border: '1px solid var(--border)', background: 'var(--bg-1,#fff)',
                      color: 'var(--text-1)',
                    }}
                  >
                    <option value="">Choose a section…</option>
                    {secs.map(s => {
                      const loc = sbLocSummary(s);
                      return (
                        <option key={s.section_crn} value={s.section_crn}>
                          {`CRN ${s.section_crn} · ${s.instructor_name || 'Staff'} · ${sbMeetingSummary(s)}`
                            + (loc ? ` · ${loc}` : '') + (s.status ? ` · ${s.status}` : '')}
                        </option>
                      );
                    })}
                  </select>
                )}
                {chosen && chosen.rmp_search_url && (
                  <a href={chosen.rmp_search_url} target="_blank" rel="noopener noreferrer"
                     title="See this professor's ratings on Rate My Professors (new tab)"
                     style={{ fontSize: '0.66rem', color: 'var(--accent)', textDecoration: 'none', whiteSpace: 'nowrap' }}>
                    ratings ↗
                  </a>
                )}
              </div>
              {chosen && sbSectionDetail(chosen)}
            </div>
          );
        })}
      </div>

      {/* ── weekly calendar ── */}
      {blocks.length > 0 && (
        <div style={{ display: 'flex', gap: 2, marginBottom: 12 }}>
          {/* hour gutter */}
          <div style={{ position: 'relative', width: 44, height: gridH, flex: '0 0 auto' }}>
            {hourLines.map(t => (
              <div key={t} style={{
                position: 'absolute', top: (t - top0) * PX - 6, right: 4,
                fontSize: '0.58rem', color: 'var(--text-3)',
              }}>{sbFmtTime(t)}</div>
            ))}
          </div>
          {activeDays.map(d => (
            <div key={d.code} style={{ flex: 1, minWidth: 0 }}>
              <div style={{ textAlign: 'center', fontSize: '0.62rem', fontWeight: 700, color: 'var(--text-2)', marginBottom: 3 }}>
                {d.label}
              </div>
              <div style={{
                position: 'relative', height: gridH, borderRadius: 6,
                border: '1px solid var(--border)', background: 'var(--bg-1,#fff)',
                overflow: 'hidden',
              }}>
                {hourLines.map(t => (
                  <div key={t} style={{
                    position: 'absolute', left: 0, right: 0, top: (t - top0) * PX,
                    borderTop: '1px solid var(--border)', opacity: 0.5,
                  }} />
                ))}
                {blocks.filter(b => b.day === d.code).map((b, i) => {
                  const bad = conflictKeys.has(b.key);
                  return (
                    <div key={b.key + i} title={`${b.key}${b.loc ? ' · ' + b.loc : ''}${bad ? ' · conflict' : ''}`}
                      style={{
                        position: 'absolute', left: 2, right: 2,
                        top: (b.start - top0) * PX, height: Math.max(14, (b.end - b.start) * PX),
                        background: colorOf[b.key] || 'var(--accent)',
                        outline: bad ? '2px solid var(--red)' : 'none',
                        borderRadius: 4, color: '#fff', fontSize: '0.56rem', lineHeight: 1.1,
                        padding: '2px 3px', overflow: 'hidden', boxShadow: bad ? '0 0 0 1px var(--red)' : 'none',
                      }}>
                      <div style={{ fontWeight: 700 }}>{b.key}</div>
                      <div style={{ opacity: 0.9 }}>{sbFmtTime(b.start)}</div>
                    </div>
                  );
                })}
              </div>
            </div>
          ))}
        </div>
      )}

      {conflictKeys.size > 0 && (
        <div style={{
          fontSize: '0.68rem', color: 'var(--red)', marginBottom: 10, fontWeight: 600,
        }}>
          {[...conflictKeys].join(' and ')} overlap. Pick different sections to resolve the conflict before registering.
        </div>
      )}

      {/* ── registration steps ── */}
      <button
        type="button"
        onClick={() => setShowSteps(v => !v)}
        style={{
          fontSize: '0.7rem', fontWeight: 700, color: 'var(--accent)', background: 'none',
          border: 'none', cursor: 'pointer', padding: 0,
        }}
      >
        {showSteps ? '▴' : '▾'} How to register {pickedCrns.length ? `(${pickedCrns.length} section${pickedCrns.length > 1 ? 's' : ''} picked)` : ''}
      </button>
      {showSteps && (
        <ol style={{ margin: '8px 0 0', paddingLeft: 18, fontSize: '0.7rem', color: 'var(--text-2)', lineHeight: 1.5 }}>
          <li>Resolve any time conflicts above so your picks don't overlap.</li>
          <li>Note your CRNs{pickedCrns.length ? <>: <b>{pickedCrns.join(', ')}</b></> : ' (the number beside each picked section)'}.</li>
          <li>Open <a href="https://mysite.socccd.edu/" target="_blank" rel="noopener noreferrer" style={{ color: 'var(--accent)' }}>MySite</a> and sign in, then go to <i>Registration → Register / Add / Drop</i>.</li>
          <li>Enter each CRN in the "Add by CRN" box for {window.tlTermLabel ? window.tlTermLabel(semId) : semId} and submit.</li>
          <li>Confirm there are no holds or unmet prerequisites, then finalize — a waitlisted section won't enroll you until a seat opens.</li>
        </ol>
      )}
      <div style={{ fontSize: '0.6rem', color: 'var(--text-3)', marginTop: 8 }}>
        Sections are scraped from the public SOCCCD schedule and may change. TransferLens can't register you — you do that in MySite.
      </div>
    </div>
  );
}

Object.assign(window, { ScheduleBuilder });
