/* Tuo v3 — Shared Components */
/* Term tooltip, buttons, cards, FAQ accordion, nav */

const { useState, useRef, useEffect, useCallback, useMemo, createContext, useContext } = React;

// Language context
const LangContext = createContext('en');
const useLang = () => useContext(LangContext);

// ── Term Tooltip with first-occurrence underline ──
function Term({ name, children }) {
  const lang = useLang();
  const [show, setShow] = useState(false);
  const [pos, setPos] = useState({});
  const ref = useRef(null);
  const tipRef = useRef(null);
  const sectionRef = useRef(null);

  // Find parent section for first-occurrence tracking
  useEffect(() => {
    let el = ref.current;
    while (el && !el.dataset.section) el = el.parentElement;
    sectionRef.current = el;
    if (el) {
      const seen = el.dataset.termsSeen ? el.dataset.termsSeen.split('|') : [];
      if (!seen.includes(name) && ref.current) {
        seen.push(name);
        el.dataset.termsSeen = seen.join('|');
        ref.current.classList.add('term-first');
      }
    }
  }, [name]);

  const openTip = useCallback((e) => {
    const rect = ref.current.getBoundingClientRect();
    setPos({
      left: rect.left + rect.width / 2,
      top: rect.top - 8,
    });
    setShow(true);
  }, []);

  const closeTip = useCallback(() => setShow(false), []);

  useEffect(() => {
    if (!show) return;
    const handler = (e) => {
      if (ref.current && !ref.current.contains(e.target) && tipRef.current && !tipRef.current.contains(e.target)) closeTip();
    };
    document.addEventListener('click', handler);
    return () => document.removeEventListener('click', handler);
  }, [show]);

  // Glossary lives at window.TUO_GLOSSARY[lang][name] = { term, def }.
  const entry = window.TUO_GLOSSARY?.[lang]?.[name];
  const tooltipContent = entry
    ? React.createElement(React.Fragment, null,
        React.createElement('strong', null, entry.term),
        React.createElement('p', null, entry.def))
    : React.createElement('p', null, name);

  return React.createElement(React.Fragment, null,
    React.createElement('span', {
      ref, className: 'term-trigger', onMouseEnter: openTip, onMouseLeave: closeTip, onClick: openTip
    }, children),
    show && ReactDOM.createPortal(
      React.createElement('div', {
        ref: tipRef, className: 'term-tooltip',
        // Tooltip is centered above the trigger and uses transform for sub-pixel alignment.
        style: { position: 'fixed', left: pos.left, top: pos.top, transform: 'translate(-50%, -100%)', zIndex: 9999 }
      }, tooltipContent),
      document.body
    )
  );
}

// ── Pill button ──
// `comingSoon` prop disables navigation, swaps cursor, and shows a custom hover tooltip.
function PillBtn({ children, variant = 'primary', href, onClick, style = {}, comingSoon = false }) {
  // Read lang from React context if available (for the tooltip text)
  const lang = (typeof useLang === 'function') ? useLang() : 'en';
  if (comingSoon) {
    const tip = lang === 'es' ? 'Próximamente' : 'Coming soon';
    return React.createElement('a', {
      href: '#',
      onClick: (e) => { e.preventDefault(); },
      className: 'pill-btn pill-' + variant + ' pill-coming-soon',
      style: { ...style, cursor: 'not-allowed' },
      'data-tooltip': tip,
      'aria-label': tip,
      title: tip
    }, children);
  }
  return React.createElement('a', {
    href, onClick, className: 'pill-btn pill-' + variant, style
  }, children);
}

// ── FAQ Accordion ──
function FAQItem({ q, a }) {
  const [open, setOpen] = useState(false);
  return React.createElement('div', { className: 'faq-item' + (open ? ' open' : ''), onClick: () => setOpen(!open) },
    React.createElement('div', { className: 'faq-q' },
      React.createElement('span', null, q),
      React.createElement('span', { className: 'faq-icon' }, open ? '−' : '+')
    ),
    open && React.createElement('div', { className: 'faq-a' }, a)
  );
}

// ── Stat Tile ──
function StatTile({ value, prefix = '', suffix = '', label, format = 'normal' }) {
  return React.createElement('div', { className: 'stat-tile' },
    React.createElement('div', { className: 'stat-tile-num' },
      prefix,
      typeof value === 'number'
        ? format === 'mShort'
          ? (value >= 1e6 ? (value / 1e6).toFixed(2).replace(/\.?0+$/, '') + 'M' : value.toLocaleString())
          : value.toLocaleString()
        : value,
      suffix
    ),
    React.createElement('div', { className: 'stat-tile-lbl' }, label)
  );
}

// ── Product Card ──
function ProductCard({ children, className = '', onMouseMove }) {
  return React.createElement('div', { className: 'product-card ' + className, onMouseMove }, children);
}

// ── Section wrapper ──
function Section({ id, children, className = '' }) {
  return React.createElement('section', { id, className: 'tuo-section ' + className, 'data-section': id }, children);
}

// ──────────────────────────────────────────────────────────────────────
// Equity curve chart (Canvas) — v5
// • weekly granularity, drawdown-aware (cummax), shaded underwater
// • crosshair hover tooltip with NAV / appreciation USD / cum return %
//   / current drawdown
// ──────────────────────────────────────────────────────────────────────

function computeDrawdownSeries(navArr) {
  // Returns:
  //   peak:   cummax up to and including index i
  //   dd:     instant drawdown ratio at index i (0..1, positive = below peak)
  //   runMax: max drawdown of the contiguous underwater "run" that index i belongs to.
  //           If index i is at a new peak, runMax[i] = the depth of the run that JUST
  //           closed (the dip the line just recovered from). This is the value the
  //           shaded region near the cursor is visually showing — it answers
  //           "how deep was that valley?", not just "how deep are we right now?".
  const N = navArr.length;
  if (!N) return { peak: [], dd: [], runMax: [] };
  const peakArr = new Array(N);
  const ddArr = new Array(N);
  const runMaxArr = new Array(N);
  let peak = navArr[0];
  let runMaxDD = 0;
  let runStartIdx = 0;
  peakArr[0] = peak;
  ddArr[0] = 0;
  runMaxArr[0] = 0;

  for (let i = 1; i < N; i++) {
    if (navArr[i] > peak) {
      // New cumulative peak — close out the previous underwater run by writing
      // its final max DD to every index in (runStartIdx, i-1] AND to i itself
      // (the peak day inherits the run that just recovered to it).
      for (let k = runStartIdx + 1; k < i; k++) runMaxArr[k] = runMaxDD;
      runMaxArr[i] = runMaxDD;
      runStartIdx = i;
      runMaxDD = 0;
      peak = navArr[i];
      ddArr[i] = 0;
    } else {
      const dd = (peak - navArr[i]) / peak;
      if (dd > runMaxDD) runMaxDD = dd;
      ddArr[i] = dd;
    }
    peakArr[i] = peak;
  }
  // Close the final (still-open) run with its max
  for (let k = runStartIdx + 1; k < N; k++) {
    if (runMaxArr[k] === undefined) runMaxArr[k] = runMaxDD;
  }
  return { peak: peakArr, dd: ddArr, runMax: runMaxArr };
}

// Distinct red-leaning fills for the underwater shading, indexed by series name.
// Each strategy gets a unique warm/red shade so when multiple are visible the user can
// still tell whose drawdown is whose. Falls back by series index for ad-hoc series.
const DD_COLOR_BY_NAME = {
  'BP Core':       '#e57373', // soft coral red
  'BP Bullish':    '#c0392b', // deep red
  'Dynamic Hedge': '#d83a7e', // magenta red
  'BTC HODL':      '#a92020', // dark red
};
const DD_COLOR_FALLBACK = ['#e57373', '#c0392b', '#d83a7e', '#a92020'];

// Build a small offscreen canvas with diagonal hatching, returned as a CanvasPattern.
// Used as overlay on top of the soft-red fill to suggest a "broken/jagged" texture.
function makeHatchPattern(ctx, hex, alpha = 0.55) {
  const sz = 6;
  const off = document.createElement('canvas');
  off.width = sz; off.height = sz;
  const o = off.getContext('2d');
  const m = hex.replace('#', '');
  const r = parseInt(m.slice(0, 2), 16);
  const g = parseInt(m.slice(2, 4), 16);
  const b = parseInt(m.slice(4, 6), 16);
  o.strokeStyle = `rgba(${r},${g},${b},${alpha})`;
  o.lineWidth = 1;
  o.lineCap = 'square';
  // Two diagonal strokes per tile so the pattern tiles seamlessly
  o.beginPath();
  o.moveTo(-1, sz + 1); o.lineTo(sz + 1, -1);
  o.moveTo(-1, 1);     o.lineTo(1, -1);
  o.moveTo(sz - 1, sz + 1); o.lineTo(sz + 1, sz - 1);
  o.stroke();
  return ctx.createPattern(off, 'repeat');
}

function EquityChart({ series, labels, colors, width = 900, height = 380, initialCapital = 100000, defaultHidden = [] }) {
  const canvasRef = useRef(null);
  const wrapRef = useRef(null);
  const [hover, setHover] = useState(null);
  const lang = useLang();

  // Pre-compute peak + drawdown per series (uses unscaled NAV — ratios are scale-invariant)
  const ddBySeries = useMemo(() => series.map(s => computeDrawdownSeries(s.data)), [series]);

  const pad = { top: 30, right: 20, bottom: 32, left: 70 };
  const w = width - pad.left - pad.right;
  const h = height - pad.top - pad.bottom;

  // ── Viewport state for pan/zoom ──
  const N = labels.length;
  const [view, setView] = useState({ start: 0, end: N - 1 });
  const [dragging, setDragging] = useState(false);
  const dragRef = useRef(null);

  // ── Series visibility (toggle via HTML legend below) ──
  // Initial hidden set comes from the `defaultHidden` prop (array of indices).
  const [hiddenSeries, setHiddenSeries] = useState(() => new Set(defaultHidden));

  // ── Range selection (click-to-mark two points → zoom + metrics panel) ──
  // selection = null  | { firstIdx, secondIdx? }  — both global indices.
  // When secondIdx is set we zoom into that range and show the metrics panel.
  const [selection, setSelection] = useState(null);
  // Reset selection AND viewport (the "zoom out" button).
  const resetView = () => {
    setView({ start: 0, end: N - 1 });
    setSelection(null);
  };
  const isVisible = (si) => !hiddenSeries.has(si);
  const toggleSeries = (si) => setHiddenSeries(prev => {
    const next = new Set(prev);
    if (next.has(si)) next.delete(si); else next.add(si);
    // Don't allow hiding ALL series — keep at least one visible.
    if (next.size >= series.length) return prev;
    return next;
  });

  // Reset viewport when the data array length changes (user changes window/strategy).
  useEffect(() => { setView({ start: 0, end: N - 1 }); }, [N]);
  // Reset visibility to defaults when the SET of series changes.
  // Use series.length as proxy — fine since simulator goes 1↔2 series, performance stays at 4.
  useEffect(() => { setHiddenSeries(new Set(defaultHidden)); }, [series.length]);

  const vStart = Math.max(0, Math.min(view.start, N - 2));
  const vEnd = Math.max(vStart + 1, Math.min(view.end, N - 1));
  const vN = vEnd - vStart + 1;

  // Y range adapts to (a) the visible viewport AND (b) only series that are toggled on.
  // → Hiding BTC HODL when its scale dwarfs the others zooms the Y axis to the strategies' detail.
  const range = useMemo(() => {
    let mn = Infinity, mx = -Infinity;
    for (let si = 0; si < series.length; si++) {
      if (hiddenSeries.has(si)) continue;
      for (let i = vStart; i <= vEnd; i++) {
        const v = series[si].data[i];
        if (v < mn) mn = v;
        if (v > mx) mx = v;
      }
    }
    if (!isFinite(mn) || !isFinite(mx)) return { minV: 0, maxV: 1 };
    const pad = (mx - mn) * 0.05 || mx * 0.05 || 1;
    return { minV: mn - pad, maxV: mx + pad };
  }, [series, vStart, vEnd, hiddenSeries]);

  // X scale maps GLOBAL index → pixel inside the viewport range.
  const xScale = (i) => pad.left + ((i - vStart) / Math.max(1, vN - 1)) * w;
  const yScale = (v) => pad.top + h - ((v - range.minV) / (range.maxV - range.minV)) * h;

  // Hex → rgba helper
  const toRgba = (hex, a) => {
    const m = hex.replace('#', '');
    const r = parseInt(m.slice(0,2), 16), g = parseInt(m.slice(2,4), 16), b = parseInt(m.slice(4,6), 16);
    return `rgba(${r},${g},${b},${a})`;
  };

  // Format helpers (locale-aware)
  const locale = lang === 'en' ? 'en-US' : 'es-419';
  const fmtUsd = (v) => (v < 0 ? '−' : '') + '$' + Math.abs(Math.round(v)).toLocaleString(locale);
  const fmtUsdSigned = (v) => (v >= 0 ? '+' : '−') + '$' + Math.abs(Math.round(v)).toLocaleString(locale);
  const fmtPct = (v, signed = true) => {
    const s = lang === 'es' ? v.toFixed(1).replace('.', ',') : v.toFixed(1);
    return (signed && v >= 0 ? '+' : (v < 0 ? '−' : '')) + (v < 0 ? s.replace('-', '') : s) + '%';
  };
  const fmtDate = (iso) => {
    if (!iso) return '';
    const d = new Date(iso + 'T00:00:00Z');
    if (isNaN(d)) return iso;
    if (lang === 'en') {
      return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric', timeZone: 'UTC' });
    }
    const months = ['ene','feb','mar','abr','may','jun','jul','ago','sep','oct','nov','dic'];
    return d.getUTCDate() + ' ' + months[d.getUTCMonth()] + ' ' + d.getUTCFullYear();
  };

  useEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;
    const ctx = canvas.getContext('2d');
    const dpr = window.devicePixelRatio || 1;
    canvas.width = width * dpr;
    canvas.height = height * dpr;
    ctx.scale(dpr, dpr);
    const { minV, maxV } = range;

    // Clear
    ctx.clearRect(0, 0, width, height);

    // Y grid (horizontal only — vertical grid removed for calmer feel per spec §A.1)
    ctx.strokeStyle = '#1a1a1a';
    ctx.lineWidth = 1;
    const yTicks = 5;
    ctx.font = '11px "Source Code Pro", monospace';
    ctx.textAlign = 'right';
    for (let i = 0; i <= yTicks; i++) {
      const y = pad.top + (i / yTicks) * h;
      ctx.beginPath(); ctx.moveTo(pad.left, y); ctx.lineTo(width - pad.right, y); ctx.stroke();
      const valBase = maxV - (i / yTicks) * (maxV - minV);
      const val = valBase * (initialCapital / 100000);
      ctx.fillStyle = '#9a9a9a';
      const lbl = val >= 1000 ? '$' + Math.round(val / 1000) + 'k' : '$' + Math.round(val);
      ctx.fillText(lbl, pad.left - 8, y + 4);
    }

    // X labels — pick ~6 evenly spaced ticks within the visible viewport
    ctx.textAlign = 'center';
    const xTickCount = Math.min(6, vN);
    for (let k = 0; k < xTickCount; k++) {
      const idx = vStart + Math.round(k * (vN - 1) / (xTickCount - 1));
      const x = xScale(idx);
      const lbl = labels[idx] && labels[idx].length >= 7 ? labels[idx].slice(0, 7) : labels[idx];
      ctx.fillStyle = '#9a9a9a';
      ctx.fillText(lbl, x, height - pad.bottom + 18);
    }

    // Clip everything that follows (drawdown regions + lines) to the chart area,
    // so when the user zooms in, lines don't bleed past the axes.
    ctx.save();
    ctx.beginPath();
    ctx.rect(pad.left, pad.top, w, h);
    ctx.clip();

    // ── Drawdown shaded regions (Option A) ──
    // For each series, render a region between the line and the peak level whenever underwater.
    // The Tuo strategies have small drawdowns (7-13%), so the shaded regions are thin —
    // we use a stronger fill alpha + a faint outline so they remain readable against the dark bg.
    series.forEach((s, si) => {
      if (!isVisible(si)) return;
      const { peak, dd } = ddBySeries[si];
      // Red-leaning shade per series (distinct so multiple visible drawdowns are distinguishable)
      const ddCol = DD_COLOR_BY_NAME[s.name] || DD_COLOR_FALLBACK[si % DD_COLOR_FALLBACK.length];
      const baseFill = toRgba(ddCol, 0.22);
      const hatch = makeHatchPattern(ctx, ddCol, 0.55);
      const edgeStroke = toRgba(ddCol, 0.55);

      // Walk through contiguous underwater stretches and shade each region twice:
      // (1) soft solid red fill as background, (2) diagonal hatch overlay for the "broken" texture.
      let i = 0;
      while (i < s.data.length) {
        if (dd[i] > 0.001) {
          let j = i;
          while (j < s.data.length && dd[j] > 0.001) j++;
          // Build the closed region path once and reuse it for both fills.
          const buildPath = () => {
            ctx.beginPath();
            for (let k = i; k < j; k++) {
              const x = xScale(k), y = yScale(peak[k]);
              k === i ? ctx.moveTo(x, y) : ctx.lineTo(x, y);
            }
            for (let k = j - 1; k >= i; k--) {
              ctx.lineTo(xScale(k), yScale(s.data[k]));
            }
            ctx.closePath();
          };
          // 1. Soft solid fill (background)
          ctx.fillStyle = baseFill;
          buildPath();
          ctx.fill();
          // 2. Diagonal hatch overlay (foreground texture) — gives the "quebrada" look
          ctx.fillStyle = hatch;
          buildPath();
          ctx.fill();
          // 3. Subtle edge stroke so the region reads as enclosed
          ctx.strokeStyle = edgeStroke;
          ctx.lineWidth = 0.8;
          buildPath();
          ctx.stroke();
          i = j;
        } else {
          i++;
        }
      }
    });

    // ── Lines ──
    series.forEach((s, si) => {
      if (!isVisible(si)) return;
      const isBenchmark = si === series.length - 1;

      // Soft glow underneath
      ctx.save();
      ctx.strokeStyle = colors[si];
      ctx.globalAlpha = isBenchmark ? 0.10 : 0.24;
      ctx.lineWidth = isBenchmark ? 5 : 8;
      ctx.filter = 'blur(6px)';
      ctx.beginPath();
      s.data.forEach((v, i) => {
        const x = xScale(i), y = yScale(v);
        i === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y);
      });
      ctx.stroke();
      ctx.restore();

      // Crisp line
      ctx.strokeStyle = colors[si];
      ctx.lineWidth = isBenchmark ? 1.5 : 2.2;
      ctx.globalAlpha = isBenchmark ? 0.7 : 1;
      ctx.setLineDash(isBenchmark ? [4, 3] : []);
      ctx.beginPath();
      s.data.forEach((v, i) => {
        const x = xScale(i), y = yScale(v);
        i === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y);
      });
      ctx.stroke();
      ctx.setLineDash([]);
      ctx.globalAlpha = 1;
    });

    // Restore from the chart-area clip set before the drawdown regions.
    ctx.restore();
    // Note: legend is rendered as HTML below the canvas (clickable to toggle series).
  }, [series, labels, colors, width, height, lang, range, ddBySeries, initialCapital, vStart, vEnd, hiddenSeries]);

  // ── Hover handlers ──
  // Map cursor X (in client coords) to a global data index, respecting the current viewport.
  function pickIdxFromX(clientX) {
    const wrap = wrapRef.current;
    if (!wrap) return null;
    const rect = wrap.getBoundingClientRect();
    const sx = (clientX - rect.left) * (width / rect.width);
    if (sx < pad.left - 4 || sx > pad.left + w + 4) return null;
    const t = (sx - pad.left) / w; // 0..1 within visible chart area
    const globalIdx = vStart + Math.round(t * (vN - 1));
    return { rect, idx: Math.max(vStart, Math.min(vEnd, globalIdx)) };
  }
  function handleMove(e) {
    if (dragging && dragRef.current) {
      // Pan: convert pixel delta into index delta
      const wrap = wrapRef.current;
      const rect = wrap.getBoundingClientRect();
      const pxPerIdx = (rect.width * (w / width)) / Math.max(1, vN - 1);
      const dxPx = e.clientX - dragRef.current.lastX;
      const dxIdx = Math.round(dxPx / pxPerIdx);
      if (dxIdx !== 0) {
        let ns = dragRef.current.startAtDown - dxIdx;
        let ne = dragRef.current.endAtDown - dxIdx;
        if (ns < 0) { ne -= ns; ns = 0; }
        if (ne > N - 1) { ns -= (ne - (N - 1)); ne = N - 1; }
        if (ns < 0) ns = 0;
        setView({ start: ns, end: ne });
      }
      setHover(null);
      return;
    }
    const r = pickIdxFromX(e.clientX);
    if (!r) { setHover(null); return; }
    setHover({ idx: r.idx, mx: e.clientX - r.rect.left, my: e.clientY - r.rect.top, scaleX: r.rect.width / width });
  }
  function handleLeave() { setHover(null); }
  function handleTouch(e) {
    const t = e.touches[0]; if (!t) return;
    const r = pickIdxFromX(t.clientX);
    if (!r) { setHover(null); return; }
    setHover({ idx: r.idx, mx: t.clientX - r.rect.left, my: t.clientY - r.rect.top, scaleX: r.rect.width / width });
  }
  // ── Pan + click-to-mark handlers (inside chart area) ──
  // We disambiguate click vs drag at mouseup time: if the mouse moved < 4px
  // between down and up, it's a click → goes to the range-selection logic.
  // Otherwise it was a drag → already handled by handleMove during the gesture.
  function handleMouseDown(e) {
    const wrap = wrapRef.current; if (!wrap) return;
    const rect = wrap.getBoundingClientRect();
    const sx = (e.clientX - rect.left) * (width / rect.width);
    if (sx < pad.left || sx > pad.left + w) return;
    dragRef.current = {
      lastX: e.clientX, startX: e.clientX,
      startAtDown: vStart, endAtDown: vEnd, moved: false
    };
    setDragging(true);
    setHover(null);
  }
  function handleMouseUp(e) {
    const ref = dragRef.current;
    const wasClick = ref && Math.abs((e?.clientX ?? ref.lastX) - ref.startX) < 4;
    dragRef.current = null;
    setDragging(false);
    if (!wasClick || !e) return;
    // Click — pick the data index under the cursor and feed range selection.
    const r = pickIdxFromX(e.clientX);
    if (!r) return;
    setSelection(prev => {
      if (!prev || prev.secondIdx != null) {
        // Start a fresh selection
        return { firstIdx: r.idx };
      }
      // Second click → finalize range
      const a = Math.min(prev.firstIdx, r.idx);
      const b = Math.max(prev.firstIdx, r.idx);
      // Zoom in to the selected range (with a tiny min span so the chart still renders)
      if (b - a >= 4) {
        setView({ start: a, end: b });
      }
      return { firstIdx: prev.firstIdx, secondIdx: r.idx };
    });
  }
  // Native wheel listener (React's onWheel is passive in React 18 → preventDefault doesn't work).
  // We attach with { passive: false } so scrolling the page doesn't fire while zooming the chart.
  useEffect(() => {
    const wrap = wrapRef.current; if (!wrap) return;
    const onWheel = (e) => {
      const rect = wrap.getBoundingClientRect();
      const sx = (e.clientX - rect.left) * (width / rect.width);
      if (sx < pad.left || sx > pad.left + w) return;
      e.preventDefault();
      const t = (sx - pad.left) / w;
      const anchor = vStart + t * (vN - 1);
      const factor = e.deltaY > 0 ? 1.18 : 1 / 1.18;
      let newSpan = Math.max(8, Math.min(N - 1, Math.round((vN - 1) * factor)));
      let ns = Math.round(anchor - (anchor - vStart) * (newSpan / (vN - 1)));
      let ne = ns + newSpan;
      if (ns < 0) { ne -= ns; ns = 0; }
      if (ne > N - 1) { ns -= (ne - (N - 1)); ne = N - 1; }
      if (ns < 0) ns = 0;
      setView({ start: ns, end: ne });
    };
    wrap.addEventListener('wheel', onWheel, { passive: false });
    return () => wrap.removeEventListener('wheel', onWheel);
  }, [vStart, vEnd, vN, N, width, w, pad.left]);

  // Build tooltip rows (rescale by initialCapital / 100000) — skip hidden series.
  const scale = initialCapital / 100000;
  // Find the BTC HODL series (if present) to derive BTC spot price for any hover index.
  // The tooltip shows the SPOT BTC price as a market-context anchor — independent of capital.
  const btcSeriesIdx = series.findIndex(s => s.name === 'BTC HODL');
  const btcPrice0 = (typeof window !== 'undefined' && window.TUO_PERF_DATA && window.TUO_PERF_DATA.btcPrice0) || null;
  const btcSpotAtHover = (hover && btcSeriesIdx !== -1 && btcPrice0)
    ? btcPrice0 * (series[btcSeriesIdx].data[hover.idx] / 100000)
    : null;

  const rows = hover ? series.map((s, si) => {
    if (!isVisible(si)) return null;
    const navNow = s.data[hover.idx] * scale;
    const nav0 = s.data[0] * scale;
    const appr = navNow - nav0;
    const pct = (navNow / nav0 - 1) * 100;
    const ddPct = ddBySeries[si].dd[hover.idx] * 100;
    const runMaxPct = (ddBySeries[si].runMax[hover.idx] || 0) * 100;
    return { name: s.name, color: colors[si], nav: navNow, appr, pct, dd: ddPct, runMax: runMaxPct };
  }).filter(Boolean) : [];

  const crosshairX = hover ? xScale(hover.idx) * (hover.scaleX || 1) : 0;
  const wrapW = wrapRef.current?.clientWidth || width;
  const tooltipW = 280;
  // Flip the tooltip to the LEFT of the cursor when there's not enough room on the right.
  // Otherwise the tooltip ends up plastered on the right edge, covering the rightmost chart data.
  const tooltipFlip = hover && (hover.mx + tooltipW + 14 > wrapW - 8);
  const tooltipLeft = hover
    ? (tooltipFlip ? Math.max(8, hover.mx - tooltipW - 14) : hover.mx + 14)
    : 0;

  // ── Range-selection metrics ──
  // Computed once a 2-click selection is finalized. Per visible series: CAGR,
  // max DD, final NAV across the [a,b] slice. Plus the elapsed time.
  const selectionMetrics = useMemo(() => {
    if (!selection || selection.secondIdx == null) return null;
    const a = Math.min(selection.firstIdx, selection.secondIdx);
    const b = Math.max(selection.firstIdx, selection.secondIdx);
    if (b - a < 2) return null;
    const startDate = new Date(labels[a] + 'T00:00:00Z');
    const endDate = new Date(labels[b] + 'T00:00:00Z');
    const days = Math.round((endDate - startDate) / 86400000);
    const years = days / 365.25;
    // Calendar-style breakdown (Y / M / D) for human label
    let y = endDate.getUTCFullYear() - startDate.getUTCFullYear();
    let m = endDate.getUTCMonth() - startDate.getUTCMonth();
    let d = endDate.getUTCDate() - startDate.getUTCDate();
    if (d < 0) { m -= 1; d += 30; }
    if (m < 0) { y -= 1; m += 12; }
    // Per visible series
    const perSeries = series.map((s, si) => {
      if (!isVisible(si)) return null;
      const slice = s.data.slice(a, b + 1);
      const navStart = slice[0] * scale;
      const navEnd = slice[slice.length - 1] * scale;
      const cagr = years > 0.05 ? (Math.pow(navEnd / navStart, 1 / years) - 1) * 100 : (navEnd / navStart - 1) * 100;
      // Max DD inside slice (peak-to-trough on this window)
      let peak = slice[0], maxDD = 0;
      for (const v of slice) {
        if (v > peak) peak = v;
        const dd = (peak - v) / peak;
        if (dd > maxDD) maxDD = dd;
      }
      return {
        name: s.name, color: colors[si],
        cagr, maxDD: maxDD * 100, navEnd,
        cagrIsAnnualized: years > 0.05,
      };
    }).filter(Boolean);
    return { startDate, endDate, days, years, y, m, d, perSeries, a, b };
  }, [selection, labels, series, hiddenSeries, colors, scale]);

  // Format the elapsed time as "1y 4m 12d" or "1 año, 4 meses, 12 días"
  const fmtElapsed = (sm) => {
    if (!sm) return '';
    const parts = [];
    if (lang === 'en') {
      if (sm.y) parts.push(sm.y + (sm.y === 1 ? ' year' : ' years'));
      if (sm.m) parts.push(sm.m + (sm.m === 1 ? ' month' : ' months'));
      if (sm.d) parts.push(sm.d + (sm.d === 1 ? ' day' : ' days'));
    } else {
      if (sm.y) parts.push(sm.y + (sm.y === 1 ? ' año' : ' años'));
      if (sm.m) parts.push(sm.m + (sm.m === 1 ? ' mes' : ' meses'));
      if (sm.d) parts.push(sm.d + (sm.d === 1 ? ' día' : ' días'));
    }
    return parts.length ? parts.join(', ') : (lang === 'en' ? '0 days' : '0 días');
  };

  // First-click marker position (during in-flight selection, before second click)
  const firstClickX = (selection && selection.firstIdx != null && selection.secondIdx == null)
    ? xScale(selection.firstIdx) * ((wrapRef.current?.clientWidth || width) / width)
    : null;

  const ddLabel = lang === 'en' ? 'DD' : 'DD';

  return React.createElement('div', {
    ref: wrapRef, className: 'chart-wrap' + (dragging ? ' dragging' : ''), style: { position: 'relative' },
    onMouseMove: handleMove, onMouseLeave: (e) => { handleMouseUp(); handleLeave(e); },
    onMouseDown: handleMouseDown, onMouseUp: handleMouseUp,
    onTouchStart: handleTouch, onTouchMove: handleTouch, onTouchEnd: handleLeave
  },
    React.createElement('canvas', {
      ref: canvasRef,
      // Canvas stretches to fill the chart-wrap. Internal coordinate system stays at
      // `width × height` (props), so all drawing math is unchanged. Mouse events
      // already convert via `rect.width / width` so hover/click work at any display size.
      style: { width: '100%', height: 'auto', aspectRatio: `${width}/${height}`, display: 'block' }
    }),
    // ── Range-selection helper UI ──
    // Reset button (top-right): visible when zoomed in OR mid-selection.
    ((vStart > 0 || vEnd < N - 1) || selection) && React.createElement('button', {
      type: 'button',
      className: 'eq-reset-btn',
      onClick: (e) => { e.stopPropagation(); resetView(); },
      onMouseDown: (e) => e.stopPropagation(), // don't trigger drag
      title: lang === 'en' ? 'Reset zoom (show full range)' : 'Reset zoom (mostrar rango completo)'
    },
      lang === 'en' ? '↻ Reset' : '↻ Reset'
    ),
    // First-click marker (vertical dashed line during in-flight selection)
    firstClickX != null && React.createElement('div', {
      className: 'eq-mark-first',
      style: { position: 'absolute', left: firstClickX, top: 0, bottom: 0, width: 1, background: 'rgba(62,207,142,0.65)', borderLeft: '1px dashed #3ecf8e', pointerEvents: 'none' }
    }),
    // Hint pill that shows "click again to set end" while waiting for second click
    firstClickX != null && React.createElement('div', {
      className: 'eq-mark-hint',
      style: { position: 'absolute', left: firstClickX + 6, top: 8, padding: '3px 8px', background: 'rgba(62,207,142,0.15)', border: '1px solid rgba(62,207,142,0.4)', borderRadius: 4, color: '#b6f5d2', fontFamily: '"Source Code Pro", monospace', fontSize: 10, pointerEvents: 'none', whiteSpace: 'nowrap', zIndex: 6 }
    }, lang === 'en' ? 'Click again to set end →' : 'Click otra vez para fijar el fin →'),
    hover && React.createElement('div', {
      className: 'eq-crosshair',
      style: { position: 'absolute', left: crosshairX, top: 0, bottom: 0, width: 1, background: 'rgba(255,255,255,0.22)', pointerEvents: 'none' }
    }),
    hover && React.createElement('div', {
      className: 'eq-tooltip',
      style: {
        position: 'absolute',
        left: Math.max(8, tooltipLeft),
        top: Math.max(8, hover.my - 12),
        background: '#0d0d0d',
        border: '1px solid #2a2a2a',
        borderRadius: 6,
        padding: '10px 12px',
        fontFamily: '"Plus Jakarta Sans", sans-serif',
        fontSize: 12,
        color: '#fafafa',
        pointerEvents: 'none',
        width: tooltipW,
        boxShadow: '0 8px 24px rgba(0,0,0,0.4)',
        zIndex: 5
      }
    },
      // Date header + BTC spot price for context. The BTC price line is rendered as a
      // small subtitle (independent of which series the user is hovering on) — it gives
      // market context: "what was BTC trading at on this date?".
      React.createElement('div', {
        style: { display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', gap: 10, marginBottom: 8, paddingBottom: 6, borderBottom: '1px solid #1f1f1f' }
      },
        React.createElement('span', {
          style: { color: '#898989', fontFamily: '"Source Code Pro", monospace', fontSize: 11 }
        }, fmtDate(labels[hover.idx])),
        btcSpotAtHover != null && React.createElement('span', {
          style: { color: '#f7931a', fontFamily: '"Source Code Pro", monospace', fontSize: 10, opacity: 0.85 }
        }, 'BTC ' + fmtUsd(btcSpotAtHover))
      ),
      ...rows.map((r, i) => {
        // Drawdown display — show both:
        //   • DD now: instant DD vs cummax (matches the line's distance from current peak)
        //   • Worst: max DD reached in the underwater run this day belongs to
        //            (matches the depth of the visible shaded region near the cursor)
        const fmtDDValue = (v) => {
          if (v <= 0) return lang === 'es' ? '0,00%' : '0.00%';
          const decimals = v < 1 ? 2 : 1;
          const num = v.toFixed(decimals);
          return '−' + (lang === 'es' ? num.replace('.', ',') : num) + '%';
        };
        const ddTxt = fmtDDValue(r.dd);
        const runMaxTxt = fmtDDValue(r.runMax);
        // Only show "worst" line if the run actually had a meaningful drawdown
        const showWorst = r.runMax > 0.05;
        return React.createElement('div', { key: i, style: { display: 'flex', alignItems: 'flex-start', gap: 8, padding: '4px 0' } },
          React.createElement('span', { style: { width: 8, height: 8, borderRadius: '50%', background: r.color, flexShrink: 0, marginTop: 4 } }),
          React.createElement('div', { style: { flex: 1, minWidth: 0 } },
            React.createElement('div', { style: { color: '#bdbdbd', fontSize: 11, marginBottom: 1 } }, r.name),
            React.createElement('div', { style: { fontFamily: '"Source Code Pro", monospace', fontSize: 10, color: '#5e5e5e' } },
              fmtPct(r.pct) + ' · DD ' + ddTxt + (showWorst ? ((lang === 'es' ? ' · peor ' : ' · worst ') + runMaxTxt) : '')
            )
          ),
          React.createElement('div', { style: { textAlign: 'right' } },
            React.createElement('div', { style: { fontFamily: '"Source Code Pro", monospace', fontWeight: 600 } }, fmtUsd(r.nav)),
            React.createElement('div', { style: { fontFamily: '"Source Code Pro", monospace', fontSize: 10, color: r.appr >= 0 ? '#3ecf8e' : '#e57373' } }, fmtUsdSigned(r.appr))
          )
        );
      })
    ),
    // ── Range-selection metrics panel (only when 2-click selection finalized) ──
    selectionMetrics && React.createElement('div', { className: 'eq-range-panel' },
      React.createElement('div', { className: 'eq-range-head' },
        React.createElement('div', null,
          React.createElement('div', { className: 'eq-range-title' },
            (lang === 'en' ? 'Selected range' : 'Rango seleccionado')
          ),
          React.createElement('div', { className: 'eq-range-dates' },
            fmtDate(labels[selectionMetrics.a]) + '  →  ' + fmtDate(labels[selectionMetrics.b])
          )
        ),
        React.createElement('div', { className: 'eq-range-elapsed' },
          React.createElement('span', { className: 'eq-range-elapsed-lbl' },
            lang === 'en' ? 'Elapsed' : 'Transcurrido'
          ),
          React.createElement('span', { className: 'eq-range-elapsed-val' },
            fmtElapsed(selectionMetrics)
          )
        ),
        React.createElement('button', {
          type: 'button', className: 'eq-range-clear',
          onClick: (e) => { e.stopPropagation(); resetView(); },
          onMouseDown: (e) => e.stopPropagation(),
          title: lang === 'en' ? 'Clear selection and reset zoom' : 'Limpiar selección y resetear zoom'
        }, '×')
      ),
      React.createElement('table', { className: 'eq-range-table' },
        React.createElement('thead', null,
          React.createElement('tr', null,
            React.createElement('th', null, ''),
            React.createElement('th', null, lang === 'en' ? 'Series' : 'Estrategia'),
            React.createElement('th', { style: { textAlign: 'right' } }, 'CAGR'),
            React.createElement('th', { style: { textAlign: 'right' } }, lang === 'en' ? 'Max DD' : 'DD máx.'),
            React.createElement('th', { style: { textAlign: 'right' } }, lang === 'en' ? 'Final NAV' : 'NAV final')
          )
        ),
        React.createElement('tbody', null,
          selectionMetrics.perSeries.map((row, i) => React.createElement('tr', { key: i },
            React.createElement('td', null,
              React.createElement('span', { className: 'eq-range-dot', style: { background: row.color } })
            ),
            React.createElement('td', { className: 'eq-range-name' }, row.name),
            React.createElement('td', { className: 'eq-range-num', style: { color: row.cagr >= 0 ? '#3ecf8e' : '#e57373' } },
              (row.cagr >= 0 ? '+' : '') + (lang === 'es' ? row.cagr.toFixed(2).replace('.', ',') : row.cagr.toFixed(2)) + '%' +
              (row.cagrIsAnnualized ? '' : (lang === 'en' ? ' (period)' : ' (período)'))
            ),
            React.createElement('td', { className: 'eq-range-num' },
              row.maxDD <= 0 ? (lang === 'es' ? '0,00%' : '0.00%')
                : '−' + (lang === 'es' ? row.maxDD.toFixed(2).replace('.', ',') : row.maxDD.toFixed(2)) + '%'
            ),
            React.createElement('td', { className: 'eq-range-num' }, fmtUsd(row.navEnd))
          ))
        )
      )
    ),
    // ── HTML legend (clickable to toggle each series on/off) ──
    React.createElement('div', { className: 'eq-legend' },
      series.map((s, si) => React.createElement('button', {
        key: si,
        type: 'button',
        className: 'eq-legend-item' + (hiddenSeries.has(si) ? ' off' : ''),
        onClick: (e) => { e.stopPropagation(); toggleSeries(si); },
        onMouseDown: (e) => e.stopPropagation(), // don't trigger chart drag
        title: hiddenSeries.has(si)
          ? (lang === 'en' ? `Show ${s.name}` : `Mostrar ${s.name}`)
          : (lang === 'en' ? `Hide ${s.name}` : `Ocultar ${s.name}`),
        style: { '--c': colors[si] }
      },
        React.createElement('span', { className: 'eq-legend-dot' }),
        React.createElement('span', { className: 'eq-legend-name' }, s.name)
      ))
    )
  );
}

Object.assign(window, { LangContext, useLang, Term, PillBtn, FAQItem, StatTile, ProductCard, Section, EquityChart, computeDrawdownSeries });
