/* FINAZ — Motor de gráfico intradía interactivo.
   Controlado por la app: recibe theme, layers, focusIdx, playing.
   Emite onFocus(idx|null) al hacer scrubbing/hover y onMarker(payload|null). */
(function () {
  "use strict";
  const { useRef, useMemo, useEffect, useState, useCallback } = React;

  // ---- path con 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;
  }

  function FinazChart(props) {
    const { theme, data, width, height, layers, focusIdx, playing, onFocus, onMarker, activeBeat } = props;
    const T = theme.tokens, C = theme.chart;
    const pts = data.points;
    const n = pts.length;
    const svgRef = useRef(null);

    // ---- draw-on de entrada (se reinicia al cambiar de dirección) ----
    const [intro, setIntro] = useState(0);
    useEffect(() => {
      setIntro(0);
      const dur = 1100, t0 = performance.now();
      const iv = setInterval(() => {
        const k = Math.min(1, (performance.now() - t0) / dur);
        setIntro(1 - Math.pow(1 - k, 3));
        if (k >= 1) clearInterval(iv);
      }, 25);
      return () => clearInterval(iv);
    }, [theme.family]);

    // ---- geometría ----
    const padL = 16, padR = 18, padT = 26, padB = layers.volumen ? 64 : 30;
    const plotW = width - padL - padR;
    const plotH = height - padT - padB;
    const vals = pts.map((p) => p.v);
    let yMin = Math.min(...vals), yMax = Math.max(...vals);
    const padV = (yMax - yMin) * 0.18 || 1;
    yMin -= padV; yMax += padV * 0.7;
    const xFor = (i) => padL + (i / (n - 1)) * plotW;
    const yFor = (v) => padT + (1 - (v - yMin) / (yMax - yMin)) * plotH;

    const geom = useMemo(() => pts.map((p, i) => ({ x: xFor(i), y: yFor(p.v) })), [width, height, n, layers.volumen]);
    const linePath = useMemo(() => smoothPath(geom, C.smooth), [geom, C.smooth]);
    const areaPath = useMemo(() => linePath ? linePath + " L" + geom[n - 1].x.toFixed(2) + "," + (padT + plotH) + " L" + geom[0].x.toFixed(2) + "," + (padT + plotH) + " Z" : "", [linePath]);

    // segmento activo (beat resaltado)
    const segPath = useMemo(() => {
      if (!activeBeat) return "";
      const slice = geom.slice(activeBeat.i0, activeBeat.i1 + 1);
      return smoothPath(slice, C.smooth);
    }, [activeBeat, geom, C.smooth]);

    const open = data.meta.open;
    const fIdx = focusIdx == null ? null : Math.max(0, Math.min(n - 1, focusIdx));
    const fp = fIdx == null ? null : pts[fIdx];

    // ---- scrubbing por puntero ----
    const idxFromEvent = useCallback((e) => {
      const r = svgRef.current.getBoundingClientRect();
      const sx = (e.clientX - r.left) * (width / r.width);
      const k = (sx - padL) / plotW;
      return Math.max(0, Math.min(n - 1, Math.round(k * (n - 1))));
    }, [width, plotW, n]);

    const handleMove = useCallback((e) => { onFocus(idxFromEvent(e)); }, [idxFromEvent, onFocus]);
    const handleLeave = useCallback(() => { if (!playing) onFocus(null); onMarker(null); }, [playing, onFocus, onMarker]);

    const grad = "grad-" + theme.id;
    const segGrad = "seg-" + theme.id;
    const glowId = "glow-" + theme.id;
    const clipId = "clip-" + theme.id;
    const reveal = padL + intro * plotW + 2;

    // niveles dentro de rango
    const levels = data.levels.filter((l) => l.value > yMin && l.value < yMax);

    return (
      React.createElement("svg", {
        ref: svgRef, width: "100%", viewBox: "0 0 " + width + " " + height,
        style: { display: "block", touchAction: "none", cursor: "crosshair" },
        onPointerMove: handleMove, onPointerLeave: handleLeave,
        onPointerDown: (e) => { try { e.currentTarget.setPointerCapture(e.pointerId); } catch (err) {} handleMove(e); },
      },
        // defs
        React.createElement("defs", null,
          React.createElement("linearGradient", { id: grad, x1: "0", y1: "0", x2: "0", y2: "1" },
            React.createElement("stop", { offset: "0%", stopColor: T.accent, stopOpacity: C.areaTop }),
            React.createElement("stop", { offset: "100%", stopColor: T.accent, stopOpacity: C.areaBottom })
          ),
          React.createElement("linearGradient", { id: segGrad, x1: "0", y1: "0", x2: "0", y2: "1" },
            React.createElement("stop", { offset: "0%", stopColor: T.accent2, stopOpacity: 0.5 }),
            React.createElement("stop", { offset: "100%", stopColor: T.accent2, stopOpacity: 0 })
          ),
          React.createElement("filter", { id: glowId, x: "-20%", y: "-40%", width: "140%", height: "180%" },
            React.createElement("feGaussianBlur", { stdDeviation: C.glow ? 3.4 : 0, result: "b" }),
            React.createElement("feMerge", null,
              React.createElement("feMergeNode", { in: "b" }),
              React.createElement("feMergeNode", { in: "SourceGraphic" })
            )
          ),
          React.createElement("clipPath", { id: clipId },
            React.createElement("rect", { x: 0, y: 0, width: reveal, height: height })
          )
        ),

        // grid
        gridEls(C.gridStyle, T, padL, padT, plotW, plotH, n, xFor),

        // niveles de observación (capa)
        layers.niveles && levels.map((l) =>
          React.createElement("g", { key: l.id },
            React.createElement("line", {
              x1: padL, x2: padL + plotW, y1: yFor(l.value), y2: yFor(l.value),
              stroke: l.kind === "res" ? T.neg : l.kind === "sup" ? T.accent : T.faint,
              strokeWidth: 1, strokeDasharray: "2 5", opacity: 0.85,
            }),
            React.createElement("rect", {
              x: padL + plotW - 116, y: yFor(l.value) - 9, width: 116, height: 18, rx: C.radius ? 4 : 0,
              fill: T.panel, opacity: 0.9, stroke: T.panelEdge, strokeWidth: 1,
            }),
            React.createElement("text", {
              x: padL + plotW - 108, y: yFor(l.value) + 3.5, fontFamily: theme.fonts.mono,
              fontSize: 9.5, fill: T.sub, letterSpacing: ".02em",
            }, l.label.toUpperCase() + " · " + l.value)
          )
        ),

        // área + línea (con clip de entrada)
        React.createElement("g", { clipPath: "url(#" + clipId + ")" },
          React.createElement("path", { d: areaPath, fill: "url(#" + grad + ")", opacity: activeBeat ? 0.5 : 1 }),
          React.createElement("path", {
            d: linePath, fill: "none", stroke: T.accent, strokeWidth: C.lineWidth,
            strokeLinecap: C.hardEdges ? "butt" : "round", strokeLinejoin: "round",
            opacity: activeBeat ? 0.4 : 1, filter: C.glow ? "url(#" + glowId + ")" : "none",
          }),
          // segmento activo resaltado
          activeBeat && React.createElement("path", {
            d: segPath, fill: "none", stroke: T.accent, strokeWidth: C.lineWidth + 1.4,
            strokeLinecap: "round", strokeLinejoin: "round",
            filter: C.glow ? "url(#" + glowId + ")" : "none",
            style: { transition: "stroke .25s" },
          })
        ),

        // volumen (capa)
        layers.volumen && intro > 0.6 && React.createElement("g", { opacity: 0.9 },
          pts.map((p, i) => {
            const bw = Math.max(1.5, plotW / n - 1.4);
            const bh = p.vol * 30;
            const inSeg = activeBeat && i >= activeBeat.i0 && i <= activeBeat.i1;
            return React.createElement("rect", {
              key: i, x: xFor(i) - bw / 2, y: height - padB + 16, width: bw, height: bh,
              fill: inSeg ? T.accent2 : T.faint, opacity: inSeg ? 0.9 : 0.45, rx: C.radius ? 1 : 0,
            });
          })
        ),

        // marcadores de evento (capa anotaciones)
        layers.anotaciones && intro > 0.85 && data.markers.map((m, i) => {
          const g = geom[m.idx];
          return React.createElement("g", { key: i, style: { cursor: "help" },
            onPointerEnter: (e) => { e.stopPropagation(); onMarker(makePayload(m, data, g, width, height)); onFocus(m.idx); },
          },
            React.createElement("circle", { cx: g.x, cy: g.y, r: 11, fill: "transparent" }),
            React.createElement("circle", { cx: g.x, cy: g.y, r: C.dotR + 3, fill: "none", stroke: m.type === "news" ? T.accent2 : T.accent3, strokeWidth: 1.2, opacity: 0.5,
              style: { transformOrigin: g.x + "px " + g.y + "px" }, className: "finaz-pulse" }),
            React.createElement("circle", { cx: g.x, cy: g.y, r: 3.2, fill: m.type === "news" ? T.accent2 : T.accent3, stroke: T.bg, strokeWidth: 1.4 })
          );
        }),

        // scrubber + readout
        fp != null && intro > 0.95 && React.createElement("g", { style: { pointerEvents: "none" } },
          React.createElement("line", { x1: xFor(fIdx), x2: xFor(fIdx), y1: padT, y2: padT + plotH, stroke: T.ink, strokeWidth: 1, opacity: 0.25, strokeDasharray: "1 4" }),
          React.createElement("circle", { cx: xFor(fIdx), cy: yFor(fp.v), r: C.dotR + 3, fill: T.accent, opacity: 0.18 }),
          React.createElement("circle", { cx: xFor(fIdx), cy: yFor(fp.v), r: C.dotR, fill: T.accent, stroke: T.bg, strokeWidth: 2 }),
          readout(fp, open, xFor(fIdx), yFor(fp.v), width, theme)
        )
      )
    );
  }

  // grid helpers
  function gridEls(style, T, padL, padT, plotW, plotH, n, xFor) {
    const els = [];
    const rows = 4;
    if (style === "lines") {
      for (let r = 0; r <= rows; r++) {
        const y = padT + (r / rows) * plotH;
        els.push(React.createElement("line", { key: "h" + r, x1: padL, x2: padL + plotW, y1: y, y2: y, stroke: T.grid, strokeWidth: 1 }));
      }
    } else if (style === "dots") {
      for (let r = 0; r <= rows; r++) {
        for (let c = 0; c <= 12; c++) {
          els.push(React.createElement("circle", { key: r + "-" + c, cx: padL + (c / 12) * plotW, cy: padT + (r / rows) * plotH, r: 0.9, fill: T.grid }));
        }
      }
    } else { // baseline
      els.push(React.createElement("line", { key: "base", x1: padL, x2: padL + plotW, y1: padT + plotH, y2: padT + plotH, stroke: T.grid, strokeWidth: 1.5 }));
    }
    return React.createElement("g", null, els);
  }

  function readout(p, open, x, y, width, theme) {
    const T = theme.tokens;
    const ch = p.v - open, pct = (ch / open) * 100;
    const up = ch >= 0;
    const col = up ? T.pos : T.neg;
    const w = 122, h = 46;
    const left = x + w + 16 > width ? x - w - 12 : x + 14;
    const top = Math.max(2, y - h - 14);
    return React.createElement("g", { transform: "translate(" + left + "," + top + ")" },
      React.createElement("rect", { width: w, height: h, rx: theme.chart.radius ? 8 : 0, fill: T.panel, stroke: T.panelEdge, strokeWidth: 1 }),
      React.createElement("text", { x: 11, y: 17, fontFamily: theme.fonts.mono, fontSize: 10, fill: T.sub, letterSpacing: ".06em" }, p.t),
      React.createElement("text", { x: w - 11, y: 17, textAnchor: "end", fontFamily: theme.fonts.mono, fontSize: 11, fontWeight: 600, fill: col },
        (up ? "+" : "") + pct.toFixed(2) + "%"),
      React.createElement("text", { x: 11, y: 36, fontFamily: theme.fonts.mono, fontSize: 16, fontWeight: 600, fill: T.ink }, p.v.toLocaleString("es-ES"))
    );
  }

  function makePayload(m, data, g, width, height) {
    if (m.type === "news") {
      const nw = data.news[m.ref];
      return { kind: "news", x: g.x, y: g.y, width, height, source: nw.source, time: nw.time, headline: nw.headline, text: nw.text };
    }
    const gl = data.glossary[m.ref];
    return { kind: "term", x: g.x, y: g.y, width, height, term: gl.term, text: gl.text };
  }

  window.FinazChart = FinazChart;
})();
