/* ============================================================
   Quiniela Mundial 2026 — shared UI primitives & hooks
   ============================================================ */
const { useState, useEffect, useRef, useSyncExternalStore, useCallback } = React;
const Q = window.QStore;

/* ---------- reactive store hook ---------- */
function useStore() {
  useSyncExternalStore(Q.subscribe, Q.getVersion);
  return Q;
}

/* ---------- date helpers (es, shown in Lima time = UTC-5) ---------- */
// Kickoffs are stored as UTC epoch ms; all labels render in America/Lima (UTC-5, no DST).
const LIMA_TZ = "America/Lima";
const LIMA_OFFSET_MS = 5 * 3600 * 1000;
const _dayFmt = new Intl.DateTimeFormat("es", { weekday: "short", day: "numeric", month: "short", timeZone: LIMA_TZ });
const _timeFmt = new Intl.DateTimeFormat("es", { hour: "2-digit", minute: "2-digit", hour12: false, timeZone: LIMA_TZ });
const cap = (s) => s.charAt(0).toUpperCase() + s.slice(1);
function fmtDay(ts) { return cap(_dayFmt.format(new Date(ts)).replace(".", "")); }
function fmtTime(ts) { return _timeFmt.format(new Date(ts)); }
function fmtWhen(ts) { return `${fmtDay(ts)} · ${fmtTime(ts)}`; }
// <input type="datetime-local"> works in Lima wall-time; convert to/from the stored UTC epoch.
function tsToLimaInput(ts) { return ts == null ? "" : new Date(ts - LIMA_OFFSET_MS).toISOString().slice(0, 16); }
function limaInputToTs(v) { if (!v) return null; const t = new Date(v + "Z").getTime(); return Number.isFinite(t) ? t + LIMA_OFFSET_MS : null; }

/* ---------- round / group display label ---------- */
const ROUND_LABEL = { R32: "16vos", R16: "8vos", QF: "CF", SF: "SF", "3PL": "3er", FIN: "Final" };
function roundLabel(group) { return ROUND_LABEL[group] || ("Grupo " + group); }
function initials(name) {
  const p = name.trim().split(/\s+/);
  return ((p[0]?.[0] || "") + (p[1]?.[0] || "")).toUpperCase();
}

/* ---------- icons (Lucide-style stroke) ---------- */
function Icon({ name, size = 22, stroke = 2 }) {
  const common = { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: stroke, strokeLinecap: "round", strokeLinejoin: "round" };
  const paths = {
    ball: <><circle cx="12" cy="12" r="9" /><path d="M12 7l4 3-1.5 4.5h-5L8 10z" /><path d="M12 3v4M5 9l3 1M19 9l-3 1M8.5 19l1.5-4.5M15.5 19L14 14.5" /></>,
    calendar: <><rect x="3" y="4.5" width="18" height="16" rx="2.5" /><path d="M3 9h18M8 2.5v4M16 2.5v4" /></>,
    trophy: <><path d="M7 4h10v5a5 5 0 0 1-10 0V4z" /><path d="M7 6H4v1a3 3 0 0 0 3 3M17 6h3v1a3 3 0 0 1-3 3M9 19h6M12 14v5M8.5 21h7" /></>,
    shield: <><path d="M12 3l8 3v6c0 5-3.5 8-8 9-4.5-1-8-4-8-9V6z" /><path d="M9 12l2 2 4-4" /></>,
    sliders: <><path d="M4 6h10M18 6h2M4 12h2M10 12h10M4 18h7M15 18h5" /><circle cx="16" cy="6" r="2" /><circle cx="8" cy="12" r="2" /><circle cx="13" cy="18" r="2" /></>,
    check: <><path d="M5 12l4.5 4.5L19 7" /></>,
    x: <><path d="M6 6l12 12M18 6L6 18" /></>,
    copy: <><rect x="9" y="9" width="11" height="11" rx="2" /><path d="M5 15V5a2 2 0 0 1 2-2h10" /></>,
    plus: <><path d="M12 5v14M5 12h14" /></>,
    lock: <><rect x="5" y="11" width="14" height="9" rx="2" /><path d="M8 11V8a4 4 0 0 1 8 0v3" /></>,
    bolt: <><path d="M13 3L4 14h6l-1 7 9-11h-6z" /></>,
    clock: <><circle cx="12" cy="12" r="9" /><path d="M12 7v5l3.5 2" /></>,
    users: <><circle cx="9" cy="8" r="3.2" /><path d="M3.5 20a5.5 5.5 0 0 1 11 0M16 6.5a3 3 0 0 1 0 5.8M21 20a5 5 0 0 0-4-4.9" /></>,
    mail: <><rect x="3" y="5" width="18" height="14" rx="2.5" /><path d="M4 7l8 6 8-6" /></>,
    chevR: <><path d="M9 6l6 6-6 6" /></>,
    edit: <><path d="M4 20h4L18.5 9.5a2 2 0 0 0-3-3L5 17v3z" /><path d="M13.5 6.5l3 3" /></>,
    table: <><rect x="3" y="4.5" width="18" height="15" rx="2" /><path d="M3 10h18M3 15h18M9.5 4.5v15M15 4.5v15" /></>,
    chevD: <><path d="M6 9l6 6 6-6" /></>,
    logout: <><path d="M15 4h3a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2h-3" /><path d="M10 17l5-5-5-5M15 12H3" /></>,
    help: <><circle cx="12" cy="12" r="9" /><path d="M9.3 9.2a2.7 2.7 0 0 1 5.3.8c0 1.8-2.6 2.1-2.6 3.8" /><path d="M12 17h.01" /></>,
    book: <><rect x="5" y="3" width="14" height="18" rx="1.5" /><path d="M9 7h6M9 11h6M9 15h4" /></>,
    trash: <><path d="M4 7h16M10 11v6M14 11v6M5 7l1 13a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2l1-13M9 7V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v3" /></>,
  };
  return <svg {...common}>{paths[name]}</svg>;
}

/* ---------- avatar ---------- */
function Avatar({ name, size = 34, gold }) {
  const bg = gold ? "linear-gradient(160deg,var(--gold-soft),var(--gold))" : "var(--q-gradient)";
  return (
    <div style={{ width: size, height: size, borderRadius: "50%", background: bg, display: "grid", placeItems: "center", color: "#fff", fontFamily: "var(--font-head)", fontWeight: 900, fontSize: size * 0.4, flex: "none" }}>
      {initials(name)}
    </div>
  );
}

/* ---------- outcome badge / strip ---------- */
const OUT_LABEL = { exact: "Exacto", result: "Resultado", miss: "Fallo", none: "Sin pronóstico" };
function OutcomeBadge({ kind, pts }) {
  const sym = kind === "exact" ? "⭐" : kind === "result" ? "✓" : kind === "miss" ? "✗" : "–";
  return (
    <span className={`qm-outcome ${kind}`}>
      <span className="dot" />
      <span>{sym} {OUT_LABEL[kind]}</span>
      {(kind === "exact" || kind === "result") && <span className="pts">+{pts}</span>}
    </span>
  );
}
function MiniStrip({ marks }) {
  return (
    <div className="qm-strip" title="Últimos 4 partidos">
      {marks.map((m, i) => {
        const sym = m.kind === "exact" ? "★" : m.kind === "result" ? "✓" : m.kind === "miss" ? "✕" : "–";
        return <i key={i} className={m.kind}>{sym}</i>;
      })}
    </div>
  );
}

/* ---------- responsive count for the recent-results strip ----------
   The name gets a protected, fixed area first (NAME_MIN) so it is never
   squeezed; the badges only fill whatever is left over — that leftover
   "designated area" is what decides how many fit. Mobile (~340px) → 1–2,
   desktop (~684px) → up to 8. Keep these in sync with .qm-lb-row CSS. */
const PREDS_NAME_MIN = 112; // protected name column — never shrinks below this
function fitPredsCount(w) {
  if (!w) return 2; // safe default before the first measure (never overflows)
  const fixed = 56 /*rank col*/ + 52 /*pts col*/ + 30 /*3 grid gaps*/ + 28 /*row padding*/;
  const perBadge = 46; // badge min-width + gap
  const badgeArea = w - fixed - PREDS_NAME_MIN; // designated space for badges
  const n = Math.floor(badgeArea / perBadge);
  return Math.max(1, Math.min(8, n));
}
/* Observe a list container's width and return how many badges fit. */
function usePredsCount(ref) {
  const [w, setW] = useState(0);
  useEffect(() => {
    const el = ref.current;
    if (!el) return;
    const update = () => setW(el.clientWidth);
    update();
    let ro;
    if (window.ResizeObserver) { ro = new ResizeObserver(update); ro.observe(el); }
    else window.addEventListener("resize", update);
    return () => { ro ? ro.disconnect() : window.removeEventListener("resize", update); };
  }, [ref]);
  return fitPredsCount(w);
}

/* ---------- movement indicator ---------- */
function Move({ delta }) {
  if (!delta) return <span className="qm-move flat">–</span>;
  const up = delta > 0;
  return <span className={`qm-move ${up ? "up" : "down"}`}>{up ? "▲" : "▼"}{Math.abs(delta)}</span>;
}

/* ---------- toast system (global) ---------- */
const _toastListeners = new Set();
function toast(msg, variant) {
  const id = Math.random().toString(36).slice(2);
  _toastListeners.forEach((fn) => fn({ id, msg, variant }));
}
function ToastHost() {
  const [items, setItems] = useState([]);
  useEffect(() => {
    const fn = (t) => {
      setItems((cur) => [...cur, t]);
      setTimeout(() => setItems((cur) => cur.filter((x) => x.id !== t.id)), 2600);
    };
    _toastListeners.add(fn);
    return () => _toastListeners.delete(fn);
  }, []);
  return (
    <div className="qm-toast-wrap">
      {items.map((t) => (
        <div key={t.id} className={`qm-toast ${t.variant || ""}`}>
          {t.variant === "ok" && <Icon name="check" size={16} />}
          {t.variant === "gold" && <span>⭐</span>}
          <span>{t.msg}</span>
        </div>
      ))}
    </div>
  );
}

/* ---------- confetti burst ---------- */
function fireConfetti() {
  const host = document.createElement("div");
  host.className = "qm-confetti";
  const colors = ["#DD3156", "#992299", "#5F38AA", "#E7A50F", "#F6C84C", "#E85A77"];
  for (let i = 0; i < 80; i++) {
    const p = document.createElement("i");
    p.style.left = Math.random() * 100 + "vw";
    p.style.background = colors[(Math.random() * colors.length) | 0];
    p.style.animationDuration = 1.6 + Math.random() * 1.4 + "s";
    p.style.animationDelay = Math.random() * 0.25 + "s";
    p.style.transform = `translateY(-20px) rotate(${Math.random() * 360}deg)`;
    if (Math.random() > 0.5) p.style.borderRadius = "50%";
    host.appendChild(p);
  }
  document.body.appendChild(host);
  setTimeout(() => host.remove(), 3400);
}

/* ---------- small stepper ---------- */
function Stepper({ value, onChange, disabled }) {
  const clamp = (n) => Math.max(0, Math.min(20, n));
  return (
    <div className="qm-stepper">
      <div className="v">{value}</div>
      {!disabled && (
        <div className="qm-step-btns">
          <button type="button" aria-label="menos" onClick={() => onChange(clamp(value - 1))}>−</button>
          <button type="button" aria-label="más" onClick={() => onChange(clamp(value + 1))}>+</button>
        </div>
      )}
    </div>
  );
}

/* ---------- error boundary (prevents a render error from white-screening the app) ---------- */
class ErrorBoundary extends React.Component {
  constructor(p) { super(p); this.state = { err: null }; }
  static getDerivedStateFromError(err) { return { err }; }
  componentDidCatch(err, info) { console.error("[UI] render error:", err, info); }
  render() {
    if (this.state.err) {
      return (
        <div className="qm-card" style={{ margin: 18, padding: 20 }}>
          <b style={{ fontFamily: "var(--font-head)", fontSize: 16 }}>Algo falló al mostrar esta sección.</b>
          <p style={{ fontSize: 13, color: "var(--fg-3)", marginTop: 6, lineHeight: 1.5 }}>
            Cambia de pestaña o recarga la página. Si acabas de importar datos, espera unos segundos y reintenta.
          </p>
          <p style={{ fontSize: 11, color: "var(--fg-3)", marginTop: 8, fontFamily: "monospace" }}>{String(this.state.err.message || this.state.err)}</p>
        </div>
      );
    }
    return this.props.children;
  }
}

Object.assign(window, {
  useStore, fmtDay, fmtTime, fmtWhen, tsToLimaInput, limaInputToTs, roundLabel, initials, cap,
  Icon, Avatar, OutcomeBadge, MiniStrip, Move, OUT_LABEL, usePredsCount,
  toast, ToastHost, fireConfetti, Stepper, ErrorBoundary,
  useState, useEffect, useRef, useCallback,
});
