/* viz.jsx — fluid vessel visualization + utilities */

const ZONES = {
  gaining: { label: 'gaining', hex: 'var(--rust)' },
  keeping: { label: 'keeping', hex: 'var(--amber)' },
  losing:  { label: 'losing',  hex: 'var(--moss)' },
};

// ── Trajectory math ─────────────────────────────────────────────
// Keeping is a band around maintenance, not exact zero.
// Product rule: threshold = 10% of the user's TDEE.
const THRESH = BALANCE_THRESHOLD;

function balanceThreshold(tdee = TDEE) {
  return Math.round(tdee * 0.10);
}

function classify(avgBalance, tdee = TDEE) {
  const threshold = balanceThreshold(tdee);
  if (avgBalance >  threshold) return 'gaining';
  if (avgBalance < -threshold) return 'losing';
  return 'keeping';
}

function verdict(days, tdee = TDEE) {
  const avg = days.reduce((s, d) => s + d.balance, 0) / days.length;
  const threshold = balanceThreshold(tdee);
  return {
    zone: classify(avg, tdee),
    avg,
    threshold,
    confidence: days.some((d) => d.intakeSource !== 'logged') ? 'rough' : days.length >= 7 ? 'higher' : 'moderate',
  };
}

// Map a balance value (kcal/day) to a 0..1 fluid level.
// Scale around the user's maintenance threshold so different TDEEs stay readable.
function balanceToLevel(b, tdee = TDEE) {
  const threshold = balanceThreshold(tdee);
  const maxVisualBalance = Math.max(tdee * 0.5, threshold * 2.5);
  const t = (b + maxVisualBalance) / (maxVisualBalance * 2);
  return Math.max(0.04, Math.min(0.96, t));
}

// ── Fluid orb ────────────────────────────────────────────────────

function FluidOrb({
  days,
  size = 360,
  animate = true,
  showMarkers = true,
  markerStyle = 'number',  // 'number' | 'dot' | 'mini'
  connector = 'smooth',     // 'smooth' | 'dashed' | 'none'
  tdee = TDEE,
}) {
  const { zone, avg, threshold } = verdict(days, tdee);
  const color = ZONES[zone].hex;

  // viewBox 400x400
  const V = 400, cx = 200, cy = 200, R = 192;

  // Average fluid level (the "verdict line")
  const level = balanceToLevel(avg, tdee);
  const surfaceY = cy + R - level * (R * 2);

  // Zone boundary y-values (relative to vertical axis)
  const lvlGainKeep = balanceToLevel(+threshold, tdee); // upper boundary
  const lvlKeepLose = balanceToLevel(-threshold, tdee); // lower boundary
  const yGainKeep = cy + R - lvlGainKeep * (R * 2);
  const yKeepLose = cy + R - lvlKeepLose * (R * 2);

  // Wave animation phase
  const [phase, setPhase] = React.useState(0);
  React.useEffect(() => {
    if (!animate) return;
    let raf, t0;
    const tick = (t) => {
      if (t0 == null) t0 = t;
      setPhase((t - t0) / 1000);
      raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(raf);
  }, [animate]);

  // Build a wavy surface path (two sine waves overlaid for natural water).
  const wavePath = (y, amp, freq, ph) => {
    const steps = 64;
    let d = `M ${cx - R - 20} ${V + 20}`;
    d += ` L ${cx - R - 20} ${y}`;
    for (let i = 0; i <= steps; i++) {
      const t = i / steps;
      const x = cx - R - 20 + t * (R * 2 + 40);
      const yy = y + Math.sin(t * Math.PI * 2 * freq + ph) * amp
                   + Math.sin(t * Math.PI * 2 * freq * 1.7 + ph * 1.3) * amp * 0.4;
      d += ` L ${x.toFixed(2)} ${yy.toFixed(2)}`;
    }
    d += ` L ${cx + R + 20} ${V + 20} Z`;
    return d;
  };

  const fluidD     = wavePath(surfaceY,     5.5, 2.2, phase * 0.8);
  const fluidEchoD = wavePath(surfaceY - 2, 4.5, 2.8, phase * 1.1 + 1.7);

  // Day markers: place each day at its own fluid level. X spread inside the orb.
  // Wider inner range for many days so markers don't overlap.
  const N = days.length;
  const inner = N >= 10 ? 0.72 : N >= 7 ? 0.62 : 0.55;
  const innerX0 = cx - R * inner;
  const innerX1 = cx + R * inner;

  // Marker size scales down as N grows.
  const markerOuterR = N <= 3 ? 14 : N <= 5 ? 12 : N <= 7 ? 10.5 : N <= 10 ? 9 : 7.5;
  const markerInnerR = markerOuterR - 4;
  const markerFontSize = N <= 5 ? 11 : N <= 7 ? 9.5 : N <= 10 ? 8.5 : 7.5;

  // Compute marker positions once
  const pts = days.map((d, i) => {
    const lv = balanceToLevel(d.balance, tdee);
    const y = cy + R - lv * (R * 2);
    const x = N === 1 ? cx : innerX0 + (i / (N - 1)) * (innerX1 - innerX0);
    const pointZone = classify(d.balance, tdee);
    return { x, y, balance: d.balance, zone: pointZone, color: ZONES[pointZone].hex };
  });

  // Smooth path through points (Catmull-Rom → cubic Bezier)
  const smoothPath = (points) => {
    if (points.length < 2) return '';
    let d = `M ${points[0].x.toFixed(2)} ${points[0].y.toFixed(2)}`;
    for (let i = 0; i < points.length - 1; i++) {
      const p0 = points[Math.max(0, i - 1)];
      const p1 = points[i];
      const p2 = points[i + 1];
      const p3 = points[Math.min(points.length - 1, i + 2)];
      const cp1x = p1.x + (p2.x - p0.x) / 6;
      const cp1y = p1.y + (p2.y - p0.y) / 6;
      const cp2x = p2.x - (p3.x - p1.x) / 6;
      const cp2y = p2.y - (p3.y - p1.y) / 6;
      d += ` C ${cp1x.toFixed(2)} ${cp1y.toFixed(2)}, ${cp2x.toFixed(2)} ${cp2y.toFixed(2)}, ${p2.x.toFixed(2)} ${p2.y.toFixed(2)}`;
    }
    return d;
  };
  const straightPath = pts.map((p) => `${p.x.toFixed(2)},${p.y.toFixed(2)}`).join(' ');

  return (
    <svg viewBox={`0 0 ${V} ${V}`} width={size} height={size} style={{ display: 'block' }}>
      <defs>
        <clipPath id="orbClipCx">
          <circle cx={cx} cy={cy} r={R - 2} />
        </clipPath>
        <filter id="orbPaper">
          <feTurbulence type="fractalNoise" baseFrequency="0.9" numOctaves="2" seed="3" />
          <feColorMatrix values="0 0 0 0 0.16  0 0 0 0 0.1  0 0 0 0 0.06  0 0 0 0.10 0" />
          <feComposite in2="SourceGraphic" operator="in" />
        </filter>
      </defs>

      {/* vessel */}
      <circle cx={cx} cy={cy} r={R} fill="var(--paper-2)" stroke="var(--ink)" strokeWidth="2.4" />

      <g clipPath="url(#orbClipCx)">
        {/* paper texture on the empty (above-fluid) portion */}
        <rect x="0" y="0" width={V} height={V} fill="var(--paper-2)" />
        <rect x="0" y="0" width={V} height={V} filter="url(#orbPaper)" opacity="0.55" />

        {/* zone hairlines (top = gain/keep, bot = keep/lose) */}
        <line x1={cx - R} x2={cx + R} y1={yGainKeep} y2={yGainKeep}
              stroke="var(--ink)" strokeOpacity="0.18" strokeWidth="1" strokeDasharray="2 6" />
        <line x1={cx - R} x2={cx + R} y1={yKeepLose} y2={yKeepLose}
              stroke="var(--ink)" strokeOpacity="0.18" strokeWidth="1" strokeDasharray="2 6" />

        {/* fluid — echo behind for depth, then main */}
        <path d={fluidEchoD} fill={color} opacity="0.32" />
        <path d={fluidD}     fill={color} />

        {/* surface line crisp edge */}
        <path d={(() => {
          // line along the upper wave only
          const steps = 64;
          let d = '';
          for (let i = 0; i <= steps; i++) {
            const t = i / steps;
            const x = cx - R - 20 + t * (R * 2 + 40);
            const yy = surfaceY + Math.sin(t * Math.PI * 2 * 2.2 + phase * 0.8) * 5.5
                     + Math.sin(t * Math.PI * 2 * 2.2 * 1.7 + phase * 0.8 * 1.3 + 1.0) * 5.5 * 0.4;
            d += (i === 0 ? `M ${x.toFixed(2)} ${yy.toFixed(2)}` : ` L ${x.toFixed(2)} ${yy.toFixed(2)}`);
          }
          return d;
        })()} fill="none" stroke="var(--ink)" strokeOpacity="0.35" strokeWidth="1.2" />

        {/* connector between day markers */}
        {showMarkers && pts.length >= 2 && connector !== 'none' && (
          connector === 'smooth' ? (
            <path d={smoothPath(pts)} fill="none" stroke="var(--ink)" strokeWidth="1.6" strokeLinecap="round" opacity="0.55" />
          ) : (
            <polyline points={straightPath} fill="none" stroke="var(--ink)" strokeWidth="1.5"
              strokeDasharray="2 5" strokeLinecap="round" opacity="0.55" />
          )
        )}

        {/* day markers */}
        {showMarkers && pts.map((p, i) => {
          if (markerStyle === 'dot') {
            return (
              <g key={i}>
                <circle cx={p.x} cy={p.y} r={markerInnerR + 1.5} fill="var(--paper-2)" />
                <circle cx={p.x} cy={p.y} r={markerInnerR - 0.5} fill={p.color} />
              </g>
            );
          }
          if (markerStyle === 'mini') {
            // Render a small fluid orb at the marker position.
            const miniR = markerOuterR * 1.4;
            const miniLv = balanceToLevel(p.balance, tdee);
            const miniSurfaceY = p.y + miniR - miniLv * (miniR * 2);
            // tiny wave path filling the inner mini-orb
            const steps = 16;
            let miniD = `M ${p.x - miniR - 1} ${p.y + miniR + 1} L ${p.x - miniR - 1} ${miniSurfaceY}`;
            for (let k = 0; k <= steps; k++) {
              const t = k / steps;
              const xx = p.x - miniR - 1 + t * (miniR * 2 + 2);
              const yy = miniSurfaceY + Math.sin(t * Math.PI * 2 * 1.6) * 0.8;
              miniD += ` L ${xx.toFixed(2)} ${yy.toFixed(2)}`;
            }
            miniD += ` L ${p.x + miniR + 1} ${p.y + miniR + 1} Z`;
            const clipId = `miniMark-${i}`;
            return (
              <g key={i}>
                <defs>
                  <clipPath id={clipId}>
                    <circle cx={p.x} cy={p.y} r={miniR - 0.5} />
                  </clipPath>
                </defs>
                <circle cx={p.x} cy={p.y} r={miniR + 0.5} fill="var(--paper-2)" stroke="var(--ink)" strokeWidth="1.4" />
                <g clipPath={`url(#${clipId})`}>
                  <rect x={p.x - miniR} y={p.y - miniR} width={miniR * 2} height={miniR * 2} fill="var(--paper-2)" />
                  <path d={miniD} fill={p.color} />
                </g>
              </g>
            );
          }
          // default: numbered
          return (
            <g key={i}>
              <circle cx={p.x} cy={p.y} r={markerOuterR} fill="var(--paper-2)" stroke="var(--ink)" strokeWidth="1.8" />
              <circle cx={p.x} cy={p.y} r={markerInnerR} fill={p.color} />
              <text x={p.x} y={p.y + markerFontSize / 3} textAnchor="middle" fontSize={markerFontSize}
                    fontFamily="JetBrains Mono, monospace" fontWeight="600" fill="var(--on-accent)">
                {i + 1}
              </text>
            </g>
          );
        })}
      </g>

      {/* outer ring crisp */}
      <circle cx={cx} cy={cy} r={R} fill="none" stroke="var(--ink)" strokeWidth="2.4" />
    </svg>
  );
}

// ── Mini orb (compact, for grids/strips) ──────────────────────────

function MiniOrb({ balance, size = 48, label, isToday = false, dim = false, isFuture = false, tdee = TDEE }) {
  const V = 100, cx = 50, cy = 50, R = 46;
  if (isFuture) {
    return (
      <svg viewBox={`0 0 ${V} ${V}`} width={size} height={size} style={{ display: 'block' }}>
        <circle cx={cx} cy={cy} r={R} fill="var(--paper-2)" stroke="var(--ink)" strokeOpacity="0.18" strokeWidth="1.4" strokeDasharray="2 3" />
        {label && (
          <text x={cx} y={cy + 4} textAnchor="middle" fontSize="14"
            fontFamily="JetBrains Mono, monospace" fontWeight="400" fill="var(--ink-3)">{label}</text>
        )}
      </svg>
    );
  }
  const zone = classify(balance, tdee);
  const color = ZONES[zone].hex;
  const level = balanceToLevel(balance, tdee);
  const surfaceY = cy + R - level * (R * 2);
  // simple wavy surface (static)
  const steps = 24;
  let waveD = `M ${cx - R - 4} ${V + 4}`;
  waveD += ` L ${cx - R - 4} ${surfaceY}`;
  for (let i = 0; i <= steps; i++) {
    const t = i / steps;
    const x = cx - R - 4 + t * (R * 2 + 8);
    const yy = surfaceY + Math.sin(t * Math.PI * 2 * 2) * 1.4;
    waveD += ` L ${x.toFixed(2)} ${yy.toFixed(2)}`;
  }
  waveD += ` L ${cx + R + 4} ${V + 4} Z`;

  return (
    <svg viewBox={`0 0 ${V} ${V}`} width={size} height={size} style={{ display: 'block', opacity: dim ? 0.55 : 1 }}>
      <defs>
        <clipPath id={`miniClip-${size}-${Math.round(balance)}`}>
          <circle cx={cx} cy={cy} r={R - 1} />
        </clipPath>
      </defs>
      <circle cx={cx} cy={cy} r={R}
        fill="var(--paper-2)"
        stroke={isToday ? 'var(--ink)' : 'var(--hair-vivid)'}
        strokeWidth={isToday ? 2.6 : 1.4}
      />
      <g clipPath={`url(#miniClip-${size}-${Math.round(balance)})`}>
        <path d={waveD} fill={color} />
      </g>
      {label && (
        <text x={cx} y={surfaceY > cy ? cy - 4 : cy + 18} textAnchor="middle" fontSize="20"
          fontFamily="JetBrains Mono, monospace" fontWeight="500"
          fill={surfaceY > cy ? 'var(--ink)' : 'var(--paper-2)'}>
          {label}
        </text>
      )}
    </svg>
  );
}

function CicoDayStrip({ days, compact = false, tdee = TDEE }) {
  const N = days.length;
  // For long cycles, cap visible cards at 7 (most recent) so each remains legible.
  const shown = N > 7 ? days.slice(-7) : days;
  const truncated = N > 7;
  const ultra = compact || shown.length > 5;

  return (
    <div style={{ display: 'flex', gap: ultra ? 6 : 10, alignItems: 'stretch' }}>
      {truncated && (
        <div style={{
          padding: '0 8px', display: 'flex', alignItems: 'center', justifyContent: 'center',
          background: 'transparent', border: '1px dashed var(--hair)', borderRadius: 10,
          minWidth: 40,
        }}>
          <span className="mono" style={{ fontSize: 10.5, color: 'var(--ink-3)', letterSpacing: '0.04em', whiteSpace: 'nowrap' }}>
            +{N - 7}
          </span>
        </div>
      )}
      {shown.map((d, i) => {
        const z = classify(d.balance, tdee);
        const color = ZONES[z].hex;
        const label = d.balance > 0 ? `+${d.balance}` : `${d.balance}`;
        const dateLabel = d.label || displayDate(d.date);
        return (
          <div key={i} style={{
            flex: 1, background: 'var(--paper-2)',
            border: '1px solid var(--hair)',
            borderRadius: 10, padding: ultra ? '8px 8px' : '10px 12px',
            display: 'flex', flexDirection: 'column', gap: ultra ? 2 : 3, alignItems: 'flex-start',
            minWidth: 0,
          }}>
            <div style={{ display: 'flex', alignItems: 'center', gap: 6, minWidth: 0 }}>
              <span style={{ width: 7, height: 7, borderRadius: 99, background: color, flexShrink: 0 }} />
              <span className="mono" style={{ fontSize: 10, color: 'var(--ink-3)', letterSpacing: '0.04em', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
                {dateLabel.toUpperCase()}
              </span>
            </div>
            {!ultra && (
              <div className="serif" style={{ fontSize: 19, lineHeight: 1, color: 'var(--ink)' }}>{z}</div>
            )}
            <div className="mono num-tab" style={{ fontSize: 11, color: 'var(--ink-2)' }}>{label} kcal</div>
          </div>
        );
      })}
    </div>
  );
}

Object.assign(window, { FluidOrb, MiniOrb, CicoDayStrip, ZONES, classify, verdict, balanceToLevel, balanceThreshold, THRESH });
