// components.jsx — shared UI for the Nurse CNE prototype

const { useState, useEffect, useRef, useMemo, useCallback, Fragment } = React;

// ─────────────────────────────────────────────────────────────────────────────
// Icons (stroke-based, matched to body font weight)
// ─────────────────────────────────────────────────────────────────────────────
const Icon = ({ name, size = 20, stroke = 1.6, color = 'currentColor' }) => {
  const s = { width: size, height: size, fill: 'none', stroke: color, strokeWidth: stroke, strokeLinecap: 'round', strokeLinejoin: 'round' };
  switch (name) {
    case 'home':     return <svg viewBox="0 0 24 24" {...s}><path d="M3 11l9-7 9 7v9a2 2 0 01-2 2h-4v-7h-6v7H5a2 2 0 01-2-2v-9z"/></svg>;
    case 'compass':  return <svg viewBox="0 0 24 24" {...s}><circle cx="12" cy="12" r="9"/><path d="M15.5 8.5l-2 5-5 2 2-5z"/></svg>;
    case 'plus':     return <svg viewBox="0 0 24 24" {...s}><path d="M12 5v14M5 12h14"/></svg>;
    case 'book':     return <svg viewBox="0 0 24 24" {...s}><path d="M4 4h12a3 3 0 013 3v13H7a3 3 0 01-3-3V4z"/><path d="M4 17a3 3 0 013-3h12"/></svg>;
    case 'user':     return <svg viewBox="0 0 24 24" {...s}><circle cx="12" cy="8" r="4"/><path d="M4 21c1.5-4 5-6 8-6s6.5 2 8 6"/></svg>;
    case 'bookmark': return <svg viewBox="0 0 24 24" {...s}><path d="M6 3h12v18l-6-4-6 4V3z"/></svg>;
    case 'bell':     return <svg viewBox="0 0 24 24" {...s}><path d="M6 9a6 6 0 1112 0c0 5 2 6 2 6H4s2-1 2-6z"/><path d="M10 19a2 2 0 004 0"/></svg>;
    case 'search':   return <svg viewBox="0 0 24 24" {...s}><circle cx="11" cy="11" r="7"/><path d="M20 20l-3.5-3.5"/></svg>;
    case 'filter':   return <svg viewBox="0 0 24 24" {...s}><path d="M3 5h18M6 12h12M10 19h4"/></svg>;
    case 'chev-r':   return <svg viewBox="0 0 24 24" {...s}><path d="M9 6l6 6-6 6"/></svg>;
    case 'chev-l':   return <svg viewBox="0 0 24 24" {...s}><path d="M15 6l-6 6 6 6"/></svg>;
    case 'chev-d':   return <svg viewBox="0 0 24 24" {...s}><path d="M6 9l6 6 6-6"/></svg>;
    case 'close':    return <svg viewBox="0 0 24 24" {...s}><path d="M6 6l12 12M6 18L18 6"/></svg>;
    case 'pin':      return <svg viewBox="0 0 24 24" {...s}><path d="M12 22s7-7.5 7-13a7 7 0 10-14 0c0 5.5 7 13 7 13z"/><circle cx="12" cy="9" r="2.5"/></svg>;
    case 'globe':    return <svg viewBox="0 0 24 24" {...s}><circle cx="12" cy="12" r="9"/><path d="M3 12h18M12 3a14 14 0 010 18M12 3a14 14 0 000 18"/></svg>;
    case 'clock':    return <svg viewBox="0 0 24 24" {...s}><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/></svg>;
    case 'calendar': return <svg viewBox="0 0 24 24" {...s}><rect x="3" y="5" width="18" height="16" rx="2"/><path d="M3 10h18M8 3v4M16 3v4"/></svg>;
    case 'check':    return <svg viewBox="0 0 24 24" {...s}><path d="M5 12.5l4.5 4.5L20 6.5"/></svg>;
    case 'check-circ': return <svg viewBox="0 0 24 24" {...s}><circle cx="12" cy="12" r="9"/><path d="M8 12.5l3 3 5-6"/></svg>;
    case 'cert':     return <svg viewBox="0 0 24 24" {...s}><circle cx="12" cy="9" r="5"/><path d="M9 13l-2 8 5-3 5 3-2-8"/></svg>;
    case 'download': return <svg viewBox="0 0 24 24" {...s}><path d="M12 4v12M7 11l5 5 5-5M5 21h14"/></svg>;
    case 'upload':   return <svg viewBox="0 0 24 24" {...s}><path d="M12 21V8M7 13l5-5 5 5M5 4h14"/></svg>;
    case 'sparkle':  return <svg viewBox="0 0 24 24" {...s}><path d="M12 3l1.7 5.3L19 10l-5.3 1.7L12 17l-1.7-5.3L5 10l5.3-1.7L12 3z"/></svg>;
    case 'list':     return <svg viewBox="0 0 24 24" {...s}><path d="M4 6h16M4 12h16M4 18h16"/></svg>;
    case 'grid':     return <svg viewBox="0 0 24 24" {...s}><rect x="4" y="4" width="7" height="7" rx="1"/><rect x="13" y="4" width="7" height="7" rx="1"/><rect x="4" y="13" width="7" height="7" rx="1"/><rect x="13" y="13" width="7" height="7" rx="1"/></svg>;
    case 'arrow-r':  return <svg viewBox="0 0 24 24" {...s}><path d="M5 12h14M13 6l6 6-6 6"/></svg>;
    case 'dot-vert': return <svg viewBox="0 0 24 24" {...s}><circle cx="12" cy="5" r="1.4" fill={color} stroke="none"/><circle cx="12" cy="12" r="1.4" fill={color} stroke="none"/><circle cx="12" cy="19" r="1.4" fill={color} stroke="none"/></svg>;
    case 'spark':    return <svg viewBox="0 0 24 24" {...s}><path d="M5 12l3 3 4-7 3 5 4-3"/></svg>;
    case 'pdf':      return <svg viewBox="0 0 24 24" {...s}><path d="M7 3h7l5 5v13H7z"/><path d="M14 3v5h5"/><path d="M9 14h1.5a1.5 1.5 0 010 3H9zM13.5 14H15v3h-1.5z" strokeWidth="1.2"/></svg>;
    case 'leaf':     return <svg viewBox="0 0 24 24" {...s}><path d="M5 19c0-7 5-12 14-13-1 9-6 14-13 14-1 0-1-1-1-1z"/><path d="M5 19l8-8"/></svg>;
    case 'shield':   return <svg viewBox="0 0 24 24" {...s}><path d="M12 3l8 3v6c0 4.5-3.5 8-8 9-4.5-1-8-4.5-8-9V6l8-3z"/></svg>;
    case 'moon':     return <svg viewBox="0 0 24 24" {...s}><path d="M21 12.8A9 9 0 1111.2 3a7 7 0 009.8 9.8z"/></svg>;
    case 'edit':     return <svg viewBox="0 0 24 24" {...s}><path d="M14 4l6 6-10 10H4v-6L14 4z"/><path d="M13 5l6 6"/></svg>;
    default:         return <svg viewBox="0 0 24 24" {...s}><circle cx="12" cy="12" r="3"/></svg>;
  }
};

// ─────────────────────────────────────────────────────────────────────────────
// Topic pill (color-coded)
// ─────────────────────────────────────────────────────────────────────────────
function TopicPill({ topic, label, size = 'sm' }) {
  const tint = TOPIC_TINT[topic] || TOPIC_TINT.ethics;
  return (
    <span className="topic-pill" style={{
      background: tint.bg,
      color: tint.fg,
      fontSize: size === 'lg' ? 12 : 11,
      padding: size === 'lg' ? '5px 11px' : undefined,
    }}>
      {label}
    </span>
  );
}

// ─────────────────────────────────────────────────────────────────────────────
// CategoryDot — core/elective/specialty marker
// ─────────────────────────────────────────────────────────────────────────────
const CAT_COLORS = {
  core:      'oklch(0.42 0.06 175)',  // jade
  elective:  'oklch(0.68 0.10 50)',   // clay
  specialty: 'oklch(0.45 0.09 270)',  // indigo
};
function CatDot({ cat, size = 7 }) {
  return <span className="dot-status" style={{ width: size, height: size, background: CAT_COLORS[cat] }} />;
}

// ─────────────────────────────────────────────────────────────────────────────
// Course card (full-width list)
// Uses M.div so it gets whileHover (subtle lift) + whileTap (depress)
// physics — feels alive without a layout shift on hover.
// Keyboard-accessible: tabIndex=0, Enter/Space triggers click.
// ─────────────────────────────────────────────────────────────────────────────
function CourseCard({ course, lang, onClick, compact = false }) {
  const ended = typeof isPastCourse === 'function' ? isPastCourse(course) : false;
  // Single source of truth for "is this open / full / closed / ending soon"
  const info = typeof enrolmentStatusInfo === 'function' ? enrolmentStatusInfo(course) : null;
  // Days until registration deadline — only show if upcoming and reasonable.
  const deadlineDays = (typeof daysUntil === 'function')
    ? daysUntil(course.registrationDeadline || course.raw?.registration_deadline)
    : null;
  const showDeadline = !ended && info?.showCta !== false &&
    deadlineDays != null && deadlineDays >= 0 && deadlineDays <= 14;

  const onKey = (e) => {
    if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); onClick && onClick(); }
  };
  return (
    <M.div
      role="button"
      tabIndex={0}
      aria-label={L(course.title, lang)}
      className={'course-card' + (ended ? ' is-ended' : '')}
      onClick={onClick}
      onKeyDown={onKey}
      whileHover={{ y: -2 }}
      whileTap={{ scale: 0.985 }}
      transition={{ duration: 0.18 }}
    >
      <div style={{ display: 'flex', justifyContent: 'space-between', gap: 12, alignItems: 'flex-start' }}>
        <div style={{ flex: 1, minWidth: 0 }}>
          <div className="row" style={{ marginBottom: 8 }}>
            <TopicPill topic={course.topic} label={L(course.topicLabel, lang)} />
            {/* Enrolment-status pill — only when there's something real to say */}
            {!ended && info && info.kind !== 'open' && (
              <span className={'cap-pill tone-' + info.tone}>
                {L(info.label, lang)}
              </span>
            )}
            {ended && (
              <span className="cap-pill tone-muted">
                {lang === 'zh' ? '已過期' : 'Ended'}
              </span>
            )}
            {!ended && course.online && <span className="chip"><Icon name="globe" size={11} stroke={1.8} /> {L(STR.online, lang)}</span>}
            {!ended && course.cost === 0 && <span className="chip">{L(STR.free, lang)}</span>}
          </div>
          <h4>{L(course.title, lang)}</h4>
        </div>
        <div style={{ textAlign: 'right', flexShrink: 0, paddingTop: 2 }}>
          <div className="pts">{course.points}</div>
          <div className="pts-label">{L(STR.pts, lang)}</div>
        </div>
      </div>
      <div className="meta" style={{ display: 'flex', gap: 14, alignItems: 'center', flexWrap: 'wrap' }}>
        <span style={{ display: 'inline-flex', gap: 5, alignItems: 'center' }}>
          <Icon name="calendar" size={13} stroke={1.5} />
          {fmtShort(course.date, lang)}{course.time ? ' · ' + course.time.split(/[–-]/)[0] : ''}
        </span>
        <span style={{ display: 'inline-flex', gap: 5, alignItems: 'center' }}>
          <Icon name="pin" size={13} stroke={1.5} />
          {L(course.providerShort, lang)}
        </span>
        {/* Seats remaining — numeric (or "不限名額" for self-study).
            Show whenever seat data exists and the course isn't already ended/full. */}
        {!ended && course.seats && course.seats.kind !== 'full' && (
          <span style={{
            display: 'inline-flex', gap: 5, alignItems: 'center',
            color: course.seats.kind === 'limited' ? 'var(--accent)' : 'var(--fg-2)',
            fontWeight: course.seats.kind === 'limited' ? 600 : 500,
          }}>
            <Icon name="user" size={13} stroke={1.6} />
            {course.seats.kind === 'unlimited'
              ? L(STR.unlimited, lang)
              : (lang === 'zh'
                  ? `剩餘 ${course.seats.remaining} 名`
                  : `${course.seats.remaining} ${L(STR.seatsRemaining, lang)}`)}
          </span>
        )}
        {!compact && showDeadline && (
          <span className={'deadline-pill' + (deadlineDays <= 3 ? ' urgent' : '')}>
            <Icon name="clock" size={11} stroke={1.7} />
            {deadlineDays === 0
              ? L(STR.closesToday, lang)
              : `${deadlineDays} ${L(deadlineDays === 1 ? STR.dayLeft : STR.daysLeft, lang)}`}
          </span>
        )}
      </div>
    </M.div>
  );
}

// ─────────────────────────────────────────────────────────────────────────────
// Mini course card (horizontal scroll)
// ─────────────────────────────────────────────────────────────────────────────
function MiniCourseCard({ course, lang, onClick }) {
  const onKey = (e) => {
    if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); onClick && onClick(); }
  };
  return (
    <M.div
      role="button"
      tabIndex={0}
      aria-label={L(course.title, lang)}
      className="mini-card"
      onClick={onClick}
      onKeyDown={onKey}
      whileHover={{ y: -2 }}
      whileTap={{ scale: 0.985 }}
      transition={{ duration: 0.18 }}
    >
      <div className="mc-head">
        <TopicPill topic={course.topic} label={L(course.topicLabel, lang)} />
        <span className="mc-date">{fmtShort(course.date, lang)}</span>
      </div>
      <h5 title={L(course.title, lang)}>{L(course.title, lang)}</h5>
      <div className="mc-foot">
        <span className="mc-provider">
          <Icon name="pin" size={11} stroke={1.6} />
          <span className="mc-provider-name">{L(course.providerShort, lang)}</span>
        </span>
        <div className="mc-pts">
          <span className="serif mc-pts-num">{course.points}</span>
          <span className="mono muted-2 mc-pts-label">{L(STR.pts, lang)}</span>
        </div>
      </div>
    </M.div>
  );
}

// ─────────────────────────────────────────────────────────────────────────────
// Segmented progress ring (categories) — used in dashboard wallet
// ─────────────────────────────────────────────────────────────────────────────
function ProgressRing({ size = 132, stroke = 9, segments }) {
  // segments: [{value, color}], sum should not exceed total
  const total = segments.reduce((s, x) => s + x.total, 0);
  const r = (size - stroke) / 2;
  const c = 2 * Math.PI * r;
  let offset = 0;
  return (
    <svg width={size} height={size} viewBox={`0 0 ${size} ${size}`} style={{ transform: 'rotate(-90deg)' }}>
      <circle cx={size/2} cy={size/2} r={r}
              stroke="var(--bg-2)" strokeWidth={stroke} fill="none" />
      {segments.map((seg, i) => {
        const dash = (seg.value / total) * c;
        const dashOffset = c - offset - dash;
        const el = (
          <circle key={i} cx={size/2} cy={size/2} r={r}
                  stroke={seg.color}
                  strokeWidth={stroke}
                  fill="none"
                  strokeLinecap="round"
                  strokeDasharray={`${dash} ${c}`}
                  strokeDashoffset={-offset}
                  style={{ transition: 'stroke-dashoffset .6s, stroke-dasharray .6s' }}
          />
        );
        offset += dash + 4; // 4px gap
        return el;
      })}
    </svg>
  );
}

// ─────────────────────────────────────────────────────────────────────────────
// Tab Bar (5 tabs with center FAB)
// • Every tab is M.button so it gets physical tap feedback.
// • The center FAB scales further (deeper press feels heavier — matches
//   the visual weight of the dark circle).
// • Active state's icon stroke shift is animated via the surrounding
//   color transition we set in CSS.
// ─────────────────────────────────────────────────────────────────────────────
function TabBar({ active, onChange, lang, onLog }) {
  const tab = (id, icon, labelKey) => (
    <M.button
      key={id}
      className={'tab' + (active === id ? ' on' : '')}
      onClick={() => onChange(id)}
      whileTap={{ scale: 0.92 }}
      transition={{ duration: 0.14, ease: window.VITAL_SPRING ? window.VITAL_SPRING.bounce : undefined }}
      aria-label={L(STR[labelKey], lang)}
      aria-current={active === id ? 'page' : undefined}
    >
      <Icon name={icon} size={22} stroke={active === id ? 1.9 : 1.6} />
      <span>{L(STR[labelKey], lang)}</span>
    </M.button>
  );
  return (
    <div className="tab-bar">
      {tab('home', 'home', 'home')}
      {tab('discover', 'compass', 'discover')}
      <M.button
        className="tab fab"
        onClick={onLog}
        aria-label={L(STR.logCourse, lang) || 'Log course'}
        whileTap={{ scale: 0.88 }}
        whileHover={{ y: -1 }}
        transition={{ duration: 0.16, ease: window.VITAL_SPRING ? window.VITAL_SPRING.bounce : undefined }}
      >
        <div className="fab-circle">
          <Icon name="plus" size={22} stroke={2.2} />
        </div>
      </M.button>
      {tab('record', 'book', 'myRecord')}
      {tab('profile', 'user', 'profile')}
    </div>
  );
}

// ─────────────────────────────────────────────────────────────────────────────
// Bottom sheet (mobile) / centered modal (≥768)
//
// Exit-animation strategy:
//   The parent typically renders us conditionally — when it sets us to
//   null we'd unmount instantly, killing any exit animation. To avoid
//   forcing the parent to wrap us in <AnimatePresence>, the Sheet
//   intercepts onClose: on user-initiated close (backdrop / x button /
//   Esc) it sets `closing=true`, lets the CSS exit animation play
//   (~240ms), THEN calls the supplied onClose. This means every code
//   path that closes the sheet flows through the same animation, with
//   zero changes upstream.
//
//   prefers-reduced-motion users skip the wait — the CSS animation
//   collapses to ~0s via the global @media block in styles.css.
// ─────────────────────────────────────────────────────────────────────────────
function Sheet({ open, onClose, children, title }) {
  const [closing, setClosing] = useState(false);
  const reduced = useReducedMotion ? useReducedMotion() : false;
  const closeRef = useRef(onClose);
  closeRef.current = onClose;

  const playClose = useCallback(() => {
    if (closing) return;
    if (reduced) { onClose && onClose(); return; }
    setClosing(true);
    setTimeout(() => {
      setClosing(false);
      closeRef.current && closeRef.current();
    }, 240);
  }, [closing, reduced, onClose]);

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

  if (!open) return null;
  return (
    <Fragment>
      <div
        className={'sheet-backdrop' + (closing ? ' is-closing' : '')}
        onClick={playClose}
        aria-hidden="true"
      />
      <div
        className={'sheet' + (closing ? ' is-closing' : '')}
        role="dialog"
        aria-modal="true"
        aria-label={title || undefined}
      >
        <div className="sheet-handle" aria-hidden="true" />
        {title && (
          <div style={{
            padding: '14px 20px 10px',
            display: 'flex', alignItems: 'center', justifyContent: 'space-between',
            flexShrink: 0,
          }}>
            <div className="h-section">{title}</div>
            <M.button
              className="icon-btn"
              onClick={playClose}
              style={{ width: 32, height: 32 }}
              whileTap={{ scale: 0.9 }}
              aria-label="Close"
            >
              <Icon name="close" size={16} />
            </M.button>
          </div>
        )}
        <div style={{ flex: 1, overflowY: 'auto', overflowX: 'hidden' }}>
          {children}
        </div>
      </div>
    </Fragment>
  );
}

// ─────────────────────────────────────────────────────────────────────────────
// Header (replaces nav bar — light, custom)
// ─────────────────────────────────────────────────────────────────────────────
function ScreenHeader({ title, eyebrow, actions, onBack, lang }) {
  return (
    <div className="top">
      <div style={{ flex: 1, minWidth: 0 }}>
        {onBack && (
          <button className="icon-btn" style={{ marginBottom: 12 }} onClick={onBack}>
            <Icon name="chev-l" size={18} />
          </button>
        )}
        {eyebrow && <div className="eyebrow" style={{ marginBottom: 6 }}>{eyebrow}</div>}
        <div className="h-screen">{title}</div>
      </div>
      {actions && <div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>{actions}</div>}
    </div>
  );
}

Object.assign(window, {
  Icon, TopicPill, CatDot, CAT_COLORS, CourseCard, MiniCourseCard,
  ProgressRing, TabBar, Sheet, ScreenHeader,
});
