/* global React */
const { useState, useEffect, useMemo, useRef } = React;

// ============================================================
// Icons (inline SVG; minimal, no flourishes)
// ============================================================
const Icon = {
  Search: () =>
  <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5">
      <circle cx="7" cy="7" r="4.5" />
      <path d="M10.5 10.5 14 14" strokeLinecap="round" />
    </svg>,

  Plus: () =>
  <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.6">
      <path d="M8 3v10M3 8h10" strokeLinecap="round" />
    </svg>,

  Arrow: ({ dir = 'right' }) =>
  <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5"
  style={{ transform: dir === 'left' ? 'scaleX(-1)' : 'none' }}>
      <path d="M3 8h10M9 4l4 4-4 4" strokeLinecap="round" strokeLinejoin="round" />
    </svg>,

  Back: () =>
  <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5">
      <path d="M13 8H3M7 4 3 8l4 4" strokeLinecap="round" strokeLinejoin="round" />
    </svg>,

  X: () =>
  <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5">
      <path d="m3.5 3.5 9 9M12.5 3.5l-9 9" strokeLinecap="round" />
    </svg>,

  Pencil: () =>
  <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.4">
      <path d="m11.5 2.5 2 2L5 13l-3 1 1-3 8.5-8.5Z" strokeLinejoin="round" />
    </svg>,

  Trash: () =>
  <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.4">
      <path d="M3 4h10M6 4V2.5h4V4M5 4l.5 9h5L11 4M6.5 6.5v5M9.5 6.5v5" strokeLinecap="round" strokeLinejoin="round" />
    </svg>,

  Book: () =>
  <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.4">
      <path d="M3 3v10l5-1 5 1V3l-5 1-5-1Z" strokeLinejoin="round" />
      <path d="M8 4v9" />
    </svg>,

  Music: () =>
  <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.4">
      <path d="M6 12V3l7-1v8" strokeLinejoin="round" />
      <circle cx="4.5" cy="12" r="1.5" />
      <circle cx="11.5" cy="10" r="1.5" />
    </svg>

};

// ============================================================
// Header / Banner
// ============================================================
const SERIES_LIST = [
{ id: 'LSB-3Y-A', short: 'A', label: 'Three-Year', year: 'A' },
{ id: 'LSB-3Y-B', short: 'B', label: 'Three-Year', year: 'B' },
{ id: 'LSB-3Y-C', short: 'C', label: 'Three-Year', year: 'C' },
{ id: 'LSB-1Y', short: '1Y', label: 'One-Year', year: '' }];

const SECTION_NAV = [
{ id: 'church-year', label: 'Church Year' },
{ id: 'lectionary', label: 'Lectionary', count: () => SERIES_LIST.length },
{ id: 'resources', label: 'Resources', count: () => (window.RESOURCE_SETS || []).length },
{ id: 'about', label: 'About' }];


function Banner({ view, onView, series, onSeries, onNextSunday }) {
  const [menuOpen, setMenuOpen] = useState(false);
  const mobileNavRef = useRef(null);
  const currentSection = SECTION_NAV.find((item) => item.id === view) || SECTION_NAV[0];

  useEffect(() => {
    if (!menuOpen) return;
    const onDoc = (e) => {if (!mobileNavRef.current?.contains(e.target)) setMenuOpen(false);};
    const onKey = (e) => {if (e.key === 'Escape') setMenuOpen(false);};
    document.addEventListener('mousedown', onDoc);
    document.addEventListener('keydown', onKey);
    return () => {
      document.removeEventListener('mousedown', onDoc);
      document.removeEventListener('keydown', onKey);
    };
  }, [menuOpen]);

  const selectView = (nextView) => {
    onView(nextView);
    setMenuOpen(false);
  };

  return (
    <header className="banner">
      <div className="banner-inner">
        <div className="banner-top">
          <div className="brand">
            <button className="brand-mark" onClick={() => onView('church-year')} aria-label="Home">
              <span className="glyph" style={{ letterSpacing: "-0.3px" }}>✠</span>
              <span>LSB Lectionary</span>
            </button>
            <span className="brand-sub">Readings &amp; Resources</span>
          </div>
          <button className="banner-meta-btn" onClick={onNextSunday} title="Open the next Sunday">
            <span className="banner-meta-label">Next Sunday
              <svg className="banner-meta-chev" viewBox="0 0 12 12" width="11" height="11" fill="none" stroke="currentColor" strokeWidth="1.5">
                <path d="M4 3l3 3-3 3" strokeLinecap="round" strokeLinejoin="round" />
              </svg>
            </span>
            <span className="banner-meta-value">{formatNextSunday()}</span>
          </button>
        </div>
        <nav className="primary-nav" role="tablist" aria-label="Sections">
          {SECTION_NAV.map((item) => (
            <button
              key={item.id}
              role="tab"
              aria-selected={view === item.id}
              onClick={() => selectView(item.id)}
              className="primary-tab">
              <span>{item.label}</span>
              {item.count && <span className="count">{item.count()}</span>}
            </button>
          ))}
        </nav>
        <div className="mobile-section-nav" ref={mobileNavRef}>
          <button
            type="button"
            className="mobile-section-current"
            aria-current="page"
            onClick={() => selectView(currentSection.id)}>
            <span>{currentSection.label}</span>
            {currentSection.count && <span className="count">{currentSection.count()}</span>}
          </button>
          <button
            type="button"
            className="mobile-menu-trigger"
            aria-label="Open section menu"
            aria-haspopup="menu"
            aria-expanded={menuOpen}
            onClick={() => setMenuOpen((open) => !open)}>
            <span />
            <span />
            <span />
          </button>
          {menuOpen && (
            <div className="mobile-section-menu" role="menu">
              {SECTION_NAV.map((item) => (
                <button
                  key={item.id}
                  role="menuitemradio"
                  aria-checked={view === item.id}
                  className="mobile-section-option"
                  onClick={() => selectView(item.id)}>
                  <span>{item.label}</span>
                  {item.count && <span className="count">{item.count()}</span>}
                </button>
              ))}
            </div>
          )}
        </div>
      </div>
    </header>);

}

function formatNextSunday() {
  const d = new Date();
  const dow = d.getDay(); // 0 = Sunday
  const daysUntilSunday = dow === 0 ? 7 : 7 - dow; // if today is Sunday, jump to next week's Sunday
  const next = new Date(d);
  next.setDate(d.getDate() + daysUntilSunday);
  return next.toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric' });
}

// ============================================================
// Series Picker (dropdown)
// ============================================================
function SeriesPicker({ series, onSeries }) {
  const [open, setOpen] = useState(false);
  const ref = useRef(null);
  const current = SERIES_LIST.find((s) => s.id === series) || SERIES_LIST[0];

  useEffect(() => {
    if (!open) return;
    const onDoc = (e) => {if (!ref.current?.contains(e.target)) setOpen(false);};
    const onKey = (e) => {if (e.key === 'Escape') setOpen(false);};
    document.addEventListener('mousedown', onDoc);
    document.addEventListener('keydown', onKey);
    return () => {
      document.removeEventListener('mousedown', onDoc);
      document.removeEventListener('keydown', onKey);
    };
  }, [open]);

  return (
    <div className="series-picker" ref={ref}>
      <button
        type="button"
        className="series-picker-trigger"
        aria-haspopup="listbox"
        aria-expanded={open}
        onClick={() => setOpen((o) => !o)}>
        
        <span className="series-picker-label">Series</span>
        <span className="series-picker-value">
          <span>{current.label}</span>
          {current.year && <span className="series-picker-year">{current.year}</span>}
        </span>
        <svg className="series-picker-chev" viewBox="0 0 12 12" width="10" height="10" fill="none" stroke="currentColor" strokeWidth="1.5">
          <path d="M3 4.5 6 7.5l3-3" strokeLinecap="round" strokeLinejoin="round" />
        </svg>
      </button>
      {open &&
      <div className="series-picker-menu" role="listbox">
          {SERIES_LIST.map((s) => {
          const selected = s.id === series;
          return (
            <button
              key={s.id}
              role="option"
              aria-selected={selected}
              disabled={s.disabled}
              className="series-picker-option"
              onClick={() => {if (!s.disabled) {onSeries(s.id);setOpen(false);}}}>
              
                <span className="opt-name">
                  <span>{s.label}</span>
                  {s.year && <span className="opt-year">{s.year}</span>}
                </span>
                {s.disabled && <span className="opt-soon">Coming soon</span>}
                {selected && !s.disabled &&
              <svg viewBox="0 0 16 16" width="13" height="13" fill="none" stroke="currentColor" strokeWidth="1.6">
                    <path d="m3.5 8.5 3 3 6-7" strokeLinecap="round" strokeLinejoin="round" />
                  </svg>
              }
              </button>);

        })}
        </div>
      }
    </div>);

}

// ============================================================
// Filter Bar (search + season chips)
// ============================================================
function FilterBar({ series, onSeries, query, onQuery, activeSeasons, onToggleSeason, seasonGroups, totalCount, visibleCount }) {
  return (
    <div className="filter-bar">
      <SeriesPicker series={series} onSeries={onSeries} />
      <div className="search">
        <span className="search-icon"><Icon.Search /></span>
        <input
          type="text"
          placeholder="Search Sundays or readings…"
          value={query}
          onChange={(e) => onQuery(e.target.value)} />
        
      </div>
      <div className="season-filter" role="group" aria-label="Filter by season">
        {seasonGroups.map(({ season }) => {
          const active = activeSeasons.includes(season.id);
          return (
            <button
              key={season.id}
              className="season-chip"
              aria-pressed={active}
              onClick={() => onToggleSeason(season.id)}
              style={{ '--dot-color': season.color }}>
              
              <span className="dot" />
              <span>{season.name}</span>
            </button>);

        })}
      </div>
      <div className="filter-spacer" />
      <div className="filter-meta">
        {visibleCount === totalCount ?
        `${totalCount} Sundays` :
        `${visibleCount} of ${totalCount} Sundays`}
      </div>
    </div>);

}

// ============================================================
// Index list (season-grouped)
// ============================================================
function IndexView({ entries, seriesId, query, activeSeasons, onSelect }) {
  const groups = useMemo(() => window.LSB_groupBySeason(entries), [entries]);

  const filteredGroups = useMemo(() => {
    const q = query.trim().toLowerCase();
    return groups.
    filter(({ season }) => activeSeasons.length === 0 || activeSeasons.includes(season.id)).
    map(({ season, items }) => ({
      season,
      items: items.filter((it) => {
        if (!q) return true;
        return (
          it.Sunday.toLowerCase().includes(q) ||
          (it['Old Testament'] || '').toLowerCase().includes(q) ||
          (it.Epistle || '').toLowerCase().includes(q) ||
          (it.Gospel || '').toLowerCase().includes(q) ||
          (it.Psalm || '').toLowerCase().includes(q));

      })
    })).
    filter((g) => g.items.length);
  }, [groups, query, activeSeasons]);

  if (filteredGroups.length === 0) {
    return (
      <div className="empty-state">
        <p className="empty-state-title">
          No Sundays match your search.
        </p>
      </div>);

  }

  return (
    <div>
      {filteredGroups.map(({ season, items }) =>
      <section key={season.id} className="season-group" style={{ '--dot-color': season.color }}>
          <header className="season-head">
            <h2 className="season-name">
              <span className="dot" />
              {season.name}
            </h2>
            <span className="season-meta">{items.length} {items.length === 1 ? 'Sunday' : 'Sundays'}</span>
            <span className="filter-spacer" />
            <span className="season-hue">— {season.hueLabel}</span>
          </header>
          {items.map((entry) => {
          return (
            <button
              key={entry._index}
              className="sunday-row"
              onClick={() => onSelect(entry._index)}
              style={{ '--dot-color': season.color }}>

                <span className="row-dot" />
                <span className="sunday-name">{entry.Sunday}</span>
                <span className="sunday-readings">
                  <ReadingChip label="OT" value={entry['Old Testament']} />
                  <ReadingChip label="Ps" value={entry.Psalm} compact />
                  <ReadingChip label="Ep" value={entry.Epistle} />
                  <ReadingChip label="Gosp" value={entry.Gospel} />
                </span>
              </button>);

        })}
        </section>
      )}
    </div>);

}

function ReadingChip({ label, value, compact }) {
  if (!value) return null;
  // Pull just the book+verse, drop antiphon/parens for the index
  const clean = compact ? value.split('(')[0].trim() : value.split('(')[0].trim();
  return (
    <span className="r">
      <span className="r-label">{label}</span>
      <span className="r-val">{clean}</span>
    </span>);

}

// ============================================================
// Sunday Detail
// ============================================================
const RESOURCE_CATEGORIES = [
{ id: 'hymn', label: 'Hymns', short: 'HY', placeholder: 'e.g., LSB 332 — Savior of the Nations, Come' },
{ id: 'choral', label: 'Choral & Anthems', short: 'CH', placeholder: 'e.g., "Wachet Auf" — Bach, BWV 140' },
{ id: 'instrumental', label: 'Prelude / Postlude', short: 'IN', placeholder: 'e.g., Buxtehude — BuxWV 211' },
{ id: 'sermon', label: 'Sermon', short: 'SR', placeholder: 'Sermon title or text…' },
{ id: 'prayer', label: 'Prayers & Collects', short: 'PR', placeholder: 'Collect of the Day…' },
{ id: 'note', label: 'Planning Notes', short: 'NT', placeholder: 'Anything else…' }];


function SundayDetail({ entry, season, seriesLabel, onBack, onPrev, onNext, hasPrev, hasNext, curatedResources, onShowScripture }) {
  return (
    <div className="detail-shell" style={{ '--dot-color': season.color }}>
      <div className="crumbs">
        <button onClick={onBack}>
          <Icon.Back />
          <span>Index</span>
        </button>
        <span className="sep">/</span>
        <span>{seriesLabel}</span>
        <span className="sep">/</span>
        <span>{season.name}</span>
      </div>

      <header className="detail-head">
        <div className="detail-title-block">
          <span className="detail-eyebrow">
            <span className="dot" />
            {season.name} · {seriesLabel}
          </span>
          <h1 className="detail-title">{entry.Sunday}</h1>
          {entry.Verse &&
          <p className="detail-sub">Verse of the Day · {entry.Verse}</p>
          }
        </div>
        <div className="detail-nav">
          <button className="nav-btn" disabled={!hasPrev} onClick={onPrev} title="Previous Sunday">
            <Icon.Arrow dir="left" />
            <span className="nav-label">Prev</span>
          </button>
          <button className="nav-btn" disabled={!hasNext} onClick={onNext} title="Next Sunday">
            <span className="nav-label">Next</span>
            <Icon.Arrow dir="right" />
          </button>
        </div>
      </header>

      {/* Readings */}
      <section className="section">
        <div className="section-head">
          <h3 className="section-title">Propers</h3>
          <span className="section-rule" />
        </div>
        <div className="readings">
          <Reading label="Introit" value={entry.Introit} onView={onShowScripture} />
          <Reading label="Gradual" value={entry.Gradual} onView={onShowScripture} />
          <Reading label="Old Testament" value={entry['Old Testament']} onView={onShowScripture} />
          <Reading label="Psalm" value={entry.Psalm} onView={onShowScripture} />
          <Reading label="Epistle" value={entry.Epistle} onView={onShowScripture} />
          <Reading label="Gospel" value={entry.Gospel} onView={onShowScripture} emphasis />
        </div>
      </section>

      {/* Curated resource-set items */}
      {curatedResources && curatedResources.length > 0 && (
        <section className="section">
          <div className="section-head">
            <h3 className="section-title">Service Planning Resources</h3>
            <span className="section-rule" />
          </div>
          <div className="res-set-list">
            {curatedResources.map((item, i) => (
              <a
                key={i}
                href={item.url}
                target="_blank"
                rel="noopener noreferrer"
                className="res-set-item"
              >
                <span className="res-set-source">{item._setTitle}</span>
                <span className="res-set-title">{item.title}</span>
                <svg className="res-set-arrow" viewBox="0 0 16 16" width="13" height="13" fill="none" stroke="currentColor" strokeWidth="1.5">
                  <path d="M3 8h10M9 4l4 4-4 4" strokeLinecap="round" strokeLinejoin="round" />
                </svg>
              </a>
            ))}
          </div>
        </section>
      )}

    </div>);

}

function Reading({ label, value, onView, emphasis }) {
  if (!value) {
    return (
      <div className="reading" style={{ cursor: 'default' }}>
        <span className="reading-label">{label}</span>
        <span className="reading-ref reading-empty">—</span>
      </div>);

  }
  const main = value.split('(')[0].trim();
  const paren = value.includes('(') ? '(' + value.split('(').slice(1).join('(') : '';
  return (
    <button className="reading" onClick={() => onView({ label, value })}>
      <span className="reading-label">{label}</span>
      <span className="reading-ref">
        {main}
        {paren && <> <span className="muted">{paren}</span></>}
      </span>
    </button>);

}

// ============================================================
// Scripture Preview Modal
// ============================================================
function ScriptureModal({ reading, onClose }) {
  const passages = useMemo(() => parseReadingPassages(reading.value), [reading.value]);
  const [activeIdx, setActiveIdx] = useState(0);
  const active = passages[activeIdx];
  const [state, setState] = useState({ status: 'idle', text: '', canonical: '', error: '' });

  useEffect(() => {
    const onKey = (e) => {if (e.key === 'Escape') onClose();};
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [onClose]);

  useEffect(() => {
    if (!active?.query) {
      setState({ status: 'error', text: '', canonical: '', error: 'This lectionary entry does not contain a passage reference that can be loaded from the ESV.' });
      return;
    }

    const controller = new AbortController();
    setState({ status: 'loading', text: '', canonical: active.query, error: '' });

    fetch(`/api/esv?q=${encodeURIComponent(active.query)}`, { signal: controller.signal })
      .then(async (res) => {
        const data = await res.json().catch(() => ({}));
        if (!res.ok) throw new Error(data.error || 'Unable to load this passage.');
        return data;
      })
      .then((data) => {
        setState({
          status: 'ready',
          text: (data.passages || []).join('\n\n').trim(),
          canonical: data.canonical || active.query,
          error: '',
        });
      })
      .catch((err) => {
        if (err.name === 'AbortError') return;
        setState({ status: 'error', text: '', canonical: active.query, error: err.message || 'Unable to load this passage.' });
      });

    return () => controller.abort();
  }, [active]);

  return (
    <div className="modal-backdrop" onMouseDown={(e) => {if (e.target === e.currentTarget) onClose();}}>
      <div className="modal scripture-modal">
        <div className="modal-head">
          <div>
            <div className="modal-eyebrow">{reading.label}</div>
            <h3 className="modal-title">{reading.value.split('(')[0].trim()}</h3>
          </div>
          <button className="modal-close" onClick={onClose} aria-label="Close scripture modal"><Icon.X /></button>
        </div>
        <div className="modal-body">
          {passages.length > 1 && (
            <div className="scripture-options" role="tablist" aria-label="Passage options">
              {passages.map((passage, idx) => (
                <button
                  key={`${passage.query}-${idx}`}
                  role="tab"
                  aria-selected={idx === activeIdx}
                  className="scripture-option"
                  onClick={() => setActiveIdx(idx)}
                >
                  {passage.label}
                </button>
              ))}
            </div>
          )}
          <div className="scripture-preview">
            {state.status === 'loading' && (
              <div className="scripture-state">Loading ESV text...</div>
            )}
            {state.status === 'error' && (
              <div className="scripture-state scripture-state-error">
                {state.error}
              </div>
            )}
            {state.status === 'ready' && (
              <>
                <ScriptureText text={state.text} />
                {active?.note && <div className="scripture-antiphon">{active.note}</div>}
                <span className="ref">— {state.canonical || active.query}</span>
              </>
            )}
          </div>
          <div className="scripture-note">
            Scripture references from the{' '}
            <a href="https://www.esv.org/" target="_blank" rel="noopener noreferrer">ESV Bible</a>.
            {' '}
            <a className="scripture-note-link" href="/copyright">Copyright Notices</a>.
          </div>
        </div>
        <div className="modal-foot">
          {active?.query && (
            <a
              className="btn btn-ghost"
              href={`https://www.esv.org/search/?q=${encodeURIComponent(active.query)}`}
              target="_blank"
              rel="noopener noreferrer"
            >
              Read Passage
            </a>
          )}
          <button className="btn" onClick={onClose}>Close</button>
        </div>
      </div>
    </div>);

}

function ScriptureText({ text }) {
  if (!text) return <div className="scripture-state">No text returned for this passage.</div>;
  return (
    <div className="scripture-text">
      {text.split(/\n{2,}/).filter(Boolean).map((paragraph, idx) => (
        <p key={idx}>{paragraph.trim()}</p>
      ))}
    </div>
  );
}

function parseReadingPassages(value) {
  const original = value || '';
  const normalized = original
    .replace(/[–—]/g, '-')
    .replace(/\s+/g, ' ')
    .trim();
  const notes = [];
  let source = normalized
    .replace(/^Psalmody\s+/i, '')
    .replace(/;\s*antiphon:.*$/i, (match) => {
      notes.push(match.replace(/^;\s*/, ''));
      return '';
    })
    .replace(/\(([^)]*(?:antiphon|lit\.?\s*text|liturgical text|minus)[^)]*)\)/gi, (match, note) => {
      notes.push(note.trim());
      return '';
    });

  source = source
    .replace(/\b(and)\b/gi, ';')
    .replace(/\s+(?:or)\s+/gi, ' || ');

  const passages = source
    .split('||')
    .map((part) => cleanPassageQuery(part))
    .filter(Boolean)
    .filter((part) => !/^(?:the song|no verse|lit\.?\s*text|liturgical text)/i.test(part));

  return passages.length
    ? passages.map((query, idx) => ({
        query,
        label: passages.length > 1 ? `Option ${idx + 1}` : 'Reading',
        original,
        note: notes.length ? notes.join('; ') : '',
      }))
    : [{ query: '', label: 'Reading', original, note: notes.join('; ') }];
}

function cleanPassageQuery(part) {
  return part
    .replace(/\bPs:\s*/gi, 'Ps ')
    .replace(/\b(Ps|Is|Jn|Lk|Mk|Matt|Rom|Rev|Cor|Thess|Tim|Pet|Sam|Kgs|Chr|Prov|Eccl|Gal|Eph|Phil|Col|Heb|Jas|James|Titus|Phlm|Acts|Gen|Ex|Lev|Num|Deut|Josh|Judg|Ruth|Ezra|Neh|Esth|Job|Song|Isa|Jer|Lam|Ezek|Dan|Hos|Joel|Amos|Obad|Jonah|Mic|Nah|Hab|Zeph|Hag|Zech|Mal)\./gi, '$1')
    .replace(/\bMt\b/g, 'Matt')
    .replace(/\bJas\b/g, 'James')
    .replace(/\(\s*/g, '')
    .replace(/\s*\)/g, '')
    .replace(/:\s+/g, ':')
    .replace(/(\d)\s+(\d)/g, '$1, $2')
    .replace(/\s*;\s*/g, '; ')
    .replace(/\s*,\s*/g, ', ')
    .replace(/\s+/g, ' ')
    .trim();
}

// ============================================================
// Resource Sets — landing + detail views
// ============================================================

const RS_SERIES_LABELS = {
  'three-year-a': 'Year A',
  'three-year-b': 'Year B',
  'three-year-c': 'Year C',
  'one-year': 'One Year',
};

function sundayLabelFromPath(path) {
  const parts = path.replace(/^\//, '').split('/').filter(Boolean);
  const seriesSlug = parts[1] || '';
  const sundaySlug = parts[2] || '';
  const series = RS_SERIES_LABELS[seriesSlug] || seriesSlug;
  const name = sundaySlug.replace(/-/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase());
  return { name, series };
}

function ResourcesView({ onOpenSet }) {
  const sets = window.RESOURCE_SETS || [];
  return (
    <div className="rs-shell">
      <div className="rs-page-head">
        <h1 className="rs-page-title">
          Resources
          <span className="rs-page-count">{sets.length}</span>
        </h1>
        <p className="rs-page-desc">Curated collections tied to specific Sundays in the lectionary.</p>
      </div>
      <div className="rs-set-grid">
        {sets.map((set) => (
          <button key={set.id} className="rs-set-card" onClick={() => onOpenSet(set.id)}>
            <div className="rs-set-card-title">{set.title}</div>
            {set.description && <div className="rs-set-card-desc">{set.description}</div>}
            <div className="rs-set-card-foot">
              <span className="rs-set-card-pill">
                {set.resources.length} {set.resources.length === 1 ? 'resource' : 'resources'}
              </span>
            </div>
          </button>
        ))}
        {sets.length === 0 && (
          <div className="rs-empty">No resource sets loaded.</div>
        )}
      </div>
    </div>
  );
}

function ResourceSetView({ setId, onBack, onOpenSunday }) {
  const set = (window.RESOURCE_SETS || []).find((s) => s.id === setId);
  if (!set) {
    return (
      <div className="rs-shell">
        <button className="btn-link rs-back-link" onClick={onBack}><Icon.Back /> Resources</button>
        <p style={{ color: 'var(--muted)', marginTop: 24 }}>Resource set not found.</p>
      </div>
    );
  }
  return (
    <div className="rs-shell">
      <div className="rs-set-head">
        <button className="btn-link rs-back-link" onClick={onBack}><Icon.Back /> Resources</button>
        <h1 className="rs-set-title">{set.title}</h1>
        {set.description && <p className="rs-set-desc">{set.description}</p>}
      </div>
      <ul className="rs-resource-list">
        {set.resources.map((res, i) => (
          <li key={i} className="rs-resource-item">
            <a className="rs-resource-link" href={res.url} target="_blank" rel="noopener noreferrer">
              <span className="rs-resource-title">{res.title}</span>
              <span className="rs-resource-arrow"><Icon.Arrow /></span>
            </a>
            {res.sundays && res.sundays.length > 0 && (
              <div className="rs-resource-sundays">
                {res.sundays.map((path) => {
                  const label = sundayLabelFromPath(path);
                  return (
                    <button key={path} className="rs-sunday-tag" onClick={() => onOpenSunday(path)}>
                      <span className="rs-sunday-name">{label.name}</span>
                      <span className="rs-sunday-series">{label.series}</span>
                    </button>
                  );
                })}
              </div>
            )}
          </li>
        ))}
      </ul>
    </div>
  );
}

// ============================================================
// Generic dropdown matching the SeriesPicker chrome.
// Used by ChurchYearView for both Series-form and Year pickers.
// ============================================================
function ChromeDropdown({ label, value, options, onChange, menuAlign = 'left' }) {
  const [open, setOpen] = useState(false);
  const ref = useRef(null);
  const current = options.find((o) => o.value === value) || options[0];

  useEffect(() => {
    if (!open) return;
    const onDoc = (e) => { if (!ref.current?.contains(e.target)) setOpen(false); };
    const onKey = (e) => { if (e.key === 'Escape') setOpen(false); };
    document.addEventListener('mousedown', onDoc);
    document.addEventListener('keydown', onKey);
    return () => {
      document.removeEventListener('mousedown', onDoc);
      document.removeEventListener('keydown', onKey);
    };
  }, [open]);

  return (
    <div className="series-picker" ref={ref}>
      <button
        type="button"
        className="series-picker-trigger"
        aria-haspopup="listbox"
        aria-expanded={open}
        onClick={() => setOpen((o) => !o)}
      >
        <span className="series-picker-label">{label}</span>
        <span className="series-picker-value">
          <span>{current?.primary}</span>
          {current?.secondary && <span className="series-picker-year">{current.secondary}</span>}
        </span>
        <svg className="series-picker-chev" viewBox="0 0 12 12" width="10" height="10" fill="none" stroke="currentColor" strokeWidth="1.5">
          <path d="M3 4.5 6 7.5l3-3" strokeLinecap="round" strokeLinejoin="round" />
        </svg>
      </button>
      {open && (
        <div className="series-picker-menu" role="listbox" style={menuAlign === 'right' ? { right: 0, left: 'auto' } : undefined}>
          {options.map((opt) => {
            const selected = opt.value === value;
            return (
              <button
                key={String(opt.value)}
                role="option"
                aria-selected={selected}
                disabled={opt.disabled}
                className="series-picker-option"
                onClick={() => { if (!opt.disabled) { onChange(opt.value); setOpen(false); } }}
              >
                <span className="opt-name">
                  <span>{opt.primary}</span>
                  {opt.secondary && <span className="opt-year">{opt.secondary}</span>}
                </span>
                {opt.soonLabel && <span className="opt-soon">{opt.soonLabel}</span>}
                {selected && !opt.disabled && (
                  <svg viewBox="0 0 16 16" width="13" height="13" fill="none" stroke="currentColor" strokeWidth="1.6">
                    <path d="m3.5 8.5 3 3 6-7" strokeLinecap="round" strokeLinejoin="round" />
                  </svg>
                )}
              </button>
            );
          })}
        </div>
      )}
    </div>
  );
}

// ============================================================
// Church Year view: dated table of all Sundays & major feasts
// for a given liturgical year (calendar year).
// ============================================================
const DOW_ABBR = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
const MONTH_ABBR = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];

function defaultChurchYear() {
  const today = new Date();
  const cy = today.getFullYear();
  // If today is on or after Advent 1 of (cy+1)'s liturgical year, default to cy+1.
  const next = window.LSB_buildChurchYear(cy + 1);
  const advent1Next = next.items[0]?.date;
  return advent1Next && today >= advent1Next ? cy + 1 : cy;
}

function formatChurchYearLabel(endYear, letter) {
  return `${endYear - 1}–${endYear}${letter ? ` · Series ${letter}` : ''}`;
}

function ChurchYearView({ year, onYear, seriesForm, onSeriesForm, onOpenSunday }) {
  const isOneYear = seriesForm === '1Y';
  const churchYear = useMemo(
    () => isOneYear ? window.LSB_buildChurchYearOneYear(year) : window.LSB_buildChurchYear(year),
    [year, isOneYear]
  );
  const seriesLetter = churchYear.seriesLetter;
  const seriesId = isOneYear ? 'LSB-1Y' : `LSB-3Y-${seriesLetter}`;
  const entries = window.LECTIONARY_DATA[seriesId] || [];

  const byKey = useMemo(() => {
    const map = {};
    entries.forEach((e, idx) => { map[e.Sunday] = { entry: e, idx }; });
    return map;
  }, [entries]);

  const yearOptions = useMemo(() => {
    const today = new Date();
    const center = today.getFullYear();
    const arr = [];
    for (let y = center - 4; y <= center + 5; y++) {
      arr.push({ value: y, primary: String(y), secondary: formatChurchYearLabel(y, window.LSB_seriesLetterForStartYear(y - 1)) });
    }
    return arr;
  }, []);

  const seriesFormOptions = [
    { value: '3Y', primary: 'Three-Year', secondary: seriesLetter },
    { value: '1Y', primary: 'One-Year' },
  ];

  const today = new Date();
  today.setHours(0, 0, 0, 0);

  return (
    <div className="cy-shell">
      <header className="cy-head">
        <div className="cy-head-titles">
          <h1 className="cy-title">Church Year</h1>
          <p className="cy-sub">
            <span>Liturgical year {churchYear.startYear}–{churchYear.endYear}</span>
            <span className="sep">·</span>
            {isOneYear ? (
              <span>One-Year Historic</span>
            ) : (
              <span>Three-Year Series {seriesLetter}</span>
            )}
            <span className="sep">·</span>
            <span>{churchYear.items.length} observances</span>
          </p>
        </div>
        <div className="cy-controls">
          <ChromeDropdown
            label="Series"
            value={seriesForm}
            options={seriesFormOptions}
            onChange={onSeriesForm}
          />
          <ChromeDropdown
            label="Year"
            value={year}
            options={yearOptions}
            onChange={onYear}
            menuAlign="right"
          />
        </div>
      </header>

      <CYTable
        churchYear={churchYear}
        byKey={byKey}
        today={today}
        onOpenSunday={(idx) => onOpenSunday(seriesId, idx)}
      />
    </div>
  );
}

function CYTable({ churchYear, byKey, today, onOpenSunday }) {
  const upcomingIdx = useMemo(() => {
    return churchYear.items.findIndex((it) => it.date >= today);
  }, [churchYear, today]);

  const scrollRef = useRef(null);
  // Scroll target is one row above the upcoming Sunday, so it lands in view too.
  const scrollTargetIdx = upcomingIdx > 0 ? upcomingIdx - 1 : upcomingIdx;

  useEffect(() => {
    const pageBody = document.querySelector('.page-body');
    if (!scrollRef.current || !pageBody) return;
    const stickyH = document.querySelector('.cy-head')?.offsetHeight ?? 0;
    const top = scrollRef.current.getBoundingClientRect().top
      + pageBody.scrollTop
      - pageBody.getBoundingClientRect().top
      - stickyH;
    pageBody.scrollTo({ top });
  }, [churchYear]);

  return (
    <div className="cy-table">
      <div className="cy-table-head">
        <span className="cy-h-date">Date</span>
        <span className="cy-h-sunday">Sunday / Feast</span>
        <span className="cy-h-readings">Readings</span>
      </div>
      {churchYear.items.map((item, i) => {
        const lect = byKey[item.sundayKey];
        const season = window.LSB_seasonFor(item.sundayKey);
        const isFeast = item.kind === 'feast';
        const isUpcoming = i === upcomingIdx;
        const isPast = item.date < today;
        const clickable = !!lect;
        return (
          <button
            key={i}
            ref={i === scrollTargetIdx ? scrollRef : undefined}
            className={`cy-row ${isFeast ? 'is-feast' : ''} ${isUpcoming ? 'is-upcoming' : ''} ${isPast ? 'is-past' : ''} ${!clickable ? 'is-disabled' : ''}`}
            style={{ '--dot-color': season.color }}
            disabled={!clickable}
            onClick={() => clickable && onOpenSunday(lect.idx)}
          >
            <span className="cy-date">
              <span className="cy-date-dow">{DOW_ABBR[item.date.getDay()]}</span>
              <span className="cy-date-md">{MONTH_ABBR[item.date.getMonth()]} {item.date.getDate()}</span>
              <span className="cy-date-y">{item.date.getFullYear()}</span>
            </span>
            <span className="cy-sunday">
              <span className="cy-row-dot" />
              <span className="cy-sunday-name">{item.sundayKey}</span>
              {isFeast && <span className="cy-feast-tag">Feast</span>}
              {isUpcoming && <span className="cy-upcoming-tag">Next</span>}
            </span>
            <span className="cy-readings">
              <ReadingChip label="OT" value={lect?.entry?.['Old Testament']} />
              <ReadingChip label="Ps" value={lect?.entry?.Psalm} compact />
              <ReadingChip label="Ep" value={lect?.entry?.Epistle} />
              <ReadingChip label="Gosp" value={lect?.entry?.Gospel} />
            </span>
          </button>
        );
      })}
    </div>
  );
}


// ============================================================
// Simple standalone page (used by About)
// ============================================================
function BlankPage({ title, subtitle, body, tag }) {
  return (
    <div className="blank-page">
      <div className="blank-emblem" aria-hidden="true">
        <svg viewBox="0 0 80 80" width="56" height="56" fill="none" stroke="currentColor" strokeWidth="1.2">
          <circle cx="40" cy="40" r="34" />
          <path d="M40 14v52M14 40h52" strokeLinecap="round" />
          <circle cx="40" cy="40" r="6" fill="currentColor" stroke="none" opacity="0.4" />
        </svg>
      </div>
      <h1 className="blank-title">{title}</h1>
      <p className="blank-subtitle">{subtitle}</p>
      <div className="blank-body">{body}</div>
      {tag && <div className="blank-tag">{tag}</div>}
    </div>
  );
}

// Expose to global so app.jsx can use
Object.assign(window, {
  Banner,
  FilterBar,
  IndexView,
  SundayDetail,
  ScriptureModal,
  ResourcesView,
  ResourceSetView,
  ChurchYearView,
  BlankPage,
  SERIES_LIST
});
