/* global React */
// Shared UI + helpers for the course-partner portal. The brand pieces mirror
// the business-plan PDF: forest hero bands with grain + a mascot watermark,
// dark stat cards, mono pill badges. The date/time helpers moved here from
// manager.jsx so every panel file can use them without redeclaring.

// ─── Date/time helpers (shared across all manager panels) ─────────────
// 'YYYY-MM-DD' for a local Date.
// NB: deliberately NOT named isoDate/addDays — manager-business.jsx already
// declares top-level `const isoDate` and `const addDays` (string-based, with
// different signatures). Every text/babel file shares one global scope, so a
// same-named function here would collide with that const and throw
// "Identifier already declared", killing that whole file at load.
function isoFromDate(d) {
  const p = n => String(n).padStart(2, '0');
  return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}`;
}
// Calendar-day arithmetic that survives a DST change. Adding 24h in
// milliseconds lands an hour off on the two clock-change days each year,
// which is enough to repeat a day (fall back) or skip one (spring forward)
// when walking a date range — stepping the date component instead lets
// Date normalize it correctly.
function addCalendarDays(d, n) {
  return new Date(d.getFullYear(), d.getMonth(), d.getDate() + n);
}
function todayStr() { return isoFromDate(new Date()); }
function dayLabel(iso) {
  return new Date(iso).toLocaleDateString('en-US', { weekday: 'short', month: 'short', day: 'numeric' });
}
function timeLabel(iso) {
  return new Date(iso).toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit' });
}
// Viewing windows (minutes-of-day). Twilight is Sandbox's core slot.
const TIME_WINDOWS = [
  { key: 'all',      label: 'All Day',  start: 0,    end: 1440 },
  { key: 'morning',  label: 'Morning',  start: 360,  end: 720  }, // 6a–12p
  { key: 'midday',   label: 'Midday',   start: 720,  end: 960  }, // 12–4p
  { key: 'twilight', label: 'Twilight', start: 960,  end: 1200 }, // 4–8p
  { key: 'night',    label: 'Night',    start: 1200, end: 1440 }, // 8p–12a
];
const hmLabel = (mins) => {
  const h = Math.floor(mins / 60), m = mins % 60;
  return new Date(2000, 0, 1, h, m).toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit' });
};
// Generate 'HH:MM' times across [start,end) at `intervalMin` minutes.
function genTimes(start, end, intervalMin) {
  const out = [];
  for (let m = start; m < end; m += intervalMin) {
    out.push(`${String(Math.floor(m / 60)).padStart(2, '0')}:${String(m % 60).padStart(2, '0')}`);
  }
  return out;
}
// 'HH:MM' of a slot's local start time, for matching against generated times.
function slotHM(iso) {
  const d = new Date(iso);
  return `${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`;
}
function minutesOfDay(iso) { const d = new Date(iso); return d.getHours() * 60 + d.getMinutes(); }
const toMin = (hm) => parseInt(hm.slice(0, 2), 10) * 60 + parseInt(hm.slice(3), 10);
function money(n) { return '$' + (n || 0).toLocaleString('en-US'); }

// ─── useCountUp — animate a stat number to its target ─────────────────
function useCountUp(target, ms = 900) {
  const [value, setValue] = React.useState(0);
  React.useEffect(() => {
    const to = Number(target) || 0;
    const from = 0;
    let raf; const t0 = performance.now();
    const tick = (t) => {
      const f = Math.min(1, (t - t0) / ms);
      const eased = 1 - Math.pow(1 - f, 3);
      setValue(Math.round(from + (to - from) * eased));
      if (f < 1) raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(raf);
  }, [target, ms]);
  return value;
}

// ─── DemoChip — marks a card as running on fabricated numbers ─────────
function DemoChip({ corner = true }) {
  return (
    <span className="pill-mono" style={{
      background: 'rgba(216,205,174,0.9)', color: 'var(--forest-dark)',
      border: '1px solid rgba(14,28,19,0.15)',
      ...(corner ? { position: 'absolute', top: 10, right: 10, zIndex: 3 } : {}),
    }}>Demo Data</span>
  );
}


// ─── Icon — small line-icon set, replaces emoji glyphs throughout the
// portal. 20x20, single stroke weight, round caps/joins — one consistent
// system rather than a mix of platform emoji renderings. ─────────────
const ICON_PATHS = {
  pin:      'M10 2c-3.3 0-6 2.6-6 6 0 4.5 6 10 6 10s6-5.5 6-10c0-3.4-2.7-6-6-6Z M10 10.3a2.3 2.3 0 1 0 0-4.6 2.3 2.3 0 0 0 0 4.6Z',
  clock:    'M10 17a7 7 0 1 0 0-14 7 7 0 0 0 0 14Z M10 6.5V10l2.6 1.6',
  flag:     'M5 18V3 M5 3.4c1.8-1 3.6-1 5.4 0s3.6 1 5.4 0v7.4c-1.8 1-3.6 1-5.4 0s-3.6-1-5.4 0',
  bars:     'M4 17V11 M9.3 17V7 M14.7 17V3 M20 17H0',
  trend:    'M3 14.5 8 9l3.5 3.5L17 6 M12.5 6H17v4.5',
  users:    'M7.2 9.3a3 3 0 1 0 0-6 3 3 0 0 0 0 6Z M1.8 17c.5-3.4 2.8-5.4 5.4-5.4s4.9 2 5.4 5.4 M14.3 4.2a2.7 2.7 0 0 1 0 5.2 M15 11.7c2.2.4 3.7 2 4.2 4.7',
  link:     'M8 12.2 12.2 8 M7.6 5.4 9 4a3.3 3.3 0 0 1 4.7 4.7l-1.4 1.4 M12.4 14.6 11 16a3.3 3.3 0 0 1-4.7-4.7l1.4-1.4',
  chevron:  'M5.5 7.5 10 12l4.5-4.5',
  power:    'M10 2.5V10 M4.7 5.2a6.3 6.3 0 1 0 10.6 0',
  refresh:  'M3 10a7 7 0 0 1 12-4.9L17 7 M17 10a7 7 0 0 1-12 4.9L3 13 M15 3v4h-4 M5 17v-4h4',
  // Clubhouse: pitched roof, body, door. Drawn at the same 20x20 and
  // stroke weight as the rest so it sits in the nav without shouting.
  clubhouse: 'M2.4 9.6 10 3.6l7.6 6 M4.4 8.9V17h11.2V8.9 M8.2 17v-4.7h3.6V17',
};
function Icon({ name, size = 18, strokeWidth = 1.6, style }) {
  const d = ICON_PATHS[name];
  if (!d) return null;
  return (
    <svg width={size} height={size} viewBox="0 0 20 20" fill="none" style={{ display: 'block', flexShrink: 0, ...style }} aria-hidden="true">
      <path d={d} stroke="currentColor" strokeWidth={strokeWidth} strokeLinecap="round" strokeLinejoin="round"/>
    </svg>
  );
}

// ─── ConfirmDialog — the site's own confirmation surface, replacing
// window.confirm for anything consequential (bulk schedule changes, etc.)
// so it matches the rest of the product instead of a raw browser popup. ──
function ConfirmDialog({ open, title, body, confirmLabel = 'Confirm', cancelLabel = 'Cancel', danger, busy, onConfirm, onCancel }) {
  React.useEffect(() => {
    if (!open) return undefined;
    const onKey = (e) => { if (e.key === 'Escape' && !busy) onCancel(); };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [open, busy, onCancel]);

  if (!open) return null;
  return (
    <div style={{
      position: 'fixed', inset: 0, zIndex: 1000, display: 'flex', alignItems: 'center', justifyContent: 'center',
      background: 'rgba(14,28,19,0.45)', padding: 20,
    }} className="fade-in" onMouseDown={() => !busy && onCancel()}>
      <div onMouseDown={e => e.stopPropagation()} className="floating rise-in" style={{ width: 'min(420px, 100%)', boxShadow: 'var(--shadow-lg)', padding: 24 }}>
        <div style={{ fontFamily: 'var(--font-display)', fontSize: 20, color: 'var(--paper)' }}>{title}</div>
        {body && <div style={{ fontSize: 13.5, color: 'var(--ink-soft)', marginTop: 10, lineHeight: 1.55 }}>{body}</div>}
        <div style={{ display: 'flex', gap: 10, marginTop: 22, justifyContent: 'flex-end' }}>
          <button className="btn btn-ghost" onClick={onCancel} disabled={busy}>{cancelLabel}</button>
          <button className={danger ? 'btn btn-danger' : 'btn btn-forest'} onClick={onConfirm} disabled={busy}>
            {busy ? 'Working…' : confirmLabel}
          </button>
        </div>
      </div>
    </div>
  );
}

// ─── SplitStrip / SplitCell — a row of related metrics read together as
// ONE bordered strip with dividers, instead of N separate floating cards.
// Used anywhere a page previously reached for "three stat cards in a row".
function SplitCell({ label, value, sub, emphasis, border, badge }) {
  return (
    <div style={{ padding: '18px 20px', borderLeft: border ? '1px solid var(--line)' : 'none', background: emphasis ? 'var(--surface-sunken)' : 'transparent' }}>
      <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8 }}>
        <div className="eyebrow">{label}</div>
        {badge}
      </div>
      <div className="metric metric-lg" style={{ marginTop: 9 }}>{value}</div>
      {sub && <div style={{ fontSize: 12, color: 'var(--ink-faint)', marginTop: 5, lineHeight: 1.45 }}>{sub}</div>}
    </div>
  );
}
function SplitStrip({ children, style, id }) {
  return (
    <div id={id} style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))', border: 'var(--hairline)', borderRadius: 'var(--r-md)', overflow: 'hidden', ...style }}>
      {children}
    </div>
  );
}

// ─── PageSkeleton — a content-shaped loading placeholder matching the
// hero + SplitStrip layout every Money/Insights/People page uses, instead
// of a generic spinner replacing the whole page. ───────────────────────
function PageSkeleton() {
  return (
    <div>
      <div className="skeleton" style={{ width: 170, height: 11, borderRadius: 4 }}/>
      <div className="skeleton" style={{ width: 240, height: 46, borderRadius: 8, marginTop: 12 }}/>
      <div className="skeleton" style={{ width: 340, height: 13, borderRadius: 4, marginTop: 16 }}/>
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))', border: 'var(--hairline)', borderRadius: 'var(--r-md)', marginTop: 30, overflow: 'hidden' }}>
        {[0, 1, 2].map(i => (
          <div key={i} style={{ padding: '18px 20px', borderLeft: i ? '1px solid var(--line)' : 'none' }}>
            <div className="skeleton" style={{ width: 110, height: 10, borderRadius: 4 }}/>
            <div className="skeleton" style={{ width: 84, height: 26, borderRadius: 6, marginTop: 11 }}/>
          </div>
        ))}
      </div>
    </div>
  );
}

Object.assign(window, {
  isoFromDate, addCalendarDays, todayStr, dayLabel, timeLabel, TIME_WINDOWS, hmLabel, genTimes, slotHM, minutesOfDay, toMin, money,
  useCountUp, DemoChip, Icon, ConfirmDialog, SplitCell, SplitStrip, PageSkeleton,
});
