// DSCard.jsx — Meridian card family
//
// One base container + three list/tile patterns built on top of it.
//
//   DSCard          — the base. variants default / hover / accent.
//   DSValueRow      — single-line list row: name + value + delta + time + chevron.
//                     Used in "Recent entries" lists, parameter history.
//   DSParameterTile — multi-line tile: eyebrow + value + delta + thumb sparkline
//                     + optional source badge slot. Used in Today's 2-up grid.
//   DSStatCard      — large summary tile: big number + delta + optional sparkline.
//                     Used at the top of Parameter Detail.
//
// All three respect the `onClick` handler, gain shadow-lift on hover when
// interactive, and use Meridian token spacing (sp-3/4) + radius-lg.

// ── Internal helpers ─────────────────────────────────────────────
const DELTA_COLORS = {
  positive: M.success,
  negative: M.crisis,
  neutral:  M.inkMuted,
};

// "negative" doesn't always mean bad — for sleep, −0.4h is bad;
// for cortisol, −0.4 may be good. Callers pass `direction` explicitly:
//   'up'      → green
//   'down'    → red
//   'neutral' → muted (no judgement)
function deltaColor(direction) {
  if (direction === 'up')   return DELTA_COLORS.positive;
  if (direction === 'down') return DELTA_COLORS.negative;
  return DELTA_COLORS.neutral;
}

// Relative time short-form used in DSValueRow
function relativeTime(date, now = new Date()) {
  const d = date instanceof Date ? date : new Date(date);
  const diffMs = now.getTime() - d.getTime();
  const diffMin = Math.round(diffMs / 60000);
  if (diffMin < 1)  return 'just now';
  if (diffMin < 60) return `${diffMin}m ago`;
  const diffH = Math.round(diffMin / 60);
  if (diffH < 24)   return `${diffH}h ago`;
  const diffD = Math.round(diffH / 24);
  if (diffD < 7)    return `${diffD}d ago`;
  return d.toLocaleDateString(undefined, { month: 'short', day: 'numeric' });
}

// Thumb-size sparkline used in DSParameterTile and DSStatCard.
//
// Props:
//   points         number[]   normalized 0..1, chronological
//   color          hex/token  default M.axis
//   width, height  number     fall back to the 'default' size if omitted
//   size           enum       'compact' (40×16) | 'default' (60×22).
//                              When provided, overrides width/height.
//                              When absent and width/height are given,
//                              uses those — backwards compat path.
//   strokeWidth    number     default 1.25
//   fillBelow      bool       default false — faint gradient fill under the line
//   showLatestDot  bool       default true  — small dot at the rightmost point
//
// Edge cases:
//   - 0 points        → faint horizontal rule at color.opacity(0.2)
//   - 1 point         → single dot at the right edge
//   - constant points → flat line at vertical centre
//
// Always aria-hidden / decorative — the parent row owns announcement.
const DSSparklineThumb_SIZES = {
  compact: { width: 40, height: 16 },
  default: { width: 60, height: 22 },
};

function DSSparklineThumb({
  points = [],
  color = M.axis,
  width,
  height,
  size,
  strokeWidth = 1.25,
  fillBelow = false,
  showLatestDot = true,
}) {
  // Resolve dimensions: `size` wins; else fall back to width/height; else default.
  let w, h;
  if (size && DSSparklineThumb_SIZES[size]) {
    ({ width: w, height: h } = DSSparklineThumb_SIZES[size]);
  } else if (width != null || height != null) {
    w = width  != null ? width  : DSSparklineThumb_SIZES.default.width;
    h = height != null ? height : DSSparklineThumb_SIZES.default.height;
  } else {
    ({ width: w, height: h } = DSSparklineThumb_SIZES.default);
  }

  // Unique gradient id — stable across renders so the fill resolves.
  const gradId = React.useId().replace(/[^a-zA-Z0-9_-]/g, '_');

  const svgProps = {
    width: w, height: h, viewBox: `0 0 ${w} ${h}`,
    style: { display: 'block', flexShrink: 0 },
    'aria-hidden': 'true',
  };

  // ── 0 points → faint horizontal rule ──
  const n = points.length;
  if (n === 0) {
    const yMid = h / 2;
    return (
      <svg {...svgProps}>
        <line x1={0} y1={yMid} x2={w} y2={yMid}
              stroke={color} strokeOpacity={0.2}
              strokeWidth={strokeWidth} strokeLinecap="round" />
      </svg>
    );
  }

  // Inner inset so stroke / dot don't clip at the edges.
  const dotR  = 2;
  const padX  = Math.max(strokeWidth / 2, showLatestDot ? dotR + 0.5 : 0);
  const padY  = Math.max(strokeWidth / 2, showLatestDot ? dotR + 0.5 : 0);
  const innerW = Math.max(0, w - padX * 2);
  const innerH = Math.max(0, h - padY * 2);

  // ── 1 point → dot at right edge ──
  if (n === 1) {
    const cx = w - padX;
    const cy = padY + (1 - points[0]) * innerH;
    return (
      <svg {...svgProps}>
        <circle cx={cx} cy={cy} r={dotR} fill={color} />
      </svg>
    );
  }

  // Detect constant series → flat line at vertical centre.
  const first = points[0];
  const allEqual = points.every((v) => v === first);

  const xs = points.map((_, i) => padX + (i / (n - 1)) * innerW);
  const ys = allEqual
    ? points.map(() => h / 2)
    : points.map((v) => padY + (1 - v) * innerH);

  const linePath = xs
    .map((x, i) => `${i === 0 ? 'M' : 'L'} ${x.toFixed(1)} ${ys[i].toFixed(1)}`)
    .join(' ');

  // Fill path closes the line down to the bottom edge.
  let fillPath = null;
  if (fillBelow) {
    const xL = xs[0].toFixed(1);
    const xR = xs[n - 1].toFixed(1);
    fillPath = `${linePath} L ${xR} ${h.toFixed(1)} L ${xL} ${h.toFixed(1)} Z`;
  }

  return (
    <svg {...svgProps}>
      {fillBelow && (
        <defs>
          <linearGradient id={gradId} x1="0" y1="0" x2="0" y2="1">
            <stop offset="0%"   stopColor={color} stopOpacity={0.18} />
            <stop offset="100%" stopColor={color} stopOpacity={0}    />
          </linearGradient>
        </defs>
      )}
      {fillPath && <path d={fillPath} fill={`url(#${gradId})`} stroke="none" />}
      <path d={linePath} fill="none" stroke={color} strokeWidth={strokeWidth}
            strokeLinecap="round" strokeLinejoin="round" />
      {showLatestDot && (
        <circle cx={xs[n - 1]} cy={ys[n - 1]} r={dotR} fill={color} />
      )}
    </svg>
  );
}

// ── DSCard (base) ────────────────────────────────────────────────
function DSCard({
  variant = 'default',
  padding = 16,
  children,
  onClick,
  style,
}) {
  const [hover, setHover] = React.useState(false);
  const isInteractive = !!onClick;
  const isAccent = variant === 'accent';
  const isHoverable = variant === 'hover' || (isInteractive && variant !== 'accent');

  return (
    <div
      onClick={onClick}
      onMouseEnter={isHoverable ? () => setHover(true) : undefined}
      onMouseLeave={isHoverable ? () => setHover(false) : undefined}
      role={isInteractive ? 'button' : undefined}
      tabIndex={isInteractive ? 0 : undefined}
      onKeyDown={isInteractive ? (e) => {
        if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); onClick(); }
      } : undefined}
      style={{
        background: isAccent ? M.axisSoft : M.surface,
        border: isAccent ? '1px solid transparent' : `1px solid ${M.rule}`,
        borderRadius: 16,
        padding,
        cursor: isInteractive ? 'pointer' : 'default',
        boxShadow: hover && isHoverable
          ? '0 4px 12px rgba(14,18,32,0.08), 0 1px 2px rgba(14,18,32,0.04)'
          : 'none',
        transition: 'box-shadow 120ms cubic-bezier(.2,.8,.2,1)',
        outline: 'none',
        ...style,
      }}
    >{children}</div>
  );
}

// ── DSValueRow ───────────────────────────────────────────────────
// Single line: name · value · delta · relativeTime · chevron.
// Designed for tightly-packed history lists. Wraps in DSCard with
// padding 0 if you want a list-of-rows treatment.
function DSValueRow({
  label,
  value,
  delta,
  direction = 'neutral',
  time,
  trailing,         // optional override of the trailing chevron
  unit,             // appended after value with a thin space
  asCard = false,   // wrap in DSCard, or render bare (for list-in-card use)
  onClick,
  style,
}) {
  const dCol = deltaColor(direction);
  const content = (
    <div
      onClick={onClick}
      onKeyDown={onClick ? (e) => {
        if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); onClick(); }
      } : undefined}
      role={onClick ? 'button' : undefined}
      tabIndex={onClick ? 0 : undefined}
      style={{
        display: 'flex',
        alignItems: 'center',
        gap: 12,
        minHeight: 44, // iOS hit target floor
        padding: asCard ? undefined : '10px 12px',
        cursor: onClick ? 'pointer' : 'default',
        outline: 'none',
        ...style,
      }}
    >
      <div style={{
        flex: 1, minWidth: 0,
        font: `400 14px/1.3 ${M.sans}`, color: M.ink,
        overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap',
      }}>{label}</div>

      <div style={{
        font: `500 14px/1 ${M.mono}`, color: M.ink,
        fontVariantNumeric: 'tabular-nums',
        whiteSpace: 'nowrap',
      }}>
        {value}{unit && <span style={{ marginLeft: 2, opacity: 0.7 }}>{unit}</span>}
      </div>

      {delta != null && (
        <div style={{
          font: `500 12px/1 ${M.mono}`, color: dCol,
          fontVariantNumeric: 'tabular-nums',
          minWidth: 38, textAlign: 'right',
          whiteSpace: 'nowrap',
        }}>{delta}</div>
      )}

      {time && (
        <div style={{
          font: `400 11px/1 ${M.sans}`, color: M.inkMuted,
          minWidth: 56, textAlign: 'right',
          whiteSpace: 'nowrap',
        }}>{typeof time === 'string' ? time : relativeTime(time)}</div>
      )}

      {trailing !== undefined ? trailing : (
        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" aria-hidden="true">
          <path d="M9 6l6 6-6 6" stroke={M.inkFaint} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
        </svg>
      )}
    </div>
  );

  if (asCard) {
    return <DSCard padding="10px 12px" onClick={onClick} style={style}>{content}</DSCard>;
  }
  return content;
}

// ── DSParameterTile ──────────────────────────────────────────────
// Multi-line tile for Today's 2-up grid: eyebrow label, big mono value,
// delta, thumbnail sparkline, optional source-badge slot.
function DSParameterTile({
  label,
  value,
  delta,
  direction = 'neutral',
  unit,
  sparkline,       // [0..1, 0..1, ...] amplitude points; thumbnail
  sourceBadge,     // any React node — usually a <DSSourceBadge />
  footnote,        // optional small line below sparkline
  onClick,
  style,
  color = M.axis,
}) {
  const dCol = deltaColor(direction);
  return (
    <DSCard padding={14} onClick={onClick} style={style}>
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: 8, marginBottom: 8 }}>
        <div style={{
          font: `500 11px/1 ${M.sans}`, color: M.inkMuted,
          letterSpacing: '0.06em', textTransform: 'uppercase',
        }}>{label}</div>
        {sourceBadge}
      </div>

      <div style={{ display: 'flex', alignItems: 'baseline', gap: 6 }}>
        <div style={{
          font: `500 22px/1 ${M.sans}`, color: M.ink,
          letterSpacing: '-0.01em',
          fontVariantNumeric: 'tabular-nums',
        }}>{value}{unit && <span style={{ marginLeft: 2, fontSize: 14, opacity: 0.7 }}>{unit}</span>}</div>
        {delta != null && (
          <div style={{
            font: `500 11px/1 ${M.mono}`, color: dCol,
            fontVariantNumeric: 'tabular-nums',
          }}>{delta}</div>
        )}
      </div>

      {sparkline && sparkline.length > 1 && (
        <div style={{ marginTop: 10 }}>
          <DSSparklineThumb points={sparkline} color={color} width={180} height={28} />
        </div>
      )}

      {footnote && (
        <div style={{
          font: `400 11px/1.4 ${M.sans}`, color: M.inkMuted, marginTop: 8,
        }}>{footnote}</div>
      )}
    </DSCard>
  );
}

// ── DSStatCard ───────────────────────────────────────────────────
// Big summary card: large value + delta + optional sparkline + helper
// text. Used at the top of Parameter Detail, on weekly recaps, etc.
function DSStatCard({
  label,
  value,
  delta,
  direction = 'neutral',
  unit,
  helper,          // longer descriptive line below the value
  sparkline,       // [0..1, ...] amplitude points
  range,           // small caption on the top-right (e.g. "7d")
  onClick,
  style,
  color = M.axis,
}) {
  const dCol = deltaColor(direction);
  return (
    <DSCard padding={18} onClick={onClick} style={style}>
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline' }}>
        <div style={{
          font: `500 11px/1 ${M.sans}`, color: M.inkMuted,
          letterSpacing: '0.06em', textTransform: 'uppercase',
        }}>{label}</div>
        {range && (
          <div style={{
            font: `400 11px/1 ${M.mono}`, color: M.inkFaint,
            fontVariantNumeric: 'tabular-nums',
          }}>{range}</div>
        )}
      </div>

      <div style={{ display: 'flex', alignItems: 'baseline', gap: 10, marginTop: 10 }}>
        <div style={{
          font: `500 38px/1 ${M.sans}`, color: M.ink,
          letterSpacing: '-0.02em',
          fontVariantNumeric: 'tabular-nums',
        }}>
          {value}
          {unit && <span style={{ fontSize: 22, opacity: 0.65, marginLeft: 2 }}>{unit}</span>}
        </div>
        {delta != null && (
          <div style={{
            font: `500 13px/1 ${M.mono}`, color: dCol,
            fontVariantNumeric: 'tabular-nums',
          }}>{delta}</div>
        )}
      </div>

      {sparkline && sparkline.length > 1 && (
        <div style={{ marginTop: 12 }}>
          <DSSparklineThumb points={sparkline} color={color} width={520} height={42} fillBelow />
        </div>
      )}

      {helper && (
        <div style={{
          font: `400 13px/1.5 ${M.sans}`, color: M.inkSoft, marginTop: 10,
        }}>{helper}</div>
      )}
    </DSCard>
  );
}

Object.assign(window, {
  DSCard, DSValueRow, DSParameterTile, DSStatCard, DSSparklineThumb,
  // helpers — exposed for parity with iOS/Android
  relativeTime, deltaColor,
});
