// Public lunar calendar — elegant, responsive, read-only.
// Renders Chinese lunar dates, 节气, traditional festivals, Chinese public
// holidays (法定节假日 + 调休), and common Western/international holidays.

const MONTH_NAMES = ['January','February','March','April','May','June','July','August','September','October','November','December'];
const MONTH_CN_S = ['一月','二月','三月','四月','五月','六月','七月','八月','九月','十月','十一月','十二月'];
const WEEKDAYS_SUN = ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'];
const WEEKDAYS_MON = ['Mon','Tue','Wed','Thu','Fri','Sat','Sun'];
const WEEKDAYS_CN = ['日','一','二','三','四','五','六'];

// ── Helpers ───────────────────────────────────────────────────
function buildGrid(year, month, weekStart) {
  const daysInMonth = new Date(year, month, 0).getDate();
  const daysInPrev = new Date(year, month - 1, 0).getDate();
  const firstDow = new Date(year, month - 1, 1).getDay();
  const offset = (firstDow - weekStart + 7) % 7;
  const cells = [];
  for (let i = 0; i < offset; i++) {
    cells.push({ day: daysInPrev - offset + 1 + i, month: month - 1, year, outside: true });
  }
  for (let d = 1; d <= daysInMonth; d++) cells.push({ day: d, month, year, outside: false });
  let nxt = 1;
  while (cells.length < 42) cells.push({ day: nxt++, month: month + 1, year, outside: true });
  return cells.slice(0, 42);
}

function useMediaQuery(q) {
  const [m, setM] = React.useState(() => window.matchMedia(q).matches);
  React.useEffect(() => {
    const mql = window.matchMedia(q);
    const fn = e => setM(e.matches);
    mql.addEventListener('change', fn);
    return () => mql.removeEventListener('change', fn);
  }, [q]);
  return m;
}

// ── Themes ────────────────────────────────────────────────────
// Shared calligraphic font stack — all themes use the Rice (WenKai) feel.
const SHARED_FONTS = {
  fontSans:  '"LXGW WenKai Screen", "Noto Serif SC", "Cormorant Garamond", Georgia, serif',
  fontSerif: '"LXGW WenKai Screen", "Cormorant Garamond", "Noto Serif SC", Georgia, serif',
  fontMono:  '"JetBrains Mono", "LXGW WenKai Screen", ui-monospace, monospace',
  fontKai:   '"LXGW WenKai Screen", "Noto Serif SC", serif',
};

const THEMES = {
  ink: {
    name: 'Ink on Paper',
    bg: '#fafaf7', panel: '#ffffff', sidebar: '#f3f2ed',
    text: '#1a1a18', dim: '#6b6a63', faint: '#b8b7b0', line: '#e5e3dc',
    accent: '#8a2a1a',
    ...SHARED_FONTS,
    radius: 2,
    lunarColor: '#8a6a3a',
    todayBg: '#1a1a18', todayFg: '#fafaf7',
    sunday: '#a85038',
    kindColors: { festival: '#a84a3a', jieqi: '#5e7d4a', lunar: '#6b5a7a', obs: '#8a6a3a', pub: '#a84a3a', work: '#6b7a8a' },
  },
  rice: {
    name: 'Rice Paper',
    bg: '#f1ead8', panel: '#f7f1e0', sidebar: '#eae1c8',
    text: '#2a1f14', dim: '#6b5537', faint: '#b39a6c', line: '#d9cca8',
    accent: '#8a2a1a',
    ...SHARED_FONTS,
    radius: 0,
    lunarColor: '#8a2a1a',
    todayBg: '#8a2a1a', todayFg: '#f7f1e0',
    sunday: '#8a2a1a',
    kindColors: { festival: '#8a2a1a', jieqi: '#5e6d3a', lunar: '#6b5537', obs: '#8a6a3a', pub: '#8a2a1a', work: '#6b5537' },
  },
  dark: {
    name: 'Dark Pro',
    bg: '#14151a', panel: '#1c1d24', sidebar: '#0f1014',
    text: '#e8e6e0', dim: '#8a8a85', faint: '#4a4a48', line: '#2a2b34',
    accent: '#d8a85a',
    ...SHARED_FONTS,
    radius: 4,
    lunarColor: '#c89460',
    todayBg: '#d8a85a', todayFg: '#14151a',
    sunday: '#d46a5a',
    kindColors: { festival: '#d46a5a', jieqi: '#9ab87a', lunar: '#a895c4', obs: '#d8a85a', pub: '#d46a5a', work: '#7a8ca8' },
  },
};

// ── Control toolbar (in-page theme / script) ─────────────────
// Note: we deliberately don't expose a font-size switch — browser zoom
// (⌘/Ctrl +/−) scales the whole layout proportionally and persists per-site,
// which is what users reach for anyway.
function Toolbar({ T, theme, setTheme, script, setScript, isMobile }) {
  const btn = (active) => ({
    border: `1px solid ${active ? T.text : T.line}`,
    background: active ? T.text : 'transparent',
    color: active ? T.bg : T.text,
    padding: isMobile ? '5px 8px' : '6px 10px',
    fontSize: isMobile ? 11 : 12,
    fontFamily: T.fontSans, cursor: 'pointer',
    borderRadius: T.radius,
    letterSpacing: 0.3,
  });
  const Group = ({ children }) => (
    <div style={{ display: 'inline-flex', gap: 4, alignItems: 'center' }}>{children}</div>
  );
  return (
    <div style={{
      display: 'flex', gap: isMobile ? 8 : 14, flexWrap: 'wrap', alignItems: 'center',
      fontFamily: T.fontSans,
    }}>
      <Group>
        <span style={{ fontSize: 10.5, color: T.dim, fontFamily: T.fontMono, letterSpacing: 1.2, textTransform: 'uppercase', marginRight: 2 }}>Theme</span>
        {[['ink','Ink'],['rice','Rice'],['dark','Dark']].map(([v, l]) => (
          <button key={v} onClick={() => setTheme(v)} style={btn(theme === v)}>{l}</button>
        ))}
      </Group>
      {!isMobile && <Group>
        <span style={{ fontSize: 10.5, color: T.dim, fontFamily: T.fontMono, letterSpacing: 1.2, textTransform: 'uppercase', marginRight: 2 }}>字</span>
        {[['s','简'],['t','繁']].map(([v, l]) => (
          <button key={v} onClick={() => setScript(v)} style={btn(script === v)}>{l}</button>
        ))}
      </Group>}
    </div>
  );
}

// ── Sidebar: identity + Subscribe ─────────────────────────────
function Sidebar({ T, script, year, month, isMobile, toolbar }) {
  const pillar = window.LunarData.yearPillar(year, month, 15, script);
  const seasonName = ['冬','冬','春','春','春','夏','夏','夏','秋','秋','秋','冬'][month-1];
  const [toast, setToast] = React.useState(null);
  const [modal, setModal] = React.useState(null);

  const showToast = (msg) => {
    setToast(msg);
    setTimeout(() => setToast(null), 1800);
  };

  const openSubscribe = (kind) => {
    setModal(kind);
  };

  if (isMobile) {
    // Collapsed mobile header, no full sidebar
    return (
      <MobileHeader T={T} pillar={pillar} year={year} month={month} seasonName={seasonName}
        onSubscribe={() => setModal('all')} toolbar={toolbar} />
    );
  }

  return (
    <aside style={{
      width: 280, flexShrink: 0,
      background: T.sidebar, borderRight: `1px solid ${T.line}`,
      padding: '32px 24px', color: T.text, fontFamily: T.fontSans,
      overflow: 'auto',
      display: 'flex', flexDirection: 'column', gap: 28,
    }}>
      <div>
        <div style={{
          fontFamily: T.fontSerif, fontSize: 40, fontStyle: 'italic',
          lineHeight: 1, letterSpacing: -0.4, fontWeight: 500,
        }}>Almanac</div>
        <div style={{
          fontSize: 11, color: T.dim, marginTop: 10,
          fontFamily: T.fontMono, letterSpacing: 1.4, textTransform: 'uppercase',
        }}>农历 · Public Calendar</div>
      </div>

      {/* Year pillar card */}
      <div style={{
        padding: '20px 18px', background: T.panel,
        border: `1px solid ${T.line}`, borderRadius: T.radius,
      }}>
        <div style={{ fontSize: 11, color: T.dim, fontFamily: T.fontMono, letterSpacing: 1.2, textTransform: 'uppercase', marginBottom: 8 }}>
          Year of the {pillar.zodiac}
        </div>
        <div style={{ fontFamily: T.fontKai, fontSize: 42, lineHeight: 1, letterSpacing: 1 }}>
          {pillar.ganzhi} <span style={{ color: T.lunarColor, fontSize: 32 }}>年</span>
        </div>
        <div style={{ marginTop: 12, paddingTop: 12, borderTop: `1px dashed ${T.line}`, fontSize: 12, color: T.dim, fontFamily: T.fontMono, letterSpacing: 0.3 }}>
          {year} · {MONTH_CN_S[month-1]} · {seasonName}
        </div>
      </div>

      {/* Included content */}
      <div>
        <SectionTitle T={T}>Includes</SectionTitle>
        {[
          { sw: T.kindColors.festival, label: script==='t' ? '節日' : '节日', en: 'Traditional festivals' },
          { sw: T.kindColors.jieqi,    label: script==='t' ? '節氣' : '节气', en: '24 solar terms' },
          { sw: T.kindColors.pub,      label: '法定',                         en: 'Public holidays + 调休' },
          { sw: T.kindColors.obs,      label: script==='t' ? '紀念' : '纪念', en: 'Western & intl. holidays' },
        ].map((row, i) => (
          <div key={i} style={{ display: 'flex', alignItems: 'baseline', gap: 10, padding: '6px 2px', fontSize: 13.5 }}>
            <span style={{ width: 8, height: 8, borderRadius: 2, background: row.sw, flexShrink: 0, transform: 'translateY(1px)' }} />
            <span style={{ fontFamily: T.fontKai, color: T.text, fontSize: 15 }}>{row.label}</span>
            <span style={{ fontSize: 12, color: T.dim, flex: 1 }}>{row.en}</span>
          </div>
        ))}
      </div>

      {/* Subscribe */}
      <div>
        <SectionTitle T={T}>Subscribe</SectionTitle>
        <div style={{ fontSize: 13, color: T.dim, lineHeight: 1.55, marginBottom: 12 }}>
          Read-only feed. Auto-updates in any CalDAV or iCal client.
        </div>

        <SubRow T={T} label="CalDAV" value="cal.memojar.net/lunar"
          onClick={() => openSubscribe('caldav')} />
        <SubRow T={T} label="webcal" value="cal.memojar.net/lunar.ics"
          onClick={() => openSubscribe('webcal')} />
        <SubRow T={T} label=".ics"   value="Download snapshot"
          onClick={() => { downloadIcs(year); showToast('Downloaded ' + `lunar-${year}.ics`); }} />
      </div>

      <div style={{ flex: 1 }} />

      {/* Footer */}
      <div style={{ fontSize: 11, color: T.faint, fontFamily: T.fontMono, letterSpacing: 0.4, lineHeight: 1.7 }}>
        v2.2 · range 1990–2040<br/>
        last published 04:00 UTC<br/>
        cal.memojar.net
      </div>

      {modal && <SubscribeModal T={T} kind={modal} year={year} onClose={() => setModal(null)} onToast={showToast} />}
      {toast && <Toast T={T}>{toast}</Toast>}
    </aside>
  );
}

function MobileHeader({ T, pillar, year, month, seasonName, onSubscribe, toolbar }) {
  return (
    <header style={{
      background: T.sidebar, borderBottom: `1px solid ${T.line}`,
      padding: '14px 16px', display: 'flex', flexDirection: 'column', gap: 10,
      color: T.text, fontFamily: T.fontSans,
    }}>
      <div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', gap: 8 }}>
        <div style={{ display: 'flex', alignItems: 'baseline', gap: 12 }}>
          <span style={{ fontFamily: T.fontSerif, fontSize: 24, fontStyle: 'italic', fontWeight: 500 }}>Almanac</span>
          <span style={{ fontFamily: T.fontKai, fontSize: 18, color: T.lunarColor }}>{pillar.ganzhi}年·{pillar.zodiac}</span>
        </div>
        <button onClick={onSubscribe} style={{
          border: `1px solid ${T.line}`, background: T.panel, color: T.text,
          padding: '5px 10px', fontSize: 11, borderRadius: T.radius, cursor: 'pointer',
          fontFamily: T.fontMono, letterSpacing: 1, textTransform: 'uppercase',
        }}>Subscribe</button>
      </div>
      {toolbar}
    </header>
  );
}

function SectionTitle({ T, children }) {
  return <div style={{
    fontSize: 10.5, fontFamily: T.fontMono, color: T.dim,
    letterSpacing: 1.6, textTransform: 'uppercase', marginBottom: 12,
  }}>{children}</div>;
}

function SubRow({ T, label, value, onClick }) {
  return (
    <button onClick={onClick} style={{
      width: '100%', display: 'flex', alignItems: 'center', gap: 10,
      background: T.panel, border: `1px solid ${T.line}`,
      padding: '10px 12px', marginBottom: 6, borderRadius: T.radius,
      cursor: 'pointer', color: T.text, textAlign: 'left',
      fontFamily: 'inherit',
    }}>
      <span style={{
        fontSize: 10, fontFamily: T.fontMono, letterSpacing: 1.2, textTransform: 'uppercase',
        color: T.dim, width: 44, flexShrink: 0,
      }}>{label}</span>
      <span style={{ fontSize: 12.5, color: T.text, flex: 1, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{value}</span>
      <span style={{ fontSize: 13, color: T.dim, flexShrink: 0 }}>›</span>
    </button>
  );
}

// ── Subscribe Modal: big URL + Copy + QR + client hints ──────
function SubscribeModal({ T, kind, year, onClose, onToast }) {
  const urls = {
    caldav:  { label: 'CalDAV Collection', url: 'https://cal.memojar.net/lunar', proto: 'https://' },
    webcal:  { label: 'iCal Subscription', url: 'webcal://cal.memojar.net/lunar.ics', proto: 'webcal://' },
    all:     { label: 'Subscribe', url: 'https://cal.memojar.net/lunar', proto: 'https://' },
  };
  const [currentKind, setKind] = React.useState(kind === 'all' ? 'caldav' : kind);
  const current = urls[currentKind];

  const copy = () => {
    navigator.clipboard?.writeText(current.url);
    onToast('Copied ' + current.url);
  };

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

  const steps = {
    caldav: [
      ['Apple Calendar', 'File › New CalDAV Account › Manual. Server: cal.memojar.net'],
      ['Fastmail / Nextcloud', 'Add subscription → paste URL'],
      ['Thunderbird', 'New Calendar → On the Network → CalDAV'],
    ],
    webcal: [
      ['iPhone / Mac', 'Tap the webcal:// link → "Subscribe"'],
      ['Google Calendar', 'Other calendars → From URL → use https://'],
      ['Outlook', 'Add calendar → Subscribe from web'],
    ],
  };

  return (
    <div onClick={onClose} style={{
      position: 'fixed', inset: 0, background: 'rgba(0,0,0,.45)', zIndex: 2000,
      display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 20,
    }}>
      <div onClick={e => e.stopPropagation()} style={{
        background: T.panel, color: T.text, borderRadius: 8,
        width: '100%', maxWidth: 520, maxHeight: '90vh', overflow: 'auto',
        fontFamily: T.fontSans, padding: '28px 28px 24px',
        border: `1px solid ${T.line}`,
        boxShadow: '0 20px 60px rgba(0,0,0,.25)',
      }}>
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', marginBottom: 16 }}>
          <div style={{ fontFamily: T.fontSerif, fontSize: 28, fontStyle: 'italic', fontWeight: 500 }}>
            {current.label}
          </div>
          <button onClick={onClose} style={{ background: 'none', border: 'none', color: T.dim, fontSize: 22, cursor: 'pointer', padding: 0 }}>×</button>
        </div>

        {kind === 'all' && (
          <div style={{ display: 'flex', gap: 4, marginBottom: 18 }}>
            {[['caldav','CalDAV'],['webcal','iCal']].map(([v, l]) => (
              <button key={v} onClick={() => setKind(v)}
                style={{
                  border: `1px solid ${currentKind === v ? T.text : T.line}`,
                  background: currentKind === v ? T.text : 'transparent',
                  color: currentKind === v ? T.bg : T.text,
                  padding: '5px 12px', fontSize: 11.5, borderRadius: T.radius, cursor: 'pointer',
                }}>{l}</button>
            ))}
          </div>
        )}

        <div style={{
          background: T.bg, border: `1px dashed ${T.line}`, borderRadius: T.radius,
          padding: '14px 16px', marginBottom: 14, wordBreak: 'break-all',
          fontFamily: T.fontMono, fontSize: 13.5, lineHeight: 1.5, color: T.text,
        }}>
          {current.url}
        </div>

        <div style={{ display: 'flex', gap: 8, marginBottom: 24 }}>
          <button onClick={copy} style={{
            flex: 1, padding: '10px 14px',
            background: T.text, color: T.bg, border: 'none', borderRadius: T.radius,
            fontSize: 13, cursor: 'pointer', fontFamily: T.fontSans, letterSpacing: 0.3,
          }}>Copy URL</button>
          {currentKind === 'webcal' && (
            <a href={current.url} style={{
              flex: 1, padding: '10px 14px',
              background: 'transparent', color: T.text,
              border: `1px solid ${T.line}`, borderRadius: T.radius,
              fontSize: 13, cursor: 'pointer', fontFamily: T.fontSans, letterSpacing: 0.3,
              textAlign: 'center', textDecoration: 'none', lineHeight: '1.3',
            }}>Open in Calendar ↗</a>
          )}
          <button onClick={() => { downloadIcs(year); onToast(`Downloaded lunar-${year}.ics`); }}
            style={{
              flex: 1, padding: '10px 14px',
              background: 'transparent', color: T.text,
              border: `1px solid ${T.line}`, borderRadius: T.radius,
              fontSize: 13, cursor: 'pointer', fontFamily: T.fontSans, letterSpacing: 0.3,
            }}>Download .ics</button>
        </div>

        <SectionTitle T={T}>How to subscribe</SectionTitle>
        <div style={{ display: 'flex', flexDirection: 'column', gap: 8, fontSize: 12.5 }}>
          {(steps[currentKind] || []).map(([who, how], i) => (
            <div key={i} style={{ display: 'flex', gap: 12, alignItems: 'baseline' }}>
              <span style={{ width: 120, flexShrink: 0, fontFamily: T.fontMono, color: T.dim, fontSize: 11 }}>{who}</span>
              <span style={{ flex: 1, color: T.text, lineHeight: 1.5 }}>{how}</span>
            </div>
          ))}
        </div>
      </div>
    </div>
  );
}

function Toast({ T, children }) {
  return (
    <div style={{
      position: 'fixed', bottom: 24, left: '50%', transform: 'translateX(-50%)',
      background: T.text, color: T.bg, padding: '10px 18px', borderRadius: T.radius,
      fontSize: 12.5, fontFamily: T.fontSans, zIndex: 3000,
      boxShadow: '0 6px 24px rgba(0,0,0,.25)', letterSpacing: 0.3,
    }}>{children}</div>
  );
}

// Minimal iCal generator — snapshot of lunar events for one year.
function downloadIcs(year) {
  const pad = n => String(n).padStart(2, '0');
  const events = [];
  // Collect festivals + jieqi + public holidays for the year
  for (let m = 1; m <= 12; m++) {
    const daysIn = new Date(year, m, 0).getDate();
    for (let d = 1; d <= daysIn; d++) {
      const f = window.LunarData.lunarFestival(year, m, d, 's');
      const j = window.LunarData.solarTerm(year, m, d, 's');
      const p = window.LunarData.chinaPublicHoliday(year, m, d);
      const s = window.LunarData.solarHoliday(year, m, d);
      const dstr = `${year}${pad(m)}${pad(d)}`;
      if (f) events.push({ d: dstr, title: f.zh });
      if (j) events.push({ d: dstr, title: j });
      if (p && p.type === 'off') events.push({ d: dstr, title: p.name + ' (放假)' });
      if (s && s.major) events.push({ d: dstr, title: s.zh });
    }
  }
  const now = new Date();
  const ts = `${now.getUTCFullYear()}${pad(now.getUTCMonth()+1)}${pad(now.getUTCDate())}T${pad(now.getUTCHours())}${pad(now.getUTCMinutes())}${pad(now.getUTCSeconds())}Z`;
  const lines = ['BEGIN:VCALENDAR','VERSION:2.0','PRODID:-//cal.memojar.net//Almanac//EN',
    'X-WR-CALNAME:Almanac · 农历','X-WR-TIMEZONE:Asia/Shanghai'];
  for (const e of events) {
    const y = e.d.slice(0,4), mo = e.d.slice(4,6), da = e.d.slice(6,8);
    const nextDay = new Date(Date.UTC(+y, +mo-1, +da+1));
    const dtend = `${nextDay.getUTCFullYear()}${pad(nextDay.getUTCMonth()+1)}${pad(nextDay.getUTCDate())}`;
    lines.push('BEGIN:VEVENT',
      `UID:${e.d}-${encodeURIComponent(e.title)}@cal.memojar.net`,
      `DTSTAMP:${ts}`,
      `DTSTART;VALUE=DATE:${e.d}`,
      `DTEND;VALUE=DATE:${dtend}`,
      `SUMMARY:${e.title}`,
      'END:VEVENT');
  }
  lines.push('END:VCALENDAR');
  const blob = new Blob([lines.join('\r\n')], { type: 'text/calendar' });
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url; a.download = `lunar-${year}.ics`;
  document.body.appendChild(a); a.click(); a.remove();
  setTimeout(() => URL.revokeObjectURL(url), 500);
}

// ── Month grid ────────────────────────────────────────────────
function MonthGrid({ T, year, month, weekStart, script, today, isMobile, onPrev, onNext, onToday, onJump, toolbar }) {
  const cells = buildGrid(year, month, weekStart);
  const weekdays = script === 's' && isMobile ? WEEKDAYS_CN : (weekStart === 0 ? WEEKDAYS_SUN : WEEKDAYS_MON);
  const orderedWeekdays = (script === 's' && isMobile)
    ? (weekStart === 0 ? WEEKDAYS_CN : [...WEEKDAYS_CN.slice(1), WEEKDAYS_CN[0]])
    : weekdays;

  return (
    <div style={{ flex: 1, display: 'flex', flexDirection: 'column', background: T.bg, color: T.text, overflow: 'hidden', minWidth: 0 }}>
      <TopBar T={T} year={year} month={month} script={script} isMobile={isMobile}
        onPrev={onPrev} onNext={onNext} onToday={onToday} onJump={onJump} toolbar={toolbar} />

      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(7, 1fr)', borderBottom: `1px solid ${T.line}`, background: T.bg }}>
        {orderedWeekdays.map((wd, i) => {
          const isSun = (weekStart === 0 && i === 0) || (weekStart === 1 && i === 6);
          return (
            <div key={i} style={{
              padding: isMobile ? '8px 6px' : '12px 14px',
              fontSize: isMobile ? 10.5 : 11.5,
              letterSpacing: script === 's' && isMobile ? 0.5 : 1.4,
              textTransform: 'uppercase',
              fontFamily: script === 's' && isMobile ? T.fontKai : T.fontMono,
              color: isSun ? T.sunday : T.dim,
              borderLeft: i === 0 ? 'none' : `1px solid ${T.line}`,
              textAlign: isMobile ? 'center' : 'left',
            }}>{wd}</div>
          );
        })}
      </div>

      <div style={{
        flex: 1, display: 'grid',
        gridTemplateColumns: 'repeat(7, 1fr)',
        gridTemplateRows: 'repeat(6, 1fr)', minHeight: 0,
      }}>
        {cells.map((cell, i) => (
          <DayCell key={i} T={T} cell={cell} index={i} weekStart={weekStart}
            script={script} isMobile={isMobile}
            isToday={cell.year === today.y && cell.month === today.m && cell.day === today.d} />
        ))}
      </div>
    </div>
  );
}

function TopBar({ T, year, month, script, isMobile, onPrev, onNext, onToday, onJump, toolbar }) {
  const daysIn = new Date(year, month, 0).getDate();
  const pillar = window.LunarData.yearPillar(year, month, 15, script);
  const seasonName = ['冬','冬','春','春','春','夏','夏','夏','秋','秋','秋','冬'][month-1];

  const termsThisMonth = [];
  for (let d = 1; d <= daysIn; d++) {
    const t = window.LunarData.solarTerm(year, month, d, script);
    if (t) termsThisMonth.push(`${t} ${d}`);
  }

  const [pickerOpen, setPickerOpen] = React.useState(false);

  return (
    <div style={{
      display: 'flex', flexDirection: isMobile ? 'column' : 'row',
      alignItems: isMobile ? 'stretch' : 'flex-end',
      justifyContent: 'space-between',
      padding: isMobile ? '16px 16px 12px' : '28px 40px 20px',
      borderBottom: `1px solid ${T.line}`, background: T.bg, position: 'relative',
      gap: isMobile ? 10 : 0,
    }}>
      <div style={{ display: 'flex', alignItems: 'flex-end', justifyContent: 'space-between', gap: 8 }}>
        <div>
          {!isMobile && <div style={{ fontSize: 11.5, fontFamily: T.fontMono, color: T.dim, letterSpacing: 1.4, textTransform: 'uppercase', marginBottom: 8 }}>
            {pillar.ganzhi}年 · {pillar.zodiac} · {seasonName}
          </div>}
          <button onClick={() => setPickerOpen(o => !o)}
            style={{
              background: 'transparent', border: 'none', cursor: 'pointer', padding: 0,
              fontFamily: T.fontSerif, fontSize: isMobile ? 32 : 64, lineHeight: 1, letterSpacing: -1.5, color: T.text,
              textAlign: 'left', fontWeight: 500, fontStyle: 'italic',
            }}>
            {MONTH_NAMES[month - 1]} <span style={{ color: T.faint }}>{year}</span>
            <span style={{ fontSize: isMobile ? 14 : 18, marginLeft: 10, color: T.dim, fontStyle: 'normal' }}>▾</span>
          </button>
          {!isMobile && termsThisMonth.length > 0 && (
            <div style={{ marginTop: 12, fontFamily: T.fontKai, fontSize: 14, color: T.kindColors.jieqi, letterSpacing: 0.5 }}>
              {termsThisMonth.join('  ·  ')}
            </div>
          )}
        </div>
        {isMobile && (
          <div style={{ display: 'flex', gap: 4 }}>
            <NavBtn T={T} onClick={onPrev} small>‹</NavBtn>
            <NavBtn T={T} filled onClick={onToday} small>今</NavBtn>
            <NavBtn T={T} onClick={onNext} small>›</NavBtn>
          </div>
        )}
      </div>

      <div style={{ display: 'flex', alignItems: 'flex-end', gap: 12, flexWrap: 'wrap', justifyContent: isMobile ? 'flex-start' : 'flex-end' }}>
        {!isMobile && toolbar}
        {!isMobile && (
          <div style={{ display: 'flex', gap: 4 }}>
            <NavBtn T={T} onClick={onPrev}>‹</NavBtn>
            <NavBtn T={T} filled onClick={onToday}>Today</NavBtn>
            <NavBtn T={T} onClick={onNext}>›</NavBtn>
          </div>
        )}
      </div>

      {pickerOpen && <YearMonthPicker T={T} year={year} month={month} isMobile={isMobile}
        onPick={(y, m) => { onJump(y, m); setPickerOpen(false); }}
        onClose={() => setPickerOpen(false)} />}
    </div>
  );
}

const MIN_YEAR = 1990, MAX_YEAR = 2040;

function YearMonthPicker({ T, year, month, isMobile, onPick, onClose }) {
  const [viewYear, setViewYear] = React.useState(year);
  const ref = React.useRef(null);
  React.useEffect(() => {
    const close = (e) => { if (ref.current && !ref.current.contains(e.target)) onClose(); };
    setTimeout(() => document.addEventListener('pointerdown', close), 0);
    return () => document.removeEventListener('pointerdown', close);
  }, [onClose]);

  // All years in range as a scroll strip
  const years = [];
  for (let y = MIN_YEAR; y <= MAX_YEAR; y++) years.push(y);

  return (
    <div ref={ref} style={{
      position: 'absolute',
      top: isMobile ? 60 : 100,
      left: isMobile ? 16 : 40,
      right: isMobile ? 16 : 'auto',
      zIndex: 50,
      background: T.panel, border: `1px solid ${T.line}`, borderRadius: T.radius,
      padding: 18, width: isMobile ? 'auto' : 360,
      boxShadow: '0 14px 44px rgba(0,0,0,.18)',
    }}>
      <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 14 }}>
        <button onClick={() => setViewYear(Math.max(MIN_YEAR, viewYear - 1))}
          style={{ background: 'transparent', border: 'none', cursor: 'pointer', color: T.text, fontSize: 20, padding: '2px 10px' }}>‹</button>
        <div style={{ fontFamily: T.fontSerif, fontSize: 30, color: T.text, fontWeight: 500, fontStyle: 'italic' }}>
          {viewYear}
        </div>
        <button onClick={() => setViewYear(Math.min(MAX_YEAR, viewYear + 1))}
          style={{ background: 'transparent', border: 'none', cursor: 'pointer', color: T.text, fontSize: 20, padding: '2px 10px' }}>›</button>
      </div>

      {/* Year scroll strip */}
      <div style={{
        display: 'flex', gap: 2, overflowX: 'auto', marginBottom: 14,
        padding: '2px 0 6px',
        scrollbarWidth: 'thin',
      }}>
        {years.map(y => (
          <button key={y} onClick={() => setViewYear(y)}
            ref={el => { if (el && y === viewYear) el.scrollIntoView({ inline: 'center', block: 'nearest' }); }}
            style={{
              flex: '0 0 auto',
              padding: '5px 10px', fontSize: 12, fontFamily: T.fontMono,
              border: `1px solid ${y === viewYear ? T.accent : 'transparent'}`,
              background: y === viewYear ? T.accent : 'transparent',
              color: y === viewYear ? T.todayFg : (y === year ? T.text : T.dim),
              cursor: 'pointer', borderRadius: T.radius,
            }}>{y}</button>
        ))}
      </div>

      <input type="range" min={MIN_YEAR} max={MAX_YEAR} value={viewYear}
        onChange={e => setViewYear(Number(e.target.value))}
        style={{ width: '100%', accentColor: T.accent, marginBottom: 14 }} />

      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 6 }}>
        {MONTH_NAMES.map((mn, i) => {
          const active = viewYear === year && i + 1 === month;
          return (
            <button key={mn} onClick={() => onPick(viewYear, i + 1)}
              style={{
                padding: '10px 4px',
                border: `1px solid ${active ? T.accent : T.line}`,
                background: active ? T.accent : 'transparent',
                color: active ? T.todayFg : T.text,
                fontFamily: T.fontSerif, fontSize: 14, fontStyle: 'italic',
                cursor: 'pointer', borderRadius: T.radius,
              }}>{mn.slice(0, 3)}</button>
          );
        })}
      </div>
    </div>
  );
}

function NavBtn({ T, children, filled, onClick, small }) {
  return (
    <button onClick={onClick} style={{
      border: `1px solid ${filled ? T.text : T.line}`,
      background: filled ? T.text : 'transparent',
      color: filled ? T.bg : T.text,
      padding: small ? '5px 10px' : '7px 14px',
      fontSize: small ? 13 : 13.5,
      fontFamily: T.fontSans,
      cursor: 'pointer', borderRadius: T.radius,
      minWidth: small ? 30 : 34,
    }}>{children}</button>
  );
}

function DayCell({ T, cell, index, script, isToday, weekStart, isMobile }) {
  const col = index % 7;
  const row = Math.floor(index / 7);
  const isSun = (weekStart === 0 && col === 0) || (weekStart === 1 && col === 6);
  const LD = window.LunarData;

  const lunar = LD.lunarLabel(cell.year, cell.month, cell.day, script);
  const jieqi = LD.solarTerm(cell.year, cell.month, cell.day, script);
  const lunarFest = LD.lunarFestival(cell.year, cell.month, cell.day, script);
  const solarFest = LD.solarHoliday(cell.year, cell.month, cell.day);
  const floatFest = LD.floatingHoliday(cell.year, cell.month, cell.day);
  const publicH = LD.chinaPublicHoliday(cell.year, cell.month, cell.day);

  // Primary lunar/festival line (priority order)
  let lunarPrimary = null, lunarTone = T.lunarColor, isStrong = false;
  if (lunarFest) { lunarPrimary = lunarFest.zh; lunarTone = T.kindColors.festival; isStrong = true; }
  else if (jieqi) { lunarPrimary = jieqi; lunarTone = T.kindColors.jieqi; isStrong = true; }
  else if (lunar) { lunarPrimary = lunar.display; if (lunar.ld === 1) { isStrong = true; lunarTone = T.kindColors.lunar; } }

  // Secondary labels: solar holiday + floating holiday (shown as small chip list)
  const secondaryLabels = [];
  if (solarFest) secondaryLabels.push({ text: solarFest.zh, tone: solarFest.major ? T.kindColors.pub : T.kindColors.obs, strong: !!solarFest.major });
  if (floatFest) secondaryLabels.push({ text: floatFest.zh, tone: T.kindColors.obs, strong: false });

  // Public holiday badge (休 / 班) — 'off' = statutory, 'inLieu' = 调休补休 (visually same).
  const badge = publicH
    ? (publicH.type === 'work'
        ? { text: '班', bg: T.kindColors.work }
        : { text: '休', bg: T.kindColors.pub })
    : null;

  return (
    <div style={{
      borderLeft: col === 0 ? 'none' : `1px solid ${T.line}`,
      borderTop: row === 0 ? 'none' : `1px solid ${T.line}`,
      padding: isMobile ? '6px 5px' : '10px 12px 8px',
      position: 'relative',
      opacity: cell.outside ? 0.3 : 1,
      display: 'flex', flexDirection: 'column',
      gap: isMobile ? 2 : 4,
      minHeight: 0, overflow: 'hidden',
    }}>
      {/* Date row */}
      <div style={{ display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: 4 }}>
        <span style={{
          fontFamily: T.fontSerif,
          fontSize: isMobile ? 18 : 28,
          lineHeight: 1, fontWeight: 500,
          fontStyle: 'italic',
          color: isSun && !cell.outside ? T.sunday : T.text,
          ...(isToday ? {
            background: T.todayBg, color: T.todayFg,
            padding: isMobile ? '3px 7px' : '4px 10px',
            borderRadius: isMobile ? 12 : T.radius,
            fontSize: isMobile ? 15 : 20,
            fontFamily: T.fontSans, fontWeight: 600, letterSpacing: -0.3,
            fontStyle: 'normal',
          } : {}),
        }}>{cell.day}</span>
        {badge && !isMobile && (
          <span style={{
            fontSize: 9.5, fontFamily: T.fontKai, color: '#fff', background: badge.bg,
            padding: '1px 5px', borderRadius: 2, lineHeight: 1.3, fontWeight: 600,
          }}>{badge.text}</span>
        )}
      </div>

      {/* Lunar/festival primary */}
      {lunarPrimary && (
        <div style={{
          fontFamily: T.fontKai,
          fontSize: isMobile ? 11 : 14,
          color: lunarTone,
          lineHeight: 1.15,
          fontWeight: isStrong ? 600 : 400,
          overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap',
        }}>{lunarPrimary}</div>
      )}

      {/* Secondary holidays */}
      {!isMobile && secondaryLabels.slice(0, 2).map((s, i) => (
        <div key={i} style={{
          fontFamily: T.fontKai, fontSize: 12, color: s.tone,
          fontWeight: s.strong ? 600 : 400, lineHeight: 1.1,
          overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap',
        }}>{s.text}</div>
      ))}

      {/* Mobile badge */}
      {badge && isMobile && (
        <div style={{
          position: 'absolute', top: 4, right: 4,
          fontSize: 8.5, fontFamily: T.fontKai, color: '#fff', background: badge.bg,
          padding: '1px 4px', borderRadius: 2, fontWeight: 600,
        }}>{badge.text}</div>
      )}
    </div>
  );
}

// ── Main Calendar shell ───────────────────────────────────────
function Calendar({ theme, setTheme, accent, weekStart: ws0 = 1, script, setScript }) {
  const baseTheme = THEMES[theme];
  const T = accent ? {
    ...baseTheme, accent,
    lunarColor: theme === 'rice' ? accent : baseTheme.lunarColor,
    todayBg: theme === 'dark' ? accent : baseTheme.todayBg,
    kindColors: { ...baseTheme.kindColors, festival: accent, pub: accent },
  } : baseTheme;

  const isMobile = useMediaQuery('(max-width: 760px)');
  const now = new Date();
  const today = { y: now.getFullYear(), m: now.getMonth() + 1, d: now.getDate() };

  const [nav, setNav] = React.useState(() => {
    try {
      const raw = localStorage.getItem('almanac-nav');
      if (raw) { const o = JSON.parse(raw); if (o.y && o.m) return o; }
    } catch {}
    return { y: today.y, m: today.m };
  });
  React.useEffect(() => {
    try { localStorage.setItem('almanac-nav', JSON.stringify(nav)); } catch {}
  }, [nav]);

  const goto = (y, m) => {
    while (m < 1)  { m += 12; y -= 1; }
    while (m > 12) { m -= 12; y += 1; }
    if (y < MIN_YEAR) { y = MIN_YEAR; m = 1; }
    if (y > MAX_YEAR) { y = MAX_YEAR; m = 12; }
    setNav({ y, m });
  };

  // Keyboard: arrow keys nav month, shift+arrow nav year
  React.useEffect(() => {
    const onKey = (e) => {
      if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return;
      if (e.key === 'ArrowLeft')  goto(nav.y, nav.m - (e.shiftKey ? 12 : 1));
      if (e.key === 'ArrowRight') goto(nav.y, nav.m + (e.shiftKey ? 12 : 1));
      if (e.key === 't' || e.key === 'T') setNav({ y: today.y, m: today.m });
    };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [nav, today.y, today.m]);

  const toolbar = <Toolbar T={T}
    theme={theme} setTheme={setTheme}
    script={script} setScript={setScript}
    isMobile={isMobile} />;

  return (
    <div style={{
      width: '100%', height: '100%',
      display: 'flex', flexDirection: isMobile ? 'column' : 'row',
      background: T.bg, color: T.text,
      fontFamily: T.fontSans, overflow: 'hidden',
      fontSize: '16px',
    }}>
      <Sidebar T={T} script={script} year={nav.y} month={nav.m} isMobile={isMobile} toolbar={toolbar} />
      <MonthGrid T={T} year={nav.y} month={nav.m} weekStart={ws0} script={script} today={today}
        isMobile={isMobile}
        onPrev={() => goto(nav.y, nav.m - 1)}
        onNext={() => goto(nav.y, nav.m + 1)}
        onToday={() => setNav({ y: today.y, m: today.m })}
        onJump={(y, m) => goto(y, m)}
        toolbar={toolbar} />
    </div>
  );
}

Object.assign(window, { Calendar, THEMES });
