/* ChartCanvas — motor de gráficos OHLCV para Finaz C03-A
 *
 * Híbrido Canvas (velas + volumen + ejes) + SVG (overlay de drawings) para:
 *   - rendering rápido de cientos de barras
 *   - drawings interactivos con click/drag preciso
 *
 * API:
 *   <ChartCanvas
 *     bars              // OHLCV
 *     width height
 *     visibleBars       // bar replay: solo render bars[0..visibleBars-1]
 *     anonymized        // eje X "Barra N" vs fechas
 *     showVolume        // pane inferior (default true)
 *     drawings          // [{type, points, state, ...}]
 *     activeTool        // 'pivot' | 'trendline' | 'horizontal' | 'channel' | 'annotation' | null
 *     onCommitDrawing   // (drawing) => void
 *     pivotMarkers      // [{index, label}]  HH/HL/LH/LL etiquetas
 *     markers           // [{index, kind, label}]  ChoCh, BoS, etc.
 *     tfBadge           // 'monthly'|'weekly'|'daily' borde superior
 *     ticker            // si null/ANON_X mostramos anónimo
 *     watermark         // texto custom o usa el default
 *   />
 *
 * Coords lógicas: x = índice de barra (0..n-1), y = precio.
 * Helpers expuestos por instancia (vía useImperativeHandle): logicToScreen(x,y), screenToLogic(px,py).
 */

const ChartCanvas = React.forwardRef(function ChartCanvas(props, ref) {
  const {
    bars,
    width = 880,
    height = 500,
    visibleBars,
    anonymized = true,
    showVolume = true,
    drawings = [],
    activeTool = null,
    onCommitDrawing,
    onCommitTrendline, // legacy alias
    pivotMarkers = [],
    markers = [],
    tfBadge,
    ticker,
    watermark = 'Educativo · No es señal de inversión · Datos curados',
    showWatermark = true,
    padding = { top: 18, right: 64, bottom: 36, left: 14 },
    volumeRatio = 0.22,
    candleColors,
    revealUpTo, // si está, las barras a partir de aquí se renderizan en outline (modo reveal-after-commit)
    rangeOverlay, // {fromIndex, toIndex, color} — banda vertical de énfasis
    onHover,
    crosshairAt, // { index, price } — para sincronizar multi-TF
    extraOverlay, // SVG extra para custom rendering
    style = {},
    children,
  } = props;

  const canvasRef = React.useRef(null);
  const svgRef = React.useRef(null);
  const [hover, setHover] = React.useState(null); // {x,y, idx, bar}
  const [pendingDrawing, setPendingDrawing] = React.useState(null); // dibujo en curso

  const colors = {
    bg: 'var(--chart-bg)',
    grid: 'var(--chart-grid)',
    axis: 'var(--chart-axis)',
    up: '#5BC68F',
    down: '#D85C5C',
    doji: '#A2A9B4',
    wick: '#6B7280',
    volUp: 'rgba(91, 198, 143, 0.4)',
    volDown: 'rgba(216, 92, 92, 0.4)',
    text: '#A2A9B4',
    textMuted: '#6B7280',
    ...candleColors,
  };

  // Visible slice: para bar replay forward-only
  const sliceEnd = visibleBars != null ? Math.min(visibleBars, bars.length) : bars.length;
  const visible = bars.slice(0, sliceEnd);

  // Range escala visible: incluye reveal si está
  const allBarsForScale = revealUpTo != null ? bars.slice(0, revealUpTo) : visible;
  const priceMin = Math.min(...allBarsForScale.map((b) => b.l));
  const priceMax = Math.max(...allBarsForScale.map((b) => b.h));
  const priceRange = priceMax - priceMin || 1;
  const pricePad = priceRange * 0.05;
  const yMin = priceMin - pricePad;
  const yMax = priceMax + pricePad;

  const volMax = Math.max(...allBarsForScale.map((b) => b.v)) || 1;

  // Geometría
  const totalBars = revealUpTo != null ? revealUpTo : bars.length;
  const priceHeight = showVolume ? height - padding.top - padding.bottom - height * volumeRatio : height - padding.top - padding.bottom;
  const volHeight = showVolume ? height * volumeRatio : 0;
  const plotWidth = width - padding.left - padding.right;
  const barWidth = plotWidth / Math.max(totalBars, 1);
  const candleW = Math.max(1.5, barWidth * 0.7);

  // Helpers de coordenadas
  function xOf(i) { return padding.left + i * barWidth + barWidth / 2; }
  function yOf(price) {
    return padding.top + ((yMax - price) / (yMax - yMin)) * priceHeight;
  }
  function xToIndex(px) { return Math.floor((px - padding.left) / barWidth); }
  function yToPrice(py) {
    return yMax - ((py - padding.top) / priceHeight) * (yMax - yMin);
  }
  function yVolOf(v) {
    const top = padding.top + priceHeight + 8;
    return top + (1 - v / volMax) * (volHeight - 8);
  }

  React.useImperativeHandle(ref, () => ({ xOf, yOf, xToIndex, yToPrice }), [width, height, bars.length]);

  // Render canvas
  React.useEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;
    const dpr = window.devicePixelRatio || 1;
    canvas.width = width * dpr;
    canvas.height = height * dpr;
    canvas.style.width = width + 'px';
    canvas.style.height = height + 'px';
    const ctx = canvas.getContext('2d');
    ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
    ctx.clearRect(0, 0, width, height);

    // Fondo
    ctx.fillStyle = getCss('--chart-bg', '#0A0C10');
    ctx.fillRect(0, 0, width, height);

    // Grid horizontal precio
    const gridColor = getCss('--chart-grid', '#1A1D23');
    ctx.strokeStyle = gridColor;
    ctx.lineWidth = 1;
    const priceTicks = 5;
    ctx.font = '10px JetBrains Mono, monospace';
    ctx.fillStyle = colors.textMuted;
    ctx.textAlign = 'left';
    ctx.textBaseline = 'middle';
    for (let i = 0; i <= priceTicks; i++) {
      const p = yMin + (yMax - yMin) * (i / priceTicks);
      const y = yOf(p);
      ctx.beginPath();
      ctx.moveTo(padding.left, y);
      ctx.lineTo(width - padding.right, y);
      ctx.stroke();
      ctx.fillText(p.toFixed(2), width - padding.right + 6, y);
    }

    // Grid vertical tiempo
    const timeTicks = Math.min(8, totalBars);
    ctx.textAlign = 'center';
    ctx.textBaseline = 'top';
    for (let i = 0; i <= timeTicks; i++) {
      const barIdx = Math.floor((i / timeTicks) * (totalBars - 1));
      const x = xOf(barIdx);
      ctx.beginPath();
      ctx.moveTo(x, padding.top);
      ctx.lineTo(x, padding.top + priceHeight);
      ctx.stroke();
      const label = anonymized ? `B${barIdx}` : barIdx.toString();
      ctx.fillText(label, x, height - padding.bottom + 6);
    }

    // Range overlay (énfasis)
    if (rangeOverlay) {
      ctx.fillStyle = rangeOverlay.color || 'rgba(240,184,110,0.07)';
      const x1 = xOf(rangeOverlay.fromIndex) - barWidth / 2;
      const x2 = xOf(rangeOverlay.toIndex) + barWidth / 2;
      ctx.fillRect(x1, padding.top, x2 - x1, priceHeight);
    }

    // Velas
    for (let i = 0; i < visible.length; i++) {
      const b = visible[i];
      const x = xOf(i);
      const isUp = b.c >= b.o;
      const color = b.c === b.o ? colors.doji : isUp ? colors.up : colors.down;

      // mecha
      ctx.strokeStyle = colors.wick;
      ctx.lineWidth = 1;
      ctx.beginPath();
      ctx.moveTo(x, yOf(b.h));
      ctx.lineTo(x, yOf(b.l));
      ctx.stroke();

      // cuerpo
      const yo = yOf(b.o);
      const yc = yOf(b.c);
      const top = Math.min(yo, yc);
      const bot = Math.max(yo, yc);
      ctx.fillStyle = color;
      ctx.fillRect(x - candleW / 2, top, candleW, Math.max(1, bot - top));
    }

    // Velas reveal (modo outline)
    if (revealUpTo != null && revealUpTo > sliceEnd) {
      for (let i = sliceEnd; i < revealUpTo; i++) {
        const b = bars[i];
        const x = xOf(i);
        const isUp = b.c >= b.o;
        const color = isUp ? colors.up : colors.down;
        ctx.strokeStyle = color;
        ctx.lineWidth = 1;
        // mecha
        ctx.beginPath();
        ctx.moveTo(x, yOf(b.h));
        ctx.lineTo(x, yOf(b.l));
        ctx.stroke();
        // cuerpo outline
        const yo = yOf(b.o), yc = yOf(b.c);
        const top = Math.min(yo, yc), bot = Math.max(yo, yc);
        ctx.strokeRect(x - candleW / 2, top, candleW, Math.max(1, bot - top));
      }
    }

    // Volumen
    if (showVolume) {
      // separador
      ctx.strokeStyle = gridColor;
      ctx.beginPath();
      ctx.moveTo(padding.left, padding.top + priceHeight + 4);
      ctx.lineTo(width - padding.right, padding.top + priceHeight + 4);
      ctx.stroke();

      for (let i = 0; i < visible.length; i++) {
        const b = visible[i];
        const x = xOf(i);
        const isUp = b.c >= b.o;
        ctx.fillStyle = isUp ? colors.volUp : colors.volDown;
        const y = yVolOf(b.v);
        const h = padding.top + priceHeight + volHeight - y;
        ctx.fillRect(x - candleW / 2, y, candleW, Math.max(1, h));
      }
    }

    // Watermark
    if (showWatermark) {
      ctx.fillStyle = colors.textMuted;
      ctx.globalAlpha = 0.55;
      ctx.font = '9px JetBrains Mono, monospace';
      ctx.textAlign = 'right';
      ctx.textBaseline = 'bottom';
      ctx.fillText(watermark, width - padding.right - 4, padding.top + priceHeight - 4);
      ctx.globalAlpha = 1;
    }

    // Borde TF
    if (tfBadge) {
      const tfColor = tfBadge === 'monthly' ? '#C6A1FF'
        : tfBadge === 'weekly' ? '#6BA3FF'
          : '#F0B86E';
      ctx.fillStyle = tfColor;
      ctx.fillRect(0, 0, width, 3);
    }
  }, [bars, visibleBars, anonymized, showVolume, width, height, revealUpTo, rangeOverlay, tfBadge]);

  // Helper para leer CSS var con fallback (en contexto canvas)
  function getCss(name, fallback) {
    try {
      const v = getComputedStyle(document.documentElement).getPropertyValue(name).trim();
      return v || fallback;
    } catch { return fallback; }
  }

  // Mouse handlers
  function onMouseMove(e) {
    const rect = svgRef.current.getBoundingClientRect();
    const px = e.clientX - rect.left;
    const py = e.clientY - rect.top;
    if (px < padding.left || px > width - padding.right || py < padding.top || py > padding.top + priceHeight) {
      setHover(null);
      if (onHover) onHover(null);
      return;
    }
    const idx = xToIndex(px);
    if (idx < 0 || idx >= visible.length) {
      setHover(null);
      return;
    }
    const bar = visible[idx];
    const price = yToPrice(py);
    setHover({ x: px, y: py, idx, bar, price });
    if (onHover) onHover({ idx, bar, price });

    // Si hay un trendline pendiente con 1 punto, hacemos preview
    if (pendingDrawing && pendingDrawing.type === 'trendline' && pendingDrawing.points.length === 1) {
      setPendingDrawing({ ...pendingDrawing, hoverPoint: { x: idx, y: price } });
    }
  }

  function onMouseLeave() { setHover(null); if (onHover) onHover(null); }

  function onClick(e) {
    if (!activeTool) return;
    const rect = svgRef.current.getBoundingClientRect();
    const px = e.clientX - rect.left;
    const py = e.clientY - rect.top;
    if (px < padding.left || px > width - padding.right || py < padding.top || py > padding.top + priceHeight) return;
    const idx = xToIndex(px);
    if (idx < 0 || idx >= visible.length) return;

    if (activeTool === 'pivot') {
      const bar = visible[idx];
      // Snap a high o low más cercano dentro de la vela
      const price = yToPrice(py);
      const kind = Math.abs(price - bar.h) < Math.abs(price - bar.l) ? 'high' : 'low';
      const snappedPrice = kind === 'high' ? bar.h : bar.l;
      // Validar pivot: ¿N barras a cada lado sin superar?
      const w = 5;
      let valid = idx >= w && idx < bars.length - w;
      if (valid) {
        for (let k = 1; k <= w; k++) {
          if (kind === 'high' && (bars[idx - k].h >= bar.h || bars[idx + k].h >= bar.h)) { valid = false; break; }
          if (kind === 'low' && (bars[idx - k].l <= bar.l || bars[idx + k].l <= bar.l)) { valid = false; break; }
        }
      }
      onCommitDrawing && onCommitDrawing({
        type: 'pivot',
        index: idx,
        kind,
        price: snappedPrice,
        state: valid ? 'valid' : 'invalid',
      });
      return;
    }

    if (activeTool === 'trendline' || activeTool === 'channel') {
      // Snap al pivot más cercano (vela): low si click bajo precio medio, high si arriba
      const bar = visible[idx];
      const price = yToPrice(py);
      const snapped = Math.abs(price - bar.h) < Math.abs(price - bar.l) ? bar.h : bar.l;
      const point = { x: idx, y: snapped };

      if (!pendingDrawing) {
        setPendingDrawing({ type: activeTool, points: [point], hoverPoint: null });
      } else if (pendingDrawing.points.length === 1) {
        const draft = { type: activeTool, points: [...pendingDrawing.points, point] };
        const { touches, touchedIndices } = window.synthetic.countTouches(bars, draft.points[0], draft.points[1]);
        const state = touches >= 3 ? 'valid' : 'pending';
        const finalDrawing = { ...draft, touches, touchedIndices, state };
        onCommitDrawing && onCommitDrawing(finalDrawing);
        setPendingDrawing(null);
      }
      return;
    }

    if (activeTool === 'horizontal-support' || activeTool === 'horizontal-resistance') {
      const bar = visible[idx];
      const price = yToPrice(py);
      const snapped = Math.abs(price - bar.h) < Math.abs(price - bar.l) ? bar.h : bar.l;
      onCommitDrawing && onCommitDrawing({
        type: 'horizontal',
        kind: activeTool === 'horizontal-support' ? 'support' : 'resistance',
        price: snapped,
        state: 'valid',
      });
      return;
    }

    if (activeTool === 'annotation') {
      const price = yToPrice(py);
      onCommitDrawing && onCommitDrawing({
        type: 'annotation',
        index: idx,
        price,
        text: 'Nota',
        state: 'valid',
      });
      return;
    }
  }

  function onKeyDown(e) {
    if (e.key === 'Escape' && pendingDrawing) {
      setPendingDrawing(null);
    }
  }

  // Cursor por tool
  const cursor = activeTool ? 'crosshair' : 'default';

  // Pivot labels (HH/HL/LH/LL) calculados desde markers prop
  const pivotLabelColor = (label) => {
    if (label === 'HH') return 'var(--tool-pivot-hh)';
    if (label === 'HL') return 'var(--tool-pivot-hl)';
    if (label === 'LH') return 'var(--tool-pivot-lh)';
    if (label === 'LL') return 'var(--tool-pivot-ll)';
    return 'var(--text-secondary)';
  };

  return (
    <div className="chart-canvas-root" style={{ position: 'relative', width, height, ...style }} onKeyDown={onKeyDown} tabIndex={-1}>
      <canvas ref={canvasRef} style={{ display: 'block', position: 'absolute', inset: 0 }} />
      <svg
        ref={svgRef}
        width={width}
        height={height}
        style={{ position: 'absolute', inset: 0, cursor }}
        onMouseMove={onMouseMove}
        onMouseLeave={onMouseLeave}
        onClick={onClick}
      >
        {/* Drawings */}
        <DrawingsLayer drawings={drawings} xOf={xOf} yOf={yOf} visibleBars={sliceEnd} totalWidth={width - padding.right} padLeft={padding.left} priceTop={padding.top} priceBottom={padding.top + priceHeight} />

        {/* Pivot markers HH/HL/LH/LL */}
        {pivotMarkers.map((m, i) => {
          if (m.index >= sliceEnd) return null;
          const cx = xOf(m.index);
          const cy = yOf(m.price);
          const isHigh = m.kind === 'high';
          const labelY = isHigh ? cy - 16 : cy + 22;
          return (
            <g key={`pm-${i}`}>
              <circle cx={cx} cy={cy} r={3} fill={pivotLabelColor(m.label)} stroke="var(--chart-bg)" strokeWidth={1.5} />
              <rect x={cx - 14} y={labelY - 9} width={28} height={14} rx={2} fill="var(--surface-1)" stroke={pivotLabelColor(m.label)} strokeOpacity={0.5} />
              <text x={cx} y={labelY + 1} textAnchor="middle" fontSize={9} fontWeight={700} fontFamily="IBM Plex Sans, sans-serif" fill={pivotLabelColor(m.label)}>{m.label}</text>
            </g>
          );
        })}

        {/* Markers genéricos (ChoCh, BoS, etc.) */}
        {markers.map((m, i) => {
          if (m.index >= sliceEnd) return null;
          const cx = xOf(m.index);
          const cy = yOf(m.price);
          const color = m.kind === 'choch' ? 'var(--tool-choch)' : m.kind === 'bos' ? 'var(--tool-bos)' : 'var(--text-secondary)';
          return (
            <g key={`mk-${i}`}>
              <line x1={cx} y1={padding.top} x2={cx} y2={padding.top + priceHeight} stroke={color} strokeWidth={1} strokeDasharray="3 3" opacity={0.4} />
              <rect x={cx - 22} y={cy - 28} width={44} height={16} rx={2} fill={color} />
              <text x={cx} y={cy - 17} textAnchor="middle" fontSize={9} fontWeight={700} fontFamily="IBM Plex Sans, sans-serif" fill="#0A0C10" letterSpacing="0.05em">{m.kind.toUpperCase()}</text>
            </g>
          );
        })}

        {/* Pending drawing (preview en curso) */}
        {pendingDrawing && pendingDrawing.points.length === 1 && pendingDrawing.hoverPoint && (
          <line
            x1={xOf(pendingDrawing.points[0].x)}
            y1={yOf(pendingDrawing.points[0].y)}
            x2={xOf(pendingDrawing.hoverPoint.x)}
            y2={yOf(pendingDrawing.hoverPoint.y)}
            stroke="var(--tool-trendline)"
            strokeWidth={1.5}
            strokeDasharray="4 4"
            opacity={0.7}
          />
        )}
        {pendingDrawing && pendingDrawing.points.map((p, i) => (
          <circle key={`pp-${i}`} cx={xOf(p.x)} cy={yOf(p.y)} r={4} fill="none" stroke="var(--tool-trendline)" strokeWidth={1.5} />
        ))}

        {/* Crosshair externo (sync multi-TF) */}
        {crosshairAt && crosshairAt.index != null && crosshairAt.index < sliceEnd && (
          <g pointerEvents="none">
            <line x1={xOf(crosshairAt.index)} y1={padding.top} x2={xOf(crosshairAt.index)} y2={padding.top + priceHeight} stroke="var(--border-emphasis)" strokeWidth={1} strokeDasharray="3 3" />
          </g>
        )}

        {/* Crosshair propio */}
        {hover && (
          <g pointerEvents="none">
            <line x1={hover.x} y1={padding.top} x2={hover.x} y2={padding.top + priceHeight} stroke="var(--border-strong)" strokeWidth={1} strokeDasharray="2 3" opacity={0.6} />
            <line x1={padding.left} y1={hover.y} x2={width - padding.right} y2={hover.y} stroke="var(--border-strong)" strokeWidth={1} strokeDasharray="2 3" opacity={0.6} />
            {/* Tag de precio */}
            <rect x={width - padding.right + 2} y={hover.y - 8} width={padding.right - 4} height={16} fill="var(--surface-3)" rx={2} />
            <text x={width - padding.right + 6} y={hover.y + 3} fontSize={10} fontFamily="JetBrains Mono, monospace" fill="var(--text-primary)">{hover.price.toFixed(2)}</text>
          </g>
        )}

        {extraOverlay}
      </svg>

      {/* Ticker / TF label superior */}
      {(ticker || tfBadge) && (
        <div style={{
          position: 'absolute', top: 8, left: padding.left + 4, display: 'flex', gap: 10, alignItems: 'center',
          fontFamily: 'var(--font-sans)', fontSize: 11, fontWeight: 600, letterSpacing: '0.04em',
          color: 'var(--text-secondary)', pointerEvents: 'none', textTransform: 'uppercase',
        }}>
          {tfBadge && (
            <span style={{
              padding: '2px 6px', borderRadius: 2,
              background: tfBadge === 'monthly' ? 'rgba(198,161,255,0.15)' : tfBadge === 'weekly' ? 'rgba(107,163,255,0.15)' : 'rgba(240,184,110,0.15)',
              color: tfBadge === 'monthly' ? '#C6A1FF' : tfBadge === 'weekly' ? '#6BA3FF' : '#F0B86E',
            }}>{tfBadge}</span>
          )}
          {ticker && <span>{ticker}</span>}
        </div>
      )}

      {children}
    </div>
  );
});

/* Capa SVG de drawings — render por tipo */
function DrawingsLayer({ drawings, xOf, yOf, visibleBars, totalWidth, padLeft, priceTop, priceBottom }) {
  return (
    <g>
      {drawings.map((d, i) => {
        if (d.hidden) return null;
        if (d.type === 'pivot') return <PivotMark key={i} d={d} xOf={xOf} yOf={yOf} />;
        if (d.type === 'trendline') return <Trendline key={i} d={d} xOf={xOf} yOf={yOf} extendTo={totalWidth} padLeft={padLeft} priceTop={priceTop} priceBottom={priceBottom} />;
        if (d.type === 'channel') return <Trendline key={i} d={d} xOf={xOf} yOf={yOf} extendTo={totalWidth} padLeft={padLeft} priceTop={priceTop} priceBottom={priceBottom} />;
        if (d.type === 'horizontal') return <HorizontalLevel key={i} d={d} yOf={yOf} extendTo={totalWidth} padLeft={padLeft} />;
        if (d.type === 'annotation') return <Annotation key={i} d={d} xOf={xOf} yOf={yOf} />;
        if (d.type === 'zone') return <ZoneBand key={i} d={d} yOf={yOf} extendTo={totalWidth} padLeft={padLeft} />;
        return null;
      })}
    </g>
  );
}

function PivotMark({ d, xOf, yOf }) {
  const cx = xOf(d.index), cy = yOf(d.price);
  const color = d.state === 'invalid' ? 'var(--signal-warning)' : d.kind === 'high' ? 'var(--tool-pivot-hh)' : 'var(--tool-pivot-ll)';
  return (
    <g>
      <circle cx={cx} cy={cy} r={4} fill={color} stroke="var(--chart-bg)" strokeWidth={1.5} />
      {d.state === 'invalid' && (
        <text x={cx + 10} y={cy + 3} fontSize={9} fill="var(--signal-warning)" fontFamily="JetBrains Mono">⚠</text>
      )}
    </g>
  );
}

function Trendline({ d, xOf, yOf, extendTo, padLeft, priceTop, priceBottom }) {
  if (d.points.length < 2) return null;
  const p1 = d.points[0], p2 = d.points[1];
  const x1 = xOf(p1.x), y1 = yOf(p1.y);
  const x2 = xOf(p2.x), y2 = yOf(p2.y);
  const m = (y2 - y1) / (x2 - x1);

  const isValid = d.state === 'valid';
  const isShopping = d.state === 'discarded';
  const color = isValid ? 'var(--tool-trendline)' : isShopping ? 'var(--text-disabled)' : 'var(--signal-warning)';
  const dashed = !isValid;

  // Calcular xEnd para válidas: extender, pero clamp al rectángulo del chart.
  let xEnd = isShopping ? x2 : extendTo;
  let yEnd = isShopping ? y2 : y2 + m * (xEnd - x2);
  if (!isShopping && priceTop != null && priceBottom != null) {
    if (yEnd < priceTop) {
      xEnd = x2 + (priceTop - y2) / m;
      yEnd = priceTop;
    } else if (yEnd > priceBottom) {
      xEnd = x2 + (priceBottom - y2) / m;
      yEnd = priceBottom;
    }
  }

  // Badge: si la línea termina cerca del borde, mover el badge al inicio para no recortar
  const badgeOnRight = xEnd < extendTo - 80;
  const badgeX = badgeOnRight ? xEnd + 8 : x2 - 80;
  const badgeY = badgeOnRight ? yEnd : y2;

  return (
    <g>
      <line x1={x1} y1={y1} x2={xEnd} y2={yEnd} stroke={color} strokeWidth={isValid ? 1.6 : 1.2} strokeDasharray={dashed ? '5 4' : ''} opacity={isShopping ? 0.35 : 0.9} />
      <circle cx={x1} cy={y1} r={3.5} fill={color} stroke="var(--chart-bg)" strokeWidth={1.5} opacity={isShopping ? 0.5 : 1} />
      <circle cx={x2} cy={y2} r={3.5} fill={color} stroke="var(--chart-bg)" strokeWidth={1.5} opacity={isShopping ? 0.5 : 1} />
      {d.touches != null && !isShopping && (
        <g>
          <rect x={badgeX} y={badgeY - 9} width={isValid ? 60 : 80} height={18} rx={2} fill="var(--surface-2)" stroke={color} strokeOpacity={0.5} />
          <text x={badgeX + 4} y={badgeY + 3} fontSize={10} fill={color} fontFamily="IBM Plex Sans" fontWeight={600}>
            {isValid ? `✓ ${d.touches} toques` : `⚠ ${d.touches} toques`}
          </text>
        </g>
      )}
      {d.touchedIndices && isValid && d.touchedIndices.map((ti) => (
        <circle key={ti} cx={xOf(ti)} cy={yOf(m * (ti - p1.x) + p1.y)} r={3} fill="none" stroke={color} strokeWidth={1.5} opacity={0.6} />
      ))}
    </g>
  );
}

function HorizontalLevel({ d, yOf, extendTo, padLeft }) {
  const y = yOf(d.price);
  const color = d.kind === 'support' ? 'var(--tool-support)'
    : d.kind === 'resistance' ? 'var(--tool-resistance)'
    : d.kind === 'entry' ? 'var(--signal-info)'
    : 'var(--text-secondary)';
  const labelText = d.label ? d.label : d.price.toFixed(1);
  const labelW = Math.max(40, labelText.length * 6.5);
  return (
    <g>
      <line x1={padLeft} y1={y} x2={extendTo} y2={y} stroke={color} strokeWidth={1.5} strokeDasharray={d.kind === 'resistance' || d.kind === 'entry' ? '6 4' : ''} opacity={0.85} />
      <rect x={extendTo - labelW - 4} y={y - 8} width={labelW} height={16} rx={2} fill={color} />
      <text x={extendTo - labelW / 2 - 4} y={y + 3} textAnchor="middle" fontSize={9} fontWeight={700} fill="#0A0C10" fontFamily="JetBrains Mono">{labelText}</text>
    </g>
  );
}

function Annotation({ d, xOf, yOf }) {
  const cx = xOf(d.index);
  const cy = yOf(d.price);
  return (
    <g>
      <line x1={cx} y1={cy} x2={cx + 14} y2={cy - 20} stroke="var(--tool-annotation)" strokeWidth={1} />
      <rect x={cx + 12} y={cy - 32} width={Math.max(60, (d.text || '').length * 6.5)} height={20} rx={3} fill="var(--surface-2)" stroke="var(--tool-annotation)" strokeOpacity={0.6} />
      <text x={cx + 18} y={cy - 18} fontSize={10} fill="var(--tool-annotation)" fontFamily="IBM Plex Sans">{d.text}</text>
    </g>
  );
}

function ZoneBand({ d, yOf, extendTo, padLeft }) {
  const y1 = yOf(Math.max(d.priceHigh, d.priceLow));
  const y2 = yOf(Math.min(d.priceHigh, d.priceLow));
  const color = d.kind === 'support' ? 'var(--tool-support)' : 'var(--tool-resistance)';
  return (
    <g>
      <rect x={padLeft} y={y1} width={extendTo - padLeft} height={y2 - y1} fill={color} fillOpacity={0.08} stroke={color} strokeOpacity={0.4} strokeDasharray="4 3" />
    </g>
  );
}

window.ChartCanvas = ChartCanvas;
