/* ============================================================
   METABOLE — UI primitives, hooks, charts
   ============================================================ */
const { useState, useRef, useEffect, useMemo, useCallback } = React;

/* ---------------- Hooks ---------------- */
function useInView(options = {}) {
  const ref = useRef(null);
  const [inView, setInView] = useState(false);
  useEffect(() => {
    const el = ref.current;
    if (!el) return;
    if (window.__forceInView) { setInView(true); return; }
    const once = options.once !== false;
    const margin = options.vh ?? 0.9;
    const check = () => {
      const r = el.getBoundingClientRect();
      const vh = window.innerHeight || document.documentElement.clientHeight;
      return r.top < vh * margin && r.bottom > 0;
    };
    if (check()) { setInView(true); if (once) return; }
    let io;
    if (typeof IntersectionObserver !== "undefined") {
      io = new IntersectionObserver(([e]) => {
        if (e.isIntersecting) { setInView(true); if (once) io.disconnect(); }
        else if (!once) setInView(false);
      }, { threshold: options.threshold ?? 0.12, rootMargin: options.rootMargin ?? "0px 0px -6% 0px" });
      io.observe(el);
    }
    const onScroll = () => {
      const v = check();
      if (v) { setInView(true); if (once) cleanup(); }
      else if (!once) setInView(false);
    };
    const cleanup = () => {
      window.removeEventListener("scroll", onScroll);
      window.removeEventListener("resize", onScroll);
    };
    window.addEventListener("scroll", onScroll, { passive: true });
    window.addEventListener("resize", onScroll);
    return () => { io && io.disconnect(); cleanup(); };
  }, []);
  return [ref, inView];
}

function Reveal({ children, delay = 0, className = "", style = {}, as = "div" }) {
  const [ref, inView] = useInView();
  const Tag = as;
  return (
    <Tag ref={ref}
      className={"reveal " + (inView ? "in " : "") + className}
      style={{ transitionDelay: (inView ? delay : 0) + "ms", ...style }}>
      {children}
    </Tag>
  );
}

function useCountUp(target, active, { dur = 1200, decimals = 0 } = {}) {
  const [val, setVal] = useState(0);
  useEffect(() => {
    if (!active) return;
    let raf, start;
    const from = 0, to = Number(target);
    const tick = (t) => {
      if (start == null) start = t;
      const p = Math.min(1, (t - start) / dur);
      const e = 1 - Math.pow(1 - p, 3);
      setVal(from + (to - from) * e);
      if (p < 1) raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(raf);
  }, [active, target]);
  return decimals ? val.toFixed(decimals) : Math.round(val);
}

function AnimatedNumber({ value, decimals = 0, active = true, suffix = "", prefix = "" }) {
  const v = useCountUp(value, active, { decimals });
  return <span>{prefix}{v}{suffix}</span>;
}

/* ---------------- Icons (simple geometric line glyphs) ---------------- */
function Icon({ name, size = 24, stroke = 1.6, color = "currentColor", style }) {
  const p = {
    fill: "none", stroke: color, strokeWidth: stroke,
    strokeLinecap: "round", strokeLinejoin: "round",
  };
  const paths = {
    camera: <g {...p}><rect x="3" y="6.5" width="18" height="13" rx="3" /><circle cx="12" cy="13" r="3.4" /><path d="M8.5 6.5l1.2-2h4.6l1.2 2" /></g>,
    trend: <g {...p}><path d="M3 16.5l5-5 3.5 3L20 7" /><path d="M20 11V7h-4" /></g>,
    grid: <g {...p}><rect x="3.5" y="3.5" width="7" height="7" rx="1.6" /><rect x="13.5" y="3.5" width="7" height="7" rx="1.6" /><rect x="3.5" y="13.5" width="7" height="7" rx="1.6" /><rect x="13.5" y="13.5" width="7" height="7" rx="1.6" /></g>,
    chat: <g {...p}><path d="M4 5.5h16a1.5 1.5 0 0 1 1.5 1.5v8a1.5 1.5 0 0 1-1.5 1.5H9l-4 3.5V17H4a1.5 1.5 0 0 1-1.5-1.5V7A1.5 1.5 0 0 1 4 5.5Z" /><circle cx="9" cy="11" r="0.6" fill={color} stroke="none" /><circle cx="12.5" cy="11" r="0.6" fill={color} stroke="none" /><circle cx="16" cy="11" r="0.6" fill={color} stroke="none" /></g>,
    book: <g {...p}><path d="M5 4.5h9a3 3 0 0 1 3 3V20a2.5 2.5 0 0 0-2.5-2.5H5Z" /><path d="M5 4.5v13" /><path d="M9 9h4M9 12h4" /></g>,
    wave: <g {...p}><path d="M2.5 13c2 0 2-6 4-6s2 10 4 10 2-12 4-12 2 8 4 8h2.5" /></g>,
    moon: <g {...p}><path d="M20 14.5A8 8 0 0 1 9.5 4 8 8 0 1 0 20 14.5Z" /></g>,
    spark: <g {...p}><path d="M12 3.5l1.8 4.7L18.5 10l-4.7 1.8L12 16.5l-1.8-4.7L5.5 10l4.7-1.8L12 3.5Z" /><path d="M18.5 15.5l.7 1.8 1.8.7-1.8.7-.7 1.8-.7-1.8-1.8-.7 1.8-.7Z" /></g>,
    swatch: <g {...p}><circle cx="9" cy="9" r="5" /><path d="M14 9a5 5 0 1 1-5 9" /><circle cx="9" cy="9" r="0.7" fill={color} stroke="none" /></g>,
    medal: <g {...p}><circle cx="12" cy="14.5" r="5" /><path d="M9 9.5L6.5 3.5h11L15 9.5" /><path d="M12 12.5l.9 1.8 2 .3-1.45 1.4.34 2-1.79-.95-1.79.95.34-2L9.1 14.6l2-.3Z" /></g>,
    doc: <g {...p}><path d="M6.5 3.5h7l4 4V20a1 1 0 0 1-1 1h-10a1 1 0 0 1-1-1V4.5a1 1 0 0 1 1-1Z" /><path d="M13.5 3.5V8h4" /><path d="M9 12.5h6M9 16h4" /></g>,
    shield: <g {...p}><path d="M12 3l7 2.5v5.5c0 4.5-3 7.8-7 9.5-4-1.7-7-5-7-9.5V5.5L12 3Z" /><path d="M9 12l2 2 3.5-3.8" /></g>,
    lock: <g {...p}><rect x="5" y="10.5" width="14" height="9.5" rx="2.2" /><path d="M8 10.5V8a4 4 0 0 1 8 0v2.5" /><circle cx="12" cy="15" r="1.1" fill={color} stroke="none" /></g>,
    apple: <g><path fill={color} d="M16.4 12.9c0-2.2 1.8-3.2 1.9-3.3-1-1.5-2.6-1.7-3.2-1.7-1.4-.1-2.6.8-3.3.8s-1.7-.8-2.9-.8c-1.5 0-2.9.9-3.6 2.2-1.6 2.7-.4 6.8 1.1 9 .7 1.1 1.6 2.3 2.8 2.2 1.1 0 1.5-.7 2.9-.7s1.7.7 2.9.7 2-1 2.7-2.1c.9-1.2 1.2-2.4 1.2-2.5 0 0-2.3-.9-2.3-3.5Z"/><path fill={color} d="M14.6 6.3c.6-.8 1-1.8.9-2.9-.9 0-2 .6-2.6 1.3-.6.7-1.1 1.7-1 2.7 1 .1 2-.4 2.7-1.1Z"/></g>,
    check: <g {...p}><path d="M5 12.5l4.5 4.5L19 7" /></g>,
    arrow: <g {...p}><path d="M5 12h13M12.5 6l6 6-6 6" /></g>,
    plus: <g {...p}><path d="M12 5.5v13M5.5 12h13" /></g>,
    droplet: <g {...p}><path d="M12 3.5c3 3.6 6 6.6 6 9.8A6 6 0 1 1 6 13.3c0-3.2 3-6.2 6-9.8Z" /></g>,
    bolt: <g {...p}><path d="M13 3l-7 9.5h5L10 21l7-9.5h-5L13 3Z" /></g>,
    bell: <g {...p}><path d="M6 16.5V11a6 6 0 0 1 12 0v5.5l1.5 2H4.5L6 16.5Z" /><path d="M10 19.5a2 2 0 0 0 4 0" /></g>,
    syringe: <g {...p}><path d="M14 4.5l5.5 5.5M17 5l2 2M12.5 7.5L16.5 11.5M5 18.5L13 10.5M5 18.5l-1.8 1.8M5 18.5l2.2 2.2M8.5 14l2 2" /></g>,
    pin: <g {...p}><path d="M12 21c4-4.5 6-7.6 6-10.5A6 6 0 0 0 6 10.5C6 13.4 8 16.5 12 21Z" /><circle cx="12" cy="10.5" r="2.2" /></g>,
    clock: <g {...p}><circle cx="12" cy="12" r="8.5" /><path d="M12 7.5V12l3 1.8" /></g>,
    star: <g {...p}><path d="M12 4l2.4 4.9 5.4.8-3.9 3.8.9 5.4L12 16.4 7.2 18.9l.9-5.4L4.2 9.7l5.4-.8L12 4Z" /></g>,
    minus: <g {...p}><path d="M5.5 12h13" /></g>,
    flask: <g {...p}><path d="M9.5 3.5v6L5 18.5a1.5 1.5 0 0 0 1.4 2.1h11.2A1.5 1.5 0 0 0 19 18.5l-4.5-9v-6" /><path d="M8.5 3.5h7M7.3 14h9.4" /></g>,
  };
  return (
    <svg width={size} height={size} viewBox="0 0 24 24" style={style} aria-hidden="true">
      {paths[name] || null}
    </svg>
  );
}

/* ---------------- Logo ---------------- */
function Logo({ size = 22, color = "var(--green)", text = true }) {
  return (
    <span style={{ display: "inline-flex", alignItems: "center", gap: 9 }}>
      <svg width={size} height={size} viewBox="0 0 28 28" aria-hidden="true">
        <circle cx="14" cy="14" r="13" fill="none" stroke={color} strokeWidth="2" />
        <path d="M7 18.5c2.2 0 2.2-9 4.4-9s2.2 13 4.4 13 2.2-9 4.4-9"
          fill="none" stroke={color} strokeWidth="2.1" strokeLinecap="round" />
        <circle cx="20.6" cy="13.5" r="1.7" fill="var(--clay)" />
      </svg>
      {text && <span style={{ fontWeight: 700, fontSize: size * 0.92, letterSpacing: "-0.03em", color: "var(--ink)" }}>Metabole</span>}
    </span>
  );
}

/* ---------------- WeightTrend chart ---------------- */
function buildPath(pts) {
  return pts.map((pt, i) => (i === 0 ? "M" : "L") + pt[0].toFixed(1) + " " + pt[1].toFixed(1)).join(" ");
}
function WeightTrend({ active, w = 360, h = 170, compact = false }) {
  const data = [214, 212.4, 210.1, 209.6, 207.2, 205.8, 204.9, 202.3, 201.1, 199.4, 197.8, 196.2, 195.1, 193.6];
  const pad = { l: 8, r: 8, t: 16, b: 18 };
  const min = 190, max = 216;
  const innerW = w - pad.l - pad.r, innerH = h - pad.t - pad.b;
  const pts = data.map((d, i) => [pad.l + (i / (data.length - 1)) * innerW, pad.t + (1 - (d - min) / (max - min)) * innerH]);
  const line = buildPath(pts);
  const area = line + ` L${pts[pts.length - 1][0]} ${pad.t + innerH} L${pts[0][0]} ${pad.t + innerH} Z`;
  const goalY = pad.t + (1 - (188 - min) / (max - min)) * innerH;
  const ref = useRef(null);
  const [len, setLen] = useState(600);
  useEffect(() => { if (ref.current) setLen(ref.current.getTotalLength()); }, []);
  const last = pts[pts.length - 1];
  return (
    <svg viewBox={`0 0 ${w} ${h}`} width="100%" style={{ display: "block" }}>
      <defs>
        <linearGradient id="wt-fill" x1="0" y1="0" x2="0" y2="1">
          <stop offset="0%" stopColor="var(--sage)" stopOpacity="0.32" />
          <stop offset="100%" stopColor="var(--sage)" stopOpacity="0" />
        </linearGradient>
      </defs>
      {[0, 1, 2, 3].map(i => (
        <line key={i} x1={pad.l} x2={w - pad.r} y1={pad.t + (i / 3) * innerH} y2={pad.t + (i / 3) * innerH}
          stroke="var(--line)" strokeWidth="1" strokeDasharray="2 4" />
      ))}
      <path d={area} fill="url(#wt-fill)" style={{ opacity: active ? 1 : 0, transition: "opacity .8s ease .3s" }} />
      <path ref={ref} d={line} fill="none" stroke="var(--green)" strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round"
        style={{ strokeDasharray: len, strokeDashoffset: active ? 0 : len, transition: "stroke-dashoffset 1.5s cubic-bezier(.4,.1,.2,1)" }} />
      <line x1={pad.l} x2={w - pad.r} y1={goalY} y2={goalY} stroke="var(--clay)" strokeWidth="1.4" strokeDasharray="4 4" opacity="0.7" />
      <circle cx={last[0]} cy={last[1]} r="4.5" fill="var(--green)" stroke="var(--surface)" strokeWidth="2"
        style={{ opacity: active ? 1 : 0, transition: "opacity .4s ease 1.4s" }} />
      {!compact && <text x={pad.l + 2} y={goalY - 6} fontSize="10" fill="var(--clay-deep)" fontFamily="var(--mono)">GOAL 188</text>}
    </svg>
  );
}

/* ---------------- Sparkline ---------------- */
function Sparkline({ data, color = "var(--green)", w = 90, h = 30, active = true }) {
  const min = Math.min(...data), max = Math.max(...data);
  const pts = data.map((d, i) => [(i / (data.length - 1)) * w, h - 2 - ((d - min) / (max - min || 1)) * (h - 4)]);
  return (
    <svg viewBox={`0 0 ${w} ${h}`} width={w} height={h}>
      <path d={buildPath(pts)} fill="none" stroke={color} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"
        style={{ opacity: active ? 1 : 0, transition: "opacity .6s" }} />
    </svg>
  );
}

/* ---------------- Sleep weekly bars ---------------- */
function WeeklyBars({ active, data = [7.2, 6.4, 8.1, 5.9, 7.8, 6.7, 8.4], goal = 8, labels = ["M", "T", "W", "T", "F", "S", "S"] }) {
  const max = 9;
  return (
    <div style={{ display: "flex", alignItems: "flex-end", gap: 8, height: 110 }}>
      {data.map((d, i) => (
        <div key={i} style={{ flex: 1, display: "flex", flexDirection: "column", alignItems: "center", gap: 6, height: "100%", justifyContent: "flex-end" }}>
          <div style={{ width: "100%", maxWidth: 22, height: "100%", display: "flex", alignItems: "flex-end" }}>
            <div style={{
              width: "100%", borderRadius: 6,
              background: d >= goal ? "var(--green)" : "var(--mint-2)",
              height: active ? (d / max * 100) + "%" : "0%",
              transition: `height .9s cubic-bezier(.2,.7,.3,1) ${i * 70}ms`,
            }} />
          </div>
          <span className="mono" style={{ fontSize: 10, color: "var(--ink-3)" }}>{labels[i]}</span>
        </div>
      ))}
    </div>
  );
}

/* ---------------- Sleep heatmap ---------------- */
function SleepHeatmap({ active, weeks = 16 }) {
  const cells = useMemo(() => Array.from({ length: weeks * 7 }, (_, i) => {
    const base = Math.sin(i * 0.5) * 0.3 + 0.55 + (Math.random() * 0.35);
    return Math.max(0.08, Math.min(1, base));
  }), [weeks]);
  return (
    <div style={{ display: "grid", gridTemplateColumns: `repeat(${weeks}, 1fr)`, gap: 3 }}>
      {Array.from({ length: 7 }).map((_, row) => (
        Array.from({ length: weeks }).map((_, col) => {
          const v = cells[col * 7 + row];
          return <div key={row + "-" + col} title={(v * 9).toFixed(1) + "h"} style={{
            aspectRatio: "1", borderRadius: 3,
            background: `color-mix(in oklab, var(--green) ${Math.round(v * 100)}%, var(--mint-soft))`,
            opacity: active ? 1 : 0,
            transition: `opacity .5s ease ${(col * 18 + row * 6)}ms`,
          }} />;
        })
      ))}
    </div>
  );
}

/* ---------------- Progress ring ---------------- */
function Ring({ pct = 72, size = 64, stroke = 7, color = "var(--green)", track = "var(--mint)", active = true, label, value }) {
  const r = (size - stroke) / 2;
  const c = 2 * Math.PI * r;
  return (
    <div style={{ position: "relative", width: size, height: size, display: "grid", placeItems: "center" }}>
      <svg width={size} height={size} style={{ transform: "rotate(-90deg)" }}>
        <circle cx={size / 2} cy={size / 2} r={r} fill="none" stroke={track} strokeWidth={stroke} />
        <circle cx={size / 2} cy={size / 2} r={r} fill="none" stroke={color} strokeWidth={stroke} strokeLinecap="round"
          strokeDasharray={c} strokeDashoffset={active ? c * (1 - pct / 100) : c}
          style={{ transition: "stroke-dashoffset 1.1s cubic-bezier(.3,.8,.3,1)" }} />
      </svg>
      <div style={{ position: "absolute", textAlign: "center", lineHeight: 1 }}>
        <div style={{ fontWeight: 700, fontSize: size * 0.24 }}>{value ?? pct + "%"}</div>
        {label && <div className="mono" style={{ fontSize: 8.5, color: "var(--ink-3)", marginTop: 2 }}>{label}</div>}
      </div>
    </div>
  );
}

Object.assign(window, {
  useInView, Reveal, useCountUp, AnimatedNumber, Icon, Logo,
  WeightTrend, Sparkline, WeeklyBars, SleepHeatmap, Ring, buildPath,
});
