// DSSystemWidget.jsx — Meridian · per-system "representing widget" for Today
//
// Ported from AXIS-6 SystemWidgetKit + SystemWidgetFaces, DS-renamed.
//
// Every active prebuilt system (and every AI-generated module) earns one
// feature-aware Today card in a SHARED SHAPE:
//
//   ┌─────────────────────────────────────────────┐
//   │ [icon] EYEBROW  system name · meta   [chip]  │   ← origin / generated / paused
//   │ Headline — the at-a-glance state             │
//   │ ▁▂▃ sparkline / progress strip ▅▆▇           │   ← face-specific
//   │ ───────────────────────────────────────────  │
//   │ [ Primary log / act CTA ]            Open  →  │   ← 44pt targets
//   └─────────────────────────────────────────────┘
//   tapping the body opens the system.
//
// Faces: journal · food · experiment · routine · goal · cycle · social.
// States: default · acted · paused · empty (faces ignore states that don't apply).
//
// Single entry point: DSSystemWidget({ face, state, ...props }) dispatches to
// the right face. DSBlockTile is the pre-design generic fallback for systems
// without a bespoke face.
//
// Calm Meridian: one structural accent (Axis blue); green only for streaks /
// positive deltas; numbers in JetBrains Mono tabular. Reduced-motion honoured —
// the progress ring only animates under (prefers-reduced-motion: no-preference),
// otherwise it renders its final state.

const DSW_Icon = window.MIcons;
const DSW_noop = () => {};

// One-time styles: ring draw + reduced-motion guard.
if (typeof document !== 'undefined' && !document.getElementById('dsw-anim')) {
  const s = document.createElement('style');
  s.id = 'dsw-anim';
  s.textContent = `
    @media (prefers-reduced-motion: no-preference) {
      @keyframes dsw-ring { from { stroke-dashoffset: var(--dsw-circ); } to { stroke-dashoffset: var(--dsw-off); } }
      .dsw-ring-anim { animation: dsw-ring .7s cubic-bezier(.2,.8,.2,1) both; }
    }
  `;
  document.head.appendChild(s);
}

// ──────────────────────────────────────────────────────────
// Source badge — a small inline fallback. The kit's full DSSourceBadge
// (7 variants × 2 sizes) lives in its own file; we only need the compact
// chip here, so we use the real one when present and degrade gracefully
// when this preview loads the widget without it.
// ──────────────────────────────────────────────────────────
const DSW_BADGE = {
  journal:    { user: true,  glyph: '✎', label: 'Journal' },
  experiment: { user: false, glyph: '▶', label: 'Experiment' },
  predefined: { user: true,  glyph: '★', label: 'Predefined' },
  synced:     { user: false, glyph: '↻', label: 'Synced' },
};
function DSW_SourceBadge({ variant }) {
  if (typeof window !== 'undefined' && window.DSSourceBadge) {
    return window.DSSourceBadge({ variant, size: 'compact' });
  }
  const v = DSW_BADGE[variant];
  if (!v) return null;
  return (
    <span role="img" aria-label={`Source: ${v.label}`} style={{
      display: 'inline-flex', alignItems: 'center', gap: 4,
      padding: '3px 7px', borderRadius: 999,
      background: v.user ? M.inkMuted : M.axisSoft,
      color: v.user ? '#FFFFFF' : M.axisDark,
      font: `500 10px/1 ${M.sans}`, letterSpacing: '0.01em', whiteSpace: 'nowrap',
      userSelect: 'none',
    }}>
      <span aria-hidden="true" style={{ opacity: 0.92 }}>{v.glyph}</span>
      <span>{v.label}</span>
    </span>
  );
}

// ──────────────────────────────────────────────────────────
// Face glyphs — small Meridian-style line icons (stroke 2.2, round)
// drawn locally because the shared set has no pencil / fork / target.
// ──────────────────────────────────────────────────────────
const DSFaceGlyph = {
  journal: (p) => (
    <svg width={p.size||18} height={p.size||18} viewBox="0 0 24 24" fill="none" stroke="currentColor"
         strokeWidth="2.1" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
      <path d="M4 5a1 1 0 0 1 1-1h11l4 4v11a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1z"/>
      <path d="M8 9h6M8 13h8M8 17h5"/>
    </svg>
  ),
  food: (p) => (
    <svg width={p.size||18} height={p.size||18} viewBox="0 0 24 24" fill="none" stroke="currentColor"
         strokeWidth="2.1" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
      <path d="M6 3v7M9 3v7M6 10h3M7.5 10v11"/>
      <path d="M16 3c-1.5 0-2.5 2-2.5 5s1 4 2.5 4 2.5-1 2.5-4S17.5 3 16 3zM16 12v9"/>
    </svg>
  ),
  experiment: (p) => DSW_Icon.Beaker({ size: p.size||18, strokeWidth: 2.1 }),
  routine: (p) => (
    <svg width={p.size||18} height={p.size||18} viewBox="0 0 24 24" fill="none" stroke="currentColor"
         strokeWidth="2.1" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
      <path d="M4 6.5l2 2 3-3M4 13.5l2 2 3-3M4 20l2 2 3-3"/>
      <path d="M13 6h7M13 13h7M13 20h7"/>
    </svg>
  ),
  goal: (p) => (
    <svg width={p.size||18} height={p.size||18} viewBox="0 0 24 24" fill="none" stroke="currentColor"
         strokeWidth="2.1" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
      <circle cx="12" cy="12" r="8"/><circle cx="12" cy="12" r="4"/><circle cx="12" cy="12" r="0.6" fill="currentColor"/>
    </svg>
  ),
  // Cycle — phased ring with a marker; locally drawn (no DS equivalent).
  cycle: (p) => (
    <svg width={p.size||18} height={p.size||18} viewBox="0 0 24 24" fill="none" stroke="currentColor"
         strokeWidth="2.1" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
      <path d="M20 12a8 8 0 1 1-3.2-6.4"/>
      <path d="M20 4v4h-4"/>
      <circle cx="12" cy="4" r="1.1" fill="currentColor" stroke="none"/>
    </svg>
  ),
  social: (p) => DSW_Icon.Profile({ size: p.size||18, strokeWidth: 2.1 }),
  library: (p) => (
    <svg width={p.size||18} height={p.size||18} viewBox="0 0 24 24" fill="none" stroke="currentColor"
         strokeWidth="2.1" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
      <path d="M5 4h9a2 2 0 0 1 2 2v14a2 2 0 0 0-2-2H5zM19 6v14M16 4l3 .6"/>
    </svg>
  ),
};

// State → palette resolver. Keeps the shell calm; paused desaturates.
function DSW_stateTone(state) {
  if (state === 'paused') {
    return { iconBg: M.surfaceAlt, iconFg: M.inkFaint, cardOpacity: 0.62, mutedStrip: true };
  }
  return { iconBg: M.axisSoft, iconFg: M.axisDark, cardOpacity: 1, mutedStrip: false };
}

// ──────────────────────────────────────────────────────────
// Shared shell — every system widget is one of these.
// ──────────────────────────────────────────────────────────
function DSWidgetShell({
  glyph,            // key into DSFaceGlyph
  name,             // system name (eyebrow)
  meta,             // optional small meta after the name
  state = 'default',
  generated = false,
  sourceBadge,      // optional source-badge node (overridden by generated / paused chips)
  headline,         // node — the at-a-glance state
  strip,            // node — sparkline / progress strip
  ctaLabel,
  ctaVariant = 'primary',
  onCta,
  ctaDisabled = false,
  acted = false,    // shows the logged-today confirmation in place of CTA chrome
  actedLabel,       // e.g. "Logged 13:14"
  onOpen,
  onTap,
}) {
  const tone = DSW_stateTone(state);
  const isPaused = state === 'paused';

  return (
    <DSCard padding={0} onClick={onTap} style={{ opacity: tone.cardOpacity }}>
      {/* Header */}
      <div style={{ display: 'flex', alignItems: 'flex-start', gap: 11, padding: '13px 14px 0' }}>
        <div style={{
          flexShrink: 0, width: 34, height: 34, borderRadius: 10,
          background: tone.iconBg, color: tone.iconFg,
          display: 'flex', alignItems: 'center', justifyContent: 'center',
        }}>
          {DSFaceGlyph[glyph]?.({ size: 18 })}
        </div>

        <div style={{ flex: 1, minWidth: 0, paddingTop: 1 }}>
          <div style={{
            font: `500 11px/1.3 ${M.sans}`, color: M.inkMuted,
            letterSpacing: '0.07em', textTransform: 'uppercase',
            whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis',
          }}>{name}{meta && <span style={{ color: M.inkFaint, textTransform: 'none', letterSpacing: 0 }}> · {meta}</span>}</div>
        </div>

        <div style={{ flexShrink: 0, display: 'flex', alignItems: 'center', gap: 6 }}>
          {isPaused
            ? <DSChip variant="default" style={{ padding: '4px 9px', fontSize: 11, color: M.inkMuted }} dot={M.inkFaint}>Paused</DSChip>
            : generated
              ? <DSChip variant="accent" style={{ padding: '3px 8px', fontSize: 10 }}>AI · generated</DSChip>
              : sourceBadge || null}
        </div>
      </div>

      {/* Headline */}
      <div style={{ padding: '9px 14px 0', filter: isPaused ? 'grayscale(0.4)' : 'none' }}>
        {headline}
      </div>

      {/* Strip */}
      {strip && (
        <div style={{ padding: '11px 14px 0', opacity: tone.mutedStrip ? 0.5 : 1 }}>
          {strip}
        </div>
      )}

      {/* Footer — CTA + Open. Both ≥44pt. */}
      <div style={{
        display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 10,
        marginTop: 12, padding: '0 8px 6px 14px',
        borderTop: `1px solid ${M.ruleSoft}`,
      }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 10, minHeight: 48 }}>
          {acted ? (
            <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
              <span style={{
                width: 20, height: 20, borderRadius: 999, background: M.success,
                display: 'inline-flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0,
              }}><DSW_Icon.Check size={13} strokeWidth={2.6} /></span>
              <span style={{ font: `500 13px/1 ${M.sans}`, color: M.ink }}>
                {actedLabel || 'Logged today'}
              </span>
            </div>
          ) : (
            <DSButton
              size="sm" variant={isPaused ? 'secondary' : ctaVariant}
              disabled={ctaDisabled}
              onClick={(e) => { e.stopPropagation(); onCta?.(); }}
              style={{ minHeight: 36, padding: '9px 13px' }}
            >{ctaLabel}</DSButton>
          )}
          {acted && onCta && !isPaused && (
            <button onClick={(e) => { e.stopPropagation(); onCta?.(); }} style={DSW_textBtn}>
              Add another
            </button>
          )}
        </div>

        <button
          onClick={(e) => { e.stopPropagation(); onOpen?.(); }}
          aria-label={`Open ${name}`}
          style={{
            display: 'inline-flex', alignItems: 'center', gap: 4,
            minHeight: 44, padding: '0 8px',
            background: 'transparent', border: 'none', cursor: 'pointer',
            font: `500 13px/1 ${M.sans}`, color: isPaused ? M.inkMuted : M.axis,
          }}>
          Open <DSW_Icon.ChevronR size={15} />
        </button>
      </div>
    </DSCard>
  );
}

const DSW_textBtn = {
  background: 'transparent', border: 'none', cursor: 'pointer',
  font: `500 13px/1 ${M.sans}`, color: M.axis, padding: '6px 4px',
};

// Headline helper — strong leading clause + muted tail.
function DSW_Headline({ children, sub }) {
  return (
    <>
      <div style={{ font: `500 16px/1.3 ${M.sans}`, color: M.ink, letterSpacing: '-0.005em', textWrap: 'pretty' }}>
        {children}
      </div>
      {sub && <div style={{ font: `400 12.5px/1.45 ${M.sans}`, color: M.inkMuted, marginTop: 3 }}>{sub}</div>}
    </>
  );
}

// ──────────────────────────────────────────────────────────
// Strip primitives
// ──────────────────────────────────────────────────────────

// Journaling: 7-day entry cadence. Filled = wrote; hollow = none; today ringed.
function DSW_CadenceStrip({ days = [1,0,2,1,0,1,0], todayIndex = 6, muted }) {
  const labels = ['M','T','W','T','F','S','S'];
  return (
    <div style={{ display: 'flex', alignItems: 'flex-end', gap: 7 }}>
      {days.map((c, i) => {
        const isToday = i === todayIndex;
        const filled = c > 0;
        const h = filled ? 14 + Math.min(c, 3) * 6 : 10;
        return (
          <div key={i} style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 5 }}>
            <div style={{
              width: 12, height: h, borderRadius: 6,
              background: filled ? (muted ? M.inkFaint : M.axis) : 'transparent',
              border: filled ? 'none' : `1.5px ${isToday ? 'solid' : 'dashed'} ${isToday ? M.axis : M.rule}`,
              boxShadow: isToday && filled ? `0 0 0 2px ${M.axisSoft}` : 'none',
            }} />
            <div style={{ font: `500 9px/1 ${M.mono}`, color: isToday ? M.axisDark : M.inkFaint }}>{labels[i]}</div>
          </div>
        );
      })}
    </div>
  );
}

// Food: meal slots filled as logged.
function DSW_MealDots({ logged = ['breakfast','lunch'], muted }) {
  const slots = [
    { id: 'breakfast', label: 'Breakfast' },
    { id: 'lunch',     label: 'Lunch' },
    { id: 'dinner',    label: 'Dinner' },
    { id: 'snack',     label: 'Snacks' },
  ];
  return (
    <div style={{ display: 'flex', gap: 6 }}>
      {slots.map(s => {
        const on = logged.includes(s.id);
        return (
          <div key={s.id} style={{
            flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 5,
            padding: '7px 4px', borderRadius: 9,
            background: on ? (muted ? M.surfaceAlt : M.axisSoft) : M.surfaceAlt,
            border: `1px solid ${on ? 'transparent' : M.ruleSoft}`,
            font: `500 10px/1 ${M.sans}`,
            color: on ? (muted ? M.inkMuted : M.axisDark) : M.inkFaint,
          }}>
            {on && <span style={{
              width: 12, height: 12, borderRadius: 999, background: muted ? M.inkFaint : M.axis,
              display: 'inline-flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0,
            }}><DSW_Icon.Check size={9} strokeWidth={3} /></span>}
            {s.label}
          </div>
        );
      })}
    </div>
  );
}

// Experiments: day-of-N tracker (reuses the daily-tracking dot rhythm).
function DSW_StreakStrip({ day = 6, total = 14, streak = 5, muted }) {
  return (
    <div>
      <div style={{ display: 'flex', gap: 4 }}>
        {Array.from({ length: total }).map((_, i) => {
          const past = i < day - 1;
          const isToday = i === day - 1;
          return (
            <div key={i} style={{
              flex: 1, height: 6, borderRadius: 4,
              background: past ? (muted ? M.inkFaint : M.axis)
                        : isToday ? (muted ? M.inkMuted : M.axisDark)
                        : M.ruleSoft,
              boxShadow: isToday ? `0 0 0 2px ${M.axisSoft}` : 'none',
            }} />
          );
        })}
      </div>
      <div style={{ display: 'flex', justifyContent: 'space-between', marginTop: 7 }}>
        <span style={{ font: `500 11px/1 ${M.mono}`, color: M.inkMuted, fontVariantNumeric: 'tabular-nums' }}>
          Day {day} / {total}
        </span>
        <span style={{ font: `500 11px/1 ${M.sans}`, color: muted ? M.inkMuted : M.success }}>
          {streak}-day streak
        </span>
      </div>
    </div>
  );
}

// Routines: segmented step bar + next step.
function DSW_StepProgress({ done = 2, total = 5, nextLabel = 'Cold shower', muted }) {
  return (
    <div>
      <div style={{ display: 'flex', gap: 5 }}>
        {Array.from({ length: total }).map((_, i) => (
          <div key={i} style={{
            flex: 1, height: 7, borderRadius: 4,
            background: i < done ? (muted ? M.inkFaint : M.axis) : M.ruleSoft,
          }} />
        ))}
      </div>
      <div style={{ display: 'flex', justifyContent: 'space-between', marginTop: 7 }}>
        <span style={{ font: `500 11px/1 ${M.mono}`, color: M.inkMuted, fontVariantNumeric: 'tabular-nums' }}>
          {done} / {total} done
        </span>
        {nextLabel && <span style={{ font: `400 11px/1 ${M.sans}`, color: M.inkMuted }}>Next · {nextLabel}</span>}
      </div>
    </div>
  );
}

// Goals: progress ring + days-left, laid beside a short caption.
function DSW_ProgressRing({ value = 0.68, size = 56, stroke = 6, daysLeft = 4, muted, caption }) {
  const r = (size - stroke) / 2;
  const circ = 2 * Math.PI * r;
  const off = circ * (1 - Math.max(0, Math.min(1, value)));
  const col = muted ? M.inkFaint : M.axis;
  return (
    <div style={{ display: 'flex', alignItems: 'center', gap: 14 }}>
      <div style={{ position: 'relative', width: size, height: size, flexShrink: 0 }}>
        <svg width={size} height={size} viewBox={`0 0 ${size} ${size}`}>
          <circle cx={size/2} cy={size/2} r={r} fill="none" stroke={M.ruleSoft} strokeWidth={stroke} />
          <circle
            cx={size/2} cy={size/2} r={r} fill="none" stroke={col} strokeWidth={stroke}
            strokeLinecap="round"
            strokeDasharray={circ}
            strokeDashoffset={off}
            className="dsw-ring-anim"
            style={{ '--dsw-circ': `${circ}px`, '--dsw-off': `${off}px`, transform: 'rotate(-90deg)', transformOrigin: 'center' }}
          />
        </svg>
        <div style={{
          position: 'absolute', inset: 0, display: 'flex', alignItems: 'center', justifyContent: 'center',
          font: `500 14px/1 ${M.mono}`, color: M.ink, fontVariantNumeric: 'tabular-nums',
        }}>{Math.round(value * 100)}<span style={{ fontSize: 9, opacity: 0.6 }}>%</span></div>
      </div>
      <div style={{ minWidth: 0 }}>
        {caption}
        <div style={{ font: `500 11px/1 ${M.mono}`, color: muted ? M.inkMuted : M.warning, marginTop: caption ? 6 : 0, fontVariantNumeric: 'tabular-nums' }}>
          {daysLeft} days left
        </div>
      </div>
    </div>
  );
}

// Cycle: phase ribbon — current day marked inside a four-phase band.
function DSW_PhaseRibbon({ day = 14, total = 28, phase = 'Ovulatory', muted }) {
  // Four phases as proportional segments of the cycle.
  const phases = [
    { id: 'Menstrual',  span: 5,  col: muted ? M.inkFaint : '#C26A6A' },
    { id: 'Follicular', span: 8,  col: muted ? M.ruleSoft : '#E6EAFF' },
    { id: 'Ovulatory',  span: 4,  col: muted ? M.inkMuted : M.axis },
    { id: 'Luteal',     span: 11, col: muted ? M.ruleSoft : '#CFE3D8' },
  ];
  const pct = Math.max(0, Math.min(1, (day - 0.5) / total)) * 100;
  return (
    <div>
      <div style={{ position: 'relative', height: 10 }}>
        <div style={{ display: 'flex', gap: 2, height: 10, borderRadius: 5, overflow: 'hidden' }}>
          {phases.map(p => (
            <div key={p.id} style={{ flex: p.span, background: p.col }} />
          ))}
        </div>
        <div style={{
          position: 'absolute', top: -3, left: `${pct}%`, transform: 'translateX(-50%)',
          width: 4, height: 16, borderRadius: 3,
          background: M.ink, boxShadow: `0 0 0 2px ${M.surface}`,
        }} />
      </div>
      <div style={{ display: 'flex', justifyContent: 'space-between', marginTop: 8 }}>
        <span style={{ font: `500 11px/1 ${M.mono}`, color: M.inkMuted, fontVariantNumeric: 'tabular-nums' }}>
          Day {day} / {total}
        </span>
        <span style={{ font: `500 11px/1 ${M.sans}`, color: muted ? M.inkMuted : M.axisDark }}>{phase} phase</span>
      </div>
    </div>
  );
}

// ──────────────────────────────────────────────────────────
// Per-system faces — each a thin config over DSWidgetShell.
// ──────────────────────────────────────────────────────────

function DSJournalWidget({ state = 'default', onTap = DSW_noop, onOpen = DSW_noop, onCta = DSW_noop }) {
  const days = { default:[1,0,1,1,0,1,0], acted:[1,0,1,1,0,1,1], paused:[1,0,1,1,0,0,0], empty:[0,0,0,0,0,0,0] }[state];
  const headline = {
    default: <DSW_Headline sub="Last entry · yesterday 22:10">Today's prompt — what's on your mind this morning?</DSW_Headline>,
    acted:   <DSW_Headline sub="280 words · 2 min ago">Entry saved for today.</DSW_Headline>,
    paused:  <DSW_Headline sub="Resume to get daily prompts again">Journaling is paused.</DSW_Headline>,
    empty:   <DSW_Headline sub="Your reflections stay private to you">Start your first journal entry.</DSW_Headline>,
  }[state];
  return (
    <DSWidgetShell
      glyph="journal" name="Journal" state={state}
      sourceBadge={<DSW_SourceBadge variant="journal" />}
      headline={headline}
      strip={<DSW_CadenceStrip days={days} muted={state==='paused'} />}
      acted={state==='acted'} actedLabel="Logged 22:10"
      ctaLabel={state==='paused' ? 'Resume' : state==='empty' ? 'Write first entry' : 'New entry'}
      onCta={onCta} onOpen={onOpen} onTap={onTap}
    />
  );
}

function DSFoodWidget({ state = 'default', onTap = DSW_noop, onOpen = DSW_noop, onCta = DSW_noop }) {
  const logged = { default:['breakfast','lunch'], acted:['breakfast','lunch','dinner'], paused:['breakfast'], empty:[] }[state];
  const headline = {
    default: <DSW_Headline sub="Dinner not logged yet">2 of 3 meals logged today.</DSW_Headline>,
    acted:   <DSW_Headline sub="Grilled salmon · 14:42">All 3 meals in — nicely done.</DSW_Headline>,
    paused:  <DSW_Headline sub="Resume to keep your meal log">Food logging is paused.</DSW_Headline>,
    empty:   <DSW_Headline sub="Snap a photo, speak it, or quick-tag">No meals logged yet today.</DSW_Headline>,
  }[state];
  return (
    <DSWidgetShell
      glyph="food" name="Food" state={state}
      headline={headline}
      strip={<DSW_MealDots logged={logged} muted={state==='paused'} />}
      acted={state==='acted'} actedLabel="Dinner logged 14:42"
      ctaLabel={state==='paused' ? 'Resume' : 'Log a meal'}
      onCta={onCta} onOpen={onOpen} onTap={onTap}
    />
  );
}

function DSExperimentWidget({ state = 'default', onTap = DSW_noop, onOpen = DSW_noop, onCta = DSW_noop }) {
  const cfg = {
    default: { day: 6,  streak: 5 },
    acted:   { day: 6,  streak: 6 },
    paused:  { day: 6,  streak: 0 },
    empty:   { day: 1,  streak: 0 },
  }[state];
  const headline = {
    default: <DSW_Headline sub="Did you take magnesium last night?">Day 6 — log today's data point.</DSW_Headline>,
    acted:   <DSW_Headline sub="Magnesium · taken · sleep 7h 20m">Today's data point is in.</DSW_Headline>,
    paused:  <DSW_Headline sub="Resume to continue the 14-day run">Experiment is paused.</DSW_Headline>,
    empty:   <DSW_Headline sub="Starts collecting from your first log">Day 1 — begin tracking.</DSW_Headline>,
  }[state];
  return (
    <DSWidgetShell
      glyph="experiment" name="Experiment" meta="Magnesium & sleep" state={state}
      sourceBadge={<DSW_SourceBadge variant="experiment" />}
      headline={headline}
      strip={<DSW_StreakStrip day={cfg.day} total={14} streak={cfg.streak} muted={state==='paused'} />}
      acted={state==='acted'} actedLabel="Logged today · day 6"
      ctaLabel={state==='paused' ? 'Resume' : state==='empty' ? 'Log day 1' : 'Log today'}
      onCta={onCta} onOpen={onOpen} onTap={onTap}
    />
  );
}

function DSRoutineWidget({ state = 'default', onTap = DSW_noop, onOpen = DSW_noop, onCta = DSW_noop }) {
  const cfg = {
    default: { done: 2, next: 'Cold shower' },
    acted:   { done: 5, next: null },
    paused:  { done: 0, next: 'Hydrate' },
    empty:   { done: 0, next: 'Hydrate' },
  }[state];
  const headline = {
    default: <DSW_Headline sub="Next up · cold shower">2 of 5 morning steps done.</DSW_Headline>,
    acted:   <DSW_Headline sub="Finished 08:05 · 5-day streak">Morning routine complete.</DSW_Headline>,
    paused:  <DSW_Headline sub="Resume to see today's steps">Morning routine is paused.</DSW_Headline>,
    empty:   <DSW_Headline sub="5 steps · about 20 min">Ready to start your routine.</DSW_Headline>,
  }[state];
  return (
    <DSWidgetShell
      glyph="routine" name="Morning routine" state={state}
      sourceBadge={<DSW_SourceBadge variant="predefined" />}
      headline={headline}
      strip={<DSW_StepProgress done={cfg.done} total={5} nextLabel={cfg.next} muted={state==='paused'} />}
      acted={state==='acted'} actedLabel="All steps done · 08:05"
      ctaLabel={state==='paused' ? 'Resume' : state==='empty' ? 'Start routine' : 'Mark next done'}
      onCta={onCta} onOpen={onOpen} onTap={onTap}
    />
  );
}

function DSGoalWidget({ state = 'default', onTap = DSW_noop, onOpen = DSW_noop, onCta = DSW_noop }) {
  const cfg = {
    default: { value: 0.68, days: 4 },
    acted:   { value: 0.74, days: 4 },
    paused:  { value: 0.68, days: 4 },
    empty:   { value: 0.0,  days: 14 },
  }[state];
  const caption = {
    default: <div style={{ font: `500 13px/1.35 ${M.sans}`, color: M.ink }}>6,800 / 10,000 steps</div>,
    acted:   <div style={{ font: `500 13px/1.35 ${M.sans}`, color: M.ink }}>7,400 / 10,000 steps</div>,
    paused:  <div style={{ font: `500 13px/1.35 ${M.sans}`, color: M.inkMuted }}>6,800 / 10,000 steps</div>,
    empty:   <div style={{ font: `500 13px/1.35 ${M.sans}`, color: M.ink }}>0 / 10,000 steps</div>,
  }[state];
  const headline = {
    default: <DSW_Headline>10k steps a day — keep the streak alive.</DSW_Headline>,
    acted:   <DSW_Headline sub="Today's progress synced">Logged — you're ahead of pace.</DSW_Headline>,
    paused:  <DSW_Headline sub="Resume to rejoin the challenge">Challenge is paused.</DSW_Headline>,
    empty:   <DSW_Headline sub="14-day challenge · starts today">Join the 10k-a-day challenge.</DSW_Headline>,
  }[state];
  return (
    <DSWidgetShell
      glyph="goal" name="Goal" meta="10k steps" state={state}
      headline={headline}
      strip={<DSW_ProgressRing value={cfg.value} daysLeft={cfg.days} caption={caption} muted={state==='paused'} />}
      acted={state==='acted'} actedLabel="Progress logged today"
      ctaLabel={state==='paused' ? 'Resume' : state==='empty' ? 'Join challenge' : 'Log progress'}
      onCta={onCta} onOpen={onOpen} onTap={onTap}
    />
  );
}

// Cycle — new face. Phase ribbon + day-of-cycle, with the same four states.
function DSCycleWidget({ state = 'default', onTap = DSW_noop, onOpen = DSW_noop, onCta = DSW_noop }) {
  const cfg = {
    default: { day: 14, phase: 'Ovulatory' },
    acted:   { day: 14, phase: 'Ovulatory' },
    paused:  { day: 14, phase: 'Ovulatory' },
    empty:   { day: 1,  phase: 'Menstrual' },
  }[state];
  const headline = {
    default: <DSW_Headline sub="Fertile window · symptoms light">Day 14 — ovulatory phase.</DSW_Headline>,
    acted:   <DSW_Headline sub="Mood · calm · flow · none · 09:12">Today's check-in is in.</DSW_Headline>,
    paused:  <DSW_Headline sub="Resume to keep predictions accurate">Cycle tracking is paused.</DSW_Headline>,
    empty:   <DSW_Headline sub="Log your period to start predictions">Set your cycle start day.</DSW_Headline>,
  }[state];
  return (
    <DSWidgetShell
      glyph="cycle" name="Cycle" meta="28-day average" state={state}
      sourceBadge={<DSW_SourceBadge variant="synced" />}
      headline={headline}
      strip={<DSW_PhaseRibbon day={cfg.day} total={28} phase={cfg.phase} muted={state==='paused'} />}
      acted={state==='acted'} actedLabel="Checked in · 09:12"
      ctaLabel={state==='paused' ? 'Resume' : state==='empty' ? 'Set start day' : 'Check in'}
      onCta={onCta} onOpen={onOpen} onTap={onTap}
    />
  );
}

// ── Social — lighter card for browse/social systems (no daily log CTA) ─
// kind: 'standings' (Shared challenges) · 'activity' (Group experiments)
//       'read' (Library) · 'community' (Community)
// The single-entry `state` maps to social presence: default→light news,
// acted→quiet, empty→none. Callers may also pass `kind` directly.
function DSSocialWidget({ kind, state = 'default', onOpen = DSW_noop, onTap = DSW_noop }) {
  const resolvedKind = kind || ({
    default: 'standings', acted: 'activity', paused: 'read', empty: 'community',
  }[state]) || 'standings';
  const map = {
    standings: {
      glyph: 'goal', name: 'Shared challenge', meta: 'Spring steps',
      line: <span>You're <b style={{ fontWeight: 600, color: M.ink }}>3rd of 12</b> — 400 steps behind 2nd.</span>,
      open: 'Standings',
    },
    activity: {
      glyph: 'experiment', name: 'Group experiment', meta: 'Caffeine cut-off',
      line: <span><b style={{ fontWeight: 600, color: M.ink }}>4 of 6</b> members logged today. Your turn's open.</span>,
      open: 'View group',
    },
    read: {
      glyph: 'library', name: 'Library',
      line: <span>Suggested · <b style={{ fontWeight: 600, color: M.ink }}>"Perimenopause &amp; sleep"</b> · 6-min read.</span>,
      open: 'Read',
    },
    community: {
      glyph: 'social', name: 'Community', meta: 'Cycle tracking',
      line: <span><b style={{ fontWeight: 600, color: M.ink }}>12 new</b> posts in threads you follow.</span>,
      open: 'Open',
    },
  };
  const c = map[resolvedKind];
  return (
    <DSCard padding={0} onClick={onTap}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 11, padding: '12px 8px 12px 14px', minHeight: 56 }}>
        <div style={{
          flexShrink: 0, width: 30, height: 30, borderRadius: 9,
          background: M.surfaceAlt, color: M.inkMuted,
          display: 'flex', alignItems: 'center', justifyContent: 'center',
        }}>{DSFaceGlyph[c.glyph]?.({ size: 16 })}</div>
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{
            font: `500 10px/1.3 ${M.sans}`, color: M.inkMuted,
            letterSpacing: '0.07em', textTransform: 'uppercase',
          }}>{c.name}{c.meta && <span style={{ color: M.inkFaint, textTransform: 'none', letterSpacing: 0 }}> · {c.meta}</span>}</div>
          <div style={{ font: `400 13px/1.4 ${M.sans}`, color: M.inkSoft, marginTop: 2, textWrap: 'pretty' }}>{c.line}</div>
        </div>
        <button onClick={(e) => { e.stopPropagation(); onOpen(); }} aria-label={c.open}
          style={{
            flexShrink: 0, minHeight: 44, padding: '0 10px',
            display: 'inline-flex', alignItems: 'center', gap: 3,
            background: 'transparent', border: 'none', cursor: 'pointer',
            font: `500 12.5px/1 ${M.sans}`, color: M.axis,
          }}>{c.open}<DSW_Icon.ChevronR size={14} /></button>
      </div>
    </DSCard>
  );
}

// ──────────────────────────────────────────────────────────
// DSBlockTile — generic pre-design fallback for systems without a
// bespoke face. Name + last-update relative time + optional sparkline
// + optional source badge. Calm, compact, tappable.
// ──────────────────────────────────────────────────────────
function DSBlockTile({
  name,
  meta,
  updatedAt,            // Date | string | relative string — last update
  sparkline,            // [0..1, ...] amplitude points; optional
  sourceVariant,        // 'manual'|'journal'|'predefined'|'synced'|... optional
  glyph = 'goal',       // any DSFaceGlyph key
  onTap = DSW_noop,
  onOpen = DSW_noop,
}) {
  const rel = updatedAt == null
    ? null
    : (typeof updatedAt === 'string'
        ? updatedAt
        : (window.relativeTime ? window.relativeTime(updatedAt) : String(updatedAt)));
  const Glyph = DSFaceGlyph[glyph] || DSFaceGlyph.goal;
  return (
    <DSCard padding={0} onClick={onTap}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 11, padding: '12px 8px 12px 14px', minHeight: 60 }}>
        <div style={{
          flexShrink: 0, width: 32, height: 32, borderRadius: 9,
          background: M.surfaceAlt, color: M.inkMuted,
          display: 'flex', alignItems: 'center', justifyContent: 'center',
        }}>{Glyph({ size: 17 })}</div>

        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 7 }}>
            <span style={{
              font: `500 14px/1.3 ${M.sans}`, color: M.ink,
              overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap',
            }}>{name}</span>
            {sourceVariant && <DSW_SourceBadge variant={sourceVariant} />}
          </div>
          <div style={{ font: `400 11.5px/1.4 ${M.sans}`, color: M.inkMuted, marginTop: 2 }}>
            {meta && <span>{meta}</span>}
            {meta && rel && <span style={{ color: M.inkFaint }}> · </span>}
            {rel && <span>Updated {rel}</span>}
            {!meta && !rel && <span style={{ color: M.inkFaint }}>No bespoke view yet</span>}
          </div>
        </div>

        {sparkline && sparkline.length > 1 && window.DSSparklineThumb && (
          <div style={{ flexShrink: 0 }}>
            {window.DSSparklineThumb({ points: sparkline, color: M.axis, size: 'default' })}
          </div>
        )}

        <button onClick={(e) => { e.stopPropagation(); onOpen(); }} aria-label={`Open ${name}`}
          style={{
            flexShrink: 0, minHeight: 44, padding: '0 8px',
            display: 'inline-flex', alignItems: 'center',
            background: 'transparent', border: 'none', cursor: 'pointer', color: M.axis,
          }}><DSW_Icon.ChevronR size={16} /></button>
      </div>
    </DSCard>
  );
}

// ──────────────────────────────────────────────────────────
// DSSystemWidget — single entry point. Dispatches on `face`.
//   face  ∈ journal | food | experiment | routine | goal | cycle | social
//   state ∈ default | acted | paused | empty (faces ignore N/A states)
// Unknown faces fall back to DSBlockTile.
// ──────────────────────────────────────────────────────────
const DSW_FACES = {
  journal:    DSJournalWidget,
  food:       DSFoodWidget,
  experiment: DSExperimentWidget,
  routine:    DSRoutineWidget,
  goal:       DSGoalWidget,
  cycle:      DSCycleWidget,
  social:     DSSocialWidget,
};

function DSSystemWidget({ face = 'journal', state = 'default', ...props }) {
  const Face = DSW_FACES[face];
  if (!Face) {
    return <DSBlockTile name={props.name || face} {...props} />;
  }
  return <Face state={state} {...props} />;
}

window.DSSystemWidget_loaded = true;
Object.assign(window, {
  DSSystemWidget,
  DSWidgetShell,
  DSJournalWidget, DSFoodWidget, DSExperimentWidget,
  DSRoutineWidget, DSGoalWidget, DSCycleWidget, DSSocialWidget,
  DSBlockTile,
  DSFaceGlyph,
});
