/* FINAZ · Catálogo estético — NÚCLEO compartido.
   Geometría del gráfico, hook de animación de entrada, lienzo de partículas
   (motor cinético reutilizable por varias direcciones) y lectura al hover.
   Coordenadas internas del SVG: 1000 × 540 (viewBox). */
(function () {
  "use strict";
  const { useRef, useEffect, useState, useMemo } = React;

  const VBW = 1000, VBH = 540;
  const PAD = { l: 64, r: 64, t: 70, b: 92 };

  // ---- path suavizado (Catmull-Rom → bézier) ----
  function smoothPath(pts, smooth) {
    if (pts.length < 2) return "";
    if (smooth <= 0.001) return "M" + pts.map((p) => p.x.toFixed(2) + "," + p.y.toFixed(2)).join(" L");
    const k = smooth * 0.33;
    let d = "M" + pts[0].x.toFixed(2) + "," + pts[0].y.toFixed(2);
    for (let i = 0; i < pts.length - 1; i++) {
      const p0 = pts[i - 1] || pts[i], p1 = pts[i], p2 = pts[i + 1], p3 = pts[i + 2] || p2;
      const c1x = p1.x + (p2.x - p0.x) * k, c1y = p1.y + (p2.y - p0.y) * k;
      const c2x = p2.x - (p3.x - p1.x) * k, c2y = p2.y - (p3.y - p1.y) * k;
      d += " C" + c1x.toFixed(2) + "," + c1y.toFixed(2) + " " + c2x.toFixed(2) + "," + c2y.toFixed(2) + " " + p2.x.toFixed(2) + "," + p2.y.toFixed(2);
    }
    return d;
  }

  // ---- geometría a partir de los puntos del dato ----
  function useGeom(points) {
    return useMemo(() => {
      const n = points.length;
      const plotW = VBW - PAD.l - PAD.r;
      const plotH = VBH - PAD.t - PAD.b;
      const vals = points.map((p) => p.v);
      let yMin = Math.min(...vals), yMax = Math.max(...vals);
      const padV = (yMax - yMin) * 0.22 || 1;
      yMin -= padV; yMax += padV * 0.7;
      const xFor = (i) => PAD.l + (i / (n - 1)) * plotW;
      const yFor = (v) => PAD.t + (1 - (v - yMin) / (yMax - yMin)) * plotH;
      const pts = points.map((p, i) => {
        const x = xFor(i), y = yFor(p.v);
        return { x, y, nx: x / VBW, ny: y / VBH, v: p.v, t: p.t, vol: p.vol, i };
      });
      const baseY = PAD.t + plotH;
      // picos locales (para floraciones / nodos)
      const peaks = [];
      for (let i = 2; i < n - 2; i++) {
        if (pts[i].v > pts[i - 1].v && pts[i].v > pts[i - 2].v && pts[i].v >= pts[i + 1].v && pts[i].v >= pts[i + 2].v) peaks.push(i);
      }
      return { pts, n, baseY, plotW, plotH, yMin, yMax, peaks, VBW, VBH, PAD };
    }, [points]);
  }

  // ---- animación de entrada 0→1 (ease-out cúbico) ----
  function useIntro(key, dur) {
    const reduce = typeof window !== "undefined" && window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
    const [t, setT] = useState(reduce ? 1 : 0);
    useEffect(() => {
      if (reduce) { setT(1); return; }
      setT(0);
      let raf, t0 = performance.now();
      const d = dur || 1500;
      // salvaguarda: si rAF se estrangula (pestaña en segundo plano), revela igualmente
      const safety = setTimeout(() => setT(1), d + 1200);
      const tick = (now) => {
        const k = Math.min(1, (now - t0) / d);
        setT(1 - Math.pow(1 - k, 3));
        if (k < 1) raf = requestAnimationFrame(tick); else clearTimeout(safety);
      };
      raf = requestAnimationFrame(tick);
      return () => { cancelAnimationFrame(raf); clearTimeout(safety); };
    }, [key]);
    return t;
  }

  // interpolador y(nx) sobre la línea
  function makeLineY(pts) {
    return (nx) => {
      const x = nx * VBW;
      if (x <= pts[0].x) return pts[0].y;
      if (x >= pts[pts.length - 1].x) return pts[pts.length - 1].y;
      for (let i = 0; i < pts.length - 1; i++) {
        if (x >= pts[i].x && x <= pts[i + 1].x) {
          const k = (x - pts[i].x) / (pts[i + 1].x - pts[i].x);
          return pts[i].y + (pts[i + 1].y - pts[i].y) * k;
        }
      }
      return pts[pts.length - 1].y;
    };
  }

  /* ====================================================================
     LIENZO DE PARTÍCULAS — motor cinético reutilizable.
     mode: bio | fluid | swarm | spores | bloom | petal | fog | cosmic
     Se dimensiona al contenedor; coordenadas normalizadas [0,1].
     ==================================================================== */
  function ParticleCanvas({ mode, palette, geom, active }) {
    const cvRef = useRef(null);
    const wrapRef = useRef(null);

    useEffect(() => {
      if (!mode || mode === "none") return;
      const cv = cvRef.current, wrap = wrapRef.current;
      if (!cv || !wrap) return;
      const ctx = cv.getContext("2d");
      const reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
      let W = 0, H = 0, dpr = Math.min(2, window.devicePixelRatio || 1);

      const lineY = makeLineY(geom.pts);
      const cols = palette;
      const peaks = geom.peaks.map((i) => ({ nx: geom.pts[i].nx, ny: geom.pts[i].ny }));

      let parts = [];
      let started = performance.now();

      function resize() {
        const r = wrap.getBoundingClientRect();
        W = r.width; H = r.height;
        cv.width = W * dpr; cv.height = H * dpr;
        cv.style.width = W + "px"; cv.style.height = H + "px";
        ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
      }
      const ro = new ResizeObserver(resize); ro.observe(wrap); resize();

      const rnd = (a, b) => a + Math.random() * (b - a);
      const pick = () => cols[(Math.random() * cols.length) | 0];

      function init() {
        parts = [];
        const COUNT = {
          bio: 120, fluid: 90, swarm: geom.pts.length, spores: 110,
          bloom: 0, petal: 70, fog: 7, cosmic: 150,
        }[mode] || 90;

        if (mode === "swarm") {
          geom.pts.forEach((p) => {
            const ang = rnd(0, Math.PI * 2), rad = rnd(0.1, 0.5);
            parts.push({
              tx: p.nx, ty: p.ny,
              x: p.nx + Math.cos(ang) * rad, y: p.ny + Math.sin(ang) * rad * 0.6,
              col: pick(), r: rnd(0.8, 1.8), ph: rnd(0, Math.PI * 2), sp: rnd(0.6, 1.4),
            });
          });
        } else if (mode === "fog") {
          for (let i = 0; i < COUNT; i++) parts.push({ y: rnd(0.15, 1.0), x: rnd(-0.2, 0.2), spd: rnd(0.004, 0.016) * (i % 2 ? 1 : -1), h: rnd(0.12, 0.26), op: rnd(0.05, 0.16), col: pick() });
        } else if (mode === "bloom") {
          // las floraciones se generan en draw a partir de peaks
        } else {
          for (let i = 0; i < COUNT; i++) {
            const onLine = (mode === "bio" || mode === "spores") && Math.random() < 0.55;
            const nx = rnd(0.03, 0.97);
            parts.push({
              x: nx,
              y: onLine ? lineY(nx) / VBH + rnd(-0.02, 0.02) : rnd(0.06, 0.96),
              vx: rnd(-0.0006, 0.0006), vy: rnd(-0.0014, -0.0003),
              r: rnd(0.6, 2.2), col: pick(), a: rnd(0.2, 0.8), ph: rnd(0, Math.PI * 2),
              onLine,
            });
          }
        }
      }
      init();

      let raf;
      function frame(now) {
        const el = (now - started) / 1000;
        ctx.clearRect(0, 0, W, H);
        ctx.globalCompositeOperation = (mode === "fog") ? "source-over" : "lighter";

        if (mode === "swarm") {
          const settle = Math.min(1, el / 2.2);
          parts.forEach((p) => {
            const ease = 1 - Math.pow(1 - settle, 3);
            const jx = Math.sin(now * 0.0008 * p.sp + p.ph) * 0.004 * (1 - ease + 0.18);
            const jy = Math.cos(now * 0.0007 * p.sp + p.ph) * 0.004 * (1 - ease + 0.18);
            p.x += ((p.tx - p.x) * 0.06) ; p.y += ((p.ty - p.y) * 0.06);
            const X = (p.x + jx) * W, Y = (p.y + jy) * H;
            ctx.globalAlpha = 0.5 + 0.4 * ease;
            ctx.fillStyle = p.col;
            ctx.beginPath(); ctx.arc(X, Y, p.r, 0, 7); ctx.fill();
          });
        } else if (mode === "fog") {
          parts.forEach((p) => {
            p.x += p.spd; if (p.x > 1.2) p.x = -0.4; if (p.x < -0.4) p.x = 1.2;
            const g = ctx.createLinearGradient(0, p.y * H, 0, (p.y + p.h) * H);
            g.addColorStop(0, "transparent"); g.addColorStop(0.5, hexA(p.col, p.op)); g.addColorStop(1, "transparent");
            ctx.globalAlpha = 1; ctx.fillStyle = g;
            ctx.fillRect(0, p.y * H, W, p.h * H);
          });
        } else if (mode === "bloom") {
          peaks.forEach((pk, k) => {
            const t0 = (k * 0.5) % 4, lt = (el + t0) % 4, grow = Math.min(1, lt / 1.2);
            const petals = 7, R = (0.02 + grow * 0.05) * H, fade = 1 - Math.max(0, (lt - 2.4) / 1.6);
            if (fade <= 0) return;
            const cx = pk.nx * W, cy = pk.ny * H;
            for (let i = 0; i < petals; i++) {
              const a = (i / petals) * Math.PI * 2 + lt * 0.3;
              const px = cx + Math.cos(a) * R, py = cy + Math.sin(a) * R * 0.9;
              ctx.globalAlpha = 0.5 * fade; ctx.fillStyle = cols[i % cols.length];
              ctx.beginPath(); ctx.arc(px, py, R * 0.5, 0, 7); ctx.fill();
            }
            ctx.globalAlpha = 0.7 * fade; ctx.fillStyle = cols[0];
            ctx.beginPath(); ctx.arc(cx, cy, R * 0.34, 0, 7); ctx.fill();
          });
        } else if (mode === "petal") {
          parts.forEach((p) => {
            p.x += p.vx + Math.sin(now * 0.0006 + p.ph) * 0.0009;
            p.y -= p.vy * 0.8; // cae
            p.y += 0.0016;
            if (p.y > 1.02) { p.y = -0.03; p.x = Math.random(); }
            const X = p.x * W, Y = p.y * H;
            ctx.globalAlpha = p.a * 0.8; ctx.fillStyle = p.col;
            ctx.save(); ctx.translate(X, Y); ctx.rotate(now * 0.0005 + p.ph);
            ctx.beginPath(); ctx.ellipse(0, 0, p.r * 1.6, p.r * 0.7, 0, 0, 7); ctx.fill(); ctx.restore();
          });
        } else {
          // bio · spores · fluid · cosmic
          parts.forEach((p) => {
            p.x += p.vx; p.y += p.vy;
            if (mode === "fluid") { p.x += Math.sin(p.y * 9 + now * 0.0004) * 0.0009; p.y += Math.cos(p.x * 7 + now * 0.0003) * 0.0006; }
            if (p.y < 0.03) { p.y = 0.97; p.x = Math.random(); }
            if (p.x < 0) p.x = 1; if (p.x > 1) p.x = 0;
            const tw = mode === "cosmic" ? (0.4 + 0.6 * Math.abs(Math.sin(now * 0.001 + p.ph))) : 1;
            const X = p.x * W, Y = p.y * H, R = p.r * (mode === "fluid" ? 6 : 1);
            ctx.globalAlpha = p.a * tw * (mode === "fluid" ? 0.16 : 0.85);
            if (mode === "fluid") {
              const g = ctx.createRadialGradient(X, Y, 0, X, Y, R);
              g.addColorStop(0, hexA(p.col, 0.5)); g.addColorStop(1, "transparent");
              ctx.fillStyle = g;
            } else ctx.fillStyle = p.col;
            ctx.beginPath(); ctx.arc(X, Y, R, 0, 7); ctx.fill();
          });
        }
        ctx.globalAlpha = 1;
        if (!reduce) raf = requestAnimationFrame(frame);
      }
      if (reduce) { frame(started + 2200); }
      else raf = requestAnimationFrame(frame);

      return () => { cancelAnimationFrame(raf); ro.disconnect(); };
    }, [mode, active]);

    if (!mode || mode === "none") return null;
    return React.createElement("div", { ref: wrapRef, className: "cat-pcanvas" },
      React.createElement("canvas", { ref: cvRef }));
  }

  function hexA(hex, a) {
    const h = hex.replace("#", "");
    const r = parseInt(h.substring(0, 2), 16), g = parseInt(h.substring(2, 4), 16), b = parseInt(h.substring(4, 6), 16);
    return "rgba(" + r + "," + g + "," + b + "," + a + ")";
  }

  // ---- lectura al hover (dot + valor) compartida, en coords SVG ----
  function HoverReadout({ pt, T, fonts, open }) {
    if (!pt) return null;
    const ch = pt.v - open, pct = (ch / open) * 100, up = ch >= 0;
    const col = up ? T.pos : T.neg;
    const w = 138, h = 50;
    const left = pt.x + w + 18 > VBW ? pt.x - w - 14 : pt.x + 16;
    const top = Math.max(6, pt.y - h - 16);
    return React.createElement("g", { style: { pointerEvents: "none" } },
      React.createElement("line", { x1: pt.x, x2: pt.x, y1: PAD.t - 6, y2: VBH - PAD.b + 6, stroke: T.ink, strokeWidth: 1, opacity: 0.18, strokeDasharray: "1 5" }),
      React.createElement("circle", { cx: pt.x, cy: pt.y, r: 11, fill: T.accent, opacity: 0.16 }),
      React.createElement("circle", { cx: pt.x, cy: pt.y, r: 4.5, fill: T.accent, stroke: T.bg, strokeWidth: 2 }),
      React.createElement("g", { transform: "translate(" + left + "," + top + ")" },
        React.createElement("rect", { width: w, height: h, rx: 9, fill: hexA(T.ink, 0.04), stroke: hexA(T.ink, 0.16), strokeWidth: 1, style: { backdropFilter: "blur(4px)" } }),
        React.createElement("text", { x: 13, y: 19, fontFamily: fonts.mono, fontSize: 11, fill: T.sub, letterSpacing: ".06em" }, pt.t),
        React.createElement("text", { x: w - 13, y: 19, textAnchor: "end", fontFamily: fonts.mono, fontSize: 12, fontWeight: 600, fill: col }, (up ? "+" : "") + pct.toFixed(2) + "%"),
        React.createElement("text", { x: 13, y: 39, fontFamily: fonts.mono, fontSize: 17, fontWeight: 600, fill: T.ink }, pt.v.toLocaleString("es-ES"))
      )
    );
  }

  window.FZCORE = { VBW, VBH, PAD, smoothPath, useGeom, useIntro, makeLineY, ParticleCanvas, HoverReadout, hexA };
})();
