/* ============================================================
   PROGRESS PORTFOLIO — shared UI primitives
   ============================================================ */
const { useState, useEffect, useRef, useMemo, useCallback } = React;

/* ---- Card ---- */
function Card({ title, right, children, className, glow, pad }) {
  return (
    <div className={"card" + (glow ? " card-glow" : "") + (className ? " " + className : "")}>
      {(title || right) && (
        <div className="card-head">
          {title && <div className="card-title">{title}</div>}
          {right}
        </div>
      )}
      <div className="card-body" style={pad === false ? { padding: 0 } : null}>{children}</div>
    </div>
  );
}

/* ---- horizontal stat / progress bar ---- */
function Bar({ pct, hue, height, label, value, mono }) {
  const h = height || 8;
  const color = hue != null ? `oklch(0.82 0.15 ${hue})` : "var(--accent)";
  return (
    <div className="barwrap">
      {(label || value != null) && (
        <div className="barlabel">
          <span>{label}</span>
          <span className={mono ? "mono" : ""}>{value}</span>
        </div>
      )}
      <div className="bartrack" style={{ height: h }}>
        <div className="barfill" style={{ width: Math.min(100, Math.max(0, pct * 100)) + "%", background: color, boxShadow: `0 0 12px ${color}66` }} />
      </div>
    </div>
  );
}

/* ---- circular progress ring ---- */
function Ring({ pct, size, stroke, children, hue, faint }) {
  const s = size || 120;
  const sw = stroke || 9;
  const r = (s - sw) / 2;
  const c = 2 * Math.PI * r;
  const color = hue != null ? `oklch(0.82 0.15 ${hue})` : "var(--accent)";
  return (
    <div className="ring" style={{ width: s, height: s }}>
      <svg width={s} height={s}>
        <circle cx={s / 2} cy={s / 2} r={r} fill="none" stroke="var(--line)" strokeWidth={sw} opacity={faint ? 0.4 : 1} />
        <circle cx={s / 2} cy={s / 2} r={r} fill="none" stroke={color} strokeWidth={sw} strokeLinecap="round"
          strokeDasharray={c} strokeDashoffset={c * (1 - Math.min(1, Math.max(0, pct)))}
          style={{ transition: "stroke-dashoffset .6s cubic-bezier(.2,.8,.2,1)", filter: `drop-shadow(0 0 6px ${color}77)`, transform: "rotate(-90deg)", transformOrigin: "center" }} />
      </svg>
      <div className="ring-center">{children}</div>
    </div>
  );
}

/* ---- sparkline ---- */
function Spark({ data, w, h, hue, fill }) {
  w = w || 200; h = h || 48;
  if (!data || data.length < 2) return <div className="spark-empty" style={{ height: h }}>no data yet</div>;
  const max = Math.max(...data, 1), min = Math.min(...data, 0);
  const span = max - min || 1;
  const pts = data.map((v, i) => [(i / (data.length - 1)) * w, h - ((v - min) / span) * (h - 6) - 3]);
  const d = pts.map((p, i) => (i ? "L" : "M") + p[0].toFixed(1) + " " + p[1].toFixed(1)).join(" ");
  const color = hue != null ? `oklch(0.82 0.15 ${hue})` : "var(--accent)";
  const area = `${d} L ${w} ${h} L 0 ${h} Z`;
  return (
    <svg width="100%" height={h} viewBox={`0 0 ${w} ${h}`} preserveAspectRatio="none" className="spark">
      {fill && <path d={area} fill={color} opacity="0.12" />}
      <path d={d} fill="none" stroke={color} strokeWidth="2" strokeLinejoin="round" strokeLinecap="round" />
      <circle cx={pts[pts.length - 1][0]} cy={pts[pts.length - 1][1]} r="2.6" fill={color} />
    </svg>
  );
}

/* ---- bar chart ---- */
function BarChart({ data, h, hue }) {
  h = h || 120;
  const max = Math.max(...data.map((d) => d.v), 1);
  const color = hue != null ? `oklch(0.82 0.15 ${hue})` : "var(--accent)";
  return (
    <div className="barchart" style={{ height: h }}>
      {data.map((d, i) => (
        <div className="barchart-col" key={i} title={`${d.label}: ${d.v}`}>
          <div className="barchart-bar" style={{ height: Math.max(2, (d.v / max) * (h - 22)) + "px", background: color, boxShadow: d.v ? `0 0 8px ${color}55` : "none" }} />
          <div className="barchart-lab">{d.label}</div>
        </div>
      ))}
    </div>
  );
}

/* ---- star / dot rating input ---- */
function Rating({ value, onChange, max, icon, readOnly }) {
  max = max || 5;
  const [hover, setHover] = useState(0);
  const items = [];
  for (let i = 1; i <= max; i++) {
    const on = (hover || value) >= i;
    items.push(
      <button key={i} type="button" className={"rate-dot" + (on ? " on" : "")} disabled={readOnly}
        onMouseEnter={() => !readOnly && setHover(i)} onMouseLeave={() => !readOnly && setHover(0)}
        onClick={() => !readOnly && onChange(i)}>{icon || "●"}</button>
    );
  }
  return <div className="rate">{items}</div>;
}

/* ---- modal ---- */
function Modal({ open, onClose, title, children, wide, footer, dismissable }) {
  useEffect(() => {
    if (!open) return;
    const h = (e) => { if (e.key === "Escape" && dismissable !== false) onClose(); };
    window.addEventListener("keydown", h);
    return () => window.removeEventListener("keydown", h);
  }, [open, onClose, dismissable]);
  if (!open) return null;
  return (
    <div className="modal-scrim" onMouseDown={(e) => { if (e.target === e.currentTarget && dismissable !== false) onClose(); }}>
      <div className={"modal" + (wide ? " modal-wide" : "")}>
        <div className="modal-head">
          <div className="modal-title">{title}</div>
          {dismissable !== false && <button className="icon-btn" onClick={onClose}>✕</button>}
        </div>
        <div className="modal-body">{children}</div>
        {footer && <div className="modal-foot">{footer}</div>}
      </div>
    </div>
  );
}

/* ---- segmented control ---- */
function Seg({ options, value, onChange, small }) {
  return (
    <div className={"seg" + (small ? " seg-sm" : "")}>
      {options.map((o) => (
        <button key={o.value} className={"seg-btn" + (value === o.value ? " on" : "")} onClick={() => onChange(o.value)}>{o.label}</button>
      ))}
    </div>
  );
}

/* ---- stat chip ---- */
function StatChip({ stat }) {
  const s = window.PP.CATS[stat];
  if (!s) return null;
  return <span className="statchip" style={{ "--ch": `oklch(0.82 0.15 ${s.hue})` }}>{stat}</span>;
}

/* ---- XP toast (floating gain) ---- */
function XpToast({ toasts }) {
  return (
    <div className="xp-toasts">
      {toasts.map((t) => (
        <div className="xp-toast" key={t.id} style={t.stat ? { "--tc": `oklch(0.82 0.15 ${window.PP.CATS[t.stat].hue})` } : null}>
          <span className="xp-amt">+{t.val} {t.unit}</span>{t.stat && <span className="xp-stat">{t.stat}</span>}
        </div>
      ))}
    </div>
  );
}

/* ---- empty state ---- */
function Empty({ children }) { return <div className="empty">{children}</div>; }

/* ---- completion animation FX (used by checklist + settings preview) ---- */
const THEME_FX_ANIMS = ["bloom", "sunburst", "leaffall", "frost", "mario", "neon", "rupee", "bubble", "shimmer", "spark", "ink", "matrix"];
function runCompletionFx(row, anim, color) {
  if (!row || !anim || anim === "none") return;
  // signature theme effects are handled by themefx.js
  if (THEME_FX_ANIMS.indexOf(anim) >= 0) { if (window.PP && window.PP.themeCompletionFx) window.PP.themeCompletionFx(row, anim, color); return; }
  const cls = "cfx-" + anim;
  row.classList.remove(cls); void row.offsetWidth; row.classList.add(cls);
  setTimeout(() => row.classList.remove(cls), 850);
  if (anim === "confetti" || anim === "pixelburst") {
    const layer = document.createElement("span");
    layer.className = "cfx-particles";
    const n = anim === "pixelburst" ? 11 : 16;
    for (let i = 0; i < n; i++) {
      const p = document.createElement("i");
      p.className = "cfx-p " + (anim === "pixelburst" ? "px" : "cf");
      const ang = (Math.PI * 2 * i) / n + Math.random() * 0.6;
      const dist = 22 + Math.random() * 30;
      p.style.setProperty("--dx", (Math.cos(ang) * dist).toFixed(1) + "px");
      p.style.setProperty("--dy", (Math.sin(ang) * dist).toFixed(1) + "px");
      p.style.background = anim === "cf" || anim === "confetti" ? ["var(--accent)", "#fff", "oklch(0.82 0.16 60)", "oklch(0.78 0.16 300)"][i % 4] : (color || "var(--accent)");
      layer.appendChild(p);
    }
    row.appendChild(layer);
    setTimeout(() => layer.remove(), 950);
  }
}

/* ---- number input row ---- */
function Field({ label, children, hint }) {
  return (
    <label className="field">
      <span className="field-label">{label}</span>
      {children}
      {hint && <span className="field-hint">{hint}</span>}
    </label>
  );
}

Object.assign(window, {
  Card, Bar, Ring, Spark, BarChart, Rating, Modal, Seg, StatChip, XpToast, Empty, Field, runCompletionFx,
  useState, useEffect, useRef, useMemo, useCallback,
});
