// DSValueChart.jsx — Meridian parameter-history chart
//
// A line chart with circle data-points, axis labels, a range picker, and
// an optional normal-range overlay band. Used in the Parameter Detail
// "History" tab (DSPageTabs · history) and as the chart inside
// DSValueChart's full-screen drill-in (triggered by long-press on a
// DSSparklineView).
//
// Internal compositions:
//   DSValueChartPoint — a single tappable data dot (hover/focus halo +
//                       optional callout with value & date).
//
// Public props
//   data            [{ date: Date|string|number, value: number, note?, source? }]
//   range           'day' | 'week' | 'month' | 'quarter' | 'year'
//                   defaults to 'week' (uncontrolled) or honors prop (controlled)
//   onRangeChange   fn(range)
//   normalRange     { low, high } — draws an axisSoft band behind the line
//   unit            string suffix shown after each Y-axis label, e.g. 'h', 'mg'
//   valueFormatter  fn(value) -> string — used for axis labels + point callouts
//   height          number, default 240
//   onPointTap      fn(point) — opens the entry (defaults to no-op)
//   color           hex string — line + dot color, defaults to axis
//   emptyHint       string shown in the empty state
//
// The chart is self-contained: it computes its own Y domain from the
// filtered data + the normal range (if any), with 10% padding above & below.

const DSValueChart_RANGES = [
  { id: 'day',     label: '24h',  ms: 24 * 60 * 60 * 1000,        xTicks: 4, xFmt: (d) => `${d.getHours().toString().padStart(2,'0')}:00` },
  { id: 'week',    label: '7d',   ms: 7 * 24 * 60 * 60 * 1000,    xTicks: 7, xFmt: (d) => ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'][d.getDay()] },
  { id: 'month',   label: '30d',  ms: 30 * 24 * 60 * 60 * 1000,   xTicks: 5, xFmt: (d) => `${d.getDate()}` },
  { id: 'quarter', label: '90d',  ms: 90 * 24 * 60 * 60 * 1000,   xTicks: 6, xFmt: (d) => ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'][d.getMonth()] },
  { id: 'year',    label: '1y',   ms: 365 * 24 * 60 * 60 * 1000,  xTicks: 12, xFmt: (d) => ['J','F','M','A','M','J','J','A','S','O','N','D'][d.getMonth()] },
];

// Best-effort date coercion
const toDate = (d) => (d instanceof Date ? d : new Date(d));

function DSValueChart({
  data = [],
  range: rangeProp,
  onRangeChange,
  normalRange,
  unit = '',
  valueFormatter,
  height = 240,
  onPointTap,
  color = M.axis,
  now = new Date(),
  emptyHint = 'No values logged in this range yet.',
}) {
  // Honor `range` as an initial value; only stay fully controlled if a
  // consumer is also listening via onRangeChange. This keeps the picker
  // interactive even when the caller just passes a starting range.
  const [rangeState, setRangeState] = React.useState(rangeProp || 'week');
  const isControlled = !!onRangeChange;
  const range = isControlled ? (rangeProp || rangeState) : rangeState;
  const setRange = (r) => {
    if (onRangeChange) onRangeChange(r);
    setRangeState(r);
  };
  const rangeDef = DSValueChart_RANGES.find(r => r.id === range) || DSValueChart_RANGES[1];

  // ── Window the data to the range ───────────────────────────────
  const tNow = now.getTime();
  const tStart = tNow - rangeDef.ms;
  const points = React.useMemo(() => (
    data
      .map(d => ({ ...d, _t: toDate(d.date).getTime() }))
      .filter(d => d._t >= tStart && d._t <= tNow)
      .sort((a, b) => a._t - b._t)
  ), [data, tStart, tNow]);

  const fmt = valueFormatter || ((v) => `${v}${unit ? unit : ''}`);

  // ── Y domain ───────────────────────────────────────────────────
  const yValues = points.map(p => p.value);
  let yMin, yMax;
  if (yValues.length || normalRange) {
    const vals = [...yValues];
    if (normalRange) { vals.push(normalRange.low, normalRange.high); }
    const rawMin = Math.min(...vals);
    const rawMax = Math.max(...vals);
    const pad = Math.max((rawMax - rawMin) * 0.15, Math.abs(rawMax || 1) * 0.05);
    yMin = rawMin - pad;
    yMax = rawMax + pad;
  } else {
    yMin = 0; yMax = 1;
  }
  // Avoid degenerate domain
  if (yMax - yMin < 0.001) { yMax = yMin + 1; }

  // ── Layout (responsive width, fixed paddings) ──────────────────
  const pad = { top: 14, right: 16, bottom: 28, left: 44 };
  const VBW = 600; // viewBox width — SVG is preserveAspectRatio='none' on X
  const VBH = height;
  const innerW = VBW - pad.left - pad.right;
  const innerH = VBH - pad.top - pad.bottom;

  const xScale = (t) => pad.left + ((t - tStart) / (tNow - tStart)) * innerW;
  const yScale = (v) => pad.top + (1 - (v - yMin) / (yMax - yMin)) * innerH;

  // Y ticks — 4 evenly-spaced
  const Y_TICKS = 4;
  const yTickValues = Array.from({ length: Y_TICKS }, (_, i) => yMin + (i / (Y_TICKS - 1)) * (yMax - yMin));

  // X ticks — `xTicks` evenly spaced timestamps
  const xTickTimes = Array.from({ length: rangeDef.xTicks }, (_, i) => (
    tStart + (i / (rangeDef.xTicks - 1)) * (tNow - tStart)
  ));

  // ── Hover/tap state ────────────────────────────────────────────
  const [hoverIdx, setHoverIdx] = React.useState(null);

  // ── Polyline path ──────────────────────────────────────────────
  const linePath = points.map((p, i) => `${i === 0 ? 'M' : 'L'} ${xScale(p._t).toFixed(2)} ${yScale(p.value).toFixed(2)}`).join(' ');

  return (
    <div style={{ background: M.surface, border: `1px solid ${M.rule}`, borderRadius: 16, padding: '14px 14px 12px' }}>
      {/* Range picker */}
      <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 10 }}>
        <div style={{ font: `500 11px/1 ${M.sans}`, color: M.inkMuted, letterSpacing: '0.06em', textTransform: 'uppercase' }}>
          History
        </div>
        <div style={{
          display: 'inline-flex', gap: 2,
          padding: 3, background: M.canvas,
          border: `1px solid ${M.rule}`, borderRadius: 8,
        }}>
          {DSValueChart_RANGES.map(r => {
            const active = r.id === range;
            return (
              <button key={r.id} onClick={() => setRange(r.id)} style={{
                font: `500 11px/1 ${M.sans}`,
                padding: '5px 9px',
                background: active ? M.surface : 'transparent',
                color: active ? M.ink : M.inkMuted,
                border: 'none',
                borderRadius: 6,
                cursor: 'pointer',
                boxShadow: active ? '0 1px 2px rgba(14,18,32,0.06)' : 'none',
                transition: 'all 120ms cubic-bezier(.2,.8,.2,1)',
              }}>{r.label}</button>
            );
          })}
        </div>
      </div>

      {/* Chart */}
      <div style={{ position: 'relative', width: '100%', height }}>
        <svg viewBox={`0 0 ${VBW} ${VBH}`} preserveAspectRatio="none"
             width="100%" height={height}
             style={{ display: 'block', overflow: 'visible' }}
             onMouseLeave={() => setHoverIdx(null)}>
          {/* Y gridlines */}
          {yTickValues.map((v, i) => (
            <line key={i}
              x1={pad.left} y1={yScale(v)}
              x2={VBW - pad.right} y2={yScale(v)}
              stroke={M.ruleSoft} strokeWidth="1" />
          ))}

          {/* Normal-range band */}
          {normalRange && (
            <rect
              x={pad.left} width={innerW}
              y={yScale(normalRange.high)}
              height={Math.max(0, yScale(normalRange.low) - yScale(normalRange.high))}
              fill={M.axisSoft} opacity="0.65" />
          )}

          {/* Normal-range hairlines */}
          {normalRange && (
            <>
              <line x1={pad.left} x2={VBW - pad.right}
                    y1={yScale(normalRange.high)} y2={yScale(normalRange.high)}
                    stroke={M.axis} strokeOpacity="0.35" strokeWidth="1" strokeDasharray="2 3" />
              <line x1={pad.left} x2={VBW - pad.right}
                    y1={yScale(normalRange.low)} y2={yScale(normalRange.low)}
                    stroke={M.axis} strokeOpacity="0.35" strokeWidth="1" strokeDasharray="2 3" />
            </>
          )}

          {/* Y-axis labels — dedupe adjacent identical labels so tight
              domains don't render "6, 6, 6, 6" */}
          {(() => {
            const labels = yTickValues.map(fmt);
            return yTickValues.map((v, i) => {
              if (i > 0 && labels[i] === labels[i - 1]) return null;
              return (
                <text key={i}
                  x={pad.left - 8} y={yScale(v)}
                  dy="0.32em" textAnchor="end"
                  style={{ font: `400 11px/1 ${M.mono}`, fill: M.inkMuted, fontVariantNumeric: 'tabular-nums' }}
                  vectorEffect="non-scaling-stroke">
                  {labels[i]}
                </text>
              );
            });
          })()}

          {/* X-axis labels */}
          {xTickTimes.map((t, i) => {
            const isFirst = i === 0;
            const isLast = i === xTickTimes.length - 1;
            const anchor = isFirst ? 'start' : (isLast ? 'end' : 'middle');
            return (
              <text key={i}
                x={xScale(t)} y={VBH - pad.bottom + 16}
                textAnchor={anchor}
                style={{ font: `400 11px/1 ${M.mono}`, fill: M.inkMuted }}>
                {rangeDef.xFmt(new Date(t))}
              </text>
            );
          })}

          {/* Line */}
          {points.length > 1 && (
            <path d={linePath} fill="none"
              stroke={color} strokeWidth="1.5"
              strokeLinecap="round" strokeLinejoin="round" />
          )}

          {/* Points */}
          {points.map((p, i) => (
            <DSValueChartPoint key={i}
              cx={xScale(p._t)} cy={yScale(p.value)}
              color={color}
              active={hoverIdx === i}
              onHover={() => setHoverIdx(i)}
              onTap={() => onPointTap?.(p)} />
          ))}

          {/* Hover callout */}
          {hoverIdx != null && points[hoverIdx] && (() => {
            const p = points[hoverIdx];
            const x = xScale(p._t);
            const y = yScale(p.value);
            const label = `${fmt(p.value)} · ${new Date(p._t).toLocaleDateString(undefined, { month: 'short', day: 'numeric' })}`;
            const w = Math.max(110, label.length * 6.5);
            const flip = x > VBW - 130; // flip the callout left of the point near right edge
            const cx = flip ? x - w - 10 : x + 10;
            const cy = Math.max(pad.top, y - 28);
            return (
              <g pointerEvents="none">
                <rect x={cx} y={cy} width={w} height={26} rx="6"
                  fill={M.ink} opacity="0.94" />
                <text x={cx + w / 2} y={cy + 13} dy="0.32em" textAnchor="middle"
                  style={{ font: `500 11px/1 ${M.mono}`, fill: '#fff', fontVariantNumeric: 'tabular-nums' }}>
                  {label}
                </text>
              </g>
            );
          })()}
        </svg>

        {/* Empty state — overlaid so range picker stays interactive */}
        {points.length === 0 && (
          <div style={{
            position: 'absolute', inset: pad.top + 'px ' + pad.right + 'px ' + pad.bottom + 'px ' + pad.left + 'px',
            display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center',
            gap: 6, pointerEvents: 'none', textAlign: 'center',
          }}>
            <div style={{
              width: 28, height: 28, borderRadius: 999,
              background: M.surfaceAlt, border: `1px dashed ${M.rule}`,
              display: 'flex', alignItems: 'center', justifyContent: 'center',
              color: M.inkMuted,
            }}>
              <svg width="14" height="14" viewBox="0 0 24 24" fill="none">
                <path d="M12 5v14M5 12h14" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
              </svg>
            </div>
            <div style={{ font: `400 13px/1.4 ${M.sans}`, color: M.inkMuted, maxWidth: 260 }}>
              {emptyHint}
            </div>
          </div>
        )}
      </div>
    </div>
  );
}

// ── DSValueChartPoint (internal) ─────────────────────────────────
function DSValueChartPoint({ cx, cy, color, active, onHover, onTap }) {
  return (
    <g style={{ cursor: onTap ? 'pointer' : 'default' }}
       onMouseEnter={onHover} onClick={onTap}>
      {/* Wide invisible hit target */}
      <circle cx={cx} cy={cy} r="14" fill="transparent" />
      {/* Active halo */}
      {active && <circle cx={cx} cy={cy} r="7" fill={color} opacity="0.18" />}
      {/* Dot */}
      <circle cx={cx} cy={cy} r={active ? 3.5 : 2.5}
        fill={color}
        style={{ transition: 'r 120ms cubic-bezier(.2,.8,.2,1)' }} />
    </g>
  );
}

window.DSValueChart = DSValueChart;
window.DSValueChartPoint = DSValueChartPoint;
window.DSValueChart_RANGES = DSValueChart_RANGES;
